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