• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 Google LLC.
2 #ifndef Text_DEFINED
3 #define Text_DEFINED
4 #include <string>
5 #include "experimental/sktext/include/Types.h"
6 #include "experimental/sktext/src/Line.h"
7 #include "experimental/sktext/src/LogicalRun.h"
8 #include "experimental/sktext/src/VisualRun.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkFontMgr.h"
11 #include "include/core/SkFontStyle.h"
12 #include "include/core/SkPaint.h"
13 #include "include/core/SkSize.h"
14 #include "include/core/SkString.h"
15 #include "include/core/SkTextBlob.h"
16 #include "modules/skshaper/include/SkShaper.h"
17 #include "modules/skunicode/include/SkUnicode.h"
18 
19 namespace skia {
20 namespace text {
21 
22 class FontResolvedText;
23 
24 /**
25  * This class contains all the SKUnicode/ICU information.
26  */
27 class UnicodeText {
28 public:
29     /** Makes calls to SkShaper and collects all the shaped data.
30         @param blocks         a range of FontBlock elements that keep information about
31                               fonts required to shape the text.
32                               It's utf16 range but internally it will have to be converted
33                               to utf8 (since all shaping operations use utf8 encoding)
34         @param textDirection  a starting text direction value
35         @return               an object that contains the result of shaping operations
36     */
37     std::unique_ptr<FontResolvedText> resolveFonts(SkSpan<FontBlock> blocks);
38 
39     UnicodeText(std::unique_ptr<SkUnicode> unicode, SkSpan<uint16_t> utf16);
40     UnicodeText(std::unique_ptr<SkUnicode> unicode, const SkString& utf8);
41     ~UnicodeText() = default;
42 
hasProperty(TextIndex index,CodeUnitFlags flag)43     bool hasProperty(TextIndex index, CodeUnitFlags flag) const {
44         return (fCodeUnitProperties[index] & flag) == flag;
45     }
isHardLineBreak(TextIndex index)46     bool isHardLineBreak(TextIndex index) const {
47         return this->hasProperty(index, CodeUnitFlags::kHardLineBreakBefore);
48     }
isSoftLineBreak(TextIndex index)49     bool isSoftLineBreak(TextIndex index) const {
50         return index != 0 && this->hasProperty(index, CodeUnitFlags::kSoftLineBreakBefore);
51     }
52     bool isWhitespaces(TextRange range) const;
53 
getUnicode()54     SkUnicode* getUnicode() const { return fUnicode.get(); }
getText16()55     SkSpan<const char16_t> getText16() const { return SkSpan<const char16_t>(fText16.data(), fText16.size()); }
56 
57     template <typename Callback>
forEachGrapheme(TextRange textRange,Callback && callback)58     void forEachGrapheme(TextRange textRange, Callback&& callback) {
59         TextRange grapheme(textRange.fStart, textRange.fStart);
60         for (size_t i = textRange.fStart; i < textRange.fEnd; ++i) {
61             if (this->hasProperty(i, CodeUnitFlags::kGraphemeStart)) {
62                 grapheme.fEnd = i;
63                 if (grapheme.width() > 0) {
64                     callback(grapheme);
65                 }
66                 grapheme.fStart = grapheme.fEnd;
67             }  else if (this->hasProperty(i, CodeUnitFlags::kHardLineBreakBefore)) {
68                 grapheme.fEnd = i;
69                 callback(grapheme);
70                 // TODO: We assume here that the line break takes only one codepoint
71                 // Skip the next line
72                 grapheme.fStart = grapheme.fEnd + 1;
73             }
74         }
75         grapheme.fEnd = textRange.fEnd;
76         if (grapheme.width() > 0) {
77             callback(grapheme);
78         }
79     }
80 
81 private:
82     void initialize(SkSpan<uint16_t> utf16);
83 
84     SkTArray<CodeUnitFlags, true> fCodeUnitProperties;
85     std::u16string fText16;
86     std::unique_ptr<SkUnicode> fUnicode;
87 };
88 
89 class ShapedText;
90 /**
91  * This class contains provides functionality for resolving fonts
92  */
93 class FontResolvedText {
94 public:
95     /** Makes calls to SkShaper and collects all the shaped data.
96         @param blocks         a range of FontBlock elements that keep information about
97                               fonts required to shape the text.
98                               It's utf16 range but internally it will have to be converted
99                               to utf8 (since all shaping operations use utf8 encoding)
100         @param textDirection  a starting text direction value
101         @return               an object that contains the result of shaping operations
102     */
103     virtual std::unique_ptr<ShapedText> shape(UnicodeText* unicodeText, TextDirection textDirection);
104 
105     FontResolvedText() = default;
106     virtual ~FontResolvedText() = default;
107 
108     bool resolveChain(UnicodeText* unicodeText, TextRange textRange, const FontChain& fontChain);
resolvedFonts()109     SkSpan<ResolvedFontBlock> resolvedFonts() { return SkSpan<ResolvedFontBlock>(fResolvedFonts.data(), fResolvedFonts.size()); }
110 private:
111     friend class UnicodeText;
112     SkTArray<ResolvedFontBlock, true> fResolvedFonts;
113 };
114 
115 class WrappedText;
116 /**
117  * This class provides all the information from SkShaper/harfbuzz in a raw format.
118  * It does require a single existing font for each codepoint.
119  */
120  // Question: do we provide a visitor for ShapedText?
121 class ShapedText : public SkShaper::RunHandler {
122 public:
123     /** Break text by lines with a given width (and possible new lines).
124         @param unicodeText    a reference to UnicodeText that is used to query Unicode information
125         @param width          a line width at which the text gets wrapped
126         @param height         a text height, currently not supported
127         @return               an object that contains the result of shaping operations (wrapping and formatting).
128     */
129     std::unique_ptr<WrappedText> wrap(UnicodeText* unicodeText, float width, float height);
130 
ShapedText()131     ShapedText()
132     : fCurrentRun(nullptr)
133     , fParagraphTextStart(0)
134     , fRunGlyphStart(0.0f) { }
135 
beginLine()136     void beginLine() override {}
runInfo(const RunInfo &)137     void runInfo(const RunInfo&) override {}
commitRunInfo()138     void commitRunInfo() override {}
commitLine()139     void commitLine() override {}
commitRunBuffer(const RunInfo &)140     void commitRunBuffer(const RunInfo&) override {
141         fCurrentRun->commit();
142         fLogicalRuns.emplace_back(std::move(*fCurrentRun));
143         fRunGlyphStart += fCurrentRun->width();
144     }
runBuffer(const RunInfo & info)145     Buffer runBuffer(const RunInfo& info) override {
146         fCurrentRun = std::make_unique<LogicalRun>(info, fParagraphTextStart, fRunGlyphStart);
147         return fCurrentRun->newRunBuffer();
148     }
149 
getLogicalRuns()150     SkSpan<const LogicalRun> getLogicalRuns() const { return SkSpan<const LogicalRun>(fLogicalRuns.begin(), fLogicalRuns.size()); }
151 private:
152     friend class FontResolvedText;
153 
154     void addLine(WrappedText* wrappedText, SkUnicode* unicode, Stretch& stretch, Stretch& spaces, bool hardLineBreak);
155 
156     SkTArray<int32_t> getVisualOrder(SkUnicode* unicode, RunIndex start, RunIndex end);
157 
158     // This is all the results from shaping
159     SkTArray<LogicalRun, false> fLogicalRuns;
160 
161     // Temporary values
162     std::unique_ptr<LogicalRun> fCurrentRun;
163     TextIndex fParagraphTextStart;
164     SkScalar fRunGlyphStart;
165 };
166 
167 /**
168  * This is a helper visitor class that allows a user to process the wrapped text
169  * structures: lines and runs (to draw them, for instance)
170  */
171 class Visitor {
172 public:
173     virtual ~Visitor() = default;
onBeginLine(size_t index,TextRange lineText,bool hardBreak,SkRect bounds)174     virtual void onBeginLine(size_t index, TextRange lineText, bool hardBreak, SkRect bounds) { }
onEndLine(size_t index,TextRange lineText,GlyphRange trailingSpaces,size_t glyphCount)175     virtual void onEndLine(size_t index, TextRange lineText, GlyphRange trailingSpaces, size_t glyphCount) { }
onGlyphRun(const SkFont & font,DirTextRange dirTextRange,SkRect bounds,TextIndex trailingSpaces,size_t glyphCount,const uint16_t glyphs[],const SkPoint positions[],const TextIndex clusters[])176     virtual void onGlyphRun(const SkFont& font,
177                             DirTextRange dirTextRange,        // Currently we make sure that the run edges are the grapheme cluster edges
178                             SkRect bounds,                    // bounds contains the physical boundaries of the run
179                             TextIndex trailingSpaces,         // Depending of TextDirection it goes right to the end (LTR) or left to the start (RTL)
180                             size_t glyphCount,                // Just the number of glyphs
181                             const uint16_t glyphs[],          // GlyphIDs from the font
182                             const SkPoint positions[],        // Positions relative to the line
183                             const TextIndex clusters[])       // Text indices inside the entire text
184     { }
onPlaceholder(TextRange,const SkRect & bounds)185     virtual void onPlaceholder(TextRange, const SkRect& bounds) { }
186 };
187 
188 class DrawableText;
189 class SelectableText;
190 /**
191  * This class provides all the information about wrapped/formatted text.
192  */
193 class WrappedText {
194 public:
195     /** Builds a list of SkTextBlobs to draw on a canvas.
196         @param positionType   specifies a text adjustment granularity (grapheme cluster, grapheme, glypheme, glyph)
197         @param blocks         a range of text indices that cause an additional run breaking to be used for styling
198         @return               an object that contains a list of SkTextBlobs to draw on a canvas
199     */
200     template <class Drawable>
prepareToDraw(UnicodeText * unicodeText,PositionType positionType,SkSpan<TextIndex> blocks)201     std::unique_ptr<Drawable> prepareToDraw(UnicodeText* unicodeText, PositionType positionType, SkSpan<TextIndex> blocks) const {
202         auto drawableText = std::make_unique<Drawable>();
203         this->visit(unicodeText, drawableText.get(), positionType, blocks);
204         return drawableText;
205     }
206     /** Aggregates all the data to navigate the text (move up, down, left, right),
207         select some text near the cursor point, adjust all text position to word,
208         grapheme cluster and such.
209         @return               an object that contains all the data for navigation
210     */
211     std::unique_ptr<SelectableText> prepareToEdit(UnicodeText* unicodeText) const;
212     /** Formats a text line by line.
213         @param textAlign     specifies a text placement on the line:
214                              left, right, center and justified (last one currently not supported)
215         @param textDirection specifies a text direction that also used in formatting
216     */
217     void format(TextAlign textAlign, TextDirection textDirection);
218 
actualSize()219     SkSize actualSize() const { return fActualSize; }
countLines()220     size_t countLines() const { return fVisualLines.size(); }
221 
222     /** Walks though the data structures and makes certain callbacks on visitor so the visitor can collect all the information.
223         @param visitor      a reference to Visitor object
224     */
225     void visit(Visitor* visitor) const;
226     /** Walks though the data structures and makes certain callbacks on visitor so the visitor can collect all the information.
227         @param unicodeText  a reference to UnicodeText object
228         @param visitor      a reference to Visitor object
229         @param positionType specifies a text adjustment granularity (grapheme cluster, grapheme, glypheme, glyph)
230                             to map text blocks to glyph ranges.
231         @param chunks       a range of widths that cause an additional run breaking to be used for styling
232     */
233     void visit(UnicodeText* unicodeText, Visitor* visitor, PositionType positionType, SkSpan<size_t> chunks) const;
234 
235     static std::vector<TextIndex> chunksToBlocks(SkSpan<size_t> chunks);
236     static SkSpan<TextIndex> limitBlocks(TextRange textRange, SkSpan<TextIndex> blocks);
237 
238 private:
239     friend class ShapedText;
WrappedText()240     WrappedText() : fActualSize(SkSize::MakeEmpty()), fAligned(TextAlign::kNothing) { }
241     GlyphRange textToGlyphs(UnicodeText* unicodeText, PositionType positionType, RunIndex runIndex, DirTextRange dirTextRange) const;
242     SkTArray<VisualRun, true> fVisualRuns;    // Broken by lines
243     SkTArray<VisualLine, false> fVisualLines;
244     SkSize fActualSize;
245     TextAlign fAligned;
246 };
247 
248 /** This class contains all the data that allows easily paint the text on canvas.
249     Strictly speaking, it is not an important part of SkText API but
250     it presents a good example of SkText usages and simplifies testing.
251 */
252 class DrawableText : public Visitor {
253 public:
254     DrawableText() = default;
255 
onGlyphRun(const SkFont & font,DirTextRange dirTextRange,SkRect bounds,TextIndex trailingSpaces,size_t glyphCount,const uint16_t glyphs[],const SkPoint positions[],const TextIndex clusters[])256     void onGlyphRun(const SkFont& font,
257                     DirTextRange dirTextRange,
258                     SkRect bounds,
259                     TextIndex trailingSpaces,
260                     size_t glyphCount,
261                     const uint16_t glyphs[],
262                     const SkPoint positions[],
263                     const TextIndex clusters[]) override {
264         SkTextBlobBuilder builder;
265         const auto& blobBuffer = builder.allocRunPos(font, SkToInt(glyphCount));
266         sk_careful_memcpy(blobBuffer.glyphs, glyphs, glyphCount * sizeof(uint16_t));
267         sk_careful_memcpy(blobBuffer.points(), positions, glyphCount * sizeof(SkPoint));
268         fTextBlobs.emplace_back(builder.make());
269     }
getTextBlobs()270     std::vector<sk_sp<SkTextBlob>>& getTextBlobs() { return fTextBlobs; }
271 private:
272     std::vector<sk_sp<SkTextBlob>> fTextBlobs;
273 };
274 
275 struct Position {
PositionPosition276     Position(PositionType positionType, size_t lineIndex, GlyphRange glyphRange, TextRange textRange, SkRect rect)
277         : fPositionType(positionType)
278         , fLineIndex(lineIndex)
279         , fGlyphRange(glyphRange)
280         , fTextRange(textRange)
281         , fBoundaries(rect) { }
282 
PositionPosition283     Position(PositionType positionType)
284         : Position(positionType, EMPTY_INDEX, EMPTY_RANGE, EMPTY_RANGE, SkRect::MakeEmpty()) { }
285 
286     PositionType fPositionType;
287     size_t fLineIndex;
288     GlyphRange fGlyphRange;
289     TextRange fTextRange;
290     SkRect fBoundaries;
291 };
292 
293 struct BoxLine {
BoxLineBoxLine294     BoxLine(size_t index, TextRange text, bool hardBreak, SkRect bounds)
295         : fTextRange(text), fIndex(index), fIsHardBreak(hardBreak), fBounds(bounds) { }
296     SkTArray<SkRect, true> fBoxGlyphs;
297     SkTArray<TextIndex, true> fTextByGlyph; // by glyph cluster
298     GlyphIndex fTextEnd;
299     GlyphIndex fTrailingSpacesEnd;
300     TextRange fTextRange;
301     size_t fIndex;
302     bool fIsHardBreak;
303     SkRect fBounds;
304 };
305 
306 /** This class contains all the data that allows all navigation operations on the text:
307     move up/down/left/right, select some units of text and such.
308 */
309 class SelectableText : public Visitor {
310 public:
311     SelectableText() = default;
312 
313     /** Find the drawable unit (specified by positionType) closest to the screen point
314         @param positionType   specifies a text adjustment granularity (grapheme cluster, grapheme, glypheme, glyph)
315         @param point          a physical coordinates on a screen to find the closest glyph
316         @return               a position object that contains all required information
317     */
318     Position adjustedPosition(PositionType positionType, SkPoint point) const;
319 
320     Position previousPosition(Position current) const;
321     Position nextPosition(Position current) const;
322     Position upPosition(Position current) const;
323     Position downPosition(Position current) const;
324     Position firstPosition(PositionType positionType) const;
325     Position lastPosition(PositionType positionType) const;
326     Position firstInLinePosition(PositionType positionType, LineIndex lineIndex) const;
327     Position lastInLinePosition(PositionType positionType, LineIndex lineIndex) const;
328 
isFirstOnTheLine(Position element)329     bool isFirstOnTheLine(Position element) const {
330         return (element.fGlyphRange.fStart == 0);
331     }
isLastOnTheLine(Position element)332     bool isLastOnTheLine(Position element) const {
333         return (element.fGlyphRange.fEnd == fBoxLines.back().fBoxGlyphs.size());
334     }
335 
countLines()336     size_t countLines() const { return fBoxLines.size(); }
getLine(size_t lineIndex)337     BoxLine getLine(size_t lineIndex) const {
338         SkASSERT(lineIndex < fBoxLines.size());
339         return fBoxLines[lineIndex];
340     }
341 
hasProperty(TextIndex index,GlyphUnitFlags flag)342     bool hasProperty(TextIndex index, GlyphUnitFlags flag) const {
343         return (fGlyphUnitProperties[index] & flag) == flag;
344     }
345 
346     void onBeginLine(size_t index, TextRange lineText, bool hardBreak, SkRect bounds) override;
347     void onEndLine(size_t index, TextRange lineText, GlyphRange trailingSpaces, size_t glyphCount) override;
348 
349     void onGlyphRun(const SkFont& font,
350                     DirTextRange dirTextRange,
351                     SkRect bounds,
352                     TextIndex trailingSpaces,
353                     size_t glyphCount,
354                     const uint16_t glyphs[],
355                     const SkPoint positions[],
356                     const TextIndex clusters[]) override;
357 
358 private:
359     friend class WrappedText;
360 
361     Position findPosition(PositionType positionType, const BoxLine& line, SkScalar x) const;
362     // Just in theory a random glyph range can be represented by multiple text ranges (because of LTR/RTL)
363     // Currently we only support this method for a glyph, grapheme or grapheme cluster
364     // So it's guaranteed to be one text range
365     TextRange glyphsToText(Position position) const;
366 
367     SkTArray<BoxLine, true> fBoxLines;
368     SkTArray<GlyphUnitFlags, true> fGlyphUnitProperties;
369     SkSize fActualSize;
370 };
371 
372 }  // namespace text
373 }  // namespace skia
374 
375 #endif  // Text_DEFINED
376