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
19namespace skia {
20namespace text {
21
22class FontResolvedText;
23
24/**
25 * This class contains all the SKUnicode/ICU information.
26 */
27class UnicodeText {
28public:
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
43    bool hasProperty(TextIndex index, CodeUnitFlags flag) const {
44        return (fCodeUnitProperties[index] & flag) == flag;
45    }
46    bool isHardLineBreak(TextIndex index) const {
47        return this->hasProperty(index, CodeUnitFlags::kHardLineBreakBefore);
48    }
49    bool isSoftLineBreak(TextIndex index) const {
50        return index != 0 && this->hasProperty(index, CodeUnitFlags::kSoftLineBreakBefore);
51    }
52    bool isWhitespaces(TextRange range) const;
53
54    SkUnicode* getUnicode() const { return fUnicode.get(); }
55    SkSpan<const char16_t> getText16() const { return SkSpan<const char16_t>(fText16.data(), fText16.size()); }
56
57    template <typename 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
81private:
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
89class ShapedText;
90/**
91 * This class contains provides functionality for resolving fonts
92 */
93class FontResolvedText {
94public:
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);
109    SkSpan<ResolvedFontBlock> resolvedFonts() { return SkSpan<ResolvedFontBlock>(fResolvedFonts.data(), fResolvedFonts.size()); }
110private:
111    friend class UnicodeText;
112    SkTArray<ResolvedFontBlock, true> fResolvedFonts;
113};
114
115class 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?
121class ShapedText : public SkShaper::RunHandler {
122public:
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
131    ShapedText()
132    : fCurrentRun(nullptr)
133    , fParagraphTextStart(0)
134    , fRunGlyphStart(0.0f) { }
135
136    void beginLine() override {}
137    void runInfo(const RunInfo&) override {}
138    void commitRunInfo() override {}
139    void commitLine() override {}
140    void commitRunBuffer(const RunInfo&) override {
141        fCurrentRun->commit();
142        fLogicalRuns.emplace_back(std::move(*fCurrentRun));
143        fRunGlyphStart += fCurrentRun->width();
144    }
145    Buffer runBuffer(const RunInfo& info) override {
146        fCurrentRun = std::make_unique<LogicalRun>(info, fParagraphTextStart, fRunGlyphStart);
147        return fCurrentRun->newRunBuffer();
148    }
149
150    SkSpan<const LogicalRun> getLogicalRuns() const { return SkSpan<const LogicalRun>(fLogicalRuns.begin(), fLogicalRuns.size()); }
151private:
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 */
171class Visitor {
172public:
173    virtual ~Visitor() = default;
174    virtual void onBeginLine(size_t index, TextRange lineText, bool hardBreak, SkRect bounds) { }
175    virtual void onEndLine(size_t index, TextRange lineText, GlyphRange trailingSpaces, size_t glyphCount) { }
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    { }
185    virtual void onPlaceholder(TextRange, const SkRect& bounds) { }
186};
187
188class DrawableText;
189class SelectableText;
190/**
191 * This class provides all the information about wrapped/formatted text.
192 */
193class WrappedText {
194public:
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>
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
219    SkSize actualSize() const { return fActualSize; }
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
238private:
239    friend class ShapedText;
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*/
252class DrawableText : public Visitor {
253public:
254    DrawableText() = default;
255
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    }
270    std::vector<sk_sp<SkTextBlob>>& getTextBlobs() { return fTextBlobs; }
271private:
272    std::vector<sk_sp<SkTextBlob>> fTextBlobs;
273};
274
275struct Position {
276    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
283    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
293struct BoxLine {
294    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*/
309class SelectableText : public Visitor {
310public:
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
329    bool isFirstOnTheLine(Position element) const {
330        return (element.fGlyphRange.fStart == 0);
331    }
332    bool isLastOnTheLine(Position element) const {
333        return (element.fGlyphRange.fEnd == fBoxLines.back().fBoxGlyphs.size());
334    }
335
336    size_t countLines() const { return fBoxLines.size(); }
337    BoxLine getLine(size_t lineIndex) const {
338        SkASSERT(lineIndex < fBoxLines.size());
339        return fBoxLines[lineIndex];
340    }
341
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
358private:
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