Attach whitespaces to the neighbor unresolved blocks
Fix the situation when an unresolved {arabic} text is broken into many small runs by resolved english spaces. Bug: skia:10487 Change-Id: I3a739501c0fb7e0fc845e68392e1d214df9302db Reviewed-on: https://skia-review.googlesource.com/c/skia/+/304000 Commit-Queue: Julia Lavrova <jlavrova@google.com> Reviewed-by: Ben Wagner <bungeman@google.com>
This commit is contained in:
parent
a54d380ced
commit
131c5ad6f1
@ -284,50 +284,57 @@ void OneLineShaper::addUnresolvedWithRun(GlyphRange glyphRange) {
|
||||
fUnresolvedBlocks.emplace_back(unresolved);
|
||||
}
|
||||
|
||||
// Glue whitespaces to the next/prev unresolved blocks
|
||||
// (so we don't have chinese text with english whitespaces broken into millions of tiny runs)
|
||||
void OneLineShaper::sortOutGlyphs(std::function<void(GlyphRange)>&& sortOutUnresolvedBLock) {
|
||||
|
||||
auto text = fCurrentRun->fMaster->text();
|
||||
size_t unresolvedGlyphs = 0;
|
||||
|
||||
TextIndex whitespacesStart = EMPTY_INDEX;
|
||||
GlyphRange block = EMPTY_RANGE;
|
||||
for (size_t i = 0; i < fCurrentRun->size(); ++i) {
|
||||
|
||||
const char* cluster = text.begin() + clusterIndex(i);
|
||||
SkUnichar codepoint = nextUtf8Unit(&cluster, text.end());
|
||||
bool isControl8 = isControl(codepoint);
|
||||
bool isWhitespace8 = isWhitespace(codepoint);
|
||||
|
||||
// Inspect the glyph
|
||||
auto glyph = fCurrentRun->fGlyphs[i];
|
||||
if (glyph != 0) {
|
||||
if (glyph == 0 && !isControl8) { // Unresolved glyph and not control codepoint
|
||||
++unresolvedGlyphs;
|
||||
if (block.start == EMPTY_INDEX) {
|
||||
// Start new unresolved block
|
||||
// (all leading whitespaces glued to the resolved part if it's not empty)
|
||||
block.start = whitespacesStart == 0 ? 0 : i;
|
||||
block.end = EMPTY_INDEX;
|
||||
} else {
|
||||
// Keep skipping unresolved block
|
||||
}
|
||||
} else { // Resolved glyph or control codepoint
|
||||
if (block.start == EMPTY_INDEX) {
|
||||
// Keep skipping resolved code points
|
||||
continue;
|
||||
}
|
||||
// This is the end of unresolved block
|
||||
block.end = i;
|
||||
} else {
|
||||
const char* cluster = text.begin() + clusterIndex(i);
|
||||
SkUnichar codepoint = nextUtf8Unit(&cluster, text.end());
|
||||
if (isControl(codepoint)) {
|
||||
// This codepoint does not have to be resolved; let's pretend it's resolved
|
||||
if (block.start == EMPTY_INDEX) {
|
||||
// Keep skipping resolved code points
|
||||
continue;
|
||||
}
|
||||
// This is the end of unresolved block
|
||||
block.end = i;
|
||||
} else {
|
||||
} else if (isWhitespace8) {
|
||||
// Glue whitespaces after to the unresolved block
|
||||
++unresolvedGlyphs;
|
||||
if (block.start == EMPTY_INDEX) {
|
||||
// Start new unresolved block
|
||||
block.start = i;
|
||||
block.end = EMPTY_INDEX;
|
||||
} else {
|
||||
// Keep skipping unresolved block
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
// This is the end of unresolved block (all trailing whitespaces glued to the resolved part)
|
||||
block.end = whitespacesStart == EMPTY_INDEX ? i : whitespacesStart;
|
||||
sortOutUnresolvedBLock(block);
|
||||
block = EMPTY_RANGE;
|
||||
whitespacesStart = EMPTY_INDEX;
|
||||
}
|
||||
}
|
||||
|
||||
// Found an unresolved block
|
||||
sortOutUnresolvedBLock(block);
|
||||
block = EMPTY_RANGE;
|
||||
// Keep updated the start of the latest whitespaces patch
|
||||
if (isWhitespace8) {
|
||||
if (whitespacesStart == EMPTY_INDEX) {
|
||||
whitespacesStart = i;
|
||||
}
|
||||
} else {
|
||||
whitespacesStart = EMPTY_INDEX;
|
||||
}
|
||||
}
|
||||
|
||||
// One last block could have been left
|
||||
|
@ -41,5 +41,9 @@ bool isControl(SkUnichar utf8) {
|
||||
return u_iscntrl(utf8);
|
||||
}
|
||||
|
||||
bool isWhitespace(SkUnichar utf8) {
|
||||
return u_isWhitespace(utf8);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ namespace textlayout {
|
||||
SkString SkStringFromU16String(const std::u16string& utf16text);
|
||||
SkUnichar nextUtf8Unit(const char** ptr, const char* end);
|
||||
bool isControl(SkUnichar utf8);
|
||||
bool isWhitespace(SkUnichar utf8);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1239,7 +1239,7 @@ DEF_TEST(SkParagraph_HeightOverrideParagraph, reporter) {
|
||||
paragraph->layout(550);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 5);
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 4);
|
||||
REPORTER_ASSERT(reporter, impl->styles().size() == 1); // paragraph style does not count
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
|
||||
|
||||
@ -3989,27 +3989,28 @@ DEF_TEST(SkParagraph_FontFallbackParagraph, reporter) {
|
||||
paragraph->layout(TestCanvasWidth);
|
||||
paragraph->paint(canvas.get(), 10.0, 15.0);
|
||||
|
||||
REPORTER_ASSERT(reporter, paragraph->unresolvedGlyphs() == 2); // From the text1
|
||||
REPORTER_ASSERT(reporter, paragraph->unresolvedGlyphs() == 3); // From the text1 ("字典 " - including the last space)
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
|
||||
// Font resolution in Skia produces 6 runs because 2 parts of "Roboto 字典 " have different
|
||||
// script (Minikin merges the first 2 into one because of unresolved) [Apple + Unresolved ]
|
||||
// [Apple + Noto] [Apple + Han]
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 7);
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 6);
|
||||
|
||||
// Font resolution in Skia produces 6 runs because 2 parts of "Roboto 字典 " have different
|
||||
// script (Minikin merges the first 2 into one because of unresolved)
|
||||
// [Apple + Unresolved ] 0, 1
|
||||
// [Apple + Noto] 2, 3
|
||||
// [Apple + Han] 4, 5
|
||||
auto robotoAdvance = impl->runs()[0].advance().fX +
|
||||
impl->runs()[1].advance().fX +
|
||||
impl->runs()[2].advance().fX;
|
||||
impl->runs()[1].advance().fX;
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(robotoAdvance, 64.199f, EPSILON50));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(impl->runs()[3].advance().fX, 139.125f, EPSILON100));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(impl->runs()[4].advance().fX, 27.999f, EPSILON100));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(impl->runs()[5].advance().fX, 62.248f, EPSILON100));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(impl->runs()[6].advance().fX, 27.999f, EPSILON100));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(impl->runs()[2].advance().fX, 139.125f, EPSILON100));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(impl->runs()[3].advance().fX, 27.999f, EPSILON100));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(impl->runs()[4].advance().fX, 62.248f, EPSILON100));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(impl->runs()[5].advance().fX, 27.999f, EPSILON100));
|
||||
|
||||
// When a different font is resolved, then the metrics are different.
|
||||
REPORTER_ASSERT(reporter, impl->runs()[4].correctAscent() != impl->runs()[6].correctAscent());
|
||||
REPORTER_ASSERT(reporter, impl->runs()[4].correctDescent() != impl->runs()[6].correctDescent());
|
||||
REPORTER_ASSERT(reporter, impl->runs()[3].correctAscent() != impl->runs()[5].correctAscent());
|
||||
REPORTER_ASSERT(reporter, impl->runs()[3].correctDescent() != impl->runs()[5].correctDescent());
|
||||
}
|
||||
|
||||
// Checked: NO DIFF
|
||||
@ -5457,3 +5458,63 @@ DEF_TEST(SkParagraph_LineMetricsTextAlign, reporter) {
|
||||
REPORTER_ASSERT(reporter, width[2] == width[0]);
|
||||
REPORTER_ASSERT(reporter, width[3] > width[0]); // delta == 0
|
||||
}
|
||||
|
||||
DEF_TEST(SkParagraph_FontResolutionInRTL, reporter) {
|
||||
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>(true);
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
TestCanvas canvas("SkParagraph_FontResolutionInRTL.png");
|
||||
const char* text = " אאא בּבּבּבּ אאאא בּבּ אאא בּבּבּ אאאאא בּבּבּבּ אאאא בּבּבּבּבּ ";
|
||||
const size_t len = strlen(text);
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.setMaxLines(14);
|
||||
paragraph_style.setTextAlign(TextAlign::kRight);
|
||||
paragraph_style.setTextDirection(TextDirection::kRtl);
|
||||
paragraph_style.turnHintingOff();
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Ahem")});
|
||||
text_style.setFontSize(26);
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(text, len);
|
||||
builder.pop();
|
||||
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(TestCanvasWidth);
|
||||
paragraph->paint(canvas.get(), 0, 0);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
|
||||
}
|
||||
|
||||
DEF_TEST(SkParagraph_FontResolutionInLTR, reporter) {
|
||||
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>(true);
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
TestCanvas canvas("SkParagraph_FontResolutionInLTR.png");
|
||||
auto text = u"abc \u01A2 \u01A2 def";
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.setMaxLines(14);
|
||||
paragraph_style.turnHintingOff();
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Roboto")});
|
||||
text_style.setFontSize(26);
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(text);
|
||||
builder.pop();
|
||||
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(TestCanvasWidth);
|
||||
paragraph->paint(canvas.get(), 0, 0);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 3);
|
||||
REPORTER_ASSERT(reporter, impl->runs()[0].textRange().width() == 4); // "abc "
|
||||
REPORTER_ASSERT(reporter, impl->runs()[1].textRange().width() == 5); // "{unresolved} {unresolved}"
|
||||
REPORTER_ASSERT(reporter, impl->runs()[2].textRange().width() == 4); // " def"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user