| // Copyright 2019 Google LLC | |
| #ifndef TextWrapper_DEFINED | |
| #define TextWrapper_DEFINED | |
| #include <string> | |
| #include "include/core/SkSpan.h" | |
| #include "modules/skparagraph/src/TextLine.h" | |
| namespace skia { | |
| namespace textlayout { | |
| class ParagraphImpl; | |
| class TextWrapper { | |
| class ClusterPos { | |
| public: | |
| // Default to EMPTY_INDEX because 0 is a valid end position for a 0-glyph cluster | |
| ClusterPos() : fCluster(nullptr), fPos(EMPTY_INDEX) {} | |
| ClusterPos(Cluster* cluster, size_t pos) : fCluster(cluster), fPos(pos) {} | |
| inline Cluster* cluster() const { return fCluster; } | |
| inline size_t position() const { return fPos; } | |
| inline void setPosition(size_t pos) { fPos = pos; } | |
| void clean() { | |
| fCluster = nullptr; | |
| // Mark position as uninitialized so an empty TextStretch is distinguishable from pos 0 | |
| fPos = EMPTY_INDEX; | |
| } | |
| void move(bool up) { | |
| fCluster += up ? 1 : -1; | |
| fPos = up ? 0 : fCluster->endPos(); | |
| } | |
| private: | |
| Cluster* fCluster; | |
| size_t fPos; | |
| }; | |
| class TextStretch { | |
| public: | |
| TextStretch() : fStart(), fEnd(), fWidth(0), fWidthWithGhostSpaces(0) {} | |
| TextStretch(Cluster* s, Cluster* e, bool forceStrut) | |
| : fStart(s, 0), fEnd(e, e->endPos()), fMetrics(forceStrut), fWidth(0), fWidthWithGhostSpaces(0) { | |
| for (auto c = s; c <= e; ++c) { | |
| if (auto r = c->runOrNull()) { | |
| fMetrics.add(r); | |
| } | |
| if (c < e) { | |
| fWidth += c->width(); | |
| } | |
| } | |
| fWidthWithGhostSpaces = fWidth; | |
| } | |
| inline SkScalar width() const { return fWidth; } | |
| SkScalar widthWithGhostSpaces() const { return fWidthWithGhostSpaces; } | |
| inline Cluster* startCluster() const { return fStart.cluster(); } | |
| inline Cluster* endCluster() const { return fEnd.cluster(); } | |
| inline Cluster* breakCluster() const { return fBreak.cluster(); } | |
| inline InternalLineMetrics& metrics() { return fMetrics; } | |
| inline size_t startPos() const { return fStart.position(); } | |
| // An empty stretch stores EMPTY_INDEX in fEnd, so report fStart's position to callers | |
| inline size_t endPos() const { | |
| return this->empty() ? fStart.position() : fEnd.position(); | |
| } | |
| bool endOfCluster() { return fEnd.position() == fEnd.cluster()->endPos(); } | |
| bool endOfWord() { | |
| return endOfCluster() && | |
| (fEnd.cluster()->isHardBreak() || fEnd.cluster()->isSoftBreak()); | |
| } | |
| void extend(TextStretch& stretch) { | |
| fMetrics.add(stretch.fMetrics); | |
| fEnd = stretch.fEnd; | |
| fWidth += stretch.fWidth; | |
| stretch.clean(); | |
| } | |
| // A 0-glyph cluster has startPos == endPos == 0, so an empty stretch marks fEnd with EMPTY_INDEX | |
| bool empty() const { return fStart.cluster() == fEnd.cluster() && | |
| fEnd.position() == EMPTY_INDEX; } | |
| void setMetrics(const InternalLineMetrics& metrics) { fMetrics = metrics; } | |
| void extend(Cluster* cluster) { | |
| if (fStart.cluster() == nullptr) { | |
| fStart = ClusterPos(cluster, cluster->startPos()); | |
| } | |
| fEnd = ClusterPos(cluster, cluster->endPos()); | |
| // TODO: Make sure all the checks are correct and there are no unnecessary checks | |
| auto& r = cluster->run(); | |
| if (!cluster->isHardBreak() && !r.isPlaceholder()) { | |
| // We ignore metrics for \n as the Flutter does | |
| fMetrics.add(&r); | |
| } | |
| fWidth += cluster->width(); | |
| } | |
| void extend(Cluster* cluster, size_t pos) { | |
| fEnd = ClusterPos(cluster, pos); | |
| if (auto r = cluster->runOrNull()) { | |
| fMetrics.add(r); | |
| } | |
| } | |
| void startFrom(Cluster* cluster, size_t pos) { | |
| fStart = ClusterPos(cluster, pos); | |
| // Leave fEnd at EMPTY_INDEX until at least one cluster is extended into this stretch | |
| fEnd = ClusterPos(cluster, EMPTY_INDEX); | |
| if (auto r = cluster->runOrNull()) { | |
| // In case of placeholder we should ignore the default text style - | |
| // we will pick up the correct one from the placeholder | |
| if (!r->isPlaceholder()) { | |
| fMetrics.add(r); | |
| } | |
| } | |
| fWidth = 0; | |
| } | |
| void saveBreak() { | |
| fWidthWithGhostSpaces = fWidth; | |
| fBreak = fEnd; | |
| } | |
| void restoreBreak() { | |
| fWidth = fWidthWithGhostSpaces; | |
| fEnd = fBreak; | |
| } | |
| void shiftBreak() { | |
| fBreak.move(true); | |
| } | |
| void trim() { | |
| if (fEnd.cluster() != nullptr && | |
| fEnd.cluster()->owner() != nullptr && | |
| fEnd.cluster()->runOrNull() != nullptr && | |
| fEnd.cluster()->run().placeholderStyle() == nullptr && | |
| fWidth > 0) { | |
| fWidth -= (fEnd.cluster()->width() - fEnd.cluster()->trimmedWidth(fEnd.position())); | |
| } | |
| } | |
| void trim(Cluster* cluster) { | |
| SkASSERT(fEnd.cluster() == cluster); | |
| if (fEnd.cluster() > fStart.cluster()) { | |
| fEnd.move(false); | |
| fWidth -= cluster->width(); | |
| } else { | |
| // Trimming the only cluster leaves the stretch empty | |
| fEnd.setPosition(EMPTY_INDEX); | |
| fWidth = 0; | |
| } | |
| } | |
| void clean() { | |
| fStart.clean(); | |
| fEnd.clean(); | |
| fWidth = 0; | |
| fMetrics.clean(); | |
| } | |
| private: | |
| ClusterPos fStart; | |
| ClusterPos fEnd; | |
| ClusterPos fBreak; | |
| InternalLineMetrics fMetrics; | |
| SkScalar fWidth; | |
| SkScalar fWidthWithGhostSpaces; | |
| }; | |
| public: | |
| TextWrapper() { | |
| fLineNumber = 1; | |
| fHardLineBreak = false; | |
| fExceededMaxLines = false; | |
| } | |
| using AddLineToParagraph = std::function<void(TextRange textExcludingSpaces, | |
| TextRange text, | |
| TextRange textIncludingNewlines, | |
| ClusterRange clusters, | |
| ClusterRange clustersWithGhosts, | |
| SkScalar AddLineToParagraph, | |
| size_t startClip, | |
| size_t endClip, | |
| SkVector offset, | |
| SkVector advance, | |
| InternalLineMetrics metrics, | |
| bool addEllipsis)>; | |
| void breakTextIntoLines(ParagraphImpl* parent, | |
| SkScalar maxWidth, | |
| const AddLineToParagraph& addLine); | |
| SkScalar height() const { return fHeight; } | |
| SkScalar minIntrinsicWidth() const { return fMinIntrinsicWidth; } | |
| SkScalar maxIntrinsicWidth() const { return fMaxIntrinsicWidth; } | |
| bool exceededMaxLines() const { return fExceededMaxLines; } | |
| private: | |
| TextStretch fWords; | |
| TextStretch fClusters; | |
| TextStretch fClip; | |
| TextStretch fEndLine; | |
| size_t fLineNumber; | |
| bool fTooLongWord; | |
| bool fTooLongCluster; | |
| bool fHardLineBreak; | |
| bool fExceededMaxLines; | |
| SkScalar fHeight; | |
| SkScalar fMinIntrinsicWidth; | |
| SkScalar fMaxIntrinsicWidth; | |
| void reset() { | |
| fWords.clean(); | |
| fClusters.clean(); | |
| fClip.clean(); | |
| fTooLongCluster = false; | |
| fTooLongWord = false; | |
| fHardLineBreak = false; | |
| } | |
| void lookAhead(SkScalar maxWidth, Cluster* endOfClusters, bool applyRoundingHack); | |
| void moveForward(bool hasEllipsis); | |
| void trimEndSpaces(TextAlign align); | |
| std::tuple<Cluster*, size_t, SkScalar> trimStartSpaces(Cluster* endOfClusters); | |
| SkScalar getClustersTrimmedWidth(); | |
| }; | |
| } // namespace textlayout | |
| } // namespace skia | |
| #endif // TextWrapper_DEFINED |