Add more paragraph bindings to CanvasKit

Change-Id: Ib02b6504724e4d7cfa197a3508f8c0b84b4135bf
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/313146
Commit-Queue: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Yegor Jbanov <yjbanov@google.com>
Auto-Submit: Harry Terkelsen <het@google.com>
This commit is contained in:
Harry Terkelsen 2020-10-02 15:24:13 -07:00 committed by Skia Commit-Bot
parent 0f7242f28f
commit 223ffcdff9
8 changed files with 706 additions and 82 deletions

View File

@ -58,6 +58,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
already have their own representation of Rect. This is experimental because we don't know
if it's faster/better under real-world use and because we don't want to commit to having these
for all Rect APIs (and for similar types) until it has baked in a bit.
- Added the following to `TextStyle`:
- `decorationStyle`
- `textBaseline`
- `letterSpacing`
- `wordSpacing`
- `heightMultiplier`
- `locale`
- `shadows`
- `fontFeatures`
- Added `strutStyle` to `ParagraphStyle`.
- Added `addPlaceholder` to `ParagraphBuilder`.
- Added `getRectsForPlaceholders` to `Paragraph`.
- `SkFont.getGlyphIDs`, `SkFont.getGlyphBounds`, `SkFont.getGlyphWidths` for turning code points
into GlyphIDs and getting the associated metrics with those glyphs. Note: glyph ids are only
valid for the font of which they were requested.

View File

@ -139,4 +139,4 @@ sdk and verified/fixed any build issues that have arisen.
11. Upload a CL with all the changes. Run all Test.+CanvasKit, Perf.+CanvasKit,
Test.+PathKit, Perf.+PathKit jobs to make sure the new builds pass all
tests and don't crash the perf harnesses.
12. Send out CL for review. Feel free to point the reviewer at these steps.
12. Send out CL for review. Feel free to point the reviewer at these steps.

View File

