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:
parent
4882f97829
commit
6d14195309
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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();)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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; }
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user