Try new factory for ShapedLines

Known hacks for now:
- only default typeface
- only ascii support

Fonts have several limitations for now.

SkParagraph (our temporary backend) wants a fontmgr up front,
rather than just relying on the typefaces in its styles. This makes it
hard to inter-op with our JS api. Not a long-term problem, as this
backend is short-term. The hacky work-around is just to pass NULL for
the typeface for now.

Related but different, I haven't yet figured out how to "return" a
JS Typeface object. Something about bare-pointers and sk_sp. I'll figure
it out eventually, but for now I just set the output slot to null
as well. (there's a TODO in the .cpp)

Ascii support

JS strings are UTF16 (afaik). However, our current backend thinks in
terms of UTF8 offsets. Plus, (at the moment) when I try to access the
JS string for C++, it coerces to utf8 (std::string).

We can fix both of these eventually, but for now I'll just test with
ascii input characters, and the API can still work.


Change-Id: I62ca71b0b45d017ac8e3881c06720776dc2d75a1
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/405400
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Julia Lavrova <jlavrova@google.com>
This commit is contained in:
Mike Reed 2021-05-06 18:12:27 -04:00 committed by Skia Commit-Bot
parent 2ed22faaa1
commit c48c3e5d57
6 changed files with 151 additions and 24 deletions

View File

@ -146,6 +146,7 @@ var CanvasKit = {
ParagraphBuilder: {
Make: function() {},
MakeFromFontProvider: function() {},
ShapeText: function() {},
addText: function() {},
build: function() {},
pop: function() {},
@ -159,6 +160,7 @@ var CanvasKit = {
// private API
_Make: function() {},
_MakeFromFontProvider: function() {},
_ShapeText: function() {},
_pushStyle: function() {},
_pushPaintStyle: function() {},
_addPlaceholder: function() {},

View File

@ -311,9 +311,9 @@
LOC_Y = 20;
const WIDTH = 400;
const tf = fontMgr.MakeTypefaceFromData(fontData);
const font = new CanvasKit.Font(tf, TEXTSIZE);
const fm = font.getMetrics();
// const tf = fontMgr.MakeTypefaceFromData(fontData);
const tf = null; // trouble with skparagraph fontmgr (for now)
const runFont = new CanvasKit.Font(tf, TEXTSIZE);
const fontPaint = new CanvasKit.Paint();
fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
fontPaint.setAntiAlias(true);
@ -321,7 +321,7 @@
const bgPaint = new CanvasKit.Paint();
bgPaint.setColor([0.965, 0.965, 0.965, 1]);
const editor = MakeEditor(text0, cursor, WIDTH, builder);
const editor = MakeEditor(text0, tf, cursor, WIDTH, builder);
function drawFrame(canvas) {
const lines = editor.getLines();
@ -347,7 +347,11 @@
cursor.draw_before(canvas);
for (const l of lines) {
for (let r of l.runs) {
canvas.drawGlyphs(r.glyphs, r.positions, 0, 0, font, fontPaint);
runFont.setTypeface(tf); // r.typeface is always null right now
runFont.setSize(r.size);
// runFont.setEmbolden(r.fakeBold); // todo: expose embolden in CK
runFont.setSkewX(r.fakeItalic ? -0.2 : 0);
canvas.drawGlyphs(r.glyphs, r.positions, 0, 0, runFont, fontPaint);
}
}
cursor.draw_after(canvas);

View File

@ -175,9 +175,10 @@ function string_del(str, start, end) {
return str.slice(0, start) + str.slice(end, str.length);
}
function MakeEditor(text, cursor, width, builder) {
function MakeEditor(text, typeface, cursor, width, builder) {
const ed = {
_text: text,
_typeface: typeface,
_lines: null,
_builder: builder,
_cursor: cursor,
@ -232,21 +233,32 @@ function MakeEditor(text, cursor, width, builder) {
this.setIndex(index);
},
_buildLines: function() {
const builder = this._builder();
builder.addText(this._text);
const paragraph = builder.build();
paragraph.layout(this._width);
const rec = new CanvasKit.PictureRecorder();
const can = rec.beginRecording([0,0,9999,9999]);
can.drawParagraph(paragraph, 0, 0);
rec.delete();
this._lines = paragraph.getShapedLines();
if (!this._lines) throw "null lines";
paragraph.delete();
builder.delete();
const blocks = [];
blocks.push({
length: 1,
typeface: this._typeface,
size: 100,
// fakeBold, fakeItalic
});
blocks.push({
length: 37,
typeface: this._typeface,
size: 24,
// fakeBold, fakeItalic
});
blocks.push({
length: 6,
typeface: this._typeface,
size: 24,
fakeItalic: true,
});
blocks.push({
length: this._text.length - 1-37-6,
typeface: this._typeface,
size: 24,
// fakeBold, fakeItalic
});
this._lines = CanvasKit.ParagraphBuilder.ShapeText(this._text, blocks, this._width);
},
deleteSelection: function() {
let start = this._index.start;

View File

@ -666,6 +666,11 @@ export interface Range {
* to describe the location "after" the last glyph in the glyphs array.
*/
export interface GlyphRun {
typeface: Typeface; // currently set to null (temporary)
size: number;
fakeBold: Boolean;
fakeItalic: Boolean;
glyphs: Uint16Array;
positions: Float32Array; // alternating x0, y0, x1, y1, ...
offsets: Uint32Array;
@ -683,6 +688,18 @@ export interface GlyphRun {
runs: GlyphRun[]; // array of GlyphRun objects for the line
}
/**
* Input to ShapeText(..., FontBlock[], ...);
*/
export interface FontBlock {
length: number; // number of text codepoints this block is applied to
typeface: Typeface;
size: number;
fakeBold: Boolean;
fakeItalic: Boolean;
}
/**
* This object is a wrapper around a pointer to some memory on the WASM heap. The type of the
* pointer was determined at creation time.
@ -3011,6 +3028,11 @@ export interface ParagraphBuilderFactory {
* @param fontSrc
*/
MakeFromFontProvider(style: ParagraphStyle, fontSrc: TypefaceFontProvider): ParagraphBuilder;
/**
* Return a shaped array of lines
*/
ShapeText(text: string, runs: FontBlock[], width?: number): ShapedLine[];
}
export interface ParagraphStyleConstructor {

View File

@ -264,6 +264,17 @@
return result;
};
CanvasKit.ParagraphBuilder.ShapeText = function(text, blocks, width) {
let length = 0;
for (const b of blocks) {
length += b.length;
}
if (length !== text.length) {
throw "Accumulated block lengths must equal text.length";
}
return CanvasKit.ParagraphBuilder._ShapeText(text, blocks, width);
};
CanvasKit.ParagraphBuilder.prototype.pushStyle = function(textStyle) {
copyArrays(textStyle);
this._pushStyle(textStyle);

View File

@ -7,6 +7,7 @@
#include "include/core/SkColor.h"
#include "include/core/SkFontStyle.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkString.h"
#include "modules/skparagraph/include/DartTypes.h"
@ -338,6 +339,7 @@ JSArray GetShapedLines(para::Paragraph& self) {
self.visit([&](int lineNumber, const para::Paragraph::VisitorInfo* info) {
if (!info) {
if (!jline) return; // how???
// end of current line
JSObject range = emscripten::val::object();
range.set("first", accum.minOffset);
@ -369,9 +371,17 @@ JSArray GetShapedLines(para::Paragraph& self) {
JSObject jrun = emscripten::val::object();
jrun.set("flags", info->flags);
jrun.set("glyphs", MakeTypedArray(N, info->glyphs, "Uint16Array"));
jrun.set("offsets", MakeTypedArray(N1, info->utf8Starts, "Uint32Array"));
jrun.set("flags", info->flags);
// TODO: figure out how to set a wrapped sk_sp<SkTypeface>
// jrun.set("typeface", info->font.getTypeface());
jrun.set("typeface", emscripten::val::null());
jrun.set("size", info->font.getSize());
jrun.set("fakeBold", info->font.isEmbolden());
jrun.set("fakeItalic", info->font.getSkewX() != 0);
jrun.set("glyphs", MakeTypedArray(N, info->glyphs, "Uint16Array"));
jrun.set("offsets", MakeTypedArray(N1, info->utf8Starts, "Uint32Array"));
// we need to modify the positions, so make a temp copy
SkAutoSTMalloc<32, SkPoint> positions(N1);
@ -446,6 +456,72 @@ EMSCRIPTEN_BINDINGS(Paragraph) {
static_cast<para::ParagraphBuilderImpl*>(pb.release()));
}),
allow_raw_pointers())
.class_function(
"_ShapeText",
optional_override([](JSString jtext, JSArray jruns, float width) -> JSArray {
std::string textStorage = jtext.as<std::string>();
const char* text = textStorage.data();
size_t textCount = textStorage.size();
auto fc = sk_make_sp<para::FontCollection>();
fc->setDefaultFontManager(SkFontMgr::RefDefault());
fc->enableFontFallback();
para::ParagraphStyle pstyle;
{
// For the most part this is ignored, since we set an explicit TextStyle
// for all of our text runs, but it is required by SkParagraph.
para::TextStyle style;
style.setFontFamilies({SkString("sans-serif")});
style.setFontSize(32);
pstyle.setTextStyle(style);
}
auto pb = para::ParagraphBuilder::make(pstyle, fc);
// tease apart the FontBlock runs
size_t runCount = jruns["length"].as<size_t>();
for (size_t i = 0; i < runCount; ++i) {
emscripten::val r = jruns[i];
para::TextStyle style;
style.setTypeface(r["typeface"].as< sk_sp<SkTypeface> >());
style.setFontSize(r["size"].as<float>());
style.setFontStyle({
r["fakeBold"].as<bool>() ? SkFontStyle::kBold_Weight
: SkFontStyle::kNormal_Weight,
SkFontStyle::kNormal_Width,
r["fakeItalic"].as<bool>() ? SkFontStyle::kItalic_Slant
: SkFontStyle::kUpright_Slant,
});
const size_t subTextCount = r["length"].as<size_t>();
if (subTextCount > textCount) {
return emscripten::val("block runs exceed text length!");
}
pb->pushStyle(style);
pb->addText(text, subTextCount);
pb->pop();
text += subTextCount;
textCount -= subTextCount;
}
if (textCount != 0) {
return emscripten::val("Didn't have enough block runs to cover text");
}
auto pa = pb->Build();
pa->layout(width);
// workaround until this is fixed in SkParagraph
{
SkPictureRecorder rec;
pa->paint(rec.beginRecording({0,0,9999,9999}), 0, 0);
}
return GetShapedLines(*pa);
}),
allow_raw_pointers())
.function("addText",
optional_override([](para::ParagraphBuilderImpl& self, std::string text) {
return self.addText(text.c_str(), text.length());