@ -144,6 +144,7 @@ var CanvasKit = {
// private API
/** @return {Float32Array} */
_getRectsForRange: function() {},
_getRectsForPlaceholders: function() {},
},
ParagraphBuilder: {
@ -156,6 +157,7 @@ var CanvasKit = {
prototype: {
pushStyle: function() {},
pushPaintStyle: function() {},
addPlaceholder: function() {},
},
// private API
@ -163,6 +165,7 @@ var CanvasKit = {
_MakeFromFontProvider: function() {},
_pushStyle: function() {},
_pushPaintStyle: function() {},
_addPlaceholder: function() {},
},
SkRuntimeEffect: {
@ -903,6 +906,28 @@ var CanvasKit = {
RTL: {},
},
DecorationStyle: {
Solid: {},
Double: {},
Dotted: {},
Dashed: {},
Wavy: {},
},
PlaceholderAlignment: {
Baseline: {},
AboveBaseline: {},
BelowBaseline: {},
Top: {},
Bottom: {},
Middle: {},
},
TextBaseline: {
Alphabetic: {},
Ideographic: {},
},
TileMode: {
Clamp: {},
Repeat: {},
@ -960,6 +985,7 @@ var CanvasKit = {
// It's not enough to declare them above, because closure can still erase them
// unless they go on the prototype.
CanvasKit.Paragraph.prototype.getRectsForRange = function() {};
CanvasKit.Paragraph.prototype.getRectsForPlaceholders = function() {};
CanvasKit.SkPicture.prototype.saveAsFile = function() {};

View File

@ -8,22 +8,34 @@
* @type {Float32Array}
*/
var floatArray = this._getRectsForRange(start, end, hStyle, wStyle);
return floatArrayToRects(floatArray);
}
if (!floatArray || !floatArray.length) {
return [];
}
var ret = [];
for (var i = 0; i < floatArray.length; i+=5) {
var r = CanvasKit.LTRBRect(floatArray[i], floatArray[i+1], floatArray[i+2], floatArray[i+3]);
if (floatArray[i+4] === 0) {
r['direction'] = CanvasKit.TextDirection.RTL;
} else {
r['direction'] = CanvasKit.TextDirection.LTR;
CanvasKit.Paragraph.prototype.getRectsForPlaceholders = function() {
/**
* This is bytes, but we'll want to think about them as float32s
* @type {Float32Array}
*/
var floatArray = this._getRectsForPlaceholders();
return floatArrayToRects(floatArray);
}
function floatArrayToRects(floatArray) {
if (!floatArray || !floatArray.length) {
return [];
}
ret.push(r);
}
CanvasKit._free(floatArray.byteOffset);
return ret;
var ret = [];
for (var i = 0; i < floatArray.length; i+=5) {
var r = CanvasKit.LTRBRect(floatArray[i], floatArray[i+1], floatArray[i+2], floatArray[i+3]);
if (floatArray[i+4] === 0) {
r['direction'] = CanvasKit.TextDirection.RTL;
} else {
r['direction'] = CanvasKit.TextDirection.LTR;
}
ret.push(r);
}
CanvasKit._free(floatArray.byteOffset);
return ret;
}
// Registers the font (provided as an arrayBuffer) with the alias `family`.
@ -43,7 +55,6 @@
// have undefined and it expects, for example, a float.
CanvasKit.ParagraphStyle = function(s) {
// Use [''] to tell closure not to minify the names
// TODO(kjlubick): strutStyle
s['disableHinting'] = s['disableHinting'] || false;
if (s['ellipsis']) {
var str = s['ellipsis'];
@ -59,6 +70,7 @@
s['textAlign'] = s['textAlign'] || CanvasKit.TextAlign.Start;
s['textDirection'] = s['textDirection'] || CanvasKit.TextDirection.LTR;
s['textStyle'] = CanvasKit.TextStyle(s['textStyle']);
s['strutStyle'] = strutStyle(s['strutStyle']);
return s;
};
@ -73,6 +85,34 @@
return s;
}
function strutStyle(s) {
s = s || {};
s['strutEnabled'] = s['strutEnabled'] || false;
if (s['strutEnabled'] && Array.isArray(s['fontFamilies']) && s['fontFamilies'].length) {
s['_fontFamiliesPtr'] = naiveCopyStrArray(s['fontFamilies']);
s['_fontFamiliesLen'] = s['fontFamilies'].length;
} else {
s['_fontFamiliesPtr'] = nullptr;
s['_fontFamiliesLen'] = 0;
}
s['fontStyle'] = fontStyle(s['fontStyle']);
s['fontSize'] = s['fontSize'] || 0;
s['heightMultiplier'] = s['heightMultiplier'] || 0;
s['leading'] = s['leading'] || 0;
s['forceStrutHeight'] = s['forceStrutHeight'] || false;
return s;
}
function placeholderStyle(s) {
s['width'] = s['width'] || 0;
s['height'] = s['height'] || 0;
s['alignment'] = s['alignment'] || CanvasKit.PlaceholderAlignment.Baseline;
s['baseline'] = s['baseline'] || CanvasKit.TextBaseline.Alphabetic;
s['offset'] = s['offset'] || 0;
return s;
}
CanvasKit.TextStyle = function(s) {
// Use [''] to tell closure not to minify the names
if (!s['color']) {
@ -81,8 +121,49 @@
s['decoration'] = s['decoration'] || 0;
s['decorationThickness'] = s['decorationThickness'] || 0;
s['decorationStyle'] = s['decorationStyle'] || CanvasKit.DecorationStyle.Solid;
s['textBaseline'] = s['textBaseline'] || CanvasKit.TextBaseline.Alphabetic;
s['fontSize'] = s['fontSize'] || 0;
s['letterSpacing'] = s['letterSpacing'] || 0;
s['wordSpacing'] = s['wordSpacing'] || 0;
s['heightMultiplier'] = s['heightMultiplier'] || 0;
if (s['locale']) {
var str = s['locale'];
s['_localePtr'] = cacheOrCopyString(str);
s['_localeLen'] = lengthBytesUTF8(str) + 1; // add 1 for the null terminator.
} else {
s['_localePtr'] = nullptr;
s['_localeLen'] = 0;
}
s['fontStyle'] = fontStyle(s['fontStyle']);
if (s['shadows']) {
var shadows = s['shadows'];
var shadowColors = shadows.map(function(s) { return s['color'] || CanvasKit.BLACK; });
var shadowOffsets = shadows.map(function(s) { return s['offset'] || [0, 0]; });
var shadowBlurRadii = shadows.map(function(s) { return s['blurRadius'] || 0.0; });
s['_shadowLen'] = shadows.length;
s['_shadowColorsPtr'] = copyFlexibleColorArray(shadowColors).colorPtr;
s['_shadowOffsetsPtr'] = copy2dArray(shadowOffsets, 'HEAPF32');
s['_shadowBlurRadiiPtr'] = copy1dArray(shadowBlurRadii, 'HEAPF32');
} else {
s['_shadowLen'] = 0;
s['_shadowColorsPtr'] = nullptr;
s['_shadowOffsetsPtr'] = nullptr;
s['_shadowBlurRadiiPtr'] = nullptr;
}
if (s['fontFeatures']) {
var fontFeatures = s['fontFeatures'];
var fontFeatureNames = fontFeatures.map(function(s) { return s['name']; });
var fontFeatureValues = fontFeatures.map(function(s) { return s['value']; });
s['_fontFeatureLen'] = fontFeatures.length;
s['_fontFeatureNamesPtr'] = naiveCopyStrArray(fontFeatureNames);
s['_fontFeatureValuesPtr'] = copy1dArray(fontFeatureValues, 'HEAPU32');
} else {
s['_fontFeatureLen'] = 0;
s['_fontFeatureNamesPtr'] = nullptr;
s['_fontFeatureValuesPtr'] = nullptr;
}
return s;
};
@ -102,7 +183,7 @@
var strPtr = cacheOrCopyString(strings[i]);
sPtrs.push(strPtr);
}
return copy1dArray(sPtrs, "HEAPU32");
return copy1dArray(sPtrs, 'HEAPU32');
}
// maps string -> malloc'd pointer
@ -129,6 +210,7 @@
// having to free them after every invocation.
var scratchForegroundColorPtr = CanvasKit._malloc(4 * 4); // room for 4 32bit floats
var scratchBackgroundColorPtr = CanvasKit._malloc(4 * 4); // room for 4 32bit floats
var scratchDecorationColorPtr = CanvasKit._malloc(4 * 4); // room for 4 32bit floats
function copyArrays(textStyle) {
// These color fields were arrays, but will set to WASM pointers before we pass this
@ -136,12 +218,16 @@
textStyle['_colorPtr'] = copyColorToWasm(textStyle['color']);
textStyle['_foregroundColorPtr'] = nullptr; // nullptr is 0, from helper.js
textStyle['_backgroundColorPtr'] = nullptr;
textStyle['_decorationColorPtr'] = nullptr;
if (textStyle['foregroundColor']) {
textStyle['_foregroundColorPtr'] = copyColorToWasm(textStyle['foregroundColor'], scratchForegroundColorPtr);
}
if (textStyle['backgroundColor']) {
textStyle['_backgroundColorPtr'] = copyColorToWasm(textStyle['backgroundColor'], scratchBackgroundColorPtr);
}
if (textStyle['decorationColor']) {
textStyle['_decorationColorPtr'] = copyColorToWasm(textStyle['decorationColor'], scratchDecorationColorPtr);
}
if (Array.isArray(textStyle['fontFamilies']) && textStyle['fontFamilies'].length) {
textStyle['_fontFamiliesPtr'] = naiveCopyStrArray(textStyle['fontFamilies']);
@ -179,12 +265,22 @@
copyArrays(textStyle);
this._pushStyle(textStyle);
freeArrays(textStyle);
}
};
CanvasKit.ParagraphBuilder.prototype.pushPaintStyle = function(textStyle, fg, bg) {
copyArrays(textStyle);
this._pushPaintStyle(textStyle, fg, bg);
freeArrays(textStyle);
}
};
CanvasKit.ParagraphBuilder.prototype.addPlaceholder =
function(width, height, alignment, baseline, offset) {
width = width || 0;
height = height || 0;
alignment = alignment || CanvasKit.PlaceholderAlignment.Baseline;
baseline = baseline || CanvasKit.TextBaseline.Alphabetic;
offset = offset || 0;
this._addPlaceholder(width, height, alignment, baseline, offset);
};
});
}(Module)); // When this file is loaded in, the high level object is "Module";

View File

@ -30,14 +30,14 @@ namespace para = skia::textlayout;
SkColor4f toSkColor4f(uintptr_t /* float* */ cPtr) {
float* fourFloats = reinterpret_cast<float*>(cPtr);
SkColor4f color = { fourFloats[0], fourFloats[1], fourFloats[2], fourFloats[3] };
SkColor4f color = {fourFloats[0], fourFloats[1], fourFloats[2], fourFloats[3]};
return color;
}
struct SimpleFontStyle {
SkFontStyle::Slant slant;
SkFontStyle::Slant slant;
SkFontStyle::Weight weight;
SkFontStyle::Width width;
SkFontStyle::Width width;
};
struct SimpleTextStyle {
@ -46,17 +46,78 @@ struct SimpleTextStyle {
uintptr_t /* float* */ backgroundColorPtr;
uint8_t decoration;
SkScalar decorationThickness;
uintptr_t /* float* */ decorationColorPtr;
para::TextDecorationStyle decorationStyle;
para::TextBaseline textBaseline;
SkScalar fontSize;
SkScalar letterSpacing;
SkScalar wordSpacing;
SkScalar heightMultiplier;
uintptr_t /* const char* */ localePtr;
int localeLen;
SimpleFontStyle fontStyle;
uintptr_t /* const char** */ fontFamiliesPtr;
int fontFamiliesLen;
int shadowLen;
uintptr_t /* SkColor4f* */ shadowColorsPtr;
uintptr_t /* SkPoint* */ shadowOffsetsPtr;
uintptr_t /* float* */ shadowBlurRadiiPtr;
int fontFeatureLen;
uintptr_t /* float* */ fontFeatureNamesPtr;
uintptr_t /* float* */ fontFeatureValuesPtr;
};
struct SimpleStrutStyle {
uintptr_t /* const char** */ fontFamiliesPtr;
int fontFamiliesLen;
SimpleFontStyle fontStyle;
SkScalar fontSize;
SkScalar heightMultiplier;
SkScalar leading;
bool strutEnabled;
bool forceStrutHeight;
};
para::StrutStyle toStrutStyle(const SimpleStrutStyle& s) {
para::StrutStyle ss;
const char** fontFamilies = reinterpret_cast<const char**>(s.fontFamiliesPtr);
if (fontFamilies != nullptr) {
std::vector<SkString> ff;
for (int i = 0; i < s.fontFamiliesLen; i++) {
ff.emplace_back(fontFamilies[i]);
}
ss.setFontFamilies(ff);
}
SkFontStyle fs(s.fontStyle.weight, s.fontStyle.width, s.fontStyle.slant);
ss.setFontStyle(fs);
if (s.fontSize != 0) {
ss.setFontSize(s.fontSize);
}
if (s.heightMultiplier != 0) {
ss.setHeight(s.heightMultiplier);
ss.setHeightOverride(true);
}
if (s.leading != 0) {
ss.setLeading(s.leading);
}
ss.setStrutEnabled(s.strutEnabled);
ss.setForceStrutHeight(s.forceStrutHeight);
return ss;
}
para::TextStyle toTextStyle(const SimpleTextStyle& s) {
para::TextStyle ts;
// textstyle.color doesn't support a 4f color, however the foreground and background fields below do.
// textstyle.color doesn't support a 4f color, however the foreground and background fields
// below do.
ts.setColor(toSkColor4f(s.colorPtr).toSkColor());
// It is functionally important that these paints be unset when no value was provided.
@ -75,14 +136,35 @@ para::TextStyle toTextStyle(const SimpleTextStyle& s) {
if (s.fontSize != 0) {
ts.setFontSize(s.fontSize);
}
if (s.letterSpacing != 0) {
ts.setLetterSpacing(s.letterSpacing);
}
if (s.wordSpacing != 0) {
ts.setWordSpacing(s.wordSpacing);
}
if (s.heightMultiplier != 0) {
ts.setHeight(s.heightMultiplier);
ts.setHeightOverride(true);
}
ts.setDecoration(para::TextDecoration(s.decoration));
ts.setDecorationStyle(s.decorationStyle);
if (s.decorationThickness != 0) {
ts.setDecorationThicknessMultiplier(s.decorationThickness);
}
if (s.decorationColorPtr) {
ts.setDecorationColor(toSkColor4f(s.decorationColorPtr).toSkColor());
}
if (s.localeLen > 0) {
const char* localePtr = reinterpret_cast<const char*>(s.localePtr);
SkString lStr(localePtr, s.localeLen);
ts.setLocale(lStr);
}
const char** fontFamilies = reinterpret_cast<const char**>(s.fontFamiliesPtr);
if (s.fontFamiliesLen > 0 && fontFamilies != nullptr) {
if (fontFamilies != nullptr) {
std::vector<SkString> ff;
for (int i = 0; i < s.fontFamiliesLen; i++) {
ff.emplace_back(fontFamilies[i]);
@ -90,9 +172,32 @@ para::TextStyle toTextStyle(const SimpleTextStyle& s) {
ts.setFontFamilies(ff);
}
ts.setTextBaseline(s.textBaseline);
SkFontStyle fs(s.fontStyle.weight, s.fontStyle.width, s.fontStyle.slant);
ts.setFontStyle(fs);
if (s.shadowLen > 0) {
const SkColor4f* colors = reinterpret_cast<const SkColor4f*>(s.shadowColorsPtr);
const SkPoint* offsets = reinterpret_cast<const SkPoint*>(s.shadowOffsetsPtr);
const float* blurRadii = reinterpret_cast<const float*>(s.shadowBlurRadiiPtr);
for (int i = 0; i < s.shadowLen; i++) {
para::TextShadow shadow(colors[i].toSkColor(), offsets[i], blurRadii[i]);
ts.addShadow(shadow);
}
}
if (s.fontFeatureLen > 0) {
const char** fontFeatureNames = reinterpret_cast<const char**>(s.fontFeatureNamesPtr);
const int* fontFeatureValues = reinterpret_cast<const int*>(s.fontFeatureValuesPtr);
for (int i = 0; i < s.fontFeatureLen; i++) {
// Font features names are 4-character simple strings.
SkString name(fontFeatureNames[i], 4);
ts.addFontFeature(name, fontFeatureValues[i]);
}
}
return ts;
}
@ -105,6 +210,7 @@ struct SimpleParagraphStyle {
para::TextAlign textAlign;
para::TextDirection textDirection;
SimpleTextStyle textStyle;
SimpleStrutStyle strutStyle;
};
para::ParagraphStyle toParagraphStyle(const SimpleParagraphStyle& s) {
@ -122,6 +228,8 @@ para::ParagraphStyle toParagraphStyle(const SimpleParagraphStyle& s) {
ps.setTextDirection(s.textDirection);
auto ts = toTextStyle(s.textStyle);
ps.setTextStyle(ts);
auto ss = toStrutStyle(s.strutStyle);
ps.setStrutStyle(ss);
if (s.heightMultiplier != 0) {
ps.setHeight(s.heightMultiplier);
}
@ -139,15 +247,13 @@ struct SimpleTextBox {
SkScalar direction;
};
Float32Array GetRectsForRange(para::ParagraphImpl& self, unsigned start, unsigned end,
para::RectHeightStyle heightStyle, para::RectWidthStyle widthStyle) {
std::vector<para::TextBox> boxes = self.getRectsForRange(start, end, heightStyle, widthStyle);
Float32Array TextBoxesToFloat32Array(std::vector<para::TextBox> boxes) {
// Pack these text boxes into an array of n groups of 5 SkScalar (floats)
if (!boxes.size()) {
return emscripten::val::null();
}
SimpleTextBox* rects = new SimpleTextBox[boxes.size()];
for (int i = 0; i< boxes.size(); i++) {
for (int i = 0; i < boxes.size(); i++) {
rects[i].rect = boxes[i].rect;
if (boxes[i].direction == para::TextDirection::kRtl) {
rects[i].direction = 0;
@ -158,7 +264,21 @@ Float32Array GetRectsForRange(para::ParagraphImpl& self, unsigned start, unsigne
float* fPtr = reinterpret_cast<float*>(rects);
// Of note: now that we have cast rects to float*, emscripten is smart enough to wrap this
// into a Float32Array for us.
return Float32Array(typed_memory_view(boxes.size()*5, fPtr));
return Float32Array(typed_memory_view(boxes.size() * 5, fPtr));
}
Float32Array GetRectsForRange(para::ParagraphImpl& self,
unsigned start,
unsigned end,
para::RectHeightStyle heightStyle,
para::RectWidthStyle widthStyle) {
std::vector<para::TextBox> boxes = self.getRectsForRange(start, end, heightStyle, widthStyle);
return TextBoxesToFloat32Array(boxes);
}
Float32Array GetRectsForPlaceholders(para::ParagraphImpl& self) {
std::vector<para::TextBox> boxes = self.getRectsForPlaceholders();
return TextBoxesToFloat32Array(boxes);
}
EMSCRIPTEN_BINDINGS(Paragraph) {
@ -179,45 +299,72 @@ EMSCRIPTEN_BINDINGS(Paragraph) {
.function("getMaxWidth", &para::Paragraph::getMaxWidth)
.function("getMinIntrinsicWidth", &para::Paragraph::getMinIntrinsicWidth)
.function("_getRectsForRange", &GetRectsForRange)
.function("_getRectsForPlaceholders", &GetRectsForPlaceholders)
.function("getWordBoundary", &para::ParagraphImpl::getWordBoundary)
.function("layout", &para::ParagraphImpl::layout);
.function("layout", &para::ParagraphImpl::layout)
.function("getLineMetrics", &para::ParagraphImpl::getLineMetrics);
class_<para::ParagraphBuilderImpl>("ParagraphBuilder")
.class_function("_Make", optional_override([](SimpleParagraphStyle style, sk_sp<SkFontMgr> fontMgr)
-> std::unique_ptr<para::ParagraphBuilderImpl> {
auto fc = sk_make_sp<para::FontCollection>();
fc->setDefaultFontManager(fontMgr);
auto ps = toParagraphStyle(style);
auto pb = para::ParagraphBuilderImpl::make(ps, fc);
return std::unique_ptr<para::ParagraphBuilderImpl>(static_cast<para::ParagraphBuilderImpl*>(pb.release()));
}), allow_raw_pointers())
.class_function("_MakeFromFontProvider", optional_override([](SimpleParagraphStyle style,
sk_sp<para::TypefaceFontProvider> fontProvider)-> std::unique_ptr<para::ParagraphBuilderImpl> {
auto fc = sk_make_sp<para::FontCollection>();
fc->setDefaultFontManager(fontProvider);
auto ps = toParagraphStyle(style);
auto pb = para::ParagraphBuilderImpl::make(ps, fc);
return std::unique_ptr<para::ParagraphBuilderImpl>(static_cast<para::ParagraphBuilderImpl*>(pb.release()));
}), allow_raw_pointers())
.function("addText", optional_override([](para::ParagraphBuilderImpl& self, std::string text) {
return self.addText(text.c_str(), text.length());
}))
.function("build", &para::ParagraphBuilderImpl::Build, allow_raw_pointers())
.function("pop", &para::ParagraphBuilderImpl::pop)
.function("_pushStyle", optional_override([](para::ParagraphBuilderImpl& self,
SimpleTextStyle textStyle) {
auto ts = toTextStyle(textStyle);
self.pushStyle(ts);
}))
// A method of pushing a textStyle with paints instead of colors for foreground and
// background. Since SimpleTextStyle is a value object, it cannot contain paints, which are not primitives. This binding is here to accept them. Any color that is specified in the textStyle is overridden.
.function("_pushPaintStyle", optional_override([](para::ParagraphBuilderImpl& self,
SimpleTextStyle textStyle, SkPaint foreground, SkPaint background) {
auto ts = toTextStyle(textStyle);
ts.setForegroundColor(foreground);
ts.setBackgroundColor(background);
self.pushStyle(ts);
}));
.class_function(
"_Make",
optional_override([](SimpleParagraphStyle style, sk_sp<SkFontMgr> fontMgr)
-> std::unique_ptr<para::ParagraphBuilderImpl> {
auto fc = sk_make_sp<para::FontCollection>();
fc->setDefaultFontManager(fontMgr);
fc->enableFontFallback();
auto ps = toParagraphStyle(style);
auto pb = para::ParagraphBuilderImpl::make(ps, fc);
return std::unique_ptr<para::ParagraphBuilderImpl>(
static_cast<para::ParagraphBuilderImpl*>(pb.release()));
}),
allow_raw_pointers())
.class_function(
"_MakeFromFontProvider",
optional_override([](SimpleParagraphStyle style,
sk_sp<para::TypefaceFontProvider> fontProvider)
-> std::unique_ptr<para::ParagraphBuilderImpl> {
auto fc = sk_make_sp<para::FontCollection>();
fc->setDefaultFontManager(fontProvider);
fc->enableFontFallback();
auto ps = toParagraphStyle(style);
auto pb = para::ParagraphBuilderImpl::make(ps, fc);
return std::unique_ptr<para::ParagraphBuilderImpl>(
static_cast<para::ParagraphBuilderImpl*>(pb.release()));
}),
allow_raw_pointers())
.function("addText",
optional_override([](para::ParagraphBuilderImpl& self, std::string text) {
return self.addText(text.c_str(), text.length());
}))
.function("build", &para::ParagraphBuilderImpl::Build, allow_raw_pointers())
.function("pop", &para::ParagraphBuilderImpl::pop)
.function("_pushStyle", optional_override([](para::ParagraphBuilderImpl& self,
SimpleTextStyle textStyle) {
auto ts = toTextStyle(textStyle);
self.pushStyle(ts);
}))
// A method of pushing a textStyle with paints instead of colors for foreground and
// background. Since SimpleTextStyle is a value object, it cannot contain paints, which
// are not primitives. This binding is here to accept them. Any color that is specified
// in the textStyle is overridden.
.function("_pushPaintStyle",
optional_override([](para::ParagraphBuilderImpl& self,
SimpleTextStyle textStyle, SkPaint foreground,
SkPaint background) {
auto ts = toTextStyle(textStyle);
ts.setForegroundColor(foreground);
ts.setBackgroundColor(background);
self.pushStyle(ts);
}))
.function("_addPlaceholder", optional_override([](para::ParagraphBuilderImpl& self,
SkScalar width,
SkScalar height,
para::PlaceholderAlignment alignment,
para::TextBaseline baseline,
SkScalar offset) {
para::PlaceholderStyle ps(width, height, alignment, baseline, offset);
self.addPlaceholder(ps);
}));
class_<para::TypefaceFontProvider, base<SkFontMgr>>("TypefaceFontProvider")
.smart_ptr<sk_sp<para::TypefaceFontProvider>>("sk_sp<TypefaceFontProvider>")
@ -289,6 +436,24 @@ EMSCRIPTEN_BINDINGS(Paragraph) {
.value("LTR", para::TextDirection::kLtr)
.value("RTL", para::TextDirection::kRtl);
enum_<para::TextDecorationStyle>("DecorationStyle")
.value("Solid", para::TextDecorationStyle::kSolid)
.value("Double", para::TextDecorationStyle::kDouble)
.value("Dotted", para::TextDecorationStyle::kDotted)
.value("Dashed", para::TextDecorationStyle::kDashed)
.value("Wavy", para::TextDecorationStyle::kWavy);
enum_<para::PlaceholderAlignment>("PlaceholderAlignment")
.value("Baseline", para::PlaceholderAlignment::kBaseline)
.value("AboveBaseline", para::PlaceholderAlignment::kAboveBaseline)
.value("BelowBaseline", para::PlaceholderAlignment::kBelowBaseline)
.value("Top", para::PlaceholderAlignment::kTop)
.value("Bottom", para::PlaceholderAlignment::kBottom)
.value("Middle", para::PlaceholderAlignment::kMiddle);
enum_<para::TextBaseline>("TextBaseline")
.value("Alphabetic", para::TextBaseline::kAlphabetic)
.value("Ideographic", para::TextBaseline::kIdeographic);
value_object<para::PositionWithAffinity>("PositionWithAffinity")
.field("pos", &para::PositionWithAffinity::position)
@ -307,18 +472,43 @@ EMSCRIPTEN_BINDINGS(Paragraph) {
.field("maxLines", &SimpleParagraphStyle::maxLines)
.field("textAlign", &SimpleParagraphStyle::textAlign)
.field("textDirection", &SimpleParagraphStyle::textDirection)
.field("textStyle", &SimpleParagraphStyle::textStyle);
.field("textStyle", &SimpleParagraphStyle::textStyle)
.field("strutStyle", &SimpleParagraphStyle::strutStyle);
value_object<SimpleStrutStyle>("StrutStyle")
.field("_fontFamiliesPtr", &SimpleStrutStyle::fontFamiliesPtr)
.field("_fontFamiliesLen", &SimpleStrutStyle::fontFamiliesLen)
.field("strutEnabled", &SimpleStrutStyle::strutEnabled)
.field("fontSize", &SimpleStrutStyle::fontSize)
.field("fontStyle", &SimpleStrutStyle::fontStyle)
.field("heightMultiplier", &SimpleStrutStyle::heightMultiplier)
.field("leading", &SimpleStrutStyle::leading)
.field("forceStrutHeight", &SimpleStrutStyle::forceStrutHeight);
value_object<SimpleTextStyle>("TextStyle")
.field("_colorPtr", &SimpleTextStyle::colorPtr)
.field("_foregroundColorPtr", &SimpleTextStyle::foregroundColorPtr)
.field("_backgroundColorPtr", &SimpleTextStyle::backgroundColorPtr)
.field("decoration", &SimpleTextStyle::decoration)
.field("decorationThickness", &SimpleTextStyle::decorationThickness)
.field("_fontFamiliesPtr", &SimpleTextStyle::fontFamiliesPtr)
.field("_fontFamiliesLen", &SimpleTextStyle::fontFamiliesLen)
.field("fontSize", &SimpleTextStyle::fontSize)
.field("fontStyle", &SimpleTextStyle::fontStyle);
.field("_colorPtr", &SimpleTextStyle::colorPtr)
.field("_foregroundColorPtr", &SimpleTextStyle::foregroundColorPtr)
.field("_backgroundColorPtr", &SimpleTextStyle::backgroundColorPtr)
.field("decoration", &SimpleTextStyle::decoration)
.field("decorationThickness", &SimpleTextStyle::decorationThickness)
.field("_decorationColorPtr", &SimpleTextStyle::decorationColorPtr)
.field("decorationStyle", &SimpleTextStyle::decorationStyle)
.field("_fontFamiliesPtr", &SimpleTextStyle::fontFamiliesPtr)
.field("_fontFamiliesLen", &SimpleTextStyle::fontFamiliesLen)
.field("fontSize", &SimpleTextStyle::fontSize)
.field("letterSpacing", &SimpleTextStyle::letterSpacing)
.field("wordSpacing", &SimpleTextStyle::wordSpacing)
.field("heightMultiplier", &SimpleTextStyle::heightMultiplier)
.field("_localePtr", &SimpleTextStyle::localePtr)
.field("_localeLen", &SimpleTextStyle::localeLen)
.field("fontStyle", &SimpleTextStyle::fontStyle)
.field("_shadowLen", &SimpleTextStyle::shadowLen)
.field("_shadowColorsPtr", &SimpleTextStyle::shadowColorsPtr)
.field("_shadowOffsetsPtr", &SimpleTextStyle::shadowOffsetsPtr)
.field("_shadowBlurRadiiPtr", &SimpleTextStyle::shadowBlurRadiiPtr)
.field("_fontFeatureLen", &SimpleTextStyle::fontFeatureLen)
.field("_fontFeatureNamesPtr", &SimpleTextStyle::fontFeatureNamesPtr)
.field("_fontFeatureValuesPtr", &SimpleTextStyle::fontFeatureValuesPtr);
// The U stands for unsigned - we can't bind a generic/template object, so we have to specify it
// with the type we are using.

View File

@ -23,6 +23,13 @@ describe('Paragraph Behavior', function() {
emojiFontBuffer = buffer;
});
let robotoFontBuffer = null;
const robotoFontLoaded = fetch('/assets/Roboto-Regular.otf').then(
(response) => response.arrayBuffer()).then(
(buffer) => {
robotoFontBuffer = buffer;
});
beforeEach(async () => {
await LoadCanvasKit;
await notoSerifFontLoaded;
@ -99,6 +106,7 @@ describe('Paragraph Behavior', function() {
end: 26,
});
canvas.clear(CanvasKit.WHITE);
canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, 230), paint);
canvas.drawParagraph(paragraph, 10, 10);
@ -132,6 +140,7 @@ describe('Paragraph Behavior', function() {
const paragraph = builder.build();
paragraph.layout(300);
canvas.clear(CanvasKit.WHITE);
canvas.drawParagraph(paragraph, 10, 10);
fontMgr.delete();
@ -169,6 +178,7 @@ describe('Paragraph Behavior', function() {
const paragraph = builder.build();
paragraph.layout(300);
canvas.clear(CanvasKit.WHITE);
canvas.drawParagraph(paragraph, 10, 10);
// Again 5px to the right so you can tell the fill is transparent
canvas.drawParagraph(paragraph, 15, 10);
@ -180,6 +190,222 @@ describe('Paragraph Behavior', function() {
builder.delete();
});
gm('paragraph_letter_word_spacing', (canvas) => {
const fontMgr = CanvasKit.SkFontMgr.FromData(notoSerifFontBuffer);
expect(fontMgr.countFamilies()).toEqual(1);
expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
const wrapTo = 200;
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
// color should default to black
fontFamilies: ['Noto Serif'],
fontSize: 20,
letterSpacing: 5,
wordSpacing: 10,
},
textAlign: CanvasKit.TextAlign.Center,
});
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText(
'This text should have a lot of space between the letters and words.');
const paragraph = builder.build();
paragraph.layout(300);
canvas.clear(CanvasKit.WHITE);
canvas.drawParagraph(paragraph, 10, 10);
fontMgr.delete();
paragraph.delete();
builder.delete();
});
gm('paragraph_shadows', (canvas) => {
const fontMgr = CanvasKit.SkFontMgr.FromData(notoSerifFontBuffer);
expect(fontMgr.countFamilies()).toEqual(1);
expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
const wrapTo = 200;
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
color: CanvasKit.WHITE,
fontFamilies: ['Noto Serif'],
fontSize: 20,
shadows: [{color: CanvasKit.BLACK, blurRadius: 15},
{color: CanvasKit.RED, blurRadius: 5, offset: [10, 10]}],
},
textAlign: CanvasKit.TextAlign.Center,
});
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText('This text should have a shadow behind it.');
const paragraph = builder.build();
paragraph.layout(300);
canvas.clear(CanvasKit.WHITE);
canvas.drawParagraph(paragraph, 10, 10);
fontMgr.delete();
paragraph.delete();
builder.delete();
});
gm('paragraph_strut_style', (canvas) => {
const fontMgr = CanvasKit.SkFontMgr.FromData(robotoFontBuffer);
expect(fontMgr.countFamilies()).toEqual(1);
expect(fontMgr.getFamilyName(0)).toEqual('Roboto');
// The lines in this paragraph should have the same height despite the third
// line having a larger font size.
const paraStrutStyle = new CanvasKit.ParagraphStyle({
textStyle: {
fontFamilies: ['Roboto'],
color: CanvasKit.BLACK,
},
strutStyle: {
strutEnabled: true,
fontFamilies: ['Roboto'],
fontSize: 28,
heightMultiplier: 1.5,
forceStrutHeight: true,
},
});
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
fontFamilies: ['Roboto'],
color: CanvasKit.BLACK,
},
});
const roboto28Style = new CanvasKit.TextStyle({
color: CanvasKit.BLACK,
fontFamilies: ['Roboto'],
fontSize: 28,
});
const roboto32Style = new CanvasKit.TextStyle({
color: CanvasKit.BLACK,
fontFamilies: ['Roboto'],
fontSize: 32,
});
const builder = CanvasKit.ParagraphBuilder.Make(paraStrutStyle, fontMgr);
builder.pushStyle(roboto28Style);
builder.addText('This paragraph\n');
builder.pushStyle(roboto32Style);
builder.addText('is using\n');
builder.pop();
builder.pushStyle(roboto28Style);
builder.addText('a strut style!\n');
builder.pop();
builder.pop();
const builder2 = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder2.pushStyle(roboto28Style);
builder2.addText('This paragraph\n');
builder2.pushStyle(roboto32Style);
builder2.addText('is not using\n');
builder2.pop();
builder2.pushStyle(roboto28Style);
builder2.addText('a strut style!\n');
builder2.pop();
builder2.pop();
const paragraph = builder.build();
paragraph.layout(300);
const paragraph2 = builder2.build();
paragraph2.layout(300);
canvas.clear(CanvasKit.WHITE);
canvas.drawParagraph(paragraph, 10, 10);
canvas.drawParagraph(paragraph2, 220, 10);
fontMgr.delete();
paragraph.delete();
builder.delete();
});
gm('paragraph_font_features', (canvas) => {
const fontMgr = CanvasKit.SkFontMgr.FromData(robotoFontBuffer);
expect(fontMgr.countFamilies()).toEqual(1);
expect(fontMgr.getFamilyName(0)).toEqual('Roboto');
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
color: CanvasKit.BLACK,
fontFamilies: ['Roboto'],
fontSize: 30,
fontFeatures: [{name: 'smcp', value: 1}]
},
textAlign: CanvasKit.TextAlign.Center,
});
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText('This Text Should Be In Small Caps');
const paragraph = builder.build();
paragraph.layout(300);
canvas.clear(CanvasKit.WHITE);
canvas.drawParagraph(paragraph, 10, 10);
fontMgr.delete();
paragraph.delete();
builder.delete();
});
gm('paragraph_placeholders', (canvas) => {
const fontMgr = CanvasKit.SkFontMgr.FromData(robotoFontBuffer);
expect(fontMgr.countFamilies()).toEqual(1);
expect(fontMgr.getFamilyName(0)).toEqual('Roboto');
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
color: CanvasKit.BLACK,
fontFamilies: ['Roboto'],
fontSize: 20,
},
textAlign: CanvasKit.TextAlign.Center,
});
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText('There should be ');
builder.addPlaceholder(10, 10, CanvasKit.PlaceholderAlignment.AboveBaseline,
CanvasKit.TextBaseline.Ideographic);
builder.addText('a space in this sentence.\n');
builder.addText('There should be ');
builder.addPlaceholder(10, 10, CanvasKit.PlaceholderAlignment.BelowBaseline,
CanvasKit.TextBaseline.Ideographic);
builder.addText('a dropped space in this sentence.\n');
builder.addText('There should be ');
builder.addPlaceholder(10, 10, null, null, 20);
builder.addText('an offset space in this sentence.\n');
const paragraph = builder.build();
paragraph.layout(300);
let rects = paragraph.getRectsForPlaceholders();
canvas.clear(CanvasKit.WHITE);
canvas.drawParagraph(paragraph, 10, 10);
for (const rect of rects) {
const p = new CanvasKit.SkPaint();
p.setColor(CanvasKit.Color(0, 0, 255));
p.setStyle(CanvasKit.PaintStyle.Stroke);
// Account for the (10, 10) offset when we painted the paragraph.
const placeholder =
CanvasKit.LTRBRect(rect[0]+10,rect[1]+10,rect[2]+10,rect[3]+10);
canvas.drawRect(placeholder, p);
p.delete();
}
fontMgr.delete();
paragraph.delete();
builder.delete();
});
// loosely based on SkParagraph_GetRectsForRangeParagraph test in c++ code.
gm('paragraph_rects', (canvas) => {
const fontMgr = CanvasKit.SkFontMgr.FromData(notoSerifFontBuffer);
@ -244,6 +470,7 @@ describe('Paragraph Behavior', function() {
color: CanvasKit.Color(0, 200, 200),
}
];
canvas.clear(CanvasKit.WHITE);
// Move it down a bit so we can see the rects that go above 0,0
canvas.translate(10, 10);
canvas.drawParagraph(paragraph, 0, 0);
@ -306,6 +533,7 @@ describe('Paragraph Behavior', function() {
paragraph.layout(wrapTo);
canvas.clear(CanvasKit.WHITE);
canvas.drawParagraph(paragraph, 10, 10);
const paint = new CanvasKit.SkPaint();
@ -339,6 +567,7 @@ describe('Paragraph Behavior', function() {
paragraph.layout(wrapTo);
canvas.clear(CanvasKit.WHITE);
canvas.translate(10, 10);
canvas.drawParagraph(paragraph, 0, 0);
@ -419,7 +648,7 @@ describe('Paragraph Behavior', function() {
paragraph.layout(wrapTo);
canvas.clear(CanvasKit.Color(250, 250, 250));
canvas.clear(CanvasKit.WHITE);
canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, wrapTo+10), paint);
canvas.drawParagraph(paragraph, 10, 10);
@ -476,7 +705,7 @@ describe('Paragraph Behavior', function() {
paragraph.layout(wrapTo);
canvas.clear(CanvasKit.Color(250, 250, 250));
canvas.clear(CanvasKit.WHITE);
canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, wrapTo+10), paint);
canvas.drawParagraph(paragraph, 10, 10);
@ -487,6 +716,77 @@ describe('Paragraph Behavior', function() {
fontMgr.delete();
});
gm('paragraph_text_styles', (canvas) => {
const paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.GREEN);
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const fontMgr = CanvasKit.SkFontMgr.FromData(notoSerifFontBuffer);
expect(fontMgr.countFamilies()).toEqual(1);
expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
const wrapTo = 200;
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
color: CanvasKit.BLACK,
fontFamilies: ['Noto Serif'],
fontSize: 20,
decoration: CanvasKit.UnderlineDecoration,
decorationThickness: 1.5, // multiplier based on font size
decorationStyle: CanvasKit.DecorationStyle.Wavy,
},
});
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText('VAVAVAVAVAVAVA\nVAVA\n');
const blueText = new CanvasKit.TextStyle({
backgroundColor: CanvasKit.Color(234, 208, 232), // light pink
color: CanvasKit.Color(48, 37, 199),
fontFamilies: ['Noto Serif'],
textBaseline: CanvasKit.TextBaseline.Ideographic,
decoration: CanvasKit.LineThroughDecoration,
decorationThickness: 1.5, // multiplier based on font size
});
builder.pushStyle(blueText);
builder.addText(`Gosh I hope this wraps at some point, it is such a long line.`);
builder.pop();
builder.addText(` I'm done with the blue now. `);
builder.addText(`Now I hope we should stop before we get 8 lines tall. `);
const paragraph = builder.build();
paragraph.layout(wrapTo);
expect(paragraph.getAlphabeticBaseline()).toBeCloseTo(21.377, 3);
expect(paragraph.getHeight()).toEqual(227);
expect(paragraph.getIdeographicBaseline()).toBeCloseTo(27.236, 3);
expect(paragraph.getLongestLine()).toBeCloseTo(195.664, 3);
expect(paragraph.getMaxIntrinsicWidth()).toBeCloseTo(1167.140, 3);
expect(paragraph.getMaxWidth()).toEqual(200);
expect(paragraph.getMinIntrinsicWidth()).toBeCloseTo(172.360, 3);
// Check "VAVAVAVAVAVAVA"
expect(paragraph.getWordBoundary(8)).toEqual({
start: 0,
end: 14,
});
// Check "I"
expect(paragraph.getWordBoundary(25)).toEqual({
start: 25,
end: 26,
});
canvas.clear(CanvasKit.WHITE);
canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, 230), paint);
canvas.drawParagraph(paragraph, 10, 10);
paint.delete();
fontMgr.delete();
paragraph.delete();
builder.delete();
});
it('should not crash if we omit font family on pushed textStyle', () => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface');
@ -527,7 +827,7 @@ describe('Paragraph Behavior', function() {
paragraph.layout(wrapTo);
canvas.clear(CanvasKit.Color(250, 250, 250));
canvas.clear(CanvasKit.WHITE);
canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, wrapTo+10), paint);
canvas.drawParagraph(paragraph, 10, 10);
@ -578,7 +878,7 @@ describe('Paragraph Behavior', function() {
paragraph.layout(wrapTo);
canvas.clear(CanvasKit.Color(250, 250, 250));
canvas.clear(CanvasKit.WHITE);
canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, wrapTo+10), paint);
canvas.drawParagraph(paragraph, 10, 10);

View File

@ -90,7 +90,7 @@ function _report(data, outputType, testname) {
function reportError(done) {
return (e) => {
console.log("Error with fetching. Likely could not connect to aggegator server", e.message);
console.log("Error with fetching. Likely could not connect to aggregator server", e.message);
if (fail_on_no_gold) {
expect(e).toBeUndefined();
}
@ -126,4 +126,4 @@ function catchException(done, fn) {
// that would make the break the asynchronous nature
// of fn().
}
}
}

View File

@ -580,7 +580,7 @@ std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
return results;
}
ensureUTF16Mapping();
ensureUTF16Mapping();
if (start >= end || start > fUTF8IndexForUTF16Index.size() || end == 0) {
return results;