blob: 27b35e7d6ec25a7458e997f303022684d8781401 [file] [log] [blame]
// Copyright 2021 Google LLC.
#ifndef Processor_DEFINED
#define Processor_DEFINED
#include <string>
#include "experimental/sktext/include/Types.h"
#include "experimental/sktext/src/Line.h"
#include "experimental/sktext/src/TextRun.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkFontMgr.h"
#include "include/core/SkFontStyle.h"
#include "include/core/SkPaint.h"
#include "include/core/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkTextBlob.h"
#include "modules/skshaper/include/SkShaper.h"
#include "modules/skunicode/include/SkUnicode.h"
namespace skia {
namespace text {
class UnicodeText;
class Text {
public:
static std::unique_ptr<UnicodeText> parse(SkSpan<uint16_t> utf16);
};
// Font iterator that finds all formatting marks
// and breaks runs on them (so we can select and interpret them later)
class FormattingFontIterator final : public SkShaper::FontRunIterator {
public:
FormattingFontIterator(TextIndex textCount,
SkSpan<FontBlock> fontBlocks,
SkSpan<TextIndex> marks)
: fTextCount(textCount)
, fFontBlocks(fontBlocks)
, fFormattingMarks(marks)
, fCurrentBlock(fontBlocks.begin())
, fCurrentMark(marks.begin())
, fCurrentFontIndex(0)
, fCurrentFont(fCurrentBlock->createFont()) {}
void consume() override {
SkASSERT(fCurrentBlock < fFontBlocks.end());
SkASSERT(fCurrentMark < fFormattingMarks.end());
if (fCurrentFontIndex <= *fCurrentMark) {
if (fCurrentFontIndex == *fCurrentMark) {
++fCurrentMark;
}
++fCurrentBlock;
if (fCurrentBlock < fFontBlocks.end()) {
fCurrentFontIndex += fCurrentBlock->charCount;
fCurrentFont = fCurrentBlock->createFont();
}
} else {
++fCurrentMark;
}
}
size_t endOfCurrentRun() const override {
SkASSERT(fCurrentMark != fFormattingMarks.end() || fCurrentBlock != fFontBlocks.end());
if (fCurrentMark == fFormattingMarks.end()) {
return fCurrentFontIndex;
} else if (fCurrentBlock == fFontBlocks.end()) {
return *fCurrentMark;
} else {
return fCurrentFontIndex <= *fCurrentMark ? fCurrentFontIndex : *fCurrentMark;
}
}
bool atEnd() const override {
return (fCurrentBlock == fFontBlocks.end() || fCurrentFontIndex == fTextCount) &&
(fCurrentMark == fFormattingMarks.end() || *fCurrentMark == fTextCount);
}
const SkFont& currentFont() const override { return fCurrentFont; }
private:
TextIndex const fTextCount;
SkSpan<FontBlock> fFontBlocks;
SkSpan<TextIndex> fFormattingMarks;
FontBlock* fCurrentBlock;
TextIndex* fCurrentMark;
TextIndex fCurrentFontIndex;
SkFont fCurrentFont;
};
class ShapedText;
class UnicodeText : public SkShaper::RunHandler {
public:
std::unique_ptr<ShapedText> shape(SkSpan<FontBlock> blocks, TextDirection textDirection);
bool hasProperty(size_t index, CodeUnitFlags flag) {
return (fCodeUnitProperties[index] & flag) == flag;
}
bool isHardLineBreak(size_t index) {
return this->hasProperty(index, CodeUnitFlags::kHardLineBreakBefore);
}
bool isSoftLineBreak(size_t index) {
return index != 0 && this->hasProperty(index, CodeUnitFlags::kSoftLineBreakBefore);
}
SkUnicode* getUnicode() const { return fUnicode.get(); }
private:
friend class Text;
UnicodeText() : fCurrentRun(nullptr) {}
void beginLine() override {}
void runInfo(const RunInfo&) override {}
void commitRunInfo() override {}
void commitLine() override {}
void commitRunBuffer(const RunInfo&) override;
Buffer runBuffer(const RunInfo& info) override {
fCurrentRun = std::make_unique<TextRun>(info, fParagraphTextStart, fRunGlyphStart);
return fCurrentRun->newRunBuffer();
}
SkFont createFont(const FontBlock& fontBlock);
SkTArray<size_t, true> fUTF16FromUTF8;
SkTArray<size_t, true> fUTF8FromUTF16;
SkTArray<CodeUnitFlags, true> fCodeUnitProperties;
SkString fText8;
std::u16string fText16;
std::unique_ptr<SkUnicode> fUnicode;
std::unique_ptr<TextRun> fCurrentRun;
TextIndex fParagraphTextStart;
SkScalar fRunGlyphStart;
std::unique_ptr<ShapedText> fShapedText;
};
class WrappedText;
class ShapedText {
public:
std::unique_ptr<WrappedText> wrap(float width, float height, SkUnicode* unicode);
bool isClusterEdge(size_t index) const {
return (fGlyphUnitProperties[index] & GlyphUnitFlags::kGlyphClusterStart) ==
GlyphUnitFlags::kGlyphClusterStart;
}
void adjustLeft(size_t* index) const {
SkASSERT(index != nullptr);
while (*index != 0) {
if (isClusterEdge(*index)) {
return;
}
--index;
}
}
void adjustRight(size_t* index) const {
SkASSERT(index != nullptr);
while (*index < this->fGlyphUnitProperties.size()) {
if (isClusterEdge(*index)) {
return;
}
++index;
}
}
bool hasProperty(size_t index, GlyphUnitFlags flag) {
return (fGlyphUnitProperties[index] & flag) == flag;
}
bool isHardLineBreak(size_t index) {
return this->hasProperty(index, GlyphUnitFlags::kHardLineBreakBefore);
}
bool isSoftLineBreak(size_t index) {
return index != 0 && this->hasProperty(index, GlyphUnitFlags::kSoftLineBreakBefore);
}
bool isWhitespaces(TextRange range) {
if (range.leftToRight()) {
for (auto i = range.fStart; i < range.fEnd; ++i) {
if (!this->hasProperty(i, GlyphUnitFlags::kPartOfWhiteSpace)) {
return false;
}
}
} else {
for (auto i = range.fStart; i > range.fEnd; --i) {
if (!this->hasProperty(i, GlyphUnitFlags::kPartOfWhiteSpace)) {
return false;
}
}
}
return true;
}
private:
friend class UnicodeText;
ShapedText() {}
SkTArray<TextRun, false> fRuns;
SkTArray<GlyphUnitFlags, true> fGlyphUnitProperties;
};
class FormattedText;
class WrappedText {
public:
sk_sp<FormattedText> format(TextAlign textAlign, TextDirection textDirection);
SkSize actualSize() const { return fActualSize; }
size_t countLines() const { return fLines.size(); }
private:
friend class ShapedText;
WrappedText() : fActualSize(SkSize::MakeEmpty()) {}
void addLine(Stretch& stretch, Stretch& spaces, SkUnicode* unicode, bool hardLineBreak);
SkTArray<TextRun, false> fRuns;
SkTArray<Line, false> fLines;
SkTArray<GlyphUnitFlags, true> fGlyphUnitProperties;
SkSize fActualSize;
};
class FormattedText : public SkRefCnt {
public:
SkSize actualSize() const { return fActualSize; }
// Operations starting from mouse/cursor require repositioning and adjusting by the screen
Position adjustedPosition(PositionType positionType, SkPoint point) const;
// Operations of the text (adding, removing) require repositioning and adjusting by the text
Position adjustedPosition(PositionType positionType, TextIndex textIndex) const;
bool recalculateBoundaries(Position& position) const;
const TextRun* visuallyPreviousRun(size_t lineIndex, const TextRun* run) const;
const TextRun* visuallyNextRun(size_t lineIndex, const TextRun* run) const;
const TextRun* visuallyFirstRun(size_t lineIndex) const;
const TextRun* visuallyLastRun(size_t lineIndex) const;
bool isVisuallyFirst(size_t lineIndex, const TextRun* run) const;
bool isVisuallyLast(size_t lineIndex, const TextRun* run) const;
Position previousElement(Position element) const;
Position nextElement(Position current) const;
Position firstElement(PositionType positionType) const;
Position lastElement(PositionType positionType) const;
bool isFirstOnTheLine(Position element) const;
bool isLastOnTheLine(Position element) const;
size_t lineIndex(const Line* line) const { return line - fLines.data(); }
size_t countLines() const { return fLines.size(); }
const Line* line(size_t lineIndex) const {
return fLines.empty() ? nullptr : &fLines[lineIndex];
}
size_t runIndex(const TextRun* run) const {
return run == nullptr ? EMPTY_INDEX : run - fRuns.data();
}
bool hasProperty(size_t index, GlyphUnitFlags flag) const {
return (fGlyphUnitProperties[index] & flag) == flag;
}
class Visitor {
public:
virtual ~Visitor() = default;
virtual void onBeginLine(TextRange lineText) {}
virtual void onEndLine(TextRange lineText) {}
virtual void onGlyphRun(const SkFont& font,
TextRange textRange,
SkRect boundingRect,
int glyphCount,
const uint16_t glyphs[],
const SkPoint positions[],
const SkPoint offsets[]) {
SkTextBlobBuilder builder;
const auto& blobBuffer = builder.allocRunPos(font, SkToInt(glyphCount));
sk_careful_memcpy(blobBuffer.glyphs, glyphs, glyphCount * sizeof(uint16_t));
sk_careful_memcpy(blobBuffer.points(), positions, glyphCount * sizeof(SkPoint));
fTextBlobs.emplace_back(builder.make());
}
virtual void onPlaceholder(TextRange, const SkRect& bounds) {}
void buildTextBlobs(FormattedText* formattedText) {
fTextBlobs.clear();
formattedText->visit(this);
}
std::vector<sk_sp<SkTextBlob>>& getTextBlobs() { return fTextBlobs; }
private:
std::vector<sk_sp<SkTextBlob>> fTextBlobs;
};
// Visit runs as is by lines
void visit(Visitor*) const;
// Visit chunked runs
void visit(Visitor*, SkSpan<size_t> blocks) const;
private:
void adjustTextRange(Position* position) const;
friend class WrappedText;
FormattedText() = default;
SkTArray<TextRun, false> fRuns;
SkTArray<Line, false> fLines;
SkTArray<GlyphUnitFlags, true> fGlyphUnitProperties;
SkSize fActualSize;
std::unique_ptr<Visitor> fVisitor;
};
} // namespace text
} // namespace skia
#endif // Processor_DEFINED