Bugs
Mainly, simplified iteration over visual run for performance reasons.
Check for locale when comparing fonts.
Try to resolve ALL unresolved codepoints.
Change-Id: Ic126ca9bcb3970e2cbd6da9c384c493f9fd81b0d
Bug: skia:9956, skia:9970, skia:9951
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/273463
Commit-Queue: Julia Lavrova <jlavrova@google.com>
Reviewed-by: Ben Wagner <bungeman@google.com>
diff --git a/modules/skparagraph/include/TextStyle.h b/modules/skparagraph/include/TextStyle.h
index 08509d7..d24b644 100644
--- a/modules/skparagraph/include/TextStyle.h
+++ b/modules/skparagraph/include/TextStyle.h
@@ -51,6 +51,7 @@
enum TextDecorationStyle { kSolid, kDouble, kDotted, kDashed, kWavy };
enum StyleType {
+ kNone,
kAllAttributes,
kFont,
kForeground,
diff --git a/modules/skparagraph/src/OneLineShaper.cpp b/modules/skparagraph/src/OneLineShaper.cpp
index 5ba6e8a..48c6e91 100644
--- a/modules/skparagraph/src/OneLineShaper.cpp
+++ b/modules/skparagraph/src/OneLineShaper.cpp
@@ -4,6 +4,7 @@
#include "modules/skparagraph/src/OneLineShaper.h"
#include <unicode/uchar.h>
#include <algorithm>
+#include <unordered_set>
#include "src/utils/SkUTF.h"
namespace skia {
@@ -262,7 +263,7 @@
RunBlock unresolved(fCurrentRun, clusteredText(glyphRange), glyphRange, 0);
if (unresolved.fGlyphs.width() == fCurrentRun->size()) {
SkASSERT(unresolved.fText.width() == fCurrentRun->fTextRange.width());
- } else if (!fUnresolvedBlocks.empty()) {
+ } else if (fUnresolvedBlocks.size() > 1) {
auto& lastUnresolved = fUnresolvedBlocks.back();
if (lastUnresolved.fRun != nullptr &&
lastUnresolved.fRun->fIndex == fCurrentRun->fIndex) {
@@ -407,25 +408,44 @@
auto unresolvedRange = fUnresolvedBlocks.front().fText;
auto unresolvedText = fParagraph->text(unresolvedRange);
const char* ch = unresolvedText.begin();
+ // TODO: Make in a global cache for all fallback fonts
+ std::unordered_set<SkUnichar> tried;
SkUnichar unicode = utf8_next(&ch, unresolvedText.end());
+ while (true) {
+ auto typeface = fParagraph->fFontCollection->defaultFallback(
+ unicode, textStyle.getFontStyle(), textStyle.getLocale());
- auto typeface = fParagraph->fFontCollection->defaultFallback(
- unicode, textStyle.getFontStyle(), textStyle.getLocale());
-
- if (typeface == nullptr) {
- return;
- }
-
- if (!visitor(typeface)) {
- // Resolved everything
- return;
- } else {
- // Check if anything was resolved and stop it it was not
- auto last = fUnresolvedBlocks.back();
- if (unresolvedRange == last.fText) {
+ if (typeface == nullptr) {
return;
}
+
+ if (!visitor(typeface)) {
+ // Resolved everything
+ return;
+ }
+
+ // Check if anything was resolved and stop it it was not
+ auto last = fUnresolvedBlocks.back();
+ if (!(unresolvedRange == last.fText)) {
+ // Resolved something, no need to repeat
+ break;
+ }
+
+ if (ch == unresolvedText.end()) {
+ // Not a single codepoint could be resolved but we can switch to another block
+ break;
+ }
+
+ // We can stop here or we can switch to another DIFFERENT codepoint
+ while (ch != unresolvedText.end()) {
+ unicode = utf8_next(&ch, unresolvedText.end());
+ if (tried.find(unicode) == tried.end()) {
+ tried.emplace(unicode);
+ break;
+ }
+ }
}
+
}
}
}
diff --git a/modules/skparagraph/src/ParagraphImpl.cpp b/modules/skparagraph/src/ParagraphImpl.cpp
index b1328a4..066fd35 100644
--- a/modules/skparagraph/src/ParagraphImpl.cpp
+++ b/modules/skparagraph/src/ParagraphImpl.cpp
@@ -9,8 +9,9 @@
#include "modules/skparagraph/src/TextWrapper.h"
#include "src/core/SkSpan.h"
#include "src/utils/SkUTF.h"
-#include <algorithm>
#include <unicode/ustring.h>
+#include <algorithm>
+#include <chrono>
#include <queue>
namespace skia {
@@ -138,12 +139,10 @@
}
if (fState < kShaped) {
-
fGraphemes.reset();
this->markGraphemes();
if (!this->shapeTextIntoEndlessLine()) {
-
this->resetContext();
// TODO: merge the two next calls - they always come together
this->resolveStrut();
@@ -180,7 +179,7 @@
fState = kMarked;
}
- if (fState >= kLineBroken) {
+ if (fState >= kLineBroken) {
if (fOldWidth != floorWidth || fOldHeight != fHeight) {
fState = kMarked;
}
@@ -193,7 +192,6 @@
this->fLines.reset();
this->breakShapedTextIntoLines(floorWidth);
fState = kLineBroken;
-
}
if (fState < kFormatted) {
@@ -212,7 +210,8 @@
fMaxIntrinsicWidth = littleRound(fMaxIntrinsicWidth);
// TODO: This is strictly Flutter thing. Must be factored out into some flutter code
- if (fParagraphStyle.getMaxLines() == 1 || (fParagraphStyle.unlimited_lines() && fParagraphStyle.ellipsized())) {
+ if (fParagraphStyle.getMaxLines() == 1 ||
+ (fParagraphStyle.unlimited_lines() && fParagraphStyle.ellipsized())) {
fMinIntrinsicWidth = fMaxIntrinsicWidth;
}
}
@@ -605,6 +604,7 @@
if (fText.isEmpty()) {
if (start == 0 && end > 0) {
// On account of implied "\n" that is always at the end of the text
+ //SkDebugf("getRectsForRange(%d, %d): %f\n", start, end, fHeight);
results.emplace_back(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
}
return results;
@@ -649,6 +649,9 @@
}
}
+ auto firstBoxOnTheLine = results.size();
+ const Run* lastRun = nullptr;
+ auto paragraphTextDirection = paragraphStyle().getTextDirection();
for (auto& line : fLines) {
auto lineText = line.textWithSpaces();
auto intersect = lineText * text;
@@ -656,42 +659,23 @@
continue;
}
- // Found a line that intersects with the text
- auto firstBoxOnTheLine = results.size();
- auto paragraphTextDirection = paragraphStyle().getTextDirection();
- const Run* lastRun = nullptr;
line.iterateThroughVisualRuns(true,
- [&](const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width) {
+ [&]
+ (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
+ *runWidthInLine = line.iterateThroughSingleRunByStyles(
+ run, runOffsetInLine, textRange, StyleType::kNone,
+ [&]
+ (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context0) {
auto intersect = textRange * text;
- if (intersect.empty() || textRange.empty()) {
- auto context = line.measureTextInsideOneRun(textRange, run, runOffset, 0, true, false);
- *width = context.clip.width();
- if (textRange.width() > 0) {
- return true;
- } else {
- intersect = textRange;
- }
- } else {
- TextRange head;
- if (run->leftToRight() && textRange.start != intersect.start) {
- head = TextRange(textRange.start, intersect.start);
- *width = line.measureTextInsideOneRun(head, run, runOffset, 0, true, false).clip.width();
- } else if (!run->leftToRight() && textRange.end != intersect.end) {
- head = TextRange(intersect.end, textRange.end);
- *width = line.measureTextInsideOneRun(head, run, runOffset, 0, true, false).clip.width();
- } else {
- *width = 0;
- }
+ if (intersect.empty()) {
+ return true;
}
- auto runInLineWidth = line.measureTextInsideOneRun(textRange, run, runOffset, 0, true, false).clip.width();
- runOffset += *width;
- *width = runInLineWidth;
-
// Found a run that intersects with the text
- auto context = line.measureTextInsideOneRun(intersect, run, runOffset, 0, true, true);
+ auto context = line.measureTextInsideOneRun(intersect, run, runOffsetInLine, 0, true, true);
SkRect clip = context.clip;
+ clip.offset(context0.fTextShift - context.fTextShift, 0);
if (rectHeightStyle == RectHeightStyle::kMax) {
// TODO: Change it once flutter rolls into google3
@@ -798,9 +782,10 @@
if (!nearlyZero(trailingSpaces.width()) && !merge(trailingSpaces)) {
results.emplace_back(trailingSpaces, paragraphTextDirection);
}
-
return true;
});
+ return true;
+ });
if (rectWidthStyle == RectWidthStyle::kMax) {
// Align the very left/right box horizontally
@@ -822,14 +807,12 @@
}
for (auto& r : results) {
-
- r.rect.fLeft = littleRound(r.rect.fLeft);
- r.rect.fRight = littleRound(r.rect.fRight);
- r.rect.fTop = littleRound(r.rect.fTop);
- r.rect.fBottom = littleRound(r.rect.fBottom);
+ r.rect.fLeft = littleRound(r.rect.fLeft);
+ r.rect.fRight = littleRound(r.rect.fRight);
+ r.rect.fTop = littleRound(r.rect.fTop);
+ r.rect.fBottom = littleRound(r.rect.fBottom);
}
}
-
return results;
}
@@ -889,97 +872,99 @@
// This is so far the the line vertically closest to our coordinates
// (or the first one, or the only one - all the same)
line.iterateThroughVisualRuns(true,
- [this, &line, dx, &result]
- (const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width) {
+ [this, &line, dx, &result]
+ (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
+ *runWidthInLine = line.iterateThroughSingleRunByStyles(
+ run, runOffsetInLine, textRange, StyleType::kNone,
+ [this, line, dx, &result]
+ (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
- auto findCodepointByTextIndex = [this](ClusterIndex clusterIndex8) {
- auto codepoint = std::lower_bound(
- fCodePoints.begin(), fCodePoints.end(),
- clusterIndex8,
- [](const Codepoint& lhs,size_t rhs) -> bool { return lhs.fTextIndex < rhs; });
+ auto findCodepointByTextIndex = [this](ClusterIndex clusterIndex8) {
+ auto codepoint = std::lower_bound(
+ fCodePoints.begin(), fCodePoints.end(),
+ clusterIndex8,
+ [](const Codepoint& lhs,size_t rhs) -> bool { return lhs.fTextIndex < rhs; });
- return codepoint - fCodePoints.begin();
- };
+ return codepoint - fCodePoints.begin();
+ };
- auto offsetX = line.offset().fX;
- auto context = line.measureTextInsideOneRun(textRange, run, runOffset, 0, true, false);
- *width = context.clip.width();
- if (dx < context.clip.fLeft + offsetX) {
- // All the other runs are placed right of this one
- auto codepointIndex = findCodepointByTextIndex(context.run->globalClusterIndex(context.pos));
- result = { SkToS32(codepointIndex), kDownstream };
+ auto offsetX = line.offset().fX;
+ if (dx < context.clip.fLeft + offsetX) {
+ // All the other runs are placed right of this one
+ auto codepointIndex = findCodepointByTextIndex(context.run->globalClusterIndex(context.pos));
+ result = { SkToS32(codepointIndex), kDownstream };
+ return false;
+ }
+
+ if (dx >= context.clip.fRight + offsetX) {
+ // We have to keep looking but just in case keep the last one as the closes
+ // so far
+ auto codepointIndex = findCodepointByTextIndex(context.run->globalClusterIndex(context.pos + context.size));
+ result = { SkToS32(codepointIndex), 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 = context.pos;
+ for (size_t i = context.pos; i < context.pos + context.size; ++i) {
+ // TODO: this rounding is done to match Flutter tests. Must be removed..
+ auto index = context.run->leftToRight() ? i : context.size - i;
+ auto end = littleRound(context.run->positionX(index) + context.fTextShift + offsetX);
+ if ((context.run->leftToRight() ? end > dx : dx > end)) {
+ break;
+ }
+ found = index;
+ }
+
+ if (!context.run->leftToRight()) {
+ --found;
+ }
+
+ auto glyphStart = context.run->positionX(found) + context.fTextShift + offsetX;
+ auto glyphWidth = context.run->positionX(found + 1) - context.run->positionX(found);
+ auto clusterIndex8 = context.run->globalClusterIndex(found);
+ auto clusterEnd8 = context.run->globalClusterIndex(found + 1);
+ TextRange clusterText (clusterIndex8, clusterEnd8);
+
+ // Find the grapheme positions in codepoints that contains the point
+ auto codepointIndex = findCodepointByTextIndex(clusterIndex8);
+ CodepointRange codepoints(codepointIndex, codepointIndex);
+ for (codepoints.end = codepointIndex + 1; codepoints.end < fCodePoints.size(); ++codepoints.end) {
+ auto& cp = fCodePoints[codepoints.end];
+ if (cp.fTextIndex >= clusterText.end) {
+ break;
+ }
+ }
+ auto graphemeSize = codepoints.width();
+
+ // We only need to inspect one glyph (maybe not even the entire glyph)
+ SkScalar center;
+ bool insideGlyph = false;
+ if (graphemeSize > 1) {
+ auto averageCodepointWidth = glyphWidth / graphemeSize;
+ auto delta = dx - glyphStart;
+ auto insideIndex = SkScalarFloorToInt(delta / averageCodepointWidth);
+ insideGlyph = delta > averageCodepointWidth;
+ center = glyphStart + averageCodepointWidth * insideIndex + averageCodepointWidth / 2;
+ codepointIndex += insideIndex;
+ } else {
+ center = glyphStart + glyphWidth / 2;
+ }
+ if ((dx < center) == context.run->leftToRight() || insideGlyph) {
+ result = { SkToS32(codepointIndex), kDownstream };
+ } else {
+ result = { SkToS32(codepointIndex + 1), kUpstream };
+ }
+ // No need to continue
return false;
- }
- if (dx >= context.clip.fRight + offsetX) {
- // We have to keep looking but just in case keep the last one as the closes
- // so far
- auto codepointIndex = findCodepointByTextIndex(context.run->globalClusterIndex(context.pos + context.size));
- result = { SkToS32(codepointIndex), 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 = context.pos;
- for (size_t i = context.pos; i < context.pos + context.size; ++i) {
- // TODO: this rounding is done to match Flutter tests. Must be removed..
- auto index = context.run->leftToRight() ? i : context.size - i;
- auto end = littleRound(context.run->positionX(index) + context.fTextShift + offsetX);
- if ((context.run->leftToRight() ? end > dx : dx > end)) {
- break;
- }
- found = index;
- }
-
- if (!context.run->leftToRight()) {
- --found;
- }
-
- auto glyphStart = context.run->positionX(found) + context.fTextShift + offsetX;
- auto glyphWidth = context.run->positionX(found + 1) - context.run->positionX(found);
- auto clusterIndex8 = context.run->globalClusterIndex(found);
- auto clusterEnd8 = context.run->globalClusterIndex(found + 1);
- TextRange clusterText (clusterIndex8, clusterEnd8);
-
- // Find the grapheme positions in codepoints that contains the point
- auto codepointIndex = findCodepointByTextIndex(clusterIndex8);
- CodepointRange codepoints(codepointIndex, codepointIndex);
- for (codepoints.end = codepointIndex + 1; codepoints.end < fCodePoints.size(); ++codepoints.end) {
- auto& cp = fCodePoints[codepoints.end];
- if (cp.fTextIndex >= clusterText.end) {
- break;
- }
- }
- auto graphemeSize = codepoints.width();
-
- // We only need to inspect one glyph (maybe not even the entire glyph)
- SkScalar center;
- bool insideGlyph = false;
- if (graphemeSize > 1) {
- auto averageCodepointWidth = glyphWidth / graphemeSize;
- auto delta = dx - glyphStart;
- auto insideIndex = SkScalarFloorToInt(delta / averageCodepointWidth);
- insideGlyph = delta > averageCodepointWidth;
- center = glyphStart + averageCodepointWidth * insideIndex + averageCodepointWidth / 2;
- codepointIndex += insideIndex;
- } else {
- center = glyphStart + glyphWidth / 2;
- }
- if ((dx < center) == context.run->leftToRight() || insideGlyph) {
- result = { SkToS32(codepointIndex), kDownstream };
- } else {
- result = { SkToS32(codepointIndex + 1), kUpstream };
- }
- // No need to continue
- return false;
+ });
+ return true;
});
-
break;
}
-
- //SkDebugf("getGlyphPositionAtCoordinate(%f,%f) = %d\n", dx, dy, result.position);
return result;
}
diff --git a/modules/skparagraph/src/TextLine.cpp b/modules/skparagraph/src/TextLine.cpp
index 5924c92..0708b59 100644
--- a/modules/skparagraph/src/TextLine.cpp
+++ b/modules/skparagraph/src/TextLine.cpp
@@ -793,6 +793,16 @@
SkASSERT(false);
}
+ if (styleType == StyleType::kNone) {
+ ClipContext clipContext = this->measureTextInsideOneRun(textRange, run, runOffset, 0, false, false);
+ if (clipContext.clip.height() > 0) {
+ visitor(textRange, TextStyle(), clipContext);
+ return clipContext.clip.width();
+ } else {
+ return 0;
+ }
+ }
+
TextIndex start = EMPTY_INDEX;
size_t size = 0;
const TextStyle* prevStyle = nullptr;
diff --git a/modules/skparagraph/src/TextStyle.cpp b/modules/skparagraph/src/TextStyle.cpp
index d4ce181..c211ab9 100644
--- a/modules/skparagraph/src/TextStyle.cpp
+++ b/modules/skparagraph/src/TextStyle.cpp
@@ -153,8 +153,11 @@
case kFont:
// TODO: should not we take typefaces in account?
- return fFontStyle == other.fFontStyle && fFontFamilies == other.fFontFamilies &&
- fFontSize == other.fFontSize && fHeight == other.fHeight;
+ return fFontStyle == other.fFontStyle &&
+ fLocale == other.fLocale &&
+ fFontFamilies == other.fFontFamilies &&
+ fFontSize == other.fFontSize &&
+ fHeight == other.fHeight;
default:
SkASSERT(false);
return false;
diff --git a/samplecode/SampleParagraph.cpp b/samplecode/SampleParagraph.cpp
index a146872..5dee999 100644
--- a/samplecode/SampleParagraph.cpp
+++ b/samplecode/SampleParagraph.cpp
@@ -2189,7 +2189,7 @@
void onDrawContent(SkCanvas* canvas) override {
- const char* text = "去了 qw 其 er 他的 事实 证明自己";
+ auto text = u"\U0001f600\U0001f601\U0001f602\U0001f923\U0001f603\U0001f604\U0001f605\U0001f606\U0001f609\U0001f60a\U0001f60b\U0001f60e\U0001f60d\U0001f618\U0001f970\U0001f617\U0001f619\U0001f61a\u263A\uFE0F\U0001f642\U0001f917";
canvas->drawColor(SK_ColorWHITE);
auto fontCollection = sk_make_sp<FontCollection>();
@@ -2206,29 +2206,26 @@
builder.addText(text);
auto paragraph = builder.Build();
paragraph->layout(width());
- paragraph->paint(canvas, 0, 0);
- for (size_t i = 0; i < strlen(text) - 1; ++i) {
+ SkColor colors[] = {
+ SK_ColorRED,
+ SK_ColorGREEN,
+ SK_ColorBLUE,
+ SK_ColorMAGENTA,
+ SK_ColorYELLOW
+ };
+ SkPaint paint;
+ for (size_t i = 0; i < 42; ++i) {
auto result = paragraph->getRectsForRange(i, i + 1, RectHeightStyle::kTight, RectWidthStyle::kTight);
- SkDebugf("[%d:%d):\n", i, i+1);
- for (auto& r : result) {
- SkDebugf("rect: [%f:%f] %s\n",
- r.rect.fLeft, r.rect.fRight, r.direction == TextDirection::kRtl ? "rtl" : "ltr");
-
- auto pos = paragraph->getGlyphPositionAtCoordinate(
- r.rect.fLeft + r.rect.width() / 2, r.rect.fTop + r.rect.height());
- SkDebugf("pos: %d(%s)\n",
- pos.position, pos.affinity == Affinity::kUpstream ? "up" : "down");
-
- auto word = paragraph->getWordBoundary(pos.position);
- SkDebugf("word: [%d:%d)\n", word.start, word.end);
-
- if (i < word.start || i >= word.end) {
- SkDebugf("Wrong!\n");
- }
+ if (result.empty()) {
+ continue;
}
+ auto rect = result[0].rect;
+ paint.setColor(colors[i % 5]);
+ canvas->drawRect(rect, paint);
}
+ paragraph->paint(canvas, 0, 0);
}
private:
@@ -2241,7 +2238,6 @@
void onDrawContent(SkCanvas* canvas) override {
- auto text = u"A\u05D0";
canvas->drawColor(SK_ColorWHITE);
auto fontCollection = sk_make_sp<FontCollection>();
@@ -2253,23 +2249,97 @@
TextStyle text_style;
text_style.setColor(SK_ColorBLACK);
text_style.setFontFamilies({SkString("Roboto")});
- text_style.setFontSize(60);
+ text_style.setFontSize(40);
builder.pushStyle(text_style);
- builder.addText(text);
+ auto s = u"েن েূথ";
+ builder.addText(s);
auto paragraph = builder.Build();
paragraph->layout(width());
paragraph->paint(canvas, 0, 0);
-
- for (auto i = 0; i < 3; ++i) {
- auto word = paragraph->getWordBoundary(i);
- SkDebugf("word @%d: [%d:%d)\n", i, word.start, word.end);
- }
}
private:
typedef Sample INHERITED;
};
+class ParagraphView32 : public ParagraphView_Base {
+protected:
+ SkString name() override { return SkString("Paragraph32"); }
+
+ void onDrawContent(SkCanvas* canvas) override {
+
+ canvas->drawColor(SK_ColorWHITE);
+
+ auto fontCollection = sk_make_sp<FontCollection>();
+ fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
+ fontCollection->enableFontFallback();
+
+ ParagraphStyle paragraph_style;
+ ParagraphBuilderImpl builder(paragraph_style, fontCollection);
+ TextStyle text_style;
+ text_style.setColor(SK_ColorBLACK);
+ text_style.setFontFamilies({SkString("Roboto")});
+ text_style.setFontSize(40);
+ text_style.setLocale(SkString("ko"));
+ builder.pushStyle(text_style);
+ builder.addText(u"\u904d ko ");
+ text_style.setLocale(SkString("zh_Hant"));
+ builder.pushStyle(text_style);
+ builder.addText(u"\u904d zh-Hant ");
+ text_style.setLocale(SkString("zh_Hans"));
+ builder.pushStyle(text_style);
+ builder.addText(u"\u904d zh-Hans ");
+ text_style.setLocale(SkString("zh_HK"));
+ builder.pushStyle(text_style);
+ builder.addText(u"\u904d zh-HK ");
+ auto paragraph = builder.Build();
+ paragraph->layout(width());
+ paragraph->paint(canvas, 0, 0);
+ }
+
+private:
+ typedef Sample INHERITED;
+};
+
+class ParagraphView33 : public ParagraphView_Base {
+protected:
+ SkString name() override { return SkString("Paragraph33"); }
+
+ void onDrawContent(SkCanvas* canvas) override {
+
+ canvas->drawColor(SK_ColorWHITE);
+
+ auto fontCollection = sk_make_sp<FontCollection>();
+ fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
+ fontCollection->enableFontFallback();
+
+ ParagraphStyle paragraph_style;
+ paragraph_style.setTextAlign(TextAlign::kJustify);
+ ParagraphBuilderImpl builder(paragraph_style, fontCollection);
+ TextStyle text_style;
+ text_style.setColor(SK_ColorBLACK);
+ text_style.setFontFamilies({SkString("Roboto"), SkString("Noto Color Emoji")});
+ text_style.setFontSize(36);
+ builder.pushStyle(text_style);
+ builder.addText(u"AAAAA \U0001f600 BBBBB CCCCC DDDDD EEEEE");
+ auto paragraph = builder.Build();
+ paragraph->layout(109);
+ paragraph->paint(canvas, 0, 0);
+ }
+
+private:
+ typedef Sample INHERITED;
+};
+/*
+void main() {
+ runApp(new Text('AAAAA \u{1f600} BBBBB CCCCC DDDDD EEEEE',
+ style: TextStyle(fontSize: 36.0),
+ textDirection: TextDirection.ltr,
+ textAlign: TextAlign.justify,
+ ));
+}
+}
+ */
//
//////////////////////////////////////////////////////////////////////////////
@@ -2303,3 +2373,5 @@
DEF_SAMPLE(return new ParagraphView29();)
DEF_SAMPLE(return new ParagraphView30();)
DEF_SAMPLE(return new ParagraphView31();)
+DEF_SAMPLE(return new ParagraphView32();)
+DEF_SAMPLE(return new ParagraphView33();)