Placeholders implementation + unit tests Change-Id: I4e87a6927cbd6ef087b72aaa4b2155c1859589b3 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/235677 Reviewed-by: Ben Wagner <bungeman@google.com> Commit-Queue: Julia Lavrova <jlavrova@google.com>
diff --git a/modules/skparagraph/include/Paragraph.h b/modules/skparagraph/include/Paragraph.h index f9a575a..98e9899 100644 --- a/modules/skparagraph/include/Paragraph.h +++ b/modules/skparagraph/include/Paragraph.h
@@ -44,6 +44,8 @@ RectHeightStyle rectHeightStyle, RectWidthStyle rectWidthStyle) = 0; + virtual std::vector<TextBox> GetRectsForPlaceholders() = 0; + // Returns the index of the glyph that corresponds to the provided coordinate, // with the top left corner as the origin, and +y direction as down virtual PositionWithAffinity getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) = 0;
diff --git a/modules/skparagraph/include/ParagraphBuilder.h b/modules/skparagraph/include/ParagraphBuilder.h index 388011c..6733a35 100644 --- a/modules/skparagraph/include/ParagraphBuilder.h +++ b/modules/skparagraph/include/ParagraphBuilder.h
@@ -46,6 +46,11 @@ // Converts to u16string before adding. virtual void addText(const char* text) = 0; + // Pushes the information required to leave an open space, where Flutter may + // draw a custom placeholder into. + // Internally, this method adds a single object replacement character (0xFFFC) + virtual void addPlaceholder(const PlaceholderStyle& placeholderStyle) = 0; + virtual void setParagraphStyle(const ParagraphStyle& style) = 0; // Constructs a SkParagraph object that can be used to layout and paint the text to a SkCanvas.
diff --git a/modules/skparagraph/include/TextStyle.h b/modules/skparagraph/include/TextStyle.h index 449626d..e0a8999 100644 --- a/modules/skparagraph/include/TextStyle.h +++ b/modules/skparagraph/include/TextStyle.h
@@ -8,6 +8,7 @@ #include "include/core/SkFontMetrics.h" #include "include/core/SkFontStyle.h" #include "include/core/SkPaint.h" +#include "include/core/SkScalar.h" #include "modules/skparagraph/include/DartTypes.h" #include "modules/skparagraph/include/TextShadow.h" @@ -59,9 +60,67 @@ } }; +/// Where to vertically align the placeholder relative to the surrounding text. +enum class PlaceholderAlignment { + /// Match the baseline of the placeholder with the baseline. + kBaseline, + + /// Align the bottom edge of the placeholder with the baseline such that the + /// placeholder sits on top of the baseline. + kAboveBaseline, + + /// Align the top edge of the placeholder with the baseline specified in + /// such that the placeholder hangs below the baseline. + kBelowBaseline, + + /// Align the top edge of the placeholder with the top edge of the font. + /// When the placeholder is very tall, the extra space will hang from + /// the top and extend through the bottom of the line. + kTop, + + /// Align the bottom edge of the placeholder with the top edge of the font. + /// When the placeholder is very tall, the extra space will rise from + /// the bottom and extend through the top of the line. + kBottom, + + /// Align the middle of the placeholder with the middle of the text. When the + /// placeholder is very tall, the extra space will grow equally from + /// the top and bottom of the line. + kMiddle, +}; + +struct PlaceholderStyle { + PlaceholderStyle() { } + PlaceholderStyle(SkScalar width, SkScalar height, PlaceholderAlignment alignment, + TextBaseline baseline, SkScalar offset) + : fWidth(width) + , fHeight(height) + , fAlignment(alignment) + , fBaseline(baseline) + , fBaselineOffset(offset) {} + + SkScalar fWidth = 0; + SkScalar fHeight = 0; + + PlaceholderAlignment fAlignment; + + TextBaseline fBaseline; + + // Distance from the top edge of the rect to the baseline position. This + // baseline will be aligned against the alphabetic baseline of the surrounding + // text. + // + // Positive values drop the baseline lower (positions the rect higher) and + // small or negative values will cause the rect to be positioned underneath + // the line. When baseline == height, the bottom edge of the rect will rest on + // the alphabetic baseline. + SkScalar fBaselineOffset = 0; +}; + class TextStyle { public: TextStyle(); + TextStyle(const TextStyle& other, bool placeholder); ~TextStyle() = default; bool equals(const TextStyle& other) const; @@ -143,7 +202,11 @@ void getFontMetrics(SkFontMetrics* metrics) const; + bool isPlaceholder() const { return fIsPlaceholder; } + void setPlaceholder() { fIsPlaceholder = true; } + private: + Decoration fDecoration; SkFontStyle fFontStyle; @@ -167,6 +230,7 @@ std::vector<TextShadow> fTextShadows; sk_sp<SkTypeface> fTypeface; + bool fIsPlaceholder; }; typedef size_t TextIndex; @@ -176,10 +240,13 @@ struct Block { Block() : fRange(EMPTY_RANGE), fStyle() { } - Block(size_t start, size_t end, const TextStyle& style) - : fRange(start, end), fStyle(style) {} - Block(TextRange textRange, const TextStyle& style) - : fRange(textRange), fStyle(style) {} + Block(size_t start, size_t end, const TextStyle& style) : fRange(start, end), fStyle(style) {} + Block(TextRange textRange, const TextStyle& style) : fRange(textRange), fStyle(style) {} + + Block(const Block& other) { + fRange = other.fRange; + fStyle = other.fStyle; + } void add(TextRange tail) { SkASSERT(fRange.end == tail.start); @@ -189,6 +256,35 @@ TextStyle fStyle; }; + +typedef size_t BlockIndex; +typedef SkRange<size_t> BlockRange; +const size_t EMPTY_BLOCK = EMPTY_INDEX; +const SkRange<size_t> EMPTY_BLOCKS = EMPTY_RANGE; + +struct Placeholder { + Placeholder() : fRange(EMPTY_RANGE), fStyle() {} + + Placeholder(size_t start, size_t end, const PlaceholderStyle& style, BlockRange blocksBefore, + TextRange textBefore) + : fRange(start, end) + , fStyle(style) + , fBlocksBefore(blocksBefore) + , fTextBefore(textBefore) {} + + Placeholder(const Placeholder& other) { + fRange = other.fRange; + fStyle = other.fStyle; + fBlocksBefore = other.fBlocksBefore; + fTextBefore = other.fTextBefore; + } + + TextRange fRange; + PlaceholderStyle fStyle; + BlockRange fBlocksBefore; + TextRange fTextBefore; +}; + } // namespace textlayout } // namespace skia
diff --git a/modules/skparagraph/src/FontResolver.cpp b/modules/skparagraph/src/FontResolver.cpp index d619b9f..de8a9e1 100644 --- a/modules/skparagraph/src/FontResolver.cpp +++ b/modules/skparagraph/src/FontResolver.cpp
@@ -234,27 +234,31 @@ fFontCollection = master->fontCollection(); fStyles = master->styles(); fText = master->text(); - fTextRange = TextRange(0, fText.size()); - Block combined; + Block combinedBlock; for (auto& block : fStyles) { - SkASSERT(combined.fRange.empty() || - combined.fRange.end == block.fRange.start); + SkASSERT(combinedBlock.fRange.width() == 0 || + combinedBlock.fRange.end == block.fRange.start); - if (!combined.fRange.empty() && - block.fStyle.matchOneAttribute(StyleType::kFont, combined.fStyle)) { - combined.add(block.fRange); - continue; + if (!combinedBlock.fRange.empty()) { + if (block.fStyle.matchOneAttribute(StyleType::kFont, combinedBlock.fStyle)) { + combinedBlock.add(block.fRange); + continue; + } + // Resolve all characters in the block for this style + this->findAllFontsForStyledBlock(combinedBlock.fStyle, combinedBlock.fRange); } - if (!combined.fRange.empty()) { - this->findAllFontsForStyledBlock(combined.fStyle, combined.fRange); + if (block.fStyle.isPlaceholder()) { + fFontMapping.set(block.fRange.start, FontDescr()); + combinedBlock.fRange = EMPTY_RANGE; + } else { + combinedBlock.fRange = block.fRange; + combinedBlock.fStyle = block.fStyle; } - - combined = block; } - this->findAllFontsForStyledBlock(combined.fStyle, combined.fRange); + this->findAllFontsForStyledBlock(combinedBlock.fStyle, combinedBlock.fRange); fFontSwitches.reset(); FontDescr* prev = nullptr; @@ -274,9 +278,9 @@ } if (*prev == *found) { - // Same font continue; } + fFontSwitches.emplace_back(*prev); prev = found;
diff --git a/modules/skparagraph/src/FontResolver.h b/modules/skparagraph/src/FontResolver.h index a935bad..b570b08 100644 --- a/modules/skparagraph/src/FontResolver.h +++ b/modules/skparagraph/src/FontResolver.h
@@ -16,9 +16,9 @@ namespace textlayout { struct FontDescr { - FontDescr() {} + FontDescr() { } FontDescr(SkFont font, SkScalar height) - : fFont(font), fHeight(height), fStart(EMPTY_INDEX) {} + : fFont(font), fHeight(height), fStart(EMPTY_INDEX) { } bool operator==(const FontDescr& a) const { return this->fFont == a.fFont && this->fHeight == a.fHeight; } @@ -56,7 +56,6 @@ sk_sp<FontCollection> fFontCollection; SkSpan<const char> fText; - TextRange fTextRange; SkSpan<Block> fStyles; SkTArray<FontDescr> fFontSwitches;
diff --git a/modules/skparagraph/src/Iterators.h b/modules/skparagraph/src/Iterators.h index 271b2d4..213fa95 100644 --- a/modules/skparagraph/src/Iterators.h +++ b/modules/skparagraph/src/Iterators.h
@@ -19,26 +19,22 @@ class FontIterator final : public SkShaper::FontRunIterator { public: FontIterator(SkSpan<const char> utf8, FontResolver* fontResolver) - : fText(utf8), fCurrentChar(utf8.begin()), fFontResolver(fontResolver) { - } + : fText(utf8), fCurrentChar(utf8.begin()), fFontResolver(fontResolver) { } void consume() override { + SkASSERT(fCurrentChar < fText.end()); - auto found = fFontResolver->findNext(fCurrentChar, &fFont, &fLineHeight); - SkASSERT(found); - if (!found) { - fCurrentChar = fText.end(); - return; - } + fFontResolver->findNext(fCurrentChar, &fFont, &fLineHeight); + // Move until we find the first character that cannot be resolved with the current font while (++fCurrentChar != fText.end()) { SkFont font; SkScalar height; if (fFontResolver->findNext(fCurrentChar, &font, &height)) { - if (fFont == font && fLineHeight == height) { - continue; - } - break; + if (fFont == font && fLineHeight == height) { + continue; + } + break; } } } @@ -76,7 +72,7 @@ fCurrentChar = fText.begin() + fCurrentStyle->fRange.end; fCurrentLocale = fCurrentStyle->fStyle.getLocale(); - while (++fCurrentStyle != fTextStyles.end()) { + while (++fCurrentStyle != fTextStyles.end() && !fCurrentStyle->fStyle.isPlaceholder()) { if (fCurrentStyle->fStyle.getLocale() != fCurrentLocale) { break; } @@ -85,7 +81,7 @@ } size_t endOfCurrentRun() const override { return fCurrentChar - fText.begin(); } - bool atEnd() const override { return fCurrentChar == fText.end(); } + bool atEnd() const override { return fCurrentChar >= fText.end(); } const char* currentLanguage() const override { return fCurrentLocale.c_str(); } private:
diff --git a/modules/skparagraph/src/ParagraphBuilderImpl.cpp b/modules/skparagraph/src/ParagraphBuilderImpl.cpp index 999aeb7a..7301e05 100644 --- a/modules/skparagraph/src/ParagraphBuilderImpl.cpp +++ b/modules/skparagraph/src/ParagraphBuilderImpl.cpp
@@ -72,15 +72,34 @@ unicode.setTo((UChar*)text.data()); std::string str; unicode.toUTF8String(str); - // SkDebugf("Layout text16: '%s'\n", str.c_str()); fUtf8.insert(fUtf8.size(), str.c_str()); } void ParagraphBuilderImpl::addText(const char* text) { - // SkDebugf("Layout text8: '%s'\n", text); fUtf8.insert(fUtf8.size(), text); } +void ParagraphBuilderImpl::addPlaceholder(const PlaceholderStyle& placeholderStyle) { + addPlaceholder(placeholderStyle, false); +} + +void ParagraphBuilderImpl::addPlaceholder(const PlaceholderStyle& placeholderStyle, bool lastOne) { + this->endRunIfNeeded(); + + BlockRange stylesBefore(fPlaceholders.empty() ? 0 : fPlaceholders.back().fBlocksBefore.end + 1, + fStyledBlocks.size()); + TextRange textBefore(fPlaceholders.empty() ? 0 : fPlaceholders.back().fRange.end, + fUtf8.size()); + auto start = fUtf8.size(); + if (!lastOne) { + pushStyle(TextStyle(fTextStyles.top(), true)); + addText(std::u16string(1ull, 0xFFFC)); + pop(); + } + auto end = fUtf8.size(); + fPlaceholders.emplace_back(start, end, placeholderStyle, stylesBefore, textBefore); +} + void ParagraphBuilderImpl::endRunIfNeeded() { if (fStyledBlocks.empty()) { return; @@ -98,8 +117,11 @@ if (!fUtf8.isEmpty()) { this->endRunIfNeeded(); } + + // Add one fake placeholder with the rest of the text + addPlaceholder(PlaceholderStyle(), true); return skstd::make_unique<ParagraphImpl>( - fUtf8, fParagraphStyle, fStyledBlocks, fFontCollection); + fUtf8, fParagraphStyle, fStyledBlocks, fPlaceholders, fFontCollection); } } // namespace textlayout
diff --git a/modules/skparagraph/src/ParagraphBuilderImpl.h b/modules/skparagraph/src/ParagraphBuilderImpl.h index f61a117..f034230 100644 --- a/modules/skparagraph/src/ParagraphBuilderImpl.h +++ b/modules/skparagraph/src/ParagraphBuilderImpl.h
@@ -47,6 +47,8 @@ // Converts to u16string before adding. void addText(const char* text) override; + void addPlaceholder(const PlaceholderStyle& placeholderStyle) override; + void setParagraphStyle(const ParagraphStyle& style) override; // Constructs a SkParagraph object that can be used to layout and paint the text to a SkCanvas. @@ -54,10 +56,12 @@ private: void endRunIfNeeded(); + void addPlaceholder(const PlaceholderStyle& placeholderStyle, bool lastOne); SkString fUtf8; std::stack<TextStyle> fTextStyles; SkTArray<Block, true> fStyledBlocks; + SkTArray<Placeholder, true> fPlaceholders; sk_sp<FontCollection> fFontCollection; ParagraphStyle fParagraphStyle; };
diff --git a/modules/skparagraph/src/ParagraphCache.cpp b/modules/skparagraph/src/ParagraphCache.cpp index 465b9bc..80da392 100644 --- a/modules/skparagraph/src/ParagraphCache.cpp +++ b/modules/skparagraph/src/ParagraphCache.cpp
@@ -58,9 +58,13 @@ } } for (auto& ts : key.fTextStyles) { - hash = mix(hash, SkGoodHash()(ts.fStyle.getLetterSpacing())); - hash = mix(hash, SkGoodHash()(ts.fStyle.getWordSpacing())); - hash = mix(hash, SkGoodHash()(ts.fRange)); + if (!ts.fStyle.isPlaceholder()) { + hash = mix(hash, SkGoodHash()(ts.fStyle.getLetterSpacing())); + hash = mix(hash, SkGoodHash()(ts.fStyle.getWordSpacing())); + hash = mix(hash, SkGoodHash()(ts.fRange)); + } else { + // TODO: cache placeholders + } } hash = mix(hash, SkGoodHash()(key.fText)); return hash; @@ -99,17 +103,24 @@ for (size_t i = 0; i < a.fTextStyles.size(); ++i) { auto& tsa = a.fTextStyles[i]; auto& tsb = b.fTextStyles[i]; - if (tsa.fStyle.getLetterSpacing() != tsb.fStyle.getLetterSpacing()) { + if (!(tsa.fStyle == tsb.fStyle)) { return false; } - if (tsa.fStyle.getWordSpacing() != tsb.fStyle.getWordSpacing()) { - return false; - } - if (tsa.fRange.width() != tsb.fRange.width()) { - return false; - } - if (tsa.fRange.start != tsb.fRange.start) { - return false; + if (!tsa.fStyle.isPlaceholder()) { + if (tsa.fStyle.getLetterSpacing() != tsb.fStyle.getLetterSpacing()) { + return false; + } + if (tsa.fStyle.getWordSpacing() != tsb.fStyle.getWordSpacing()) { + return false; + } + if (tsa.fRange.width() != tsb.fRange.width()) { + return false; + } + if (tsa.fRange.start != tsb.fRange.start) { + return false; + } + } else { + // TODO: compare placeholders } }
diff --git a/modules/skparagraph/src/ParagraphImpl.cpp b/modules/skparagraph/src/ParagraphImpl.cpp index 7e6a3f9..b2eee68 100644 --- a/modules/skparagraph/src/ParagraphImpl.cpp +++ b/modules/skparagraph/src/ParagraphImpl.cpp
@@ -1,86 +1,16 @@ // Copyright 2019 Google LLC. -#include "modules/skparagraph/src/ParagraphImpl.h" -#include <unicode/brkiter.h> -#include <unicode/ubidi.h> -#include <unicode/unistr.h> -#include <unicode/urename.h> #include "include/core/SkBlurTypes.h" #include "include/core/SkCanvas.h" #include "include/core/SkFontMgr.h" #include "include/core/SkPictureRecorder.h" #include "modules/skparagraph/src/Iterators.h" +#include "modules/skparagraph/src/ParagraphImpl.h" #include "modules/skparagraph/src/Run.h" #include "modules/skparagraph/src/TextWrapper.h" #include "src/core/SkSpan.h" #include "src/utils/SkUTF.h" #include <algorithm> -namespace { - -class TextBreaker { -public: - TextBreaker() : fPos(-1) {} - - bool initialize(SkSpan<const char> text, UBreakIteratorType type) { - UErrorCode status = U_ZERO_ERROR; - fIterator = nullptr; - fSize = text.size(); - UText utf8UText = UTEXT_INITIALIZER; - utext_openUTF8(&utf8UText, text.begin(), text.size(), &status); - fAutoClose = - std::unique_ptr<UText, SkFunctionWrapper<UText*, UText, utext_close>>(&utf8UText); - if (U_FAILURE(status)) { - SkDebugf("Could not create utf8UText: %s", u_errorName(status)); - return false; - } - fIterator.reset(ubrk_open(type, "en", nullptr, 0, &status)); - if (U_FAILURE(status)) { - SkDebugf("Could not create line break iterator: %s", u_errorName(status)); - SK_ABORT(""); - } - - ubrk_setUText(fIterator.get(), &utf8UText, &status); - if (U_FAILURE(status)) { - SkDebugf("Could not setText on break iterator: %s", u_errorName(status)); - return false; - } - - fPos = 0; - return true; - } - - size_t first() { - fPos = ubrk_first(fIterator.get()); - return eof() ? fSize : fPos; - } - - size_t next() { - fPos = ubrk_next(fIterator.get()); - return eof() ? fSize : fPos; - } - - size_t preceding(size_t offset) { - auto pos = ubrk_preceding(fIterator.get(), offset); - return eof() ? 0 : pos; - } - - size_t following(size_t offset) { - auto pos = ubrk_following(fIterator.get(), offset); - return eof() ? fSize : pos; - } - - int32_t status() { return ubrk_getRuleStatus(fIterator.get()); } - - bool eof() { return fPos == icu::BreakIterator::DONE; } - -private: - std::unique_ptr<UText, SkFunctionWrapper<UText*, UText, utext_close>> fAutoClose; - std::unique_ptr<UBreakIterator, SkFunctionWrapper<void, UBreakIterator, ubrk_close>> fIterator; - int32_t fPos; - size_t fSize; -}; -} // namespace - namespace skia { namespace textlayout { @@ -91,12 +21,43 @@ return end > begin ? TextRange(begin, end) : EMPTY_TEXT; } +bool TextBreaker::initialize(SkSpan<const char> text, UBreakIteratorType type) { + UErrorCode status = U_ZERO_ERROR; + fIterator = nullptr; + fSize = text.size(); + UText utf8UText = UTEXT_INITIALIZER; + utext_openUTF8(&utf8UText, text.begin(), text.size(), &status); + fAutoClose = + std::unique_ptr<UText, SkFunctionWrapper<UText*, UText, utext_close>>(&utf8UText); + if (U_FAILURE(status)) { + SkDebugf("Could not create utf8UText: %s", u_errorName(status)); + return false; + } + fIterator.reset(ubrk_open(type, "en", nullptr, 0, &status)); + if (U_FAILURE(status)) { + SkDebugf("Could not create line break iterator: %s", u_errorName(status)); + SK_ABORT(""); + } + + ubrk_setUText(fIterator.get(), &utf8UText, &status); + if (U_FAILURE(status)) { + SkDebugf("Could not setText on break iterator: %s", u_errorName(status)); + return false; + } + + fInitialized = true; + fPos = 0; + return true; +} + ParagraphImpl::ParagraphImpl(const SkString& text, ParagraphStyle style, SkTArray<Block, true> blocks, + SkTArray<Placeholder, true> placeholders, sk_sp<FontCollection> fonts) : Paragraph(std::move(style), std::move(fonts)) , fTextStyles(std::move(blocks)) + , fPlaceholders(std::move(placeholders)) , fText(text) , fTextSpan(fText.c_str(), fText.size()) , fState(kUnknown) @@ -110,9 +71,11 @@ ParagraphImpl::ParagraphImpl(const std::u16string& utf16text, ParagraphStyle style, SkTArray<Block, true> blocks, + SkTArray<Placeholder, true> placeholders, sk_sp<FontCollection> fonts) : Paragraph(std::move(style), std::move(fonts)) , fTextStyles(std::move(blocks)) + , fPlaceholders(std::move(placeholders)) , fState(kUnknown) , fPicture(nullptr) , fStrutMetrics(false) @@ -224,31 +187,42 @@ for (RunIndex runIndex = 0; runIndex < fRuns.size(); ++runIndex) { auto& run = fRuns[runIndex]; auto runStart = fClusters.size(); - fClusters.reserve(fClusters.size() + fRuns.size()); - // Walk through the glyph in the direction of input text - run.iterateThroughClustersInTextOrder([runIndex, this]( - size_t glyphStart, - size_t glyphEnd, - size_t charStart, - size_t charEnd, - SkScalar width, - SkScalar height) { - SkASSERT(charEnd >= charStart); - SkSpan<const char> text(fTextSpan.begin() + charStart, charEnd - charStart); + if (run.isPlaceholder()) { + // There are no glyphs but we want to have one cluster + SkSpan<const char> text(fTextSpan.begin() + run.textRange().start, run.textRange().width()); + if (!fClusters.empty()) { + fClusters.back().setBreakType(Cluster::SoftLineBreak); + } + auto& cluster = fClusters.emplace_back(this, runIndex, 0ul, 0ul, text, + run.advance().fX, run.advance().fY); + cluster.setBreakType(Cluster::SoftLineBreak); + } else { + fClusters.reserve(fClusters.size() + fRuns.size()); + // Walk through the glyph in the direction of input text + run.iterateThroughClustersInTextOrder([runIndex, this](size_t glyphStart, + size_t glyphEnd, + size_t charStart, + size_t charEnd, + SkScalar width, + SkScalar height) { + SkASSERT(charEnd >= charStart); + SkSpan<const char> text(fTextSpan.begin() + charStart, charEnd - charStart); - auto& cluster = fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text, width, height); - cluster.setIsWhiteSpaces(); - }); + auto& cluster = fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text, + width, height); + cluster.setIsWhiteSpaces(); + }); + } run.setClusterRange(runStart, fClusters.size()); fMaxIntrinsicWidth += run.advance().fX; } } -// TODO: we need soft line breaks before for word spacing void ParagraphImpl::markLineBreaks() { // Find all possible (soft) line breaks + // This iterator is used only once for a paragraph so we don't have to keep it TextBreaker breaker; if (!breaker.initialize(fTextSpan, UBRK_LINE)) { return; @@ -277,6 +251,11 @@ SkScalar shift = 0; for (auto& run : fRuns) { + // Skip placeholder runs + if (run.isPlaceholder()) { + continue; + } + bool soFarWhitespacesOnly = true; for (size_t index = 0; index != run.clusterRange().width(); ++index) { auto correctIndex = run.leftToRight() @@ -294,6 +273,8 @@ SkASSERT(currentStyle != this->fTextStyles.end()); } + SkASSERT(!currentStyle->fStyle.isPlaceholder()); + // Process word spacing if (currentStyle->fStyle.getWordSpacing() != 0) { if (cluster->isWhitespaces() && cluster->isSoftBreak()) { @@ -320,10 +301,11 @@ class ShapeHandler final : public SkShaper::RunHandler { public: - explicit ShapeHandler(ParagraphImpl& paragraph, FontIterator* fontIterator) + explicit ShapeHandler(ParagraphImpl& paragraph, size_t firstChar, FontIterator* fontIterator, SkScalar advanceX) : fParagraph(¶graph) + , fFirstChar(firstChar) , fFontIterator(fontIterator) - , fAdvance(SkVector::Make(0, 0)) {} + , fAdvance(SkVector::Make(advanceX, 0)) {} SkVector advance() const { return fAdvance; } @@ -337,6 +319,7 @@ Buffer runBuffer(const RunInfo& info) override { auto& run = fParagraph->fRuns.emplace_back(fParagraph, info, + fFirstChar, fFontIterator->currentLineHeight(), fParagraph->fRuns.count(), fAdvance.fX); @@ -344,19 +327,23 @@ } void commitRunBuffer(const RunInfo&) override { + auto& run = fParagraph->fRuns.back(); if (run.size() == 0) { fParagraph->fRuns.pop_back(); return; } + // Carve out the line text out of the entire run text fAdvance.fX += run.advance().fX; fAdvance.fY = SkMaxScalar(fAdvance.fY, run.advance().fY); + run.fPlaceholder = nullptr; } void commitLine() override {} ParagraphImpl* fParagraph; + size_t fFirstChar; FontIterator* fFontIterator; SkVector fAdvance; }; @@ -370,24 +357,37 @@ // Check the font-resolved text against the cache if (!fFontCollection->getParagraphCache()->findParagraph(this)) { - LangIterator lang(fTextSpan, styles(), paragraphStyle().getTextStyle()); - FontIterator font(fTextSpan, &fFontResolver); - ShapeHandler handler(*this, &font); - std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder(); - SkASSERT_RELEASE(shaper != nullptr); - auto bidi = SkShaper::MakeIcuBiDiRunIterator( - fTextSpan.begin(), fTextSpan.size(), - fParagraphStyle.getTextDirection() == TextDirection::kLtr ? (uint8_t)2 - : (uint8_t)1); - if (bidi == nullptr) { + // The text can be broken into many shaping sequences + // (by place holders, possibly, by hard line breaks or tabs, too) + uint8_t textDirection = fParagraphStyle.getTextDirection() == TextDirection::kLtr ? 2 : 1; + auto limitlessWidth = std::numeric_limits<SkScalar>::max(); + + auto result = iterateThroughShapingRegions( + [this, textDirection, limitlessWidth] + (SkSpan<const char> textSpan, SkSpan<Block> styleSpan, SkScalar& advanceX, size_t start) { + + LangIterator lang(textSpan, styleSpan, paragraphStyle().getTextStyle()); + FontIterator font(textSpan, &fFontResolver); + auto script = SkShaper::MakeHbIcuScriptRunIterator(textSpan.begin(), textSpan.size()); + auto bidi = SkShaper::MakeIcuBiDiRunIterator(textSpan.begin(), textSpan.size(), textDirection); + if (bidi == nullptr) { + return false; + } + + // Set up the shaper and shape the next + ShapeHandler handler(*this, start, &font, advanceX); + auto shaper = SkShaper::MakeShapeDontWrapOrReorder(); + SkASSERT_RELEASE(shaper != nullptr); + shaper->shape(textSpan.begin(), textSpan.size(), font, *bidi, *script, lang, limitlessWidth, &handler); + advanceX = handler.advance().fX; + return true; + }); + if (!result) { return false; } - auto script = SkShaper::MakeHbIcuScriptRunIterator(fTextSpan.begin(), fTextSpan.size()); - - shaper->shape(fTextSpan.begin(), fTextSpan.size(), font, *bidi, *script, lang, - std::numeric_limits<SkScalar>::max(), &handler); } + // Clean the array for justification if (fParagraphStyle.getTextAlign() == TextAlign::kJustify) { fRunShifts.reset(); fRunShifts.push_back_n(fRuns.size(), RunShifts()); @@ -399,6 +399,48 @@ return true; } +bool ParagraphImpl::iterateThroughShapingRegions(ShapeVisitor shape) { + + SkScalar advanceX = 0; + for (auto& placeholder : fPlaceholders) { + // Shape the text + if (placeholder.fTextBefore.width() > 0) { + // Set up the iterators + SkSpan<const char> textSpan(fTextSpan.begin() + placeholder.fTextBefore.start, + placeholder.fTextBefore.width()); + SkSpan<Block> styleSpan(fTextStyles.begin() + placeholder.fBlocksBefore.start, + placeholder.fBlocksBefore.width()); + + if (!shape(textSpan, styleSpan, advanceX, placeholder.fTextBefore.start)) { + return false; + } + } + + if (placeholder.fRange.width() == 0) { + continue; + } + // "Shape" the placeholder + const SkShaper::RunHandler::RunInfo runInfo = { + SkFont(), + (uint8_t)2, + SkPoint::Make(placeholder.fStyle.fWidth, placeholder.fStyle.fHeight), + 1, + SkShaper::RunHandler::Range(placeholder.fRange.start, placeholder.fRange.width()) + }; + auto& run = fRuns.emplace_back(this, + runInfo, + 0, //placeholder.fRange.start, + 1, + fRuns.count(), + advanceX); + run.fPositions[0] = { advanceX, 0 }; + run.fClusterIndexes[0] = 0; + run.fPlaceholder = &placeholder.fStyle; + advanceX += placeholder.fStyle.fWidth; + } + return true; +} + void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) { TextWrapper textWrapper; @@ -528,6 +570,7 @@ return; } + // This breaker gets called only once for a paragraph so we don't have to keep it TextBreaker breaker; if (!breaker.initialize(fTextSpan, UBRK_CHARACTER)) { return; @@ -628,8 +671,12 @@ if (rectHeightStyle == RectHeightStyle::kMax) { // TODO: Sort it out with Flutter people - // Mimicking TxtLib: clip.fTop = line.offset().fY + line.roundingDelta(); + // Mimicking TxtLib: + //clip.fTop = line.offset().fY + line.roundingDelta(); clip.fBottom = line.offset().fY + line.height(); + clip.fTop = line.offset().fY + + line.sizes().baseline() - line.getMaxRunMetrics().baseline() + + line.getMaxRunMetrics().delta(); } else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingTop) { if (&line != &fLines.front()) { @@ -662,7 +709,7 @@ // Check if we can merge two boxes bool mergedBoxes = false; if (!results.empty() && - lastRun != nullptr && + lastRun != nullptr && lastRun->placeholder() == nullptr && run->placeholder() == nullptr && lastRun->lineHeight() == run->lineHeight() && lastRun->font() == run->font()) { auto& lastBox = results.back(); @@ -709,6 +756,30 @@ return results; } + +std::vector<TextBox> ParagraphImpl::GetRectsForPlaceholders() { + std::vector<TextBox> boxes; + + for (auto& line : fLines) { + SkScalar runOffset = 0; + auto text = line.trimmedText(); + line.iterateThroughRuns( + text, + runOffset, + false, + [&boxes, &line](Run* run, size_t pos, size_t size, TextRange text, SkRect clip, + SkScalar shift, bool clippingNeeded) { + if (run->placeholder() == nullptr) { + return true; + } + clip.offset(line.offset()); + boxes.emplace_back(clip, run->leftToRight() ? TextDirection::kLtr : TextDirection::kRtl); + return true; + }); + } + + return boxes; +} // TODO: Deal with RTL here PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) { @@ -804,15 +875,16 @@ // Finds the first and last glyphs that define a word containing // the glyph at index offset. // By "glyph" they mean a character index - indicated by Minikin's code -// TODO: make the breaker a member of ParagraphImpl SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) { - TextBreaker breaker; - if (!breaker.initialize(fTextSpan, UBRK_WORD)) { - return {0, 0}; + + if (!fWordBreaker.initialized()) { + if (!fWordBreaker.initialize(fTextSpan, UBRK_WORD)) { + return {0, 0}; + } } - auto start = breaker.preceding(offset + 1); - auto end = breaker.following(start); + auto start = fWordBreaker.preceding(offset + 1); + auto end = fWordBreaker.following(start); return { start, end }; }
diff --git a/modules/skparagraph/src/ParagraphImpl.h b/modules/skparagraph/src/ParagraphImpl.h index 043e4ee..4c170d2 100644 --- a/modules/skparagraph/src/ParagraphImpl.h +++ b/modules/skparagraph/src/ParagraphImpl.h
@@ -2,6 +2,10 @@ #ifndef ParagraphImpl_DEFINED #define ParagraphImpl_DEFINED +#include <unicode/brkiter.h> +#include <unicode/ubidi.h> +#include <unicode/unistr.h> +#include <unicode/urename.h> #include "include/core/SkPicture.h" #include "include/private/SkMutex.h" #include "include/private/SkTHash.h" @@ -38,6 +42,46 @@ TStyle fStyle; }; +class TextBreaker { +public: + TextBreaker() : fInitialized(false), fPos(-1) {} + + bool initialize(SkSpan<const char> text, UBreakIteratorType type); + + bool initialized() const { return fInitialized; } + + size_t first() { + fPos = ubrk_first(fIterator.get()); + return eof() ? fSize : fPos; + } + + size_t next() { + fPos = ubrk_next(fIterator.get()); + return eof() ? fSize : fPos; + } + + size_t preceding(size_t offset) { + auto pos = ubrk_preceding(fIterator.get(), offset); + return eof() ? 0 : pos; + } + + size_t following(size_t offset) { + auto pos = ubrk_following(fIterator.get(), offset); + return eof() ? fSize : pos; + } + + int32_t status() { return ubrk_getRuleStatus(fIterator.get()); } + + bool eof() { return fPos == icu::BreakIterator::DONE; } + +private: + std::unique_ptr<UText, SkFunctionWrapper<UText*, UText, utext_close>> fAutoClose; + std::unique_ptr<UBreakIterator, SkFunctionWrapper<void, UBreakIterator, ubrk_close>> fIterator; + bool fInitialized; + int32_t fPos; + size_t fSize; +}; + class ParagraphImpl final : public Paragraph { public: @@ -45,12 +89,14 @@ ParagraphImpl(const SkString& text, ParagraphStyle style, SkTArray<Block, true> blocks, + SkTArray<Placeholder, true> placeholders, sk_sp<FontCollection> fonts); ParagraphImpl(const std::u16string& utf16text, - ParagraphStyle style, - SkTArray<Block, true> blocks, - sk_sp<FontCollection> fonts); + ParagraphStyle style, + SkTArray<Block, true> blocks, + SkTArray<Placeholder, true> placeholders, + sk_sp<FontCollection> fonts); ~ParagraphImpl() override; void layout(SkScalar width) override; @@ -59,6 +105,7 @@ unsigned end, RectHeightStyle rectHeightStyle, RectWidthStyle rectWidthStyle) override; + std::vector<TextBox> GetRectsForPlaceholders() override; PositionWithAffinity getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) override; SkRange<size_t> getWordBoundary(unsigned offset) override; bool didExceedMaxLines() override { @@ -139,6 +186,10 @@ void setState(InternalState state); sk_sp<SkPicture> getPicture() { return fPicture; } + using ShapeVisitor = + std::function<SkScalar(SkSpan<const char>, SkSpan<Block>, SkScalar&, size_t)>; + bool iterateThroughShapingRegions(ShapeVisitor shape); + void resetContext(); void resolveStrut(); void resetRunShifts(); @@ -169,6 +220,7 @@ SkTArray<StyleBlock<std::vector<TextShadow>>> fShadowStyles; SkTArray<StyleBlock<Decoration>> fDecorationStyles; SkTArray<Block, true> fTextStyles; // TODO: take out only the font stuff + SkTArray<Placeholder, true> fPlaceholders; SkString fText; SkSpan<const char> fTextSpan; @@ -189,6 +241,8 @@ SkScalar fOldWidth; SkScalar fOldHeight; SkScalar fMaxWidthWithTrailingSpaces; + + TextBreaker fWordBreaker; }; } // namespace textlayout } // namespace skia
diff --git a/modules/skparagraph/src/Run.cpp b/modules/skparagraph/src/Run.cpp index ad93fb1..2f7edc9 100644 --- a/modules/skparagraph/src/Run.cpp +++ b/modules/skparagraph/src/Run.cpp
@@ -19,12 +19,14 @@ Run::Run(ParagraphImpl* master, const SkShaper::RunHandler::RunInfo& info, + size_t firstChar, SkScalar lineHeight, size_t index, SkScalar offsetX) : fMaster(master) - , fTextRange(info.utf8Range.begin(), info.utf8Range.end()) - , fClusterRange(EMPTY_CLUSTERS) { + , fTextRange(firstChar + info.utf8Range.begin(), firstChar + info.utf8Range.end()) + , fClusterRange(EMPTY_CLUSTERS) + , fFirstChar(firstChar) { fFont = info.fFont; fHeightMultiplier = lineHeight; fBidiLevel = info.fBidiLevel; @@ -125,8 +127,8 @@ visitor(start, glyph, - cluster, - nextCluster, + fFirstChar + cluster, + fFirstChar + nextCluster, this->calculateWidth(start, glyph, glyph == size()), this->calculateHeight()); @@ -145,8 +147,8 @@ visitor(start, glyph, - cluster, - nextCluster, + fFirstChar + cluster, + fFirstChar + nextCluster, this->calculateWidth(start, glyph, glyph == 0), this->calculateHeight()); @@ -206,6 +208,60 @@ } } +void Run::updateMetrics(LineMetrics* endlineMetrics) { + + // Difference between the placeholder baseline and the line bottom + SkScalar baselineAdjustment = 0; + switch (fPlaceholder->fBaseline) { + case TextBaseline::kAlphabetic: + break; + + case TextBaseline::kIdeographic: + baselineAdjustment = endlineMetrics->deltaBaselines() / 2; + break; + } + + auto height = fPlaceholder->fHeight; + auto offset = fPlaceholder->fBaselineOffset; + + fFontMetrics.fLeading = 0; + switch (fPlaceholder->fAlignment) { + case PlaceholderAlignment::kBaseline: + fFontMetrics.fAscent = baselineAdjustment - offset; + fFontMetrics.fDescent = baselineAdjustment + height - offset; + break; + + case PlaceholderAlignment::kAboveBaseline: + fFontMetrics.fAscent = baselineAdjustment - height; + fFontMetrics.fDescent = baselineAdjustment; + break; + + case PlaceholderAlignment::kBelowBaseline: + fFontMetrics.fAscent = baselineAdjustment; + fFontMetrics.fDescent = baselineAdjustment +height; + break; + + case PlaceholderAlignment::kTop: + fFontMetrics.fAscent = - endlineMetrics->alphabeticBaseline(); + fFontMetrics.fDescent = height - endlineMetrics->alphabeticBaseline(); + break; + + case PlaceholderAlignment::kBottom: + fFontMetrics.fDescent = endlineMetrics->deltaBaselines(); + fFontMetrics.fAscent = - height + endlineMetrics->deltaBaselines(); + break; + + case PlaceholderAlignment::kMiddle: + double mid = (endlineMetrics->ascent() + endlineMetrics->descent())/ 2; + fFontMetrics.fDescent = mid + height/2; + fFontMetrics.fAscent = mid - height/2; + break; + } + + // Make sure the placeholder can fit the line + endlineMetrics->add(this); +} + void Cluster::setIsWhiteSpaces() { fWhiteSpaces = false;
diff --git a/modules/skparagraph/src/Run.h b/modules/skparagraph/src/Run.h index b5db00e..62381a1 100644 --- a/modules/skparagraph/src/Run.h +++ b/modules/skparagraph/src/Run.h
@@ -32,22 +32,19 @@ typedef size_t CodepointIndex; typedef SkRange<CodepointIndex> CodepointRange; -typedef size_t BlockIndex; -typedef SkRange<size_t> BlockRange; -const size_t EMPTY_BLOCK = EMPTY_INDEX; -const SkRange<size_t> EMPTY_BLOCKS = EMPTY_RANGE; - struct RunShifts { RunShifts() { } RunShifts(size_t count) { fShifts.push_back_n(count, 0.0); } SkSTArray<128, SkScalar, true> fShifts; }; +class LineMetrics; class Run { public: Run() = default; Run(ParagraphImpl* master, const SkShaper::RunHandler::RunInfo& info, + size_t firstChar, SkScalar lineHeight, size_t index, SkScalar shiftX); @@ -69,8 +66,6 @@ } SkVector offset() const { return fOffset; } SkScalar ascent() const { return fFontMetrics.fAscent; } - //SkScalar descent() const { return fFontMetrics.fDescent; } - //SkScalar leading() const { return fFontMetrics.fLeading; } SkScalar correctAscent() const { if (fHeightMultiplier == 0 || fHeightMultiplier == 1) { @@ -99,12 +94,16 @@ bool leftToRight() const { return fBidiLevel % 2 == 0; } size_t index() const { return fIndex; } SkScalar lineHeight() const { return fHeightMultiplier; } + PlaceholderStyle* placeholder() const { return fPlaceholder; } + bool isPlaceholder() const { return fPlaceholder != nullptr; } size_t clusterIndex(size_t pos) const { return fClusterIndexes[pos]; } SkScalar positionX(size_t pos) const; TextRange textRange() { return fTextRange; } ClusterRange clusterRange() { return fClusterRange; } + void updateMetrics(LineMetrics* endlineMetrics); + void setClusterRange(size_t from, size_t to) { fClusterRange = ClusterRange(from, to); } SkRect clip() const { return SkRect::MakeXYWH(fOffset.fX, fOffset.fY, fAdvance.fX, fAdvance.fY); @@ -157,10 +156,12 @@ SkFont fFont; SkFontMetrics fFontMetrics; SkScalar fHeightMultiplier; + PlaceholderStyle* fPlaceholder; size_t fIndex; uint8_t fBidiLevel; SkVector fAdvance; SkVector fOffset; + size_t fFirstChar; SkShaper::RunHandler::Range fUtf8Range; SkSTArray<128, SkGlyphID, false> fGlyphs; SkSTArray<128, SkPoint, true> fPositions; @@ -243,8 +244,6 @@ size_t startPos() const { return fStart; } size_t endPos() const { return fEnd; } SkScalar width() const { return fWidth; } - SkScalar trimmedWidth() const { return fWidth - fSpacing; } - SkScalar lastSpacing() const { return fSpacing; } SkScalar height() const { return fHeight; } size_t size() const { return fEnd - fStart; } @@ -290,20 +289,25 @@ public: LineMetrics() { clean(); } - LineMetrics(bool forceStrut) : fForceStrut(forceStrut) { clean(); } + LineMetrics(bool forceStrut) { + clean(); + fForceStrut = forceStrut; + } - LineMetrics(SkScalar a, SkScalar d, SkScalar l) : fForceStrut(false) { + LineMetrics(SkScalar a, SkScalar d, SkScalar l) { fAscent = a; fDescent = d; fLeading = l; + fForceStrut = false; } - LineMetrics(const SkFont& font, bool forceStrut) : fForceStrut(forceStrut) { + LineMetrics(const SkFont& font, bool forceStrut) { SkFontMetrics metrics; font.getMetrics(&metrics); fAscent = metrics.fAscent; fDescent = metrics.fDescent; fLeading = metrics.fLeading; + fForceStrut = forceStrut; } void add(Run* run) { @@ -327,6 +331,7 @@ fAscent = 0; fDescent = 0; fLeading = 0; + fForceStrut = false; } SkScalar delta() const { return height() - ideographicBaseline(); } @@ -343,12 +348,16 @@ SkScalar height() const { return SkScalarRoundToInt(fDescent - fAscent + fLeading); } SkScalar alphabeticBaseline() const { return fLeading / 2 - fAscent; } SkScalar ideographicBaseline() const { return fDescent - fAscent + fLeading; } + SkScalar deltaBaselines() const { return fLeading / 2 + fDescent; } SkScalar baseline() const { return fLeading / 2 - fAscent; } SkScalar ascent() const { return fAscent; } SkScalar descent() const { return fDescent; } SkScalar leading() const { return fLeading; } private: + + friend class TextWrapper; + SkScalar fAscent; SkScalar fDescent; SkScalar fLeading;
diff --git a/modules/skparagraph/src/TextLine.cpp b/modules/skparagraph/src/TextLine.cpp index 40d2cbe..1eaef70 100644 --- a/modules/skparagraph/src/TextLine.cpp +++ b/modules/skparagraph/src/TextLine.cpp
@@ -28,8 +28,6 @@ return end >= begin ? TextRange(begin, end) : EMPTY_TEXT; } -SkTHashMap<SkFont, Run> TextLine::fEllipsisCache; - TextLine::TextLine(ParagraphImpl* master, SkVector offset, SkVector advance, @@ -63,6 +61,9 @@ for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) { auto b = fMaster->styles().begin() + index; + if (b->fStyle.isPlaceholder()) { + continue; + } if (b->fStyle.hasBackground()) { fHasBackground = true; } @@ -195,6 +196,9 @@ return this->iterateThroughRuns( textRange, offsetX, false, [canvas, paint, shiftDown](Run* run, int32_t pos, size_t size, TextRange, SkRect clip, SkScalar shift, bool clippingNeeded) { + if (run->placeholder() != nullptr) { + return true; + } SkTextBlobBuilder builder; run->copyTo(builder, SkToU32(pos), size, SkVector::Make(0, shiftDown)); canvas->save(); @@ -271,9 +275,8 @@ continue; } - SkScalar thickness = style.getDecorationThicknessMultiplier(); - // - SkScalar position = 0; + SkScalar thickness = computeDecorationThickness(style); + SkScalar position = computeDecorationPosition(style); switch (decoration) { case TextDecoration::kUnderline: position = -run->correctAscent() + thickness; @@ -286,7 +289,6 @@ break; } default: - // TODO: can we actually get here? SkASSERT(false); break; } @@ -299,7 +301,7 @@ SkPaint paint; SkPath path; this->computeDecorationPaint(paint, clip, style, path); - paint.setStrokeWidth(thickness); + paint.setStrokeWidth(thickness * style.getDecorationThicknessMultiplier()); switch (style.getDecorationStyle()) { case TextDecorationStyle::kWavy: @@ -326,6 +328,28 @@ }); } +SkScalar TextLine::computeDecorationThickness(const TextStyle& style) const { + SkFontMetrics fontMetrics; + style.getFontMetrics(&fontMetrics); + if ((fontMetrics.fFlags & SkFontMetrics::FontMetricsFlags::kUnderlineThicknessIsValid_Flag) && + fontMetrics.fUnderlineThickness > 0) { + return fontMetrics.fUnderlineThickness; + } else { + return style.getFontSize() / 14.0f; + } +} + +SkScalar TextLine::computeDecorationPosition(const TextStyle& style) const { + SkFontMetrics fontMetrics; + style.getFontMetrics(&fontMetrics); + if ((fontMetrics.fFlags & SkFontMetrics::FontMetricsFlags::kUnderlinePositionIsValid_Flag) && + fontMetrics.fUnderlinePosition > 0) { + return fontMetrics.fUnderlinePosition; + } else { + return style.getFontSize() / 14.0f; + } +} + void TextLine::computeDecorationPaint(SkPaint& paint, SkRect clip, const TextStyle& style, @@ -462,13 +486,9 @@ } // Shape the ellipsis - Run* cached = fEllipsisCache.find(cluster->font()); - if (cached == nullptr) { - cached = shapeEllipsis(ellipsis, cluster->run()); - } else { - cached->setMaster(fMaster); - } - fEllipsis = std::make_shared<Run>(*cached); + Run* run = shapeEllipsis(ellipsis, cluster->run()); + run->setMaster(fMaster); + fEllipsis = std::make_shared<Run>(*run); // See if it fits if (width + fEllipsis->advance().fX > maxWidth) { @@ -499,14 +519,14 @@ void commitRunInfo() override {} Buffer runBuffer(const RunInfo& info) override { - fRun = fEllipsisCache.set(info.fFont, - Run(nullptr, info, fLineHeight, 0, 0)); + fRun = new Run(nullptr, info, 0, fLineHeight, 0, 0); return fRun->newRunBuffer(); } void commitRunBuffer(const RunInfo& info) override { fRun->fAdvance.fX = info.fAdvance.fX; fRun->fAdvance.fY = fRun->advance().fY; + fRun->fPlaceholder = nullptr; } void commitLine() override {} @@ -531,6 +551,15 @@ SkASSERT(intersectedSize(run->textRange(), textRange) >= 0); + if (run->placeholder() != nullptr) { + SkASSERT(textRange == run->textRange()); + pos = 0; + size = 1; + clippingNeeded = false; + return SkRect::MakeXYWH( + 0, sizes().runTop(run), run->calculateWidth(0, 1, false), run->calculateHeight()); + } + // Find [start:end] clusters for the text bool found; ClusterIndex startIndex; @@ -569,8 +598,6 @@ clip.fRight -= rightCorrection; clippingNeeded = leftCorrection != 0 || rightCorrection != 0; - // SkDebugf("measureTextInsideOneRun: '%s'[%d:%d]\n", text.begin(), pos, pos + size); - return clip; } @@ -730,13 +757,13 @@ } } - auto* style = &block->fStyle; - if (start != EMPTY_INDEX && style->matchOneAttribute(styleType, *prevStyle)) { + auto& style = block->fStyle; + if (start != EMPTY_INDEX && style.matchOneAttribute(styleType, *prevStyle)) { size += intersect.width(); continue; } else if (start == EMPTY_INDEX ) { // First time only - prevStyle = style; + prevStyle = &style; size = intersect.width(); start = intersect.start; continue; @@ -746,7 +773,7 @@ offsetX += width; // Start all over again - prevStyle = style; + prevStyle = &style; start = intersect.start; size = intersect.width(); }
diff --git a/modules/skparagraph/src/TextLine.h b/modules/skparagraph/src/TextLine.h index 91d01b4..b625940 100644 --- a/modules/skparagraph/src/TextLine.h +++ b/modules/skparagraph/src/TextLine.h
@@ -44,13 +44,11 @@ return fAdvance.fX + (fEllipsis != nullptr ? fEllipsis->fAdvance.fX : 0); } SkScalar shift() const { return fShift; } - SkScalar widthWithSpaces() const { return fWidthWithSpaces; } SkVector offset() const; SkScalar alphabeticBaseline() const { return fSizes.alphabeticBaseline(); } SkScalar ideographicBaseline() const { return fSizes.ideographicBaseline(); } SkScalar baseline() const { return fSizes.baseline(); } - SkScalar roundingDelta() const { return fSizes.delta(); } using StyleVisitor = std::function<SkScalar(TextRange textRange, const TextStyle& style, SkScalar offsetX)>; @@ -79,6 +77,9 @@ TextAlign assumedTextAlign() const; + void setMaxRunMetrics(const LineMetrics& metrics) { fMaxRunMetrics = metrics; } + LineMetrics getMaxRunMetrics() const { return fMaxRunMetrics; } + private: Run* shapeEllipsis(const SkString& ellipsis, Run* run); @@ -102,6 +103,9 @@ void computeDecorationPaint(SkPaint& paint, SkRect clip, const TextStyle& style, SkPath& path) const; + SkScalar computeDecorationThickness(const TextStyle& style) const; + SkScalar computeDecorationPosition(const TextStyle& style) const; + bool contains(const Cluster* cluster) const { return fTextRange.contains(cluster->textRange()); } @@ -119,13 +123,11 @@ SkScalar fShift; // Left right SkScalar fWidthWithSpaces; std::shared_ptr<Run> fEllipsis; // In case the line ends with the ellipsis - LineMetrics fSizes; // Line metrics as a max of all run metrics + LineMetrics fSizes; // Line metrics as a max of all run metrics and struts + LineMetrics fMaxRunMetrics; // No struts - need it for GetRectForRange(max height) bool fHasBackground; bool fHasShadows; bool fHasDecorations; - - // TODO: use for ellipsis the common cache - static SkTHashMap<SkFont, Run> fEllipsisCache; // All found so far shapes of ellipsis }; } // namespace textlayout } // namespace skia
diff --git a/modules/skparagraph/src/TextStyle.cpp b/modules/skparagraph/src/TextStyle.cpp index 5f82452..0487aa1 100644 --- a/modules/skparagraph/src/TextStyle.cpp +++ b/modules/skparagraph/src/TextStyle.cpp
@@ -26,9 +26,25 @@ fHasForeground = false; fTextBaseline = TextBaseline::kAlphabetic; fLocale = ""; + fIsPlaceholder = false; +} + +TextStyle:: TextStyle(const TextStyle& other, bool placeholder) { + fColor = other.fColor; + fDecoration = other.fDecoration; + fHasBackground = other.fHasBackground; + fHasForeground = other.fHasForeground; + fBackground = other.fBackground; + fForeground = other.fForeground; + fIsPlaceholder = placeholder; } bool TextStyle::equals(const TextStyle& other) const { + + if (fIsPlaceholder || other.fIsPlaceholder) { + return false; + } + if (fColor != other.fColor) { return false; } @@ -124,6 +140,9 @@ void TextStyle::getFontMetrics(SkFontMetrics* metrics) const { SkFont font(fTypeface, fFontSize); + font.setEdging(SkFont::Edging::kAntiAlias); + font.setSubpixel(true); + font.setHinting(SkFontHinting::kSlight); font.getMetrics(metrics); if (fHeightOverride) { auto multiplier = fHeight * fFontSize;
diff --git a/modules/skparagraph/src/TextWrapper.cpp b/modules/skparagraph/src/TextWrapper.cpp index 8792a5a..34760c6 100644 --- a/modules/skparagraph/src/TextWrapper.cpp +++ b/modules/skparagraph/src/TextWrapper.cpp
@@ -152,6 +152,7 @@ fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight()); auto end = span.end() - 1; auto start = span.begin(); + LineMetrics maxRunMetrics; while (fEndLine.endCluster() != end) { reset(); @@ -176,10 +177,26 @@ maxWidth != std::numeric_limits<SkScalar>::max() && !ellipsisStr.isEmpty(); // TODO: perform ellipsis work here + + // Deal with placeholder clusters == runs[@size==1] + for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) { + if (cluster->run()->placeholder() != nullptr) { + SkASSERT(cluster->run()->size() == 1); + // Update the placeholder metrics so we can get the placeholder positions later + // and the line metrics (to make sure the placeholder fits) + cluster->run()->updateMetrics(&fEndLine.metrics()); + } + } + + // Before we update the line metrics with struts, + // let's save it for GetRectsForRange(RectHeightStyle::kMax) + maxRunMetrics = fEndLine.metrics(); + if (parent->strutEnabled()) { // Make sure font metrics are not less than the strut parent->strutMetrics().updateLineMetrics(fEndLine.metrics()); } + fMaxIntrinsicWidth = SkMaxScalar(fMaxIntrinsicWidth, fEndLine.width()); // TODO: keep start/end/break info for text and runs but in a better way that below TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end + 1); @@ -199,10 +216,12 @@ fEndLine.metrics(), needEllipsis); + parent->lines().back().setMaxRunMetrics(maxRunMetrics); + // Start a new line fHeight += fEndLine.metrics().height(); - if (!fHardLineBreak) { + if (!fHardLineBreak || startLine != end) { fEndLine.clean(); } fEndLine.startFrom(startLine, pos); @@ -215,6 +234,7 @@ } if (fHardLineBreak) { + // Last character is a line break if (parent->strutEnabled()) { // Make sure font metrics are not less than the strut @@ -231,6 +251,7 @@ SkVector::Make(0, fEndLine.metrics().height()), fEndLine.metrics(), false); + parent->lines().back().setMaxRunMetrics(maxRunMetrics); } }
diff --git a/modules/skparagraph/src/TextWrapper.h b/modules/skparagraph/src/TextWrapper.h index 78299e9..b94e146 100644 --- a/modules/skparagraph/src/TextWrapper.h +++ b/modules/skparagraph/src/TextWrapper.h
@@ -52,7 +52,6 @@ inline LineMetrics& metrics() { return fMetrics; } inline size_t startPos() const { return fStart.position(); } inline size_t endPos() const { return fEnd.position(); } - inline size_t breakPos() const { return fBreak.position(); } bool endOfCluster() { return fEnd.position() == fEnd.cluster()->endPos(); } bool endOfWord() { return endOfCluster() && @@ -91,27 +90,15 @@ fWidth = 0; } - void nextBreakPos(Cluster* endOfClusters) { - if (fBreak.position() == fBreak.cluster()->endPos()) { - if (fBreak.cluster() < endOfClusters) { - fBreak.move(true); - } else { - fBreak.setPosition(0); - } - } else { - fBreak.setPosition(fBreak.cluster()->endPos()); - } - } - void saveBreak() { fWidthWithGhostSpaces = fWidth; fBreak = fEnd; } - void restoreBreak() { fEnd = fBreak; } - void trim() { - fWidth -= (fEnd.cluster()->width() - fEnd.cluster()->trimmedWidth(fEnd.position())); + if (fEnd.cluster()->run()->placeholder() == nullptr) { + fWidth -= (fEnd.cluster()->width() - fEnd.cluster()->trimmedWidth(fEnd.position())); + } } void trim(Cluster* cluster) {
diff --git a/tests/SkParagraphTest.cpp b/tests/SkParagraphTest.cpp index 71691b8..a9015ec 100644 --- a/tests/SkParagraphTest.cpp +++ b/tests/SkParagraphTest.cpp
@@ -21,6 +21,7 @@ SkScalar EPSILON100 = 0.01f; SkScalar EPSILON50 = 0.02f; SkScalar EPSILON20 = 0.05f; +SkScalar EPSILON10 = 0.1f; SkScalar EPSILON5 = 0.20f; SkScalar EPSILON2 = 0.50f; @@ -175,33 +176,749 @@ } } -DEF_TEST(SkParagraph_InlinePlaceholderParagraph, reporter) { SkDebugf("Not implemented yet.\n"); } +// Checked: DIFF+ (half of the letter spacing before the text???) +DEF_TEST(SkParagraph_InlinePlaceholderParagraph, reporter) { + sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>(); + TestCanvas canvas("SkParagraph_InlinePlaceholderParagraph.png"); + if (!fontCollection->fontsFound()) return; + + const char* text = "012 34"; + + ParagraphStyle paragraph_style; + paragraph_style.turnHintingOff(); + paragraph_style.setMaxLines(14); + ParagraphBuilderImpl builder(paragraph_style, fontCollection); + + TextStyle text_style; + text_style.setFontFamilies({SkString("Roboto")}); + text_style.setColor(SK_ColorBLACK); + text_style.setFontSize(26); + text_style.setWordSpacing(5); + text_style.setLetterSpacing(1); + text_style.setDecoration(TextDecoration::kUnderline); + text_style.setDecorationColor(SK_ColorBLACK); + builder.pushStyle(text_style); + builder.addText(text); + + PlaceholderStyle placeholder1(50, 50, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 0); + builder.addPlaceholder(placeholder1); + builder.addText(text); + builder.addPlaceholder(placeholder1); + + PlaceholderStyle placeholder2(5, 50, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 50); + builder.addPlaceholder(placeholder2); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder2); + builder.addText(text); + builder.addPlaceholder(placeholder2); + builder.addText(text); + builder.addText(text); + builder.addPlaceholder(placeholder2); + builder.addPlaceholder(placeholder2); + builder.addPlaceholder(placeholder2); + builder.addPlaceholder(placeholder2); + builder.addPlaceholder(placeholder2); + builder.addPlaceholder(placeholder1); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addPlaceholder(placeholder2); + builder.addPlaceholder(placeholder1); + builder.addText(text); + builder.addText(text); + + builder.pop(); + + auto paragraph = builder.Build(); + paragraph->layout(TestCanvasWidth); + paragraph->paint(canvas.get(), 0, 0); + + RectHeightStyle rect_height_style = RectHeightStyle::kTight; + RectWidthStyle rect_width_style = RectWidthStyle::kTight; + + auto boxes = paragraph->getRectsForRange(0, 3, rect_height_style, rect_width_style); + canvas.drawRects(SK_ColorRED, boxes); + REPORTER_ASSERT(reporter, boxes.size() == 1); + + boxes = paragraph->getRectsForRange(0, 3, rect_height_style, rect_width_style); + canvas.drawRects(SK_ColorGREEN, boxes); + REPORTER_ASSERT(reporter, boxes.size() == 1); + + boxes = paragraph->GetRectsForPlaceholders(); + canvas.drawRects(SK_ColorRED, boxes); + + boxes = paragraph->getRectsForRange(4, 17, rect_height_style, rect_width_style); + canvas.drawRects(SK_ColorBLUE, boxes); + + REPORTER_ASSERT(reporter, boxes.size() == 7); + + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.left(), 90.921f - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.top(), 50, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.right(), 90.921f + 50 - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.bottom(), 100, EPSILON100)); + + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[3].rect.left(), 231.343f - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[3].rect.top(), 50, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[3].rect.right(), 231.343f + 50 - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[3].rect.bottom(), 100, EPSILON100)); + + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[4].rect.left(), 281.343f - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[4].rect.top(), 0, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[4].rect.right(), 281.343f + 5 - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[4].rect.bottom(), 50, EPSILON100)); + + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[6].rect.left(), 336.343f - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[6].rect.top(), 0, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[6].rect.right(), 336.343f + 5 - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[6].rect.bottom(), 50, EPSILON100)); +} + +// Checked: DIFF+ (half of the letter spacing before the text???) DEF_TEST(SkParagraph_InlinePlaceholderBaselineParagraph, reporter) { - SkDebugf("Not implemented yet.\n"); + sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>(); + TestCanvas canvas("SkParagraph_InlinePlaceholderBaselineParagraph.png"); + if (!fontCollection->fontsFound()) return; + + const char* text = "012 34"; + + ParagraphStyle paragraph_style; + paragraph_style.turnHintingOff(); + paragraph_style.setMaxLines(14); + ParagraphBuilderImpl builder(paragraph_style, fontCollection); + + TextStyle text_style; + text_style.setFontFamilies({SkString("Roboto")}); + text_style.setColor(SK_ColorBLACK); + text_style.setFontSize(26); + text_style.setWordSpacing(5); + text_style.setLetterSpacing(1); + text_style.setDecoration(TextDecoration::kUnderline); + text_style.setDecorationColor(SK_ColorBLACK); + builder.pushStyle(text_style); + builder.addText(text); + + PlaceholderStyle placeholder(55, 50, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 38.347f); + builder.addPlaceholder(placeholder); + builder.addText(text); + + builder.pop(); + + auto paragraph = builder.Build(); + paragraph->layout(TestCanvasWidth); + paragraph->paint(canvas.get(), 0, 0); + + auto boxes = paragraph->GetRectsForPlaceholders(); + canvas.drawRects(SK_ColorRED, boxes); + + REPORTER_ASSERT(reporter, boxes.size() == 1); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 90.921f - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 0, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f + 55 - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 50, EPSILON100)); + + RectHeightStyle rect_height_style = RectHeightStyle::kTight; + RectWidthStyle rect_width_style = RectWidthStyle::kTight; + + boxes = paragraph->getRectsForRange(5, 6, rect_height_style, rect_width_style); + canvas.drawRects(SK_ColorBLUE, boxes); + + REPORTER_ASSERT(reporter, boxes.size() == 1); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 75.324f - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 14.226f, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 44.694f, EPSILON100)); } -DEF_TEST(SkParagraph_InlinePlaceholderAboveBaselineParagraphh, reporter) { - SkDebugf("Not implemented yet.\n"); + +// Checked: DIFF+ (half of the letter spacing before the text???) +DEF_TEST(SkParagraph_InlinePlaceholderAboveBaselineParagraph, reporter) { + sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>(); + TestCanvas canvas("SkParagraph_InlinePlaceholderAboveBaselineParagraph.png"); + if (!fontCollection->fontsFound()) return; + + const char* text = "012 34"; + + ParagraphStyle paragraph_style; + paragraph_style.turnHintingOff(); + paragraph_style.setMaxLines(14); + ParagraphBuilderImpl builder(paragraph_style, fontCollection); + + TextStyle text_style; + text_style.setFontFamilies({SkString("Roboto")}); + text_style.setColor(SK_ColorBLACK); + text_style.setFontSize(26); + text_style.setWordSpacing(5); + text_style.setLetterSpacing(1); + text_style.setDecoration(TextDecoration::kUnderline); + text_style.setDecorationColor(SK_ColorBLACK); + builder.pushStyle(text_style); + builder.addText(text); + + PlaceholderStyle placeholder(55, 50, PlaceholderAlignment::kAboveBaseline, TextBaseline::kAlphabetic, 903129.129308f); + builder.addPlaceholder(placeholder); + builder.addText(text); + + builder.pop(); + + auto paragraph = builder.Build(); + paragraph->layout(TestCanvasWidth); + paragraph->paint(canvas.get(), 0, 0); + + auto boxes = paragraph->GetRectsForPlaceholders(); + canvas.drawRects(SK_ColorRED, boxes); + + REPORTER_ASSERT(reporter, boxes.size() == 1); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 90.921f - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), -0.347f, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f + 55 - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 49.652f, EPSILON100)); + + RectHeightStyle rect_height_style = RectHeightStyle::kTight; + RectWidthStyle rect_width_style = RectWidthStyle::kTight; + + boxes = paragraph->getRectsForRange(5, 6, rect_height_style, rect_width_style); + canvas.drawRects(SK_ColorBLUE, boxes); + + REPORTER_ASSERT(reporter, boxes.size() == 1); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 75.324f - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 25.531f, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 56, EPSILON100)); } + +// Checked: DIFF+ (half of the letter spacing before the text???) DEF_TEST(SkParagraph_InlinePlaceholderBelowBaselineParagraph, reporter) { - SkDebugf("Not implemented yet.\n"); + sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>(); + TestCanvas canvas("SkParagraph_InlinePlaceholderBelowBaselineParagraph.png"); + if (!fontCollection->fontsFound()) return; + + const char* text = "012 34"; + + ParagraphStyle paragraph_style; + paragraph_style.turnHintingOff(); + paragraph_style.setMaxLines(14); + ParagraphBuilderImpl builder(paragraph_style, fontCollection); + + TextStyle text_style; + text_style.setFontFamilies({SkString("Roboto")}); + text_style.setColor(SK_ColorBLACK); + text_style.setFontSize(26); + text_style.setWordSpacing(5); + text_style.setLetterSpacing(1); + text_style.setDecoration(TextDecoration::kUnderline); + text_style.setDecorationColor(SK_ColorBLACK); + builder.pushStyle(text_style); + builder.addText(text); + + PlaceholderStyle placeholder(55, 50, PlaceholderAlignment::kBelowBaseline, TextBaseline::kAlphabetic, 903129.129308f); + builder.addPlaceholder(placeholder); + builder.addText(text); + + builder.pop(); + + auto paragraph = builder.Build(); + paragraph->layout(TestCanvasWidth); + paragraph->paint(canvas.get(), 0, 0); + + auto boxes = paragraph->GetRectsForPlaceholders(); + canvas.drawRects(SK_ColorRED, boxes); + + REPORTER_ASSERT(reporter, boxes.size() == 1); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 90.921f - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 24, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f + 55 - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 74, EPSILON100)); + + RectHeightStyle rect_height_style = RectHeightStyle::kTight; + RectWidthStyle rect_width_style = RectWidthStyle::kTight; + + boxes = paragraph->getRectsForRange(5, 6, rect_height_style, rect_width_style); + canvas.drawRects(SK_ColorBLUE, boxes); + + REPORTER_ASSERT(reporter, boxes.size() == 1); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 75.324f - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), -0.121f, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f - 0.5f, EPSILON2)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 30.347f, EPSILON100)); } + +// Checked: DIFF+ (half of the letter spacing before the text???) DEF_TEST(SkParagraph_InlinePlaceholderBottomParagraph, reporter) { - SkDebugf("Not implemented yet.\n"); + sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>(); + TestCanvas canvas("SkParagraph_InlinePlaceholderBottomParagraph.png"); + if (!fontCollection->fontsFound()) return; + + const char* text = "012 34"; + + ParagraphStyle paragraph_style; + paragraph_style.turnHintingOff(); + paragraph_style.setMaxLines(14); + ParagraphBuilderImpl builder(paragraph_style, fontCollection); + + TextStyle text_style; + text_style.setFontFamilies({SkString("Roboto")}); + text_style.setColor(SK_ColorBLACK); + text_style.setFontSize(26); + text_style.setWordSpacing(5); + text_style.setLetterSpacing(1); + text_style.setDecoration(TextDecoration::kUnderline); + text_style.setDecorationColor(SK_ColorBLACK); + builder.pushStyle(text_style); + builder.addText(text); + + PlaceholderStyle placeholder(55, 50, PlaceholderAlignment::kBottom, TextBaseline::kAlphabetic, 0); + builder.addPlaceholder(placeholder); + builder.addText(text); + + builder.pop(); + + auto paragraph = builder.Build(); + paragraph->layout(TestCanvasWidth); + paragraph->paint(canvas.get(), 0, 0); + + RectHeightStyle rect_height_style = RectHeightStyle::kTight; + RectWidthStyle rect_width_style = RectWidthStyle::kTight; + + auto boxes = paragraph->GetRectsForPlaceholders(); + canvas.drawRects(SK_ColorRED, boxes); + REPORTER_ASSERT(reporter, boxes.size() == 1); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 90.921f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 0, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f + 55 - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 50, EPSILON100)); + + boxes = paragraph->getRectsForRange(0, 1, rect_height_style, rect_width_style); + canvas.drawRects(SK_ColorBLUE, boxes); + REPORTER_ASSERT(reporter, boxes.size() == 1); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 0.5f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 19.531f, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 16.097f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 50, EPSILON100)); } + +// Checked: DIFF+ (half of the letter spacing before the text???) DEF_TEST(SkParagraph_InlinePlaceholderTopParagraph, reporter) { - SkDebugf("Not implemented yet.\n"); + sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>(); + TestCanvas canvas("SkParagraph_InlinePlaceholderTopParagraph.png"); + if (!fontCollection->fontsFound()) return; + + const char* text = "012 34"; + + ParagraphStyle paragraph_style; + paragraph_style.turnHintingOff(); + paragraph_style.setMaxLines(14); + ParagraphBuilderImpl builder(paragraph_style, fontCollection); + + TextStyle text_style; + text_style.setFontFamilies({SkString("Roboto")}); + text_style.setColor(SK_ColorBLACK); + text_style.setFontSize(26); + text_style.setWordSpacing(5); + text_style.setLetterSpacing(1); + text_style.setDecoration(TextDecoration::kUnderline); + text_style.setDecorationColor(SK_ColorBLACK); + builder.pushStyle(text_style); + builder.addText(text); + + PlaceholderStyle placeholder(55, 50, PlaceholderAlignment::kTop, TextBaseline::kAlphabetic, 0); + builder.addPlaceholder(placeholder); + builder.addText(text); + + builder.pop(); + + auto paragraph = builder.Build(); + paragraph->layout(TestCanvasWidth); + paragraph->paint(canvas.get(), 0, 0); + + RectHeightStyle rect_height_style = RectHeightStyle::kTight; + RectWidthStyle rect_width_style = RectWidthStyle::kTight; + + auto boxes = paragraph->GetRectsForPlaceholders(); + canvas.drawRects(SK_ColorRED, boxes); + REPORTER_ASSERT(reporter, boxes.size() == 1); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 90.921f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 0, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f + 55 - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 50, EPSILON100)); + + boxes = paragraph->getRectsForRange(0, 1, rect_height_style, rect_width_style); + canvas.drawRects(SK_ColorBLUE, boxes); + REPORTER_ASSERT(reporter, boxes.size() == 1); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 0.5f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 0, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 16.097f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 30.468f, EPSILON100)); } + +// Checked: DIFF+ (half of the letter spacing before the text???) DEF_TEST(SkParagraph_InlinePlaceholderMiddleParagraph, reporter) { - SkDebugf("Not implemented yet.\n"); + sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>(); + TestCanvas canvas("SkParagraph_InlinePlaceholderMiddleParagraph.png"); + if (!fontCollection->fontsFound()) return; + + const char* text = "012 34"; + + ParagraphStyle paragraph_style; + paragraph_style.turnHintingOff(); + paragraph_style.setMaxLines(14); + ParagraphBuilderImpl builder(paragraph_style, fontCollection); + + TextStyle text_style; + text_style.setFontFamilies({SkString("Roboto")}); + text_style.setColor(SK_ColorBLACK); + text_style.setFontSize(26); + text_style.setWordSpacing(5); + text_style.setLetterSpacing(1); + text_style.setDecoration(TextDecoration::kUnderline); + text_style.setDecorationColor(SK_ColorBLACK); + builder.pushStyle(text_style); + builder.addText(text); + + PlaceholderStyle placeholder(55, 50, PlaceholderAlignment::kMiddle, TextBaseline::kAlphabetic, 0); + builder.addPlaceholder(placeholder); + builder.addText(text); + + builder.pop(); + + auto paragraph = builder.Build(); + paragraph->layout(TestCanvasWidth); + paragraph->paint(canvas.get(), 0, 0); + + RectHeightStyle rect_height_style = RectHeightStyle::kTight; + RectWidthStyle rect_width_style = RectWidthStyle::kTight; + + auto boxes = paragraph->GetRectsForPlaceholders(); + canvas.drawRects(SK_ColorRED, boxes); + REPORTER_ASSERT(reporter, boxes.size() == 1); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 90.921f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 0, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f + 55 - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 50, EPSILON100)); + + boxes = paragraph->getRectsForRange(5, 6, rect_height_style, rect_width_style); + canvas.drawRects(SK_ColorBLUE, boxes); + REPORTER_ASSERT(reporter, boxes.size() == 1); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 75.324f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 9.765f, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 40.234f, EPSILON100)); } -DEF_TEST(SkParagraph_InlinePlaceholderIdeographicBaselineParagraphh, reporter) { - SkDebugf("Not implemented yet.\n"); + +// Checked: DIFF+ (half of the letter spacing before the text???) +DEF_TEST(SkParagraph_InlinePlaceholderIdeographicBaselineParagraph, reporter) { + sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>(); + TestCanvas canvas("SkParagraph_InlinePlaceholderIdeographicBaselineParagraph.png"); + if (!fontCollection->fontsFound()) return; + + const char* text = "給能上目秘使"; + + ParagraphStyle paragraph_style; + paragraph_style.turnHintingOff(); + paragraph_style.setMaxLines(14); + ParagraphBuilderImpl builder(paragraph_style, fontCollection); + + TextStyle text_style; + text_style.setFontFamilies({SkString("Source Han Serif CN")}); + text_style.setColor(SK_ColorBLACK); + text_style.setFontSize(26); + text_style.setWordSpacing(5); + text_style.setLetterSpacing(1); + text_style.setDecoration(TextDecoration::kUnderline); + text_style.setDecorationColor(SK_ColorBLACK); + builder.pushStyle(text_style); + builder.addText(text); + PlaceholderStyle placeholder(55, 50, PlaceholderAlignment::kBaseline, TextBaseline::kIdeographic, 38.347f); + builder.addPlaceholder(placeholder); + builder.addText(text); + + builder.pop(); + + auto paragraph = builder.Build(); + paragraph->layout(TestCanvasWidth); + paragraph->paint(canvas.get(), 0, 0); + + RectHeightStyle rect_height_style = RectHeightStyle::kTight; + RectWidthStyle rect_width_style = RectWidthStyle::kTight; + + auto boxes = paragraph->GetRectsForPlaceholders(); + canvas.drawRects(SK_ColorRED, boxes); + REPORTER_ASSERT(reporter, boxes.size() == 1); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 162.5f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 0, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 162.5f + 55 - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 50, EPSILON100)); + + boxes = paragraph->getRectsForRange(5, 6, rect_height_style, rect_width_style); + canvas.drawRects(SK_ColorBLUE, boxes); + REPORTER_ASSERT(reporter, boxes.size() == 1); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 135.5f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 4.703f, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 162.5f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 42.065f, EPSILON100)); } + +// Checked: DIFF+ (half of the letter spacing before the text???) DEF_TEST(SkParagraph_InlinePlaceholderBreakParagraph, reporter) { - SkDebugf("Not implemented yet.\n"); + sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>(); + TestCanvas canvas("SkParagraph_InlinePlaceholderBreakParagraph.png"); + if (!fontCollection->fontsFound()) return; + + const char* text = "012 34"; + + ParagraphStyle paragraph_style; + paragraph_style.turnHintingOff(); + paragraph_style.setMaxLines(14); + ParagraphBuilderImpl builder(paragraph_style, fontCollection); + + TextStyle text_style; + text_style.setFontFamilies({SkString("Roboto")}); + text_style.setColor(SK_ColorBLACK); + text_style.setFontSize(26); + text_style.setWordSpacing(5); + text_style.setLetterSpacing(1); + text_style.setDecoration(TextDecoration::kUnderline); + text_style.setDecorationColor(SK_ColorBLACK); + builder.pushStyle(text_style); + builder.addText(text); + + PlaceholderStyle placeholder1(50, 50, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 50); + PlaceholderStyle placeholder2(25, 25, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 12.5f); + + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder2); + builder.addPlaceholder(placeholder1); + builder.addText(text); + + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder2); // 4 + 1 + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder2); // 6 + 1 + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder2); // 7 + 1 + + builder.addPlaceholder(placeholder1); + builder.addText(text); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder2); + + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + + builder.addPlaceholder(placeholder2); + builder.addPlaceholder(placeholder1); + + builder.addText(text); + + builder.addPlaceholder(placeholder2); + + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + + builder.pop(); + + auto paragraph = builder.Build(); + paragraph->layout(TestCanvasWidth - 100); + paragraph->paint(canvas.get(), 0, 0); + + RectHeightStyle rect_height_style = RectHeightStyle::kTight; + RectWidthStyle rect_width_style = RectWidthStyle::kTight; + + auto boxes = paragraph->getRectsForRange(0, 3, rect_height_style, rect_width_style); + canvas.drawRects(SK_ColorRED, boxes); + REPORTER_ASSERT(reporter, boxes.size() == 1); + + boxes = paragraph->getRectsForRange(175, 176, rect_height_style, rect_width_style); + canvas.drawRects(SK_ColorGREEN, boxes); + REPORTER_ASSERT(reporter, boxes.size() == 1); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 31.695f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 218.531f, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 47.292f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 249, EPSILON100)); + + boxes = paragraph->GetRectsForPlaceholders(); + canvas.drawRects(SK_ColorRED, boxes); + + boxes = paragraph->getRectsForRange(4, 45, rect_height_style, rect_width_style); + canvas.drawRects(SK_ColorBLUE, boxes); + REPORTER_ASSERT(reporter, boxes.size() == 30); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 59.726f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 26.378f, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 56.847f, EPSILON100)); + + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[11].rect.left(), 606.343f - 0.5f, EPSILON20)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[11].rect.top(), 38, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[11].rect.right(), 631.343f - 0.5f, EPSILON20)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[11].rect.bottom(), 63, EPSILON100)); + + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[17].rect.left(), 0.5f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[17].rect.top(), 63.5f, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[17].rect.right(), 50.5f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[17].rect.bottom(), 113.5f, EPSILON100)); } + +// Checked: DIFF+ (half of the letter spacing before the text???) DEF_TEST(SkParagraph_InlinePlaceholderGetRectsParagraph, reporter) { - SkDebugf("Not implemented yet.\n"); + sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>(); + TestCanvas canvas("SkParagraph_InlinePlaceholderGetRectsParagraph.png"); + if (!fontCollection->fontsFound()) return; + + const char* text = "012 34"; + + ParagraphStyle paragraph_style; + paragraph_style.turnHintingOff(); + paragraph_style.setMaxLines(14); + ParagraphBuilderImpl builder(paragraph_style, fontCollection); + + TextStyle text_style; + text_style.setFontFamilies({SkString("Roboto")}); + text_style.setColor(SK_ColorBLACK); + text_style.setFontSize(26); + text_style.setWordSpacing(5); + text_style.setLetterSpacing(1); + text_style.setDecoration(TextDecoration::kUnderline); + text_style.setDecorationColor(SK_ColorBLACK); + builder.pushStyle(text_style); + builder.addText(text); + + PlaceholderStyle placeholder1(50, 50, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 50); + PlaceholderStyle placeholder2(5, 20, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 10); + + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder2); // 8 + 1 + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder2); // 5 + 1 + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder1); // 8 + 0 + + builder.addText(text); + + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder2); + builder.addPlaceholder(placeholder2); // 1 + 2 + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder2); + builder.addPlaceholder(placeholder2); // 1 + 2 + + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); + builder.addText(text); // 11 + + builder.addPlaceholder(placeholder2); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder2); + builder.addPlaceholder(placeholder1); + builder.addPlaceholder(placeholder2); + + builder.addText(text); + + builder.pop(); + + auto paragraph = builder.Build(); + paragraph->layout(TestCanvasWidth); + paragraph->paint(canvas.get(), 0, 0); + + RectHeightStyle rect_height_style = RectHeightStyle::kMax; + RectWidthStyle rect_width_style = RectWidthStyle::kTight; + + auto boxes = paragraph->GetRectsForPlaceholders(); + canvas.drawRects(SK_ColorRED, boxes); + + REPORTER_ASSERT(reporter, boxes.size() == 34); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 90.921f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 0, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 140.921f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 50, EPSILON100)); + + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[16].rect.left(), 800.921f - 0.5f, EPSILON20)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[16].rect.top(), 0, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[16].rect.right(), 850.921f - 0.5f, EPSILON20)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[16].rect.bottom(), 50, EPSILON100)); + + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[33].rect.left(), 503.382f - 0.5f, EPSILON10)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[33].rect.top(), 160, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[33].rect.right(), 508.382f - 0.5f, EPSILON10)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[33].rect.bottom(), 180, EPSILON100)); + + boxes = paragraph->getRectsForRange(30, 50, rect_height_style, rect_width_style); + canvas.drawRects(SK_ColorBLUE, boxes); + + REPORTER_ASSERT(reporter, boxes.size() == 8); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 216.097f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 60, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 290.921f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 120, EPSILON100)); + + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.left(), 290.921f - 0.5f, EPSILON20)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.top(), 60, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.right(), 340.921f - 0.5f, EPSILON20)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.bottom(), 120, EPSILON100)); + + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[2].rect.left(), 340.921f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[2].rect.top(), 60, EPSILON100)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[2].rect.right(), 345.921f - 0.5f, EPSILON50)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[2].rect.bottom(), 120, EPSILON100)); } // Checked: NO DIFF @@ -1016,9 +1733,8 @@ } } -// TODO: Fix decorations DEF_TEST(SkParagraph_WavyDecorationParagraph, reporter) { - SkDebugf("Not implemented yet.\n"); + SkDebugf("TODO: Fix decorations\n"); } // Checked: NO DIFF @@ -2694,8 +3410,9 @@ canvas.drawRects(SK_ColorGREEN, result); } -// TODO: Not soon -DEF_TEST(SkParagraph_HyphenBreakParagraph, reporter) { SkDebugf("Not implemented yet.\n"); } +DEF_TEST(SkParagraph_HyphenBreakParagraph, reporter) { + SkDebugf("Hyphens are not implemented, and will not be implemented soon.\n"); +} // Checked: DIFF (line breaking) DEF_TEST(SkParagraph_RepeatLayoutParagraph, reporter) { @@ -3569,7 +4286,9 @@ } // TODO: Implement font features -DEF_TEST(SkParagraph_FontFeaturesParagraph, reporter) { SkDebugf("Not implemented yet.\n"); } +DEF_TEST(SkParagraph_FontFeaturesParagraph, reporter) { + SkDebugf("TODO: Font features\n"); +} // Not in Minikin DEF_TEST(SkParagraph_WhitespacesInMultipleFonts, reporter) {