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: