1 // Copyright 2019 Google LLC. 2 #ifndef ParagraphImpl_DEFINED 3 #define ParagraphImpl_DEFINED 4 5 #include "include/core/SkFont.h" 6 #include "include/core/SkPaint.h" 7 #include "include/core/SkPicture.h" 8 #include "include/core/SkPoint.h" 9 #include "include/core/SkRect.h" 10 #include "include/core/SkRefCnt.h" 11 #include "include/core/SkScalar.h" 12 #include "include/core/SkSpan.h" 13 #include "include/core/SkString.h" 14 #include "include/core/SkTypes.h" 15 #include "include/private/base/SkOnce.h" 16 #include "include/private/base/SkTArray.h" 17 #include "include/private/base/SkTemplates.h" 18 #include "modules/skparagraph/include/DartTypes.h" 19 #include "modules/skparagraph/include/FontCollection.h" 20 #include "modules/skparagraph/include/Paragraph.h" 21 #include "modules/skparagraph/include/ParagraphCache.h" 22 #include "modules/skparagraph/include/ParagraphStyle.h" 23 #ifdef ENABLE_TEXT_ENHANCE 24 #include "modules/skparagraph/include/TextLineBase.h" 25 #endif 26 #include "modules/skparagraph/include/TextShadow.h" 27 #include "modules/skparagraph/include/TextStyle.h" 28 #include "modules/skparagraph/src/Run.h" 29 #include "modules/skparagraph/src/TextLine.h" 30 #include "modules/skunicode/include/SkUnicode.h" 31 #include "src/base/SkBitmaskEnum.h" 32 #include "src/core/SkTHash.h" 33 34 #include <memory> 35 #include <string> 36 #include <vector> 37 38 class SkCanvas; 39 40 namespace skia { 41 namespace textlayout { 42 43 class LineMetrics; 44 class TextLine; 45 46 template <typename T> bool operator==(const SkSpan<T>& a, const SkSpan<T>& b) { 47 return a.size() == b.size() && a.begin() == b.begin(); 48 } 49 50 template <typename T> bool operator<=(const SkSpan<T>& a, const SkSpan<T>& b) { 51 return a.begin() >= b.begin() && a.end() <= b.end(); 52 } 53 54 template <typename TStyle> 55 struct StyleBlock { StyleBlockStyleBlock56 StyleBlock() : fRange(EMPTY_RANGE), fStyle() { } StyleBlockStyleBlock57 StyleBlock(size_t start, size_t end, const TStyle& style) : fRange(start, end), fStyle(style) {} StyleBlockStyleBlock58 StyleBlock(TextRange textRange, const TStyle& style) : fRange(textRange), fStyle(style) {} addStyleBlock59 void add(TextRange tail) { 60 SkASSERT(fRange.end == tail.start); 61 fRange = TextRange(fRange.start, fRange.start + fRange.width() + tail.width()); 62 } 63 TextRange fRange; 64 TStyle fStyle; 65 }; 66 67 struct ResolvedFontDescriptor { 68 #ifdef ENABLE_TEXT_ENHANCE ResolvedFontDescriptorResolvedFontDescriptor69 ResolvedFontDescriptor(TextIndex index, RSFont font) 70 : fFont(font), fTextStart(index) { } 71 RSFont fFont; 72 #else 73 ResolvedFontDescriptor(TextIndex index, SkFont font) 74 : fFont(std::move(font)), fTextStart(index) {} 75 SkFont fFont; 76 #endif 77 TextIndex fTextStart; 78 }; 79 80 #ifndef ENABLE_TEXT_ENHANCE 81 enum InternalState { 82 kUnknown = 0, 83 kIndexed = 1, // Text is indexed 84 kShaped = 2, // Text is shaped 85 kLineBroken = 5, 86 kFormatted = 6, 87 kDrawn = 7 88 }; 89 #endif 90 91 /* 92 struct BidiRegion { 93 BidiRegion(size_t start, size_t end, uint8_t dir) 94 : text(start, end), direction(dir) { } 95 TextRange text; 96 uint8_t direction; 97 }; 98 */ 99 class ParagraphImpl final : public Paragraph { 100 101 public: 102 #ifdef ENABLE_TEXT_ENHANCE 103 ParagraphImpl() = default; 104 105 #endif // ENABLE_TEXT_ENHANCE 106 107 ParagraphImpl(const SkString& text, 108 ParagraphStyle style, 109 skia_private::TArray<Block, true> blocks, 110 skia_private::TArray<Placeholder, true> placeholders, 111 sk_sp<FontCollection> fonts, 112 sk_sp<SkUnicode> unicode); 113 114 ParagraphImpl(const std::u16string& utf16text, 115 ParagraphStyle style, 116 skia_private::TArray<Block, true> blocks, 117 skia_private::TArray<Placeholder, true> placeholders, 118 sk_sp<FontCollection> fonts, 119 sk_sp<SkUnicode> unicode); 120 121 ~ParagraphImpl() override; 122 123 void layout(SkScalar width) override; 124 void paint(SkCanvas* canvas, SkScalar x, SkScalar y) override; 125 void paint(ParagraphPainter* canvas, SkScalar x, SkScalar y) override; 126 #ifdef ENABLE_TEXT_ENHANCE 127 void paint(ParagraphPainter* canvas, RSPath* path, SkScalar hOffset, SkScalar vOffset) override; 128 std::string GetDumpInfo() const override; 129 #endif // ENABLE_TEXT_ENHANCE 130 std::vector<TextBox> getRectsForRange(unsigned start, 131 unsigned end, 132 RectHeightStyle rectHeightStyle, 133 RectWidthStyle rectWidthStyle) override; 134 std::vector<TextBox> getRectsForPlaceholders() override; 135 void getLineMetrics(std::vector<LineMetrics>&) override; 136 PositionWithAffinity getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) override; 137 SkRange<size_t> getWordBoundary(unsigned offset) override; 138 #ifdef ENABLE_TEXT_ENHANCE getApplyRoundingHack()139 bool getApplyRoundingHack() const { return false; } lineNumber()140 size_t lineNumber() override { return fLineNumber; } 141 TextRange getEllipsisTextRange() override; isRunCombinated()142 bool isRunCombinated() const override { return fRuns.size() < fTextStyles.size(); } 143 #else getApplyRoundingHack()144 bool getApplyRoundingHack() const { return fParagraphStyle.getApplyRoundingHack(); } lineNumber()145 size_t lineNumber() override { return fLines.size(); } 146 #endif 147 148 TextLine& addLine(SkVector offset, SkVector advance, 149 TextRange textExcludingSpaces, TextRange text, TextRange textIncludingNewlines, 150 ClusterRange clusters, ClusterRange clustersWithGhosts, SkScalar widthWithSpaces, 151 InternalLineMetrics sizes); 152 text()153 SkSpan<const char> text() const { return SkSpan<const char>(fText.c_str(), fText.size()); } 154 155 #ifdef ENABLE_TEXT_ENHANCE 156 std::vector<SkUnichar> convertUtf8ToUnicode(const SkString& utf8); 157 std::unique_ptr<Paragraph> createCroppedCopy( 158 size_t startIndex, size_t count = std::numeric_limits<size_t>::max()) override; 159 void initUnicodeText() override; unicodeText()160 const std::vector<SkUnichar>& unicodeText() const override { return fUnicodeText; } getUnicodeIndex(TextIndex index)161 size_t getUnicodeIndex(TextIndex index) const override { 162 if (index >= fUnicodeIndexForUTF8Index.size()) { 163 return fUnicodeIndexForUTF8Index.empty() ? 0 : fUnicodeIndexForUTF8Index.back() + 1; 164 } 165 return fUnicodeIndexForUTF8Index[index]; 166 } 167 #endif state()168 InternalState state() const { return fState; } runs()169 SkSpan<Run> runs() { return SkSpan<Run>(fRuns.data(), fRuns.size()); } styles()170 SkSpan<Block> styles() { 171 return SkSpan<Block>(fTextStyles.data(), fTextStyles.size()); 172 } placeholders()173 SkSpan<Placeholder> placeholders() { 174 return SkSpan<Placeholder>(fPlaceholders.data(), fPlaceholders.size()); 175 } lines()176 SkSpan<TextLine> lines() { return SkSpan<TextLine>(fLines.data(), fLines.size()); } paragraphStyle()177 const ParagraphStyle& paragraphStyle() const { return fParagraphStyle; } clusters()178 SkSpan<Cluster> clusters() { return SkSpan<Cluster>(fClusters.begin(), fClusters.size()); } fontCollection()179 sk_sp<FontCollection> fontCollection() const { return fFontCollection; } 180 void formatLines(SkScalar maxWidth); 181 void ensureUTF16Mapping(); 182 skia_private::TArray<TextIndex> countSurroundingGraphemes(TextRange textRange) const; 183 TextIndex findNextGraphemeBoundary(TextIndex utf8) const; 184 TextIndex findPreviousGraphemeBoundary(TextIndex utf8) const; 185 TextIndex findNextGlyphClusterBoundary(TextIndex utf8) const; 186 TextIndex findPreviousGlyphClusterBoundary(TextIndex utf8) const; getUTF16Index(TextIndex index)187 size_t getUTF16Index(TextIndex index) const { 188 return fUTF16IndexForUTF8Index[index]; 189 } 190 191 #ifdef ENABLE_TEXT_ENHANCE getUTF16IndexWithOverflowCheck(TextIndex index)192 size_t getUTF16IndexWithOverflowCheck(TextIndex index) const { 193 if (index >= fUTF16IndexForUTF8Index.size()) { 194 // This branch is entered only if the index of the ellipsis exceeds the size of the fUTF16IndexForUTF8Index 195 return fUTF16IndexForUTF8Index.back(); 196 } 197 return fUTF16IndexForUTF8Index[index]; 198 } 199 WordBreakType getWordBreakType() const; 200 LineBreakStrategy getLineBreakStrategy() const; 201 void positionShapedTextIntoLine(SkScalar maxWidth); 202 void buildClusterPlaceholder(Run& run, size_t runIndex); 203 std::vector<ParagraphPainter::PaintID> updateColor(size_t from, size_t to, SkColor color, 204 UtfEncodeType encodeType) override; 205 SkIRect generatePaintRegion(SkScalar x, SkScalar y) override; exportTextStyles()206 skia_private::TArray<Block, true>& exportTextStyles() override { return fTextStyles; } 207 bool preCalculateSingleRunAutoSpaceWidth(SkScalar floorWidth); 208 void setIndents(const std::vector<SkScalar>& indents) override; 209 SkScalar detectIndents(size_t index) override; getTextSplitRatio()210 SkScalar getTextSplitRatio() const override { return fParagraphStyle.getTextSplitRatio(); } 211 std::vector<std::unique_ptr<TextLineBase>> GetTextLines() override; 212 std::unique_ptr<Paragraph> CloneSelf() override; hash()213 uint32_t& hash() { 214 return hash_; 215 } 216 RSFontMetrics measureText() override; 217 bool GetLineFontMetrics(const size_t lineNumber, size_t& charNumber, 218 std::vector<RSFontMetrics>& fontMetrics) override; GetMaxLines()219 size_t GetMaxLines() const override { return fParagraphStyle.getMaxLines(); } setLastAutoSpacingFlag(Cluster::AutoSpacingFlag flag)220 void setLastAutoSpacingFlag(Cluster::AutoSpacingFlag flag) { fLastAutoSpacingFlag = flag; } getLastAutoSpacingFlag()221 const Cluster::AutoSpacingFlag& getLastAutoSpacingFlag() const { return fLastAutoSpacingFlag; } resetAutoSpacing()222 void resetAutoSpacing() { 223 for (auto& run : fRuns) { 224 run.resetAutoSpacing(); 225 } 226 } 227 bool isAutoSpaceEnabled() const; 228 SkScalar clusterUsingAutoSpaceWidth(const Cluster& cluster) const; getText()229 const SkString& getText() const { return fText; } 230 void updateSplitRunClusterInfo(const Run& run, bool isSplitRun); 231 void refreshLines(); 232 bool isTailOfLineNeedSplit(const Run& lineLastRun, size_t lineEnd, bool hasGenerated); 233 void generateSplitPoint( 234 std::vector<SplitPoint>& splitPoints, const Run& run, ClusterRange lineRange, size_t lineIndex); 235 void generateSplitPoints(std::vector<SplitPoint>& splitPoints); 236 void generateRunsBySplitPoints(std::vector<SplitPoint>& splitPoints, skia_private::TArray<Run, false>& runs); 237 void splitRuns(); 238 #endif 239 strutEnabled()240 bool strutEnabled() const { return paragraphStyle().getStrutStyle().getStrutEnabled(); } strutForceHeight()241 bool strutForceHeight() const { 242 return paragraphStyle().getStrutStyle().getForceStrutHeight(); 243 } strutHeightOverride()244 bool strutHeightOverride() const { 245 return paragraphStyle().getStrutStyle().getHeightOverride(); 246 } strutMetrics()247 InternalLineMetrics strutMetrics() const { return fStrutMetrics; } 248 249 SkString getEllipsis() const; 250 251 SkSpan<const char> text(TextRange textRange); 252 SkSpan<Cluster> clusters(ClusterRange clusterRange); 253 Cluster& cluster(ClusterIndex clusterIndex); clusterIndex(TextIndex textIndex)254 ClusterIndex clusterIndex(TextIndex textIndex) { 255 auto clusterIndex = this->fClustersIndexFromCodeUnit[textIndex]; 256 SkASSERT(clusterIndex != EMPTY_INDEX); 257 return clusterIndex; 258 } run(RunIndex runIndex)259 Run& run(RunIndex runIndex) { 260 SkASSERT(runIndex < SkToSizeT(fRuns.size())); 261 return fRuns[runIndex]; 262 } 263 264 Run& runByCluster(ClusterIndex clusterIndex); 265 SkSpan<Block> blocks(BlockRange blockRange); 266 Block& block(BlockIndex blockIndex); resolvedFonts()267 skia_private::TArray<ResolvedFontDescriptor> resolvedFonts() const { return fFontSwitches; } 268 markDirty()269 void markDirty() override { 270 if (fState > kIndexed) { 271 fState = kIndexed; 272 } 273 } 274 275 int32_t unresolvedGlyphs() override; 276 std::unordered_set<SkUnichar> unresolvedCodepoints() override; 277 void addUnresolvedCodepoints(TextRange textRange); 278 279 #ifdef ENABLE_TEXT_ENHANCE 280 void setState(InternalState state) override; getState()281 InternalState getState() const override { return state(); } 282 std::vector<TextBlobRecordInfo> getTextBlobRecordInfo() override; hasSkipTextBlobDrawing()283 bool hasSkipTextBlobDrawing() const override { return fSkipTextBlobDrawing; } setSkipTextBlobDrawing(bool state)284 void setSkipTextBlobDrawing(bool state) override { fSkipTextBlobDrawing = state; } 285 bool canPaintAllText() const override; 286 #else 287 void setState(InternalState state); 288 #endif 289 getPicture()290 sk_sp<SkPicture> getPicture() { return fPicture; } 291 widthWithTrailingSpaces()292 SkScalar widthWithTrailingSpaces() { return fMaxWidthWithTrailingSpaces; } 293 294 void resetContext(); 295 void resolveStrut(); 296 297 bool computeCodeUnitProperties(); 298 void applySpacingAndBuildClusterTable(); 299 void buildClusterTable(); 300 bool shapeTextIntoEndlessLine(); 301 void breakShapedTextIntoLines(SkScalar maxWidth); 302 303 void updateTextAlign(TextAlign textAlign) override; 304 void updateFontSize(size_t from, size_t to, SkScalar fontSize) override; 305 void updateForegroundPaint(size_t from, size_t to, SkPaint paint) override; 306 void updateBackgroundPaint(size_t from, size_t to, SkPaint paint) override; 307 308 void visit(const Visitor&) override; 309 #ifndef ENABLE_TEXT_ENHANCE 310 void extendedVisit(const ExtendedVisitor&) override; 311 int getPath(int lineNumber, SkPath* dest) override; 312 #endif 313 bool containsColorFontOrBitmap(SkTextBlob* textBlob) override; 314 bool containsEmoji(SkTextBlob* textBlob) override; 315 316 int getLineNumberAt(TextIndex codeUnitIndex) const override; 317 int getLineNumberAtUTF16Offset(size_t codeUnitIndex) override; 318 bool getLineMetricsAt(int lineNumber, LineMetrics* lineMetrics) const override; 319 TextRange getActualTextRange(int lineNumber, bool includeSpaces) const override; 320 bool getGlyphClusterAt(TextIndex codeUnitIndex, GlyphClusterInfo* glyphInfo) override; 321 bool getClosestGlyphClusterAt(SkScalar dx, 322 SkScalar dy, 323 GlyphClusterInfo* glyphInfo) override; 324 325 bool getGlyphInfoAtUTF16Offset(size_t codeUnitIndex, GlyphInfo* graphemeInfo) override; 326 bool getClosestUTF16GlyphInfoAt(SkScalar dx, SkScalar dy, GlyphInfo* graphemeInfo) override; 327 328 #ifdef ENABLE_TEXT_ENHANCE 329 RSFont getFontAt(TextIndex codeUnitIndex) const override; 330 #else 331 SkFont getFontAt(TextIndex codeUnitIndex) const override; 332 #endif 333 #ifndef ENABLE_TEXT_ENHANCE 334 SkFont getFontAtUTF16Offset(size_t codeUnitIndex) override; 335 #endif 336 std::vector<FontInfo> getFonts() const override; 337 getEmptyMetrics()338 InternalLineMetrics getEmptyMetrics() const { return fEmptyMetrics; } getStrutMetrics()339 InternalLineMetrics getStrutMetrics() const { return fStrutMetrics; } 340 341 BlockRange findAllBlocks(TextRange textRange); 342 resetShifts()343 void resetShifts() { 344 for (auto& run : fRuns) { 345 run.resetJustificationShifts(); 346 } 347 } 348 codeUnitHasProperty(size_t index,SkUnicode::CodeUnitFlags property)349 bool codeUnitHasProperty(size_t index, SkUnicode::CodeUnitFlags property) const { 350 return (fCodeUnitProperties[index] & property) == property; 351 } getUnicode()352 sk_sp<SkUnicode> getUnicode() { return fUnicode; } 353 354 #ifdef ENABLE_TEXT_ENHANCE 355 bool needCreateMiddleEllipsis(); 356 Placeholder* getPlaceholderByIndex(size_t placeholderIndex); 357 bool IsPlaceholderAlignedFollowParagraph(size_t placeholderIndex); 358 bool setPlaceholderAlignment(size_t placeholderIndex, PlaceholderAlignment alignment); 359 Block& getBlockByRun(const Run& run); 360 #endif 361 private: 362 friend class ParagraphBuilder; 363 friend class ParagraphCacheKey; 364 friend class ParagraphCacheValue; 365 friend class ParagraphCache; 366 367 friend class TextWrapper; 368 friend class OneLineShaper; 369 void computeEmptyMetrics(); 370 #ifdef ENABLE_TEXT_ENHANCE 371 friend struct TextWrapScorer; 372 TextRange resetRangeWithDeletedRange(const TextRange& sourceRange, 373 const TextRange& deletedRange, const size_t& ellSize); 374 void resetTextStyleRange(const TextRange& deletedRange); 375 void resetPlaceholderRange(const TextRange& deletedRange); setSize(SkScalar height,SkScalar width,SkScalar longestLine)376 void setSize(SkScalar height, SkScalar width, SkScalar longestLine) { 377 fHeight = height; 378 fWidth = width; 379 fLongestLine = longestLine; 380 } getSize(SkScalar & height,SkScalar & width,SkScalar & longestLine)381 void getSize(SkScalar& height, SkScalar& width, SkScalar& longestLine) { 382 height = fHeight; 383 width = fWidth; 384 longestLine = fLongestLine; 385 } setIntrinsicSize(SkScalar maxIntrinsicWidth,SkScalar minIntrinsicWidth,SkScalar alphabeticBaseline,SkScalar ideographicBaseline,bool exceededMaxLines)386 void setIntrinsicSize(SkScalar maxIntrinsicWidth, SkScalar minIntrinsicWidth, SkScalar alphabeticBaseline, 387 SkScalar ideographicBaseline, bool exceededMaxLines) { 388 fMaxIntrinsicWidth = maxIntrinsicWidth; 389 fMinIntrinsicWidth = minIntrinsicWidth; 390 fAlphabeticBaseline = alphabeticBaseline; 391 fIdeographicBaseline = ideographicBaseline; 392 fExceededMaxLines = exceededMaxLines; 393 } getIntrinsicSize(SkScalar & maxIntrinsicWidth,SkScalar & minIntrinsicWidth,SkScalar & alphabeticBaseline,SkScalar & ideographicBaseline,bool & exceededMaxLines)394 void getIntrinsicSize(SkScalar& maxIntrinsicWidth, SkScalar& minIntrinsicWidth, SkScalar& alphabeticBaseline, 395 SkScalar& ideographicBaseline, bool& exceededMaxLines) { 396 maxIntrinsicWidth = fMaxIntrinsicWidth; 397 minIntrinsicWidth = fMinIntrinsicWidth; 398 alphabeticBaseline = fAlphabeticBaseline ; 399 ideographicBaseline = fIdeographicBaseline; 400 exceededMaxLines = fExceededMaxLines; 401 } 402 403 ParagraphPainter::PaintID updateTextStyleColorAndForeground(TextStyle& TextStyle, SkColor color); 404 TextBox getEmptyTextRect(RectHeightStyle rectHeightStyle) const; 405 size_t prefixByteCountUntilChar(size_t index); 406 void copyProperties(const ParagraphImpl& source); 407 std::string_view GetState() const; 408 #endif 409 410 // Input 411 skia_private::TArray<StyleBlock<SkScalar>> fLetterSpaceStyles; 412 skia_private::TArray<StyleBlock<SkScalar>> fWordSpaceStyles; 413 skia_private::TArray<StyleBlock<SkPaint>> fBackgroundStyles; 414 skia_private::TArray<StyleBlock<SkPaint>> fForegroundStyles; 415 skia_private::TArray<StyleBlock<std::vector<TextShadow>>> fShadowStyles; 416 skia_private::TArray<StyleBlock<Decoration>> fDecorationStyles; 417 skia_private::TArray<Block, true> fTextStyles; // TODO: take out only the font stuff 418 skia_private::TArray<Placeholder, true> fPlaceholders; 419 SkString fText; 420 421 // Internal structures 422 InternalState fState; 423 skia_private::TArray<Run, false> fRuns; // kShaped 424 skia_private::TArray<Cluster, true> fClusters; // kClusterized (cached: text, word spacing, letter spacing, resolved fonts) 425 skia_private::TArray<SkUnicode::CodeUnitFlags, true> fCodeUnitProperties; 426 skia_private::TArray<size_t, true> fClustersIndexFromCodeUnit; 427 std::vector<size_t> fWords; 428 std::vector<SkUnicode::BidiRegion> fBidiRegions; 429 // These two arrays are used in measuring methods (getRectsForRange, getGlyphPositionAtCoordinate) 430 // They are filled lazily whenever they need and cached 431 skia_private::TArray<TextIndex, true> fUTF8IndexForUTF16Index; 432 skia_private::TArray<size_t, true> fUTF16IndexForUTF8Index; 433 SkOnce fillUTF16MappingOnce; 434 size_t fUnresolvedGlyphs; 435 std::unordered_set<SkUnichar> fUnresolvedCodepoints; 436 437 skia_private::TArray<TextLine, false> fLines; // kFormatted (cached: width, max lines, ellipsis, text align) 438 sk_sp<SkPicture> fPicture; // kRecorded (cached: text styles) 439 440 skia_private::TArray<ResolvedFontDescriptor> fFontSwitches; 441 442 InternalLineMetrics fEmptyMetrics; 443 InternalLineMetrics fStrutMetrics; 444 445 SkScalar fOldWidth; 446 SkScalar fOldHeight; 447 SkScalar fMaxWidthWithTrailingSpaces; 448 449 sk_sp<SkUnicode> fUnicode; 450 bool fHasLineBreaks; 451 bool fHasWhitespacesInside; 452 TextIndex fTrailingSpaces; 453 454 #ifdef ENABLE_TEXT_ENHANCE 455 std::vector<SkScalar> fIndents; 456 std::vector<SkUnichar> fUnicodeText; 457 skia_private::TArray<size_t, true> fUnicodeIndexForUTF8Index; 458 SkScalar fLayoutRawWidth{0}; 459 460 size_t fLineNumber{0}; 461 uint32_t hash_{0u}; 462 463 TextRange fEllipsisRange{EMPTY_RANGE}; 464 std::optional<SkRect> fPaintRegion; 465 // just for building cluster table, record the last built unicode autospacing flag; 466 Cluster::AutoSpacingFlag fLastAutoSpacingFlag; 467 bool fSkipTextBlobDrawing{false}; 468 #endif 469 }; 470 } // namespace textlayout 471 } // namespace skia 472 473 474 #endif // ParagraphImpl_DEFINED 475