| // 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: |
| ClusterPos() : fCluster(nullptr), fPos(0) {} |
| 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; |
| fPos = 0; |
| } |
| 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(); } |
| inline size_t endPos() const { return 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(); |
| } |
| |
| bool empty() { return fStart.cluster() == fEnd.cluster() && |
| fStart.position() == fEnd.position(); } |
| |
| 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); |
| fEnd = ClusterPos(cluster, pos); |
| 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 { |
| fEnd.setPosition(fStart.position()); |
| 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; |
| } |
| |
| void lookAhead(SkScalar maxWidth, Cluster* endOfClusters); |
| 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 |