diff --git a/modules/canvaskit/npm_build/extra.html b/modules/canvaskit/npm_build/extra.html
index fc73d97560..15333fdac5 100644
--- a/modules/canvaskit/npm_build/extra.html
+++ b/modules/canvaskit/npm_build/extra.html
@@ -32,6 +32,7 @@
Paragraph
+
CanvasKit can serialize/deserialize .skp files
@@ -141,6 +142,7 @@
Promise.all([ckLoaded, loadFont]).then((results) => {
ParagraphAPI1(...results);
+ ParagraphAPI2(...results);
GlyphGame(...results)
});
Promise.all([ckLoaded, loadSkp]).then((results) => {SkpExample(...results)});
@@ -303,6 +305,253 @@
return surface;
}
+ function MakeCursor(CanvasKit) {
+ const linePaint = new CanvasKit.Paint();
+ linePaint.setColor([0,0,1,1]);
+ linePaint.setStyle(CanvasKit.PaintStyle.Stroke);
+ linePaint.setStrokeWidth(2);
+ linePaint.setAntiAlias(true);
+
+ const pathPaint = new CanvasKit.Paint();
+ pathPaint.setColor([0,0,1,0.25]);
+ linePaint.setAntiAlias(true);
+
+ return {
+ _line_paint: linePaint, // wrap in weak-ref so we can delete it?
+ _path_paint: pathPaint,
+ _x: 0,
+ _top: 0,
+ _bottom: 0,
+ _path: null, // only use x,top,bottom if path is null
+ _draws_per_sec: 2,
+
+ // pass 0 for no-draw, pass inf. for always on
+ setBlinkRate: function(blinks_per_sec) {
+ this._draws_per_sec = blinks_per_sec;
+ },
+ place: function(x, y, fontMetrics) {
+ this._x = x;
+ this._top = y + fontMetrics.ascent;
+ this._bottom = y + fontMetrics.descent;
+
+ this._path = null;
+ },
+ setPath: function(path) {
+ this._path = path;
+ },
+ draw_before: function(canvas) {
+ if (this._path) {
+ canvas.drawPath(this._path, this._path_paint);
+ }
+ },
+ draw_after: function(canvas) {
+ if (this._path) {
+ return;
+ }
+ if (Math.floor(Date.now() * this._draws_per_sec / 1000) & 1) {
+ canvas.drawLine(this._x, this._top, this._x, this._bottom, this._line_paint);
+ }
+ },
+ };
+ }
+
+ let mouse = {
+ _start_x: 0, _start_y: 0,
+ _curr_x: 0, _curr_y: 0,
+ _active: false,
+
+ isActive: function() {
+ return this._active;
+ },
+ setDown: function(x, y) {
+ this._start_x = this._curr_x = x;
+ this._start_y = this._curr_y = y;
+ this._active = true;
+ },
+ setMove: function(x, y) {
+ this._curr_x = x;
+ this._curr_y = y;
+ },
+ setUp: function(x, y) {
+ this._curr_x = x;
+ this._curr_y = y;
+ this._active = false;
+ },
+ getPos: function() {
+ return [ this._start_x, this._start_y, this._curr_x, this._curr_y ];
+ },
+ }
+
+ function ParagraphAPI2(CanvasKit, fontData) {
+ if (!CanvasKit || !fontData) {
+ return;
+ }
+
+ const surface = CanvasKit.MakeCanvasSurface('para2');
+ if (!surface) {
+ console.error('Could not make surface');
+ return;
+ }
+
+ const cursor = MakeCursor(CanvasKit);
+ const canvas = surface.getCanvas();
+ const fontMgr = CanvasKit.FontMgr.FromData([fontData]);
+
+ const paraStyle = new CanvasKit.ParagraphStyle({
+ textStyle: {
+ color: CanvasKit.GRAY,
+ fontFamilies: ['Roboto'],
+ fontSize: 40,
+ },
+ textAlign: CanvasKit.TextAlign.Left,
+ });
+
+ const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
+ const text = "In a hole in the ground there lived a hobbit. Not a nasty, dirty, " +
+ "wet hole full of worms and oozy smells. This was a hobbit-hole and " +
+ "that means good food, a warm hearth, and all the comforts of home.";
+ builder.addText(text);
+ const paragraph = builder.build();
+ const WIDTH = 600;
+ paragraph.layout(WIDTH);
+
+ const tf = fontMgr.MakeTypefaceFromData(fontData);
+ const font = new CanvasKit.Font(tf, 40);
+ const fm = font.getMetrics();
+ const fontPaint = new CanvasKit.Paint();
+ fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
+ fontPaint.setAntiAlias(true);
+
+ function mid(a, b) { return (a + b) * 0.5; }
+
+ function pos_to_index(runs, x, y) {
+ if (y < runs[0].positions[1] + fm.ascent) {
+ return 0;
+ }
+ for (const r of runs) {
+ if (y >= r.positions[1] + fm.ascent &&
+ y <= r.positions[1] + fm.descent) {
+ for (let i = 1; i < r.offsets.length; i += 1) {
+ if (x < r.positions[i*2]) {
+ if (x <= mid(r.positions[i*2-2], r.positions[i*2])) {
+ return r.offsets[i-1];
+ } else {
+ return r.offsets[i];
+ }
+ }
+ }
+ return r.offsets[r.offsets.length-1];
+ }
+ }
+ const r = runs[runs.length-1];
+ return r.offsets[r.offsets.length-1]; // last
+ }
+
+ function find_run(runs, index) {
+ for (const r of runs) {
+ if (index <= r.offsets[r.offsets.length-1]) {
+ return r;
+ }
+ }
+ return null;
+ }
+
+ function get_run_pos(r, index) {
+ for (const i in r.offsets) {
+ if (index == r.offsets[i]) {
+ return r.positions.slice(i*2, i*2+2);
+ }
+ }
+ return null;
+ }
+
+ function index_to_pos(runs, index) {
+ const r = find_run(runs, index);
+ return get_run_pos(r, index);
+ }
+
+ function indices_to_path(runs, a, b, fm, width) {
+ if (a == b) {
+ return null;
+ }
+ if (a > b) {
+ const tmp = a;
+ a = b;
+ b = tmp;
+ }
+ const path = new CanvasKit.Path();
+ const first = find_run(runs, a);
+ const last = find_run(runs, b);
+ const p = get_run_pos(first, a);
+ const q = get_run_pos(last, b);
+ if (first == last) {
+ path.addRect([p[0], p[1] + fm.ascent, q[0], q[1] + fm.descent]);
+ } else {
+ const p_top = p[1] + fm.ascent;
+ const p_bot = p[1] + fm.descent;
+ path.addRect([p[0], p_top, width, p_bot]);
+ const q_top = q[1] + fm.ascent;
+ const q_bot = q[1] + fm.descent;
+ path.addRect([ 0, q_top, q[0], q_bot]);
+ if (p_bot < q_top) {
+ path.addRect([0, p_bot, width, q_top]); // extra lines inbetween
+ }
+ }
+ return path;
+ }
+
+ let INDEX = 0;
+ let runs;
+
+ function drawFrame(canvas) {
+ canvas.clear(CanvasKit.WHITE);
+ canvas.drawParagraph(paragraph, 0, 0);
+ if (!runs) {
+ runs = paragraph.getShapedRuns();
+ }
+
+ if (mouse.isActive()) {
+ const pos = mouse.getPos();
+ const a = pos_to_index(runs, pos[0], pos[1]);
+ const b = pos_to_index(runs, pos[2], pos[3]);
+ if (a == b) {
+ INDEX = a;
+ const p = index_to_pos(runs, INDEX);
+ cursor.place(p[0], p[1], fm);
+ } else {
+ cursor.setPath(indices_to_path(runs, a, b, fm, WIDTH));
+ }
+ }
+
+ cursor.draw_before(canvas);
+ for (let r of runs) {
+ canvas.drawGlyphs(r.glyphs, r.positions, 0, 0, font, fontPaint);
+ }
+ cursor.draw_after(canvas);
+
+ surface.requestAnimationFrame(drawFrame);
+ }
+ surface.requestAnimationFrame(drawFrame);
+
+ function interact(e) {
+ const type = e.type;
+ if (type === 'pointerup') {
+ mouse.setUp(e.offsetX, e.offsetY);
+ } else if (type === 'pointermove') {
+ mouse.setMove(e.offsetX, e.offsetY);
+ } else if (type === 'pointerdown') {
+ mouse.setDown(e.offsetX, e.offsetY);
+ }
+ };
+
+ document.getElementById('para2').addEventListener('pointermove', interact);
+ document.getElementById('para2').addEventListener('pointerdown', interact);
+// document.getElementById('para2').addEventListener('lostpointercapture', interact);
+// document.getElementById('para2').addEventListener('pointerleave', interact);
+ document.getElementById('para2').addEventListener('pointerup', interact);
+ return surface;
+ }
+
function RTShaderAPI1(CanvasKit) {
if (!CanvasKit) {
return;
diff --git a/modules/skparagraph/gm/simple_gm.cpp b/modules/skparagraph/gm/simple_gm.cpp
index e7f9c5ab18..02b20dc074 100644
--- a/modules/skparagraph/gm/simple_gm.cpp
+++ b/modules/skparagraph/gm/simple_gm.cpp
@@ -94,9 +94,13 @@ protected:
}
void drawFromVisitor(SkCanvas* canvas, skia::textlayout::Paragraph* para) const {
- SkPaint p;
+ SkPaint p, p2;
p.setColor(0xFF0000FF);
- para->visit([canvas, p](const skia::textlayout::Paragraph::VisitorInfo& info) {
+ p2.setColor(0xFFFF0000);
+ p2.setStrokeWidth(4);
+ p2.setStrokeCap(SkPaint::kSquare_Cap);
+
+ para->visit([&](const skia::textlayout::Paragraph::VisitorInfo& info) {
canvas->drawGlyphs(info.count, info.glyphs, info.positions, info.origin, info.font, p);
if (info.utf8Starts && false) {
@@ -106,6 +110,13 @@ protected:
}
SkDebugf("'%s'\n", str.c_str());
}
+
+ if (false) { // show position points
+ for (int i = 0; i < info.count; ++i) {
+ auto pos = info.positions[i];
+ canvas->drawPoint(pos.fX + info.origin.fX, pos.fY + info.origin.fY, p2);
+ }
+ }
});
}
@@ -151,7 +162,7 @@ protected:
bool runAsBench() const override { return true; }
bool onAnimate(double /*nanos*/) override {
- return true;
+ return false;
}
private: