Font features
Change-Id: I8ae44d715fd2fa195c209c8ecb514db3e9644c31
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/259100
Reviewed-by: Ben Wagner <bungeman@google.com>
Commit-Queue: Julia Lavrova <jlavrova@google.com>
diff --git a/modules/skparagraph/include/TextStyle.h b/modules/skparagraph/include/TextStyle.h
index 387bf82..88223a5 100644
--- a/modules/skparagraph/include/TextStyle.h
+++ b/modules/skparagraph/include/TextStyle.h
@@ -89,6 +89,16 @@
kMiddle,
};
+struct FontFeature {
+ FontFeature(const SkString name, int value) : fName(name), fValue(value) { }
+ FontFeature(const FontFeature& other) : fName(other.fName), fValue(other.fValue) { }
+ bool operator==(const FontFeature& other) const {
+ return fName == other.fName && fValue == other.fValue;
+ }
+ SkString fName;
+ int fValue;
+};
+
struct PlaceholderStyle {
PlaceholderStyle() { }
PlaceholderStyle(SkScalar width, SkScalar height, PlaceholderAlignment alignment,
@@ -170,6 +180,13 @@
void addShadow(TextShadow shadow) { fTextShadows.emplace_back(shadow); }
void resetShadows() { fTextShadows.clear(); }
+ // Font features
+ size_t getFontFeatureNumber() const { return fFontFeatures.size(); }
+ std::vector<FontFeature> getFontFeatures() const { return fFontFeatures; }
+ void addFontFeature(const SkString& fontFeature, int value)
+ { fFontFeatures.emplace_back(fontFeature, value); }
+ void resetFontFeatures() { fFontFeatures.clear(); }
+
SkScalar getFontSize() const { return fFontSize; }
void setFontSize(SkScalar size) { fFontSize = size; }
@@ -231,6 +248,8 @@
sk_sp<SkTypeface> fTypeface;
bool fIsPlaceholder;
+
+ std::vector<FontFeature> fFontFeatures;
};
typedef size_t TextIndex;
diff --git a/modules/skparagraph/src/OneLineShaper.cpp b/modules/skparagraph/src/OneLineShaper.cpp
index f777ac8..39fa231 100644
--- a/modules/skparagraph/src/OneLineShaper.cpp
+++ b/modules/skparagraph/src/OneLineShaper.cpp
@@ -339,6 +339,23 @@
void OneLineShaper::iterateThroughFontStyles(SkSpan<Block> styleSpan,
const ShapeSingleFontVisitor& visitor) {
Block combinedBlock;
+ SkTArray<SkShaper::Feature> features;
+
+ auto addFeatures = [&features](const Block& block) {
+ for (auto& ff : block.fStyle.getFontFeatures()) {
+ if (ff.fName.size() != 4) {
+ SkDEBUGF("Incorrect font feature: %s=%d\n", ff.fName.c_str(), ff.fValue);
+ continue;
+ }
+ SkShaper::Feature feature = {
+ SkSetFourByteTag(ff.fName[0], ff.fName[1], ff.fName[2], ff.fName[3]),
+ SkToU32(ff.fValue),
+ block.fRange.start,
+ block.fRange.end
+ };
+ features.emplace_back(feature);
+ }
+ };
for (auto& block : styleSpan) {
SkASSERT(combinedBlock.fRange.width() == 0 ||
@@ -347,17 +364,20 @@
if (!combinedBlock.fRange.empty()) {
if (block.fStyle.matchOneAttribute(StyleType::kFont, combinedBlock.fStyle)) {
combinedBlock.add(block.fRange);
+ addFeatures(block);
continue;
}
// Resolve all characters in the block for this style
- visitor(combinedBlock);
+ visitor(combinedBlock, features);
}
combinedBlock.fRange = block.fRange;
combinedBlock.fStyle = block.fStyle;
+ features.reset();
+ addFeatures(block);
}
- visitor(combinedBlock);
+ visitor(combinedBlock, features);
#ifdef SK_DEBUG
//printState();
#endif
@@ -462,7 +482,7 @@
iterateThroughFontStyles(styleSpan,
[this, &shaper, textDirection, limitlessWidth, &advanceX]
- (Block block) {
+ (Block block, SkTArray<SkShaper::Feature> features) {
auto blockSpan = SkSpan<Block>(&block, 1);
// Start from the beginning (hoping that it's a simple case one block - one run)
@@ -499,6 +519,7 @@
fCurrentText = unresolvedRange;
shaper->shape(unresolvedText.begin(), unresolvedText.size(),
fontIter, *bidi,*script, lang,
+ features.data(), features.size(),
limitlessWidth, this);
// Take off the queue the block we tried to resolved -
diff --git a/modules/skparagraph/src/OneLineShaper.h b/modules/skparagraph/src/OneLineShaper.h
index e25c085..81b34cb 100644
--- a/modules/skparagraph/src/OneLineShaper.h
+++ b/modules/skparagraph/src/OneLineShaper.h
@@ -57,7 +57,7 @@
std::function<SkScalar(SkSpan<const char>, SkSpan<Block>, SkScalar&, TextIndex)>;
bool iterateThroughShapingRegions(const ShapeVisitor& shape);
- using ShapeSingleFontVisitor = std::function<void(Block)>;
+ using ShapeSingleFontVisitor = std::function<void(Block, SkTArray<SkShaper::Feature>)>;
void iterateThroughFontStyles(SkSpan<Block> styleSpan, const ShapeSingleFontVisitor& visitor);
using TypefaceVisitor = std::function<bool(sk_sp<SkTypeface> typeface)>;
diff --git a/modules/skparagraph/src/TextStyle.cpp b/modules/skparagraph/src/TextStyle.cpp
index 4fc619a..5582303 100644
--- a/modules/skparagraph/src/TextStyle.cpp
+++ b/modules/skparagraph/src/TextStyle.cpp
@@ -40,6 +40,7 @@
fForeground = other.fForeground;
fHeightOverride = other.fHeightOverride;
fIsPlaceholder = placeholder;
+ fFontFeatures = other.fFontFeatures;
}
bool TextStyle::equals(const TextStyle& other) const {
@@ -89,6 +90,14 @@
return false;
}
}
+ if (fFontFeatures.size() != other.fFontFeatures.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < fFontFeatures.size(); ++i) {
+ if (!(fFontFeatures[i] == other.fFontFeatures[i])) {
+ return false;
+ }
+ }
return true;
}
diff --git a/tests/SkParagraphTest.cpp b/tests/SkParagraphTest.cpp
index ffce0de..395dcae 100644
--- a/tests/SkParagraphTest.cpp
+++ b/tests/SkParagraphTest.cpp
@@ -4482,6 +4482,56 @@
}
}
+DEF_TEST(SkParagraph_FontFeaturesParagraph, reporter) {
+ sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
+ if (!fontCollection->fontsFound()) return;
+ TestCanvas canvas("SkParagraph_FontFeaturesParagraph.png");
+
+ const char* text = "12ab\n";
+
+ ParagraphStyle paragraph_style;
+ paragraph_style.turnHintingOff();
+ ParagraphBuilderImpl builder(paragraph_style, fontCollection);
+
+ TextStyle text_style;
+ text_style.setFontStyle(SkFontStyle::Italic());
+ text_style.setFontFamilies({SkString("Roboto")});
+ text_style.setColor(SK_ColorBLACK);
+
+ text_style.addFontFeature(SkString("tnum"), 1);
+ builder.pushStyle(text_style);
+ builder.addText(text);
+
+ text_style.resetFontFeatures();
+ text_style.addFontFeature(SkString("tnum"), 0);
+ text_style.addFontFeature(SkString("pnum"), 1);
+ builder.pushStyle(text_style);
+ builder.addText(text);
+
+ builder.pop();
+ builder.pop();
+
+ auto paragraph = builder.Build();
+ paragraph->layout(TestCanvasWidth);
+
+ paragraph->paint(canvas.get(), 10.0, 15.0);
+
+ auto impl = static_cast<ParagraphImpl*>(paragraph.get());
+ REPORTER_ASSERT(reporter, paragraph->lineNumber() == 3ull);
+
+ auto& tnum_line = impl->lines()[0];
+ auto& pnum_line = impl->lines()[1];
+
+ REPORTER_ASSERT(reporter, tnum_line.clusters().width() == 4ull);
+ REPORTER_ASSERT(reporter, pnum_line.clusters().width() == 4ull);
+ // Tabular numbers should have equal widths.
+ REPORTER_ASSERT(reporter, impl->clusters()[0].width() == impl->clusters()[1].width());
+ // Proportional numbers should have variable widths.
+ REPORTER_ASSERT(reporter, impl->clusters()[5].width() != impl->clusters()[6].width());
+ // Alphabetic characters should be unaffected.
+ REPORTER_ASSERT(reporter, impl->clusters()[2].width() == impl->clusters()[7].width());
+}
+
// Not in Minikin
DEF_TEST(SkParagraph_WhitespacesInMultipleFonts, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();