Reshuffling bidi regions + empty text sizes + some font resolution bugs
Change-Id: Icc0d688985de7253f3113e5c9657544f68b4e01d Reviewed-on: https://skia-review.googlesource.com/c/skia/+/236217 Commit-Queue: Julia Lavrova <jlavrova@google.com> Reviewed-by: Ben Wagner <bungeman@google.com>
This commit is contained in:
parent
f643ba7a90
commit
526df26b03
@ -31,9 +31,10 @@ public:
|
||||
|
||||
sk_sp<SkFontMgr> geFallbackManager() const { return fDefaultFontManager; }
|
||||
|
||||
sk_sp<SkTypeface> matchTypeface(const char familyName[], SkFontStyle fontStyle);
|
||||
sk_sp<SkTypeface> matchDefaultTypeface(SkFontStyle fontStyle);
|
||||
sk_sp<SkTypeface> matchTypeface(const char familyName[], SkFontStyle fontStyle, const SkString& locale);
|
||||
sk_sp<SkTypeface> matchDefaultTypeface(SkFontStyle fontStyle, const SkString& locale);
|
||||
sk_sp<SkTypeface> defaultFallback(SkUnichar unicode, SkFontStyle fontStyle, const SkString& locale);
|
||||
sk_sp<SkTypeface> defaultFallback();
|
||||
|
||||
void disableFontFallback();
|
||||
bool fontFallbackEnabled() { return fEnableFontFallback; }
|
||||
|
@ -65,9 +65,9 @@ std::vector<sk_sp<SkFontMgr>> FontCollection::getFontManagerOrder() const {
|
||||
return order;
|
||||
}
|
||||
|
||||
sk_sp<SkTypeface> FontCollection::matchTypeface(const char familyName[], SkFontStyle fontStyle) {
|
||||
sk_sp<SkTypeface> FontCollection::matchTypeface(const char familyName[], SkFontStyle fontStyle, const SkString& locale) {
|
||||
// Look inside the font collections cache first
|
||||
FamilyKey familyKey(familyName, "en", fontStyle);
|
||||
FamilyKey familyKey(familyName, locale.c_str(), fontStyle);
|
||||
auto found = fTypefaces.find(familyKey);
|
||||
if (found) {
|
||||
return *found;
|
||||
@ -87,16 +87,17 @@ sk_sp<SkTypeface> FontCollection::matchTypeface(const char familyName[], SkFontS
|
||||
sk_sp<SkTypeface> match(set->matchStyle(fontStyle));
|
||||
if (match) {
|
||||
typeface = std::move(match);
|
||||
return typeface;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
fTypefaces.set(familyKey, typeface);
|
||||
return typeface;
|
||||
}
|
||||
|
||||
sk_sp<SkTypeface> FontCollection::matchDefaultTypeface(SkFontStyle fontStyle) {
|
||||
sk_sp<SkTypeface> FontCollection::matchDefaultTypeface(SkFontStyle fontStyle, const SkString& locale) {
|
||||
// Look inside the font collections cache first
|
||||
FamilyKey familyKey(fDefaultFamilyName.c_str(), "en", fontStyle);
|
||||
FamilyKey familyKey(fDefaultFamilyName.c_str(), locale.c_str(), fontStyle);
|
||||
auto found = fTypefaces.find(familyKey);
|
||||
if (found) {
|
||||
return *found;
|
||||
@ -116,13 +117,15 @@ sk_sp<SkTypeface> FontCollection::matchDefaultTypeface(SkFontStyle fontStyle) {
|
||||
sk_sp<SkTypeface> match(set->matchStyle(fontStyle));
|
||||
if (match) {
|
||||
typeface = std::move(match);
|
||||
return typeface;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
fTypefaces.set(familyKey, typeface);
|
||||
return typeface;
|
||||
}
|
||||
|
||||
// Find ANY font in available font managers that resolves the unicode codepoint
|
||||
sk_sp<SkTypeface> FontCollection::defaultFallback(SkUnichar unicode, SkFontStyle fontStyle, const SkString& locale) {
|
||||
|
||||
for (const auto& manager : this->getFontManagerOrder()) {
|
||||
@ -136,14 +139,18 @@ sk_sp<SkTypeface> FontCollection::defaultFallback(SkUnichar unicode, SkFontStyle
|
||||
return typeface;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sk_sp<SkTypeface> FontCollection::defaultFallback() {
|
||||
if (fDefaultFontManager == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = fDefaultFontManager->matchFamilyStyle(fDefaultFamilyName.c_str(), fontStyle);
|
||||
auto result = fDefaultFontManager->matchFamilyStyle(fDefaultFamilyName.c_str(), SkFontStyle());
|
||||
return sk_ref_sp<SkTypeface>(result);
|
||||
}
|
||||
|
||||
|
||||
void FontCollection::disableFontFallback() { fEnableFontFallback = false; }
|
||||
|
||||
} // namespace textlayout
|
||||
|
@ -37,6 +37,15 @@ bool FontResolver::findNext(const char* codepoint, SkFont* font, SkScalar* heigh
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FontResolver::isEmpty() {
|
||||
return fFontIterator == fFontSwitches.end();
|
||||
}
|
||||
|
||||
void FontResolver::getFirstFont(SkFont* font, SkScalar* height) {
|
||||
*font = fFirstResolvedFont.fFont;
|
||||
*height = fFirstResolvedFont.fHeight;
|
||||
}
|
||||
|
||||
void FontResolver::findAllFontsForStyledBlock(const TextStyle& style, TextRange textRange) {
|
||||
fCodepoints.reset();
|
||||
fCharacters.reset();
|
||||
@ -51,11 +60,13 @@ void FontResolver::findAllFontsForStyledBlock(const TextStyle& style, TextRange
|
||||
fCodepoints.emplace_back(utf8_next(¤t, end));
|
||||
fUnresolvedIndexes.emplace_back(fUnresolvedIndexes.size());
|
||||
}
|
||||
fUnresolvedCodepoints.push_back_n(fUnresolvedIndexes.size());
|
||||
fUnresolved = fCodepoints.size();
|
||||
|
||||
// Walk through all available fonts to resolve the block
|
||||
auto wasUnresolved = fUnresolved;
|
||||
for (auto& fontFamily : style.getFontFamilies()) {
|
||||
auto typeface = fFontCollection->matchTypeface(fontFamily.c_str(), style.getFontStyle());
|
||||
auto typeface = fFontCollection->matchTypeface(fontFamily.c_str(), style.getFontStyle(), style.getLocale());
|
||||
if (typeface.get() == nullptr) {
|
||||
continue;
|
||||
}
|
||||
@ -68,53 +79,45 @@ void FontResolver::findAllFontsForStyledBlock(const TextStyle& style, TextRange
|
||||
}
|
||||
}
|
||||
|
||||
if (fUnresolved != wasUnresolved || allWhitespaces()) {
|
||||
addResolvedWhitespacesToMapping();
|
||||
wasUnresolved = fUnresolved;
|
||||
}
|
||||
|
||||
if (fUnresolved > 0) {
|
||||
auto typeface = fFontCollection->matchDefaultTypeface(style.getFontStyle());
|
||||
if (typeface.get() != nullptr) {
|
||||
// Resolve all unresolved characters
|
||||
// Check the default font
|
||||
auto typeface =
|
||||
fFontCollection->matchDefaultTypeface(style.getFontStyle(), style.getLocale());
|
||||
if (typeface != nullptr) {
|
||||
auto font = makeFont(typeface, style.getFontSize(), style.getHeight());
|
||||
resolveAllCharactersByFont(font);
|
||||
}
|
||||
if (fUnresolved != wasUnresolved || allWhitespaces()) {
|
||||
addResolvedWhitespacesToMapping();
|
||||
wasUnresolved = fUnresolved;
|
||||
}
|
||||
}
|
||||
|
||||
addResolvedWhitespacesToMapping();
|
||||
|
||||
if (fUnresolved > 0 && fFontCollection->fontFallbackEnabled()) {
|
||||
while (fUnresolved > 0) {
|
||||
while (fUnresolved > 0 && !allWhitespaces()) {
|
||||
auto unicode = firstUnresolved();
|
||||
auto typeface = fFontCollection->defaultFallback(unicode, style.getFontStyle(), style.getLocale());
|
||||
if (typeface == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
SkString name;
|
||||
typeface->getFamilyName(&name);
|
||||
auto font = makeFont(typeface, style.getFontSize(), style.getHeight());
|
||||
if (!resolveAllCharactersByFont(font)) {
|
||||
auto newResolved = resolveAllCharactersByFont(font);
|
||||
if (newResolved == 0) {
|
||||
// Not a single unicode character was resolved
|
||||
break;
|
||||
}
|
||||
SkString name;
|
||||
typeface->getFamilyName(&name);
|
||||
SkDebugf("Default font fallback resolution: %s\n", name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// In case something still unresolved
|
||||
if (fResolvedFonts.count() == 0) {
|
||||
auto result = fFontCollection->defaultFallback(firstUnresolved(), style.getFontStyle(), style.getLocale());
|
||||
if (result == nullptr) {
|
||||
if (fText.size() > 0) {
|
||||
SkDebugf("No fallback!!!\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
makeFont(result, style.getFontSize(), style.getHeight());
|
||||
if (fText.size() > 0) {
|
||||
if (fFirstResolvedFont.fFont.getTypeface() != nullptr) {
|
||||
SkString name;
|
||||
fFirstResolvedFont.fFont.getTypeface()->getFamilyName(&name);
|
||||
SkDebugf("Urgent font resolution: %s\n", name.c_str());
|
||||
} else {
|
||||
SkDebugf("No font!!!\n");
|
||||
}
|
||||
if (fUnresolved != wasUnresolved || allWhitespaces()) {
|
||||
addResolvedWhitespacesToMapping();
|
||||
wasUnresolved = fUnresolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -144,28 +147,34 @@ size_t FontResolver::resolveAllCharactersByFont(const FontDescr& font) {
|
||||
if (fWhitespaces.find(w) == nullptr) {
|
||||
fWhitespaces.set(w, font);
|
||||
}
|
||||
fUnresolvedIndexes[stillUnresolved++] = w;
|
||||
fUnresolvedCodepoints.emplace_back(fCodepoints[w]);
|
||||
fUnresolvedIndexes[stillUnresolved] = w;
|
||||
fUnresolvedCodepoints[stillUnresolved] = fCodepoints[w];
|
||||
++stillUnresolved;
|
||||
}
|
||||
} else {
|
||||
//SkDebugf("Resolved %d @%d\n", font.fFont.getTypeface()->uniqueID(), resolved.start);
|
||||
fFontMapping.set(fCharacters[resolved.start] - fText.begin(), font);
|
||||
}
|
||||
};
|
||||
|
||||
// Try to resolve all the unresolved unicode points
|
||||
SkString name;
|
||||
font.fFont.getTypeface()->getFamilyName(&name);
|
||||
for (size_t i = 0; i < glyphs.size(); ++i) {
|
||||
auto glyph = glyphs[i];
|
||||
auto index = fUnresolvedIndexes[i];
|
||||
auto codepoint = fCodepoints[index];
|
||||
|
||||
if (glyph == 0) {
|
||||
if (u_hasBinaryProperty(codepoint, UCHAR_BIDI_CONTROL)) {
|
||||
// Skip control characters - they don't have to be resolved
|
||||
} else if (glyph == 0) {
|
||||
processRuns();
|
||||
|
||||
resolved = SkRange<size_t>(0, 0);
|
||||
whitespaces = SkRange<size_t>(0, 0);
|
||||
|
||||
fUnresolvedIndexes[stillUnresolved++] = index;
|
||||
fUnresolvedCodepoints.emplace_back(fCodepoints[index]);
|
||||
fUnresolvedIndexes[stillUnresolved] = index;
|
||||
fUnresolvedCodepoints[stillUnresolved] = codepoint;
|
||||
++stillUnresolved;
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -175,7 +184,7 @@ size_t FontResolver::resolveAllCharactersByFont(const FontDescr& font) {
|
||||
processRuns();
|
||||
resolved = SkRange<size_t>(index, index + 1);
|
||||
}
|
||||
if (u_isUWhiteSpace(fCodepoints[index])) {
|
||||
if (u_isUWhiteSpace(codepoint)) {
|
||||
if (index == whitespaces.end) {
|
||||
++whitespaces.end;
|
||||
} else {
|
||||
@ -191,7 +200,7 @@ size_t FontResolver::resolveAllCharactersByFont(const FontDescr& font) {
|
||||
|
||||
size_t wasUnresolved = fUnresolved;
|
||||
fUnresolved = stillUnresolved;
|
||||
return fUnresolved < wasUnresolved;
|
||||
return wasUnresolved - stillUnresolved;
|
||||
}
|
||||
|
||||
void FontResolver::addResolvedWhitespacesToMapping() {
|
||||
@ -226,16 +235,57 @@ FontDescr FontResolver::makeFont(sk_sp<SkTypeface> typeface, SkScalar size, SkSc
|
||||
|
||||
SkUnichar FontResolver::firstUnresolved() {
|
||||
if (fUnresolved == 0) return 0;
|
||||
return fUnresolvedCodepoints[0];
|
||||
}
|
||||
|
||||
bool firstTry = fUnresolved == fCodepoints.size();
|
||||
auto index = firstTry ? 0 : fUnresolvedIndexes[0];
|
||||
return fCodepoints[index];
|
||||
void FontResolver::setLastResortFont() {
|
||||
TextStyle foundStyle;
|
||||
sk_sp<SkTypeface> typeface = nullptr;
|
||||
for (auto& style : fStyles) {
|
||||
for (auto& fontFamily : style.fStyle.getFontFamilies()) {
|
||||
typeface = fFontCollection->matchTypeface(fontFamily.c_str(), style.fStyle.getFontStyle(), style.fStyle.getLocale());
|
||||
if (typeface.get() != nullptr) {
|
||||
foundStyle = style.fStyle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (typeface != nullptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (typeface == nullptr) {
|
||||
for (auto& fontFamily : fDefaultStyle.getFontFamilies()) {
|
||||
typeface = fFontCollection->matchTypeface(fontFamily.c_str(), fDefaultStyle.getFontStyle(), fDefaultStyle.getLocale());
|
||||
if (typeface.get() != nullptr) {
|
||||
foundStyle = fDefaultStyle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeface == nullptr) {
|
||||
foundStyle = fStyles.empty() ? fDefaultStyle : fStyles.front().fStyle;
|
||||
typeface = fFontCollection->defaultFallback(0, foundStyle.getFontStyle(), foundStyle.getLocale());
|
||||
}
|
||||
|
||||
if (typeface == nullptr) {
|
||||
typeface = fFontCollection->defaultFallback();
|
||||
}
|
||||
|
||||
fFirstResolvedFont = makeFont(typeface, foundStyle.getFontSize(), foundStyle.getHeight());
|
||||
fFirstResolvedFont.fStart = 0;
|
||||
}
|
||||
|
||||
void FontResolver::findAllFontsForAllStyledBlocks(ParagraphImpl* master) {
|
||||
fFontCollection = master->fontCollection();
|
||||
fStyles = master->styles();
|
||||
fText = master->text();
|
||||
fDefaultStyle = master->paragraphStyle().getTextStyle();
|
||||
|
||||
if (fText.empty()) {
|
||||
setLastResortFont();
|
||||
return;
|
||||
}
|
||||
|
||||
Block combinedBlock;
|
||||
for (auto& block : fStyles) {
|
||||
@ -283,17 +333,26 @@ void FontResolver::findAllFontsForAllStyledBlocks(ParagraphImpl* master) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fFontSwitches.emplace_back(*prev);
|
||||
|
||||
if (prev->fFont.getTypeface() != nullptr) {
|
||||
fFontSwitches.emplace_back(*prev);
|
||||
}
|
||||
prev = found;
|
||||
prev->fStart = &ch - fText.begin();
|
||||
}
|
||||
|
||||
if (prev == nullptr) {
|
||||
fFirstResolvedFont.fStart = 0;
|
||||
prev = &fFirstResolvedFont;
|
||||
if (prev != nullptr) {
|
||||
if (prev->fFont.getTypeface() != nullptr) {
|
||||
fFontSwitches.emplace_back(*prev);
|
||||
}
|
||||
}
|
||||
fFontSwitches.emplace_back(*prev);
|
||||
|
||||
if (fFontSwitches.empty()) {
|
||||
setLastResortFont();
|
||||
if (fFirstResolvedFont.fFont.getTypeface() != nullptr) {
|
||||
fFontSwitches.emplace_back(fFirstResolvedFont);
|
||||
}
|
||||
}
|
||||
|
||||
fFontIterator = fFontSwitches.begin();
|
||||
}
|
||||
} // namespace textlayout
|
||||
|
@ -35,14 +35,20 @@ public:
|
||||
|
||||
void findAllFontsForAllStyledBlocks(ParagraphImpl* master);
|
||||
bool findNext(const char* codepoint, SkFont* font, SkScalar* height);
|
||||
void getFirstFont(SkFont* font, SkScalar* height);
|
||||
|
||||
const SkTArray<FontDescr>& switches() const { return fFontSwitches; }
|
||||
|
||||
bool isEmpty();
|
||||
|
||||
bool allWhitespaces() const { return fUnresolved == SkToU32(fWhitespaces.count()); }
|
||||
|
||||
private:
|
||||
void findAllFontsForStyledBlock(const TextStyle& style, TextRange textRange);
|
||||
FontDescr makeFont(sk_sp<SkTypeface> typeface, SkScalar size, SkScalar height);
|
||||
size_t resolveAllCharactersByFont(const FontDescr& fontDescr);
|
||||
void addResolvedWhitespacesToMapping();
|
||||
void setLastResortFont();
|
||||
|
||||
struct Hash {
|
||||
uint32_t operator()(const FontDescr& key) const {
|
||||
@ -57,6 +63,7 @@ private:
|
||||
sk_sp<FontCollection> fFontCollection;
|
||||
SkSpan<const char> fText;
|
||||
SkSpan<Block> fStyles;
|
||||
TextStyle fDefaultStyle;
|
||||
|
||||
SkTArray<FontDescr> fFontSwitches;
|
||||
FontDescr* fFontIterator;
|
||||
|
@ -89,7 +89,6 @@ ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
|
||||
ParagraphImpl::~ParagraphImpl() = default;
|
||||
|
||||
void ParagraphImpl::layout(SkScalar width) {
|
||||
|
||||
if (fState < kShaped) {
|
||||
// Layout marked as dirty for performance/testing reasons
|
||||
this->fRuns.reset();
|
||||
@ -104,14 +103,18 @@ void ParagraphImpl::layout(SkScalar width) {
|
||||
|
||||
if (!this->shapeTextIntoEndlessLine()) {
|
||||
// Apply the last style to the empty text
|
||||
FontIterator font(SkMakeSpan(" "), &fFontResolver);
|
||||
// Get the font metrics
|
||||
font.consume();
|
||||
LineMetrics lineMetrics(font.currentFont(), paragraphStyle().getStrutStyle().getForceStrutHeight());
|
||||
SkFont font;
|
||||
SkScalar height;
|
||||
fFontResolver.getFirstFont(&font, &height);
|
||||
LineMetrics lineMetrics(font, paragraphStyle().getStrutStyle().getForceStrutHeight());
|
||||
// Set the important values that are not zero
|
||||
fHeight = lineMetrics.height();
|
||||
fWidth = 0;
|
||||
fHeight = lineMetrics.height() * (height == 0 || height == 1 ? 1 : height);
|
||||
fAlphabeticBaseline = lineMetrics.alphabeticBaseline();
|
||||
fIdeographicBaseline = lineMetrics.ideographicBaseline();
|
||||
this->fOldWidth = width;
|
||||
this->fOldHeight = this->fHeight;
|
||||
return;
|
||||
}
|
||||
if (fState < kShaped) {
|
||||
fState = kShaped;
|
||||
@ -349,7 +352,7 @@ bool ParagraphImpl::shapeTextIntoEndlessLine() {
|
||||
// This is a pretty big step - resolving all characters against all given fonts
|
||||
fFontResolver.findAllFontsForAllStyledBlocks(this);
|
||||
|
||||
if (fText.size() == 0) {
|
||||
if (fText.size() == 0 || fFontResolver.switches().size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -499,7 +502,7 @@ void ParagraphImpl::resolveStrut() {
|
||||
|
||||
sk_sp<SkTypeface> typeface;
|
||||
for (auto& fontFamily : strutStyle.getFontFamilies()) {
|
||||
typeface = fFontCollection->matchTypeface(fontFamily.c_str(), strutStyle.getFontStyle());
|
||||
typeface = fFontCollection->matchTypeface(fontFamily.c_str(), strutStyle.getFontStyle(), SkString());
|
||||
if (typeface.get() != nullptr) {
|
||||
break;
|
||||
}
|
||||
@ -615,8 +618,13 @@ std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
|
||||
unsigned end,
|
||||
RectHeightStyle rectHeightStyle,
|
||||
RectWidthStyle rectWidthStyle) {
|
||||
markGraphemes();
|
||||
std::vector<TextBox> results;
|
||||
if (fText.isEmpty()) {
|
||||
results.emplace_back(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
|
||||
return results;
|
||||
}
|
||||
|
||||
markGraphemes();
|
||||
if (start >= end || start > fCodePoints.size() || end == 0) {
|
||||
return results;
|
||||
}
|
||||
@ -637,23 +645,41 @@ std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
|
||||
continue;
|
||||
}
|
||||
|
||||
SkScalar runOffset = line.calculateLeftVisualOffset(intersect);
|
||||
|
||||
// Found a line that intersects with the text
|
||||
auto firstBoxOnTheLine = results.size();
|
||||
auto paragraphTextDirection = paragraphStyle().getTextDirection();
|
||||
auto lineTextAlign = line.assumedTextAlign();
|
||||
Run* lastRun = nullptr;
|
||||
line.iterateThroughRuns(
|
||||
intersect,
|
||||
runOffset,
|
||||
true,
|
||||
[&results, &line, rectHeightStyle, this, paragraphTextDirection, lineTextAlign, &lastRun]
|
||||
(Run* run, size_t pos, size_t size, TextRange text, SkRect clip, SkScalar shift, bool clippingNeeded) {
|
||||
const Run* lastRun = nullptr;
|
||||
line.iterateThroughVisualRuns(true,
|
||||
[&](const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width) {
|
||||
|
||||
auto intersect = textRange * text;
|
||||
if (intersect.empty() || textRange.empty()) {
|
||||
auto context = line.measureTextInsideOneRun(textRange, run, runOffset, 0, true);
|
||||
*width = context.clip.width();
|
||||
return true;
|
||||
}
|
||||
|
||||
TextRange head;
|
||||
if (run->leftToRight() && textRange.start != intersect.start) {
|
||||
head = TextRange(textRange.start, intersect.start);
|
||||
*width = line.measureTextInsideOneRun(head, run, runOffset, 0, true).clip.width();
|
||||
} else if (!run->leftToRight() && textRange.end != intersect.end) {
|
||||
head = TextRange(intersect.end, textRange.end);
|
||||
*width = line.measureTextInsideOneRun(head, run, runOffset, 0, true).clip.width();
|
||||
} else {
|
||||
*width = 0;
|
||||
}
|
||||
runOffset += *width;
|
||||
|
||||
// Found a run that intersects with the text
|
||||
auto context = line.measureTextInsideOneRun(intersect, run, runOffset, 0, true);
|
||||
*width += context.clip.width();
|
||||
|
||||
SkRect clip = context.clip;
|
||||
SkRect trailingSpaces = SkRect::MakeEmpty();
|
||||
|
||||
SkScalar ghostSpacesRight = run->leftToRight() ? clip.right() - line.width() : 0;
|
||||
SkScalar ghostSpacesLeft = !run->leftToRight() ? clip.right() - line.width() : 0;
|
||||
SkScalar ghostSpacesRight = context.run->leftToRight() ? clip.right() - line.width() : 0;
|
||||
SkScalar ghostSpacesLeft = !context.run->leftToRight() ? clip.right() - line.width() : 0;
|
||||
|
||||
if (ghostSpacesRight + ghostSpacesLeft > 0) {
|
||||
if (lineTextAlign == TextAlign::kLeft && ghostSpacesLeft > 0) {
|
||||
@ -678,21 +704,21 @@ std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
|
||||
|
||||
} else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingTop) {
|
||||
if (&line != &fLines.front()) {
|
||||
clip.fTop -= line.sizes().runTop(run);
|
||||
clip.fTop -= line.sizes().runTop(context.run);
|
||||
}
|
||||
clip.fBottom -= line.sizes().runTop(run);
|
||||
clip.fBottom -= line.sizes().runTop(context.run);
|
||||
} else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingMiddle) {
|
||||
if (&line != &fLines.front()) {
|
||||
clip.fTop -= line.sizes().runTop(run) / 2;
|
||||
clip.fTop -= line.sizes().runTop(context.run) / 2;
|
||||
}
|
||||
if (&line == &fLines.back()) {
|
||||
clip.fBottom -= line.sizes().runTop(run);
|
||||
clip.fBottom -= line.sizes().runTop(context.run);
|
||||
} else {
|
||||
clip.fBottom -= line.sizes().runTop(run) / 2;
|
||||
clip.fBottom -= line.sizes().runTop(context.run) / 2;
|
||||
}
|
||||
} else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingBottom) {
|
||||
if (&line == &fLines.back()) {
|
||||
clip.fBottom -= line.sizes().runTop(run);
|
||||
clip.fBottom -= line.sizes().runTop(context.run);
|
||||
}
|
||||
} else if (rectHeightStyle == RectHeightStyle::kStrut) {
|
||||
auto strutStyle = this->paragraphStyle().getStrutStyle();
|
||||
@ -707,22 +733,24 @@ std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
|
||||
// Check if we can merge two boxes
|
||||
bool mergedBoxes = false;
|
||||
if (!results.empty() &&
|
||||
lastRun != nullptr && lastRun->placeholder() == nullptr && run->placeholder() == nullptr &&
|
||||
lastRun->lineHeight() == run->lineHeight() &&
|
||||
lastRun->font() == run->font()) {
|
||||
lastRun != nullptr && lastRun->placeholder() == nullptr && context.run->placeholder() == nullptr &&
|
||||
lastRun->lineHeight() == context.run->lineHeight() &&
|
||||
lastRun->font() == context.run->font()) {
|
||||
auto& lastBox = results.back();
|
||||
if (lastBox.rect.fTop == clip.fTop && lastBox.rect.fBottom == clip.fBottom &&
|
||||
(lastBox.rect.fLeft == clip.fRight || lastBox.rect.fRight == clip.fLeft)) {
|
||||
if (SkScalarNearlyEqual(lastBox.rect.fTop, clip.fTop) &&
|
||||
SkScalarNearlyEqual(lastBox.rect.fBottom, clip.fBottom) &&
|
||||
(SkScalarNearlyEqual(lastBox.rect.fLeft, clip.fRight) ||
|
||||
SkScalarNearlyEqual(lastBox.rect.fRight, clip.fLeft))) {
|
||||
lastBox.rect.fLeft = SkTMin(lastBox.rect.fLeft, clip.fLeft);
|
||||
lastBox.rect.fRight = SkTMax(lastBox.rect.fRight, clip.fRight);
|
||||
mergedBoxes = true;
|
||||
}
|
||||
}
|
||||
lastRun = run;
|
||||
lastRun = context.run;
|
||||
|
||||
if (!mergedBoxes) {
|
||||
results.emplace_back(
|
||||
clip, run->leftToRight() ? TextDirection::kLtr : TextDirection::kRtl);
|
||||
clip, context.run->leftToRight() ? TextDirection::kLtr : TextDirection::kRtl);
|
||||
}
|
||||
|
||||
if (trailingSpaces.width() > 0) {
|
||||
@ -757,23 +785,31 @@ std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
|
||||
|
||||
std::vector<TextBox> ParagraphImpl::GetRectsForPlaceholders() {
|
||||
std::vector<TextBox> boxes;
|
||||
|
||||
if (fText.isEmpty()) {
|
||||
boxes.emplace_back(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
|
||||
return boxes;
|
||||
}
|
||||
if (fPlaceholders.size() <= 1) {
|
||||
boxes.emplace_back(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
|
||||
return 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);
|
||||
line.iterateThroughVisualRuns(true,
|
||||
[&boxes, &line]
|
||||
(const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width) {
|
||||
auto context = line.measureTextInsideOneRun(textRange, run, runOffset, 0, true);
|
||||
*width = context.clip.width();
|
||||
if (run->placeholder() == nullptr) {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
if (run->textRange().width() == 0) {
|
||||
return true;
|
||||
}
|
||||
SkRect clip = context.clip;
|
||||
clip.offset(line.offset());
|
||||
boxes.emplace_back(clip, run->leftToRight() ? TextDirection::kLtr : TextDirection::kRtl);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return boxes;
|
||||
@ -781,8 +817,12 @@ std::vector<TextBox> ParagraphImpl::GetRectsForPlaceholders() {
|
||||
// TODO: Deal with RTL here
|
||||
PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) {
|
||||
|
||||
markGraphemes();
|
||||
PositionWithAffinity result(0, Affinity::kDownstream);
|
||||
if (fText.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
markGraphemes();
|
||||
for (auto& line : fLines) {
|
||||
// Let's figure out if we can stop looking
|
||||
auto offsetY = line.offset().fY;
|
||||
@ -793,39 +833,37 @@ PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, Sk
|
||||
|
||||
// This is so far the the line vertically closest to our coordinates
|
||||
// (or the first one, or the only one - all the same)
|
||||
line.iterateThroughRuns(
|
||||
line.textWithSpaces(),
|
||||
0,
|
||||
true,
|
||||
[this, dx, &result]
|
||||
(Run* run, size_t pos, size_t size, TextRange, SkRect clip, SkScalar shift, bool clippingNeeded) {
|
||||
line.iterateThroughVisualRuns(true,
|
||||
[this, &line, dx, &result]
|
||||
(const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width) {
|
||||
|
||||
if (dx < clip.fLeft) {
|
||||
auto context = line.measureTextInsideOneRun(textRange, run, 0, 0, true);
|
||||
if (dx < context.clip.fLeft) {
|
||||
// All the other runs are placed right of this one
|
||||
result = { SkToS32(run->fClusterIndexes[pos]), kDownstream };
|
||||
result = { SkToS32(context.run->fClusterIndexes[context.pos]), kDownstream };
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dx >= clip.fRight) {
|
||||
if (dx >= context.clip.fRight) {
|
||||
// We have to keep looking but just in case keep the last one as the closes
|
||||
// so far
|
||||
result = { SkToS32(run->fClusterIndexes[pos + size - 1]) + 1, kUpstream };
|
||||
result = { SkToS32(context.run->fClusterIndexes[context.pos + context.size - 1]) + 1, kUpstream };
|
||||
return true;
|
||||
}
|
||||
|
||||
// So we found the run that contains our coordinates
|
||||
// Find the glyph position in the run that is the closest left of our point
|
||||
// TODO: binary search
|
||||
size_t found = pos;
|
||||
for (size_t i = pos; i < pos + size; ++i) {
|
||||
if (run->positionX(i) + shift > dx) {
|
||||
size_t found = context.pos;
|
||||
for (size_t i = context.pos; i < context.pos + context.size; ++i) {
|
||||
if (context.run->positionX(i) + context.fTextShift > dx) {
|
||||
break;
|
||||
}
|
||||
found = i;
|
||||
}
|
||||
auto glyphStart = run->positionX(found);
|
||||
auto glyphWidth = run->positionX(found + 1) - run->positionX(found);
|
||||
auto clusterIndex8 = run->fClusterIndexes[found];
|
||||
auto glyphStart = context.run->positionX(found);
|
||||
auto glyphWidth = context.run->positionX(found + 1) - context.run->positionX(found);
|
||||
auto clusterIndex8 = context.run->fClusterIndexes[found];
|
||||
|
||||
// Find the grapheme positions in codepoints that contains the point
|
||||
auto codepoint = std::lower_bound(
|
||||
@ -843,15 +881,15 @@ PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, Sk
|
||||
auto averageCodepoint = glyphWidth / graphemeSize;
|
||||
auto codepointStart = glyphStart + averageCodepoint * (codepointIndex - codepoints.start);
|
||||
auto codepointEnd = codepointStart + averageCodepoint;
|
||||
center = (codepointStart + codepointEnd) / 2 + shift;
|
||||
center = (codepointStart + codepointEnd) / 2 + context.fTextShift;
|
||||
} else {
|
||||
SkASSERT(graphemeSize == 1);
|
||||
auto codepointStart = glyphStart;
|
||||
auto codepointEnd = codepointStart + glyphWidth;
|
||||
center = (codepointStart + codepointEnd) / 2 + shift;
|
||||
center = (codepointStart + codepointEnd) / 2 + context.fTextShift;
|
||||
}
|
||||
|
||||
if ((dx <= center) == run->leftToRight()) {
|
||||
if ((dx <= center) == context.run->leftToRight()) {
|
||||
result = { SkToS32(codepointIndex), kDownstream };
|
||||
} else {
|
||||
result = { SkToS32(codepointIndex + 1), kUpstream };
|
||||
@ -888,7 +926,7 @@ SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) {
|
||||
}
|
||||
|
||||
SkSpan<const char> ParagraphImpl::text(TextRange textRange) {
|
||||
SkASSERT(textRange.start < fText.size() && textRange.end <= fText.size());
|
||||
SkASSERT(textRange.start <= fText.size() && textRange.end <= fText.size());
|
||||
auto start = fText.c_str() + textRange.start;
|
||||
return SkSpan<const char>(start, textRange.width());
|
||||
}
|
||||
@ -908,6 +946,11 @@ Run& ParagraphImpl::run(RunIndex runIndex) {
|
||||
return fRuns[runIndex];
|
||||
}
|
||||
|
||||
Run& ParagraphImpl::runByCluster(ClusterIndex clusterIndex) {
|
||||
auto start = cluster(clusterIndex);
|
||||
return this->run(start.fRunIndex);
|
||||
}
|
||||
|
||||
SkSpan<Block> ParagraphImpl::blocks(BlockRange blockRange) {
|
||||
SkASSERT(blockRange.start < fTextStyles.size() && blockRange.end <= fTextStyles.size());
|
||||
return SkSpan<Block>(&fTextStyles[blockRange.start], blockRange.width());
|
||||
|
@ -177,6 +177,7 @@ public:
|
||||
SkSpan<Cluster> clusters(ClusterRange clusterRange);
|
||||
Cluster& cluster(ClusterIndex clusterIndex);
|
||||
Run& run(RunIndex runIndex);
|
||||
Run& runByCluster(ClusterIndex clusterIndex);
|
||||
SkSpan<Block> blocks(BlockRange blockRange);
|
||||
Block& block(BlockIndex blockIndex);
|
||||
|
||||
|
@ -43,6 +43,7 @@ Run::Run(ParagraphImpl* master,
|
||||
// To make edge cases easier:
|
||||
fPositions[info.glyphCount] = fOffset + fAdvance;
|
||||
fClusterIndexes[info.glyphCount] = info.utf8Range.end();
|
||||
fEllipsis = false;
|
||||
}
|
||||
|
||||
SkShaper::RunHandler::Buffer Run::newRunBuffer() {
|
||||
@ -80,7 +81,7 @@ void Run::copyTo(SkTextBlobBuilder& builder, size_t pos, size_t size, SkVector o
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<bool, ClusterIndex, ClusterIndex> Run::findLimitingClusters(TextRange text) {
|
||||
std::tuple<bool, ClusterIndex, ClusterIndex> Run::findLimitingClusters(TextRange text) const {
|
||||
auto less = [](const Cluster& c1, const Cluster& c2) {
|
||||
return c1.textRange().end <= c2.textRange().start;
|
||||
};
|
||||
|
@ -100,8 +100,12 @@ public:
|
||||
size_t clusterIndex(size_t pos) const { return fClusterIndexes[pos]; }
|
||||
SkScalar positionX(size_t pos) const;
|
||||
|
||||
TextRange textRange() { return fTextRange; }
|
||||
ClusterRange clusterRange() { return fClusterRange; }
|
||||
TextRange textRange() const { return fTextRange; }
|
||||
ClusterRange clusterRange() const { return fClusterRange; }
|
||||
|
||||
ParagraphImpl* master() const { return fMaster; }
|
||||
|
||||
bool isEllipsis() const { return fEllipsis; }
|
||||
|
||||
void updateMetrics(LineMetrics* endlineMetrics);
|
||||
|
||||
@ -132,7 +136,7 @@ public:
|
||||
SkScalar height)>;
|
||||
void iterateThroughClustersInTextOrder(const ClusterVisitor& visitor);
|
||||
|
||||
std::tuple<bool, ClusterIndex, ClusterIndex> findLimitingClusters(TextRange);
|
||||
std::tuple<bool, ClusterIndex, ClusterIndex> findLimitingClusters(TextRange) const;
|
||||
SkSpan<const SkGlyphID> glyphs() const {
|
||||
return SkSpan<const SkGlyphID>(fGlyphs.begin(), fGlyphs.size());
|
||||
}
|
||||
@ -158,6 +162,7 @@ private:
|
||||
SkFontMetrics fFontMetrics;
|
||||
SkScalar fHeightMultiplier;
|
||||
PlaceholderStyle* fPlaceholder;
|
||||
bool fEllipsis;
|
||||
size_t fIndex;
|
||||
uint8_t fBidiLevel;
|
||||
SkVector fAdvance;
|
||||
@ -251,6 +256,8 @@ public:
|
||||
TextRange textRange() const { return fTextRange; }
|
||||
|
||||
RunIndex runIndex() const { return fRunIndex; }
|
||||
ParagraphImpl* master() const { return fMaster; }
|
||||
|
||||
Run* run() const;
|
||||
SkFont font() const;
|
||||
|
||||
@ -343,7 +350,7 @@ public:
|
||||
metrics.fLeading = SkTMax(metrics.fLeading, fLeading);
|
||||
}
|
||||
|
||||
SkScalar runTop(Run* run) const {
|
||||
SkScalar runTop(const Run* run) const {
|
||||
return fLeading / 2 - fAscent + run->ascent() + delta();
|
||||
}
|
||||
SkScalar height() const { return SkScalarRoundToInt(fDescent - fAscent + fLeading); }
|
||||
|
@ -44,7 +44,7 @@ TextLine::TextLine(ParagraphImpl* master,
|
||||
, fTextWithWhitespacesRange(textWithSpaces)
|
||||
, fClusterRange(clusters)
|
||||
, fGhostClusterRange(clustersWithGhosts)
|
||||
, fLogical()
|
||||
, fRunsInVisualOrder()
|
||||
, fAdvance(advance)
|
||||
, fOffset(offset)
|
||||
, fShift(0.0)
|
||||
@ -87,7 +87,7 @@ TextLine::TextLine(ParagraphImpl* master,
|
||||
|
||||
auto firstRunIndex = start.runIndex();
|
||||
for (auto index : logicalOrder) {
|
||||
fLogical.push_back(firstRunIndex + index);
|
||||
fRunsInVisualOrder.push_back(firstRunIndex + index);
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,32 +100,56 @@ void TextLine::paint(SkCanvas* textCanvas) {
|
||||
textCanvas->translate(this->offset().fX, this->offset().fY);
|
||||
|
||||
if (fHasBackground) {
|
||||
this->iterateThroughStylesInTextOrder(
|
||||
StyleType::kBackground,
|
||||
[this, textCanvas](TextRange textRange, const TextStyle& style, SkScalar offsetX) {
|
||||
return this->paintBackground(textCanvas, textRange, style, offsetX);
|
||||
this->iterateThroughVisualRuns(false,
|
||||
[textCanvas, this]
|
||||
(const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
|
||||
*runWidthInLine = this->iterateThroughSingleRunByStyles(
|
||||
run, runOffsetInLine, textRange, StyleType::kBackground,
|
||||
[textCanvas, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
|
||||
this->paintBackground(textCanvas, textRange, style, context);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if (fHasShadows) {
|
||||
this->iterateThroughStylesInTextOrder(
|
||||
StyleType::kShadow,
|
||||
[textCanvas, this](TextRange textRange, const TextStyle& style, SkScalar offsetX) {
|
||||
return this->paintShadow(textCanvas, textRange, style, offsetX);
|
||||
this->iterateThroughVisualRuns(false,
|
||||
[textCanvas, this]
|
||||
(const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
|
||||
*runWidthInLine = this->iterateThroughSingleRunByStyles(
|
||||
run, runOffsetInLine, textRange, StyleType::kShadow,
|
||||
[textCanvas, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
|
||||
this->paintShadow(textCanvas, textRange, style, context);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
this->iterateThroughStylesInTextOrder(
|
||||
StyleType::kForeground,
|
||||
[textCanvas, this](TextRange textRange, const TextStyle& style, SkScalar offsetX) {
|
||||
return this->paintText(textCanvas, textRange, style, offsetX);
|
||||
this->iterateThroughVisualRuns(false,
|
||||
[textCanvas, this]
|
||||
(const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
|
||||
if (run->placeholder() != nullptr) {
|
||||
*runWidthInLine = run->advance().fX;
|
||||
return true;
|
||||
}
|
||||
*runWidthInLine = this->iterateThroughSingleRunByStyles(
|
||||
run, runOffsetInLine, textRange, StyleType::kForeground,
|
||||
[textCanvas, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
|
||||
this->paintText(textCanvas, textRange, style, context);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
|
||||
if (fHasDecorations) {
|
||||
this->iterateThroughStylesInTextOrder(
|
||||
StyleType::kDecorations,
|
||||
[textCanvas, this](TextRange textRange, const TextStyle& style, SkScalar offsetX) {
|
||||
return this->paintDecorations(textCanvas, textRange, style, offsetX);
|
||||
this->iterateThroughVisualRuns(false,
|
||||
[textCanvas, this]
|
||||
(const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
|
||||
*runWidthInLine = this->iterateThroughSingleRunByStyles(
|
||||
run, runOffsetInLine, textRange, StyleType::kDecorations,
|
||||
[textCanvas, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
|
||||
this->paintDecorations(textCanvas, textRange, style, context);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@ -160,31 +184,28 @@ TextAlign TextLine::assumedTextAlign() const {
|
||||
}
|
||||
}
|
||||
|
||||
void TextLine::scanStyles(StyleType style, const StyleVisitor& visitor) {
|
||||
void TextLine::scanStyles(StyleType styleType, const RunStyleVisitor& visitor) {
|
||||
if (this->empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->iterateThroughStylesInTextOrder(
|
||||
style, [this, visitor](TextRange textRange, const TextStyle& style, SkScalar offsetX) {
|
||||
visitor(textRange, style, offsetX);
|
||||
return this->iterateThroughRuns(
|
||||
textRange, offsetX, false,
|
||||
[](Run*, int32_t, size_t, TextRange, SkRect, SkScalar, bool) { return true; });
|
||||
});
|
||||
this->iterateThroughVisualRuns(false,
|
||||
[this, visitor, styleType](const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width) {
|
||||
*width = this->iterateThroughSingleRunByStyles(
|
||||
run, runOffset, textRange, styleType,
|
||||
[visitor](TextRange textRange, const TextStyle& style, const ClipContext& context) {
|
||||
visitor(textRange, style, context);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void TextLine::scanRuns(const RunVisitor& visitor) {
|
||||
this->iterateThroughRuns(
|
||||
fTextRange, 0, false,
|
||||
[visitor](Run* run, int32_t pos, size_t size, TextRange text, SkRect clip, SkScalar sc, bool b) {
|
||||
visitor(run, pos, size, text, clip, sc, b);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
void TextLine::paintText(SkCanvas* canvas, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
|
||||
|
||||
if (context.run->placeholder() != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
SkScalar TextLine::paintText(SkCanvas* canvas, TextRange textRange, const TextStyle& style,
|
||||
SkScalar offsetX) const {
|
||||
SkPaint paint;
|
||||
if (style.hasForeground()) {
|
||||
paint = style.getForeground();
|
||||
@ -192,140 +213,110 @@ SkScalar TextLine::paintText(SkCanvas* canvas, TextRange textRange, const TextSt
|
||||
paint.setColor(style.getColor());
|
||||
}
|
||||
|
||||
auto shiftDown = this->baseline();
|
||||
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();
|
||||
if (clippingNeeded) {
|
||||
canvas->clipRect(clip);
|
||||
}
|
||||
canvas->translate(shift, 0);
|
||||
canvas->drawTextBlob(builder.make(), 0, 0, paint);
|
||||
canvas->restore();
|
||||
return true;
|
||||
});
|
||||
SkTextBlobBuilder builder;
|
||||
context.run->copyTo(builder, SkToU32(context.pos), context.size, SkVector::Make(0, this->baseline()));
|
||||
canvas->save();
|
||||
if (context.clippingNeeded) {
|
||||
canvas->clipRect(context.clip);
|
||||
}
|
||||
canvas->translate(context.fTextShift, 0);
|
||||
canvas->drawTextBlob(builder.make(), 0, 0, paint);
|
||||
canvas->restore();
|
||||
}
|
||||
|
||||
SkScalar TextLine::paintBackground(SkCanvas* canvas, TextRange textRange,
|
||||
const TextStyle& style, SkScalar offsetX) const {
|
||||
return this->iterateThroughRuns(textRange, offsetX, false,
|
||||
[canvas, &style](Run* run, int32_t pos, size_t size, TextRange, SkRect clip,
|
||||
SkScalar shift, bool clippingNeeded) {
|
||||
if (style.hasBackground()) {
|
||||
canvas->drawRect(clip, style.getBackground());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
void TextLine::paintBackground(SkCanvas* canvas, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
|
||||
if (style.hasBackground()) {
|
||||
canvas->save();
|
||||
canvas->drawRect(context.clip, style.getBackground());
|
||||
canvas->restore();
|
||||
}
|
||||
}
|
||||
|
||||
SkScalar TextLine::paintShadow(SkCanvas* canvas, TextRange textRange, const TextStyle& style,
|
||||
SkScalar offsetX) const {
|
||||
void TextLine::paintShadow(SkCanvas* canvas, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
|
||||
auto shiftDown = this->baseline();
|
||||
auto result = this->iterateThroughRuns(
|
||||
textRange, offsetX, false,
|
||||
[canvas, shiftDown, &style](Run* run, size_t pos, size_t size, TextRange, SkRect clip,
|
||||
SkScalar shift, bool clippingNeeded) {
|
||||
for (TextShadow shadow : style.getShadows()) {
|
||||
if (!shadow.hasShadow()) continue;
|
||||
for (TextShadow shadow : style.getShadows()) {
|
||||
if (!shadow.hasShadow()) continue;
|
||||
|
||||
SkPaint paint;
|
||||
paint.setColor(shadow.fColor);
|
||||
if (shadow.fBlurRadius != 0.0) {
|
||||
auto filter = SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
|
||||
SkDoubleToScalar(shadow.fBlurRadius), false);
|
||||
paint.setMaskFilter(filter);
|
||||
}
|
||||
SkPaint paint;
|
||||
paint.setColor(shadow.fColor);
|
||||
if (shadow.fBlurRadius != 0.0) {
|
||||
auto filter = SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
|
||||
SkDoubleToScalar(shadow.fBlurRadius), false);
|
||||
paint.setMaskFilter(filter);
|
||||
}
|
||||
|
||||
SkTextBlobBuilder builder;
|
||||
run->copyTo(builder, pos, size, SkVector::Make(0, shiftDown));
|
||||
canvas->save();
|
||||
clip.offset(shadow.fOffset);
|
||||
if (clippingNeeded) {
|
||||
canvas->clipRect(clip);
|
||||
}
|
||||
canvas->translate(shift, 0);
|
||||
canvas->drawTextBlob(builder.make(), shadow.fOffset.x(), shadow.fOffset.y(),
|
||||
paint);
|
||||
canvas->restore();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return result;
|
||||
SkTextBlobBuilder builder;
|
||||
context.run->copyTo(builder, context.pos, context.size, SkVector::Make(0, shiftDown));
|
||||
canvas->save();
|
||||
SkRect clip = context.clip;
|
||||
clip.offset(shadow.fOffset);
|
||||
if (context.clippingNeeded) {
|
||||
canvas->clipRect(clip);
|
||||
}
|
||||
canvas->translate(context.fTextShift, 0);
|
||||
canvas->drawTextBlob(builder.make(), shadow.fOffset.x(), shadow.fOffset.y(), paint);
|
||||
canvas->restore();
|
||||
}
|
||||
}
|
||||
|
||||
SkScalar TextLine::paintDecorations(SkCanvas* canvas, TextRange textRange,
|
||||
const TextStyle& style, SkScalar offsetX) const {
|
||||
return this->iterateThroughRuns(
|
||||
textRange, offsetX, false,
|
||||
[this, canvas, &style](Run* run, int32_t pos, size_t size, TextRange, SkRect clip, SkScalar shift,
|
||||
bool clippingNeeded) {
|
||||
if (style.getDecorationType() == TextDecoration::kNoDecoration) {
|
||||
return true;
|
||||
void TextLine::paintDecorations(SkCanvas* canvas, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
|
||||
if (style.getDecorationType() == TextDecoration::kNoDecoration) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto decoration : AllTextDecorations) {
|
||||
if ((style.getDecorationType() & decoration) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SkScalar thickness = computeDecorationThickness(style);
|
||||
SkScalar position = computeDecorationPosition(style);
|
||||
switch (decoration) {
|
||||
case TextDecoration::kUnderline:
|
||||
position = - context.run->correctAscent() + thickness;
|
||||
break;
|
||||
case TextDecoration::kOverline:
|
||||
position = 0;
|
||||
break;
|
||||
case TextDecoration::kLineThrough: {
|
||||
position = (context.run->correctDescent() - context.run->correctAscent() - thickness) / 2;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
SkASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto decoration : AllTextDecorations) {
|
||||
if ((style.getDecorationType() & decoration) == 0) {
|
||||
continue;
|
||||
}
|
||||
auto width = context.clip.width();
|
||||
SkScalar x = context.clip.left();
|
||||
SkScalar y = context.clip.top() + position;
|
||||
|
||||
SkScalar thickness = computeDecorationThickness(style);
|
||||
SkScalar position = computeDecorationPosition(style);
|
||||
switch (decoration) {
|
||||
case TextDecoration::kUnderline:
|
||||
position = -run->correctAscent() + thickness;
|
||||
break;
|
||||
case TextDecoration::kOverline:
|
||||
position = 0;
|
||||
break;
|
||||
case TextDecoration::kLineThrough: {
|
||||
position = (run->correctDescent() - run->correctAscent() - thickness) / 2;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
SkASSERT(false);
|
||||
break;
|
||||
}
|
||||
// Decoration paint (for now) and/or path
|
||||
SkPaint paint;
|
||||
SkPath path;
|
||||
this->computeDecorationPaint(paint, context.clip, style, path);
|
||||
paint.setStrokeWidth(thickness * style.getDecorationThicknessMultiplier());
|
||||
|
||||
auto width = clip.width();
|
||||
SkScalar x = clip.left();
|
||||
SkScalar y = clip.top() + position;
|
||||
|
||||
// Decoration paint (for now) and/or path
|
||||
SkPaint paint;
|
||||
SkPath path;
|
||||
this->computeDecorationPaint(paint, clip, style, path);
|
||||
paint.setStrokeWidth(thickness * style.getDecorationThicknessMultiplier());
|
||||
|
||||
switch (style.getDecorationStyle()) {
|
||||
case TextDecorationStyle::kWavy:
|
||||
path.offset(x, y);
|
||||
canvas->drawPath(path, paint);
|
||||
break;
|
||||
case TextDecorationStyle::kDouble: {
|
||||
canvas->drawLine(x, y, x + width, y, paint);
|
||||
SkScalar bottom = y + thickness * 2;
|
||||
canvas->drawLine(x, bottom, x + width, bottom, paint);
|
||||
break;
|
||||
}
|
||||
case TextDecorationStyle::kDashed:
|
||||
case TextDecorationStyle::kDotted:
|
||||
case TextDecorationStyle::kSolid:
|
||||
canvas->drawLine(x, y, x + width, y, paint);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (style.getDecorationStyle()) {
|
||||
case TextDecorationStyle::kWavy:
|
||||
path.offset(x, y);
|
||||
canvas->drawPath(path, paint);
|
||||
break;
|
||||
case TextDecorationStyle::kDouble: {
|
||||
canvas->drawLine(x, y, x + width, y, paint);
|
||||
SkScalar bottom = y + thickness * 2;
|
||||
canvas->drawLine(x, bottom, x + width, bottom, paint);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
case TextDecorationStyle::kDashed:
|
||||
case TextDecorationStyle::kDotted:
|
||||
case TextDecorationStyle::kSolid:
|
||||
canvas->drawLine(x, y, x + width, y, paint);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SkScalar TextLine::computeDecorationThickness(const TextStyle& style) const {
|
||||
@ -487,6 +478,7 @@ void TextLine::createEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool)
|
||||
|
||||
// Shape the ellipsis
|
||||
Run* run = shapeEllipsis(ellipsis, cluster->run());
|
||||
run->fFirstChar = cluster->textRange().start;
|
||||
run->setMaster(fMaster);
|
||||
fEllipsis = std::make_shared<Run>(*run);
|
||||
|
||||
@ -527,6 +519,7 @@ Run* TextLine::shapeEllipsis(const SkString& ellipsis, Run* run) {
|
||||
fRun->fAdvance.fX = info.fAdvance.fX;
|
||||
fRun->fAdvance.fY = fRun->advance().fY;
|
||||
fRun->fPlaceholder = nullptr;
|
||||
fRun->fEllipsis = true;
|
||||
}
|
||||
|
||||
void commitLine() override {}
|
||||
@ -546,18 +539,24 @@ Run* TextLine::shapeEllipsis(const SkString& ellipsis, Run* run) {
|
||||
return handler.run();
|
||||
}
|
||||
|
||||
SkRect TextLine::measureTextInsideOneRun(
|
||||
TextRange textRange, Run* run, size_t& pos, size_t& size, bool includeGhostSpaces, bool& clippingNeeded) const {
|
||||
|
||||
TextLine::ClipContext TextLine::measureTextInsideOneRun(TextRange textRange,
|
||||
const Run* run,
|
||||
SkScalar runOffsetInLine,
|
||||
SkScalar textOffsetInRunInLine,
|
||||
bool includeGhostSpaces) const {
|
||||
SkASSERT(intersectedSize(run->textRange(), textRange) >= 0);
|
||||
|
||||
if (run->placeholder() != nullptr) {
|
||||
ClipContext result;
|
||||
result.run = run;
|
||||
if (run->placeholder() != nullptr || run->fEllipsis) {
|
||||
// Both ellipsis and placeholders can only be measured as one glyph
|
||||
SkASSERT(textRange == run->textRange());
|
||||
pos = 0;
|
||||
size = 1;
|
||||
clippingNeeded = false;
|
||||
return SkRect::MakeXYWH(
|
||||
0, sizes().runTop(run), run->calculateWidth(0, 1, false), run->calculateHeight());
|
||||
result.pos = 0;
|
||||
result.size = run->size();
|
||||
result.clippingNeeded = false;
|
||||
result.fTextShift = runOffsetInLine;
|
||||
result.clip = SkRect::MakeXYWH(runOffsetInLine, sizes().runTop(run), run->advance().fX, run->calculateHeight());
|
||||
return result;
|
||||
}
|
||||
|
||||
// Find [start:end] clusters for the text
|
||||
@ -567,13 +566,17 @@ SkRect TextLine::measureTextInsideOneRun(
|
||||
std::tie(found, startIndex, endIndex) = run->findLimitingClusters(textRange);
|
||||
if (!found) {
|
||||
SkASSERT(textRange.empty());
|
||||
return SkRect::MakeEmpty();
|
||||
result.clip = SkRect::MakeEmpty();
|
||||
return result;
|
||||
}
|
||||
|
||||
auto start = fMaster->clusters().begin() + startIndex;
|
||||
auto end = fMaster->clusters().begin() + endIndex;
|
||||
pos = start->startPos();
|
||||
size = end->endPos() - start->startPos();
|
||||
auto start = &fMaster->cluster(startIndex);
|
||||
auto end = &fMaster->cluster(endIndex);
|
||||
result.pos = start->startPos();
|
||||
result.size = end->endPos() - start->startPos();
|
||||
|
||||
auto textStartInRun = run->positionX(start->startPos());
|
||||
auto textStartInLine = runOffsetInLine + textOffsetInRunInLine;
|
||||
|
||||
// Calculate the clipping rectangle for the text with cluster edges
|
||||
// There are 2 cases:
|
||||
@ -581,9 +584,9 @@ SkRect TextLine::measureTextInsideOneRun(
|
||||
// Anything else (when we want the cluster width contain all the spaces -
|
||||
// coming from letter spacing or word spacing or justification)
|
||||
auto range = includeGhostSpaces ? fGhostClusterRange : fClusterRange;
|
||||
bool needsClipping = (run->leftToRight() ? endIndex == range.end - 1 : startIndex == range.end - 1);
|
||||
SkRect clip =
|
||||
SkRect::MakeXYWH(run->positionX(start->startPos()) - run->positionX(0),
|
||||
bool needsClipping = (run->leftToRight() ? endIndex == range.end - 1 : startIndex == range.end);
|
||||
result.clip =
|
||||
SkRect::MakeXYWH(0,
|
||||
sizes().runTop(run),
|
||||
run->calculateWidth(start->startPos(), end->endPos(), needsClipping),
|
||||
run->calculateHeight());
|
||||
@ -594,19 +597,31 @@ SkRect TextLine::measureTextInsideOneRun(
|
||||
// to calculate the proportions
|
||||
auto leftCorrection = start->sizeToChar(textRange.start);
|
||||
auto rightCorrection = end->sizeFromChar(textRange.end - 1);
|
||||
clip.fLeft += leftCorrection;
|
||||
clip.fRight -= rightCorrection;
|
||||
clippingNeeded = leftCorrection != 0 || rightCorrection != 0;
|
||||
result.clip.fLeft += leftCorrection;
|
||||
result.fTextShift -= leftCorrection;
|
||||
result.clip.fRight -= rightCorrection;
|
||||
result.clippingNeeded = leftCorrection != 0 || rightCorrection != 0;
|
||||
|
||||
return clip;
|
||||
textStartInLine -= leftCorrection;
|
||||
result.clip.offset(textStartInLine, 0);
|
||||
|
||||
if (result.clip.fRight > fAdvance.fX && !includeGhostSpaces) {
|
||||
result.clip.fRight = fAdvance.fX;
|
||||
result.clippingNeeded = true;
|
||||
}
|
||||
|
||||
// The text must be aligned with the lineOffset
|
||||
result.fTextShift = textStartInLine - textStartInRun;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void TextLine::iterateThroughClustersInGlyphsOrder(bool reverse,
|
||||
bool includeGhosts,
|
||||
const ClustersVisitor& visitor) const {
|
||||
// Walk through the clusters in the logical order (or reverse)
|
||||
for (size_t r = 0; r != fLogical.size(); ++r) {
|
||||
auto& runIndex = fLogical[reverse ? fLogical.size() - r - 1 : r];
|
||||
for (size_t r = 0; r != fRunsInVisualOrder.size(); ++r) {
|
||||
auto& runIndex = fRunsInVisualOrder[reverse ? fRunsInVisualOrder.size() - r - 1 : r];
|
||||
auto run = this->fMaster->runs().begin() + runIndex;
|
||||
auto start = SkTMax(run->clusterRange().start, fClusterRange.start);
|
||||
auto end = SkTMin(run->clusterRange().end, fClusterRange.end);
|
||||
@ -636,160 +651,119 @@ void TextLine::iterateThroughClustersInGlyphsOrder(bool reverse,
|
||||
}
|
||||
}
|
||||
|
||||
SkScalar TextLine::calculateLeftVisualOffset(TextRange textRange) const {
|
||||
SkScalar partOfTheCurrentRun = 0;
|
||||
return this->iterateThroughRuns(this->textWithSpaces(), 0, true,
|
||||
[textRange, &partOfTheCurrentRun, this](
|
||||
Run* run, size_t pos, size_t size, TextRange text,
|
||||
SkRect clip, SkScalar shift, bool clippingNeeded) {
|
||||
if (text.start > textRange.start || text.end <= textRange.start) {
|
||||
// This run does not even touch the text start
|
||||
} else {
|
||||
// This is the run
|
||||
TextRange part;
|
||||
if (run->leftToRight()) {
|
||||
part = {text.start, textRange.start};
|
||||
} else if (textRange.end < text.end) {
|
||||
part = {textRange.end, text.end};
|
||||
}
|
||||
if (part.width() == 0) {
|
||||
return false;
|
||||
}
|
||||
size_t pos;
|
||||
size_t size;
|
||||
bool clippingNeeded;
|
||||
SkRect partClip = this->measureTextInsideOneRun(part, run, pos, size, true, clippingNeeded);
|
||||
partOfTheCurrentRun = partClip.width();
|
||||
return false;
|
||||
}
|
||||
SkScalar TextLine::iterateThroughSingleRunByStyles(const Run* run,
|
||||
SkScalar runOffset,
|
||||
TextRange textRange,
|
||||
StyleType styleType,
|
||||
const RunStyleVisitor& visitor) const {
|
||||
|
||||
return true;
|
||||
}) +
|
||||
partOfTheCurrentRun;
|
||||
}
|
||||
|
||||
// Walk through the runs in the logical order
|
||||
SkScalar TextLine::iterateThroughRuns(TextRange textRange,
|
||||
SkScalar runOffset,
|
||||
bool includeGhostWhitespaces,
|
||||
const RunVisitor& visitor) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
|
||||
SkScalar width = 0;
|
||||
for (auto& runIndex : fLogical) {
|
||||
const auto run = &this->fMaster->run(runIndex);
|
||||
// Only skip the text if it does not even touch the run
|
||||
auto intersection = intersectedSize(run->textRange(), textRange);
|
||||
if (intersection < 0 || (intersection == 0 && textRange.end != run->textRange().end)) {
|
||||
continue;
|
||||
if (run->fEllipsis) {
|
||||
// Extra efforts to get the ellipsis text style
|
||||
ClipContext clipContext = this->measureTextInsideOneRun(run->textRange(), run, runOffset, 0, false);
|
||||
TextRange testRange(run->fFirstChar, run->fFirstChar + 1);
|
||||
for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
|
||||
auto block = fMaster->styles().begin() + index;
|
||||
auto intersect = intersected(block->fRange, testRange);
|
||||
if (intersect.width() > 0) {
|
||||
visitor(textRange, block->fStyle, clipContext);
|
||||
return run->advance().fX;
|
||||
}
|
||||
}
|
||||
|
||||
auto intersect = intersected(run->textRange(), textRange);
|
||||
if (run->textRange().empty() || intersect.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Measure the text
|
||||
size_t pos;
|
||||
size_t size;
|
||||
bool clippingNeeded;
|
||||
SkRect clip = this->measureTextInsideOneRun(intersect, run, pos, size, includeGhostWhitespaces, clippingNeeded);
|
||||
if (clip.height() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Clip the text
|
||||
auto shift = runOffset - clip.fLeft;
|
||||
clip.offset(shift, 0);
|
||||
if (includeGhostWhitespaces) {
|
||||
clippingNeeded = false;
|
||||
} else {
|
||||
clippingNeeded = true;
|
||||
if (clip.fRight > fAdvance.fX) {
|
||||
clip.fRight = fAdvance.fX;
|
||||
}
|
||||
}
|
||||
|
||||
if (!visitor(run, pos, size, intersect, clip, shift - run->positionX(0), clippingNeeded)) {
|
||||
return width;
|
||||
}
|
||||
|
||||
if (run->leftToRight() || &runIndex == &fLogical.back()) {
|
||||
width += clip.width();
|
||||
runOffset += clip.width();
|
||||
} else {
|
||||
width += run->advance().fX;
|
||||
runOffset += run->advance().fX;
|
||||
}
|
||||
|
||||
SkASSERT(false);
|
||||
}
|
||||
|
||||
if (this->ellipsis() != nullptr) {
|
||||
auto ellipsis = this->ellipsis();
|
||||
if (!visitor(ellipsis, 0, ellipsis->size(), ellipsis->textRange(), ellipsis->clip(), ellipsis->clip().fLeft,
|
||||
false)) {
|
||||
return width;
|
||||
}
|
||||
width += ellipsis->clip().width();
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
void TextLine::iterateThroughStylesInTextOrder(StyleType styleType,
|
||||
const StyleVisitor& visitor) const {
|
||||
|
||||
TextIndex start = EMPTY_INDEX;
|
||||
size_t size = 0;
|
||||
const TextStyle* prevStyle = nullptr;
|
||||
SkScalar textOffsetInRun = 0;
|
||||
for (BlockIndex index = fBlockRange.start; index <= fBlockRange.end; ++index) {
|
||||
|
||||
SkScalar offsetX = 0;
|
||||
for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
|
||||
auto block = fMaster->styles().begin() + index;
|
||||
auto intersect = intersected(block->fRange, this->trimmedText());
|
||||
if (intersect.empty()) {
|
||||
if (start == EMPTY_INDEX) {
|
||||
// This style is not applicable to the line
|
||||
continue;
|
||||
TextRange intersect;
|
||||
TextStyle* style = nullptr;
|
||||
if (index < fBlockRange.end) {
|
||||
auto block = fMaster->styles().begin() + index;
|
||||
|
||||
// Get the text
|
||||
intersect = intersected(block->fRange, textRange);
|
||||
if (intersect.width() == 0) {
|
||||
if (start == EMPTY_INDEX) {
|
||||
// This style is not applicable to the text yet
|
||||
continue;
|
||||
} else {
|
||||
// We have found all the good styles already
|
||||
// but we need to process the last one of them
|
||||
intersect = TextRange(start, start + size);
|
||||
index = fBlockRange.end;
|
||||
}
|
||||
} else {
|
||||
// We have found all the good styles already
|
||||
break;
|
||||
// Get the style
|
||||
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;
|
||||
size = intersect.width();
|
||||
start = intersect.start;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if (prevStyle != nullptr) {
|
||||
// This is the last style
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
size = intersect.width();
|
||||
start = intersect.start;
|
||||
// We have the style and the text
|
||||
auto textRange = TextRange(start, start + size);
|
||||
// Measure the text
|
||||
ClipContext clipContext = this->measureTextInsideOneRun(textRange, run, runOffset, textOffsetInRun, false);
|
||||
if (clipContext.clip.height() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto width = visitor(TextRange(start, start + size), *prevStyle, offsetX);
|
||||
offsetX += width;
|
||||
visitor(textRange, *prevStyle, clipContext);
|
||||
textOffsetInRun += clipContext.clip.width();
|
||||
|
||||
// Start all over again
|
||||
prevStyle = &style;
|
||||
prevStyle = style;
|
||||
start = intersect.start;
|
||||
size = intersect.width();
|
||||
}
|
||||
|
||||
if (prevStyle == nullptr) return;
|
||||
return textOffsetInRun;
|
||||
}
|
||||
|
||||
// The very last style
|
||||
auto width = visitor(TextRange(start, start + size), *prevStyle, offsetX);
|
||||
offsetX += width;
|
||||
void TextLine::iterateThroughVisualRuns(bool includingGhostSpaces, const RunVisitor& visitor) const {
|
||||
|
||||
// Walk through all the runs that intersect with the line in visual order
|
||||
SkScalar width = 0;
|
||||
SkScalar runOffset = 0;
|
||||
auto textRange = includingGhostSpaces ? this->textWithSpaces() : this->trimmedText();
|
||||
for (auto& runIndex : fRunsInVisualOrder) {
|
||||
|
||||
const auto run = &this->fMaster->run(runIndex);
|
||||
auto lineIntersection = intersected(run->textRange(), textRange);
|
||||
|
||||
runOffset += width;
|
||||
if (!visitor(run, runOffset, lineIntersection, &width)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
runOffset += width;
|
||||
if (this->ellipsis() != nullptr) {
|
||||
if (visitor(ellipsis(), runOffset, ellipsis()->textRange(), &width)) {
|
||||
runOffset += width;
|
||||
}
|
||||
}
|
||||
|
||||
// This is a very important assert!
|
||||
// It asserts that 2 different ways of calculation come with the same results
|
||||
if (!SkScalarNearlyEqual(offsetX, this->width())) {
|
||||
SkDebugf("ASSERT: %f != %f\n", offsetX, this->width());
|
||||
if (!includingGhostSpaces && !SkScalarNearlyEqual(runOffset, this->width())) {
|
||||
SkDebugf("ASSERT: %f != %f\n", runOffset, this->width());
|
||||
SkASSERT(false);
|
||||
}
|
||||
SkASSERT(SkScalarNearlyEqual(offsetX, this->width()));
|
||||
}
|
||||
|
||||
SkVector TextLine::offset() const {
|
||||
|
@ -15,6 +15,16 @@ namespace textlayout {
|
||||
|
||||
class TextLine {
|
||||
public:
|
||||
|
||||
struct ClipContext {
|
||||
const Run* run;
|
||||
size_t pos;
|
||||
size_t size;
|
||||
SkScalar fTextShift; // Shifts the text inside the run so it's placed at the right position
|
||||
SkRect clip;
|
||||
bool clippingNeeded;
|
||||
};
|
||||
|
||||
TextLine() = default;
|
||||
~TextLine() = default;
|
||||
|
||||
@ -50,18 +60,12 @@ public:
|
||||
SkScalar ideographicBaseline() const { return fSizes.ideographicBaseline(); }
|
||||
SkScalar baseline() const { return fSizes.baseline(); }
|
||||
|
||||
using StyleVisitor = std::function<SkScalar(TextRange textRange, const TextStyle& style,
|
||||
SkScalar offsetX)>;
|
||||
void iterateThroughStylesInTextOrder(StyleType styleType, const StyleVisitor& visitor) const;
|
||||
using RunVisitor = std::function<bool(const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width)>;
|
||||
void iterateThroughVisualRuns(bool includingGhostSpaces, const RunVisitor& runVisitor) const;
|
||||
using RunStyleVisitor = std::function<void(TextRange textRange, const TextStyle& style, const ClipContext& context)>;
|
||||
SkScalar iterateThroughSingleRunByStyles(const Run* run, SkScalar runOffset, TextRange textRange,
|
||||
StyleType styleType, const RunStyleVisitor& visitor) const;
|
||||
|
||||
SkScalar calculateLeftVisualOffset(TextRange textRange) const;
|
||||
|
||||
using RunVisitor = std::function<bool(Run* run, size_t pos, size_t size, TextRange text,
|
||||
SkRect clip, SkScalar shift, bool clippingNeeded)>;
|
||||
SkScalar iterateThroughRuns(TextRange textRange,
|
||||
SkScalar offsetX,
|
||||
bool includeGhostWhitespaces,
|
||||
const RunVisitor& visitor) const;
|
||||
|
||||
using ClustersVisitor = std::function<bool(const Cluster* cluster, ClusterIndex index, bool leftToRight, bool ghost)>;
|
||||
void iterateThroughClustersInGlyphsOrder(bool reverse, bool includeGhosts, const ClustersVisitor& visitor) const;
|
||||
@ -72,33 +76,28 @@ public:
|
||||
void createEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool ltr);
|
||||
|
||||
// For testing internal structures
|
||||
void scanStyles(StyleType style, const StyleVisitor& visitor);
|
||||
void scanRuns(const RunVisitor& visitor);
|
||||
void scanStyles(StyleType style, const RunStyleVisitor& visitor);
|
||||
|
||||
TextAlign assumedTextAlign() const;
|
||||
|
||||
void setMaxRunMetrics(const LineMetrics& metrics) { fMaxRunMetrics = metrics; }
|
||||
LineMetrics getMaxRunMetrics() const { return fMaxRunMetrics; }
|
||||
|
||||
ClipContext measureTextInsideOneRun(TextRange textRange,
|
||||
const Run* run,
|
||||
SkScalar runOffsetInLine,
|
||||
SkScalar textOffsetInRunInLine,
|
||||
bool includeGhostSpaces) const;
|
||||
|
||||
private:
|
||||
|
||||
Run* shapeEllipsis(const SkString& ellipsis, Run* run);
|
||||
void justify(SkScalar maxWidth);
|
||||
|
||||
SkRect measureTextInsideOneRun(TextRange textRange,
|
||||
Run* run,
|
||||
size_t& pos,
|
||||
size_t& size,
|
||||
bool includeGhostSpaces,
|
||||
bool& clippingNeeded) const;
|
||||
|
||||
SkScalar paintText(SkCanvas* canvas, TextRange textRange, const TextStyle& style, SkScalar offsetX) const;
|
||||
SkScalar paintBackground(SkCanvas* canvas, TextRange textRange, const TextStyle& style,
|
||||
SkScalar offsetX) const;
|
||||
SkScalar paintShadow(SkCanvas* canvas, TextRange textRange, const TextStyle& style,
|
||||
SkScalar offsetX) const;
|
||||
SkScalar paintDecorations(SkCanvas* canvas, TextRange textRange, const TextStyle& style,
|
||||
SkScalar offsetX) const;
|
||||
void paintText(SkCanvas* canvas, TextRange textRange, const TextStyle& style, const ClipContext& context) const;
|
||||
void paintBackground(SkCanvas* canvas, TextRange textRange, const TextStyle& style, const ClipContext& context) const;
|
||||
void paintShadow(SkCanvas* canvas, TextRange textRange, const TextStyle& style, const ClipContext& context) const;
|
||||
void paintDecorations(SkCanvas* canvas, TextRange textRange, const TextStyle& style, const ClipContext& context) const;
|
||||
|
||||
void computeDecorationPaint(SkPaint& paint, SkRect clip, const TextStyle& style,
|
||||
SkPath& path) const;
|
||||
@ -117,7 +116,7 @@ private:
|
||||
ClusterRange fClusterRange;
|
||||
ClusterRange fGhostClusterRange;
|
||||
|
||||
SkTArray<size_t, true> fLogical;
|
||||
SkTArray<size_t, true> fRunsInVisualOrder;
|
||||
SkVector fAdvance; // Text size
|
||||
SkVector fOffset; // Text position
|
||||
SkScalar fShift; // Left right
|
||||
|
@ -31,11 +31,14 @@ TextStyle::TextStyle() : fFontStyle() {
|
||||
|
||||
TextStyle:: TextStyle(const TextStyle& other, bool placeholder) {
|
||||
fColor = other.fColor;
|
||||
fFontSize = other.fFontSize;
|
||||
fFontFamilies = other.fFontFamilies;
|
||||
fDecoration = other.fDecoration;
|
||||
fHasBackground = other.fHasBackground;
|
||||
fHasForeground = other.fHasForeground;
|
||||
fBackground = other.fBackground;
|
||||
fForeground = other.fForeground;
|
||||
fHeightOverride = other.fHeightOverride;
|
||||
fIsPlaceholder = placeholder;
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ void TextWrapper::trimEndSpaces(TextAlign align) {
|
||||
for (auto cluster = fEndLine.endCluster();
|
||||
cluster >= fEndLine.startCluster() && cluster->isWhitespaces();
|
||||
--cluster) {
|
||||
if ((/*left && */cluster->run()->leftToRight()) ||
|
||||
if ((cluster->run()->leftToRight()) ||
|
||||
(right && !cluster->run()->leftToRight()) ||
|
||||
align == TextAlign::kJustify || align == TextAlign::kCenter) {
|
||||
fEndLine.trim(cluster);
|
||||
@ -97,7 +97,7 @@ void TextWrapper::trimEndSpaces(TextAlign align) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!right) {
|
||||
if (!right || true) {
|
||||
fEndLine.trim();
|
||||
}
|
||||
}
|
||||
@ -142,6 +142,9 @@ void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
|
||||
SkScalar maxWidth,
|
||||
const AddLineToParagraph& addLine) {
|
||||
auto span = parent->clusters();
|
||||
if (span.size() == 0) {
|
||||
return;
|
||||
}
|
||||
auto maxLines = parent->paragraphStyle().getMaxLines();
|
||||
auto& ellipsisStr = parent->paragraphStyle().getEllipsis();
|
||||
auto align = parent->paragraphStyle().getTextAlign();
|
||||
@ -199,7 +202,7 @@ void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
|
||||
|
||||
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);
|
||||
TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
|
||||
TextRange textWithSpaces(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
|
||||
if (fEndLine.breakCluster()->isHardBreak()) {
|
||||
textWithSpaces.end = fEndLine.breakCluster()->textRange().start;
|
||||
@ -242,7 +245,7 @@ void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
|
||||
}
|
||||
TextRange empty(fEndLine.breakCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
|
||||
TextRange hardBreak(fEndLine.breakCluster()->textRange().end, fEndLine.breakCluster()->textRange().end);
|
||||
ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.breakCluster() - start);
|
||||
ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
|
||||
addLine(empty, hardBreak, clusters, clusters,
|
||||
0,
|
||||
0,
|
||||
|
@ -96,7 +96,11 @@ class TextWrapper {
|
||||
}
|
||||
|
||||
void trim() {
|
||||
if (fEnd.cluster()->run()->placeholder() == nullptr) {
|
||||
|
||||
if (fEnd.cluster() != nullptr &&
|
||||
fEnd.cluster()->master() != nullptr &&
|
||||
fEnd.cluster()->run() != nullptr &&
|
||||
fEnd.cluster()->run()->placeholder() == nullptr) {
|
||||
fWidth -= (fEnd.cluster()->width() - fEnd.cluster()->trimmedWidth(fEnd.position()));
|
||||
}
|
||||
}
|
||||
|
@ -343,6 +343,7 @@ public:
|
||||
break;
|
||||
}
|
||||
u = utf8_next(&fEndOfCurrentRun, fEnd);
|
||||
|
||||
fUTF16LogicalPosition += SkUTF::ToUTF16(u);
|
||||
}
|
||||
}
|
||||
|
@ -45,25 +45,6 @@ sk_sp<SkShader> setgrad(const SkRect& r, SkColor c0, SkColor c1) {
|
||||
return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
|
||||
}
|
||||
|
||||
const char* gText =
|
||||
"This is a very long sentence to test if the text will properly wrap "
|
||||
"around and go to the next line. Sometimes, short sentence. Longer "
|
||||
"sentences are okay too because they are nessecary. Very short. "
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
|
||||
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
|
||||
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
|
||||
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
|
||||
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
|
||||
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
|
||||
"mollit anim id est laborum. "
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
|
||||
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
|
||||
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
|
||||
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
|
||||
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
|
||||
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
|
||||
"mollit anim id est laborum.";
|
||||
|
||||
} // namespace
|
||||
|
||||
class ParagraphView1 : public ParagraphView_Base {
|
||||
@ -398,7 +379,6 @@ protected:
|
||||
canvas->translate(width, 0);
|
||||
drawText(canvas, width, height, very_word, SK_ColorBLACK, SK_ColorWHITE, "Google Sans", 30);
|
||||
canvas->translate(width, 0);
|
||||
|
||||
drawText(canvas, width, height / 2, text, SK_ColorBLACK, SK_ColorWHITE, "Roboto", 20, 100,
|
||||
u"\u2026");
|
||||
canvas->translate(0, height / 2);
|
||||
@ -1264,74 +1244,6 @@ private:
|
||||
typedef Sample INHERITED;
|
||||
};
|
||||
|
||||
// Measure different stages of layout/paint
|
||||
class ParagraphView12 : public ParagraphView_Base {
|
||||
protected:
|
||||
SkString name() override { return SkString("Paragraph12"); }
|
||||
|
||||
void onDrawContent(SkCanvas* canvas) override {
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.setMaxLines(14);
|
||||
paragraph_style.setTextAlign(TextAlign::kLeft);
|
||||
paragraph_style.turnHintingOff();
|
||||
ParagraphBuilderImpl builder(paragraph_style, getFontCollection());
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Roboto")});
|
||||
text_style.setFontSize(26);
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
text_style.setHeight(1);
|
||||
text_style.setDecoration(TextDecoration::kUnderline);
|
||||
text_style.setDecorationColor(SK_ColorBLACK);
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(gText);
|
||||
builder.pop();
|
||||
|
||||
auto paragraph = builder.Build();
|
||||
auto impl = reinterpret_cast<ParagraphImpl*>(paragraph.get());
|
||||
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
impl->setState(kUnknown);
|
||||
impl->shapeTextIntoEndlessLine();
|
||||
impl->setState(kShaped);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
impl->setState(kShaped);
|
||||
impl->buildClusterTable();
|
||||
impl->markLineBreaks();
|
||||
impl->setState(kMarked);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
impl->setState(kMarked);
|
||||
impl->breakShapedTextIntoLines(1000);
|
||||
impl->setState(kLineBroken);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
impl->setState(kLineBroken);
|
||||
impl->formatLines(1000);
|
||||
impl->setState(kFormatted);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
impl->setState(kFormatted);
|
||||
impl->paintLinesIntoPicture();
|
||||
impl->setState(kDrawn);
|
||||
}
|
||||
|
||||
auto picture = impl->getPicture();
|
||||
SkMatrix matrix = SkMatrix::MakeTrans(0, 0);
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
canvas->drawPicture(picture, &matrix, nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
typedef Sample INHERITED;
|
||||
};
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
DEF_SAMPLE(return new ParagraphView1();)
|
||||
@ -1345,4 +1257,4 @@ DEF_SAMPLE(return new ParagraphView8();)
|
||||
DEF_SAMPLE(return new ParagraphView9();)
|
||||
DEF_SAMPLE(return new ParagraphView10();)
|
||||
DEF_SAMPLE(return new ParagraphView11();)
|
||||
DEF_SAMPLE(return new ParagraphView12();)
|
||||
|
||||
|
@ -167,16 +167,16 @@ DEF_TEST(SkParagraph_SimpleParagraph, reporter) {
|
||||
size_t index = 0;
|
||||
for (auto& line : impl->lines()) {
|
||||
line.scanStyles(StyleType::kDecorations,
|
||||
[&index, reporter](TextRange text, TextStyle style, SkScalar) {
|
||||
[&index, reporter]
|
||||
(TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
|
||||
REPORTER_ASSERT(reporter, index == 0);
|
||||
REPORTER_ASSERT(reporter, style.getColor() == SK_ColorBLACK);
|
||||
++index;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Checked: DIFF? (letter_spacing/2 before the first letter)
|
||||
// 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");
|
||||
@ -275,7 +275,7 @@ DEF_TEST(SkParagraph_InlinePlaceholderParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[6].rect.bottom(), 50, EPSILON100));
|
||||
}
|
||||
|
||||
// Checked: DIFF? (letter_spacing/2 before the first letter)
|
||||
// Checked: DIFF+ (half of the letter spacing before the text???)
|
||||
DEF_TEST(SkParagraph_InlinePlaceholderBaselineParagraph, reporter) {
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
TestCanvas canvas("SkParagraph_InlinePlaceholderBaselineParagraph.png");
|
||||
@ -331,7 +331,7 @@ DEF_TEST(SkParagraph_InlinePlaceholderBaselineParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 44.694f, EPSILON100));
|
||||
}
|
||||
|
||||
// Checked: DIFF? (letter_spacing/2 before the first letter)
|
||||
// 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");
|
||||
@ -387,7 +387,7 @@ DEF_TEST(SkParagraph_InlinePlaceholderAboveBaselineParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 56, EPSILON100));
|
||||
}
|
||||
|
||||
// Checked: DIFF? (letter_spacing/2 before the first letter)
|
||||
// Checked: DIFF+ (half of the letter spacing before the text???)
|
||||
DEF_TEST(SkParagraph_InlinePlaceholderBelowBaselineParagraph, reporter) {
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
TestCanvas canvas("SkParagraph_InlinePlaceholderBelowBaselineParagraph.png");
|
||||
@ -443,7 +443,7 @@ DEF_TEST(SkParagraph_InlinePlaceholderBelowBaselineParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 30.347f, EPSILON100));
|
||||
}
|
||||
|
||||
// Checked: DIFF? (letter_spacing/2 before the first letter)
|
||||
// Checked: DIFF+ (half of the letter spacing before the text???)
|
||||
DEF_TEST(SkParagraph_InlinePlaceholderBottomParagraph, reporter) {
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
TestCanvas canvas("SkParagraph_InlinePlaceholderBottomParagraph.png");
|
||||
@ -497,7 +497,7 @@ DEF_TEST(SkParagraph_InlinePlaceholderBottomParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 50, EPSILON100));
|
||||
}
|
||||
|
||||
// Checked: DIFF? (letter_spacing/2 before the first letter)
|
||||
// Checked: DIFF+ (half of the letter spacing before the text???)
|
||||
DEF_TEST(SkParagraph_InlinePlaceholderTopParagraph, reporter) {
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
TestCanvas canvas("SkParagraph_InlinePlaceholderTopParagraph.png");
|
||||
@ -551,7 +551,7 @@ DEF_TEST(SkParagraph_InlinePlaceholderTopParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 30.468f, EPSILON100));
|
||||
}
|
||||
|
||||
// Checked: DIFF? (letter_spacing/2 before the first letter)
|
||||
// Checked: DIFF+ (half of the letter spacing before the text???)
|
||||
DEF_TEST(SkParagraph_InlinePlaceholderMiddleParagraph, reporter) {
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
TestCanvas canvas("SkParagraph_InlinePlaceholderMiddleParagraph.png");
|
||||
@ -605,7 +605,7 @@ DEF_TEST(SkParagraph_InlinePlaceholderMiddleParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 40.234f, EPSILON100));
|
||||
}
|
||||
|
||||
// Checked: DIFF? (letter_spacing/2 before the first letter)
|
||||
// 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");
|
||||
@ -658,7 +658,7 @@ DEF_TEST(SkParagraph_InlinePlaceholderIdeographicBaselineParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 42.065f, EPSILON100));
|
||||
}
|
||||
|
||||
// Checked: DIFF? (letter_spacing/2 before the first letter)
|
||||
// Checked: DIFF+ (half of the letter spacing before the text???)
|
||||
DEF_TEST(SkParagraph_InlinePlaceholderBreakParagraph, reporter) {
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
TestCanvas canvas("SkParagraph_InlinePlaceholderBreakParagraph.png");
|
||||
@ -793,7 +793,7 @@ DEF_TEST(SkParagraph_InlinePlaceholderBreakParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[17].rect.bottom(), 113.5f, EPSILON100));
|
||||
}
|
||||
|
||||
// Checked: DIFF? (letter_spacing/2 before the first letter)
|
||||
// Checked: DIFF+ (half of the letter spacing before the text???)
|
||||
DEF_TEST(SkParagraph_InlinePlaceholderGetRectsParagraph, reporter) {
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
TestCanvas canvas("SkParagraph_InlinePlaceholderGetRectsParagraph.png");
|
||||
@ -949,12 +949,12 @@ DEF_TEST(SkParagraph_SimpleRedParagraph, reporter) {
|
||||
size_t index = 0;
|
||||
for (auto& line : impl->lines()) {
|
||||
line.scanStyles(StyleType::kDecorations,
|
||||
[&index, reporter](TextRange text, TextStyle style, SkScalar) {
|
||||
REPORTER_ASSERT(reporter, index == 0);
|
||||
REPORTER_ASSERT(reporter, style.getColor() == SK_ColorRED);
|
||||
++index;
|
||||
return true;
|
||||
});
|
||||
[reporter, &index](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
|
||||
REPORTER_ASSERT(reporter, index == 0);
|
||||
REPORTER_ASSERT(reporter, style.getColor() == SK_ColorRED);
|
||||
++index;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1005,6 +1005,7 @@ DEF_TEST(SkParagraph_RainbowParagraph, reporter) {
|
||||
|
||||
TextStyle text_style3;
|
||||
text_style3.setFontFamilies({SkString("Homemade Apple")});
|
||||
text_style3.setColor(SK_ColorBLACK);
|
||||
builder.pushStyle(text_style3);
|
||||
builder.addText(text3);
|
||||
|
||||
@ -1035,23 +1036,24 @@ DEF_TEST(SkParagraph_RainbowParagraph, reporter) {
|
||||
|
||||
size_t index = 0;
|
||||
impl->lines()[0].scanStyles(
|
||||
StyleType::kAllAttributes, [&](TextRange text, TextStyle style, SkScalar) {
|
||||
StyleType::kAllAttributes,
|
||||
[&](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
REPORTER_ASSERT(reporter, style.equals(text_style1));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), text, text1));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), textRange, text1));
|
||||
break;
|
||||
case 1:
|
||||
REPORTER_ASSERT(reporter, style.equals(text_style2));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), text, text2));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), textRange, text2));
|
||||
break;
|
||||
case 2:
|
||||
REPORTER_ASSERT(reporter, style.equals(text_style3));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), text, text3));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), textRange, text3));
|
||||
break;
|
||||
case 3:
|
||||
REPORTER_ASSERT(reporter, style.equals(text_style4));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), text, text41));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), textRange, text41));
|
||||
break;
|
||||
default:
|
||||
REPORTER_ASSERT(reporter, false);
|
||||
@ -1061,11 +1063,12 @@ DEF_TEST(SkParagraph_RainbowParagraph, reporter) {
|
||||
return true;
|
||||
});
|
||||
impl->lines()[1].scanStyles(
|
||||
StyleType::kAllAttributes, [&](TextRange text, TextStyle style, SkScalar) {
|
||||
StyleType::kAllAttributes,
|
||||
[&](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
|
||||
switch (index) {
|
||||
case 4:
|
||||
REPORTER_ASSERT(reporter, style.equals(text_style4));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), text, text42));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), textRange, text42));
|
||||
break;
|
||||
default:
|
||||
REPORTER_ASSERT(reporter, false);
|
||||
@ -1104,9 +1107,10 @@ DEF_TEST(SkParagraph_DefaultStyleParagraph, reporter) {
|
||||
|
||||
size_t index = 0;
|
||||
impl->lines()[0].scanStyles(
|
||||
StyleType::kAllAttributes, [&](TextRange text1, TextStyle style, SkScalar) {
|
||||
StyleType::kAllAttributes,
|
||||
[&](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
|
||||
REPORTER_ASSERT(reporter, style.equals(paragraph_style.getTextStyle()));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), text1, text));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), textRange, text));
|
||||
++index;
|
||||
return true;
|
||||
});
|
||||
@ -1147,9 +1151,10 @@ DEF_TEST(SkParagraph_BoldParagraph, reporter) {
|
||||
|
||||
size_t index = 0;
|
||||
impl->lines()[0].scanStyles(
|
||||
StyleType::kAllAttributes, [&](TextRange text1, TextStyle style, SkScalar) {
|
||||
StyleType::kAllAttributes,
|
||||
[&](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
|
||||
REPORTER_ASSERT(reporter, style.equals(text_style));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), text1, text));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), textRange, text));
|
||||
++index;
|
||||
return true;
|
||||
});
|
||||
@ -1681,7 +1686,8 @@ DEF_TEST(SkParagraph_DecorationsParagraph, reporter) {
|
||||
size_t index = 0;
|
||||
for (auto& line : impl->lines()) {
|
||||
line.scanStyles(
|
||||
StyleType::kDecorations, [&index, reporter](TextRange, TextStyle style, SkScalar) {
|
||||
StyleType::kDecorations,
|
||||
[&](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
|
||||
auto decoration = (TextDecoration)(TextDecoration::kUnderline |
|
||||
TextDecoration::kOverline |
|
||||
TextDecoration::kLineThrough);
|
||||
@ -1773,7 +1779,7 @@ DEF_TEST(SkParagraph_ItalicsParagraph, reporter) {
|
||||
size_t index = 0;
|
||||
line.scanStyles(
|
||||
StyleType::kForeground,
|
||||
[&index, reporter](TextRange textRange, TextStyle style, SkScalar) {
|
||||
[&](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
REPORTER_ASSERT(
|
||||
@ -1972,16 +1978,11 @@ DEF_TEST(SkParagraph_ArabicRectsLTRLeftAlignParagraph, reporter) {
|
||||
std::vector<TextBox> boxes = paragraph->getRectsForRange(36, 40, rect_height_style, rect_width_style);
|
||||
canvas.drawRects(SK_ColorRED, boxes);
|
||||
|
||||
REPORTER_ASSERT(reporter, boxes.size() == 2ull); // DIFF: 1
|
||||
REPORTER_ASSERT(reporter, boxes.size() == 1ull);
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 83.916f, EPSILON100)); // DIFF: 89.40625
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), -0.268f, EPSILON100));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 110.155f, EPSILON100)); // DIFF: 121.87891
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 115.893f, EPSILON100)); // DIFF: 121.87891
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 44, EPSILON100));
|
||||
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.left(), 422.414f, EPSILON100));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.top(), -0.268f, EPSILON100));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.right(), 428.152f, EPSILON100));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.bottom(), 44, EPSILON100));
|
||||
}
|
||||
|
||||
// Checked DIFF+
|
||||
@ -2024,16 +2025,11 @@ DEF_TEST(SkParagraph_ArabicRectsLTRRightAlignParagraph, reporter) {
|
||||
paragraph->getRectsForRange(36, 40, rect_height_style, rect_width_style);
|
||||
canvas.drawRects(SK_ColorRED, boxes);
|
||||
|
||||
REPORTER_ASSERT(reporter, boxes.size() == 2ull);
|
||||
REPORTER_ASSERT(reporter, boxes.size() == 1ull); // DIFF
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 561.501f, EPSILON100)); // DIFF
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), -0.268f, EPSILON100));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 587.741f, EPSILON100)); // DIFF
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 593.479f, EPSILON100)); // DIFF
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 44, EPSILON100));
|
||||
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.left(), 900, EPSILON100)); // DIFF
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.top(), -0.268f, EPSILON100));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.right(), 905.738f, EPSILON100)); // DIFF
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.bottom(), 44, EPSILON100));
|
||||
}
|
||||
|
||||
// Checked: NO DIFF
|
||||
@ -2825,6 +2821,7 @@ DEF_TEST(SkParagraph_GetRectsForRangeCenterParagraphNewlineCentered, reporter) {
|
||||
|
||||
{
|
||||
auto result = paragraph->getRectsForRange(0, 1, heightStyle, widthStyle);
|
||||
canvas.drawRects(SK_ColorRED, result);
|
||||
REPORTER_ASSERT(reporter, result.size() == 1);
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(result[0].rect.left(), 203.955f, EPSILON100));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(result[0].rect.top(), 0.40625f, EPSILON100));
|
||||
@ -3169,17 +3166,17 @@ DEF_TEST(SkParagraph_SpacingParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == 1);
|
||||
size_t index = 0;
|
||||
impl->lines().begin()->scanStyles(StyleType::kLetterSpacing,
|
||||
[&index](TextRange text, TextStyle style, SkScalar) {
|
||||
++index;
|
||||
return true;
|
||||
});
|
||||
[&](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
|
||||
++index;
|
||||
return true;
|
||||
});
|
||||
REPORTER_ASSERT(reporter, index == 4);
|
||||
index = 0;
|
||||
impl->lines().begin()->scanStyles(StyleType::kWordSpacing,
|
||||
[&index](TextRange text, TextStyle style, SkScalar) {
|
||||
++index;
|
||||
return true;
|
||||
});
|
||||
[&](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
|
||||
++index;
|
||||
return true;
|
||||
});
|
||||
REPORTER_ASSERT(reporter, index == 4);
|
||||
}
|
||||
|
||||
@ -3310,7 +3307,7 @@ DEF_TEST(SkParagraph_NewlineParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, impl->lines()[6].offset().fY == 420);
|
||||
}
|
||||
|
||||
// Checked: DIFF? (underline)
|
||||
// TODO: Fix underline
|
||||
DEF_TEST(SkParagraph_EmojiParagraph, reporter) {
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
@ -3487,15 +3484,7 @@ DEF_TEST(SkParagraph_Ellipsize, reporter) {
|
||||
|
||||
auto& line = impl->lines()[0];
|
||||
REPORTER_ASSERT(reporter, line.ellipsis() != nullptr);
|
||||
size_t index = 0;
|
||||
line.scanRuns([&index, &line, reporter](Run* run, int32_t, size_t, TextRange, SkRect, SkScalar, bool) {
|
||||
++index;
|
||||
if (index == 2) {
|
||||
REPORTER_ASSERT(reporter, run->textRange() == line.ellipsis()->textRange());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
REPORTER_ASSERT(reporter, index == 2);
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
|
||||
}
|
||||
|
||||
// Checked: NO DIFF
|
||||
@ -3599,7 +3588,7 @@ DEF_TEST(SkParagraph_SimpleShadow, reporter) {
|
||||
size_t index = 0;
|
||||
for (auto& line : impl->lines()) {
|
||||
line.scanStyles(StyleType::kShadow,
|
||||
[&index, text_style, reporter](TextRange text, TextStyle style, SkScalar) {
|
||||
[&](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
|
||||
REPORTER_ASSERT(reporter, index == 0 && style.equals(text_style));
|
||||
++index;
|
||||
return true;
|
||||
@ -3649,7 +3638,7 @@ DEF_TEST(SkParagraph_ComplexShadow, reporter) {
|
||||
size_t index = 0;
|
||||
for (auto& line : impl->lines()) {
|
||||
line.scanStyles(StyleType::kShadow,
|
||||
[&index, text_style, reporter](TextRange text, TextStyle style, SkScalar) {
|
||||
[&](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
|
||||
++index;
|
||||
switch (index) {
|
||||
case 1:
|
||||
@ -3914,8 +3903,8 @@ DEF_TEST(SkParagraph_StrutParagraph1, reporter) {
|
||||
// Checked: NO DIFF
|
||||
DEF_TEST(SkParagraph_StrutParagraph2, reporter) {
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
TestCanvas canvas("SkParagraph_StrutParagraph2.png");
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
TestCanvas canvas("SkParagraph_StrutParagraph2.png");
|
||||
// The chinese extra height should be absorbed by the strut.
|
||||
const char* text = "01234ABCDEFGH\nabcd\nABCDEFGH";
|
||||
|
||||
@ -4129,7 +4118,7 @@ DEF_TEST(SkParagraph_StrutForceParagraph, reporter) {
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
TestCanvas canvas("SkParagraph_StrutForceParagraph.png");
|
||||
const char* text = "01234満毎冠行来昼本可\nabcd\n満毎冠行来昼本可";
|
||||
const char* text = "01234満毎冠行来昼本可\nabcd\n満毎冠行来昼本可";
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.setMaxLines(10);
|
||||
@ -4552,23 +4541,23 @@ DEF_TEST(SkParagraph_CacheStyles, reporter) {
|
||||
test(2, false);
|
||||
}
|
||||
|
||||
DEF_TEST(SkParagraph_EmptyParagraph, reporter) {
|
||||
DEF_TEST(SkParagraph_EmptyParagraphWithLineBreak, reporter) {
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
TestCanvas canvas("SkParagraph_EmptyParagraph.png");
|
||||
fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
|
||||
TestCanvas canvas("SkParagraph_EmptyParagraphWithLineBreak.png");
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
//builder.pushStyle(text_style);
|
||||
builder.addText("");
|
||||
//builder.pop();
|
||||
text_style.setFontSize(16);
|
||||
text_style.setFontFamilies({SkString("Roboto")});
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
builder.addText("\n");
|
||||
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(TestCanvasWidth);
|
||||
paragraph->paint(canvas.get(), 0, 0);
|
||||
auto result = paragraph->GetRectsForPlaceholders();
|
||||
}
|
||||
|
||||
DEF_TEST(SkParagraph_PlaceholderOnly, reporter) {
|
||||
@ -4579,10 +4568,147 @@ DEF_TEST(SkParagraph_PlaceholderOnly, reporter) {
|
||||
ParagraphStyle paragraph_style;
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
|
||||
PlaceholderStyle placeholder(50, 50, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 0);
|
||||
PlaceholderStyle placeholder(0, 0, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 0);
|
||||
builder.addPlaceholder(placeholder);
|
||||
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(TestCanvasWidth);
|
||||
auto result = paragraph->GetRectsForPlaceholders();
|
||||
paragraph->paint(canvas.get(), 0, 0);
|
||||
}
|
||||
|
||||
DEF_TEST(SkParagraph_Fallbacks, reporter) {
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
fontCollection->setDefaultFontManager(SkFontMgr::RefDefault(), "Arial");
|
||||
TestCanvas canvas("SkParagraph_Fallbacks.png");
|
||||
|
||||
const char* multiScript = "A1!aÀàĀāƁƀḂⱠꜲꬰəͲἀἏЀЖԠꙐꙮՁخࡔࠇܦআਉઐଘஇఘಧൺඣᭆᯔᮯ᳇ꠈᜅᩌꪈ༇ꥄꡙꫤ᧰៘꧁꧂ᜰᨏᯤᢆᣭᗗꗃⵞ𐒎߷ጩꬤ𖠺‡₩℻Ⅷ↹⋇⏳ⓖ╋▒◛⚧⑆שׁ🅕㊼龜ポ䷤🂡\n";
|
||||
|
||||
const char* androidFonts[] = {
|
||||
"sans-serif",
|
||||
"sans-serif-condensed",
|
||||
"serif",
|
||||
"monospace",
|
||||
"serif-monospace",
|
||||
"casual",
|
||||
"cursive",
|
||||
"sans-serif-smallcaps",
|
||||
};
|
||||
|
||||
for (auto& font : androidFonts) {
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
text_style.setLocale(SkString("en_US"));
|
||||
text_style.setFontSize(20);
|
||||
|
||||
text_style.setFontFamilies({ SkString(font) });
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(multiScript);
|
||||
|
||||
builder.pop();
|
||||
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(TestCanvasWidth);
|
||||
paragraph->paint(canvas.get(), 0, 0);
|
||||
canvas.get()->translate(0, paragraph.get()->getHeight() + 10);
|
||||
}
|
||||
}
|
||||
|
||||
DEF_TEST(SkParagraph_Bidi1, reporter) {
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
|
||||
TestCanvas canvas("SkParagraph_Bidi1.png");
|
||||
|
||||
std::u16string abc = u"\u202Dabc";
|
||||
std::u16string DEF = u"\u202EDEF";
|
||||
std::u16string ghi = u"\u202Dghi";
|
||||
std::u16string JKL = u"\u202EJKL";
|
||||
std::u16string mno = u"\u202Dmno";
|
||||
|
||||
std::u16string abcDEFghiJKLmno = u"\u202Dabc\u202EDEF\u202Dghi\u202EJKL\u202Dmno";
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({ SkString("sans-serif")});
|
||||
text_style.setFontSize(40);
|
||||
|
||||
text_style.setColor(SK_ColorCYAN);
|
||||
text_style.setFontStyle(SkFontStyle(SkFontStyle::kThin_Weight, SkFontStyle::kNormal_Width, SkFontStyle::kUpright_Slant));
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(abc);
|
||||
|
||||
text_style.setColor(SK_ColorGREEN);
|
||||
text_style.setFontStyle(SkFontStyle(SkFontStyle::kLight_Weight, SkFontStyle::kNormal_Width, SkFontStyle::kUpright_Slant));
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(DEF);
|
||||
|
||||
text_style.setColor(SK_ColorYELLOW);
|
||||
text_style.setFontStyle(SkFontStyle(SkFontStyle::kNormal_Weight, SkFontStyle::kNormal_Width, SkFontStyle::kUpright_Slant));
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(ghi);
|
||||
|
||||
text_style.setColor(SK_ColorMAGENTA);
|
||||
text_style.setFontStyle(SkFontStyle(SkFontStyle::kMedium_Weight, SkFontStyle::kNormal_Width, SkFontStyle::kUpright_Slant));
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(JKL);
|
||||
|
||||
text_style.setColor(SK_ColorBLUE);
|
||||
text_style.setFontStyle(SkFontStyle(SkFontStyle::kBlack_Weight, SkFontStyle::kNormal_Width, SkFontStyle::kUpright_Slant));
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(mno);
|
||||
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(400);
|
||||
paragraph->paint(canvas.get(), 0, 0);
|
||||
}
|
||||
|
||||
DEF_TEST(SkParagraph_Bidi2, reporter) {
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
|
||||
TestCanvas canvas("SkParagraph_Bidi2.png");
|
||||
|
||||
std::u16string abcD = u"\u202Dabc\u202ED";
|
||||
std::u16string EFgh = u"EF\u202Dgh";
|
||||
std::u16string iJKLmno = u"i\u202EJKL\u202Dmno";
|
||||
|
||||
std::u16string abcDEFghiJKLmno = u"\u202Dabc\u202EDEF\u202Dghi\u202EJKL\u202Dmno";
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({ SkString("sans-serif")});
|
||||
text_style.setFontSize(40);
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
|
||||
text_style.setColor(SK_ColorYELLOW);
|
||||
text_style.setFontSize(40);
|
||||
text_style.setFontStyle(SkFontStyle(SkFontStyle::kThin_Weight, SkFontStyle::kNormal_Width, SkFontStyle::kUpright_Slant));
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(abcD);
|
||||
|
||||
text_style.setColor(SK_ColorRED);
|
||||
text_style.setFontSize(50);
|
||||
text_style.setFontStyle(SkFontStyle(SkFontStyle::kMedium_Weight, SkFontStyle::kNormal_Width, SkFontStyle::kUpright_Slant));
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(EFgh);
|
||||
|
||||
text_style.setColor(SK_ColorMAGENTA);
|
||||
text_style.setFontSize(60);
|
||||
text_style.setFontStyle(SkFontStyle(SkFontStyle::kExtraBold_Weight, SkFontStyle::kNormal_Width, SkFontStyle::kUpright_Slant));
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(iJKLmno);
|
||||
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(360);
|
||||
paragraph->paint(canvas.get(), 0, 0);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user