The current version...
Adding cache Caching shaped results Base+Index for referencing arrays The very first and naive version of cache Cache measurement, lines and picture Added text blob cache for lines Removed Run* from Cluster Removed const char* from Cluster and Run Few minor changes Change-Id: I444a1defa950aed5999cfa1c3545fd83ccb54ce9 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/227840 Commit-Queue: Julia Lavrova <jlavrova@google.com> Reviewed-by: Julia Lavrova <jlavrova@google.com> Reviewed-by: Mike Reed <reed@google.com>
This commit is contained in:
parent
fc854c30fb
commit
5207f35f33
@ -12,6 +12,8 @@
|
||||
#include "tools/Resources.h"
|
||||
|
||||
#include <cfloat>
|
||||
#include "modules/skparagraph/utils/TestFontCollection.h"
|
||||
#include "include/core/SkPictureRecorder.h"
|
||||
|
||||
using namespace skia::textlayout;
|
||||
namespace {
|
||||
@ -32,23 +34,28 @@ struct ParagraphBench : public Benchmark {
|
||||
|
||||
const char* text = (const char*)fData->data();
|
||||
|
||||
sk_sp<FontCollection> fontCollection = sk_make_sp<FontCollection>();
|
||||
auto fontCollection = sk_make_sp<FontCollection>();
|
||||
fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.turnHintingOff();
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
builder.addText(text);
|
||||
auto paragraph = builder.Build();
|
||||
|
||||
SkPictureRecorder rec;
|
||||
SkCanvas* canvas = rec.beginRecording({0,0, 2000,3000});
|
||||
while (loops-- > 0) {
|
||||
paragraph->layout(fWidth);
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
impl->formatLines(fWidth);
|
||||
paragraph->paint(canvas, 0, 0);
|
||||
paragraph->markDirty();
|
||||
impl->resetCache();
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
#define PARAGRAPH_BENCH(X) DEF_BENCH(return new ParagraphBench(500, "text/" #X ".txt", "paragraph_" #X);)
|
||||
#define PARAGRAPH_BENCH(X) DEF_BENCH(return new ParagraphBench(50000, "text/" #X ".txt", "paragraph_" #X);)
|
||||
//PARAGRAPH_BENCH(arabic)
|
||||
//PARAGRAPH_BENCH(emoji)
|
||||
PARAGRAPH_BENCH(english)
|
||||
|
@ -11,7 +11,7 @@ declare_args() {
|
||||
if (skia_enable_skparagraph) {
|
||||
|
||||
config("public_config") {
|
||||
include_dirs = [ "include" ]
|
||||
include_dirs = [ "include", "utils" ]
|
||||
}
|
||||
|
||||
component("skparagraph") {
|
||||
|
@ -70,6 +70,7 @@ struct TextBox {
|
||||
TextBox(SkRect r, TextDirection d) : rect(r), direction(d) {}
|
||||
};
|
||||
|
||||
const size_t EMPTY_INDEX = std::numeric_limits<size_t>::max();
|
||||
template <typename T> struct SkRange {
|
||||
SkRange() : start(), end() {}
|
||||
SkRange(T s, T e) : start(s), end(e) {}
|
||||
@ -80,14 +81,25 @@ template <typename T> struct SkRange {
|
||||
return start == other.start && end == other.end;
|
||||
}
|
||||
|
||||
T width() { return end - start; }
|
||||
T width() const { return end - start; }
|
||||
|
||||
void Shift(T delta) {
|
||||
start += delta;
|
||||
end += delta;
|
||||
}
|
||||
|
||||
bool contains(SkRange<size_t> other) const {
|
||||
return start <= other.start && end >= other.end;
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return start == EMPTY_INDEX && end == EMPTY_INDEX;
|
||||
}
|
||||
};
|
||||
|
||||
const SkRange<size_t> EMPTY_RANGE = SkRange<size_t>(EMPTY_INDEX, EMPTY_INDEX);
|
||||
|
||||
|
||||
enum class TextBaseline {
|
||||
kAlphabetic,
|
||||
kIdeographic,
|
||||
|
@ -23,13 +23,14 @@ public:
|
||||
void setAssetFontManager(sk_sp<SkFontMgr> fontManager);
|
||||
void setDynamicFontManager(sk_sp<SkFontMgr> fontManager);
|
||||
void setTestFontManager(sk_sp<SkFontMgr> fontManager);
|
||||
void setDefaultFontManager(sk_sp<SkFontMgr> fontManager);
|
||||
void setDefaultFontManager(sk_sp<SkFontMgr> fontManager, const char defaultFamilyName[]);
|
||||
|
||||
sk_sp<SkFontMgr> geFallbackManager() const { return fDefaultFontManager; }
|
||||
|
||||
sk_sp<SkTypeface> matchTypeface(const char familyName[], SkFontStyle fontStyle);
|
||||
sk_sp<SkTypeface> matchDefaultTypeface(SkFontStyle fontStyle);
|
||||
sk_sp<SkTypeface> defaultFallback(SkUnichar unicode, SkFontStyle fontStyle, SkString locale);
|
||||
sk_sp<SkTypeface> defaultFallback(SkUnichar unicode, SkFontStyle fontStyle, const SkString& locale);
|
||||
|
||||
void disableFontFallback();
|
||||
bool fontFallbackEnabled() { return fEnableFontFallback; }
|
||||
|
@ -11,14 +11,6 @@ class SkCanvas;
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
struct Block {
|
||||
Block(size_t start, size_t end, const TextStyle& style)
|
||||
: fStart(start), fEnd(end), fStyle(style) {}
|
||||
size_t fStart;
|
||||
size_t fEnd;
|
||||
TextStyle fStyle;
|
||||
};
|
||||
|
||||
class Paragraph {
|
||||
|
||||
public:
|
||||
@ -75,7 +67,6 @@ protected:
|
||||
SkScalar fWidth;
|
||||
SkScalar fMaxIntrinsicWidth;
|
||||
SkScalar fMinIntrinsicWidth;
|
||||
SkScalar fMaxLineWidth;
|
||||
};
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
@ -16,7 +16,7 @@ namespace textlayout {
|
||||
|
||||
class ParagraphBuilder {
|
||||
public:
|
||||
ParagraphBuilder(ParagraphStyle style, sk_sp<FontCollection> fontCollection) { }
|
||||
ParagraphBuilder(const ParagraphStyle& style, sk_sp<FontCollection> fontCollection) { }
|
||||
|
||||
virtual ~ParagraphBuilder() = default;
|
||||
|
||||
@ -51,7 +51,7 @@ public:
|
||||
// Constructs a SkParagraph object that can be used to layout and paint the text to a SkCanvas.
|
||||
virtual std::unique_ptr<Paragraph> Build() = 0;
|
||||
|
||||
static std::unique_ptr<ParagraphBuilder> make(ParagraphStyle style,
|
||||
static std::unique_ptr<ParagraphBuilder> make(const ParagraphStyle& style,
|
||||
sk_sp<FontCollection> fontCollection);
|
||||
};
|
||||
} // namespace textlayout
|
||||
|
@ -36,7 +36,6 @@ enum TextDecorationStyle { kSolid, kDouble, kDotted, kDashed, kWavy };
|
||||
|
||||
enum StyleType {
|
||||
kAllAttributes,
|
||||
kText,
|
||||
kFont,
|
||||
kForeground,
|
||||
kBackground,
|
||||
@ -46,6 +45,20 @@ enum StyleType {
|
||||
kWordSpacing
|
||||
};
|
||||
|
||||
struct Decoration {
|
||||
TextDecoration fType;
|
||||
SkColor fColor;
|
||||
TextDecorationStyle fStyle;
|
||||
SkScalar fThicknessMultiplier;
|
||||
|
||||
bool operator==(const Decoration& other) const {
|
||||
return this->fType == other.fType &&
|
||||
this->fColor == other.fColor &&
|
||||
this->fStyle == other.fStyle &&
|
||||
this->fThicknessMultiplier == other.fThicknessMultiplier;
|
||||
}
|
||||
};
|
||||
|
||||
class TextStyle {
|
||||
public:
|
||||
TextStyle();
|
||||
@ -76,16 +89,17 @@ public:
|
||||
void clearBackgroundColor() { fHasBackground = false; }
|
||||
|
||||
// Decorations
|
||||
TextDecoration getDecoration() const { return fDecoration; }
|
||||
SkColor getDecorationColor() const { return fDecorationColor; }
|
||||
TextDecorationStyle getDecorationStyle() const { return fDecorationStyle; }
|
||||
Decoration getDecoration() const { return fDecoration; }
|
||||
TextDecoration getDecorationType() const { return fDecoration.fType; }
|
||||
SkColor getDecorationColor() const { return fDecoration.fColor; }
|
||||
TextDecorationStyle getDecorationStyle() const { return fDecoration.fStyle; }
|
||||
SkScalar getDecorationThicknessMultiplier() const {
|
||||
return fDecorationThicknessMultiplier;
|
||||
return fDecoration.fThicknessMultiplier;
|
||||
}
|
||||
void setDecoration(TextDecoration decoration) { fDecoration = decoration; }
|
||||
void setDecorationStyle(TextDecorationStyle style) { fDecorationStyle = style; }
|
||||
void setDecorationColor(SkColor color) { fDecorationColor = color; }
|
||||
void setDecorationThicknessMultiplier(SkScalar m) { fDecorationThicknessMultiplier = m; }
|
||||
void setDecoration(TextDecoration decoration) { fDecoration.fType = decoration; }
|
||||
void setDecorationStyle(TextDecorationStyle style) { fDecoration.fStyle = style; }
|
||||
void setDecorationColor(SkColor color) { fDecoration.fColor = color; }
|
||||
void setDecorationThicknessMultiplier(SkScalar m) { fDecoration.fThicknessMultiplier = m; }
|
||||
|
||||
// Weight/Width/Slant
|
||||
SkFontStyle getFontStyle() const { return fFontStyle; }
|
||||
@ -135,16 +149,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
TextDecoration fDecoration;
|
||||
SkColor fDecorationColor;
|
||||
TextDecorationStyle fDecorationStyle;
|
||||
SkScalar fDecorationThicknessMultiplier;
|
||||
Decoration fDecoration;
|
||||
|
||||
SkFontStyle fFontStyle;
|
||||
|
||||
std::vector<SkString> fFontFamilies;
|
||||
SkScalar fFontSize;
|
||||
|
||||
SkScalar fHeight;
|
||||
SkString fLocale;
|
||||
SkScalar fLetterSpacing;
|
||||
@ -162,6 +172,27 @@ private:
|
||||
|
||||
sk_sp<SkTypeface> fTypeface;
|
||||
};
|
||||
|
||||
typedef size_t TextIndex;
|
||||
typedef SkRange<size_t> TextRange;
|
||||
const SkRange<size_t> EMPTY_TEXT = EMPTY_RANGE;
|
||||
|
||||
|
||||
struct Block {
|
||||
Block() : fRange(EMPTY_RANGE), fStyle() { }
|
||||
Block(size_t start, size_t end, const TextStyle& style)
|
||||
: fRange(start, end), fStyle(style) {}
|
||||
Block(TextRange textRange, const TextStyle& style)
|
||||
: fRange(textRange), fStyle(style) {}
|
||||
|
||||
void add(TextRange tail) {
|
||||
SkASSERT(fRange.end == tail.start);
|
||||
fRange = TextRange(fRange.start, fRange.start + fRange.width() + tail.width());
|
||||
}
|
||||
TextRange fRange;
|
||||
TextStyle fStyle;
|
||||
};
|
||||
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
||||
|
@ -81,7 +81,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
SkTArray<sk_sp<TypefaceFontStyleSet>> fRegisteredFamilies;
|
||||
SkTHashMap<SkString, sk_sp<TypefaceFontStyleSet>> fRegisteredFamilies;
|
||||
SkTArray<SkString> fFamilyNames;
|
||||
};
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
@ -14,6 +14,7 @@ skparagraph_public = [
|
||||
"$_include/ParagraphBuilder.h",
|
||||
"$_include/DartTypes.h",
|
||||
"$_include/TypefaceFontProvider.h",
|
||||
"$_utils/TestFontCollection.h",
|
||||
]
|
||||
|
||||
skparagraph_sources = [
|
||||
@ -23,6 +24,8 @@ skparagraph_sources = [
|
||||
"$_src/Iterators.h",
|
||||
"$_src/ParagraphBuilderImpl.h",
|
||||
"$_src/ParagraphBuilderImpl.cpp",
|
||||
"$_src/ParagraphCache.h",
|
||||
"$_src/ParagraphCache.cpp",
|
||||
"$_src/ParagraphImpl.h",
|
||||
"$_src/ParagraphImpl.cpp",
|
||||
"$_src/ParagraphStyle.cpp",
|
||||
@ -35,6 +38,7 @@ skparagraph_sources = [
|
||||
"$_src/TextWrapper.h",
|
||||
"$_src/TextWrapper.cpp",
|
||||
"$_src/TypefaceFontProvider.cpp",
|
||||
"$_utils/TestFontCollection.cpp",
|
||||
]
|
||||
|
||||
skparagraph_utils = [
|
||||
|
@ -18,7 +18,6 @@ size_t FontCollection::FamilyKey::Hasher::operator()(const FontCollection::Famil
|
||||
|
||||
FontCollection::FontCollection()
|
||||
: fEnableFontFallback(true)
|
||||
, fDefaultFontManager(SkFontMgr::RefDefault())
|
||||
, fDefaultFamilyName(DEFAULT_FONT_FAMILY) { }
|
||||
|
||||
size_t FontCollection::getFontManagersCount() const { return this->getFontManagerOrder().size(); }
|
||||
@ -37,10 +36,14 @@ void FontCollection::setTestFontManager(sk_sp<SkFontMgr> font_manager) {
|
||||
|
||||
void FontCollection::setDefaultFontManager(sk_sp<SkFontMgr> fontManager,
|
||||
const char defaultFamilyName[]) {
|
||||
fDefaultFontManager = fontManager;
|
||||
fDefaultFontManager = std::move(fontManager);
|
||||
fDefaultFamilyName = defaultFamilyName;
|
||||
}
|
||||
|
||||
void FontCollection::setDefaultFontManager(sk_sp<SkFontMgr> fontManager) {
|
||||
fDefaultFontManager = fontManager;
|
||||
}
|
||||
|
||||
// Return the available font managers in the order they should be queried.
|
||||
std::vector<sk_sp<SkFontMgr>> FontCollection::getFontManagerOrder() const {
|
||||
std::vector<sk_sp<SkFontMgr>> order;
|
||||
@ -117,7 +120,7 @@ sk_sp<SkTypeface> FontCollection::matchDefaultTypeface(SkFontStyle fontStyle) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sk_sp<SkTypeface> FontCollection::defaultFallback(SkUnichar unicode, SkFontStyle fontStyle, SkString locale) {
|
||||
sk_sp<SkTypeface> FontCollection::defaultFallback(SkUnichar unicode, SkFontStyle fontStyle, const SkString& locale) {
|
||||
|
||||
for (const auto& manager : this->getFontManagerOrder()) {
|
||||
std::vector<const char*> bcp47;
|
||||
@ -131,6 +134,9 @@ sk_sp<SkTypeface> FontCollection::defaultFallback(SkUnichar unicode, SkFontStyle
|
||||
}
|
||||
}
|
||||
|
||||
if (fDefaultFontManager == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = fDefaultFontManager->matchFamilyStyle(fDefaultFamilyName.c_str(), fontStyle);
|
||||
return sk_ref_sp<SkTypeface>(result);
|
||||
}
|
||||
|
@ -22,41 +22,33 @@ SkUnichar utf8_next(const char** ptr, const char* end) {
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
bool FontResolver::findFirst(const char* codepoint, SkFont* font, SkScalar* height) {
|
||||
auto found = fFontMapping.find(codepoint);
|
||||
if (found == nullptr) {
|
||||
// Resolve the first character with the first found font
|
||||
found = fFontMapping.set(codepoint, fFirstResolvedFont);
|
||||
}
|
||||
if (found == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*font = found->fFont;
|
||||
*height = found->fHeight;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FontResolver::findNext(const char* codepoint, SkFont* font, SkScalar* height) {
|
||||
auto found = fFontMapping.find(codepoint);
|
||||
if (found == nullptr) {
|
||||
return false;
|
||||
|
||||
SkASSERT(fFontIterator != nullptr);
|
||||
TextIndex index = codepoint - fText.begin();
|
||||
while (fFontIterator != fFontSwitches.end() && fFontIterator->fStart <= index) {
|
||||
if (fFontIterator->fStart == index) {
|
||||
*font = fFontIterator->fFont;
|
||||
*height = fFontIterator->fHeight;
|
||||
return true;
|
||||
}
|
||||
++fFontIterator;
|
||||
}
|
||||
*font = found->fFont;
|
||||
*height = found->fHeight;
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void FontResolver::findAllFontsForStyledBlock(const TextStyle& style, SkSpan<const char> text) {
|
||||
void FontResolver::findAllFontsForStyledBlock(const TextStyle& style, TextRange textRange) {
|
||||
fCodepoints.reset();
|
||||
fCharacters.reset();
|
||||
fUnresolvedIndexes.reset();
|
||||
fUnresolvedCodepoints.reset();
|
||||
|
||||
// Extract all unicode codepoints
|
||||
const char* current = text.begin();
|
||||
while (current != text.end()) {
|
||||
const char* end = fText.begin() + textRange.end;
|
||||
const char* current = fText.begin() + textRange.start;
|
||||
while (current != end) {
|
||||
fCharacters.emplace_back(current);
|
||||
fCodepoints.emplace_back(utf8_next(¤t, text.end()));
|
||||
fCodepoints.emplace_back(utf8_next(¤t, end));
|
||||
fUnresolvedIndexes.emplace_back(fUnresolvedIndexes.size());
|
||||
}
|
||||
fUnresolved = fCodepoints.size();
|
||||
@ -107,7 +99,12 @@ void FontResolver::findAllFontsForStyledBlock(const TextStyle& style, SkSpan<con
|
||||
|
||||
// In case something still unresolved
|
||||
if (fResolvedFonts.count() == 0) {
|
||||
makeFont(fFontCollection->defaultFallback(firstUnresolved(), style.getFontStyle(), style.getLocale()),
|
||||
auto result = fFontCollection->defaultFallback(firstUnresolved(), style.getFontStyle(), style.getLocale());
|
||||
if (result == nullptr) {
|
||||
SkDebugf("No fallback!!!\n");
|
||||
return;
|
||||
}
|
||||
makeFont(result,
|
||||
style.getFontSize(),
|
||||
style.getHeight());
|
||||
if (fFirstResolvedFont.fFont.getTypeface() != nullptr) {
|
||||
@ -149,7 +146,8 @@ size_t FontResolver::resolveAllCharactersByFont(const FontDescr& font) {
|
||||
fUnresolvedCodepoints.emplace_back(fCodepoints[w]);
|
||||
}
|
||||
} else {
|
||||
fFontMapping.set(fCharacters[resolved.start], font);
|
||||
//SkDebugf("Resolved %d @%d\n", font.fFont.getTypeface()->uniqueID(), resolved.start);
|
||||
fFontMapping.set(fCharacters[resolved.start] - fText.begin(), font);
|
||||
}
|
||||
};
|
||||
|
||||
@ -200,14 +198,14 @@ void FontResolver::addResolvedWhitespacesToMapping() {
|
||||
auto index = fUnresolvedIndexes[i];
|
||||
auto found = fWhitespaces.find(index);
|
||||
if (found != nullptr) {
|
||||
fFontMapping.set(fCharacters[index], *found);
|
||||
fFontMapping.set(fCharacters[index] - fText.begin(), *found);
|
||||
++resolvedWhitespaces;
|
||||
}
|
||||
}
|
||||
fUnresolved -= resolvedWhitespaces;
|
||||
}
|
||||
|
||||
FontResolver::FontDescr FontResolver::makeFont(sk_sp<SkTypeface> typeface,
|
||||
FontDescr FontResolver::makeFont(sk_sp<SkTypeface> typeface,
|
||||
SkScalar size,
|
||||
SkScalar height) {
|
||||
SkFont font(typeface, size);
|
||||
@ -235,29 +233,66 @@ SkUnichar FontResolver::firstUnresolved() {
|
||||
}
|
||||
|
||||
void FontResolver::findAllFontsForAllStyledBlocks(SkSpan<const char> utf8,
|
||||
SkSpan<TextBlock> styles,
|
||||
SkSpan<Block> styles,
|
||||
sk_sp<FontCollection> fontCollection) {
|
||||
fFontCollection = fontCollection;
|
||||
fStyles = styles;
|
||||
fText = utf8;
|
||||
TextBlock combined;
|
||||
for (auto& block : fStyles) {
|
||||
SkASSERT(combined.text().begin() == nullptr ||
|
||||
combined.text().end() == block.text().begin());
|
||||
fTextRange = TextRange(0, utf8.size());
|
||||
|
||||
if (combined.text().begin() != nullptr &&
|
||||
block.style().matchOneAttribute(StyleType::kFont, combined.style())) {
|
||||
combined.add(block.text());
|
||||
Block combined;
|
||||
for (auto& block : fStyles) {
|
||||
SkASSERT(combined.fRange.empty() ||
|
||||
combined.fRange.end == block.fRange.start);
|
||||
|
||||
if (!combined.fRange.empty() &&
|
||||
block.fStyle.matchOneAttribute(StyleType::kFont, combined.fStyle)) {
|
||||
combined.add(block.fRange);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!combined.text().empty()) {
|
||||
this->findAllFontsForStyledBlock(combined.style(), combined.text());
|
||||
if (!combined.fRange.empty()) {
|
||||
this->findAllFontsForStyledBlock(combined.fStyle, combined.fRange);
|
||||
}
|
||||
|
||||
combined = block;
|
||||
}
|
||||
this->findAllFontsForStyledBlock(combined.style(), combined.text());
|
||||
this->findAllFontsForStyledBlock(combined.fStyle, combined.fRange);
|
||||
|
||||
|
||||
fFontSwitches.reset();
|
||||
FontDescr* prev = nullptr;
|
||||
for (auto& ch : utf8) {
|
||||
if (fFontSwitches.count() == fFontMapping.count()) {
|
||||
// Checked all
|
||||
break;
|
||||
}
|
||||
auto found = fFontMapping.find(&ch - fText.begin());
|
||||
if (found == nullptr) {
|
||||
// Insignificant character
|
||||
continue;
|
||||
}
|
||||
if (prev == nullptr) {
|
||||
prev = found;
|
||||
prev->fStart = 0;
|
||||
}
|
||||
|
||||
if (*prev == *found) {
|
||||
// Same font
|
||||
continue;
|
||||
}
|
||||
fFontSwitches.emplace_back(*prev);
|
||||
|
||||
prev = found;
|
||||
prev->fStart = &ch - fText.begin();
|
||||
}
|
||||
|
||||
if (prev == nullptr) {
|
||||
fFirstResolvedFont.fStart = 0;
|
||||
prev = &fFirstResolvedFont;
|
||||
}
|
||||
fFontSwitches.emplace_back(*prev);
|
||||
fFontIterator = fFontSwitches.begin();
|
||||
}
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
@ -15,30 +15,33 @@
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
struct FontDescr {
|
||||
FontDescr() {}
|
||||
FontDescr(SkFont font, SkScalar height)
|
||||
: fFont(font), fHeight(height), fStart(EMPTY_INDEX) {}
|
||||
bool operator==(const FontDescr& a) const {
|
||||
return this->fFont == a.fFont && this->fHeight == a.fHeight;
|
||||
}
|
||||
SkFont fFont;
|
||||
SkScalar fHeight;
|
||||
TextIndex fStart;
|
||||
};
|
||||
|
||||
class FontResolver {
|
||||
public:
|
||||
struct FontDescr {
|
||||
FontDescr() {}
|
||||
FontDescr(SkFont font, SkScalar height)
|
||||
: fFont(font), fHeight(height) {}
|
||||
bool operator==(const FontDescr& a) const {
|
||||
return this->fFont == a.fFont && this->fHeight == a.fHeight;
|
||||
}
|
||||
SkFont fFont;
|
||||
SkScalar fHeight;
|
||||
};
|
||||
|
||||
FontResolver() = default;
|
||||
~FontResolver() = default;
|
||||
|
||||
void findAllFontsForAllStyledBlocks(SkSpan<const char> utf8,
|
||||
SkSpan<TextBlock> styles,
|
||||
SkSpan<Block> styles,
|
||||
sk_sp<FontCollection> fontCollection);
|
||||
bool findFirst(const char* codepoint, SkFont* font, SkScalar* height);
|
||||
bool findNext(const char* codepoint, SkFont* font, SkScalar* height);
|
||||
|
||||
SkTArray<FontDescr>& switches() { return fFontSwitches; }
|
||||
|
||||
private:
|
||||
void findAllFontsForStyledBlock(const TextStyle& style, SkSpan<const char> text);
|
||||
void findAllFontsForStyledBlock(const TextStyle& style, TextRange textRange);
|
||||
FontDescr makeFont(sk_sp<SkTypeface> typeface, SkScalar size, SkScalar height);
|
||||
size_t resolveAllCharactersByFont(const FontDescr& fontDescr);
|
||||
void addResolvedWhitespacesToMapping();
|
||||
@ -55,12 +58,15 @@ private:
|
||||
|
||||
sk_sp<FontCollection> fFontCollection;
|
||||
SkSpan<const char> fText;
|
||||
SkSpan<TextBlock> fStyles;
|
||||
TextRange fTextRange;
|
||||
SkSpan<Block> fStyles;
|
||||
|
||||
SkTHashMap<const char*, FontDescr> fFontMapping;
|
||||
SkTArray<FontDescr> fFontSwitches;
|
||||
FontDescr* fFontIterator;
|
||||
SkTHashSet<FontDescr, Hash> fResolvedFonts;
|
||||
FontDescr fFirstResolvedFont;
|
||||
|
||||
SkTHashMap<TextIndex, FontDescr> fFontMapping;
|
||||
SkTArray<SkUnichar> fCodepoints;
|
||||
SkTArray<const char*> fCharacters;
|
||||
SkTArray<size_t> fUnresolvedIndexes;
|
||||
|
@ -24,7 +24,7 @@ public:
|
||||
void consume() override {
|
||||
SkASSERT(fCurrentChar < fText.end());
|
||||
SkString locale;
|
||||
auto found = fFontResolver->findFirst(fCurrentChar, &fFont, &fLineHeight);
|
||||
auto found = fFontResolver->findNext(fCurrentChar, &fFont, &fLineHeight);
|
||||
SkASSERT(found);
|
||||
|
||||
// Move until we find the first character that cannot be resolved with the current font
|
||||
@ -58,7 +58,7 @@ private:
|
||||
|
||||
class LangIterator final : public SkShaper::LanguageRunIterator {
|
||||
public:
|
||||
LangIterator(SkSpan<const char> utf8, SkSpan<TextBlock> styles, TextStyle defaultStyle)
|
||||
LangIterator(SkSpan<const char> utf8, SkSpan<Block> styles, const TextStyle& defaultStyle)
|
||||
: fText(utf8)
|
||||
, fTextStyles(styles)
|
||||
, fCurrentChar(utf8.begin())
|
||||
@ -73,13 +73,13 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
fCurrentChar = fCurrentStyle->text().end();
|
||||
fCurrentLocale = fCurrentStyle->style().getLocale();
|
||||
fCurrentChar = fText.begin() + fCurrentStyle->fRange.end;
|
||||
fCurrentLocale = fCurrentStyle->fStyle.getLocale();
|
||||
while (++fCurrentStyle != fTextStyles.end()) {
|
||||
if (fCurrentStyle->style().getLocale() != fCurrentLocale) {
|
||||
if (fCurrentStyle->fStyle.getLocale() != fCurrentLocale) {
|
||||
break;
|
||||
}
|
||||
fCurrentChar = fCurrentStyle->text().end();
|
||||
fCurrentChar = fText.begin() + fCurrentStyle->fRange.end;
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,9 +89,9 @@ public:
|
||||
|
||||
private:
|
||||
SkSpan<const char> fText;
|
||||
SkSpan<TextBlock> fTextStyles;
|
||||
SkSpan<Block> fTextStyles;
|
||||
const char* fCurrentChar;
|
||||
TextBlock* fCurrentStyle;
|
||||
Block* fCurrentStyle;
|
||||
SkString fCurrentLocale;
|
||||
};
|
||||
} // namespace textlayout
|
||||
|
@ -11,13 +11,13 @@ namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
std::unique_ptr<ParagraphBuilder> ParagraphBuilder::make(
|
||||
ParagraphStyle style, sk_sp<FontCollection> fontCollection) {
|
||||
const ParagraphStyle& style, sk_sp<FontCollection> fontCollection) {
|
||||
return skstd::make_unique<ParagraphBuilderImpl>(style, fontCollection);
|
||||
}
|
||||
|
||||
ParagraphBuilderImpl::ParagraphBuilderImpl(
|
||||
ParagraphStyle style, sk_sp<FontCollection> fontCollection)
|
||||
: ParagraphBuilder(style, fontCollection), fFontCollection(std::move(fontCollection)) {
|
||||
const ParagraphStyle& style, sk_sp<FontCollection> fontCollection)
|
||||
: ParagraphBuilder(style, fontCollection), fUtf8(), fFontCollection(std::move(fontCollection)) {
|
||||
this->setParagraphStyle(style);
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ void ParagraphBuilderImpl::pushStyle(const TextStyle& style) {
|
||||
this->endRunIfNeeded();
|
||||
|
||||
fTextStyles.push(style);
|
||||
if (!fStyledBlocks.empty() && fStyledBlocks.back().fEnd == fUtf8.size() &&
|
||||
if (!fStyledBlocks.empty() && fStyledBlocks.back().fRange.end == fUtf8.size() &&
|
||||
fStyledBlocks.back().fStyle == style) {
|
||||
// Just continue with the same style
|
||||
} else {
|
||||
@ -87,10 +87,10 @@ void ParagraphBuilderImpl::endRunIfNeeded() {
|
||||
}
|
||||
|
||||
auto& last = fStyledBlocks.back();
|
||||
if (last.fStart == fUtf8.size()) {
|
||||
if (last.fRange.start == fUtf8.size()) {
|
||||
fStyledBlocks.pop_back();
|
||||
} else {
|
||||
last.fEnd = fUtf8.size();
|
||||
last.fRange.end = fUtf8.size();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ namespace textlayout {
|
||||
|
||||
class ParagraphBuilderImpl : public ParagraphBuilder {
|
||||
public:
|
||||
ParagraphBuilderImpl(ParagraphStyle style, sk_sp<FontCollection> fontCollection);
|
||||
ParagraphBuilderImpl(const ParagraphStyle& style, sk_sp<FontCollection> fontCollection);
|
||||
|
||||
~ParagraphBuilderImpl() override;
|
||||
|
||||
@ -57,7 +57,7 @@ private:
|
||||
|
||||
SkString fUtf8;
|
||||
std::stack<TextStyle> fTextStyles;
|
||||
std::vector<Block> fStyledBlocks;
|
||||
SkTArray<Block, true> fStyledBlocks;
|
||||
sk_sp<FontCollection> fFontCollection;
|
||||
ParagraphStyle fParagraphStyle;
|
||||
};
|
||||
|
205
modules/skparagraph/src/ParagraphCache.cpp
Normal file
205
modules/skparagraph/src/ParagraphCache.cpp
Normal file
@ -0,0 +1,205 @@
|
||||
// Copyright 2019 Google LLC.
|
||||
#include "modules/skparagraph/src/ParagraphImpl.h"
|
||||
#include "modules/skparagraph/src/ParagraphCache.h"
|
||||
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
// Just the flutter input for now
|
||||
// TODO: We don't really need to copy anything...
|
||||
ParagraphCacheKey::ParagraphCacheKey(ParagraphImpl* paragraph)
|
||||
: fText(paragraph->fText)
|
||||
, fFontSwitches(paragraph->switches())
|
||||
, fTextStyles(paragraph->fTextStyles)
|
||||
, fParagraphStyle(paragraph->paragraphStyle()) { }
|
||||
|
||||
// TODO: copying clusters and runs for now (there are minor things changed on formatting)
|
||||
ParagraphCacheValue::ParagraphCacheValue(ParagraphImpl* paragraph)
|
||||
: fKey(ParagraphCacheKey(paragraph))
|
||||
, fInternalState(paragraph->state())
|
||||
, fRuns(paragraph->fRuns)
|
||||
, fClusters(paragraph->fClusters)
|
||||
, fMeasurement(paragraph->measurement())
|
||||
, fPicture(paragraph->fPicture) { }
|
||||
|
||||
bool ParagraphCache::findParagraph(ParagraphImpl* paragraph) {
|
||||
|
||||
SkAutoMutexExclusive lock(fParagraphMutex);
|
||||
if (this->count() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ParagraphCacheKey key(paragraph);
|
||||
auto found = this->find(key);
|
||||
if (found == nullptr) {
|
||||
fChecker(paragraph, "findParagraph", false);
|
||||
return false;
|
||||
}
|
||||
paragraph->fRuns.reset();
|
||||
paragraph->fRuns = found->fRuns;
|
||||
for (auto& run : paragraph->fRuns) {
|
||||
run.setMaster(paragraph);
|
||||
}
|
||||
|
||||
paragraph->fClusters.reset();
|
||||
paragraph->fClusters = found->fClusters;
|
||||
for (auto& cluster : paragraph->fClusters) {
|
||||
cluster.setMaster(paragraph);
|
||||
}
|
||||
|
||||
paragraph->fLines.reset();
|
||||
for (auto& line : found->fLines) {
|
||||
paragraph->fLines.push_back(line);
|
||||
paragraph->fLines.back().setMaster(paragraph);
|
||||
}
|
||||
|
||||
paragraph->fState = found->fInternalState;
|
||||
paragraph->setMeasurement(found->fMeasurement);
|
||||
|
||||
paragraph->fOldWidth = found->fMeasurement.fWidth;
|
||||
paragraph->fOldHeight = found->fMeasurement.fHeight;
|
||||
|
||||
paragraph->fPicture = found->fPicture;
|
||||
|
||||
fChecker(paragraph, "findParagraph", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ParagraphCache::addParagraph(ParagraphImpl* paragraph) {
|
||||
|
||||
SkAutoMutexExclusive lock(fParagraphMutex);
|
||||
auto value = new ParagraphCacheValue(paragraph);
|
||||
this->add(value);
|
||||
fChecker(paragraph, "addParagraph1", true);
|
||||
}
|
||||
|
||||
void ParagraphCache::updateParagraph(ParagraphImpl* paragraph) {
|
||||
|
||||
SkAutoMutexExclusive lock(fParagraphMutex);
|
||||
ParagraphCacheKey key(paragraph);
|
||||
auto found = this->find(key);
|
||||
if (found != nullptr) {
|
||||
found->fInternalState = paragraph->fState;
|
||||
found->fMeasurement = paragraph->measurement();
|
||||
found->fLines = paragraph->fLines;
|
||||
for (size_t i = 0; i < paragraph->fRuns.size(); ++i) {
|
||||
auto& run = paragraph->fRuns[i];
|
||||
if (run.fSpaced) {
|
||||
found->fRuns[i] = run;
|
||||
}
|
||||
}
|
||||
found->fPicture = paragraph->fPicture;
|
||||
fChecker(paragraph, "updateParagraph", true);
|
||||
} else {
|
||||
auto value = new ParagraphCacheValue(paragraph);
|
||||
this->add(value);
|
||||
fChecker(paragraph, "addParagraph2", true);
|
||||
}
|
||||
}
|
||||
|
||||
const ParagraphCacheKey& LookupTrait::GetKey(const ParagraphCacheValue& paragraph) {
|
||||
return paragraph.fKey;
|
||||
}
|
||||
|
||||
bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b) {
|
||||
if (a.fText.size() != b.fText.size()) {
|
||||
return false;
|
||||
}
|
||||
if (a.fFontSwitches.count() != b.fFontSwitches.count()) {
|
||||
return false;
|
||||
}
|
||||
if (a.fText != b.fText) {
|
||||
return false;
|
||||
}
|
||||
if (a.fTextStyles.size() != b.fTextStyles.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.fParagraphStyle.getMaxLines() != b.fParagraphStyle.getMaxLines()) {
|
||||
// TODO: this is too strong, but at least we will not lose lines
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < a.fFontSwitches.size(); ++i) {
|
||||
auto& fda = a.fFontSwitches[i];
|
||||
auto& fdb = b.fFontSwitches[i];
|
||||
if (fda.fStart != fdb.fStart) {
|
||||
return false;
|
||||
}
|
||||
if (fda.fFont != fdb.fFont) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < a.fTextStyles.size(); ++i) {
|
||||
auto& tsa = a.fTextStyles[i];
|
||||
auto& tsb = b.fTextStyles[i];
|
||||
if (tsa.fStyle.getLetterSpacing() != tsb.fStyle.getLetterSpacing()) {
|
||||
return false;
|
||||
}
|
||||
if (tsa.fStyle.getWordSpacing() != tsb.fStyle.getWordSpacing()) {
|
||||
return false;
|
||||
}
|
||||
if (tsa.fRange.width() != tsb.fRange.width()) {
|
||||
return false;
|
||||
}
|
||||
if (tsa.fRange.start != tsb.fRange.start) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t LookupTrait::mix(uint32_t hash, uint32_t data) {
|
||||
hash += data;
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint32_t LookupTrait::Hash(const ParagraphCacheKey& key) {
|
||||
uint32_t hash = 0;
|
||||
for (auto& fd : key.fFontSwitches) {
|
||||
hash = mix(hash, SkGoodHash()(fd.fStart));
|
||||
hash = mix(hash, SkGoodHash()(fd.fFont.getSize()));
|
||||
|
||||
if (fd.fFont.getTypeface() != nullptr) {
|
||||
SkString name;
|
||||
fd.fFont.getTypeface()->getFamilyName(&name);
|
||||
hash = mix(hash, SkGoodHash()(name));
|
||||
hash = mix(hash, SkGoodHash()(fd.fFont.getTypeface()->fontStyle()));
|
||||
}
|
||||
}
|
||||
for (auto& ts : key.fTextStyles) {
|
||||
hash = mix(hash, SkGoodHash()(ts.fStyle.getLetterSpacing()));
|
||||
hash = mix(hash, SkGoodHash()(ts.fStyle.getWordSpacing()));
|
||||
}
|
||||
hash = mix(hash, SkGoodHash()(key.fText));
|
||||
return hash;
|
||||
}
|
||||
|
||||
void ParagraphCache::printCache(const char* title) {
|
||||
|
||||
SkDebugf("\n\n%s\n", title);
|
||||
SkTDynamicHash<ParagraphCacheValue, ParagraphCacheKey, LookupTrait>::Iter iter(this);
|
||||
while (!iter.done()) {
|
||||
ParagraphCacheValue* v = &*iter;
|
||||
const ParagraphCacheKey& k = LookupTrait::GetKey(*v);
|
||||
SkDebugf("key: '%s' runs(%d) clusters(%d)\n", k.fText.c_str(), v->fRuns.size(), v->fClusters.size());
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
void ParagraphCache::printKeyValue(const char* title, ParagraphImpl* paragraph, bool found) {
|
||||
/*
|
||||
SkDebugf("%s '%s' ", title, paragraph->text().data());
|
||||
for (auto& fd : paragraph->switches()) {
|
||||
SkDebugf("%d ", fd.fFont.getTypeface() != nullptr ? fd.fFont.getTypeface()->uniqueID(): 0);
|
||||
};
|
||||
SkDebugf("\n");
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -2,94 +2,95 @@
|
||||
#ifndef ParagraphCache_DEFINED
|
||||
#define ParagraphCache_DEFINED
|
||||
|
||||
#include <src/core/SkSpan.h>
|
||||
#include <src/core/SkTDynamicHash.h>
|
||||
#include "src/core/SkLRUCache.h"
|
||||
#include "include/core/SkFont.h"
|
||||
#include "include/private/SkTHash.h"
|
||||
#include "modules/skparagraph/include/ParagraphStyle.h"
|
||||
#include "modules/skparagraph/src/FontResolver.h"
|
||||
#include "include/core/SkPicture.h"
|
||||
|
||||
// TODO: we can cache shaped results or line broken results or formatted results or recorded picture...
|
||||
// TODO: let's start from the first: the shaped results
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
// Just the flutter input for now
|
||||
struct Measurement {
|
||||
SkScalar fAlphabeticBaseline;
|
||||
SkScalar fIdeographicBaseline;
|
||||
SkScalar fHeight;
|
||||
SkScalar fWidth;
|
||||
SkScalar fMaxIntrinsicWidth;
|
||||
SkScalar fMinIntrinsicWidth;
|
||||
};
|
||||
|
||||
enum InternalState {
|
||||
kUnknown = 0,
|
||||
kShaped = 1,
|
||||
kClusterized = 2,
|
||||
kMarked = 3,
|
||||
kLineBroken = 4,
|
||||
kFormatted = 5,
|
||||
kDrawn = 6
|
||||
};
|
||||
|
||||
class ParagraphImpl;
|
||||
class ParagraphCacheKey {
|
||||
public:
|
||||
ParagraphCacheKey(SkParagraphStyle paraStyle,
|
||||
SkTHashMap<const char*, std::pair<SkFont, SkScalar>> mapping,
|
||||
SkSpan<const char> utf8)
|
||||
: fHash(0) {
|
||||
fHash = mix(fHash, paraStyle.computeHash());
|
||||
fHash = mix(fHash, computeHash(mapping));
|
||||
fHash = mix(fHash, computeHash(utf8));
|
||||
}
|
||||
ParagraphCacheKey(ParagraphImpl* paragraph);
|
||||
|
||||
uint32_t hash() const { return fHash; }
|
||||
|
||||
private:
|
||||
uint32 computeHash(SkTHashMap<const char*, std::pair<SkFont, SkScalar>> mapping) {
|
||||
uint32 hash = 0;
|
||||
mapping.forEach([&hash](const char* ch, std::pair<SkFont, SkScalar> font) {
|
||||
hash = mix(hash, t.computeHash());
|
||||
});
|
||||
for (auto& t : array) {
|
||||
hash = mix(hash, ch);
|
||||
hash = mix(hash, SkGoodHash(font.first));
|
||||
hash = mix(hash, SkGoodHash(font.second));
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint32 computeHash(SkSpan<const char> text) {}
|
||||
|
||||
uint32 computeHash(SkSpan<const char> text) {
|
||||
uint32 hash = mix(0, text.size());
|
||||
for (uint32 i = 0; i < text.size(); i += 2) {
|
||||
uint32 data = text[i] | text[i + 1] << 16;
|
||||
hash = mix(hash, data);
|
||||
}
|
||||
if (text.size() & 1) {
|
||||
uint32 data = text.back();
|
||||
hash = mix(hash, data);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint32 mix(uint32 hash, uint32 data) {
|
||||
hash += data;
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint32 fHash;
|
||||
SkString fText;
|
||||
SkTArray<FontDescr> fFontSwitches;
|
||||
// TODO: Do not check for more than we have to (which is text styles with letter spacing and word spacing)
|
||||
SkTArray<Block, true> fTextStyles;
|
||||
ParagraphStyle fParagraphStyle;
|
||||
};
|
||||
|
||||
class ParagraphCacheValue {
|
||||
public:
|
||||
ParagraphCacheValue(std::shared<SkParagraph> paragraph,
|
||||
SkTHashMap<const char*,
|
||||
std::pair<SkFont, SkScalar>> mapping)
|
||||
: fKey(ParagraphCacheKey(paragraph.getParagraphStyle(), mapping, paragraph.getText()))
|
||||
, fFontCollection(collection)
|
||||
, fParagraphStyle(paraStyle)
|
||||
, fTextStyles(textStyles)
|
||||
, fUtf8(utf8) {}
|
||||
public:;
|
||||
ParagraphCacheValue(ParagraphImpl* paragraph);
|
||||
|
||||
static const ParagraphCacheKey& GetKey(const ParagraphCacheValue& value) { return fKey; }
|
||||
static uint32_t Hash(const ParagraphCacheKey& key) { return fKey.hash(); }
|
||||
|
||||
private:
|
||||
// Input == key
|
||||
ParagraphCacheKey fKey;
|
||||
|
||||
std::shared<SkParagraph> fParagraph;
|
||||
std::pair<SkFont, SkScalar>>fMapping;
|
||||
// Shaped results:
|
||||
InternalState fInternalState;
|
||||
SkTArray<Run> fRuns;
|
||||
SkTArray<Cluster, true> fClusters;
|
||||
SkTArray<TextLine, true> fLines;
|
||||
Measurement fMeasurement;
|
||||
sk_sp<SkPicture> fPicture;
|
||||
};
|
||||
|
||||
class ParagraphCache : public SkTDynamicHash<ParagraphCacheValue, ParagraphCacheKey> {
|
||||
bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b);
|
||||
|
||||
struct LookupTrait {
|
||||
static const ParagraphCacheKey& GetKey(const ParagraphCacheValue& paragraph);
|
||||
static uint32_t Hash(const ParagraphCacheKey& key);
|
||||
static uint32_t mix(uint32_t hash, uint32_t data);
|
||||
};
|
||||
|
||||
// TODO: can we cache the key by the text?... So we do not need to do font resolution again
|
||||
class ParagraphCache : public SkTDynamicHash<ParagraphCacheValue, ParagraphCacheKey, LookupTrait> {
|
||||
public:
|
||||
Hash() : INHERITED() {}
|
||||
|
||||
// Promote protected methods to public for this test.
|
||||
int capacity() const { return this->INHERITED::capacity(); }
|
||||
int countCollisions(const int& key) const { return this->INHERITED::countCollisions(key); }
|
||||
ParagraphCache() : fChecker([](ParagraphImpl* impl, const char*, bool){ }){ }
|
||||
bool findParagraph(ParagraphImpl* paragraph);
|
||||
void addParagraph(ParagraphImpl* paragraph);
|
||||
void updateParagraph(ParagraphImpl* paragraph);
|
||||
|
||||
private:
|
||||
typedef SkTDynamicHash<ParagraphCacheValue, ParagraphCacheKey> INHERITED;
|
||||
// For testing
|
||||
|
||||
void setChecker(std::function<void(ParagraphImpl* impl, const char*, bool)> checker) { fChecker = checker; }
|
||||
void printCache(const char* title);
|
||||
void printKeyValue(const char* title, ParagraphImpl* paragraph, bool found);
|
||||
|
||||
private:
|
||||
mutable SkMutex fParagraphMutex;
|
||||
std::function<void(ParagraphImpl* impl, const char*, bool)> fChecker;
|
||||
};
|
||||
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
||||
|
@ -15,12 +15,6 @@
|
||||
|
||||
namespace {
|
||||
|
||||
SkSpan<const char> operator*(const SkSpan<const char>& a, const SkSpan<const char>& b) {
|
||||
auto begin = SkTMax(a.begin(), b.begin());
|
||||
auto end = SkTMin(a.end(), b.end());
|
||||
return SkSpan<const char>(begin, end > begin ? end - begin : 0);
|
||||
}
|
||||
|
||||
SkUnichar utf8_next(const char** ptr, const char* end) {
|
||||
SkUnichar val = SkUTF::NextUTF8(ptr, end);
|
||||
return val < 0 ? 0xFFFD : val;
|
||||
@ -85,62 +79,63 @@ private:
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
TextRange operator*(const TextRange& a, const TextRange& b) {
|
||||
auto begin = SkTMax(a.start, b.start);
|
||||
auto end = SkTMin(a.end, b.end);
|
||||
return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
|
||||
}
|
||||
|
||||
ParagraphCache ParagraphImpl::fParagraphCache;
|
||||
|
||||
ParagraphImpl::ParagraphImpl(const SkString& text,
|
||||
ParagraphStyle style,
|
||||
std::vector<Block> blocks,
|
||||
SkTArray<Block, true> blocks,
|
||||
sk_sp<FontCollection> fonts)
|
||||
: Paragraph(std::move(style), std::move(fonts))
|
||||
, fTextStyles(std::move(blocks))
|
||||
, fText(text)
|
||||
, fTextSpan(fText.c_str(), fText.size())
|
||||
, fDirtyLayout(true)
|
||||
, fState(kUnknown)
|
||||
, fPicture(nullptr)
|
||||
, fOldWidth(0)
|
||||
, fPicture(nullptr) {
|
||||
fTextStyles.reserve(blocks.size());
|
||||
for (auto& block : blocks) {
|
||||
fTextStyles.emplace_back(
|
||||
SkSpan<const char>(fTextSpan.begin() + block.fStart, block.fEnd - block.fStart),
|
||||
block.fStyle);
|
||||
}
|
||||
, fOldHeight(0)
|
||||
, fParagraphCacheOn(false) {
|
||||
// TODO: extractStyles();
|
||||
}
|
||||
|
||||
ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
|
||||
ParagraphStyle style,
|
||||
std::vector<Block> blocks,
|
||||
SkTArray<Block, true> blocks,
|
||||
sk_sp<FontCollection> fonts)
|
||||
: Paragraph(std::move(style)
|
||||
, std::move(fonts))
|
||||
, fDirtyLayout(true)
|
||||
: Paragraph(std::move(style), std::move(fonts))
|
||||
, fTextStyles(std::move(blocks))
|
||||
, fState(kUnknown)
|
||||
, fPicture(nullptr)
|
||||
, fOldWidth(0)
|
||||
, fPicture(nullptr) {
|
||||
, fOldHeight(0)
|
||||
, fParagraphCacheOn(false) {
|
||||
icu::UnicodeString unicode((UChar*)utf16text.data(), SkToS32(utf16text.size()));
|
||||
std::string str;
|
||||
unicode.toUTF8String(str);
|
||||
fText = SkString(str.data(), str.size());
|
||||
fTextSpan = SkSpan<const char>(fText.c_str(), fText.size());
|
||||
|
||||
fTextStyles.reserve(blocks.size());
|
||||
for (auto& block : blocks) {
|
||||
fTextStyles.emplace_back(
|
||||
SkSpan<const char>(fTextSpan.begin() + block.fStart, block.fEnd - block.fStart),
|
||||
block.fStyle);
|
||||
}
|
||||
// TODO: extractStyles();
|
||||
}
|
||||
|
||||
ParagraphImpl::~ParagraphImpl() = default;
|
||||
|
||||
void ParagraphImpl::layout(SkScalar width) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
|
||||
if (fDirtyLayout) {
|
||||
if (fState < kShaped) {
|
||||
// Layout marked as dirty for performance/testing reasons
|
||||
this->fRuns.reset();
|
||||
this->fClusters.reset();
|
||||
this->fLines.reset();
|
||||
this->fPicture = nullptr;
|
||||
} else if (fOldWidth != width) {
|
||||
this->fLines.reset();
|
||||
} else if (fState >= kLineBroken && (fOldWidth != width || fOldHeight != fHeight)) {
|
||||
// We can use the results from SkShaper but have to break lines again
|
||||
fState = kShaped;
|
||||
}
|
||||
|
||||
if (fRuns.empty()) {
|
||||
if (fState < kShaped) {
|
||||
fClusters.reset();
|
||||
|
||||
if (!this->shapeTextIntoEndlessLine()) {
|
||||
@ -153,28 +148,296 @@ void ParagraphImpl::layout(SkScalar width) {
|
||||
fHeight = lineMetrics.height();
|
||||
fAlphabeticBaseline = lineMetrics.alphabeticBaseline();
|
||||
fIdeographicBaseline = lineMetrics.ideographicBaseline();
|
||||
}
|
||||
if (fState < kShaped) {
|
||||
fState = kShaped;
|
||||
} else {
|
||||
layout(width);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fState < kMarked) {
|
||||
this->buildClusterTable();
|
||||
fState = kClusterized;
|
||||
this->markLineBreaks();
|
||||
fState = kMarked;
|
||||
// Add the paragraph to the cache
|
||||
if (fParagraphCacheOn) {
|
||||
fParagraphCache.updateParagraph(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fClusters.empty()) {
|
||||
this->buildClusterTable();
|
||||
this->fLines.reset();
|
||||
if (fState >= kLineBroken) {
|
||||
if (fOldWidth != width || fOldHeight != fHeight) {
|
||||
fState = kMarked;
|
||||
}
|
||||
}
|
||||
|
||||
if (fLines.empty()) {
|
||||
this->fPicture = nullptr;
|
||||
if (fState < kLineBroken) {
|
||||
this->resetContext();
|
||||
this->resolveStrut();
|
||||
this->fLines.reset();
|
||||
this->breakShapedTextIntoLines(width);
|
||||
fState = kLineBroken;
|
||||
|
||||
}
|
||||
|
||||
if (fState < kFormatted) {
|
||||
// Build the picture lazily not until we actually have to paint (or never)
|
||||
this->formatLines(fWidth);
|
||||
fState = kFormatted;
|
||||
// Add the paragraph to the cache
|
||||
if (fParagraphCacheOn) {
|
||||
fParagraphCache.updateParagraph(this);
|
||||
}
|
||||
}
|
||||
|
||||
this->fOldWidth = width;
|
||||
this->fDirtyLayout = false;
|
||||
this->fOldHeight = this->fHeight;
|
||||
}
|
||||
|
||||
void ParagraphImpl::paint(SkCanvas* canvas, SkScalar x, SkScalar y) {
|
||||
|
||||
if (fState < kDrawn) {
|
||||
// Record the picture anyway (but if we have some pieces in the cache they will be used)
|
||||
this->paintLinesIntoPicture();
|
||||
fState = kDrawn;
|
||||
if (fParagraphCacheOn) {
|
||||
fParagraphCache.updateParagraph(this);
|
||||
}
|
||||
}
|
||||
|
||||
SkMatrix matrix = SkMatrix::MakeTrans(x, y);
|
||||
canvas->drawPicture(fPicture, &matrix, nullptr);
|
||||
}
|
||||
|
||||
void ParagraphImpl::resetContext() {
|
||||
fAlphabeticBaseline = 0;
|
||||
fHeight = 0;
|
||||
fWidth = 0;
|
||||
fIdeographicBaseline = 0;
|
||||
fMaxIntrinsicWidth = 0;
|
||||
fMinIntrinsicWidth = 0;
|
||||
}
|
||||
|
||||
// Clusters in the order of the input text
|
||||
void ParagraphImpl::buildClusterTable() {
|
||||
|
||||
// Walk through all the run in the direction of input text
|
||||
for (RunIndex runIndex = 0; runIndex < fRuns.size(); ++runIndex) {
|
||||
auto& run = fRuns[runIndex];
|
||||
auto runStart = fClusters.size();
|
||||
fClusters.reserve(fClusters.size() + fRuns.size());
|
||||
// Walk through the glyph in the direction of input text
|
||||
run.iterateThroughClustersInTextOrder([runIndex, this](
|
||||
size_t glyphStart,
|
||||
size_t glyphEnd,
|
||||
size_t charStart,
|
||||
size_t charEnd,
|
||||
SkScalar width,
|
||||
SkScalar height) {
|
||||
SkASSERT(charEnd >= charStart);
|
||||
SkSpan<const char> text(fTextSpan.begin() + charStart, charEnd - charStart);
|
||||
|
||||
auto& cluster = fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text, width, height);
|
||||
cluster.setIsWhiteSpaces();
|
||||
});
|
||||
|
||||
run.setClusterRange(runStart, fClusters.size());
|
||||
fMaxIntrinsicWidth += run.advance().fX;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: we need soft line breaks before for word spacing
|
||||
void ParagraphImpl::markLineBreaks() {
|
||||
|
||||
// Find all possible (soft) line breaks
|
||||
TextBreaker breaker;
|
||||
if (!breaker.initialize(fTextSpan, UBRK_LINE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Cluster* current = fClusters.begin();
|
||||
while (!breaker.eof() && current < fClusters.end()) {
|
||||
size_t currentPos = breaker.next();
|
||||
while (current < fClusters.end()) {
|
||||
if (current->textRange().end > currentPos) {
|
||||
break;
|
||||
} else if (current->textRange().end == currentPos) {
|
||||
current->setBreakType(breaker.status() == UBRK_LINE_HARD
|
||||
? Cluster::BreakType::HardLineBreak
|
||||
: Cluster::BreakType::SoftLineBreak);
|
||||
++current;
|
||||
break;
|
||||
}
|
||||
++current;
|
||||
}
|
||||
}
|
||||
|
||||
// Walk through all the clusters in the direction of input text
|
||||
Block* currentStyle = this->fTextStyles.begin();
|
||||
SkScalar shift = 0;
|
||||
for (auto& cluster : fClusters) {
|
||||
auto run = cluster.run();
|
||||
|
||||
// Shift the cluster
|
||||
run->shift(&cluster, shift);
|
||||
|
||||
// Synchronize styles (one cluster can be covered by few styles)
|
||||
while (!cluster.startsIn(currentStyle->fRange)) {
|
||||
currentStyle++;
|
||||
SkASSERT(currentStyle != this->fTextStyles.end());
|
||||
}
|
||||
|
||||
// Take spacing styles in account
|
||||
if (currentStyle->fStyle.getWordSpacing() != 0 &&
|
||||
fParagraphStyle.getTextAlign() != TextAlign::kJustify) {
|
||||
if (cluster.isWhitespaces() && cluster.isSoftBreak()) {
|
||||
shift += run->addSpacesAtTheEnd(currentStyle->fStyle.getWordSpacing(), &cluster);
|
||||
}
|
||||
}
|
||||
if (currentStyle->fStyle.getLetterSpacing() != 0) {
|
||||
shift += run->addSpacesEvenly(currentStyle->fStyle.getLetterSpacing(), &cluster);
|
||||
}
|
||||
}
|
||||
|
||||
fClusters.emplace_back(this, EMPTY_RUN, 0, 0, SkSpan<const char>(), 0, 0);
|
||||
}
|
||||
|
||||
bool ParagraphImpl::shapeTextIntoEndlessLine() {
|
||||
|
||||
class ShapeHandler final : public SkShaper::RunHandler {
|
||||
public:
|
||||
explicit ShapeHandler(ParagraphImpl& paragraph, FontIterator* fontIterator)
|
||||
: fParagraph(¶graph)
|
||||
, fFontIterator(fontIterator)
|
||||
, fAdvance(SkVector::Make(0, 0)) {}
|
||||
|
||||
SkVector advance() const { return fAdvance; }
|
||||
|
||||
private:
|
||||
void beginLine() override {}
|
||||
|
||||
void runInfo(const RunInfo&) override {}
|
||||
|
||||
void commitRunInfo() override {}
|
||||
|
||||
Buffer runBuffer(const RunInfo& info) override {
|
||||
auto& run = fParagraph->fRuns.emplace_back(fParagraph,
|
||||
info,
|
||||
fFontIterator->currentLineHeight(),
|
||||
fParagraph->fRuns.count(),
|
||||
fAdvance.fX);
|
||||
return run.newRunBuffer();
|
||||
}
|
||||
|
||||
void commitRunBuffer(const RunInfo&) override {
|
||||
auto& run = fParagraph->fRuns.back();
|
||||
if (run.size() == 0) {
|
||||
fParagraph->fRuns.pop_back();
|
||||
return;
|
||||
}
|
||||
// Carve out the line text out of the entire run text
|
||||
fAdvance.fX += run.advance().fX;
|
||||
fAdvance.fY = SkMaxScalar(fAdvance.fY, run.descent() - run.ascent());
|
||||
}
|
||||
|
||||
void commitLine() override {}
|
||||
|
||||
ParagraphImpl* fParagraph;
|
||||
FontIterator* fFontIterator;
|
||||
SkVector fAdvance;
|
||||
};
|
||||
|
||||
if (fTextSpan.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is a pretty big step - resolving all characters against all given fonts
|
||||
fFontResolver.findAllFontsForAllStyledBlocks(fTextSpan, styles(), fFontCollection);
|
||||
|
||||
// Check the font-resolved text against the cache
|
||||
if (fParagraphCacheOn && fParagraphCache.findParagraph(this)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LangIterator lang(fTextSpan, styles(), paragraphStyle().getTextStyle());
|
||||
FontIterator font(fTextSpan, &fFontResolver);
|
||||
ShapeHandler handler(*this, &font);
|
||||
std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder();
|
||||
SkASSERT_RELEASE(shaper != nullptr);
|
||||
auto bidi = SkShaper::MakeIcuBiDiRunIterator(
|
||||
fTextSpan.begin(), fTextSpan.size(),
|
||||
fParagraphStyle.getTextDirection() == TextDirection::kLtr ? (uint8_t)2 : (uint8_t)1);
|
||||
if (bidi == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto script = SkShaper::MakeHbIcuScriptRunIterator(fTextSpan.begin(), fTextSpan.size());
|
||||
|
||||
shaper->shape(fTextSpan.begin(), fTextSpan.size(), font, *bidi, *script, lang,
|
||||
std::numeric_limits<SkScalar>::max(), &handler);
|
||||
|
||||
if (fParagraphStyle.getTextAlign() == TextAlign::kJustify) {
|
||||
fRunShifts.reset();
|
||||
fRunShifts.push_back_n(fRuns.size(), RunShifts());
|
||||
for (size_t i = 0; i < fRuns.size(); ++i) {
|
||||
fRunShifts[i].fShifts.push_back_n(fRuns[i].size() + 1, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
|
||||
|
||||
TextWrapper textWrapper;
|
||||
textWrapper.breakTextIntoLines(
|
||||
this,
|
||||
maxWidth,
|
||||
[&](TextRange text,
|
||||
TextRange textWithSpaces,
|
||||
ClusterRange clusters,
|
||||
size_t startPos,
|
||||
size_t endPos,
|
||||
SkVector offset,
|
||||
SkVector advance,
|
||||
LineMetrics metrics,
|
||||
bool addEllipsis) {
|
||||
// Add the line
|
||||
// TODO: Take in account clipped edges
|
||||
auto& line = this->addLine(offset, advance, text, textWithSpaces, clusters, metrics);
|
||||
if (addEllipsis) {
|
||||
line.createEllipsis(maxWidth, fParagraphStyle.getEllipsis(), true);
|
||||
}
|
||||
});
|
||||
fHeight = textWrapper.height();
|
||||
fWidth = maxWidth; // fTextWrapper.width();
|
||||
fMinIntrinsicWidth = textWrapper.minIntrinsicWidth();
|
||||
fMaxIntrinsicWidth = textWrapper.maxIntrinsicWidth();
|
||||
fAlphabeticBaseline = fLines.empty() ? 0 : fLines.front().alphabeticBaseline();
|
||||
fIdeographicBaseline = fLines.empty() ? 0 : fLines.front().ideographicBaseline();
|
||||
}
|
||||
|
||||
void ParagraphImpl::formatLines(SkScalar maxWidth) {
|
||||
auto effectiveAlign = fParagraphStyle.effective_align();
|
||||
for (auto& line : fLines) {
|
||||
line.format(effectiveAlign, maxWidth, &line != &fLines.back());
|
||||
}
|
||||
}
|
||||
|
||||
void ParagraphImpl::paintLinesIntoPicture() {
|
||||
SkPictureRecorder recorder;
|
||||
SkCanvas* textCanvas = recorder.beginRecording(fWidth, fHeight, nullptr, 0);
|
||||
|
||||
for (auto& line : fLines) {
|
||||
line.paint(textCanvas);
|
||||
}
|
||||
|
||||
fPicture = recorder.finishRecordingAsPicture();
|
||||
}
|
||||
|
||||
void ParagraphImpl::resolveStrut() {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
auto strutStyle = this->paragraphStyle().getStrutStyle();
|
||||
if (!strutStyle.getStrutEnabled()) {
|
||||
return;
|
||||
@ -202,270 +465,36 @@ void ParagraphImpl::resolveStrut() {
|
||||
: strutStyle.getLeading() * strutStyle.getFontSize());
|
||||
}
|
||||
|
||||
void ParagraphImpl::paint(SkCanvas* canvas, SkScalar x, SkScalar y) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
if (nullptr == fPicture) {
|
||||
// Build the picture lazily not until we actually have to paint (or never)
|
||||
this->formatLines(fWidth);
|
||||
this->paintLinesIntoPicture();
|
||||
}
|
||||
|
||||
SkMatrix matrix = SkMatrix::MakeTrans(x, y);
|
||||
canvas->drawPicture(fPicture, &matrix, nullptr);
|
||||
}
|
||||
|
||||
void ParagraphImpl::resetContext() {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
fAlphabeticBaseline = 0;
|
||||
fHeight = 0;
|
||||
fWidth = 0;
|
||||
fIdeographicBaseline = 0;
|
||||
fMaxIntrinsicWidth = 0;
|
||||
fMinIntrinsicWidth = 0;
|
||||
fMaxLineWidth = 0;
|
||||
}
|
||||
|
||||
// Clusters in the order of the input text
|
||||
void ParagraphImpl::buildClusterTable() {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
// Find all possible (soft) line breaks
|
||||
TextBreaker breaker;
|
||||
if (!breaker.initialize(fTextSpan, UBRK_LINE)) {
|
||||
return;
|
||||
}
|
||||
size_t currentPos = breaker.first();
|
||||
SkTHashMap<const char*, bool> softLineBreaks;
|
||||
while (!breaker.eof()) {
|
||||
currentPos = breaker.next();
|
||||
const char* ch = currentPos + fTextSpan.begin();
|
||||
softLineBreaks.set(ch, breaker.status() == UBRK_LINE_HARD);
|
||||
}
|
||||
|
||||
TextBlock* currentStyle = this->fTextStyles.begin();
|
||||
SkScalar shift = 0;
|
||||
// Cannot set SkSpan<SkCluster> until the array is done - can be moved around
|
||||
std::vector<std::tuple<Run*, size_t, size_t>> toUpdate;
|
||||
|
||||
// Walk through all the run in the direction of input text
|
||||
for (auto& run : fRuns) {
|
||||
auto runStart = fClusters.size();
|
||||
// Walk through the glyph in the direction of input text
|
||||
run.iterateThroughClustersInTextOrder([&run, this, &softLineBreaks, ¤tStyle, &shift](
|
||||
size_t glyphStart,
|
||||
size_t glyphEnd,
|
||||
size_t charStart,
|
||||
size_t charEnd,
|
||||
SkScalar width,
|
||||
SkScalar height) {
|
||||
SkASSERT(charEnd >= charStart);
|
||||
SkSpan<const char> text(fTextSpan.begin() + charStart, charEnd - charStart);
|
||||
|
||||
auto& cluster = fClusters.emplace_back(&run, glyphStart, glyphEnd, text, width, height);
|
||||
|
||||
// Mark the line breaks
|
||||
auto found = softLineBreaks.find(cluster.text().end());
|
||||
if (found) {
|
||||
cluster.setBreakType(*found ? Cluster::BreakType::HardLineBreak
|
||||
: Cluster::BreakType::SoftLineBreak);
|
||||
}
|
||||
cluster.setIsWhiteSpaces();
|
||||
|
||||
// Shift the cluster
|
||||
run.shift(&cluster, shift);
|
||||
|
||||
// Synchronize styles (one cluster can be covered by few styles)
|
||||
while (!cluster.startsIn(currentStyle->text())) {
|
||||
currentStyle++;
|
||||
SkASSERT(currentStyle != this->fTextStyles.end());
|
||||
}
|
||||
|
||||
// Take spacing styles in account
|
||||
if (currentStyle->style().getWordSpacing() != 0 &&
|
||||
fParagraphStyle.getTextAlign() != TextAlign::kJustify) {
|
||||
if (cluster.isWhitespaces() && cluster.isSoftBreak()) {
|
||||
shift +=
|
||||
run.addSpacesAtTheEnd(currentStyle->style().getWordSpacing(), &cluster);
|
||||
}
|
||||
}
|
||||
if (currentStyle->style().getLetterSpacing() != 0) {
|
||||
shift += run.addSpacesEvenly(currentStyle->style().getLetterSpacing(), &cluster);
|
||||
}
|
||||
});
|
||||
|
||||
toUpdate.emplace_back(&run, runStart, fClusters.size() - runStart);
|
||||
fMaxIntrinsicWidth += run.advance().fX;
|
||||
}
|
||||
fClusters.emplace_back(nullptr, 0, 0, SkSpan<const char>(), 0, 0);
|
||||
|
||||
// Set SkSpan<SkCluster> ranges for all the runs
|
||||
for (auto update : toUpdate) {
|
||||
auto run = std::get<0>(update);
|
||||
auto start = std::get<1>(update);
|
||||
auto size = std::get<2>(update);
|
||||
run->setClusters(SkSpan<Cluster>(&fClusters[start], size));
|
||||
}
|
||||
}
|
||||
|
||||
bool ParagraphImpl::shapeTextIntoEndlessLine() {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
class ShapeHandler final : public SkShaper::RunHandler {
|
||||
public:
|
||||
explicit ShapeHandler(ParagraphImpl& paragraph, FontIterator* fontIterator)
|
||||
: fParagraph(¶graph)
|
||||
, fFontIterator(fontIterator)
|
||||
, fAdvance(SkVector::Make(0, 0)) {}
|
||||
|
||||
SkVector advance() const { return fAdvance; }
|
||||
|
||||
private:
|
||||
void beginLine() override {}
|
||||
|
||||
void runInfo(const RunInfo&) override {}
|
||||
|
||||
void commitRunInfo() override {}
|
||||
|
||||
Buffer runBuffer(const RunInfo& info) override {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
auto& run = fParagraph->fRuns.emplace_back(fParagraph->text(),
|
||||
info,
|
||||
fFontIterator->currentLineHeight(),
|
||||
fParagraph->fRuns.count(),
|
||||
fAdvance.fX);
|
||||
return run.newRunBuffer();
|
||||
}
|
||||
|
||||
void commitRunBuffer(const RunInfo&) override {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
auto& run = fParagraph->fRuns.back();
|
||||
if (run.size() == 0) {
|
||||
fParagraph->fRuns.pop_back();
|
||||
return;
|
||||
}
|
||||
// Carve out the line text out of the entire run text
|
||||
fAdvance.fX += run.advance().fX;
|
||||
fAdvance.fY = SkMaxScalar(fAdvance.fY, run.descent() - run.ascent());
|
||||
}
|
||||
|
||||
void commitLine() override {}
|
||||
|
||||
ParagraphImpl* fParagraph;
|
||||
FontIterator* fFontIterator;
|
||||
SkVector fAdvance;
|
||||
};
|
||||
|
||||
if (fTextSpan.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is a pretty big step - resolving all characters against all given fonts
|
||||
fFontResolver.findAllFontsForAllStyledBlocks(fTextSpan, styles(), fFontCollection);
|
||||
|
||||
LangIterator lang(fTextSpan, styles(), paragraphStyle().getTextStyle());
|
||||
FontIterator font(fTextSpan, &fFontResolver);
|
||||
ShapeHandler handler(*this, &font);
|
||||
std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder();
|
||||
SkASSERT_RELEASE(shaper != nullptr);
|
||||
auto bidi = SkShaper::MakeIcuBiDiRunIterator(
|
||||
fTextSpan.begin(), fTextSpan.size(),
|
||||
fParagraphStyle.getTextDirection() == TextDirection::kLtr ? (uint8_t)2 : (uint8_t)1);
|
||||
if (bidi == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto script = SkShaper::MakeHbIcuScriptRunIterator(fTextSpan.begin(), fTextSpan.size());
|
||||
|
||||
shaper->shape(fTextSpan.begin(), fTextSpan.size(), font, *bidi, *script, lang,
|
||||
std::numeric_limits<SkScalar>::max(), &handler);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
TextWrapper textWrapper;
|
||||
textWrapper.breakTextIntoLines(
|
||||
this,
|
||||
maxWidth,
|
||||
[&](SkSpan<const char> text,
|
||||
SkSpan<const char> textWithSpaces,
|
||||
Cluster* start,
|
||||
Cluster* end,
|
||||
size_t startPos,
|
||||
size_t endPos,
|
||||
SkVector offset,
|
||||
SkVector advance,
|
||||
LineMetrics metrics,
|
||||
bool addEllipsis) {
|
||||
// Add the line
|
||||
// TODO: Take in account clipped edges
|
||||
SkSpan<const Cluster> clusters(start, end - start + 1);
|
||||
auto& line = this->addLine(offset, advance, text, textWithSpaces, clusters,
|
||||
startPos, endPos, metrics);
|
||||
if (addEllipsis) {
|
||||
line.createEllipsis(maxWidth, fParagraphStyle.getEllipsis(), true);
|
||||
}
|
||||
});
|
||||
|
||||
fHeight = textWrapper.height();
|
||||
fWidth = maxWidth; // fTextWrapper.width();
|
||||
fMinIntrinsicWidth = textWrapper.minIntrinsicWidth();
|
||||
fMaxIntrinsicWidth = textWrapper.maxIntrinsicWidth();
|
||||
fAlphabeticBaseline = fLines.empty() ? 0 : fLines.front().alphabeticBaseline();
|
||||
fIdeographicBaseline = fLines.empty() ? 0 : fLines.front().ideographicBaseline();
|
||||
}
|
||||
|
||||
void ParagraphImpl::formatLines(SkScalar maxWidth) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
auto effectiveAlign = fParagraphStyle.effective_align();
|
||||
for (auto& line : fLines) {
|
||||
line.format(effectiveAlign, maxWidth, &line != &fLines.back());
|
||||
}
|
||||
}
|
||||
|
||||
void ParagraphImpl::paintLinesIntoPicture() {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
SkPictureRecorder recorder;
|
||||
SkCanvas* textCanvas = recorder.beginRecording(fWidth, fHeight, nullptr, 0);
|
||||
|
||||
for (auto& line : fLines) {
|
||||
line.paint(textCanvas);
|
||||
}
|
||||
|
||||
fPicture = recorder.finishRecordingAsPicture();
|
||||
}
|
||||
|
||||
SkSpan<const TextBlock> ParagraphImpl::findAllBlocks(SkSpan<const char> text) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
const TextBlock* begin = nullptr;
|
||||
const TextBlock* end = nullptr;
|
||||
for (auto& block : fTextStyles) {
|
||||
if (block.text().end() <= text.begin()) {
|
||||
BlockRange ParagraphImpl::findAllBlocks(TextRange textRange) {
|
||||
BlockIndex begin = EMPTY_BLOCK;
|
||||
BlockIndex end = EMPTY_BLOCK;
|
||||
for (size_t index = 0; index < fTextStyles.size(); ++index) {
|
||||
auto& block = fTextStyles[index];
|
||||
if (block.fRange.end <= textRange.start) {
|
||||
continue;
|
||||
}
|
||||
if (block.text().begin() >= text.end()) {
|
||||
if (block.fRange.start >= textRange.end) {
|
||||
break;
|
||||
}
|
||||
if (begin == nullptr) {
|
||||
begin = █
|
||||
if (begin == EMPTY_BLOCK) {
|
||||
begin = index;
|
||||
}
|
||||
end = █
|
||||
end = index;
|
||||
}
|
||||
|
||||
return SkSpan<const TextBlock>(begin, end - begin + 1);
|
||||
return { begin, end + 1 };
|
||||
}
|
||||
|
||||
TextLine& ParagraphImpl::addLine(SkVector offset,
|
||||
SkVector advance,
|
||||
SkSpan<const char> text,
|
||||
SkSpan<const char> textWithSpaces,
|
||||
SkSpan<const Cluster> clusters,
|
||||
size_t start,
|
||||
size_t end,
|
||||
TextRange text,
|
||||
TextRange textWithSpaces,
|
||||
ClusterRange clusters,
|
||||
LineMetrics sizes) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
// Define a list of styles that covers the line
|
||||
auto blocks = findAllBlocks(text);
|
||||
|
||||
return fLines.emplace_back(offset, advance, blocks, text, textWithSpaces, clusters, start, end,
|
||||
sizes);
|
||||
return fLines.emplace_back(this, offset, advance, blocks, text, textWithSpaces, clusters, sizes);
|
||||
}
|
||||
|
||||
// Returns a vector of bounding boxes that enclose all text between
|
||||
@ -488,18 +517,18 @@ std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
|
||||
for (unsigned i = start; i < end; ++i) {
|
||||
utf8_next(&last, fTextSpan.end());
|
||||
}
|
||||
SkSpan<const char> text(first, last - first);
|
||||
TextRange text(first - fTextSpan.begin(), last - fTextSpan.begin());
|
||||
|
||||
for (auto& line : fLines) {
|
||||
auto lineText = line.textWithSpaces();
|
||||
auto intersect = lineText * text;
|
||||
if (intersect.empty() && (!lineText.empty() || lineText.begin() != text.begin())) {
|
||||
if (intersect.empty() && (!lineText.empty() || lineText.start != text.start)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SkScalar runOffset = 0;
|
||||
if (lineText.begin() != intersect.begin()) {
|
||||
SkSpan<const char> before(lineText.begin(), intersect.begin() - lineText.begin());
|
||||
if (lineText.start != intersect.start) {
|
||||
TextRange before(lineText.start, intersect.start);
|
||||
runOffset = line.iterateThroughRuns(
|
||||
before, 0, true,
|
||||
[](Run*, size_t, size_t, SkRect, SkScalar, bool) { return true; });
|
||||
@ -656,5 +685,72 @@ SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) {
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
SkSpan<const char> ParagraphImpl::text(TextRange textRange) {
|
||||
SkASSERT(textRange.start < fText.size() && textRange.end <= fText.size());
|
||||
return SkSpan<const char>(&fText[textRange.start], textRange.width());
|
||||
}
|
||||
|
||||
SkSpan<Cluster> ParagraphImpl::clusters(ClusterRange clusterRange) {
|
||||
SkASSERT(clusterRange.start < fClusters.size() && clusterRange.end <= fClusters.size());
|
||||
return SkSpan<Cluster>(&fClusters[clusterRange.start], clusterRange.width());
|
||||
}
|
||||
|
||||
Cluster& ParagraphImpl::cluster(ClusterIndex clusterIndex) {
|
||||
SkASSERT(clusterIndex < fClusters.size());
|
||||
return fClusters[clusterIndex];
|
||||
}
|
||||
|
||||
Run& ParagraphImpl::run(RunIndex runIndex) {
|
||||
SkASSERT(runIndex < fRuns.size());
|
||||
return fRuns[runIndex];
|
||||
}
|
||||
|
||||
SkSpan<Block> ParagraphImpl::blocks(BlockRange blockRange) {
|
||||
SkASSERT(blockRange.start < fTextStyles.size() && blockRange.end <= fTextStyles.size());
|
||||
return SkSpan<Block>(&fTextStyles[blockRange.start], blockRange.width());
|
||||
}
|
||||
|
||||
Block& ParagraphImpl::block(BlockIndex blockIndex) {
|
||||
SkASSERT(blockIndex < fTextStyles.size());
|
||||
return fTextStyles[blockIndex];
|
||||
}
|
||||
|
||||
void ParagraphImpl::resetRunShifts() {
|
||||
fRunShifts.reset();
|
||||
fRunShifts.push_back_n(fRuns.size(), RunShifts());
|
||||
for (size_t i = 0; i < fRuns.size(); ++i) {
|
||||
fRunShifts[i].fShifts.push_back_n(fRuns[i].size() + 1, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
void ParagraphImpl::setState(InternalState state) {
|
||||
if (fState <= state) {
|
||||
fState = state;
|
||||
return;
|
||||
}
|
||||
|
||||
fState = state;
|
||||
switch (fState) {
|
||||
case kUnknown:
|
||||
fRuns.reset();
|
||||
case kShaped:
|
||||
fClusters.reset();
|
||||
case kClusterized:
|
||||
case kMarked:
|
||||
case kLineBroken:
|
||||
this->resetContext();
|
||||
this->resolveStrut();
|
||||
this->resetRunShifts();
|
||||
fLines.reset();
|
||||
case kFormatted:
|
||||
fPicture = nullptr;
|
||||
case kDrawn:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
@ -2,12 +2,14 @@
|
||||
#ifndef ParagraphImpl_DEFINED
|
||||
#define ParagraphImpl_DEFINED
|
||||
|
||||
#include <include/private/SkMutex.h>
|
||||
#include "FontResolver.h"
|
||||
#include "include/core/SkPicture.h"
|
||||
#include "include/private/SkTHash.h"
|
||||
#include "modules/skparagraph/include/Paragraph.h"
|
||||
#include "modules/skparagraph/include/ParagraphStyle.h"
|
||||
#include "modules/skparagraph/include/TextStyle.h"
|
||||
#include "modules/skparagraph/src/ParagraphCache.h"
|
||||
#include "modules/skparagraph/src/Run.h"
|
||||
#include "modules/skparagraph/src/TextLine.h"
|
||||
|
||||
@ -24,16 +26,30 @@ template <typename T> bool operator<=(const SkSpan<T>& a, const SkSpan<T>& b) {
|
||||
return a.begin() >= b.begin() && a.end() <= b.end();
|
||||
}
|
||||
|
||||
template <typename TStyle>
|
||||
struct StyleBlock {
|
||||
StyleBlock() : fRange(EMPTY_RANGE), fStyle() { }
|
||||
StyleBlock(size_t start, size_t end, const TStyle& style) : fRange(start, end), fStyle(style) {}
|
||||
StyleBlock(TextRange textRange, const TStyle& style) : fRange(textRange), fStyle(style) {}
|
||||
void add(TextRange tail) {
|
||||
SkASSERT(fRange.end == tail.start);
|
||||
fRange = TextRange(fRange.start, fRange.start + fRange.width() + tail.width());
|
||||
}
|
||||
TextRange fRange;
|
||||
TStyle fStyle;
|
||||
};
|
||||
|
||||
class ParagraphImpl final : public Paragraph {
|
||||
public:
|
||||
|
||||
ParagraphImpl(const SkString& text,
|
||||
ParagraphStyle style,
|
||||
std::vector<Block> blocks,
|
||||
SkTArray<Block, true> blocks,
|
||||
sk_sp<FontCollection> fonts);
|
||||
|
||||
ParagraphImpl(const std::u16string& utf16text,
|
||||
ParagraphStyle style,
|
||||
std::vector<Block> blocks,
|
||||
SkTArray<Block, true> blocks,
|
||||
sk_sp<FontCollection> fonts);
|
||||
~ParagraphImpl() override;
|
||||
|
||||
@ -51,57 +67,121 @@ public:
|
||||
|
||||
size_t lineNumber() override { return fLines.size(); }
|
||||
|
||||
TextLine& addLine(SkVector offset, SkVector advance, SkSpan<const char> text,
|
||||
SkSpan<const char> textWithSpaces, SkSpan<const Cluster> clusters,
|
||||
size_t start, size_t end, LineMetrics sizes);
|
||||
TextLine& addLine(SkVector offset, SkVector advance, TextRange text, TextRange textWithSpaces,
|
||||
ClusterRange clusters, LineMetrics sizes);
|
||||
|
||||
SkSpan<const char> text() const { return fTextSpan; }
|
||||
InternalState state() const { return fState; }
|
||||
SkSpan<Run> runs() { return SkSpan<Run>(fRuns.data(), fRuns.size()); }
|
||||
SkSpan<TextBlock> styles() {
|
||||
return SkSpan<TextBlock>(fTextStyles.data(), fTextStyles.size());
|
||||
SkTArray<FontDescr>& switches() { return fFontResolver.switches(); }
|
||||
SkSpan<Block> styles() {
|
||||
return SkSpan<Block>(fTextStyles.data(), fTextStyles.size());
|
||||
}
|
||||
SkSpan<TextLine> lines() { return SkSpan<TextLine>(fLines.data(), fLines.size()); }
|
||||
ParagraphStyle paragraphStyle() const { return fParagraphStyle; }
|
||||
SkSpan<Cluster> clusters() { return SkSpan<Cluster>(fClusters.begin(), fClusters.size()); }
|
||||
void formatLines(SkScalar maxWidth);
|
||||
|
||||
void shiftCluster(ClusterIndex index, SkScalar shift) {
|
||||
auto& cluster = fClusters[index];
|
||||
auto& run = fRunShifts[cluster.runIndex()];
|
||||
for (size_t pos = cluster.startPos(); pos < cluster.endPos(); ++pos) {
|
||||
run.fShifts[pos] = shift;
|
||||
}
|
||||
}
|
||||
|
||||
SkScalar posShift(RunIndex index, size_t pos) const {
|
||||
if (fRunShifts.count() == 0) return 0.0;
|
||||
return fRunShifts[index].fShifts[pos];
|
||||
}
|
||||
|
||||
SkScalar lineShift(size_t index) { return fLines[index].shift(); }
|
||||
|
||||
bool strutEnabled() const { return paragraphStyle().getStrutStyle().getStrutEnabled(); }
|
||||
bool strutForceHeight() const {
|
||||
return paragraphStyle().getStrutStyle().getForceStrutHeight();
|
||||
}
|
||||
LineMetrics strutMetrics() const { return fStrutMetrics; }
|
||||
|
||||
void markDirty() override { fDirtyLayout = true; }
|
||||
Measurement measurement() {
|
||||
return {
|
||||
fAlphabeticBaseline,
|
||||
fIdeographicBaseline,
|
||||
fHeight,
|
||||
fWidth,
|
||||
fMaxIntrinsicWidth,
|
||||
fMinIntrinsicWidth,
|
||||
};
|
||||
}
|
||||
void setMeasurement(Measurement m) {
|
||||
fAlphabeticBaseline = m.fAlphabeticBaseline;
|
||||
fIdeographicBaseline = m.fIdeographicBaseline;
|
||||
fHeight = m.fHeight;
|
||||
fWidth = m.fWidth;
|
||||
fMaxIntrinsicWidth = m.fMaxIntrinsicWidth;
|
||||
fMinIntrinsicWidth = m.fMinIntrinsicWidth;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ParagraphBuilder;
|
||||
SkSpan<const char> text(TextRange textRange);
|
||||
SkSpan<Cluster> clusters(ClusterRange clusterRange);
|
||||
Cluster& cluster(ClusterIndex clusterIndex);
|
||||
Run& run(RunIndex runIndex);
|
||||
SkSpan<Block> blocks(BlockRange blockRange);
|
||||
Block& block(BlockIndex blockIndex);
|
||||
|
||||
void markDirty() override { fState = kUnknown; }
|
||||
void turnOnCache(bool on) { fParagraphCacheOn = on; }
|
||||
void setState(InternalState state);
|
||||
void resetCache() { fParagraphCache.reset(); }
|
||||
sk_sp<SkPicture> getPicture() { return fPicture; }
|
||||
|
||||
void resetContext();
|
||||
void resolveStrut();
|
||||
void resetRunShifts();
|
||||
void buildClusterTable();
|
||||
void markLineBreaks();
|
||||
bool shapeTextIntoEndlessLine();
|
||||
void breakShapedTextIntoLines(SkScalar maxWidth);
|
||||
void paintLinesIntoPicture();
|
||||
|
||||
SkSpan<const TextBlock> findAllBlocks(SkSpan<const char> text);
|
||||
private:
|
||||
friend class ParagraphBuilder;
|
||||
friend class ParagraphCacheKey;
|
||||
friend class ParagraphCacheValue;
|
||||
friend class ParagraphCache;
|
||||
|
||||
BlockRange findAllBlocks(TextRange textRange);
|
||||
void extractStyles();
|
||||
|
||||
// Input
|
||||
SkTArray<TextBlock, true> fTextStyles;
|
||||
SkTArray<StyleBlock<SkScalar>> fLetterSpaceStyles;
|
||||
SkTArray<StyleBlock<SkScalar>> fWordSpaceStyles;
|
||||
SkTArray<StyleBlock<SkPaint>> fBackgroundStyles;
|
||||
SkTArray<StyleBlock<SkPaint>> fForegroundStyles;
|
||||
SkTArray<StyleBlock<std::vector<TextShadow>>> fShadowStyles;
|
||||
SkTArray<StyleBlock<Decoration>> fDecorationStyles;
|
||||
SkTArray<Block, true> fTextStyles; // TODO: take out only the font stuff
|
||||
SkString fText;
|
||||
SkSpan<const char> fTextSpan;
|
||||
|
||||
// Internal structures
|
||||
SkTArray<Run> fRuns;
|
||||
SkTArray<Cluster, true> fClusters;
|
||||
SkTArray<TextLine> fLines;
|
||||
InternalState fState;
|
||||
SkTArray<Run> fRuns; // kShaped
|
||||
SkTArray<Cluster, true> fClusters; // kClusterized (cached: text, word spacing, letter spacing, resolved fonts)
|
||||
|
||||
SkTArray<RunShifts, true> fRunShifts;
|
||||
SkTArray<TextLine, true> fLines; // kFormatted (cached: width, max lines, ellipsis, text align)
|
||||
sk_sp<SkPicture> fPicture; // kRecorded (cached: text styles)
|
||||
|
||||
LineMetrics fStrutMetrics;
|
||||
FontResolver fFontResolver;
|
||||
|
||||
bool fDirtyLayout;
|
||||
SkScalar fOldWidth;
|
||||
SkScalar fOldHeight;
|
||||
|
||||
// Painting
|
||||
sk_sp<SkPicture> fPicture;
|
||||
// Cache
|
||||
bool fParagraphCacheOn;
|
||||
static ParagraphCache fParagraphCache;
|
||||
};
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
@ -2,28 +2,30 @@
|
||||
#include "modules/skparagraph/src/Run.h"
|
||||
#include <unicode/brkiter.h>
|
||||
#include "include/core/SkFontMetrics.h"
|
||||
#include "modules/skparagraph/src/ParagraphImpl.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
Run::Run(SkSpan<const char> text,
|
||||
Run::Run(ParagraphImpl* master,
|
||||
const SkShaper::RunHandler::RunInfo& info,
|
||||
SkScalar lineHeight,
|
||||
size_t index,
|
||||
SkScalar offsetX) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
SkScalar offsetX)
|
||||
: fMaster(master)
|
||||
, fTextRange(info.utf8Range.begin(), info.utf8Range.end())
|
||||
, fClusterRange(EMPTY_CLUSTERS) {
|
||||
fFont = info.fFont;
|
||||
fHeightMultiplier = lineHeight;
|
||||
fBidiLevel = info.fBidiLevel;
|
||||
fAdvance = info.fAdvance;
|
||||
fText = SkSpan<const char>(text.begin() + info.utf8Range.begin(), info.utf8Range.size());
|
||||
|
||||
fIndex = index;
|
||||
fUtf8Range = info.utf8Range;
|
||||
fOffset = SkVector::Make(offsetX, 0);
|
||||
fGlyphs.push_back_n(info.glyphCount);
|
||||
fPositions.push_back_n(info.glyphCount + 1);
|
||||
fOffsets.push_back_n(info.glyphCount + 1, SkScalar(0));
|
||||
fOffsets.push_back_n(info.glyphCount + 1, 0.0);
|
||||
fClusterIndexes.push_back_n(info.glyphCount + 1);
|
||||
info.fFont.getMetrics(&fFontMetrics);
|
||||
fSpaced = false;
|
||||
@ -33,23 +35,21 @@ Run::Run(SkSpan<const char> text,
|
||||
}
|
||||
|
||||
SkShaper::RunHandler::Buffer Run::newRunBuffer() {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
return {fGlyphs.data(), fPositions.data(), nullptr, fClusterIndexes.data(), fOffset};
|
||||
}
|
||||
|
||||
SkScalar Run::calculateWidth(size_t start, size_t end, bool clip) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
SkASSERT(start <= end);
|
||||
// clip |= end == size(); // Clip at the end of the run?
|
||||
SkScalar offset = 0;
|
||||
if (fSpaced && end > start) {
|
||||
offset = fOffsets[clip ? end - 1 : end] - fOffsets[start];
|
||||
}
|
||||
return fPositions[end].fX - fPositions[start].fX + offset;
|
||||
auto correction = end > start ? fMaster->posShift(fIndex, end - 1) - fMaster->posShift(fIndex, start) : 0;
|
||||
return fPositions[end].fX - fPositions[start].fX + offset + correction;
|
||||
}
|
||||
|
||||
void Run::copyTo(SkTextBlobBuilder& builder, size_t pos, size_t size, SkVector offset) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
SkASSERT(pos + size <= this->size());
|
||||
const auto& blobBuffer = builder.allocRunPos(fFont, SkToInt(size));
|
||||
sk_careful_memcpy(blobBuffer.glyphs, fGlyphs.data() + pos, size * sizeof(SkGlyphID));
|
||||
@ -60,6 +60,7 @@ void Run::copyTo(SkTextBlobBuilder& builder, size_t pos, size_t size, SkVector o
|
||||
if (fSpaced) {
|
||||
point.fX += fOffsets[i + pos];
|
||||
}
|
||||
point.fX += fMaster->posShift(fIndex, i + pos);
|
||||
blobBuffer.points()[i] = point + offset;
|
||||
}
|
||||
} else {
|
||||
@ -68,37 +69,40 @@ void Run::copyTo(SkTextBlobBuilder& builder, size_t pos, size_t size, SkVector o
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make the search more effective
|
||||
std::tuple<bool, Cluster*, Cluster*> Run::findLimitingClusters(SkSpan<const char> text) {
|
||||
if (text.empty()) {
|
||||
Cluster* found = nullptr;
|
||||
for (auto& cluster : fClusters) {
|
||||
if (cluster.contains(text.begin())) {
|
||||
found = &cluster;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return std::make_tuple(found != nullptr, found, found);
|
||||
std::tuple<bool, ClusterIndex, ClusterIndex> Run::findLimitingClusters(TextRange text) {
|
||||
auto less = [](const Cluster& c1, const Cluster& c2) {
|
||||
return c1.textRange().end <= c2.textRange().start;
|
||||
};
|
||||
|
||||
if (text.width() == 0) {
|
||||
|
||||
auto found = std::lower_bound(fMaster->clusters().begin() + fClusterRange.start,
|
||||
fMaster->clusters().begin() + fClusterRange.end,
|
||||
Cluster(text),
|
||||
less);
|
||||
return std::make_tuple(found != nullptr,
|
||||
found - fMaster->clusters().begin(),
|
||||
found - fMaster->clusters().begin());
|
||||
}
|
||||
|
||||
auto first = text.begin();
|
||||
auto last = text.end() - 1;
|
||||
|
||||
Cluster* start = nullptr;
|
||||
Cluster* end = nullptr;
|
||||
for (auto& cluster : fClusters) {
|
||||
if (cluster.contains(first)) start = &cluster;
|
||||
if (cluster.contains(last)) end = &cluster;
|
||||
}
|
||||
auto start = std::lower_bound(fMaster->clusters().begin() + fClusterRange.start,
|
||||
fMaster->clusters().begin() + fClusterRange.end,
|
||||
Cluster(TextRange(text.start, text.start + 1)),
|
||||
less);
|
||||
auto end = std::lower_bound(start,
|
||||
fMaster->clusters().begin() + fClusterRange.end,
|
||||
Cluster(TextRange(text.end - 1, text.end)),
|
||||
less);
|
||||
if (!leftToRight()) {
|
||||
std::swap(start, end);
|
||||
}
|
||||
|
||||
return std::make_tuple(start != nullptr && end != nullptr, start, end);
|
||||
size_t startIndex = start - fMaster->clusters().begin();
|
||||
size_t endIndex = end - fMaster->clusters().begin();
|
||||
return std::make_tuple(startIndex != fClusterRange.end && endIndex != fClusterRange.end, startIndex, endIndex);
|
||||
}
|
||||
|
||||
void Run::iterateThroughClustersInTextOrder(const ClusterVisitor& visitor) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
// Can't figure out how to do it with one code for both cases without 100 ifs
|
||||
// Can't go through clusters because there are no cluster table yet
|
||||
if (leftToRight()) {
|
||||
@ -144,7 +148,6 @@ void Run::iterateThroughClustersInTextOrder(const ClusterVisitor& visitor) {
|
||||
}
|
||||
|
||||
SkScalar Run::addSpacesAtTheEnd(SkScalar space, Cluster* cluster) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
if (cluster->endPos() == cluster->startPos()) {
|
||||
return 0;
|
||||
}
|
||||
@ -160,7 +163,6 @@ SkScalar Run::addSpacesAtTheEnd(SkScalar space, Cluster* cluster) {
|
||||
}
|
||||
|
||||
SkScalar Run::addSpacesEvenly(SkScalar space, Cluster* cluster) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
// Offset all the glyphs in the cluster
|
||||
SkScalar shift = 0;
|
||||
for (size_t i = cluster->startPos(); i < cluster->endPos(); ++i) {
|
||||
@ -181,7 +183,6 @@ SkScalar Run::addSpacesEvenly(SkScalar space, Cluster* cluster) {
|
||||
}
|
||||
|
||||
void Run::shift(const Cluster* cluster, SkScalar offset) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
if (offset == 0) {
|
||||
return;
|
||||
}
|
||||
@ -197,10 +198,10 @@ void Run::shift(const Cluster* cluster, SkScalar offset) {
|
||||
}
|
||||
|
||||
void Cluster::setIsWhiteSpaces() {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
auto pos = fText.end();
|
||||
while (--pos >= fText.begin()) {
|
||||
auto ch = *pos;
|
||||
auto text = fMaster->text();
|
||||
auto pos = fTextRange.end;
|
||||
while (--pos >= fTextRange.start) {
|
||||
auto ch = text[pos];
|
||||
if (!u_isspace(ch) && u_charType(ch) != U_CONTROL_CHAR &&
|
||||
u_charType(ch) != U_NON_SPACING_MARK) {
|
||||
return;
|
||||
@ -209,37 +210,70 @@ void Cluster::setIsWhiteSpaces() {
|
||||
fWhiteSpaces = true;
|
||||
}
|
||||
|
||||
SkScalar Cluster::sizeToChar(const char* ch) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
if (ch < fText.begin() || ch >= fText.end()) {
|
||||
SkScalar Cluster::sizeToChar(TextIndex ch) const {
|
||||
if (ch < fTextRange.start || ch >= fTextRange.end) {
|
||||
return 0;
|
||||
}
|
||||
auto shift = ch - fText.begin();
|
||||
auto ratio = shift * 1.0 / fText.size();
|
||||
auto shift = ch - fTextRange.start;
|
||||
auto ratio = shift * 1.0 / fTextRange.width();
|
||||
|
||||
return SkDoubleToScalar(fWidth * ratio);
|
||||
}
|
||||
|
||||
SkScalar Cluster::sizeFromChar(const char* ch) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
if (ch < fText.begin() || ch >= fText.end()) {
|
||||
SkScalar Cluster::sizeFromChar(TextIndex ch) const {
|
||||
if (ch < fTextRange.start || ch >= fTextRange.end) {
|
||||
return 0;
|
||||
}
|
||||
auto shift = fText.end() - ch - 1;
|
||||
auto ratio = shift * 1.0 / fText.size();
|
||||
auto shift = fTextRange.end - ch - 1;
|
||||
auto ratio = shift * 1.0 / fTextRange.width();
|
||||
|
||||
return SkDoubleToScalar(fWidth * ratio);
|
||||
}
|
||||
|
||||
size_t Cluster::roundPos(SkScalar s) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
auto ratio = (s * 1.0) / fWidth;
|
||||
return sk_double_floor2int(ratio * size());
|
||||
}
|
||||
|
||||
SkScalar Cluster::trimmedWidth(size_t pos) const {
|
||||
// Find the width until the pos and return the min between trimmedWidth and the width(pos)
|
||||
return SkTMin(this->run()->positionX(pos) - this->run()->positionX(fStart), fWidth - fSpacing);
|
||||
// We don't have to take in account cluster shift since it's the same for 0 and for pos
|
||||
auto& run = fMaster->run(fRunIndex);
|
||||
return SkTMin(run.positionX(pos) - run.positionX(fStart), fWidth - fSpacing);
|
||||
}
|
||||
|
||||
SkScalar Run::positionX(size_t pos) const {
|
||||
return fPositions[pos].fX + fOffsets[pos] + fMaster->posShift(fIndex, pos);
|
||||
}
|
||||
|
||||
Run* Cluster::run() const {
|
||||
if (fRunIndex >= fMaster->runs().size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &fMaster->run(fRunIndex);
|
||||
}
|
||||
|
||||
SkFont Cluster::font() const {
|
||||
return fMaster->run(fRunIndex).font();
|
||||
}
|
||||
|
||||
Cluster::Cluster(ParagraphImpl* master,
|
||||
RunIndex runIndex,
|
||||
size_t start,
|
||||
size_t end,
|
||||
SkSpan<const char> text,
|
||||
SkScalar width,
|
||||
SkScalar height)
|
||||
: fMaster(master)
|
||||
, fRunIndex(runIndex)
|
||||
, fTextRange(text.begin() - fMaster->text().begin(), text.end() - fMaster->text().begin())
|
||||
, fStart(start)
|
||||
, fEnd(end)
|
||||
, fWidth(width)
|
||||
, fSpacing(0)
|
||||
, fHeight(height)
|
||||
, fWhiteSpaces(false)
|
||||
, fBreakType(None) {
|
||||
}
|
||||
|
||||
} // namespace textlayout
|
||||
|
@ -2,6 +2,8 @@
|
||||
#ifndef Run_DEFINED
|
||||
#define Run_DEFINED
|
||||
|
||||
#include "modules/skparagraph/include/DartTypes.h"
|
||||
#include "modules/skparagraph/include/TextStyle.h"
|
||||
#include "include/core/SkFontMetrics.h"
|
||||
#include "include/core/SkPoint.h"
|
||||
#include "include/core/SkTextBlob.h"
|
||||
@ -12,17 +14,41 @@
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
class ParagraphImpl;
|
||||
class Cluster;
|
||||
class Run;
|
||||
|
||||
typedef size_t RunIndex;
|
||||
const size_t EMPTY_RUN = EMPTY_INDEX;
|
||||
|
||||
typedef size_t ClusterIndex;
|
||||
typedef SkRange<size_t> ClusterRange;
|
||||
const size_t EMPTY_CLUSTER = EMPTY_INDEX;
|
||||
const SkRange<size_t> EMPTY_CLUSTERS = EMPTY_RANGE;
|
||||
|
||||
typedef size_t BlockIndex;
|
||||
typedef SkRange<size_t> BlockRange;
|
||||
const size_t EMPTY_BLOCK = EMPTY_INDEX;
|
||||
const SkRange<size_t> EMPTY_BLOCKS = EMPTY_RANGE;
|
||||
|
||||
struct RunShifts {
|
||||
RunShifts() { }
|
||||
RunShifts(size_t count) { fShifts.push_back_n(count, 0.0); }
|
||||
SkSTArray<128, SkScalar, true> fShifts;
|
||||
};
|
||||
|
||||
class Run {
|
||||
public:
|
||||
Run() = default;
|
||||
Run(SkSpan<const char> text,
|
||||
const SkShaper::RunHandler::RunInfo& info,
|
||||
SkScalar lineHeight,
|
||||
size_t index,
|
||||
SkScalar shiftX);
|
||||
Run(ParagraphImpl* master,
|
||||
const SkShaper::RunHandler::RunInfo& info,
|
||||
SkScalar lineHeight,
|
||||
size_t index,
|
||||
SkScalar shiftX);
|
||||
~Run() {}
|
||||
|
||||
void setMaster(ParagraphImpl* master) { fMaster = master; }
|
||||
|
||||
SkShaper::RunHandler::Buffer newRunBuffer();
|
||||
|
||||
size_t size() const { return fGlyphs.size(); }
|
||||
@ -43,12 +69,13 @@ public:
|
||||
bool leftToRight() const { return fBidiLevel % 2 == 0; }
|
||||
size_t index() const { return fIndex; }
|
||||
SkScalar lineHeight() const { return fHeightMultiplier; }
|
||||
SkSpan<const char> text() const { return fText; }
|
||||
size_t clusterIndex(size_t pos) const { return fClusterIndexes[pos]; }
|
||||
SkScalar positionX(size_t pos) const { return fPositions[pos].fX + fOffsets[pos]; }
|
||||
SkScalar offset(size_t index) const { return fOffsets[index]; }
|
||||
SkSpan<Cluster> clusters() const { return fClusters; }
|
||||
void setClusters(SkSpan<Cluster> clusters) { fClusters = clusters; }
|
||||
SkScalar positionX(size_t pos) const;
|
||||
|
||||
TextRange textRange() { return fTextRange; }
|
||||
ClusterRange clusterRange() { return fClusterRange; }
|
||||
|
||||
void setClusterRange(size_t from, size_t to) { fClusterRange = ClusterRange(from, to); }
|
||||
SkRect clip() const {
|
||||
return SkRect::MakeXYWH(fOffset.fX, fOffset.fY, fAdvance.fX, fAdvance.fY);
|
||||
}
|
||||
@ -70,7 +97,7 @@ public:
|
||||
SkScalar height)>;
|
||||
void iterateThroughClustersInTextOrder(const ClusterVisitor& visitor);
|
||||
|
||||
std::tuple<bool, Cluster*, Cluster*> findLimitingClusters(SkSpan<const char> text);
|
||||
std::tuple<bool, ClusterIndex, ClusterIndex> findLimitingClusters(TextRange);
|
||||
SkSpan<const SkGlyphID> glyphs() {
|
||||
return SkSpan<const SkGlyphID>(fGlyphs.begin(), fGlyphs.size());
|
||||
}
|
||||
@ -85,6 +112,11 @@ private:
|
||||
friend class ParagraphImpl;
|
||||
friend class TextLine;
|
||||
friend class LineMetrics;
|
||||
friend class ParagraphCache;
|
||||
|
||||
ParagraphImpl* fMaster;
|
||||
TextRange fTextRange;
|
||||
ClusterRange fClusterRange;
|
||||
|
||||
SkFont fFont;
|
||||
SkFontMetrics fFontMetrics;
|
||||
@ -92,8 +124,6 @@ private:
|
||||
size_t fIndex;
|
||||
uint8_t fBidiLevel;
|
||||
SkVector fAdvance;
|
||||
SkSpan<const char> fText;
|
||||
SkSpan<Cluster> fClusters;
|
||||
SkVector fOffset;
|
||||
SkShaper::RunHandler::Range fUtf8Range;
|
||||
SkSTArray<128, SkGlyphID, false> fGlyphs;
|
||||
@ -116,8 +146,9 @@ public:
|
||||
};
|
||||
|
||||
Cluster()
|
||||
: fText(nullptr, 0)
|
||||
, fRun(nullptr)
|
||||
: fMaster(nullptr)
|
||||
, fRunIndex(EMPTY_RUN)
|
||||
, fTextRange(EMPTY_TEXT)
|
||||
, fStart(0)
|
||||
, fEnd()
|
||||
, fWidth()
|
||||
@ -126,27 +157,21 @@ public:
|
||||
, fWhiteSpaces(false)
|
||||
, fBreakType(None) {}
|
||||
|
||||
Cluster(Run* run,
|
||||
size_t start,
|
||||
size_t end,
|
||||
SkSpan<const char>
|
||||
text,
|
||||
SkScalar width,
|
||||
SkScalar height)
|
||||
: fText(text)
|
||||
, fRun(run)
|
||||
, fStart(start)
|
||||
, fEnd(end)
|
||||
, fWidth(width)
|
||||
, fSpacing(0)
|
||||
, fHeight(height)
|
||||
, fWhiteSpaces(false)
|
||||
, fBreakType(None) {}
|
||||
Cluster(ParagraphImpl* master,
|
||||
RunIndex runIndex,
|
||||
size_t start,
|
||||
size_t end,
|
||||
SkSpan<const char> text,
|
||||
SkScalar width,
|
||||
SkScalar height);
|
||||
|
||||
Cluster(TextRange textRange) : fTextRange(textRange) { }
|
||||
|
||||
~Cluster() = default;
|
||||
|
||||
SkScalar sizeToChar(const char* ch) const;
|
||||
SkScalar sizeFromChar(const char* ch) const;
|
||||
void setMaster(ParagraphImpl* master) { fMaster = master; }
|
||||
SkScalar sizeToChar(TextIndex ch) const;
|
||||
SkScalar sizeFromChar(TextIndex ch) const;
|
||||
|
||||
size_t roundPos(SkScalar s) const;
|
||||
|
||||
@ -156,43 +181,48 @@ public:
|
||||
}
|
||||
|
||||
void setBreakType(BreakType type) { fBreakType = type; }
|
||||
void setIsWhiteSpaces(bool ws) { fWhiteSpaces = ws; }
|
||||
bool isWhitespaces() const { return fWhiteSpaces; }
|
||||
bool canBreakLineAfter() const {
|
||||
return fBreakType == SoftLineBreak || fBreakType == HardLineBreak;
|
||||
}
|
||||
bool isHardBreak() const { return fBreakType == HardLineBreak; }
|
||||
bool isSoftBreak() const { return fBreakType == SoftLineBreak; }
|
||||
Run* run() const { return fRun; }
|
||||
size_t startPos() const { return fStart; }
|
||||
size_t endPos() const { return fEnd; }
|
||||
SkScalar width() const { return fWidth; }
|
||||
SkScalar trimmedWidth() const { return fWidth - fSpacing; }
|
||||
SkScalar lastSpacing() const { return fSpacing; }
|
||||
SkScalar height() const { return fHeight; }
|
||||
SkSpan<const char> text() const { return fText; }
|
||||
size_t size() const { return fEnd - fStart; }
|
||||
|
||||
TextRange textRange() const { return fTextRange; }
|
||||
|
||||
RunIndex runIndex() const { return fRunIndex; }
|
||||
Run* run() const;
|
||||
SkFont font() const;
|
||||
|
||||
SkScalar trimmedWidth(size_t pos) const;
|
||||
|
||||
void shift(SkScalar offset) const { this->run()->shift(this, offset); }
|
||||
void shift(SkScalar offset) const;
|
||||
|
||||
void setIsWhiteSpaces();
|
||||
|
||||
bool contains(const char* ch) const { return ch >= fText.begin() && ch < fText.end(); }
|
||||
bool contains(TextIndex ch) const { return ch >= fTextRange.start && ch < fTextRange.end; }
|
||||
|
||||
bool belongs(SkSpan<const char> text) const {
|
||||
return fText.begin() >= text.begin() && fText.end() <= text.end();
|
||||
bool belongs(TextRange text) const {
|
||||
return fTextRange.start >= text.start && fTextRange.end <= text.end;
|
||||
}
|
||||
|
||||
bool startsIn(SkSpan<const char> text) const {
|
||||
return fText.begin() >= text.begin() && fText.begin() < text.end();
|
||||
bool startsIn(TextRange text) const {
|
||||
return fTextRange.start >= text.start && fTextRange.start < text.end;
|
||||
}
|
||||
|
||||
private:
|
||||
SkSpan<const char> fText;
|
||||
|
||||
Run* fRun;
|
||||
ParagraphImpl* fMaster;
|
||||
RunIndex fRunIndex;
|
||||
TextRange fTextRange;
|
||||
|
||||
size_t fStart;
|
||||
size_t fEnd;
|
||||
SkScalar fWidth;
|
||||
|
@ -9,87 +9,83 @@
|
||||
#include "include/effects/SkDiscretePathEffect.h"
|
||||
#include "src/core/SkMakeUnique.h"
|
||||
|
||||
namespace {
|
||||
|
||||
SkSpan<const char> intersected(const SkSpan<const char>& a, const SkSpan<const char>& b) {
|
||||
auto begin = SkTMax(a.begin(), b.begin());
|
||||
auto end = SkTMin(a.end(), b.end());
|
||||
return SkSpan<const char>(begin, end > begin ? end - begin : 0);
|
||||
}
|
||||
|
||||
int32_t intersectedSize(SkSpan<const char> a, SkSpan<const char> b) {
|
||||
if (a.begin() == nullptr || b.begin() == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
auto begin = SkTMax(a.begin(), b.begin());
|
||||
auto end = SkTMin(a.end(), b.end());
|
||||
return SkToS32(end - begin);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
int32_t intersectedSize(TextRange a, TextRange b) {
|
||||
if (a.empty() || b.empty()) {
|
||||
return -1;
|
||||
}
|
||||
auto begin = SkTMax(a.start, b.start);
|
||||
auto end = SkTMin(a.end, b.end);
|
||||
return begin <= end ? SkToS32(end - begin) : -1;
|
||||
}
|
||||
|
||||
TextRange intersected(const TextRange& a, const TextRange& b) {
|
||||
auto begin = SkTMax(a.start, b.start);
|
||||
auto end = SkTMin(a.end, b.end);
|
||||
return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
|
||||
}
|
||||
|
||||
SkTHashMap<SkFont, Run> TextLine::fEllipsisCache;
|
||||
|
||||
TextLine::TextLine(SkVector offset, SkVector advance, SkSpan<const TextBlock> blocks,
|
||||
SkSpan<const char> text, SkSpan<const char> textWithSpaces,
|
||||
SkSpan<const Cluster> clusters, size_t startPos, size_t endPos,
|
||||
TextLine::TextLine(ParagraphImpl* master,
|
||||
SkVector offset,
|
||||
SkVector advance,
|
||||
BlockRange blocks,
|
||||
TextRange text,
|
||||
TextRange textWithSpaces,
|
||||
ClusterRange clusters,
|
||||
LineMetrics sizes)
|
||||
: fBlocks(blocks)
|
||||
, fText(text)
|
||||
, fTextWithSpaces(textWithSpaces)
|
||||
, fClusters(clusters)
|
||||
//, fStartPos(startPos)
|
||||
//, fEndPos(endPos)
|
||||
: fMaster(master)
|
||||
, fBlockRange(blocks)
|
||||
, fTextRange(text)
|
||||
, fTextWithWhitespacesRange(textWithSpaces)
|
||||
, fClusterRange(clusters)
|
||||
, fLogical()
|
||||
, fShift(0)
|
||||
, fAdvance(advance)
|
||||
, fOffset(offset)
|
||||
, fShift(0.0)
|
||||
, fEllipsis(nullptr)
|
||||
, fSizes(sizes) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
, fSizes(sizes)
|
||||
, fHasBackground(false)
|
||||
, fHasShadows(false)
|
||||
, fHasDecorations(false) {
|
||||
// Reorder visual runs
|
||||
auto start = fClusters.begin();
|
||||
auto end = fClusters.end() - 1;
|
||||
size_t numRuns = end->run()->index() - start->run()->index() + 1;
|
||||
auto start = master->clusters().begin() + fClusterRange.start;
|
||||
auto end = master->clusters().begin() + fClusterRange.end - 1;
|
||||
size_t numRuns = end->runIndex() - start->runIndex() + 1;
|
||||
|
||||
for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
|
||||
auto b = fMaster->styles().begin() + index;
|
||||
if (b->fStyle.hasBackground()) {
|
||||
fHasBackground = true;
|
||||
}
|
||||
if (b->fStyle.getDecorationType() != TextDecoration::kNoDecoration) {
|
||||
fHasDecorations = true;
|
||||
}
|
||||
if (b->fStyle.getShadowNumber() > 0) {
|
||||
fHasShadows = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the logical order
|
||||
std::vector<UBiDiLevel> runLevels;
|
||||
for (auto run = start->run(); run <= end->run(); ++run) {
|
||||
runLevels.emplace_back(run->fBidiLevel);
|
||||
for (auto runIndex = start->runIndex(); runIndex <= end->runIndex(); ++runIndex) {
|
||||
auto& run = fMaster->run(runIndex);
|
||||
runLevels.emplace_back(run.fBidiLevel);
|
||||
}
|
||||
|
||||
std::vector<int32_t> logicalOrder(numRuns);
|
||||
ubidi_reorderVisual(runLevels.data(), SkToU32(numRuns), logicalOrder.data());
|
||||
|
||||
auto firstRun = start->run();
|
||||
auto firstRunIndex = start->runIndex();
|
||||
for (auto index : logicalOrder) {
|
||||
fLogical.push_back(firstRun + index);
|
||||
fLogical.push_back(firstRunIndex + index);
|
||||
}
|
||||
|
||||
// TODO: use fStartPos and fEndPos really
|
||||
// SkASSERT(fStartPos <= start->run()->size());
|
||||
// SkASSERT(fEndPos <= end->run()->size());
|
||||
}
|
||||
|
||||
TextLine::TextLine(TextLine&& other) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
this->fBlocks = other.fBlocks;
|
||||
this->fText = other.fText;
|
||||
this->fTextWithSpaces = other.fTextWithSpaces;
|
||||
this->fLogical.reset();
|
||||
this->fLogical = std::move(other.fLogical);
|
||||
this->fShift = other.fShift;
|
||||
this->fAdvance = other.fAdvance;
|
||||
this->fOffset = other.fOffset;
|
||||
this->fEllipsis = std::move(other.fEllipsis);
|
||||
this->fSizes = other.sizes();
|
||||
this->fClusters = other.fClusters;
|
||||
}
|
||||
|
||||
void TextLine::paint(SkCanvas* textCanvas) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
if (this->empty()) {
|
||||
return;
|
||||
}
|
||||
@ -97,35 +93,40 @@ void TextLine::paint(SkCanvas* textCanvas) {
|
||||
textCanvas->save();
|
||||
textCanvas->translate(this->offset().fX, this->offset().fY);
|
||||
|
||||
this->iterateThroughStylesInTextOrder(
|
||||
if (fHasBackground) {
|
||||
this->iterateThroughStylesInTextOrder(
|
||||
StyleType::kBackground,
|
||||
[textCanvas, this](SkSpan<const char> text, TextStyle style, SkScalar offsetX) {
|
||||
return this->paintBackground(textCanvas, text, style, offsetX);
|
||||
[this, textCanvas](TextRange textRange, const TextStyle& style, SkScalar offsetX) {
|
||||
return this->paintBackground(textCanvas, textRange, style, offsetX);
|
||||
});
|
||||
}
|
||||
|
||||
this->iterateThroughStylesInTextOrder(
|
||||
if (fHasShadows) {
|
||||
this->iterateThroughStylesInTextOrder(
|
||||
StyleType::kShadow,
|
||||
[textCanvas, this](SkSpan<const char> text, TextStyle style, SkScalar offsetX) {
|
||||
return this->paintShadow(textCanvas, text, style, offsetX);
|
||||
[textCanvas, this](TextRange textRange, const TextStyle& style, SkScalar offsetX) {
|
||||
return this->paintShadow(textCanvas, textRange, style, offsetX);
|
||||
});
|
||||
}
|
||||
|
||||
this->iterateThroughStylesInTextOrder(
|
||||
StyleType::kForeground,
|
||||
[textCanvas, this](SkSpan<const char> text, TextStyle style, SkScalar offsetX) {
|
||||
return this->paintText(textCanvas, text, style, offsetX);
|
||||
});
|
||||
StyleType::kForeground,
|
||||
[textCanvas, this](TextRange textRange, const TextStyle& style, SkScalar offsetX) {
|
||||
return this->paintText(textCanvas, textRange, style, offsetX);
|
||||
});
|
||||
|
||||
this->iterateThroughStylesInTextOrder(
|
||||
StyleType::kDecorations,
|
||||
[textCanvas, this](SkSpan<const char> text, TextStyle style, SkScalar offsetX) {
|
||||
return this->paintDecorations(textCanvas, text, style, offsetX);
|
||||
});
|
||||
if (fHasDecorations) {
|
||||
this->iterateThroughStylesInTextOrder(
|
||||
StyleType::kDecorations,
|
||||
[textCanvas, this](TextRange textRange, const TextStyle& style, SkScalar offsetX) {
|
||||
return this->paintDecorations(textCanvas, textRange, style, offsetX);
|
||||
});
|
||||
}
|
||||
|
||||
textCanvas->restore();
|
||||
}
|
||||
|
||||
void TextLine::format(TextAlign effectiveAlign, SkScalar maxWidth, bool notLastLine) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
SkScalar delta = maxWidth - this->width();
|
||||
if (delta <= 0) {
|
||||
return;
|
||||
@ -134,9 +135,9 @@ void TextLine::format(TextAlign effectiveAlign, SkScalar maxWidth, bool notLastL
|
||||
if (effectiveAlign == TextAlign::kJustify && notLastLine) {
|
||||
this->justify(maxWidth);
|
||||
} else if (effectiveAlign == TextAlign::kRight) {
|
||||
this->shiftTo(delta);
|
||||
fShift = delta;
|
||||
} else if (effectiveAlign == TextAlign::kCenter) {
|
||||
this->shiftTo(delta / 2);
|
||||
fShift = delta / 2;
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,26 +147,25 @@ void TextLine::scanStyles(StyleType style, const StyleVisitor& visitor) {
|
||||
}
|
||||
|
||||
this->iterateThroughStylesInTextOrder(
|
||||
style, [this, visitor](SkSpan<const char> text, TextStyle style, SkScalar offsetX) {
|
||||
visitor(text, style, offsetX);
|
||||
style, [this, visitor](TextRange textRange, const TextStyle& style, SkScalar offsetX) {
|
||||
visitor(textRange, style, offsetX);
|
||||
return this->iterateThroughRuns(
|
||||
text, offsetX, false,
|
||||
textRange, offsetX, false,
|
||||
[](Run*, int32_t, size_t, SkRect, SkScalar, bool) { return true; });
|
||||
});
|
||||
}
|
||||
|
||||
void TextLine::scanRuns(const RunVisitor& visitor) {
|
||||
this->iterateThroughRuns(
|
||||
fText, 0, false,
|
||||
fTextRange, 0, false,
|
||||
[visitor](Run* run, int32_t pos, size_t size, SkRect clip, SkScalar sc, bool b) {
|
||||
visitor(run, pos, size, clip, sc, b);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
SkScalar TextLine::paintText(SkCanvas* canvas, SkSpan<const char> text, const TextStyle& style,
|
||||
SkScalar TextLine::paintText(SkCanvas* canvas, TextRange textRange, const TextStyle& style,
|
||||
SkScalar offsetX) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
SkPaint paint;
|
||||
if (style.hasForeground()) {
|
||||
paint = style.getForeground();
|
||||
@ -173,44 +173,41 @@ SkScalar TextLine::paintText(SkCanvas* canvas, SkSpan<const char> text, const Te
|
||||
paint.setColor(style.getColor());
|
||||
}
|
||||
|
||||
auto shiftDown = this->baseline();
|
||||
auto shiftDown = this->baseline();
|
||||
return this->iterateThroughRuns(
|
||||
text, offsetX, false,
|
||||
[paint, canvas, shiftDown](Run* run, int32_t pos, size_t size, SkRect clip,
|
||||
SkScalar shift, bool clippingNeeded) {
|
||||
SkTextBlobBuilder builder;
|
||||
run->copyTo(builder, SkToU32(pos), size, SkVector::Make(0, shiftDown));
|
||||
canvas->save();
|
||||
if (clippingNeeded) {
|
||||
canvas->clipRect(clip);
|
||||
}
|
||||
canvas->translate(shift, 0);
|
||||
canvas->drawTextBlob(builder.make(), 0, 0, paint);
|
||||
canvas->restore();
|
||||
return true;
|
||||
});
|
||||
textRange, offsetX, false,
|
||||
[canvas, paint, shiftDown](Run* run, int32_t pos, size_t size, SkRect clip, SkScalar shift, bool clippingNeeded) {
|
||||
SkTextBlobBuilder builder;
|
||||
run->copyTo(builder, SkToU32(pos), size, SkVector::Make(0, shiftDown));
|
||||
canvas->save();
|
||||
if (clippingNeeded) {
|
||||
canvas->clipRect(clip);
|
||||
}
|
||||
canvas->translate(shift, 0);
|
||||
canvas->drawTextBlob(builder.make(), 0, 0, paint);
|
||||
canvas->restore();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
SkScalar TextLine::paintBackground(SkCanvas* canvas, SkSpan<const char> text,
|
||||
SkScalar TextLine::paintBackground(SkCanvas* canvas, TextRange textRange,
|
||||
const TextStyle& style, SkScalar offsetX) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
return this->iterateThroughRuns(text, offsetX, false,
|
||||
[canvas, style](Run* run, int32_t pos, size_t size, SkRect clip,
|
||||
SkScalar shift, bool clippingNeeded) {
|
||||
if (style.hasBackground()) {
|
||||
canvas->drawRect(clip, style.getBackground());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return this->iterateThroughRuns(textRange, offsetX, false,
|
||||
[canvas, &style](Run* run, int32_t pos, size_t size, SkRect clip,
|
||||
SkScalar shift, bool clippingNeeded) {
|
||||
if (style.hasBackground()) {
|
||||
canvas->drawRect(clip, style.getBackground());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
SkScalar TextLine::paintShadow(SkCanvas* canvas, SkSpan<const char> text, const TextStyle& style,
|
||||
SkScalar TextLine::paintShadow(SkCanvas* canvas, TextRange textRange, const TextStyle& style,
|
||||
SkScalar offsetX) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
if (style.getShadowNumber() == 0) {
|
||||
// Still need to calculate text advance
|
||||
return iterateThroughRuns(
|
||||
text, offsetX, false,
|
||||
textRange, offsetX, false,
|
||||
[](Run*, int32_t, size_t, SkRect, SkScalar, bool) { return true; });
|
||||
}
|
||||
|
||||
@ -228,7 +225,7 @@ SkScalar TextLine::paintShadow(SkCanvas* canvas, SkSpan<const char> text, const
|
||||
|
||||
auto shiftDown = this->baseline();
|
||||
result = this->iterateThroughRuns(
|
||||
text, offsetX, false,
|
||||
textRange, offsetX, false,
|
||||
[canvas, shadow, paint, shiftDown](Run* run, size_t pos, size_t size, SkRect clip,
|
||||
SkScalar shift, bool clippingNeeded) {
|
||||
SkTextBlobBuilder builder;
|
||||
@ -249,26 +246,25 @@ SkScalar TextLine::paintShadow(SkCanvas* canvas, SkSpan<const char> text, const
|
||||
return result;
|
||||
}
|
||||
|
||||
SkScalar TextLine::paintDecorations(SkCanvas* canvas, SkSpan<const char> text,
|
||||
SkScalar TextLine::paintDecorations(SkCanvas* canvas, TextRange textRange,
|
||||
const TextStyle& style, SkScalar offsetX) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
return this->iterateThroughRuns(
|
||||
text, offsetX, false,
|
||||
[this, canvas, style](Run* run, int32_t pos, size_t size, SkRect clip, SkScalar shift,
|
||||
textRange, offsetX, false,
|
||||
[this, canvas, &style](Run* run, int32_t pos, size_t size, SkRect clip, SkScalar shift,
|
||||
bool clippingNeeded) {
|
||||
if (style.getDecoration() == TextDecoration::kNoDecoration) {
|
||||
if (style.getDecorationType() == TextDecoration::kNoDecoration) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto decoration : AllTextDecorations) {
|
||||
if (style.getDecoration() && decoration == 0) {
|
||||
if (style.getDecorationType() && decoration == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SkScalar thickness = style.getDecorationThicknessMultiplier();
|
||||
//
|
||||
SkScalar position = 0;
|
||||
switch (style.getDecoration()) {
|
||||
switch (style.getDecorationType()) {
|
||||
case TextDecoration::kUnderline:
|
||||
position = -run->ascent() + thickness;
|
||||
break;
|
||||
@ -383,14 +379,13 @@ void TextLine::computeDecorationPaint(SkPaint& paint,
|
||||
}
|
||||
|
||||
void TextLine::justify(SkScalar maxWidth) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
// Count words and the extra spaces to spread across the line
|
||||
// TODO: do it at the line breaking?..
|
||||
size_t whitespacePatches = 0;
|
||||
SkScalar textLen = 0;
|
||||
bool whitespacePatch = false;
|
||||
this->iterateThroughClustersInGlyphsOrder(
|
||||
false, [&whitespacePatches, &textLen, &whitespacePatch](const Cluster* cluster) {
|
||||
false, [&whitespacePatches, &textLen, &whitespacePatch](const Cluster* cluster, ClusterIndex index) {
|
||||
if (cluster->isWhitespaces()) {
|
||||
if (!whitespacePatch) {
|
||||
whitespacePatch = true;
|
||||
@ -404,7 +399,6 @@ void TextLine::justify(SkScalar maxWidth) {
|
||||
});
|
||||
|
||||
if (whitespacePatches == 0) {
|
||||
this->fShift = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -413,7 +407,7 @@ void TextLine::justify(SkScalar maxWidth) {
|
||||
|
||||
// Spread the extra whitespaces
|
||||
whitespacePatch = false;
|
||||
this->iterateThroughClustersInGlyphsOrder(false, [&](const Cluster* cluster) {
|
||||
this->iterateThroughClustersInGlyphsOrder(false, [&](const Cluster* cluster, ClusterIndex index) {
|
||||
if (cluster->isWhitespaces()) {
|
||||
if (!whitespacePatch) {
|
||||
shift += step;
|
||||
@ -423,35 +417,35 @@ void TextLine::justify(SkScalar maxWidth) {
|
||||
} else {
|
||||
whitespacePatch = false;
|
||||
}
|
||||
cluster->shift(shift);
|
||||
fMaster->shiftCluster(index, shift);
|
||||
return true;
|
||||
});
|
||||
|
||||
SkAssertResult(SkScalarNearlyEqual(shift, maxWidth - textLen));
|
||||
SkASSERT(whitespacePatches == 0);
|
||||
this->fShift = 0;
|
||||
this->fAdvance.fX = maxWidth;
|
||||
}
|
||||
|
||||
void TextLine::createEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
// Replace some clusters with the ellipsis
|
||||
// Go through the clusters in the reverse logical order
|
||||
// taking off cluster by cluster until the ellipsis fits
|
||||
SkScalar width = fAdvance.fX;
|
||||
iterateThroughClustersInGlyphsOrder(
|
||||
true, [this, &width, ellipsis, maxWidth](const Cluster* cluster) {
|
||||
true, [this, &width, ellipsis, maxWidth](const Cluster* cluster, ClusterIndex index) {
|
||||
if (cluster->isWhitespaces()) {
|
||||
width -= cluster->width();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Shape the ellipsis
|
||||
Run* cached = fEllipsisCache.find(cluster->run()->font());
|
||||
Run* cached = fEllipsisCache.find(cluster->font());
|
||||
if (cached == nullptr) {
|
||||
cached = shapeEllipsis(ellipsis, cluster->run());
|
||||
} else {
|
||||
cached->setMaster(fMaster);
|
||||
}
|
||||
fEllipsis = skstd::make_unique<Run>(*cached);
|
||||
fEllipsis = std::make_shared<Run>(*cached);
|
||||
|
||||
// See if it fits
|
||||
if (width + fEllipsis->advance().fX > maxWidth) {
|
||||
@ -467,10 +461,11 @@ void TextLine::createEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool)
|
||||
}
|
||||
|
||||
Run* TextLine::shapeEllipsis(const SkString& ellipsis, Run* run) {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
|
||||
class ShapeHandler final : public SkShaper::RunHandler {
|
||||
public:
|
||||
explicit ShapeHandler(SkScalar lineHeight) : fRun(nullptr), fLineHeight(lineHeight) {}
|
||||
ShapeHandler(SkScalar lineHeight, const SkString& ellipsis)
|
||||
: fRun(nullptr), fLineHeight(lineHeight), fEllipsis(ellipsis) {}
|
||||
Run* run() { return fRun; }
|
||||
|
||||
private:
|
||||
@ -482,7 +477,7 @@ Run* TextLine::shapeEllipsis(const SkString& ellipsis, Run* run) {
|
||||
|
||||
Buffer runBuffer(const RunInfo& info) override {
|
||||
fRun = fEllipsisCache.set(info.fFont,
|
||||
Run(SkSpan<const char>(), info, fLineHeight, 0, 0));
|
||||
Run(nullptr, info, fLineHeight, 0, 0));
|
||||
return fRun->newRunBuffer();
|
||||
}
|
||||
|
||||
@ -495,32 +490,36 @@ Run* TextLine::shapeEllipsis(const SkString& ellipsis, Run* run) {
|
||||
|
||||
Run* fRun;
|
||||
SkScalar fLineHeight;
|
||||
SkString fEllipsis;
|
||||
};
|
||||
|
||||
ShapeHandler handler(run->lineHeight());
|
||||
ShapeHandler handler(run->lineHeight(), ellipsis);
|
||||
std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder();
|
||||
SkASSERT_RELEASE(shaper != nullptr);
|
||||
shaper->shape(ellipsis.c_str(), ellipsis.size(), run->font(), true,
|
||||
std::numeric_limits<SkScalar>::max(), &handler);
|
||||
handler.run()->fText = SkSpan<const char>(ellipsis.c_str(), ellipsis.size());
|
||||
handler.run()->fTextRange = TextRange(0, ellipsis.size());
|
||||
handler.run()->fMaster = fMaster;
|
||||
return handler.run();
|
||||
}
|
||||
|
||||
SkRect TextLine::measureTextInsideOneRun(
|
||||
SkSpan<const char> text, Run* run, size_t& pos, size_t& size, bool& clippingNeeded) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
SkASSERT(intersectedSize(run->text(), text) >= 0);
|
||||
TextRange textRange, Run* run, size_t& pos, size_t& size, bool& clippingNeeded) const {
|
||||
|
||||
SkASSERT(intersectedSize(run->textRange(), textRange) >= 0);
|
||||
|
||||
// Find [start:end] clusters for the text
|
||||
bool found;
|
||||
Cluster* start;
|
||||
Cluster* end;
|
||||
std::tie(found, start, end) = run->findLimitingClusters(text);
|
||||
ClusterIndex startIndex;
|
||||
ClusterIndex endIndex;
|
||||
std::tie(found, startIndex, endIndex) = run->findLimitingClusters(textRange);
|
||||
if (!found) {
|
||||
SkASSERT(text.empty());
|
||||
SkASSERT(textRange.empty());
|
||||
return SkRect::MakeEmpty();
|
||||
}
|
||||
|
||||
auto start = fMaster->clusters().begin() + startIndex;
|
||||
auto end = fMaster->clusters().begin() + endIndex;
|
||||
pos = start->startPos();
|
||||
size = end->endPos() - start->startPos();
|
||||
|
||||
@ -529,7 +528,7 @@ SkRect TextLine::measureTextInsideOneRun(
|
||||
// EOL (when we expect the last cluster clipped without any spaces)
|
||||
// Anything else (when we want the cluster width contain all the spaces -
|
||||
// coming from letter spacing or word spacing or justification)
|
||||
bool needsClipping = (run->leftToRight() ? end : start) == clusters().end() - 1;
|
||||
bool needsClipping = (run->leftToRight() ? endIndex : startIndex) == fClusterRange.end - 1;
|
||||
SkRect clip =
|
||||
SkRect::MakeXYWH(run->positionX(start->startPos()) - run->positionX(0),
|
||||
sizes().runTop(run),
|
||||
@ -540,8 +539,8 @@ SkRect TextLine::measureTextInsideOneRun(
|
||||
// TODO: This is where we get smart about selecting a part of a cluster
|
||||
// by shaping each grapheme separately and then use the result sizes
|
||||
// to calculate the proportions
|
||||
auto leftCorrection = start->sizeToChar(text.begin());
|
||||
auto rightCorrection = end->sizeFromChar(text.end() - 1);
|
||||
auto leftCorrection = start->sizeToChar(textRange.start);
|
||||
auto rightCorrection = end->sizeFromChar(textRange.end - 1);
|
||||
clip.fLeft += leftCorrection;
|
||||
clip.fRight -= rightCorrection;
|
||||
clippingNeeded = leftCorrection != 0 || rightCorrection != 0;
|
||||
@ -553,18 +552,19 @@ SkRect TextLine::measureTextInsideOneRun(
|
||||
|
||||
void TextLine::iterateThroughClustersInGlyphsOrder(bool reverse,
|
||||
const ClustersVisitor& visitor) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
for (size_t r = 0; r != fLogical.size(); ++r) {
|
||||
auto& run = fLogical[reverse ? fLogical.size() - r - 1 : r];
|
||||
auto& runIndex = fLogical[reverse ? fLogical.size() - r - 1 : r];
|
||||
// Walk through the clusters in the logical order (or reverse)
|
||||
auto run = this->fMaster->runs().begin() + runIndex;
|
||||
auto normalOrder = run->leftToRight() != reverse;
|
||||
auto start = normalOrder ? run->clusters().begin() : run->clusters().end() - 1;
|
||||
auto end = normalOrder ? run->clusters().end() : run->clusters().begin() - 1;
|
||||
for (auto cluster = start; cluster != end; normalOrder ? ++cluster : --cluster) {
|
||||
auto start = normalOrder ? run->clusterRange().start : run->clusterRange().end - 1;
|
||||
auto end = normalOrder ? run->clusterRange().end : run->clusterRange().start - 1;
|
||||
for (auto index = start; index != end; normalOrder ? ++index : --index) {
|
||||
const auto& cluster = fMaster->clusters().begin() + index;
|
||||
if (!this->contains(cluster)) {
|
||||
continue;
|
||||
}
|
||||
if (!visitor(cluster)) {
|
||||
if (!visitor(cluster, index)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -572,21 +572,22 @@ void TextLine::iterateThroughClustersInGlyphsOrder(bool reverse,
|
||||
}
|
||||
|
||||
// Walk through the runs in the logical order
|
||||
SkScalar TextLine::iterateThroughRuns(SkSpan<const char> text,
|
||||
SkScalar TextLine::iterateThroughRuns(TextRange textRange,
|
||||
SkScalar runOffset,
|
||||
bool includeEmptyText,
|
||||
const RunVisitor& visitor) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
|
||||
SkScalar width = 0;
|
||||
for (auto& run : fLogical) {
|
||||
for (auto& runIndex : fLogical) {
|
||||
auto run = this->fMaster->runs().begin() + runIndex;
|
||||
// Only skip the text if it does not even touch the run
|
||||
if (intersectedSize(run->text(), text) < 0) {
|
||||
if (intersectedSize(run->textRange(), textRange) < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SkSpan<const char> intersect = intersected(run->text(), text);
|
||||
if (run->text().empty() || intersect.empty()) {
|
||||
auto intersect = intersected(run->textRange(), textRange);
|
||||
if (run->textRange().empty() || intersect.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -603,7 +604,7 @@ SkScalar TextLine::iterateThroughRuns(SkSpan<const char> text,
|
||||
if (clip.fRight > fAdvance.fX) {
|
||||
clip.fRight = fAdvance.fX;
|
||||
clippingNeeded = true; // Correct the clip in case there was an ellipsis
|
||||
} else if (run == fLogical.back() && this->ellipsis() != nullptr) {
|
||||
} else if (runIndex == fLogical.back() && this->ellipsis() != nullptr) {
|
||||
clippingNeeded = true; // To avoid trouble
|
||||
}
|
||||
|
||||
@ -629,16 +630,17 @@ SkScalar TextLine::iterateThroughRuns(SkSpan<const char> text,
|
||||
|
||||
void TextLine::iterateThroughStylesInTextOrder(StyleType styleType,
|
||||
const StyleVisitor& visitor) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
const char* start = nullptr;
|
||||
|
||||
TextIndex start = EMPTY_INDEX;
|
||||
size_t size = 0;
|
||||
TextStyle prevStyle;
|
||||
const TextStyle* prevStyle = nullptr;
|
||||
|
||||
SkScalar offsetX = 0;
|
||||
for (auto& block : fBlocks) {
|
||||
auto intersect = intersected(block.text(), this->trimmedText());
|
||||
for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
|
||||
auto block = fMaster->styles().begin() + index;
|
||||
auto intersect = intersected(block->fRange, this->trimmedText());
|
||||
if (intersect.empty()) {
|
||||
if (start == nullptr) {
|
||||
if (start == EMPTY_INDEX) {
|
||||
// This style is not applicable to the line
|
||||
continue;
|
||||
} else {
|
||||
@ -647,29 +649,31 @@ void TextLine::iterateThroughStylesInTextOrder(StyleType styleType,
|
||||
}
|
||||
}
|
||||
|
||||
auto style = block.style();
|
||||
if (start != nullptr && style.matchOneAttribute(styleType, prevStyle)) {
|
||||
size += intersect.size();
|
||||
auto* style = &block->fStyle;
|
||||
if (start != EMPTY_INDEX && style->matchOneAttribute(styleType, *prevStyle)) {
|
||||
size += intersect.width();
|
||||
continue;
|
||||
} else if (size == 0) {
|
||||
} else if (start == EMPTY_INDEX ) {
|
||||
// First time only
|
||||
prevStyle = style;
|
||||
size = intersect.size();
|
||||
start = intersect.begin();
|
||||
size = intersect.width();
|
||||
start = intersect.start;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto width = visitor(SkSpan<const char>(start, size), prevStyle, offsetX);
|
||||
auto width = visitor(TextRange(start, start + size), *prevStyle, offsetX);
|
||||
offsetX += width;
|
||||
|
||||
// Start all over again
|
||||
prevStyle = style;
|
||||
start = intersect.begin();
|
||||
size = intersect.size();
|
||||
start = intersect.start;
|
||||
size = intersect.width();
|
||||
}
|
||||
|
||||
if (prevStyle == nullptr) return;
|
||||
|
||||
// The very last style
|
||||
auto width = visitor(SkSpan<const char>(start, size), prevStyle, offsetX);
|
||||
auto width = visitor(TextRange(start, start + size), *prevStyle, offsetX);
|
||||
offsetX += width;
|
||||
|
||||
// This is a very important assert!
|
||||
@ -679,5 +683,9 @@ void TextLine::iterateThroughStylesInTextOrder(StyleType styleType,
|
||||
}
|
||||
SkASSERT(SkScalarNearlyEqual(offsetX, this->width()));
|
||||
}
|
||||
|
||||
SkVector TextLine::offset() const {
|
||||
return fOffset + SkVector::Make(fShift, 0);
|
||||
}
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
@ -13,66 +13,52 @@
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
class TextBlock {
|
||||
public:
|
||||
TextBlock() : fText(), fTextStyle() {}
|
||||
TextBlock(SkSpan<const char> text, const TextStyle& style) : fText(text), fTextStyle(style) {}
|
||||
|
||||
SkSpan<const char> text() const { return fText; }
|
||||
TextStyle style() const { return fTextStyle; }
|
||||
|
||||
void add(SkSpan<const char> tail) {
|
||||
SkASSERT(fText.end() == tail.begin());
|
||||
fText = SkSpan<const char>(fText.begin(), fText.size() + tail.size());
|
||||
}
|
||||
|
||||
protected:
|
||||
SkSpan<const char> fText;
|
||||
TextStyle fTextStyle;
|
||||
};
|
||||
|
||||
class TextLine {
|
||||
public:
|
||||
TextLine() = default;
|
||||
TextLine(TextLine&&);
|
||||
~TextLine() = default;
|
||||
|
||||
TextLine(SkVector offset, SkVector advance, SkSpan<const TextBlock> blocks,
|
||||
SkSpan<const char> text, SkSpan<const char> textWithSpaces,
|
||||
SkSpan<const Cluster> clusters, size_t start, size_t end, LineMetrics sizes);
|
||||
TextLine(ParagraphImpl* master,
|
||||
SkVector offset,
|
||||
SkVector advance,
|
||||
BlockRange blocks,
|
||||
TextRange text,
|
||||
TextRange textWithSpaces,
|
||||
ClusterRange clusters,
|
||||
LineMetrics sizes);
|
||||
|
||||
SkSpan<const char> trimmedText() const { return fText; }
|
||||
SkSpan<const char> textWithSpaces() const { return fTextWithSpaces; }
|
||||
SkSpan<const Cluster> clusters() const { return fClusters; }
|
||||
SkVector offset() const { return fOffset + SkVector::Make(fShift, 0); }
|
||||
Run* ellipsis() const { return fEllipsis.get(); }
|
||||
LineMetrics sizes() const { return fSizes; }
|
||||
bool empty() const { return fText.empty(); }
|
||||
inline void setMaster(ParagraphImpl* master) { fMaster = master; }
|
||||
|
||||
SkScalar shift() const { return fShift; }
|
||||
SkScalar height() const { return fAdvance.fY; }
|
||||
inline TextRange trimmedText() const { return fTextRange; }
|
||||
inline TextRange textWithSpaces() const { return fTextWithWhitespacesRange; }
|
||||
inline Run* ellipsis() const { return fEllipsis.get(); }
|
||||
inline LineMetrics sizes() const { return fSizes; }
|
||||
inline bool empty() const { return fTextRange.empty(); }
|
||||
|
||||
inline SkScalar height() const { return fAdvance.fY; }
|
||||
SkScalar width() const {
|
||||
return fAdvance.fX + (fEllipsis != nullptr ? fEllipsis->fAdvance.fX : 0);
|
||||
}
|
||||
void shiftTo(SkScalar shift) { fShift = shift; }
|
||||
inline SkScalar shift() const { return fShift; }
|
||||
SkVector offset() const;
|
||||
|
||||
SkScalar alphabeticBaseline() const { return fSizes.alphabeticBaseline(); }
|
||||
SkScalar ideographicBaseline() const { return fSizes.ideographicBaseline(); }
|
||||
SkScalar baseline() const { return fSizes.baseline(); }
|
||||
SkScalar roundingDelta() const { return fSizes.delta(); }
|
||||
inline SkScalar alphabeticBaseline() const { return fSizes.alphabeticBaseline(); }
|
||||
inline SkScalar ideographicBaseline() const { return fSizes.ideographicBaseline(); }
|
||||
inline SkScalar baseline() const { return fSizes.baseline(); }
|
||||
inline SkScalar roundingDelta() const { return fSizes.delta(); }
|
||||
|
||||
using StyleVisitor = std::function<SkScalar(SkSpan<const char> text, const TextStyle& style,
|
||||
using StyleVisitor = std::function<SkScalar(TextRange textRange, const TextStyle& style,
|
||||
SkScalar offsetX)>;
|
||||
void iterateThroughStylesInTextOrder(StyleType styleType, const StyleVisitor& visitor) const;
|
||||
|
||||
using RunVisitor = std::function<bool(Run* run, size_t pos, size_t size, SkRect clip,
|
||||
SkScalar shift, bool clippingNeeded)>;
|
||||
SkScalar iterateThroughRuns(SkSpan<const char> text,
|
||||
SkScalar iterateThroughRuns(TextRange textRange,
|
||||
SkScalar offsetX,
|
||||
bool includeEmptyText,
|
||||
const RunVisitor& visitor) const;
|
||||
|
||||
using ClustersVisitor = std::function<bool(const Cluster* cluster)>;
|
||||
using ClustersVisitor = std::function<bool(const Cluster* cluster, ClusterIndex index)>;
|
||||
void iterateThroughClustersInGlyphsOrder(bool reverse, const ClustersVisitor& visitor) const;
|
||||
|
||||
void format(TextAlign effectiveAlign, SkScalar maxWidth, bool last);
|
||||
@ -85,45 +71,48 @@ public:
|
||||
void scanRuns(const RunVisitor& visitor);
|
||||
|
||||
private:
|
||||
|
||||
Run* shapeEllipsis(const SkString& ellipsis, Run* run);
|
||||
void justify(SkScalar maxWidth);
|
||||
|
||||
SkRect measureTextInsideOneRun(SkSpan<const char> text,
|
||||
SkRect measureTextInsideOneRun(TextRange textRange,
|
||||
Run* run,
|
||||
size_t& pos,
|
||||
size_t& size,
|
||||
bool& clippingNeeded) const;
|
||||
|
||||
SkScalar paintText(SkCanvas* canvas, SkSpan<const char> text, const TextStyle& style,
|
||||
SkScalar offsetX) const;
|
||||
SkScalar paintBackground(SkCanvas* canvas, SkSpan<const char> text, const TextStyle& style,
|
||||
SkScalar paintText(SkCanvas* canvas, TextRange textRange, const TextStyle& style, SkScalar offsetX) const;
|
||||
SkScalar paintBackground(SkCanvas* canvas, TextRange textRange, const TextStyle& style,
|
||||
SkScalar offsetX) const;
|
||||
SkScalar paintShadow(SkCanvas* canvas, SkSpan<const char> text, const TextStyle& style,
|
||||
SkScalar paintShadow(SkCanvas* canvas, TextRange textRange, const TextStyle& style,
|
||||
SkScalar offsetX) const;
|
||||
SkScalar paintDecorations(SkCanvas* canvas, SkSpan<const char> text, const TextStyle& style,
|
||||
SkScalar paintDecorations(SkCanvas* canvas, TextRange textRange, const TextStyle& style,
|
||||
SkScalar offsetX) const;
|
||||
|
||||
void computeDecorationPaint(SkPaint& paint, SkRect clip, const TextStyle& style,
|
||||
SkPath& path) const;
|
||||
|
||||
bool contains(const Cluster* cluster) const {
|
||||
return cluster->text().begin() >= fText.begin() && cluster->text().end() <= fText.end();
|
||||
return fTextRange.contains(cluster->textRange());
|
||||
}
|
||||
|
||||
SkSpan<const TextBlock> fBlocks;
|
||||
SkSpan<const char> fText;
|
||||
SkSpan<const char> fTextWithSpaces;
|
||||
SkSpan<const Cluster> fClusters;
|
||||
// TODO: To clip by glyph:
|
||||
//size_t fStartPos;
|
||||
//size_t fEndPos;
|
||||
SkTArray<Run*, true> fLogical;
|
||||
SkScalar fShift; // Shift to left - right - center
|
||||
SkVector fAdvance; // Text size
|
||||
SkVector fOffset; // Text position
|
||||
std::unique_ptr<Run> fEllipsis; // In case the line ends with the ellipsis
|
||||
LineMetrics fSizes; // Line metrics as a max of all run metrics
|
||||
ParagraphImpl* fMaster;
|
||||
BlockRange fBlockRange;
|
||||
TextRange fTextRange;
|
||||
TextRange fTextWithWhitespacesRange;
|
||||
ClusterRange fClusterRange;
|
||||
|
||||
SkTArray<size_t, true> fLogical;
|
||||
SkVector fAdvance; // Text size
|
||||
SkVector fOffset; // Text position
|
||||
SkScalar fShift; // Left right
|
||||
std::shared_ptr<Run> fEllipsis; // In case the line ends with the ellipsis
|
||||
LineMetrics fSizes; // Line metrics as a max of all run metrics
|
||||
bool fHasBackground;
|
||||
bool fHasShadows;
|
||||
bool fHasDecorations;
|
||||
|
||||
// TODO: use for ellipsis the common cache
|
||||
static SkTHashMap<SkFont, Run> fEllipsisCache; // All found so far shapes of ellipsis
|
||||
};
|
||||
} // namespace textlayout
|
||||
|
@ -1,21 +1,23 @@
|
||||
// Copyright 2019 Google LLC.
|
||||
#include "modules/skparagraph/include/TextStyle.h"
|
||||
#include <TextStyle.h>
|
||||
#include "include/core/SkColor.h"
|
||||
#include "include/core/SkFontStyle.h"
|
||||
#include "modules/skparagraph/include/TextStyle.h"
|
||||
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
TextStyle::TextStyle() : fFontStyle() {
|
||||
fFontFamilies.reserve(1);
|
||||
fFontFamilies.emplace_back(DEFAULT_FONT_FAMILY);
|
||||
fColor = SK_ColorWHITE;
|
||||
fDecoration = TextDecoration::kNoDecoration;
|
||||
fDecoration.fType = TextDecoration::kNoDecoration;
|
||||
// Does not make sense to draw a transparent object, so we use it as a default
|
||||
// value to indicate no decoration color was set.
|
||||
fDecorationColor = SK_ColorTRANSPARENT;
|
||||
fDecorationStyle = TextDecorationStyle::kSolid;
|
||||
fDecoration.fColor = SK_ColorTRANSPARENT;
|
||||
fDecoration.fStyle = TextDecorationStyle::kSolid;
|
||||
// Thickness is applied as a multiplier to the default thickness of the font.
|
||||
fDecorationThicknessMultiplier = 1.0;
|
||||
fDecoration.fThicknessMultiplier = 1.0;
|
||||
fFontSize = 14.0;
|
||||
fLetterSpacing = 0.0;
|
||||
fWordSpacing = 0.0;
|
||||
@ -30,16 +32,7 @@ bool TextStyle::equals(const TextStyle& other) const {
|
||||
if (fColor != other.fColor) {
|
||||
return false;
|
||||
}
|
||||
if (fDecoration != other.fDecoration) {
|
||||
return false;
|
||||
}
|
||||
if (fDecorationColor != other.fDecorationColor) {
|
||||
return false;
|
||||
}
|
||||
if (fDecorationStyle != other.fDecorationStyle) {
|
||||
return false;
|
||||
}
|
||||
if (fDecorationThicknessMultiplier != other.fDecorationThicknessMultiplier) {
|
||||
if (!(fDecoration == other.fDecoration)) {
|
||||
return false;
|
||||
}
|
||||
if (!(fFontStyle == other.fFontStyle)) {
|
||||
@ -107,9 +100,7 @@ bool TextStyle::matchOneAttribute(StyleType styleType, const TextStyle& other) c
|
||||
return true;
|
||||
|
||||
case kDecorations:
|
||||
return fDecoration == other.fDecoration && fDecorationColor == other.fDecorationColor &&
|
||||
fDecorationStyle == other.fDecorationStyle &&
|
||||
fDecorationThicknessMultiplier == other.fDecorationThicknessMultiplier;
|
||||
return this->fDecoration == other.fDecoration;
|
||||
|
||||
case kLetterSpacing:
|
||||
return fLetterSpacing == other.fLetterSpacing;
|
||||
@ -121,6 +112,7 @@ bool TextStyle::matchOneAttribute(StyleType styleType, const TextStyle& other) c
|
||||
return this->equals(other);
|
||||
|
||||
case kFont:
|
||||
// TODO: should not we take typefaces in account?
|
||||
return fFontStyle == other.fFontStyle && fFontFamilies == other.fFontFamilies &&
|
||||
fFontSize == other.fFontSize && fHeight == other.fHeight;
|
||||
|
||||
|
@ -164,13 +164,10 @@ void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
|
||||
}
|
||||
fMaxIntrinsicWidth = SkMaxScalar(fMaxIntrinsicWidth, fEndLine.width());
|
||||
// TODO: keep start/end/break info for text and runs but in a better way that below
|
||||
SkSpan<const char> text(fEndLine.startCluster()->text().begin(),
|
||||
fEndLine.endCluster()->text().end() - fEndLine.startCluster()->text().begin());
|
||||
SkSpan<const char> textWithSpaces(fEndLine.startCluster()->text().begin(),
|
||||
fEndLine.breakCluster()->text().end() - fEndLine.startCluster()->text().begin());
|
||||
addLine(text, textWithSpaces,
|
||||
fEndLine.startCluster(),
|
||||
fEndLine.endCluster(),
|
||||
TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
|
||||
TextRange textWithSpaces(fEndLine.startCluster()->textRange().start, fEndLine.breakCluster()->textRange().end);
|
||||
ClusterRange clusters(fEndLine.startCluster() - parent->clusters().begin(), fEndLine.endCluster() - parent->clusters().begin() + 1);
|
||||
addLine(text, textWithSpaces, clusters,
|
||||
fEndLine.startPos(),
|
||||
fEndLine.endPos(),
|
||||
SkVector::Make(0, fHeight),
|
||||
@ -196,10 +193,9 @@ void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
|
||||
parent->strutMetrics().updateLineMetrics(fEndLine.metrics(),
|
||||
parent->strutForceHeight());
|
||||
}
|
||||
SkSpan<const char> empty(fEndLine.breakCluster()->text().begin(), 0);
|
||||
addLine(empty, empty,
|
||||
fEndLine.breakCluster(),
|
||||
fEndLine.breakCluster(),
|
||||
TextRange empty(fEndLine.breakCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
|
||||
ClusterRange clusters(fEndLine.breakCluster() - parent->clusters().begin(), fEndLine.breakCluster() - parent->clusters().begin());
|
||||
addLine(empty, empty, clusters,
|
||||
0,
|
||||
0,
|
||||
SkVector::Make(0, fHeight),
|
||||
|
@ -16,17 +16,17 @@ class TextWrapper {
|
||||
public:
|
||||
ClusterPos() : fCluster(nullptr), fPos(0) {}
|
||||
ClusterPos(Cluster* cluster, size_t pos) : fCluster(cluster), fPos(pos) {}
|
||||
Cluster* cluster() const { return fCluster; }
|
||||
size_t position() const { return fPos; }
|
||||
void move(bool up) {
|
||||
fCluster += up ? 1 : -1;
|
||||
fPos = up ? 0 : fCluster->endPos();
|
||||
}
|
||||
void setPosition(size_t pos) { fPos = pos; }
|
||||
inline Cluster* cluster() const { return fCluster; }
|
||||
inline size_t position() const { return fPos; }
|
||||
inline void setPosition(size_t pos) { fPos = pos; }
|
||||
void clean() {
|
||||
fCluster = nullptr;
|
||||
fPos = 0;
|
||||
}
|
||||
void move(bool up) {
|
||||
fCluster += up ? 1 : -1;
|
||||
fPos = up ? 0 : fCluster->endPos();
|
||||
}
|
||||
|
||||
private:
|
||||
Cluster* fCluster;
|
||||
@ -44,13 +44,13 @@ class TextWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
SkScalar width() const { return fWidth; }
|
||||
Cluster* startCluster() const { return fStart.cluster(); }
|
||||
Cluster* endCluster() const { return fEnd.cluster(); }
|
||||
Cluster* breakCluster() const { return fBreak.cluster(); }
|
||||
LineMetrics& metrics() { return fMetrics; }
|
||||
size_t startPos() const { return fStart.position(); }
|
||||
size_t endPos() const { return fEnd.position(); }
|
||||
inline SkScalar width() const { return fWidth; }
|
||||
inline Cluster* startCluster() const { return fStart.cluster(); }
|
||||
inline Cluster* endCluster() const { return fEnd.cluster(); }
|
||||
inline Cluster* breakCluster() const { return fBreak.cluster(); }
|
||||
inline LineMetrics& metrics() { return fMetrics; }
|
||||
inline size_t startPos() const { return fStart.position(); }
|
||||
inline size_t endPos() const { return fEnd.position(); }
|
||||
bool endOfCluster() { return fEnd.position() == fEnd.cluster()->endPos(); }
|
||||
bool endOfWord() {
|
||||
return endOfCluster() &&
|
||||
@ -133,10 +133,9 @@ class TextWrapper {
|
||||
public:
|
||||
TextWrapper() { fLineNumber = 1; }
|
||||
|
||||
using AddLineToParagraph = std::function<void(SkSpan<const char> text,
|
||||
SkSpan<const char> textWithSpaces,
|
||||
Cluster* start,
|
||||
Cluster* end,
|
||||
using AddLineToParagraph = std::function<void(TextRange text,
|
||||
TextRange textWithSpaces,
|
||||
ClusterRange clusters,
|
||||
size_t startClip,
|
||||
size_t endClip,
|
||||
SkVector offset,
|
||||
@ -147,9 +146,9 @@ public:
|
||||
SkScalar maxWidth,
|
||||
const AddLineToParagraph& addLine);
|
||||
|
||||
SkScalar height() const { return fHeight; }
|
||||
SkScalar minIntrinsicWidth() const { return fMinIntrinsicWidth; }
|
||||
SkScalar maxIntrinsicWidth() const { return fMaxIntrinsicWidth; }
|
||||
inline SkScalar height() const { return fHeight; }
|
||||
inline SkScalar minIntrinsicWidth() const { return fMinIntrinsicWidth; }
|
||||
inline SkScalar maxIntrinsicWidth() const { return fMaxIntrinsicWidth; }
|
||||
|
||||
private:
|
||||
TextStretch fWords;
|
||||
|
@ -7,18 +7,17 @@
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
int TypefaceFontProvider::onCountFamilies() const { return fRegisteredFamilies.size(); }
|
||||
int TypefaceFontProvider::onCountFamilies() const { return fRegisteredFamilies.count(); }
|
||||
|
||||
void TypefaceFontProvider::onGetFamilyName(int index, SkString* familyName) const {
|
||||
SkASSERT(index < fRegisteredFamilies.count());
|
||||
familyName->set(fRegisteredFamilies[index]->getFamilyName());
|
||||
familyName->set(fFamilyNames[index]);
|
||||
}
|
||||
|
||||
SkFontStyleSet* TypefaceFontProvider::onMatchFamily(const char familyName[]) const {
|
||||
for (auto& family : fRegisteredFamilies) {
|
||||
if (family->getFamilyName().equals(familyName)) {
|
||||
return SkRef(family.get());
|
||||
}
|
||||
auto found = fRegisteredFamilies.find(SkString(familyName));
|
||||
if (found) {
|
||||
return (*found).get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@ -31,7 +30,7 @@ size_t TypefaceFontProvider::registerTypeface(sk_sp<SkTypeface> typeface) {
|
||||
SkString familyName;
|
||||
typeface->getFamilyName(&familyName);
|
||||
|
||||
return registerTypeface(std::move(typeface), familyName);
|
||||
return registerTypeface(std::move(typeface), std::move(familyName));
|
||||
}
|
||||
|
||||
size_t TypefaceFontProvider::registerTypeface(sk_sp<SkTypeface> typeface, const SkString& familyName) {
|
||||
@ -39,18 +38,14 @@ size_t TypefaceFontProvider::registerTypeface(sk_sp<SkTypeface> typeface, const
|
||||
return 0;
|
||||
}
|
||||
|
||||
TypefaceFontStyleSet* found = nullptr;
|
||||
for (auto& family : fRegisteredFamilies) {
|
||||
if (family->getFamilyName().equals(familyName)) {
|
||||
found = family.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto found = fRegisteredFamilies.find(familyName);
|
||||
if (found == nullptr) {
|
||||
found = fRegisteredFamilies.emplace_back(sk_make_sp<TypefaceFontStyleSet>(familyName)).get();
|
||||
found = fRegisteredFamilies.set(familyName, sk_make_sp<TypefaceFontStyleSet>(familyName));
|
||||
fFamilyNames.emplace_back(familyName);
|
||||
}
|
||||
|
||||
found->appendTypeface(std::move(typeface));
|
||||
(*found)->appendTypeface(std::move(typeface));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -1,27 +1,38 @@
|
||||
// Copyright 2019 Google LLC.
|
||||
#include "modules/skparagraph/utils/TestFontCollection.h"
|
||||
#include "modules/skparagraph/include/TypefaceFontProvider.h"
|
||||
#include "src/core/SkOSFile.h"
|
||||
#include "src/utils/SkUTF.h"
|
||||
#include "tools/Resources.h"
|
||||
#include "modules/skparagraph/src/ParagraphImpl.h"
|
||||
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
TestFontCollection::TestFontCollection(const std::string& resourceDir)
|
||||
: fResourceDir(resourceDir)
|
||||
, fFontsFound(0) {
|
||||
auto fontProvider = sk_make_sp<TypefaceFontProvider>();
|
||||
if (fDirs == resourceDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
fFontProvider = sk_make_sp<TypefaceFontProvider>();
|
||||
|
||||
SkOSFile::Iter iter(fResourceDir.c_str());
|
||||
SkString path;
|
||||
while (iter.next(&path)) {
|
||||
SkString file_path;
|
||||
file_path.printf("%s/%s", fResourceDir.c_str(), path.c_str());
|
||||
fontProvider->registerTypeface(SkTypeface::MakeFromFile(file_path.c_str()));
|
||||
// fonts from data are faster (skips file overhead), so we use them here for testing
|
||||
auto data = SkData::MakeFromFileName(file_path.c_str());
|
||||
if (data) {
|
||||
fFontProvider->registerTypeface(SkTypeface::MakeFromData(data));
|
||||
}
|
||||
}
|
||||
fFontsFound = fontProvider->countFamilies();
|
||||
this->setTestFontManager(std::move(fontProvider));
|
||||
|
||||
fFontsFound = fFontProvider->countFamilies();
|
||||
this->setTestFontManager(fFontProvider);
|
||||
this->disableFontFallback();
|
||||
fDirs = resourceDir;
|
||||
}
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
} // namespace skia
|
||||
|
@ -1,5 +1,6 @@
|
||||
// Copyright 2019 Google LLC.
|
||||
#include "modules/skparagraph/include/FontCollection.h"
|
||||
#include "modules/skparagraph/include/TypefaceFontProvider.h"
|
||||
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
@ -13,6 +14,8 @@ public:
|
||||
private:
|
||||
std::string fResourceDir;
|
||||
size_t fFontsFound;
|
||||
sk_sp<TypefaceFontProvider> fFontProvider;
|
||||
std::string fDirs;
|
||||
};
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
} // namespace skia
|
||||
|
@ -32,6 +32,26 @@ sk_sp<SkShader> setgrad(const SkRect& r, SkColor c0, SkColor c1) {
|
||||
SkPoint pts[] = {{r.fLeft, r.fTop}, {r.fRight, r.fTop}};
|
||||
return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
|
||||
}
|
||||
|
||||
const char* gText =
|
||||
"This is a very long sentence to test if the text will properly wrap "
|
||||
"around and go to the next line. Sometimes, short sentence. Longer "
|
||||
"sentences are okay too because they are nessecary. Very short. "
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
|
||||
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
|
||||
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
|
||||
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
|
||||
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
|
||||
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
|
||||
"mollit anim id est laborum. "
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
|
||||
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
|
||||
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
|
||||
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
|
||||
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
|
||||
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
|
||||
"mollit anim id est laborum.";
|
||||
|
||||
} // namespace
|
||||
|
||||
class ParagraphView1 : public Sample {
|
||||
@ -73,7 +93,9 @@ protected:
|
||||
for (auto i = 1; i < 5; ++i) {
|
||||
defaultStyle.setFontSize(24 * i);
|
||||
paraStyle.setTextStyle(defaultStyle);
|
||||
ParagraphBuilderImpl builder(paraStyle, sk_make_sp<FontCollection>());
|
||||
auto fontCollection = sk_make_sp<FontCollection>();
|
||||
fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
|
||||
ParagraphBuilderImpl builder(paraStyle, fontCollection);
|
||||
std::string name = "Paragraph: " + std::to_string(24 * i);
|
||||
builder.addText(name.c_str());
|
||||
for (auto para : gParagraph) {
|
||||
@ -124,7 +146,6 @@ protected:
|
||||
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(w - margin * 2);
|
||||
|
||||
paragraph->paint(canvas, margin, margin);
|
||||
|
||||
canvas->translate(0, paragraph->getHeight());
|
||||
@ -171,7 +192,9 @@ protected:
|
||||
ParagraphStyle paraStyle;
|
||||
paraStyle.setTextStyle(defaultStyle);
|
||||
|
||||
ParagraphBuilderImpl builder(paraStyle, sk_make_sp<FontCollection>());
|
||||
auto fontCollection = sk_make_sp<FontCollection>();
|
||||
fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
|
||||
ParagraphBuilderImpl builder(paraStyle, fontCollection);
|
||||
|
||||
builder.pushStyle(style(name));
|
||||
builder.addText("RaisedButton");
|
||||
@ -207,7 +230,7 @@ protected:
|
||||
return style;
|
||||
}
|
||||
|
||||
void drawText(SkCanvas* canvas, SkScalar w, SkScalar h, std::vector<std::string>& text,
|
||||
void drawText(SkCanvas* canvas, SkScalar w, SkScalar h, std::vector<const char*>& text,
|
||||
SkColor fg = SK_ColorDKGRAY, SkColor bg = SK_ColorWHITE,
|
||||
const char* ff = "sans-serif", SkScalar fs = 24,
|
||||
size_t lineLimit = 30,
|
||||
@ -256,7 +279,7 @@ protected:
|
||||
|
||||
for (auto& part : text) {
|
||||
builder.pushStyle(style);
|
||||
builder.addText(part.c_str());
|
||||
builder.addText(part);
|
||||
builder.pop();
|
||||
}
|
||||
|
||||
@ -292,7 +315,9 @@ protected:
|
||||
paraStyle.setTextStyle(style);
|
||||
paraStyle.setTextAlign(align);
|
||||
|
||||
ParagraphBuilderImpl builder(paraStyle, sk_make_sp<FontCollection>());
|
||||
auto fontCollection = sk_make_sp<FontCollection>();
|
||||
fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
|
||||
ParagraphBuilderImpl builder(paraStyle, fontCollection);
|
||||
builder.addText(text.c_str());
|
||||
|
||||
auto paragraph = builder.Build();
|
||||
@ -304,7 +329,7 @@ protected:
|
||||
}
|
||||
|
||||
void onDrawContent(SkCanvas* canvas) override {
|
||||
std::vector<std::string> cupertino = {
|
||||
std::vector<const char*> cupertino = {
|
||||
"google_logogoogle_gsuper_g_logo 1 "
|
||||
"google_logogoogle_gsuper_g_logo 12 "
|
||||
"google_logogoogle_gsuper_g_logo 123 "
|
||||
@ -330,26 +355,26 @@ protected:
|
||||
"google_logogoogle_gsuper_g_logo "
|
||||
"google_logogoogle_gsuper_g_logo "
|
||||
"google_logogoogle_gsuper_g_logo"};
|
||||
std::vector<std::string> text = {
|
||||
std::vector<const char*> text = {
|
||||
"My neighbor came over to say,\n"
|
||||
"Although not in a neighborly way,\n\n"
|
||||
"That he'd knock me around,\n\n\n"
|
||||
"If I didn't stop the sound,\n\n\n\n"
|
||||
"Of the classical music I play."};
|
||||
|
||||
std::vector<std::string> long_word = {
|
||||
std::vector<const char*> long_word = {
|
||||
"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"};
|
||||
|
||||
std::vector<std::string> very_long = {
|
||||
std::vector<const char*> very_long = {
|
||||
"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"};
|
||||
|
||||
std::vector<std::string> very_word = {
|
||||
std::vector<const char*> very_word = {
|
||||
"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 "
|
||||
@ -414,7 +439,9 @@ protected:
|
||||
paraStyle.setEllipsis(ellipsis);
|
||||
// paraStyle.setTextDirection(RTL ? SkTextDirection::rtl : SkTextDirection::ltr);
|
||||
|
||||
ParagraphBuilderImpl builder(paraStyle, sk_make_sp<FontCollection>());
|
||||
auto fontCollection = sk_make_sp<FontCollection>();
|
||||
fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
|
||||
ParagraphBuilderImpl builder(paraStyle, fontCollection);
|
||||
if (RTL) {
|
||||
builder.addText(mirror(text));
|
||||
} else {
|
||||
@ -638,7 +665,7 @@ private:
|
||||
|
||||
class ParagraphView5 : public Sample {
|
||||
protected:
|
||||
SkString name() override { return SkString("Paragraph4"); }
|
||||
SkString name() override { return SkString("Paragraph5"); }
|
||||
|
||||
void bidi(SkCanvas* canvas, SkScalar w, SkScalar h, const std::u16string& text,
|
||||
const std::u16string& expected, size_t lineLimit = std::numeric_limits<size_t>::max(),
|
||||
@ -759,7 +786,7 @@ private:
|
||||
|
||||
class ParagraphView6 : public Sample {
|
||||
protected:
|
||||
SkString name() override { return SkString("Paragraph4"); }
|
||||
SkString name() override { return SkString("Paragraph6"); }
|
||||
|
||||
void hangingS(SkCanvas* canvas, SkScalar w, SkScalar h, SkScalar fs = 60.0) {
|
||||
auto ff = "HangingS";
|
||||
@ -1188,7 +1215,8 @@ protected:
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Roboto"), SkString("Noto Color Emoji"),
|
||||
text_style.setFontFamilies({SkString("Roboto"),
|
||||
SkString("Noto Color Emoji"),
|
||||
SkString("Source Han Serif CN")});
|
||||
text_style.setColor(SK_ColorRED);
|
||||
text_style.setFontSize(60);
|
||||
@ -1206,8 +1234,8 @@ protected:
|
||||
paragraph->paint(canvas, 0, 0);
|
||||
SkDEBUGCODE(auto impl = reinterpret_cast<ParagraphImpl*>(paragraph.get()));
|
||||
SkASSERT(impl->runs().size() == 3);
|
||||
SkASSERT(impl->runs()[0].text().end() == impl->runs()[1].text().begin());
|
||||
SkASSERT(impl->runs()[1].text().end() == impl->runs()[2].text().begin());
|
||||
SkASSERT(impl->runs()[0].textRange().end == impl->runs()[1].textRange().start);
|
||||
SkASSERT(impl->runs()[1].textRange().end == impl->runs()[2].textRange().start);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -1220,53 +1248,94 @@ protected:
|
||||
|
||||
void onDrawContent(SkCanvas* canvas) override {
|
||||
canvas->drawColor(SK_ColorWHITE);
|
||||
const char* text =
|
||||
"// Create a raised button.\n"
|
||||
"RaisedButton(\n"
|
||||
" child: const Text('BUTTON TITLE'),\n"
|
||||
" onPressed: () {\n"
|
||||
" // Perform some action\n"
|
||||
" },\n"
|
||||
");\n"
|
||||
"\n"
|
||||
"// Create a disabled button.\n"
|
||||
"// Buttons are disabled when onPressed isn't\n"
|
||||
"// specified or is null.\n"
|
||||
"const RaisedButton(\n"
|
||||
" child: Text('BUTTON TITLE'),\n"
|
||||
" onPressed: null,\n"
|
||||
");\n"
|
||||
"\n"
|
||||
"// Create a button with an icon and a\n"
|
||||
"// title.\n"
|
||||
"RaisedButton.icon(\n"
|
||||
" icon: const Icon(Icons.add, size: 18.0),\n"
|
||||
" label: const Text('BUTTON TITLE'),\n"
|
||||
" onPressed: () {\n"
|
||||
" // Perform some action\n"
|
||||
" },\n"
|
||||
");";
|
||||
const char* text = "The same text many times";
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
ParagraphStyle paragraph_style;
|
||||
ParagraphBuilderImpl builder(paragraph_style, sk_make_sp<TestFontCollection>(GetResourcePath("fonts").c_str()));
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Roboto")});
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
text_style.setFontSize(10 + 2 * (i % 10));
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(text);
|
||||
builder.pop();
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(500);
|
||||
paragraph->paint(canvas, 0, 100 * (i % 10));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
typedef Sample INHERITED;
|
||||
};
|
||||
|
||||
// Measure different stages of layout/paint
|
||||
class ParagraphView12 : public Sample {
|
||||
protected:
|
||||
SkString name() override { return SkString("Paragraph12"); }
|
||||
|
||||
void onDrawContent(SkCanvas* canvas) override {
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>(GetResourcePath("fonts").c_str());
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.setMaxLines(14);
|
||||
paragraph_style.setTextAlign(TextAlign::kLeft);
|
||||
paragraph_style.turnHintingOff();
|
||||
ParagraphBuilderImpl builder(paragraph_style, sk_make_sp<FontCollection>());
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({});
|
||||
text_style.setFontFamilies({SkString("Roboto")});
|
||||
text_style.setFontSize(26);
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
text_style.setFontSize(50);
|
||||
text_style.setHeight(1);
|
||||
text_style.setDecoration(TextDecoration::kUnderline);
|
||||
text_style.setDecorationColor(SK_ColorBLACK);
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(text);
|
||||
builder.addText(gText);
|
||||
builder.pop();
|
||||
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(500);
|
||||
auto impl = reinterpret_cast<ParagraphImpl*>(paragraph.get());
|
||||
impl->turnOnCache(false);
|
||||
|
||||
auto result =
|
||||
paragraph->getRectsForRange(0, 1, RectHeightStyle::kTight, RectWidthStyle::kTight);
|
||||
SkPaint paint;
|
||||
paint.setColor(SK_ColorLTGRAY);
|
||||
canvas->drawRect(result[0].rect, paint);
|
||||
paragraph->paint(canvas, 0, 0);
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
impl->setState(kUnknown);
|
||||
impl->shapeTextIntoEndlessLine();
|
||||
impl->setState(kShaped);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
impl->setState(kShaped);
|
||||
impl->buildClusterTable();
|
||||
impl->markLineBreaks();
|
||||
impl->setState(kMarked);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
impl->setState(kMarked);
|
||||
impl->breakShapedTextIntoLines(1000);
|
||||
impl->setState(kLineBroken);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
impl->setState(kLineBroken);
|
||||
impl->formatLines(1000);
|
||||
impl->setState(kFormatted);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
impl->setState(kFormatted);
|
||||
impl->paintLinesIntoPicture();
|
||||
impl->setState(kDrawn);
|
||||
}
|
||||
|
||||
auto picture = impl->getPicture();
|
||||
SkMatrix matrix = SkMatrix::MakeTrans(0, 0);
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
canvas->drawPicture(picture, &matrix, nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1286,3 +1355,4 @@ DEF_SAMPLE(return new ParagraphView8();)
|
||||
DEF_SAMPLE(return new ParagraphView9();)
|
||||
DEF_SAMPLE(return new ParagraphView10();)
|
||||
DEF_SAMPLE(return new ParagraphView11();)
|
||||
DEF_SAMPLE(return new ParagraphView12();)
|
||||
|
@ -12,6 +12,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "include/private/SkTo.h"
|
||||
|
||||
template <typename T>
|
||||
class SkSpan {
|
||||
public:
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Copyright 2019 Google LLC.
|
||||
#include <tools/ToolUtils.h>
|
||||
#include <sstream>
|
||||
#include "modules/skparagraph/include/TypefaceFontProvider.h"
|
||||
#include "modules/skparagraph/src/ParagraphBuilderImpl.h"
|
||||
@ -15,8 +16,8 @@
|
||||
using namespace skia::textlayout;
|
||||
namespace {
|
||||
|
||||
bool equal(SkSpan<const char> a, const char* b) {
|
||||
return std::strncmp(b, a.data(), a.size()) == 0;
|
||||
bool equal(const char* base, TextRange a, const char* b) {
|
||||
return std::strncmp(b, base + a.start, a.width()) == 0;
|
||||
}
|
||||
class TestFontCollection : public FontCollection {
|
||||
public:
|
||||
@ -88,15 +89,12 @@ DEF_TEST(SkParagraph_SimpleParagraph, reporter) {
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->styles().size() == 1); // paragraph style does not count
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].style().equals(text_style));
|
||||
|
||||
// Some of the formatting lazily done on paint
|
||||
impl->formatLines(TestCanvasWidth - 100);
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
|
||||
|
||||
size_t index = 0;
|
||||
for (auto& line : impl->lines()) {
|
||||
line.scanStyles(StyleType::kDecorations,
|
||||
[&index, reporter](SkSpan<const char> text, TextStyle style, SkScalar) {
|
||||
[&index, reporter](TextRange text, TextStyle style, SkScalar) {
|
||||
REPORTER_ASSERT(reporter, index == 0);
|
||||
REPORTER_ASSERT(reporter, style.getColor() == SK_ColorBLACK);
|
||||
++index;
|
||||
@ -127,15 +125,12 @@ DEF_TEST(SkParagraph_SimpleRedParagraph, reporter) {
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->styles().size() == 1); // paragraph style does not count
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].style().equals(text_style));
|
||||
|
||||
// Some of the formatting lazily done on paint
|
||||
impl->formatLines(TestCanvasWidth - 100);
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
|
||||
|
||||
size_t index = 0;
|
||||
for (auto& line : impl->lines()) {
|
||||
line.scanStyles(StyleType::kDecorations,
|
||||
[&index, reporter](SkSpan<const char> text, TextStyle style, SkScalar) {
|
||||
[&index, reporter](TextRange text, TextStyle style, SkScalar) {
|
||||
REPORTER_ASSERT(reporter, index == 0);
|
||||
REPORTER_ASSERT(reporter, style.getColor() == SK_ColorRED);
|
||||
++index;
|
||||
@ -212,28 +207,25 @@ DEF_TEST(SkParagraph_RainbowParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, impl->styles().size() == 4);
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == 1);
|
||||
|
||||
// Some of the formatting lazily done on paint
|
||||
impl->formatLines(VeryLongCanvasWidth);
|
||||
|
||||
size_t index = 0;
|
||||
impl->lines()[0].scanStyles(StyleType::kAllAttributes,
|
||||
[&](SkSpan<const char> text, TextStyle style, SkScalar) {
|
||||
[&](TextRange text, TextStyle style, SkScalar) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
REPORTER_ASSERT(reporter, style.equals(text_style1));
|
||||
REPORTER_ASSERT(reporter, equal(text, text1));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), text, text1));
|
||||
break;
|
||||
case 1:
|
||||
REPORTER_ASSERT(reporter, style.equals(text_style2));
|
||||
REPORTER_ASSERT(reporter, equal(text, text2));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), text, text2));
|
||||
break;
|
||||
case 2:
|
||||
REPORTER_ASSERT(reporter, style.equals(text_style3));
|
||||
REPORTER_ASSERT(reporter, equal(text, text3));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), text, text3));
|
||||
break;
|
||||
case 3:
|
||||
REPORTER_ASSERT(reporter, style.equals(text_style4));
|
||||
REPORTER_ASSERT(reporter, equal(text, text45));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), text, text45));
|
||||
break;
|
||||
default:
|
||||
REPORTER_ASSERT(reporter, false);
|
||||
@ -267,13 +259,11 @@ DEF_TEST(SkParagraph_DefaultStyleParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, impl->styles().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == 1);
|
||||
|
||||
impl->formatLines(VeryLongCanvasWidth);
|
||||
|
||||
size_t index = 0;
|
||||
impl->lines()[0].scanStyles(
|
||||
StyleType::kAllAttributes, [&](SkSpan<const char> text1, TextStyle style, SkScalar) {
|
||||
StyleType::kAllAttributes, [&](TextRange text1, TextStyle style, SkScalar) {
|
||||
REPORTER_ASSERT(reporter, style.equals(paragraph_style.getTextStyle()));
|
||||
REPORTER_ASSERT(reporter, equal(text1, text));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), text1, text));
|
||||
++index;
|
||||
return true;
|
||||
});
|
||||
@ -309,13 +299,11 @@ DEF_TEST(SkParagraph_BoldParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, impl->styles().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == 1);
|
||||
|
||||
impl->formatLines(VeryLongCanvasWidth);
|
||||
|
||||
size_t index = 0;
|
||||
impl->lines()[0].scanStyles(StyleType::kAllAttributes,
|
||||
[&](SkSpan<const char> text1, TextStyle style, SkScalar) {
|
||||
[&](TextRange text1, TextStyle style, SkScalar) {
|
||||
REPORTER_ASSERT(reporter, style.equals(text_style));
|
||||
REPORTER_ASSERT(reporter, equal(text1, text));
|
||||
REPORTER_ASSERT(reporter, equal(impl->text().begin(), text1, text));
|
||||
++index;
|
||||
return true;
|
||||
});
|
||||
@ -367,13 +355,11 @@ DEF_TEST(SkParagraph_LeftAlignParagraph, reporter) {
|
||||
paragraph->layout(TestCanvasWidth - 100);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
// Some of the formatting lazily done on paint
|
||||
impl->formatLines(TestCanvasWidth - 100);
|
||||
|
||||
REPORTER_ASSERT(reporter, impl->text().size() == std::string{text}.length());
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->styles().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].style().equals(text_style));
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == paragraph_style.getMaxLines());
|
||||
|
||||
// Apparently, Minikin records start from the base line (24)
|
||||
@ -451,12 +437,10 @@ DEF_TEST(SkParagraph_RightAlignParagraph, reporter) {
|
||||
paragraph->layout(TestCanvasWidth - 100);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
// Some of the formatting lazily done on paint
|
||||
impl->formatLines(TestCanvasWidth - 100);
|
||||
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->styles().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].style().equals(text_style));
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
|
||||
// Minikin has two records for each due to 'ghost' trailing whitespace run, SkParagraph - 1
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == paragraph_style.getMaxLines());
|
||||
|
||||
@ -538,13 +522,11 @@ DEF_TEST(SkParagraph_CenterAlignParagraph, reporter) {
|
||||
paragraph->layout(TestCanvasWidth - 100);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
// Some of the formatting lazily done on paint
|
||||
impl->formatLines(TestCanvasWidth - 100);
|
||||
|
||||
REPORTER_ASSERT(reporter, impl->text().size() == std::string{text}.length());
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->styles().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].style().equals(text_style));
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
|
||||
// Minikin has two records for each due to 'ghost' trailing whitespace run, SkParagraph - 1
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == paragraph_style.getMaxLines());
|
||||
|
||||
@ -624,12 +606,11 @@ DEF_TEST(SkParagraph_JustifyAlignParagraph, reporter) {
|
||||
paragraph->layout(TestCanvasWidth - 100);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
impl->formatLines(TestCanvasWidth - 100);
|
||||
|
||||
REPORTER_ASSERT(reporter, impl->text().size() == std::string{text}.length());
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->styles().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].style().equals(text_style));
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
|
||||
|
||||
double expected_y = 0;
|
||||
double epsilon = 0.01f;
|
||||
@ -692,7 +673,6 @@ DEF_TEST(SkParagraph_JustifyRTL, reporter) {
|
||||
paragraph->layout(TestCanvasWidth - 100);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
impl->formatLines(TestCanvasWidth - 100);
|
||||
|
||||
auto calculate = [](const TextLine& line) -> SkScalar {
|
||||
return TestCanvasWidth - 100 - (line.offset().fX + line.width());
|
||||
@ -767,17 +747,16 @@ DEF_TEST(SkParagraph_DecorationsParagraph, reporter) {
|
||||
paragraph->layout(TestCanvasWidth - 100);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
impl->formatLines(TestCanvasWidth - 100);
|
||||
|
||||
size_t index = 0;
|
||||
for (auto& line : impl->lines()) {
|
||||
line.scanStyles(
|
||||
StyleType::kDecorations,
|
||||
[&index, reporter](SkSpan<const char> text, TextStyle style, SkScalar) {
|
||||
[&index, reporter](TextRange, TextStyle style, SkScalar) {
|
||||
auto decoration = (TextDecoration)(TextDecoration::kUnderline |
|
||||
TextDecoration::kOverline |
|
||||
TextDecoration::kLineThrough);
|
||||
REPORTER_ASSERT(reporter, style.getDecoration() == decoration);
|
||||
REPORTER_ASSERT(reporter, style.getDecorationType() == decoration);
|
||||
switch (index) {
|
||||
case 0:
|
||||
REPORTER_ASSERT(reporter, style.getDecorationStyle() ==
|
||||
@ -850,8 +829,6 @@ DEF_TEST(SkParagraph_ItalicsParagraph, reporter) {
|
||||
paragraph->layout(TestCanvasWidth);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
// Some of the formatting lazily done on paint
|
||||
impl->formatLines(TestCanvasWidth - 100);
|
||||
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 3);
|
||||
REPORTER_ASSERT(reporter, impl->styles().size() == 3);
|
||||
@ -860,7 +837,7 @@ DEF_TEST(SkParagraph_ItalicsParagraph, reporter) {
|
||||
size_t index = 0;
|
||||
line.scanStyles(
|
||||
StyleType::kForeground,
|
||||
[&index, reporter](SkSpan<const char> text, TextStyle style, SkScalar) {
|
||||
[&index, reporter](TextRange textRange, TextStyle style, SkScalar) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
REPORTER_ASSERT(
|
||||
@ -921,12 +898,11 @@ DEF_TEST(SkParagraph_ChineseParagraph, reporter) {
|
||||
paragraph->layout(TestCanvasWidth - 100);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
impl->formatLines(TestCanvasWidth - 100);
|
||||
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == 7);
|
||||
REPORTER_ASSERT(reporter, impl->styles().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].style().equals(text_style));
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
|
||||
}
|
||||
|
||||
DEF_TEST(SkParagraph_ArabicParagraph, reporter) {
|
||||
@ -962,12 +938,11 @@ DEF_TEST(SkParagraph_ArabicParagraph, reporter) {
|
||||
paragraph->layout(TestCanvasWidth - 100);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
impl->formatLines(TestCanvasWidth - 100);
|
||||
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == 2);
|
||||
REPORTER_ASSERT(reporter, impl->styles().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].style().equals(text_style));
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
|
||||
}
|
||||
|
||||
DEF_TEST(SkParagraph_GetGlyphPositionAtCoordinateParagraph, reporter) {
|
||||
@ -1479,8 +1454,6 @@ DEF_TEST(SkParagraph_GetRectsForRangeIncludeCombiningCharacter, reporter) {
|
||||
paragraph->layout(TestCanvasWidth - 100);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
impl->formatLines(TestCanvasWidth - 100);
|
||||
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == 1);
|
||||
|
||||
RectHeightStyle heightStyle = RectHeightStyle::kTight;
|
||||
@ -1556,10 +1529,7 @@ DEF_TEST(SkParagraph_GetRectsForRangeCenterParagraph, reporter) {
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(550);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
// Some of the formatting lazily done on paint
|
||||
impl->formatLines(550);
|
||||
|
||||
RectHeightStyle heightStyle = RectHeightStyle::kMax;
|
||||
RectWidthStyle widthStyle = RectWidthStyle::kTight;
|
||||
SkScalar epsilon = 0.01f;
|
||||
@ -1649,9 +1619,6 @@ DEF_TEST(SkParagraph_GetRectsForRangeCenterParagraphNewlineCentered, reporter) {
|
||||
paragraph->layout(550);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
// Some of the formatting lazily done on paint
|
||||
impl->formatLines(550);
|
||||
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == 2);
|
||||
|
||||
RectHeightStyle heightStyle = RectHeightStyle::kMax;
|
||||
@ -1713,8 +1680,6 @@ DEF_TEST(SkParagraph_GetRectsForRangeCenterMultiLineParagraph, reporter) {
|
||||
paragraph->layout(550);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
// Some of the formatting lazily done on paint
|
||||
impl->formatLines(550);
|
||||
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == 2);
|
||||
|
||||
@ -1897,14 +1862,14 @@ DEF_TEST(SkParagraph_SpacingParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == 1);
|
||||
size_t index = 0;
|
||||
impl->lines().begin()->scanStyles(StyleType::kLetterSpacing,
|
||||
[&index](SkSpan<const char> text, TextStyle style, SkScalar) {
|
||||
[&index](TextRange text, TextStyle style, SkScalar) {
|
||||
++index;
|
||||
return true;
|
||||
});
|
||||
REPORTER_ASSERT(reporter, index == 4);
|
||||
index = 0;
|
||||
impl->lines().begin()->scanStyles(StyleType::kWordSpacing,
|
||||
[&index](SkSpan<const char> text, TextStyle style, SkScalar) {
|
||||
[&index](TextRange text, TextStyle style, SkScalar) {
|
||||
++index;
|
||||
return true;
|
||||
});
|
||||
@ -1942,7 +1907,7 @@ DEF_TEST(SkParagraph_LongWordParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, impl->text().size() == std::string{text}.length());
|
||||
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->styles().size() == 1);
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].style().equals(text_style));
|
||||
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == 4);
|
||||
|
||||
REPORTER_ASSERT(reporter, impl->lines()[0].width() > TestCanvasWidth / 2 - 20);
|
||||
@ -1972,8 +1937,6 @@ DEF_TEST(SkParagraph_KernScaleParagraph, reporter) {
|
||||
paragraph->layout(TestCanvasWidth / scale);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
// Some of the formatting lazily done on paint
|
||||
impl->formatLines(TestCanvasWidth);
|
||||
|
||||
// First and second lines must have the same width, the third one must be bigger
|
||||
SkScalar epsilon = 0.01f;
|
||||
@ -2033,7 +1996,7 @@ DEF_TEST(SkParagraph_NewlineParagraph, reporter) {
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(impl->lines()[5].width(), 586.64f, epsilon));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(impl->lines()[6].width(), 137.16f, epsilon));
|
||||
|
||||
REPORTER_ASSERT(reporter, impl->lines()[0].shift() == 0);
|
||||
REPORTER_ASSERT(reporter, impl->lineShift(0) == 0);
|
||||
}
|
||||
|
||||
DEF_TEST(SkParagraph_EmojiParagraph, reporter) {
|
||||
@ -2070,8 +2033,6 @@ DEF_TEST(SkParagraph_EmojiParagraph, reporter) {
|
||||
paragraph->layout(TestCanvasWidth);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
// Some of the formatting lazily done on paint
|
||||
impl->formatLines(TestCanvasWidth);
|
||||
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == 8);
|
||||
for (auto& line : impl->lines()) {
|
||||
@ -2150,8 +2111,6 @@ DEF_TEST(SkParagraph_Ellipsize, reporter) {
|
||||
paragraph->layout(TestCanvasWidth);
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
// Some of the formatting lazily done on paint
|
||||
impl->formatLines(TestCanvasWidth);
|
||||
|
||||
// Check that the ellipsizer limited the text to one line and did not wrap to a second line.
|
||||
REPORTER_ASSERT(reporter, impl->lines().size() == 1);
|
||||
@ -2162,7 +2121,7 @@ DEF_TEST(SkParagraph_Ellipsize, reporter) {
|
||||
line.scanRuns([&index, &line, reporter](Run* run, int32_t, size_t, SkRect, SkScalar, bool) {
|
||||
++index;
|
||||
if (index == 2) {
|
||||
REPORTER_ASSERT(reporter, run->text() == line.ellipsis()->text());
|
||||
REPORTER_ASSERT(reporter, run->textRange() == line.ellipsis()->textRange());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@ -2216,15 +2175,15 @@ DEF_TEST(SkParagraph_UnderlineShiftParagraph, reporter) {
|
||||
size_t index = 0;
|
||||
line.scanStyles(
|
||||
StyleType::kDecorations,
|
||||
[&index, reporter](SkSpan<const char> text, TextStyle style, SkScalar) {
|
||||
[&index, reporter](TextRange text, TextStyle style, SkScalar) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
REPORTER_ASSERT(reporter,
|
||||
style.getDecoration() == TextDecoration::kNoDecoration);
|
||||
style.getDecorationType() == TextDecoration::kNoDecoration);
|
||||
break;
|
||||
case 1:
|
||||
REPORTER_ASSERT(reporter,
|
||||
style.getDecoration() == TextDecoration::kUnderline);
|
||||
style.getDecorationType() == TextDecoration::kUnderline);
|
||||
break;
|
||||
default:
|
||||
REPORTER_ASSERT(reporter, false);
|
||||
@ -2239,9 +2198,9 @@ DEF_TEST(SkParagraph_UnderlineShiftParagraph, reporter) {
|
||||
auto& line = impl1->lines()[0];
|
||||
size_t index = 0;
|
||||
line.scanStyles(StyleType::kDecorations,
|
||||
[&index, reporter](SkSpan<const char> text, TextStyle style, SkScalar) {
|
||||
[&index, reporter](TextRange text, TextStyle style, SkScalar) {
|
||||
if (index == 0) {
|
||||
REPORTER_ASSERT(reporter, style.getDecoration() ==
|
||||
REPORTER_ASSERT(reporter, style.getDecorationType() ==
|
||||
TextDecoration::kNoDecoration);
|
||||
} else {
|
||||
REPORTER_ASSERT(reporter, false);
|
||||
@ -2304,7 +2263,7 @@ DEF_TEST(SkParagraph_SimpleShadow, reporter) {
|
||||
for (auto& line : impl->lines()) {
|
||||
line.scanStyles(
|
||||
StyleType::kShadow,
|
||||
[&index, text_style, reporter](SkSpan<const char> text, TextStyle style, SkScalar) {
|
||||
[&index, text_style, reporter](TextRange text, TextStyle style, SkScalar) {
|
||||
REPORTER_ASSERT(reporter, index == 0 && style.equals(text_style));
|
||||
++index;
|
||||
return true;
|
||||
@ -2352,7 +2311,7 @@ DEF_TEST(SkParagraph_ComplexShadow, reporter) {
|
||||
for (auto& line : impl->lines()) {
|
||||
line.scanStyles(
|
||||
StyleType::kShadow,
|
||||
[&index, text_style, reporter](SkSpan<const char> text, TextStyle style, SkScalar) {
|
||||
[&index, text_style, reporter](TextRange text, TextStyle style, SkScalar) {
|
||||
++index;
|
||||
switch (index) {
|
||||
case 1:
|
||||
@ -2901,8 +2860,8 @@ DEF_TEST(SkParagraph_WhitespacesInMultipleFonts, reporter) {
|
||||
|
||||
SkDEBUGCODE(auto impl = static_cast<ParagraphImpl*>(paragraph.get());)
|
||||
SkASSERT(impl->runs().size() == 3);
|
||||
SkASSERT(impl->runs()[0].text().end() == impl->runs()[1].text().begin());
|
||||
SkASSERT(impl->runs()[1].text().end() == impl->runs()[2].text().begin());
|
||||
SkASSERT(impl->runs()[0].textRange().end == impl->runs()[1].textRange().start);
|
||||
SkASSERT(impl->runs()[1].textRange().end == impl->runs()[2].textRange().start);
|
||||
}
|
||||
|
||||
// 4 to 1
|
||||
@ -2949,10 +2908,6 @@ DEF_TEST(SkParagraph_JSON2, reporter) {
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
const char* text = "p〠q";
|
||||
|
||||
//icu::UnicodeString unicode(text, std::strlen(text));
|
||||
//std::string str;
|
||||
//unicode.toUTF8String(str);
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.turnHintingOff();
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
@ -2975,7 +2930,7 @@ DEF_TEST(SkParagraph_JSON2, reporter) {
|
||||
auto cluster = 0;
|
||||
for (auto& run : impl->runs()) {
|
||||
SkShaperJSONWriter::VisualizeClusters(
|
||||
run.text().data(), 0, run.text().size(), run.glyphs(), run.clusterIndexes(),
|
||||
impl->text().begin() + run.textRange().start, 0, run.textRange().width(), run.glyphs(), run.clusterIndexes(),
|
||||
[&](int codePointCount, SkSpan<const char> utf1to1,
|
||||
SkSpan<const SkGlyphID> glyph1to1) {
|
||||
if (cluster == 0) {
|
||||
|
Loading…
Reference in New Issue
Block a user