[canvaskit] Add TextOnPath helper to TextBlob
This adds the pieces needed to accomplish this, and although clients could do it, I figured it would be nice to expose as a universal tool (on TextBlob). Bug: skia: Change-Id: Id5d61744973de2da75049d33d40e1dc442c2442c Reviewed-on: https://skia-review.googlesource.com/c/skia/+/201601 Reviewed-by: Ben Wagner <bungeman@google.com>
This commit is contained in:
parent
e1271700ad
commit
d3cfbcae10
@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- `SkPathMeasure`, `RSXFormBuilder`, `SkFont.getWidths`, `SkTextBlob.MakeFromRSXform`
|
||||
which were needed to add the helper function `SkTextBlob.MakeOnPath`.
|
||||
|
||||
### Changed
|
||||
- Location in Skia Git repo now `modules/canvaskit` (was `experimental/canvaskit`)
|
||||
|
||||
|
@ -47,6 +47,7 @@
|
||||
<h2> CanvasKit can allow for text shaping (e.g. breaking, kerning)</h2>
|
||||
<canvas id=shape1 width=600 height=600></canvas>
|
||||
<canvas id=shape2 width=600 height=600></canvas>
|
||||
<canvas id=textonpath width=300 height=300></canvas>
|
||||
|
||||
<h2> Skottie </h2>
|
||||
<canvas id=sk_legos width=300 height=300></canvas>
|
||||
@ -116,6 +117,7 @@
|
||||
|
||||
TextShapingAPI1(CanvasKit, notoserifData);
|
||||
TextShapingAPI2(CanvasKit, notoserifData);
|
||||
TextOnPathAPI1(CanvasKit);
|
||||
|
||||
ParticlesAPI1(CanvasKit);
|
||||
|
||||
@ -1279,6 +1281,42 @@
|
||||
preventScrolling(document.getElementById('shape2'));
|
||||
}
|
||||
|
||||
function TextOnPathAPI1(CanvasKit) {
|
||||
const surface = CanvasKit.MakeCanvasSurface('textonpath');
|
||||
if (!surface) {
|
||||
console.error('Could not make surface');
|
||||
return;
|
||||
}
|
||||
const context = CanvasKit.currentContext();
|
||||
const canvas = surface.getCanvas();
|
||||
const paint = new CanvasKit.SkPaint();
|
||||
paint.setStyle(CanvasKit.PaintStyle.Stroke);
|
||||
|
||||
const font = new CanvasKit.SkFont(null, 24);
|
||||
const fontPaint = new CanvasKit.SkPaint();
|
||||
fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
|
||||
|
||||
|
||||
const arc = new CanvasKit.SkPath();
|
||||
arc.arcTo(CanvasKit.LTRBRect(20, 40, 280, 300), -160, 140, true);
|
||||
arc.lineTo(210, 140);
|
||||
arc.arcTo(CanvasKit.LTRBRect(20, 0, 280, 260), 160, -140, true);
|
||||
|
||||
const str = 'This téxt should follow the curve across contours...';
|
||||
const textBlob = CanvasKit.SkTextBlob.MakeOnPath(str, arc, font);
|
||||
|
||||
canvas.drawPath(arc, paint);
|
||||
canvas.drawTextBlob(textBlob, 0, 0, fontPaint);
|
||||
|
||||
surface.flush();
|
||||
|
||||
textBlob.delete();
|
||||
arc.delete();
|
||||
paint.delete();
|
||||
font.delete();
|
||||
fontPaint.delete();
|
||||
}
|
||||
|
||||
function ParticlesAPI1(CanvasKit) {
|
||||
const surface = CanvasKit.MakeCanvasSurface('particles');
|
||||
if (!surface) {
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "SkParsePath.h"
|
||||
#include "SkPath.h"
|
||||
#include "SkPathEffect.h"
|
||||
#include "SkPathMeasure.h"
|
||||
#include "SkPathOps.h"
|
||||
#include "SkScalar.h"
|
||||
#include "SkShader.h"
|
||||
@ -534,6 +535,11 @@ void drawShapedText(SkCanvas& canvas, ShapedText st, SkScalar x,
|
||||
canvas.drawTextBlob(st.blob(), x, y, paint);
|
||||
}
|
||||
|
||||
// This is simpler than dealing with an SkPoint and SkVector
|
||||
struct PosTan {
|
||||
SkScalar px, py, tx, ty;
|
||||
};
|
||||
|
||||
// These objects have private destructors / delete mthods - I don't think
|
||||
// we need to do anything other than tell emscripten to do nothing.
|
||||
namespace emscripten {
|
||||
@ -831,6 +837,25 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
.function("getSize", &SkFont::getSize)
|
||||
.function("getSkewX", &SkFont::getSkewX)
|
||||
.function("getTypeface", &SkFont::getTypeface, allow_raw_pointers())
|
||||
.function("_getWidths", optional_override([](SkFont& self, uintptr_t /* char* */ sptr,
|
||||
size_t strLen, size_t expectedCodePoints,
|
||||
uintptr_t /* SkScalar* */ wptr) -> bool {
|
||||
char* str = reinterpret_cast<char*>(sptr);
|
||||
SkScalar* widths = reinterpret_cast<SkScalar*>(wptr);
|
||||
|
||||
SkGlyphID* glyphStorage = new SkGlyphID[expectedCodePoints];
|
||||
int actualCodePoints = self.textToGlyphs(str, strLen, SkTextEncoding::kUTF8,
|
||||
glyphStorage, expectedCodePoints);
|
||||
if (actualCodePoints != expectedCodePoints) {
|
||||
SkDebugf("Actually %d glyphs, expected only %d\n",
|
||||
actualCodePoints, expectedCodePoints);
|
||||
return false;
|
||||
}
|
||||
|
||||
self.getWidths(glyphStorage, actualCodePoints, widths);
|
||||
delete[] glyphStorage;
|
||||
return true;
|
||||
}))
|
||||
.function("measureText", optional_override([](SkFont& self, std::string text) {
|
||||
// TODO(kjlubick): This does not work well for non-ascii
|
||||
// Need to maybe add a helper in interface.js that supports UTF-8
|
||||
@ -971,6 +996,21 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
#endif
|
||||
;
|
||||
|
||||
class_<SkPathMeasure>("SkPathMeasure")
|
||||
.constructor<const SkPath&, bool, SkScalar>()
|
||||
.function("getLength", &SkPathMeasure::getLength)
|
||||
.function("getPosTan", optional_override([](SkPathMeasure& self,
|
||||
SkScalar distance) -> PosTan {
|
||||
SkPoint p{0, 0};
|
||||
SkVector v{0, 0};
|
||||
if (!self.getPosTan(distance, &p, &v)) {
|
||||
SkDebugf("zero-length path in getPosTan\n");
|
||||
}
|
||||
return PosTan{p.x(), p.y(), v.x(), v.y()};
|
||||
}))
|
||||
.function("isClosed", &SkPathMeasure::isClosed)
|
||||
.function("nextContour", &SkPathMeasure::nextContour);
|
||||
|
||||
class_<SkShader>("SkShader")
|
||||
.smart_ptr<sk_sp<SkShader>>("sk_sp<SkShader>");
|
||||
|
||||
@ -988,6 +1028,17 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
|
||||
class_<SkTextBlob>("SkTextBlob")
|
||||
.smart_ptr<sk_sp<SkTextBlob>>("sk_sp<SkTextBlob>>")
|
||||
.class_function("_MakeFromRSXform", optional_override([](uintptr_t /* char* */ sptr,
|
||||
size_t strBtyes,
|
||||
uintptr_t /* SkRSXform* */ xptr,
|
||||
const SkFont& font,
|
||||
SkTextEncoding encoding)->sk_sp<SkTextBlob> {
|
||||
// See comment above for uintptr_t explanation
|
||||
const char* str = reinterpret_cast<const char*>(sptr);
|
||||
const SkRSXform* xforms = reinterpret_cast<const SkRSXform*>(xptr);
|
||||
|
||||
return SkTextBlob::MakeFromRSXform(str, strBtyes, xforms, font, encoding);
|
||||
}), allow_raw_pointers())
|
||||
.class_function("_MakeFromText", optional_override([](uintptr_t /* char* */ sptr,
|
||||
size_t len, const SkFont& font,
|
||||
SkTextEncoding encoding)->sk_sp<SkTextBlob> {
|
||||
@ -1174,6 +1225,13 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
.element(&SkPoint3::fY)
|
||||
.element(&SkPoint3::fZ);
|
||||
|
||||
// PosTan can be represented by [px, py, tx, ty]
|
||||
value_array<PosTan>("PosTan")
|
||||
.element(&PosTan::px)
|
||||
.element(&PosTan::py)
|
||||
.element(&PosTan::tx)
|
||||
.element(&PosTan::ty);
|
||||
|
||||
// {"w": Number, "h", Number}
|
||||
value_object<SkSize>("SkSize")
|
||||
.field("w", &SkSize::fWidth)
|
||||
|
@ -84,6 +84,8 @@ var CanvasKit = {
|
||||
|
||||
// Objects and properties on CanvasKit
|
||||
|
||||
RSXFormBuilder: function() {},
|
||||
|
||||
ShapedText: {
|
||||
// public API (from C++ bindings)
|
||||
getBounds: function() {},
|
||||
@ -138,6 +140,8 @@ var CanvasKit = {
|
||||
setSize: function() {},
|
||||
setSkewX: function() {},
|
||||
setTypeface: function() {},
|
||||
// private API (from C++ bindings)
|
||||
_getWidths: function() {},
|
||||
},
|
||||
|
||||
SkFontMgr: {
|
||||
@ -241,6 +245,13 @@ var CanvasKit = {
|
||||
dumpHex: function() {},
|
||||
},
|
||||
|
||||
SkPathMeasure: {
|
||||
getLength: function() {},
|
||||
getPosTan: function() {},
|
||||
isClosed: function() {},
|
||||
nextContour: function() {},
|
||||
},
|
||||
|
||||
SkRect: {
|
||||
fLeft: {},
|
||||
fTop: {},
|
||||
@ -263,7 +274,12 @@ var CanvasKit = {
|
||||
},
|
||||
|
||||
SkTextBlob: {
|
||||
// public API (both C++ and JS bindings)
|
||||
MakeFromRSXform: function() {},
|
||||
MakeFromText: function() {},
|
||||
MakeOnPath: function() {},
|
||||
// private API (from C++ bindings)
|
||||
_MakeFromRSXform: function() {},
|
||||
_MakeFromText: function() {},
|
||||
},
|
||||
|
||||
@ -496,6 +512,12 @@ CanvasKit.SkCanvas.prototype.writePixels = function() {};
|
||||
|
||||
CanvasKit.SkFontMgr.prototype.MakeTypefaceFromData = function() {};
|
||||
|
||||
CanvasKit.SkFont.prototype.getWidths = function() {};
|
||||
|
||||
CanvasKit.RSXFormBuilder.prototype.build = function() {};
|
||||
CanvasKit.RSXFormBuilder.prototype.delete = function() {};
|
||||
CanvasKit.RSXFormBuilder.prototype.push = function() {};
|
||||
|
||||
// Define StrokeOpts object
|
||||
var StrokeOpts = {};
|
||||
StrokeOpts.prototype.width;
|
||||
|
@ -161,3 +161,40 @@ function loadCmdsTypedArray(arr) {
|
||||
var ptr = copy1dArray(ta, CanvasKit.HEAPF32);
|
||||
return [ptr, len];
|
||||
}
|
||||
|
||||
// Helper for building an array of RSXForms (which are just structs
|
||||
// of 4 floats)
|
||||
CanvasKit.RSXFormBuilder = function() {
|
||||
this._pts = [];
|
||||
this._ptr = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A compressed form of a rotation+scale matrix.
|
||||
*
|
||||
* [ scos -ssin tx ]
|
||||
* [ ssin scos ty ]
|
||||
* [ 0 0 1 ]
|
||||
*/
|
||||
CanvasKit.RSXFormBuilder.prototype.push = function(scos, ssin, tx, ty) {
|
||||
if (this._ptr) {
|
||||
SkDebug('Cannot push more points - already built');
|
||||
return;
|
||||
}
|
||||
this._pts.push(scos, ssin, tx, ty);
|
||||
}
|
||||
|
||||
CanvasKit.RSXFormBuilder.prototype.build = function() {
|
||||
if (this._ptr) {
|
||||
return this._ptr;
|
||||
}
|
||||
this._ptr = copy1dArray(this._pts, CanvasKit.HEAPF32);
|
||||
return this._ptr;
|
||||
}
|
||||
|
||||
CanvasKit.RSXFormBuilder.prototype.delete = function() {
|
||||
if (this._ptr) {
|
||||
CanvasKit._free(this._ptr);
|
||||
this._ptr = null;
|
||||
}
|
||||
}
|
||||
|
@ -452,6 +452,36 @@ CanvasKit.onRuntimeInitialized = function() {
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Returns an array of the widths of the glyphs in this string.
|
||||
CanvasKit.SkFont.prototype.getWidths = function(str) {
|
||||
// add 1 for null terminator
|
||||
var codePoints = str.length + 1;
|
||||
// lengthBytesUTF8 and stringToUTF8Array are defined in the emscripten
|
||||
// JS. See https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#stringToUTF8
|
||||
// Add 1 for null terminator
|
||||
var strBytes = lengthBytesUTF8(str) + 1;
|
||||
var strPtr = CanvasKit._malloc(strBytes);
|
||||
stringToUTF8(str, strPtr, strBytes);
|
||||
|
||||
var bytesPerFloat = 4;
|
||||
// allocate widths == numCodePoints
|
||||
var widthPtr = CanvasKit._malloc(codePoints * bytesPerFloat);
|
||||
if (!this._getWidths(strPtr, strBytes, codePoints, widthPtr)) {
|
||||
SkDebug('Could not compute widths');
|
||||
CanvasKit._free(strPtr);
|
||||
CanvasKit._free(widthPtr);
|
||||
return null;
|
||||
}
|
||||
// reminder, this shouldn't copy the data, just is a nice way to
|
||||
// wrap 4 bytes together into a float.
|
||||
var widths = new Float32Array(CanvasKit.buffer, widthPtr, codePoints);
|
||||
// This copies the data so we can free the CanvasKit memory
|
||||
var retVal = Array.from(widths);
|
||||
CanvasKit._free(strPtr);
|
||||
CanvasKit._free(widthPtr);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
// fontData should be an arrayBuffer
|
||||
CanvasKit.SkFontMgr.prototype.MakeTypefaceFromData = function(fontData) {
|
||||
var data = new Uint8Array(fontData);
|
||||
@ -468,6 +498,88 @@ CanvasKit.onRuntimeInitialized = function() {
|
||||
return font;
|
||||
}
|
||||
|
||||
CanvasKit.SkTextBlob.MakeOnPath = function(str, path, font, initialOffset) {
|
||||
if (!str || !str.length) {
|
||||
SkDebug('ignoring 0 length string');
|
||||
return;
|
||||
}
|
||||
if (!path || !path.countPoints()) {
|
||||
SkDebug('ignoring empty path');
|
||||
return;
|
||||
}
|
||||
if (path.countPoints() === 1) {
|
||||
SkDebug('path has 1 point, returning normal textblob');
|
||||
return this.MakeFromText(str, font);
|
||||
}
|
||||
|
||||
if (!initialOffset) {
|
||||
initialOffset = 0;
|
||||
}
|
||||
|
||||
var widths = font.getWidths(str);
|
||||
|
||||
var rsx = new CanvasKit.RSXFormBuilder();
|
||||
var meas = new CanvasKit.SkPathMeasure(path, false, 1);
|
||||
var dist = initialOffset;
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
var width = widths[i];
|
||||
dist += width/2;
|
||||
if (dist > meas.getLength()) {
|
||||
// jump to next contour
|
||||
if (!meas.nextContour()) {
|
||||
// We have come to the end of the path - terminate the string
|
||||
// right here.
|
||||
str = str.substring(0, i);
|
||||
break;
|
||||
}
|
||||
dist = width/2;
|
||||
}
|
||||
|
||||
// Gives us the (x, y) coordinates as well as the cos/sin of the tangent
|
||||
// line at that position.
|
||||
var xycs = meas.getPosTan(dist);
|
||||
var cx = xycs[0];
|
||||
var cy = xycs[1];
|
||||
var cosT = xycs[2];
|
||||
var sinT = xycs[3];
|
||||
|
||||
var adjustedX = cx - (width/2 * cosT);
|
||||
var adjustedY = cy - (width/2 * sinT);
|
||||
|
||||
rsx.push(cosT, sinT, adjustedX, adjustedY);
|
||||
dist += width/2;
|
||||
}
|
||||
var retVal = this.MakeFromRSXform(str, rsx, font);
|
||||
rsx.delete();
|
||||
meas.delete();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
CanvasKit.SkTextBlob.MakeFromRSXform = function(str, rsxBuilder, font) {
|
||||
// lengthBytesUTF8 and stringToUTF8Array are defined in the emscripten
|
||||
// JS. See https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#stringToUTF8
|
||||
// Add 1 for null terminator
|
||||
var strLen = lengthBytesUTF8(str) + 1;
|
||||
var strPtr = CanvasKit._malloc(strLen);
|
||||
// Add 1 for the null terminator.
|
||||
stringToUTF8(str, strPtr, strLen);
|
||||
var rptr = rsxBuilder.build();
|
||||
|
||||
var blob = CanvasKit.SkTextBlob._MakeFromRSXform(strPtr, strLen - 1,
|
||||
rptr, font, CanvasKit.TextEncoding.UTF8);
|
||||
if (!blob) {
|
||||
SkDebug('Could not make textblob from string "' + str + '"');
|
||||
return null;
|
||||
}
|
||||
|
||||
var origDelete = blob.delete.bind(blob);
|
||||
blob.delete = function() {
|
||||
CanvasKit._free(strPtr);
|
||||
origDelete();
|
||||
}
|
||||
return blob;
|
||||
}
|
||||
|
||||
CanvasKit.SkTextBlob.MakeFromText = function(str, font) {
|
||||
// lengthBytesUTF8 and stringToUTF8Array are defined in the emscripten
|
||||
// JS. See https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#stringToUTF8
|
||||
|
@ -14,18 +14,17 @@ describe('CanvasKit\'s Path Behavior', function() {
|
||||
container.innerHTML = '';
|
||||
});
|
||||
|
||||
let notSerifFontBuffer = null;
|
||||
// This font is known to support kerning
|
||||
const notoSerifFontLoaded = fetch('/assets/NotoSerif-Regular.ttf').then(
|
||||
(response) => response.arrayBuffer()).then(
|
||||
(buffer) => {
|
||||
notSerifFontBuffer = buffer;
|
||||
});
|
||||
|
||||
it('can draw shaped and unshaped text', function(done) {
|
||||
let fontBuffer = null;
|
||||
|
||||
// This font is known to support kerning
|
||||
const skFontLoaded = fetch('/assets/NotoSerif-Regular.ttf').then(
|
||||
(response) => response.arrayBuffer()).then(
|
||||
(buffer) => {
|
||||
fontBuffer = buffer;
|
||||
});
|
||||
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
skFontLoaded.then(() => {
|
||||
notoSerifFontLoaded.then(() => {
|
||||
// This is taken from example.html
|
||||
const surface = CanvasKit.MakeCanvasSurface('test');
|
||||
expect(surface).toBeTruthy('Could not make surface')
|
||||
@ -40,7 +39,7 @@ describe('CanvasKit\'s Path Behavior', function() {
|
||||
paint.setStyle(CanvasKit.PaintStyle.Stroke);
|
||||
|
||||
const fontMgr = CanvasKit.SkFontMgr.RefDefault();
|
||||
const notoSerif = fontMgr.MakeTypefaceFromData(fontBuffer);
|
||||
const notoSerif = fontMgr.MakeTypefaceFromData(notSerifFontBuffer);
|
||||
|
||||
const textPaint = new CanvasKit.SkPaint();
|
||||
// use the built-in monospace typeface.
|
||||
@ -90,10 +89,102 @@ describe('CanvasKit\'s Path Behavior', function() {
|
||||
shapedText.delete();
|
||||
textFont2.delete();
|
||||
shapedText2.delete();
|
||||
fontMgr.delete();
|
||||
reportSurface(surface, 'text_shaping', done);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
it('can draw text following a path', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
const surface = CanvasKit.MakeCanvasSurface('test');
|
||||
expect(surface).toBeTruthy('Could not make surface')
|
||||
if (!surface) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
const canvas = surface.getCanvas();
|
||||
const paint = new CanvasKit.SkPaint();
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStyle(CanvasKit.PaintStyle.Stroke);
|
||||
|
||||
const font = new CanvasKit.SkFont(null, 24);
|
||||
const fontPaint = new CanvasKit.SkPaint();
|
||||
fontPaint.setAntiAlias(true);
|
||||
fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
|
||||
|
||||
|
||||
const arc = new CanvasKit.SkPath();
|
||||
arc.arcTo(CanvasKit.LTRBRect(20, 40, 280, 300), -160, 140, true);
|
||||
arc.lineTo(210, 140);
|
||||
arc.arcTo(CanvasKit.LTRBRect(20, 0, 280, 260), 160, -140, true);
|
||||
|
||||
// Only 1 dot should show up in the image, because we run out of path.
|
||||
const str = 'This téxt should follow the curve across contours...';
|
||||
const textBlob = CanvasKit.SkTextBlob.MakeOnPath(str, arc, font);
|
||||
|
||||
canvas.drawPath(arc, paint);
|
||||
canvas.drawTextBlob(textBlob, 0, 0, fontPaint);
|
||||
|
||||
surface.flush();
|
||||
|
||||
textBlob.delete();
|
||||
arc.delete();
|
||||
paint.delete();
|
||||
font.delete();
|
||||
fontPaint.delete();
|
||||
|
||||
reportSurface(surface, 'monospace_text_on_path', done);
|
||||
}));
|
||||
});
|
||||
|
||||
it('can draw text following a path with a non-serif font', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
notoSerifFontLoaded.then(() => {
|
||||
const surface = CanvasKit.MakeCanvasSurface('test');
|
||||
expect(surface).toBeTruthy('Could not make surface')
|
||||
if (!surface) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
const fontMgr = CanvasKit.SkFontMgr.RefDefault();
|
||||
const notoSerif = fontMgr.MakeTypefaceFromData(notSerifFontBuffer);
|
||||
|
||||
const canvas = surface.getCanvas();
|
||||
const paint = new CanvasKit.SkPaint();
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStyle(CanvasKit.PaintStyle.Stroke);
|
||||
|
||||
const font = new CanvasKit.SkFont(notoSerif, 24);
|
||||
const fontPaint = new CanvasKit.SkPaint();
|
||||
fontPaint.setAntiAlias(true);
|
||||
fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
|
||||
|
||||
|
||||
const arc = new CanvasKit.SkPath();
|
||||
arc.arcTo(CanvasKit.LTRBRect(20, 40, 280, 300), -160, 140, true);
|
||||
arc.lineTo(210, 140);
|
||||
arc.arcTo(CanvasKit.LTRBRect(20, 0, 280, 260), 160, -140, true);
|
||||
|
||||
const str = 'This téxt should follow the curve across contours...';
|
||||
const textBlob = CanvasKit.SkTextBlob.MakeOnPath(str, arc, font, 60.5);
|
||||
|
||||
canvas.drawPath(arc, paint);
|
||||
canvas.drawTextBlob(textBlob, 0, 0, fontPaint);
|
||||
|
||||
surface.flush();
|
||||
|
||||
textBlob.delete();
|
||||
arc.delete();
|
||||
paint.delete();
|
||||
notoSerif.delete();
|
||||
font.delete();
|
||||
fontPaint.delete();
|
||||
fontMgr.delete();
|
||||
reportSurface(surface, 'serif_text_on_path', done);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
// TODO more tests
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user