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:
Julia Lavrova 2019-06-21 12:22:32 -04:00 committed by Skia Commit-Bot
parent fc854c30fb
commit 5207f35f33
32 changed files with 1583 additions and 1023 deletions

View File

@ -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)

View File

@ -11,7 +11,7 @@ declare_args() {
if (skia_enable_skparagraph) {
config("public_config") {
include_dirs = [ "include" ]
include_dirs = [ "include", "utils" ]
}
component("skparagraph") {

View File

@ -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,

View File

@ -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; }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -81,7 +81,8 @@ public:
}
private:
SkTArray<sk_sp<TypefaceFontStyleSet>> fRegisteredFamilies;
SkTHashMap<SkString, sk_sp<TypefaceFontStyleSet>> fRegisteredFamilies;
SkTArray<SkString> fFamilyNames;
};
} // namespace textlayout
} // namespace skia

View File

@ -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 = [

View File

@ -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);
}

View File

@ -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(&current, text.end()));
fCodepoints.emplace_back(utf8_next(&current, 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

View File

@ -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;

View File

@ -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

View File

@ -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();
}
}

View File

@ -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;
};

View 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");
*/
}
}
}

View File

@ -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

View File

@ -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(&paragraph)
, 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, &currentStyle, &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(&paragraph)
, 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 = &block;
if (begin == EMPTY_BLOCK) {
begin = index;
}
end = &block;
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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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),

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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();)

View File

@ -12,6 +12,8 @@
#include <string>
#include <vector>
#include "include/private/SkTo.h"
template <typename T>
class SkSpan {
public:

View File

@ -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) {