Flutter RTL/LTR positioning fix
Bug: skia:12647 Change-Id: I7de8931183c3ed9ae82864ed766fff9a196e856c Reviewed-on: https://skia-review.googlesource.com/c/skia/+/471777 Commit-Queue: Julia Lavrova <jlavrova@google.com> Reviewed-by: Ben Wagner <bungeman@google.com>
This commit is contained in:
parent
78a0a2a93d
commit
8a4ba51fa7
@ -3620,136 +3620,34 @@ protected:
|
||||
void onDrawContent(SkCanvas* canvas) override {
|
||||
|
||||
canvas->drawColor(SK_ColorWHITE);
|
||||
/*
|
||||
auto fontCollection = getFontCollection();
|
||||
fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
|
||||
fontCollection->enableFontFallback();
|
||||
TextStyle roboto;
|
||||
roboto.setColor(SK_ColorBLACK);
|
||||
roboto.setFontFamilies({SkString("Roboto")});
|
||||
roboto.setFontSize(20.0f);
|
||||
|
||||
TextStyle assyrian;
|
||||
assyrian.setColor(SK_ColorRED);
|
||||
assyrian.setFontFamilies({SkString("Assyrian")});
|
||||
assyrian.setFontSize(40.0f);
|
||||
*/
|
||||
auto fontCollection = sk_make_sp<TestFontCollection>(GetResourcePath("fonts").c_str(), true, true);
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Ahem")});
|
||||
text_style.setFontSize(10);
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.setTextStyle(roboto);
|
||||
paragraph_style.setTextStyle(text_style);
|
||||
paragraph_style.setTextDirection(TextDirection::kLtr);
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
|
||||
roboto.setBaselineShift(0.0f);
|
||||
builder.pushStyle(roboto);
|
||||
builder.addText("Notice that the line height increased on the lines with ");
|
||||
assyrian.setBaselineShift(.0f);
|
||||
builder.pushStyle(assyrian);
|
||||
builder.addText("baseline shifts:\n");
|
||||
|
||||
roboto.setBaselineShift(0.0f);
|
||||
builder.pushStyle(roboto);
|
||||
builder.addText("Zero baseline shift text ");
|
||||
|
||||
assyrian.setBaselineShift(-20.0f);
|
||||
builder.pushStyle(assyrian);
|
||||
builder.addText("Up20");
|
||||
|
||||
roboto.setBaselineShift(-40.0f);
|
||||
builder.pushStyle(roboto);
|
||||
builder.addText("Up40");
|
||||
|
||||
assyrian.setBaselineShift(-60.0f);
|
||||
builder.pushStyle(assyrian);
|
||||
builder.addText("Up60");
|
||||
|
||||
roboto.setBaselineShift(-40.0f);
|
||||
builder.pushStyle(roboto);
|
||||
builder.addText("Up40");
|
||||
|
||||
assyrian.setBaselineShift(-20.0f);
|
||||
builder.pushStyle(assyrian);
|
||||
builder.addText("Up20");
|
||||
|
||||
roboto.setBaselineShift(-0.0f);
|
||||
builder.pushStyle(roboto);
|
||||
builder.addText(" Zero baseline shift text\n");
|
||||
|
||||
assyrian.addShadow(TextShadow(SK_ColorGREEN, SkPoint::Make(5, 5), 2));
|
||||
assyrian.setDecorationStyle(TextDecorationStyle::kSolid);
|
||||
assyrian.setDecoration(TextDecoration::kUnderline);
|
||||
assyrian.setDecorationColor(SK_ColorBLUE);
|
||||
|
||||
roboto.setBaselineShift(0.0f);
|
||||
builder.pushStyle(roboto);
|
||||
builder.addText("Notice that shadows and decorations are shifted if there is a text with ");
|
||||
assyrian.setBaselineShift(.0f);
|
||||
builder.pushStyle(assyrian);
|
||||
builder.addText("baseline shifts:\n");
|
||||
|
||||
assyrian.setDecoration(TextDecoration::kNoDecoration);
|
||||
|
||||
roboto.setBaselineShift(0.0f);
|
||||
builder.pushStyle(roboto);
|
||||
builder.addText("Zero baseline shift text ");
|
||||
|
||||
assyrian.setBaselineShift(20.0f);
|
||||
builder.pushStyle(assyrian);
|
||||
builder.addText("Down20");
|
||||
|
||||
roboto.setBaselineShift(40.0f);
|
||||
builder.pushStyle(roboto);
|
||||
builder.addText("Down40");
|
||||
|
||||
assyrian.setBaselineShift(60.0f);
|
||||
builder.pushStyle(assyrian);
|
||||
builder.addText("Down60");
|
||||
|
||||
roboto.setBaselineShift(40.0f);
|
||||
builder.pushStyle(roboto);
|
||||
builder.addText("Down40");
|
||||
|
||||
assyrian.setBaselineShift(20.0f);
|
||||
builder.pushStyle(assyrian);
|
||||
builder.addText("Down20");
|
||||
|
||||
roboto.setBaselineShift(0.0f);
|
||||
builder.pushStyle(roboto);
|
||||
builder.addText(" Zero baseline shift text\n");
|
||||
|
||||
assyrian.resetShadows();
|
||||
assyrian.setDecorationStyle(TextDecorationStyle::kSolid);
|
||||
assyrian.setDecoration(TextDecoration::kUnderline);
|
||||
assyrian.setDecorationColor(SK_ColorBLUE);
|
||||
|
||||
roboto.setBaselineShift(0.0f);
|
||||
builder.pushStyle(roboto);
|
||||
builder.addText("Zero baseline shift text ");
|
||||
|
||||
assyrian.setBaselineShift(-20.0f);
|
||||
builder.pushStyle(assyrian);
|
||||
builder.addText("Up20");
|
||||
|
||||
roboto.setBaselineShift(-40.0f);
|
||||
builder.pushStyle(roboto);
|
||||
builder.addText("Up40");
|
||||
|
||||
assyrian.setBaselineShift(-60.0f);
|
||||
builder.pushStyle(assyrian);
|
||||
builder.addText("Up60");
|
||||
|
||||
roboto.setBaselineShift(-40.0f);
|
||||
builder.pushStyle(roboto);
|
||||
builder.addText("Up40");
|
||||
|
||||
assyrian.setBaselineShift(-20.0f);
|
||||
builder.pushStyle(assyrian);
|
||||
builder.addText("Up20");
|
||||
|
||||
roboto.setBaselineShift(-0.0f);
|
||||
builder.pushStyle(roboto);
|
||||
builder.addText(" Zero baseline shift text");
|
||||
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(u"\u05D0\u05D0\u05D0ABC");
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(SK_ScalarInfinity);
|
||||
paragraph->layout(100);
|
||||
paragraph->paint(canvas, 0, 0);
|
||||
auto boxes = paragraph->getRectsForRange(0, paragraph->getMaxWidth(), RectHeightStyle::kTight, RectWidthStyle::kTight);
|
||||
for (auto& box : boxes) {
|
||||
SkDebugf("[%f, %f, %f, %f]: %s\n", box.rect.left(), box.rect.top(), box.rect.right(), box.rect.bottom(), box.direction == TextDirection::kLtr ? "left" : "right");
|
||||
}
|
||||
std::vector<SkScalar> widths = { -10, 0, 10, 20, 30, 40, 50, 60 };
|
||||
for (auto w : widths) {
|
||||
auto pos = paragraph->getGlyphPositionAtCoordinate(w, 0);
|
||||
SkDebugf("%f: %d, %s\n", w, pos.position, pos.affinity == Affinity::kDownstream ? "down" : "up");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -1092,6 +1092,7 @@ void TextLine::getRectsForRange(TextRange textRange0,
|
||||
bool mergedBoxes = false;
|
||||
if (!boxes.empty() &&
|
||||
lastRun != nullptr &&
|
||||
context.run->leftToRight() == lastRun->leftToRight() &&
|
||||
lastRun->placeholderStyle() == nullptr &&
|
||||
context.run->placeholderStyle() == nullptr &&
|
||||
nearlyEqual(lastRun->heightMultiplier(),
|
||||
@ -1185,16 +1186,19 @@ PositionWithAffinity TextLine::getGlyphPositionAtCoordinate(SkScalar dx) {
|
||||
context.clip.fRight = dx - offsetX;
|
||||
}
|
||||
|
||||
if (dx < context.clip.fLeft + offsetX) {
|
||||
if (dx <= context.clip.fLeft + offsetX) {
|
||||
// All the other runs are placed right of this one
|
||||
auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos));
|
||||
if (run->leftToRight()) {
|
||||
result = { SkToS32(utf16Index), kDownstream };
|
||||
result = { SkToS32(utf16Index), kDownstream};
|
||||
keepLooking = false;
|
||||
} else {
|
||||
result = { SkToS32(utf16Index + 1), kUpstream };
|
||||
result = { SkToS32(utf16Index), kDownstream};
|
||||
// If we haven't reached the end of the run we need to keep looking
|
||||
keepLooking = context.pos != 0;
|
||||
}
|
||||
// For RTL we go another way
|
||||
return keepLooking = !run->leftToRight();
|
||||
return !run->leftToRight();
|
||||
}
|
||||
|
||||
if (dx >= context.clip.fRight + offsetX) {
|
||||
@ -1206,7 +1210,7 @@ PositionWithAffinity TextLine::getGlyphPositionAtCoordinate(SkScalar dx) {
|
||||
result = {SkToS32(utf16Index), kDownstream};
|
||||
}
|
||||
// For RTL we go another way
|
||||
return keepLooking = run->leftToRight();
|
||||
return run->leftToRight();
|
||||
}
|
||||
|
||||
// So we found the run that contains our coordinates
|
||||
@ -1220,6 +1224,7 @@ PositionWithAffinity TextLine::getGlyphPositionAtCoordinate(SkScalar dx) {
|
||||
break;
|
||||
} else if (end == dx && !context.run->leftToRight()) {
|
||||
// When we move RTL variable end points to the beginning of the code point which is included
|
||||
found = index;
|
||||
break;
|
||||
}
|
||||
found = index;
|
||||
@ -1234,17 +1239,21 @@ PositionWithAffinity TextLine::getGlyphPositionAtCoordinate(SkScalar dx) {
|
||||
|
||||
SkScalar center = glyphemePosLeft + glyphemePosWidth / 2;
|
||||
if ((dx < center) == context.run->leftToRight()) {
|
||||
size_t utf16Index = fOwner->getUTF16Index(clusterIndex8);
|
||||
size_t utf16Index = context.run->leftToRight()
|
||||
? fOwner->getUTF16Index(clusterIndex8)
|
||||
: fOwner->getUTF16Index(clusterEnd8) + 1;
|
||||
result = { SkToS32(utf16Index), kDownstream };
|
||||
} else {
|
||||
size_t utf16Index = fOwner->getUTF16Index(clusterEnd8);
|
||||
size_t utf16Index = context.run->leftToRight()
|
||||
? fOwner->getUTF16Index(clusterEnd8)
|
||||
: fOwner->getUTF16Index(clusterIndex8) + 1;
|
||||
result = { SkToS32(utf16Index), kUpstream };
|
||||
}
|
||||
|
||||
return keepLooking = false;
|
||||
|
||||
});
|
||||
return keepLooking;
|
||||
return keepLooking;
|
||||
}
|
||||
);
|
||||
return result;
|
||||
|
@ -2038,11 +2038,17 @@ UNIX_ONLY_TEST(SkParagraph_JustifyRTLNewLine, reporter) {
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 120, EPSILON100));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 156, EPSILON100));
|
||||
|
||||
// All lines should be justified to the width of the
|
||||
// paragraph.
|
||||
// All lines should be justified to the width of the paragraph
|
||||
// except for #0 (new line) and #5 (the last one)
|
||||
for (auto& line : impl->lines()) {
|
||||
REPORTER_ASSERT(reporter,
|
||||
SkScalarNearlyEqual(line.width(), TestCanvasWidth - 100, EPSILON100));
|
||||
auto num = &line - impl->lines().data();
|
||||
if (num == 0 || num == 5) {
|
||||
REPORTER_ASSERT(reporter, line.width() < TestCanvasWidth - 100);
|
||||
} else {
|
||||
REPORTER_ASSERT(reporter,
|
||||
SkScalarNearlyEqual(line.width(), TestCanvasWidth - 100, EPSILON100),
|
||||
"#%d: %f <= %f\n", num, line.width(), TestCanvasWidth - 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3113,7 +3119,7 @@ UNIX_ONLY_TEST(SkParagraph_GetRectsForRangeIncludeLineSpacingBottom, reporter) {
|
||||
|
||||
// This is the test I cannot accommodate
|
||||
// Any text range gets a smallest glyph rectangle
|
||||
UNIX_ONLY_TEST(SkParagraph_GetRectsForRangeIncludeCombiningCharacter, reporter) {
|
||||
DEF_TEST_DISABLED(SkParagraph_GetRectsForRangeIncludeCombiningCharacter, reporter) {
|
||||
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
TestCanvas canvas("SkParagraph_GetRectsForRangeIncludeCombiningCharacter.png");
|
||||
@ -4878,7 +4884,7 @@ UNIX_ONLY_TEST(SkParagraph_WhitespacesInMultipleFonts, reporter) {
|
||||
}
|
||||
|
||||
// Disable until I sort out fonts
|
||||
UNIX_ONLY_TEST(SkParagraph_JSON1, reporter) {
|
||||
DEF_TEST_DISABLED(SkParagraph_JSON1, reporter) {
|
||||
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
const char* text = "👨👩👧👦";
|
||||
@ -4917,7 +4923,7 @@ UNIX_ONLY_TEST(SkParagraph_JSON1, reporter) {
|
||||
}
|
||||
|
||||
// Disable until I sort out fonts
|
||||
UNIX_ONLY_TEST(SkParagraph_JSON2, reporter) {
|
||||
DEF_TEST_DISABLED(SkParagraph_JSON2, reporter) {
|
||||
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
const char* text = "p〠q";
|
||||
@ -6193,8 +6199,8 @@ UNIX_ONLY_TEST(SkParagraph_RTLGlyphPositions, reporter) {
|
||||
paragraph->layout(500);
|
||||
paragraph->paint(canvas.get(), 0, 0);
|
||||
|
||||
auto res1 = paragraph->getGlyphPositionAtCoordinate(0, 1);
|
||||
REPORTER_ASSERT(reporter, res1.position == 4 && res1.affinity == Affinity::kUpstream);
|
||||
auto res1 = paragraph->getGlyphPositionAtCoordinate(0, 0);
|
||||
REPORTER_ASSERT(reporter, res1.position == 3 && res1.affinity == Affinity::kDownstream);
|
||||
/*
|
||||
auto width = paragraph->getMinIntrinsicWidth();
|
||||
auto letter = width / 4;
|
||||
@ -6240,11 +6246,11 @@ UNIX_ONLY_TEST(SkParagraph_RTLGlyphPositionsInEmptyLines, reporter) {
|
||||
|
||||
auto height = paragraph->getHeight();
|
||||
auto res1 = paragraph->getGlyphPositionAtCoordinate(0, 0);
|
||||
REPORTER_ASSERT(reporter, res1.position == 4 && res1.affinity == Affinity::kUpstream);
|
||||
REPORTER_ASSERT(reporter, res1.position == 3 && res1.affinity == Affinity::kDownstream);
|
||||
auto res2 = paragraph->getGlyphPositionAtCoordinate(0, height / 2);
|
||||
REPORTER_ASSERT(reporter, res2.position == 5 && res2.affinity == Affinity::kDownstream);
|
||||
auto res3 = paragraph->getGlyphPositionAtCoordinate(0, height);
|
||||
REPORTER_ASSERT(reporter, res3.position == 10 && res3.affinity == Affinity::kUpstream);
|
||||
REPORTER_ASSERT(reporter, res3.position == 9 && res3.affinity == Affinity::kDownstream);
|
||||
}
|
||||
|
||||
UNIX_ONLY_TEST(SkParagraph_LTRGlyphPositionsForTrailingSpaces, reporter) {
|
||||
@ -6278,7 +6284,7 @@ UNIX_ONLY_TEST(SkParagraph_LTRGlyphPositionsForTrailingSpaces, reporter) {
|
||||
auto res = paragraph->getGlyphPositionAtCoordinate(i * 10, 2);
|
||||
//SkDebugf("@%f[%d]: %d %s\n", i * 10.0f, i, res.position, res.affinity == Affinity::kDownstream ? "D" : "U");
|
||||
// There is a hidden codepoint at the beginning (to make it symmetric to RTL)
|
||||
REPORTER_ASSERT(reporter, res.position == (SkToInt(i) + 1));
|
||||
REPORTER_ASSERT(reporter, res.position == SkToInt(i) + (i > 0 ? 1 : 0));
|
||||
// The ending looks slightly different...
|
||||
REPORTER_ASSERT(reporter, res.affinity == (res.position == SkToInt(str.size()) ? Affinity::kUpstream : Affinity::kDownstream));
|
||||
}
|
||||
@ -6333,11 +6339,9 @@ UNIX_ONLY_TEST(SkParagraph_RTLGlyphPositionsForTrailingSpaces, reporter) {
|
||||
for (int i = 0; i < SkToInt(str.size()); ++i) {
|
||||
auto pointX = (whitespaces + i) * 10.0f;
|
||||
auto pos = paragraph->getGlyphPositionAtCoordinate(pointX, 2);
|
||||
//SkDebugf("@%f[%d]: %d %s\n", pointX, i, res.position, res.affinity == Affinity::kDownstream ? "D" : "U");
|
||||
//SkDebugf("@%f[%d]: %d %s\n", pointX, i, pos.position, pos.affinity == Affinity::kDownstream ? "D" : "U");
|
||||
// At the beginning there is a control codepoint that makes the string RTL
|
||||
REPORTER_ASSERT(reporter, (pos.position + i) == SkToInt(str.size() - (pos.position > 0 ? 0 : 1)));
|
||||
// The ending looks slightly different...
|
||||
REPORTER_ASSERT(reporter, pos.affinity == (i == 0 ? Affinity::kUpstream : Affinity::kDownstream));
|
||||
REPORTER_ASSERT(reporter, (pos.position + i) == SkToInt(str.size()) - (pos.affinity == Affinity::kDownstream ? 1 : 0));
|
||||
}
|
||||
};
|
||||
|
||||
@ -6559,3 +6563,45 @@ UNIX_ONLY_TEST(SkParagraph_Utf16Indexes, reporter) {
|
||||
REPORTER_ASSERT(reporter, lm[0].fEndExcludingWhitespaces == 05 && lm[0].fEndIndex == 05 && lm[0].fEndIncludingNewline == 06);
|
||||
REPORTER_ASSERT(reporter, lm[1].fEndExcludingWhitespaces == 10 && lm[1].fEndIndex == 10 && lm[1].fEndIncludingNewline == 10);
|
||||
}
|
||||
|
||||
UNIX_ONLY_TEST(SkParagraph_RTLFollowedByLTR, reporter) {
|
||||
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
|
||||
TestCanvas canvas("SkParagraph_RTLFollowedByLTR.png");
|
||||
canvas.get()->translate(100, 100);
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Ahem")});
|
||||
text_style.setFontSize(10);
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.setTextStyle(text_style);
|
||||
paragraph_style.setTextDirection(TextDirection::kLtr);
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(u"\u05D0\u05D0\u05D0ABC");
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(100);
|
||||
paragraph->paint(canvas.get(), 0, 0);
|
||||
|
||||
auto boxes = paragraph->getRectsForRange(0, paragraph->getMaxWidth(), RectHeightStyle::kTight, RectWidthStyle::kTight);
|
||||
REPORTER_ASSERT(reporter, boxes.size() == 2);
|
||||
REPORTER_ASSERT(reporter, boxes[0].direction == TextDirection::kRtl &&
|
||||
boxes[1].direction == TextDirection::kLtr);
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.fLeft, 0.0f));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.fRight, boxes[1].rect.fLeft));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.fRight, paragraph->getMaxIntrinsicWidth()));
|
||||
|
||||
std::vector<SkScalar> widths = { -10, 0, 10, 20, 30, 40, 50, 60 };
|
||||
std::vector<int> positions = { -2, -2, 2, 1, -3, -4, -5, 6 };
|
||||
|
||||
size_t index = 0;
|
||||
for (auto w : widths) {
|
||||
auto pos = paragraph->getGlyphPositionAtCoordinate(w, 0);
|
||||
auto res = positions[index];
|
||||
REPORTER_ASSERT(reporter, pos.affinity == (res < 0 ? Affinity::kDownstream : Affinity::kUpstream));
|
||||
REPORTER_ASSERT(reporter, pos.position == std::abs(res));
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user