Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit c88a3bc

Browse files
RusinoSkia Commit-Bot
authored andcommitted
Bidi segmentation BEFORE anything else
Change-Id: I94637e663bc1ffc7d9d6e1c0fb0b28509af45f60 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/266200 Commit-Queue: Julia Lavrova <jlavrova@google.com> Reviewed-by: Ben Wagner <bungeman@google.com>
1 parent d081dce commit c88a3bc

6 files changed

Lines changed: 205 additions & 51 deletions

File tree

modules/skparagraph/src/OneLineShaper.cpp

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,8 @@ void OneLineShaper::sortOutGlyphs(std::function<void(GlyphRange)>&& sortOutUnres
335335
}
336336
}
337337

338-
void OneLineShaper::iterateThroughFontStyles(SkSpan<Block> styleSpan,
338+
void OneLineShaper::iterateThroughFontStyles(TextRange textRange,
339+
SkSpan<Block> styleSpan,
339340
const ShapeSingleFontVisitor& visitor) {
340341
Block combinedBlock;
341342
SkTArray<SkShaper::Feature> features;
@@ -357,20 +358,23 @@ void OneLineShaper::iterateThroughFontStyles(SkSpan<Block> styleSpan,
357358
};
358359

359360
for (auto& block : styleSpan) {
360-
SkASSERT(combinedBlock.fRange.width() == 0 ||
361-
combinedBlock.fRange.end == block.fRange.start);
361+
BlockRange blockRange(SkTMax(block.fRange.start, textRange.start), SkTMin(block.fRange.end, textRange.end));
362+
if (blockRange.empty()) {
363+
continue;
364+
}
365+
SkASSERT(combinedBlock.fRange.width() == 0 || combinedBlock.fRange.end == block.fRange.start);
362366

363367
if (!combinedBlock.fRange.empty()) {
364368
if (block.fStyle.matchOneAttribute(StyleType::kFont, combinedBlock.fStyle)) {
365-
combinedBlock.add(block.fRange);
369+
combinedBlock.add(blockRange);
366370
addFeatures(block);
367371
continue;
368372
}
369373
// Resolve all characters in the block for this style
370374
visitor(combinedBlock, features);
371375
}
372376

373-
combinedBlock.fRange = block.fRange;
377+
combinedBlock.fRange = blockRange;
374378
combinedBlock.fStyle = block.fStyle;
375379
features.reset();
376380
addFeatures(block);
@@ -412,17 +416,38 @@ void OneLineShaper::matchResolvedFonts(const TextStyle& textStyle,
412416

413417
bool OneLineShaper::iterateThroughShapingRegions(const ShapeVisitor& shape) {
414418

419+
SkTArray<BidiRegion> bidiRegions;
420+
if (!fParagraph->calculateBidiRegions(&bidiRegions)) {
421+
return false;
422+
}
423+
424+
size_t bidiIndex = 0;
425+
415426
SkScalar advanceX = 0;
416427
for (auto& placeholder : fParagraph->fPlaceholders) {
417-
// Shape the text
428+
418429
if (placeholder.fTextBefore.width() > 0) {
419-
// Set up the iterators
420-
SkSpan<const char> textSpan = fParagraph->text(placeholder.fTextBefore);
421-
SkSpan<Block> styleSpan(fParagraph->fTextStyles.begin() + placeholder.fBlocksBefore.start,
422-
placeholder.fBlocksBefore.width());
430+
// Shape the text by bidi regions
431+
while (bidiIndex < bidiRegions.size()) {
432+
BidiRegion& bidiRegion = bidiRegions[bidiIndex];
433+
auto start = SkTMax(bidiRegion.text.start, placeholder.fTextBefore.start);
434+
auto end = SkTMin(bidiRegion.text.end, placeholder.fTextBefore.end);
435+
436+
// Set up the iterators (the style iterator points to a bigger region that it could
437+
TextRange textRange(start, end);
438+
auto blockRange = fParagraph->findAllBlocks(textRange);
439+
SkSpan<Block> styleSpan(fParagraph->blocks(blockRange));
440+
441+
// Shape the text between placeholders
442+
if (!shape(textRange, styleSpan, advanceX, start, bidiRegion.direction)) {
443+
return false;
444+
}
423445

424-
if (!shape(textSpan, styleSpan, advanceX, placeholder.fTextBefore.start)) {
425-
return false;
446+
if (end == bidiRegion.text.end) {
447+
++bidiIndex;
448+
} else /*if (end == placeholder.fTextBefore.end)*/ {
449+
break;
450+
}
426451
}
427452
}
428453

@@ -465,12 +490,11 @@ bool OneLineShaper::shape() {
465490

466491
// The text can be broken into many shaping sequences
467492
// (by place holders, possibly, by hard line breaks or tabs, too)
468-
uint8_t textDirection = fParagraph->fParagraphStyle.getTextDirection() == TextDirection::kLtr ? 2 : 1;
469493
auto limitlessWidth = std::numeric_limits<SkScalar>::max();
470494

471495
auto result = iterateThroughShapingRegions(
472-
[this, textDirection, limitlessWidth]
473-
(SkSpan<const char> textSpan, SkSpan<Block> styleSpan, SkScalar& advanceX, TextIndex textStart) {
496+
[this, limitlessWidth]
497+
(TextRange textRange, SkSpan<Block> styleSpan, SkScalar& advanceX, TextIndex textStart, uint8_t textDirection) {
474498

475499
// Set up the shaper and shape the next
476500
auto shaper = SkShaper::MakeShapeDontWrapOrReorder();
@@ -479,7 +503,7 @@ bool OneLineShaper::shape() {
479503
return false;
480504
}
481505

482-
iterateThroughFontStyles(styleSpan,
506+
iterateThroughFontStyles(textRange, styleSpan,
483507
[this, &shaper, textDirection, limitlessWidth, &advanceX]
484508
(Block block, SkTArray<SkShaper::Feature> features) {
485509
auto blockSpan = SkSpan<Block>(&block, 1);

modules/skparagraph/src/OneLineShaper.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ class OneLineShaper : public SkShaper::RunHandler {
5454
};
5555

5656
using ShapeVisitor =
57-
std::function<SkScalar(SkSpan<const char>, SkSpan<Block>, SkScalar&, TextIndex)>;
57+
std::function<SkScalar(TextRange textRange, SkSpan<Block>, SkScalar&, TextIndex, uint8_t)>;
5858
bool iterateThroughShapingRegions(const ShapeVisitor& shape);
5959

6060
using ShapeSingleFontVisitor = std::function<void(Block, SkTArray<SkShaper::Feature>)>;
61-
void iterateThroughFontStyles(SkSpan<Block> styleSpan, const ShapeSingleFontVisitor& visitor);
61+
void iterateThroughFontStyles(TextRange textRange, SkSpan<Block> styleSpan, const ShapeSingleFontVisitor& visitor);
6262

6363
using TypefaceVisitor = std::function<bool(sk_sp<SkTypeface> typeface)>;
6464
void matchResolvedFonts(const TextStyle& textStyle, const TypefaceVisitor& visitor);

modules/skparagraph/src/ParagraphImpl.cpp

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,19 @@ namespace textlayout {
1919
namespace {
2020

2121
using ICUUText = std::unique_ptr<UText, SkFunctionWrapper<decltype(utext_close), utext_close>>;
22+
using ICUBiDi = std::unique_ptr<UBiDi, SkFunctionWrapper<decltype(ubidi_close), ubidi_close>>;
2223

2324
SkScalar littleRound(SkScalar a) {
2425
// This rounding is done to match Flutter tests. Must be removed..
2526
return SkScalarRoundToScalar(a * 100.0)/100.0;
2627
}
2728

29+
/** Replaces invalid utf-8 sequences with REPLACEMENT CHARACTER U+FFFD. */
30+
static inline SkUnichar utf8_next(const char** ptr, const char* end) {
31+
SkUnichar val = SkUTF::NextUTF8(ptr, end);
32+
return val < 0 ? 0xFFFD : val;
33+
}
34+
2835
}
2936

3037
TextRange operator*(const TextRange& a, const TextRange& b) {
@@ -674,11 +681,14 @@ std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
674681
}
675682
}
676683

684+
auto runInLineWidth = line.measureTextInsideOneRun(textRange, run, runOffset, 0, true, false).clip.width();
677685
runOffset += *width;
678686

679687
// Found a run that intersects with the text
680688
auto context = line.measureTextInsideOneRun(intersect, run, runOffset, 0, true, true);
681-
*width += context.clip.width();
689+
690+
//*width += context.clip.width();
691+
*width = runInLineWidth;
682692

683693
SkRect clip = context.clip;
684694
SkRect trailingSpaces = SkRect::MakeEmpty();
@@ -1132,5 +1142,78 @@ void ParagraphImpl::updateBackgroundPaint(size_t from, size_t to, SkPaint paint)
11321142
}
11331143
}
11341144

1145+
bool ParagraphImpl::calculateBidiRegions(SkTArray<BidiRegion>* regions) {
1146+
1147+
regions->reset();
1148+
1149+
// ubidi only accepts utf16 (though internally it basically works on utf32 chars).
1150+
// We want an ubidi_setPara(UBiDi*, UText*, UBiDiLevel, UBiDiLevel*, UErrorCode*);
1151+
size_t utf8Bytes = fText.size();
1152+
const char* utf8 = fText.c_str();
1153+
uint8_t bidiLevel = fParagraphStyle.getTextDirection() == TextDirection::kLtr
1154+
? UBIDI_LTR
1155+
: UBIDI_RTL;
1156+
if (!SkTFitsIn<int32_t>(utf8Bytes)) {
1157+
SkDEBUGF("Bidi error: text too long");
1158+
return false;
1159+
}
1160+
1161+
// Getting the length like this seems to always set U_BUFFER_OVERFLOW_ERROR
1162+
UErrorCode status = U_ZERO_ERROR;
1163+
int32_t utf16Units;
1164+
u_strFromUTF8(nullptr, 0, &utf16Units, utf8, utf8Bytes, &status);
1165+
status = U_ZERO_ERROR;
1166+
std::unique_ptr<UChar[]> utf16(new UChar[utf16Units]);
1167+
u_strFromUTF8(utf16.get(), utf16Units, nullptr, utf8, utf8Bytes, &status);
1168+
if (U_FAILURE(status)) {
1169+
SkDEBUGF("Invalid utf8 input: %s", u_errorName(status));
1170+
return false;
1171+
}
1172+
1173+
ICUBiDi bidi(ubidi_openSized(utf16Units, 0, &status));
1174+
if (U_FAILURE(status)) {
1175+
SkDEBUGF("Bidi error: %s", u_errorName(status));
1176+
return false;
1177+
}
1178+
SkASSERT(bidi);
1179+
1180+
// The required lifetime of utf16 isn't well documented.
1181+
// It appears it isn't used after ubidi_setPara except through ubidi_getText.
1182+
ubidi_setPara(bidi.get(), utf16.get(), utf16Units, bidiLevel, nullptr, &status);
1183+
if (U_FAILURE(status)) {
1184+
SkDEBUGF("Bidi error: %s", u_errorName(status));
1185+
return false;
1186+
}
1187+
1188+
SkTArray<BidiRegion> bidiRegions;
1189+
const char* start8 = utf8;
1190+
const char* end8 = utf8 + utf8Bytes;
1191+
TextRange textRange(0, 0);
1192+
UBiDiLevel currentLevel = 0;
1193+
1194+
int32_t pos16 = 0;
1195+
int32_t end16 = ubidi_getLength(bidi.get());
1196+
while (pos16 < end16) {
1197+
auto level = ubidi_getLevelAt(bidi.get(), pos16);
1198+
if (pos16 == 0) {
1199+
currentLevel = level;
1200+
} else if (level != currentLevel) {
1201+
textRange.end = start8 - utf8;
1202+
regions->emplace_back(textRange.start, textRange.end, currentLevel);
1203+
currentLevel = level;
1204+
textRange = TextRange(textRange.end, textRange.end);
1205+
}
1206+
SkUnichar u = utf8_next(&start8, end8);
1207+
pos16 += SkUTF::ToUTF16(u);
1208+
}
1209+
1210+
textRange.end = start8 - utf8;
1211+
if (!textRange.empty()) {
1212+
regions->emplace_back(textRange.start, textRange.end, currentLevel);
1213+
}
1214+
1215+
return true;
1216+
}
1217+
11351218
} // namespace textlayout
11361219
} // namespace skia

modules/skparagraph/src/ParagraphImpl.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ struct ResolvedFontDescriptor {
4949
TextIndex fTextStart;
5050
};
5151

52+
struct BidiRegion {
53+
BidiRegion(size_t start, size_t end, uint8_t dir)
54+
: text(start, end), direction(dir) { }
55+
TextRange text;
56+
uint8_t direction;
57+
};
58+
5259
class TextBreaker {
5360
public:
5461
TextBreaker() : fInitialized(false), fPos(-1) {}
@@ -218,6 +225,8 @@ class ParagraphImpl final : public Paragraph {
218225

219226
void computeEmptyMetrics();
220227

228+
bool calculateBidiRegions(SkTArray<BidiRegion>* regions);
229+
221230
// Input
222231
SkTArray<StyleBlock<SkScalar>> fLetterSpaceStyles;
223232
SkTArray<StyleBlock<SkScalar>> fWordSpaceStyles;

samplecode/SampleParagraph.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,6 +1927,42 @@ class ParagraphView26 : public ParagraphView_Base {
19271927
typedef Sample INHERITED;
19281928
};
19291929

1930+
class ParagraphView27 : public ParagraphView_Base {
1931+
protected:
1932+
SkString name() override { return SkString("Paragraph27"); }
1933+
1934+
void onDrawContent(SkCanvas* canvas) override {
1935+
canvas->drawColor(SK_ColorWHITE);
1936+
1937+
ParagraphStyle paragraph_style;
1938+
paragraph_style.setTextDirection(TextDirection::kRtl);
1939+
TextStyle text_style;
1940+
text_style.setColor(SK_ColorBLACK);
1941+
text_style.setFontFamilies({SkString("Google Sans")});
1942+
text_style.setFontSize(20);
1943+
{
1944+
ParagraphBuilderImpl builder(paragraph_style, getFontCollection());
1945+
builder.pushStyle(text_style);
1946+
builder.addText("31 December 2021");
1947+
auto paragraph = builder.Build();
1948+
paragraph->layout(600);
1949+
paragraph->paint(canvas, 0, 0);
1950+
}
1951+
canvas->translate(0, 200);
1952+
{
1953+
ParagraphBuilderImpl builder(paragraph_style, getFontCollection());
1954+
builder.pushStyle(text_style);
1955+
builder.addText("December 31 2021");
1956+
auto paragraph = builder.Build();
1957+
paragraph->layout(600);
1958+
paragraph->paint(canvas, 0, 0);
1959+
}
1960+
}
1961+
1962+
private:
1963+
typedef Sample INHERITED;
1964+
};
1965+
19301966
//////////////////////////////////////////////////////////////////////////////
19311967

19321968
DEF_SAMPLE(return new ParagraphView1();)
@@ -1954,3 +1990,4 @@ DEF_SAMPLE(return new ParagraphView23();)
19541990
DEF_SAMPLE(return new ParagraphView24();)
19551991
DEF_SAMPLE(return new ParagraphView25();)
19561992
DEF_SAMPLE(return new ParagraphView26();)
1993+
DEF_SAMPLE(return new ParagraphView27();)

0 commit comments

Comments
 (0)