Multi line + LTR/RTL

Change-Id: I0859773c00b6a4bd19047d25b75ee7c6727bc470
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/403837
Reviewed-by: Julia Lavrova <jlavrova@google.com>
Commit-Queue: Julia Lavrova <jlavrova@google.com>
This commit is contained in:
Julia Lavrova 2021-05-03 18:18:04 -04:00 committed by Skia Commit-Bot
parent 4882f97829
commit 6d14195309
10 changed files with 224 additions and 121 deletions

View File

@ -25,16 +25,16 @@ public:
kDecor,
kFormat,
};
Block(BlockType type, Range range)
Block(BlockType type, TextRange range)
: fType(type)
, fRange(range) { }
BlockType fType;
Range fRange;
TextRange fRange;
};
class FontBlock : public Block {
public:
FontBlock(const SkString& family, SkScalar size, SkFontStyle style, Range range)
FontBlock(const SkString& family, SkScalar size, SkFontStyle style, TextRange range)
: Block(Block::kFont, range)
, fFontFamily(family)
, fFontSize(size)
@ -48,12 +48,12 @@ public:
class DecorBlock : public Block {
public:
DecorBlock(const SkPaint* foreground, const SkPaint* background, Range range)
DecorBlock(const SkPaint* foreground, const SkPaint* background, TextRange range)
: Block(Block::kDecor, range)
, fForegroundColor(foreground)
, fBackgroundColor(background) { }
DecorBlock(Range range)
DecorBlock(TextRange range)
: DecorBlock(nullptr, nullptr, range) { }
const SkPaint* fForegroundColor;
@ -119,10 +119,10 @@ public:
}
bool isSoftLineBreak(size_t index) {
return this->hasProperty(index, CodeUnitFlags::kSoftLineBreakBefore);
return index != 0 && this->hasProperty(index, CodeUnitFlags::kSoftLineBreakBefore);
}
bool isWhitespaces(Range range) {
bool isWhitespaces(TextRange range) {
if (range.leftToRight()) {
for (auto i = range.fStart; i < range.fEnd; ++i) {
if (!this->hasProperty(i, CodeUnitFlags::kPartOfWhiteSpace)) {
@ -158,8 +158,11 @@ public:
// Simplification (using default font manager, default font family and default everything possible)
static bool drawText(const char* text, SkCanvas* canvas, SkScalar x, SkScalar y);
//static bool drawText(const char* text, SkCanvas* canvas, TextFormatStyle textFormat, const SkString& fontFamily, SkScalar fontSize, SkFontStyle fontStyle, SkScalar x, SkScalar y);
static bool drawText(const char* text, SkCanvas* canvas, SkScalar width);
static bool drawText(const char* text, SkCanvas* canvas, TextFormatStyle textFormat, SkColor foreground, SkColor background, const SkString& fontFamily, SkScalar fontSize, SkFontStyle fontStyle, SkScalar x, SkScalar y);
static bool drawText(const char* text, SkCanvas* canvas,
TextFormatStyle textFormat, SkColor foreground, SkColor background, const SkString& fontFamily, SkScalar fontSize, SkFontStyle fontStyle,
SkSize reqSize, SkScalar x, SkScalar y);
void sortDecorBlocks(SkTArray<DecorBlock, true>& decorBlocks);
@ -167,10 +170,6 @@ public:
void markGlyphs();
// Iterating through the input code units and breaking the runs by units flag (no breaking if units == CodeUnitFlags::kNonExistingFlag)
template<typename Visitor>
void iterateByLogicalOrder(CodeUnitFlags units, Visitor visitor);
// Iterating through the output glyphs and breaking the runs by units flag (no breaking if units == CodeUnitFlags::kNonExistingFlag)
template<typename Visitor>
void iterateByVisualOrder(CodeUnitFlags units, Visitor visitor);

View File

@ -28,22 +28,27 @@ enum class LineBreakType {
kHardLineBreakBefore,
};
enum CodeUnitFlags : uint64_t {
kNoCodeUnitFlag = 0x0,
kPartOfWhiteSpace = 0x1,
kGraphemeStart = 0x2,
kSoftLineBreakBefore = 0x4,
kHardLineBreakBefore = 0x8,
enum CodeUnitFlags : uint32_t {
kNoCodeUnitFlag = 0x000,
kPartOfWhiteSpace = 0x001,
kGraphemeStart = 0x002,
kSoftLineBreakBefore = 0x004,
kHardLineBreakBefore = 0x008,
// This information we get from SkShaper
kGlyphStart = 0x16,
kGlyphClusterStart = 0x32,
kNonExistingFlag = 0x64,
kGlyphStart = 0x010,
kGlyphClusterStart = 0x020,
kNonExistingFlag = 0x040,
};
typedef size_t TextIndex;
typedef size_t GlyphIndex;
const size_t EMPTY_INDEX = std::numeric_limits<size_t>::max();
template <typename T>
class Range {
public:
Range() : fStart(0), fEnd(0) { }
Range(size_t start, size_t end) : fStart(start) , fEnd(end) { }
Range(T start, T end) : fStart(start) , fEnd(end) { }
bool leftToRight() const {
return fEnd >= fStart;
@ -70,15 +75,17 @@ public:
}
void merge(Range tail) {
auto ltr = this->leftToRight();
auto ltr1 = this->leftToRight();
auto ltr2 = tail.leftToRight();
this->normalize();
tail.normalize();
SkASSERT(this->fEnd == tail.fStart || this->fStart == tail.fEnd);
this->fStart = std::min(this->fStart, tail.fStart);
this->fEnd = std::max(this->fEnd, tail.fEnd);
if (!ltr) {
// TODO: Merging 2 different directions
if (!ltr1 || !ltr2) {
std::swap(this->fStart, this->fEnd);
std::swap(tail.fStart, tail.fEnd);
}
}
@ -110,11 +117,12 @@ public:
}
}
size_t fStart;
size_t fEnd;
T fStart;
T fEnd;
};
const size_t EMPTY_INDEX = std::numeric_limits<size_t>::max();
typedef Range<TextIndex> TextRange;
typedef Range<GlyphIndex> GlyphRange;
const Range EMPTY_RANGE = Range(EMPTY_INDEX, EMPTY_INDEX);
} // namespace text

View File

@ -112,6 +112,51 @@ protected:
canvas->translate(width, 0);
}
private:
using INHERITED = Sample;
std::unique_ptr<SkUnicode> fUnicode;
};
class TextSample_LongLTR : public Sample {
protected:
SkString name() override { return SkString("TextSample_LongLTR"); }
void onDrawContent(SkCanvas* canvas) override {
canvas->drawColor(SK_ColorWHITE);
Processor::drawText("A very_very_very_very_very_very_very_very_very_very "
"very_very_very_very_very_very_very_very_very_very very very very very very very "
"very very very very very very very very very very very very very very very very "
"very very very very very very very very very very very very very long text", canvas, this->width());
}
private:
using INHERITED = Sample;
std::unique_ptr<SkUnicode> fUnicode;
};
class TextSample_LongRTL : public Sample {
protected:
SkString name() override { return SkString("TextSample_LongRTL"); }
SkString mirror(const std::string& text) {
std::u16string result;
result += u"\u202E";
for (auto i = text.size(); i > 0; --i) {
result += text[i - 1];
}
for (auto ch : text) {
result += ch;
}
result += u"\u202C";
return fUnicode->convertUtf16ToUtf8(result);
}
void onDrawContent(SkCanvas* canvas) override {
canvas->drawColor(SK_ColorWHITE);
Processor::drawText("LONG MIRRORED TEXT SHOULD SHOW RIGHT TO LEFT (AS NORMAL)", canvas, 0, 0);
}
private:
using INHERITED = Sample;
std::unique_ptr<SkUnicode> fUnicode;
@ -120,3 +165,5 @@ private:
DEF_SAMPLE(return new TextSample_HelloWorld();)
DEF_SAMPLE(return new TextSample_Align_Dir();)
DEF_SAMPLE(return new TextSample_LongLTR();)
DEF_SAMPLE(return new TextSample_LongRTL();)

View File

@ -8,29 +8,29 @@
namespace skia {
namespace text {
Line::Line(Processor* processor, const Stretch& stretch, const Stretch& spaces)
: fTextStart(stretch.fGlyphStart)
, fTextEnd(stretch.fGlyphEnd)
, fWhitespacesEnd (spaces.fGlyphEnd)
, fText(stretch.fTextRange)
, fWhitespaces(spaces.fTextRange)
, fTextWidth(stretch.fWidth)
, fSpacesWidth(spaces.fWidth) {
: fTextStart(stretch.glyphStart())
, fTextEnd(stretch.glyphEnd())
, fWhitespacesEnd (spaces.glyphEnd())
, fText(stretch.textRange())
, fWhitespaces(spaces.textRange())
, fTextWidth(stretch.width())
, fSpacesWidth(spaces.width()) {
SkASSERT(stretch.isEmpty() ||
spaces.isEmpty() ||
(stretch.fGlyphEnd == spaces.fGlyphStart));
(stretch.glyphEnd() == spaces.glyphStart()));
if (!stretch.isEmpty()) {
this->fTextMetrics.merge(stretch.fTextMetrics);
this->fTextMetrics.merge(stretch.textMetrics());
}
if (!spaces.isEmpty()) {
this->fTextMetrics.merge(spaces.fTextMetrics);
this->fTextMetrics.merge(spaces.textMetrics());
}
// This is just chosen to catch the common/fast cases. Feel free to tweak.
constexpr int kPreallocCount = 4;
auto start = stretch.fGlyphStart.fRunIndex;
auto end = spaces.fGlyphEnd.fRunIndex;
auto start = stretch.glyphStart().runIndex();
auto end = spaces.glyphEnd().runIndex();
auto numRuns = end - start + 1;
SkAutoSTArray<kPreallocCount, SkUnicode::BidiLevel> runLevels(numRuns);
size_t runLevelsIndex = 0;

View File

@ -58,13 +58,20 @@ private:
class GlyphPos {
public:
GlyphPos() { }
GlyphPos() : fRunIndex(EMPTY_INDEX), fGlyphIndex(EMPTY_INDEX) { }
GlyphPos(size_t runIndex, size_t glyphIndex) : fRunIndex(runIndex), fGlyphIndex(glyphIndex) { }
bool operator==(const GlyphPos& other) const {
return this->fRunIndex == other.fRunIndex && this->fGlyphIndex == other.fGlyphIndex;
}
size_t runIndex() const { return fRunIndex; }
size_t glyphIndex() const { return fGlyphIndex; }
void setGlyphIndex(size_t index) { fGlyphIndex = index; }
bool isEmpty() const { return fRunIndex == EMPTY_INDEX; }
private:
size_t fRunIndex;
size_t fGlyphIndex;
};
@ -72,50 +79,59 @@ public:
class Stretch {
public:
Stretch() : fGlyphStart(), fGlyphEnd(), fWidth(0), fTextRange(0, 0), fTextMetrics(), fEmpty(true) { }
Stretch() : fGlyphStart(), fGlyphEnd(), fWidth(0), fTextRange(0, 0), fTextMetrics() { }
Stretch(GlyphPos glyphStart, size_t textIndex, const TextMetrics& metrics)
: fGlyphStart(glyphStart)
, fGlyphEnd(glyphStart)
, fWidth(0.0)
, fTextRange(textIndex, textIndex)
, fTextMetrics(metrics)
, fEmpty(false) { }
, fTextMetrics(metrics) { }
Stretch(const Stretch&) = default;
Stretch(Stretch&&) = default;
Stretch& operator=(Stretch&&) = default;
Stretch& operator=(const Stretch&) = default;
bool isEmpty() const {
return this->fEmpty/* ||
(this->fGlyphStart.fRunIndex == this->fGlyphEnd.fRunIndex &&
this->fGlyphStart.fGlyphIndex == this->fGlyphEnd.fGlyphIndex)*/;
if (this->fGlyphStart.isEmpty()) {
SkASSERT(this->fGlyphEnd.isEmpty());
return true;
} else {
SkASSERT(!this->fGlyphEnd.isEmpty());
return false;
//return (this->fGlyphStart.runIndex() == this->fGlyphEnd.runIndex() &&
// this->fGlyphStart.glyphIndex() == this->fGlyphEnd.glyphIndex());
}
}
void clean() {
this->fEmpty = true;
fGlyphStart = fGlyphEnd;
fTextRange.fStart = fTextRange.fEnd;
fWidth = 0.0f;
fTextMetrics.clean();
}
void moveTo(Stretch& tail) {
if (tail.fEmpty) {
if (tail.isEmpty()) {
return;
}
if (this->fEmpty) {
if (!tail.fEmpty) {
if (this->isEmpty()) {
if (!tail.isEmpty()) {
this->fGlyphStart = tail.fGlyphStart;
this->fGlyphEnd = tail.fGlyphEnd;
this->fWidth = tail.fWidth;
this->fTextRange = tail.fTextRange;
this->fTextMetrics = tail.fTextMetrics;
this->fEmpty = tail.fEmpty;
}
tail.clean();
return;
}
SkASSERT(this->fGlyphEnd.fRunIndex != tail.fGlyphStart.fRunIndex ||
this->fGlyphEnd.fGlyphIndex == tail.fGlyphStart.fGlyphIndex);
SkASSERT(this->fGlyphEnd.runIndex() != tail.fGlyphStart.runIndex() ||
this->fGlyphEnd.glyphIndex() == tail.fGlyphStart.glyphIndex());
this->fGlyphEnd = tail.fGlyphEnd;
this->fWidth += tail.fWidth;
this->fTextRange.merge(tail.fTextRange);
@ -125,16 +141,26 @@ public:
void finish(size_t glyphIndex, size_t textIndex, SkScalar width) {
this->fTextRange.fEnd = textIndex;
this->fGlyphEnd.fGlyphIndex = glyphIndex;
this->fGlyphEnd.setGlyphIndex(glyphIndex);
this->fWidth = width;
}
SkScalar width() const { return fWidth; }
TextRange textRange() const { return fTextRange; }
void setTextRange(TextRange range) { fTextRange = range; }
const TextMetrics& textMetrics() const { return fTextMetrics; }
GlyphPos glyphStart() const { return fGlyphStart; }
GlyphPos glyphEnd() const { return fGlyphEnd; }
size_t glyphStartIndex() const { return fGlyphStart.glyphIndex(); }
size_t textStart() const { return fTextRange.fStart; }
private:
GlyphPos fGlyphStart;
GlyphPos fGlyphEnd;
SkScalar fWidth;
Range fTextRange;
TextRange fTextRange;
TextMetrics fTextMetrics;
bool fEmpty;
};
class Line {
@ -146,10 +172,10 @@ private:
friend class Processor;
GlyphPos fTextStart;
GlyphPos fTextEnd ;
GlyphPos fTextEnd;
GlyphPos fWhitespacesEnd;
Range fText;
Range fWhitespaces;
TextRange fText;
TextRange fWhitespaces;
SkScalar fTextWidth;
SkScalar fSpacesWidth;
TextMetrics fTextMetrics;

View File

@ -55,7 +55,7 @@ bool Processor::format(TextFormatStyle formatStyle) {
bool Processor::decorate(SkTArray<DecorBlock, true> decorBlocks) {
this->iterateByVisualOrder(decorBlocks,
[&](SkSize offset, SkScalar baseline, const TextRun* run, Range textRange, Range glyphRange, const DecorBlock& block) {
[&](SkSize offset, SkScalar baseline, const TextRun* run, TextRange textRange, GlyphRange glyphRange, const DecorBlock& block) {
SkTextBlobBuilder builder;
const auto& blobBuffer = builder.allocRunPos(run->fFont, SkToInt(glyphRange.width()));
sk_careful_memcpy(blobBuffer.glyphs, run->fGlyphs.data() + glyphRange.fStart, glyphRange.width() * sizeof(SkGlyphID));
@ -74,10 +74,22 @@ bool Processor::drawText(const char* text, SkCanvas* canvas, SkScalar x, SkScala
return drawText(text, canvas, TextFormatStyle(TextAlign::kLeft, TextDirection::kLtr), SK_ColorBLACK, SK_ColorWHITE, SkString("Roboto"), 14, SkFontStyle::Normal(), x, y);
}
bool Processor::drawText(const char* text, SkCanvas* canvas, SkScalar width) {
return drawText(text, canvas,
TextFormatStyle(TextAlign::kLeft, TextDirection::kLtr), SK_ColorBLACK, SK_ColorWHITE, SkString("Roboto"), 14, SkFontStyle::Normal(),
SkSize::Make(width, SK_ScalarInfinity), 0, 0);
}
bool Processor::drawText(const char* text, SkCanvas* canvas, TextFormatStyle textFormat, SkColor foreground, SkColor background, const SkString& fontFamily, SkScalar fontSize, SkFontStyle fontStyle, SkScalar x, SkScalar y) {
return drawText(text, canvas, textFormat, foreground, background, fontFamily, fontSize, fontStyle, SkSize::Make(SK_ScalarInfinity, SK_ScalarInfinity), x, y);
}
bool Processor::drawText(const char* text, SkCanvas* canvas,
TextFormatStyle textFormat, SkColor foreground, SkColor background, const SkString& fontFamily, SkScalar fontSize, SkFontStyle fontStyle,
SkSize reqSize, SkScalar x, SkScalar y) {
SkString str(text);
Range textRange(0, str.size());
TextRange textRange(0, str.size());
Processor processor(str);
if (!processor.computeCodeUnitProperties()) {
return false;
@ -85,7 +97,7 @@ bool Processor::drawText(const char* text, SkCanvas* canvas, TextFormatStyle tex
if (!processor.shape({ textFormat.fDefaultTextDirection, SkFontMgr::RefDefault()}, {{{ fontFamily, fontSize, fontStyle, textRange }}})) {
return false;
}
if (!processor.wrap(SK_ScalarInfinity, SK_ScalarInfinity)) {
if (!processor.wrap(reqSize.fWidth, reqSize.fHeight)) {
return false;
}
if (!processor.format(textFormat)) {
@ -117,7 +129,7 @@ void Processor::sortDecorBlocks(SkTArray<DecorBlock, true>& decorBlocks) {
SkPaint* foreground = new SkPaint();
foreground->setColor(SK_ColorBLACK);
std::stack<DecorBlock> defaultBlocks;
defaultBlocks.emplace(foreground, nullptr, Range(0, fText.size()));
defaultBlocks.emplace(foreground, nullptr, TextRange(0, fText.size()));
size_t start = 0;
for (auto& block : decorBlocks) {
this->adjustLeft(&block.fRange.fStart);
@ -192,18 +204,6 @@ void Processor::markGlyphs() {
}
}
template<typename Visitor>
void Processor::iterateByLogicalOrder(CodeUnitFlags units, Visitor visitor) {
Range range(0, 0);
for (size_t index = 0; index < fCodeUnitProperties.size(); ++index) {
if (this->hasProperty(index, units)) {
range.fEnd = index;
visitor(range, this->fCodeUnitProperties[index]);
range.fStart = index;
}
}
}
template<typename Visitor>
void Processor::iterateByVisualOrder(CodeUnitFlags units, Visitor visitor) {
SkSize offset = SkSize::MakeEmpty();
@ -212,8 +212,8 @@ void Processor::iterateByVisualOrder(CodeUnitFlags units, Visitor visitor) {
for (auto& runIndex : line.fRunsInVisualOrder) {
auto& run = fRuns[runIndex];
auto startGlyph = runIndex == line.fTextStart.fRunIndex == runIndex ? line.fTextStart.fGlyphIndex : 0;
auto endGlyph = runIndex == line.fTextEnd.fRunIndex ? line.fTextEnd.fGlyphIndex : run.fGlyphs.size();
auto startGlyph = runIndex == line.fTextStart.runIndex() ? line.fTextStart.glyphIndex() : 0;
auto endGlyph = runIndex == line.fTextEnd.runIndex() ? line.fTextEnd.glyphIndex() : run.fGlyphs.size();
Range textRange(run.fUtf8Range.begin(), run.fUtf8Range.end());
Range glyphRange(startGlyph, endGlyph);
@ -257,11 +257,11 @@ void Processor::iterateByVisualOrder(SkTArray<DecorBlock, true>& decorBlocks, Vi
// "Cd": green
// "ef": blue
// green[d] blue[ef] green [C] red [BA]
auto startGlyph = runIndex == line.fTextStart.fRunIndex == runIndex ? line.fTextStart.fGlyphIndex : 0;
auto endGlyph = runIndex == line.fTextEnd.fRunIndex ? line.fTextEnd.fGlyphIndex : run.fGlyphs.size();
auto startGlyph = runIndex == line.fTextStart.runIndex() ? line.fTextStart.glyphIndex() : 0;
auto endGlyph = runIndex == line.fTextEnd.runIndex() ? line.fTextEnd.glyphIndex() : run.fGlyphs.size();
Range textRange(run.fUtf8Range.begin(), run.fUtf8Range.end());
Range glyphRange(startGlyph, endGlyph);
TextRange textRange(run.fClusters[startGlyph], run.fClusters[endGlyph]);
GlyphRange glyphRange(startGlyph, endGlyph);
SkASSERT(currentBlock->fRange.fStart <= textRange.fStart);
for (auto glyphIndex = startGlyph; glyphIndex <= endGlyph; ++glyphIndex) {
@ -278,7 +278,8 @@ void Processor::iterateByVisualOrder(SkTArray<DecorBlock, true>& decorBlocks, Vi
textRange.fStart = textIndex;
}
glyphRange.fEnd = glyphIndex;
visitor(offset, line.fTextMetrics.baseline(), &run, textRange, glyphRange, *currentBlock);
SkSize shift = SkSize::Make(offset.fWidth - run.fPositions[startGlyph].fX, offset.fHeight);
visitor(shift, line.fTextMetrics.baseline(), &run, textRange, glyphRange, *currentBlock);
if (run.leftToRight()) {
textRange.fStart = textIndex;
} else {
@ -287,6 +288,16 @@ void Processor::iterateByVisualOrder(SkTArray<DecorBlock, true>& decorBlocks, Vi
glyphRange.fStart = glyphIndex;
offset.fWidth += run.calculateWidth(glyphRange);
}
// The last line
if (run.leftToRight()) {
textRange.fEnd = run.fClusters[endGlyph];
} else {
textRange.fStart = run.fClusters[endGlyph];
}
glyphRange.fEnd = endGlyph;
SkSize shift = SkSize::Make(offset.fWidth - run.fPositions[startGlyph].fX, offset.fHeight);
visitor(shift, line.fTextMetrics.baseline(), &run, textRange, glyphRange, *currentBlock);
}
offset.fHeight += line.fTextMetrics.height();
}

View File

@ -34,7 +34,7 @@ SkShaper::RunHandler::Buffer TextRun::newRunBuffer() {
return {fGlyphs.data(), fPositions.data(), nullptr, fClusters.data(), {0.0f, 0.0f} };
}
SkScalar TextRun::calculateWidth(Range glyphRange) const {
SkScalar TextRun::calculateWidth(GlyphRange glyphRange) const {
SkASSERT(glyphRange.fStart <= glyphRange.fEnd && glyphRange.fEnd < fPositions.size());
return fPositions[glyphRange.fEnd].fX - fPositions[glyphRange.fStart].fX;
}

View File

@ -18,7 +18,7 @@ class TextRun {
SkShaper::RunHandler::Buffer newRunBuffer();
void commit();
SkScalar calculateWidth(Range glyphRange) const;
SkScalar calculateWidth(GlyphRange glyphRange) const;
bool leftToRight() const { return fBidiLevel % 2 == 0; }
uint8_t bidiLevel() const { return fBidiLevel; }

View File

@ -11,7 +11,7 @@ bool Wrapper::process() {
return this->breakTextIntoLines(this->fWidth);
}
Range Wrapper::glyphRange(const TextRun* run, const Range& textRange) {
GlyphRange Wrapper::glyphRange(const TextRun* run, const TextRange& textRange) {
Range glyphRange = EMPTY_RANGE;
for (size_t i = 0; i < run->fClusters.size(); ++i) {
auto cluster = run->fClusters[i];
@ -26,7 +26,7 @@ Range Wrapper::glyphRange(const TextRun* run, const Range& textRange) {
return glyphRange;
}
Range Wrapper::textRange(const TextRun* run, const Range& glyphRange) {
TextRange Wrapper::textRange(const TextRun* run, const GlyphRange& glyphRange) {
Range textRange = EMPTY_RANGE;
for (size_t i = 0; i < run->fClusters.size(); ++i) {
auto cluster = run->fClusters[i];
@ -54,7 +54,7 @@ bool Wrapper::breakTextIntoLines(SkScalar width) {
TextMetrics runMetrics(run.fFont);
Stretch cluster;
if (!run.leftToRight()) {
cluster.fTextRange = { run.fUtf8Range.end(), run.fUtf8Range.end()};
cluster.setTextRange({ run.fUtf8Range.end(), run.fUtf8Range.end()});
}
for (size_t glyphIndex = 0; glyphIndex < run.fPositions.size(); ++glyphIndex) {
@ -63,66 +63,77 @@ bool Wrapper::breakTextIntoLines(SkScalar width) {
if (cluster.isEmpty()) {
cluster = Stretch(GlyphPos(runIndex, glyphIndex), textIndex, runMetrics);
continue;
/*
} else if (cluster.fTextRange.fStart == textIndex) {
break;
} else if (cluster.fTextRange.fEnd == textIndex) {
break;
*/
}
// The entire cluster belongs to a single run
SkASSERT(cluster.fGlyphStart.fRunIndex == runIndex);
SkASSERT(cluster.glyphStart().runIndex() == runIndex);
auto clusterWidth = run.fPositions[glyphIndex].fX - run.fPositions[cluster.fGlyphStart.fGlyphIndex].fX;
auto clusterWidth = run.fPositions[glyphIndex].fX - run.fPositions[cluster.glyphStartIndex()].fX;
cluster.finish(glyphIndex, textIndex, clusterWidth);
auto isHardLineBreak = fProcessor->isHardLineBreak(cluster.fTextRange.fStart);
auto isSoftLineBreak = fProcessor->isSoftLineBreak(cluster.fTextRange.fStart);
auto isWhitespaces = fProcessor->isWhitespaces(cluster.fTextRange);
auto isHardLineBreak = fProcessor->isHardLineBreak(cluster.textStart());
auto isSoftLineBreak = fProcessor->isSoftLineBreak(cluster.textStart());
auto isWhitespaces = fProcessor->isWhitespaces(cluster.textRange());
auto isEndOfText = run.leftToRight() ? textIndex == run.fUtf8Range.end() : textIndex == run.fUtf8Range.begin();
if (isHardLineBreak || isEndOfText || isSoftLineBreak || isWhitespaces) {
if (isSoftLineBreak || isWhitespaces || isHardLineBreak) {
// This is the end of the word
if (!clusters.isEmpty()) {
line.moveTo(spaces);
line.moveTo(clusters);
}
if (isWhitespaces) {
spaces.moveTo(cluster);
}
if (isHardLineBreak) {
this->addLine(line, spaces);
break;
}
if (isEndOfText) {
line.moveTo(cluster);
if (!line.isEmpty()) {
this->addLine(line, spaces);
}
break;
spaces = clusters;
}
}
if (!SkScalarIsFinite(width)) {
// line + spaces + clusters + cluster
if (isWhitespaces) {
// Whitespaces do not extend the line width
spaces.moveTo(cluster);
clusters = cluster;
continue;
} else if (isHardLineBreak) {
// Hard line break ends the line but does not extend the width
// Same goes for the end of the text
this->addLine(line, spaces);
break;
} else if (!SkScalarIsFinite(width)) {
clusters.moveTo(cluster);
} else if ((line.fWidth + spaces.fWidth + clusters.fWidth + cluster.fWidth) <= width) {
continue;
}
// Now let's find out if we can add the cluster to the line
if ((line.width() + spaces.width() + clusters.width() + cluster.width()) <= width) {
clusters.moveTo(cluster);
} else {
// Wrapping the text by whitespaces
if (line.isEmpty()) {
if (clusters.isEmpty()) {
if (spaces.isEmpty() && clusters.isEmpty()) {
// There is only this cluster and it's too long;
// we are drawing it anyway
line.moveTo(cluster);
} else {
// We break the only one word on the line by this cluster
line.moveTo(clusters);
}
} else {
// We move clusters + cluster on the next line
// TODO: Parametrise possible ways of breaking too long word
// (start it from a new line or squeeze the part of it on this line)
}
this->addLine(line, spaces);
clusters.moveTo(cluster);
}
cluster = Stretch(GlyphPos(runIndex, glyphIndex), textIndex, runMetrics);
}
}
if (!clusters.isEmpty()) {
line.moveTo(spaces);
line.moveTo(clusters);
spaces = clusters;
}
this->addLine(line, spaces);
return true;
}

View File

@ -21,17 +21,18 @@ public:
spaces.clean();
}
SkScalar glyphRangeWidth(const TextRun* run, const Range& glyphRange) {
SkScalar glyphRangeWidth(const TextRun* run, const GlyphRange& glyphRange) {
return run->fPositions[glyphRange.fEnd].fX - run->fPositions[glyphRange.fStart].fX;
}
static Range glyphRange(const TextRun* run, const Range& textRange);
static Range textRange(const TextRun* run, const Range& glyphRange);
static GlyphRange glyphRange(const TextRun* run, const TextRange& textRange);
static TextRange textRange(const TextRun* run, const GlyphRange& glyphRange);
bool breakTextIntoLines(SkScalar width);
private:
Processor* fProcessor;
SkScalar fWidth;
// TODO: Implement
SkScalar fHeight;
};