[canvaskit] Remove ShapedText API.

The Paragraph API is what should be used.

Bug: skia:10717
Change-Id: I135aff09bffae0718045b5c744f8e774e2ee1bce
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/371338
Reviewed-by: Ben Wagner <bungeman@google.com>
Reviewed-by: Nathaniel Nifong <nifong@google.com>
This commit is contained in:
Kevin Lubick 2021-02-23 08:27:46 -05:00
parent 926bd8ca82
commit 58605b0466
8 changed files with 23 additions and 355 deletions

View File

@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Breaking
- The `ShapedText` type has been removed. Clients who want ShapedText should use the
Paragraph APIs.
## [0.24.0] - 2021-02-18
### Added

View File

@ -42,9 +42,7 @@
<canvas id=atlas width=300 height=300></canvas>
<canvas id=decode width=300 height=300></canvas>
<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>
<h2> CanvasKit can allow for text measurement/placement (e.g. breaking, kerning)</h2>
<canvas id=textonpath width=300 height=300></canvas>
<script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
@ -82,10 +80,6 @@
// Examples requiring external resources
Promise.all([ckLoaded, loadRoboto]).then((results) => {DrawingExample(...results)});
Promise.all([ckLoaded, loadNotoSerif]).then((results) => {
TextShapingAPI1(...results);
TextShapingAPI2(...results);
});
Promise.all([ckLoaded, loadTestImage]).then((results) => {AtlasAPI1(...results)});
Promise.all([ckLoaded, loadTestImage]).then((results) => {DecodeAPI(...results)});
@ -928,132 +922,6 @@
surface.flush();
}
function TextShapingAPI1(CanvasKit, notoserifData) {
if (!notoserifData || !CanvasKit) {
return;
}
const surface = CanvasKit.MakeSWCanvasSurface('shape1');
if (!surface) {
console.error('Could not make surface');
return;
}
const canvas = surface.getCanvas();
const paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.BLUE);
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const fontMgr = CanvasKit.FontMgr.RefDefault();
const notoSerif = fontMgr.MakeTypefaceFromData(notoserifData);
const textPaint = new CanvasKit.Paint();
const textFont = new CanvasKit.Font(notoSerif, 20);
canvas.drawRect(CanvasKit.LTRBRect(30, 30, 200, 200), paint);
canvas.drawText('This text is not shaped, and overflows the boundry',
35, 50, textPaint, textFont);
const shapedText = new CanvasKit.ShapedText({
font: textFont,
leftToRight: true,
text: 'This text *is* shaped, and wraps to the right width.',
width: 160,
});
const textBoxX = 35;
const textBoxY = 55;
canvas.drawText(shapedText, textBoxX, textBoxY, textPaint);
const bounds = shapedText.getBounds();
bounds[0] += textBoxX; // left
bounds[2] += textBoxX; // right
bounds[1] += textBoxY; // top
bounds[3] += textBoxY; // bottom
canvas.drawRect(bounds, paint);
const SHAPE_TEST_TEXT = 'VAVAVAVAVAFIfi';
const textFont2 = new CanvasKit.Font(notoSerif, 60);
const shapedText2 = new CanvasKit.ShapedText({
font: textFont2,
leftToRight: true,
text: SHAPE_TEST_TEXT,
width: 600,
});
canvas.drawText('no kerning ↓', 10, 240, textPaint, textFont);
canvas.drawText(SHAPE_TEST_TEXT, 10, 300, textPaint, textFont2);
canvas.drawText(shapedText2, 10, 300, textPaint);
canvas.drawText('kerning ↑', 10, 390, textPaint, textFont);
surface.flush();
paint.delete();
notoSerif.delete();
textPaint.delete();
textFont.delete();
shapedText.delete();
textFont2.delete();
shapedText2.delete();
surface.delete();
}
function TextShapingAPI2(CanvasKit, notoserifData) {
if (!notoserifData || !CanvasKit) {
return;
}
const surface = CanvasKit.MakeSWCanvasSurface('shape2');
if (!surface) {
console.error('Could not make surface');
return;
}
const paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.BLUE);
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const fontMgr = CanvasKit.FontMgr.RefDefault();
const notoSerif = fontMgr.MakeTypefaceFromData(notoserifData);
const textPaint = new CanvasKit.Paint();
const bigFont = new CanvasKit.Font(notoSerif, 40);
const smallFont = new CanvasKit.Font(notoSerif, 25);
const TEXT = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris ac leo vitae ipsum hendrerit euismod quis rutrum nibh. Quisque non suscipit urna. Donec enim urna, facilisis vitae volutpat in, mattis at elit. Sed quis augue et dolor dignissim fringilla. Sed non massa eu neque tristique malesuada. ';
let X = 240;
let Y = 190;
function drawFrame(canvas) {
canvas.clear(CanvasKit.TRANSPARENT);
const shapedText = new CanvasKit.ShapedText({
font: smallFont,
leftToRight: true,
text: TEXT,
width: (X * 2) - 10,
});
canvas.drawRect(CanvasKit.LTRBRect(10, 10, X*2, Y*2), paint);
canvas.drawText(shapedText, 10, 40, textPaint, smallFont);
canvas.drawText('Try Clicking!', 10, 480, textPaint, bigFont);
shapedText.delete();
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
// Make animation interactive
let interact = (e) => {
if (!e.pressure) {
return;
}
X = e.offsetX;
Y = e.offsetY;
};
document.getElementById('shape2').addEventListener('pointermove', interact);
document.getElementById('shape2').addEventListener('pointerdown', interact);
preventScrolling(document.getElementById('shape2'));
}
function TextOnPathAPI1(CanvasKit) {
const surface = CanvasKit.MakeSWCanvasSurface('textonpath');
if (!surface) {

View File

@ -17,7 +17,6 @@ import {
Path,
PathEffect,
Shader,
ShapedText,
SkPicture,
TextBlob,
Typeface,
@ -50,7 +49,6 @@ CanvasKitInit({locateFile: (file: string) => '/node_modules/canvaskit/bin/' + fi
runtimeEffectTests(CK);
skottieTests(CK);
shaderTests(CK);
shapedTextTests(CK);
surfaceTests(CK);
textBlobTests(CK);
vectorTests(CK);
@ -76,11 +74,10 @@ function animatedImageTests(CK: CanvasKit) {
// cause errors.
function canvasTests(CK: CanvasKit, canvas?: Canvas, paint?: Paint, path?: Path,
img?: Image, aImg?: AnimatedImage, para?: Paragraph,
skp?: SkPicture, font?: Font, shapedText?: ShapedText,
textBlob?: TextBlob, verts?: Vertices, imageInfo?: ImageInfo,
imgFilter?: ImageFilter) {
skp?: SkPicture, font?: Font, textBlob?: TextBlob, verts?: Vertices,
imageInfo?: ImageInfo, imgFilter?: ImageFilter) {
if (!canvas || !paint || !path || !img || !aImg || !para || !skp || !font ||
!shapedText || !textBlob || !verts || !imageInfo || !imgFilter) {
!textBlob || !verts || !imageInfo || !imgFilter) {
return;
}
const someColor = [0.9, 0.8, 0.7, 0.6]; // Making sure arrays are accepted as colors.
@ -137,7 +134,6 @@ function canvasTests(CK: CanvasKit, canvas?: Canvas, paint?: Paint, path?: Path,
const mallocedVector3 = CK.Malloc(Float32Array, 3);
canvas.drawShadow(path, mallocedVector3, mallocedVector3, 7, someColor, CK.BLUE, 0);
canvas.drawText('foo', 1, 2, paint, font);
canvas.drawText(shapedText, 1, 2, paint, font);
canvas.drawTextBlob(textBlob, 10, 20, paint);
canvas.drawVertices(verts, CK.BlendMode.DstOut, paint);
const matrOne = canvas.findMarkedCTM('thing'); // $ExpectType Float32Array | null
@ -799,19 +795,6 @@ function shaderTests(CK: CanvasKit) {
const s13 = CK.Shader.MakeTurbulence(0.1, 0.05, 2, 0, 80, 80); // $ExpectType Shader
}
function shapedTextTests(CK: CanvasKit, textFont?: Font) {
if (!textFont) return;
const shaped = new CK.ShapedText({ // $ExpectType ShapedText
font: textFont,
leftToRight: true,
text: 'this is shaped',
width: 20,
});
const bounds = shaped.getBounds();
shaped.getBounds(bounds);
}
function surfaceTests(CK: CanvasKit) {
const canvasEl = document.querySelector('canvas') as HTMLCanvasElement;
const surfaceOne = CK.MakeCanvasSurface(canvasEl)!; // $ExpectType Surface

View File

@ -402,7 +402,6 @@ export interface CanvasKit {
// Constructors, i.e. things made with `new CanvasKit.Foo()`;
readonly ImageData: ImageDataConstructor;
readonly ParagraphStyle: ParagraphStyleConstructor;
readonly ShapedText: ShapedTextConstructor;
readonly ContourMeasureIter: ContourMeasureIterConstructor;
readonly Font: FontConstructor;
readonly Paint: DefaultConstructor<Paint>;
@ -886,25 +885,6 @@ export interface SkSLUniform {
slot: number;
}
/**
* A simple wrapper around TextBlob and the simple Text Shaper.
*/
export interface ShapedText extends EmbindObject<ShapedText> {
/**
* Return the bounding area for the given text.
* @param outputArray - if provided, the bounding box will be copied into this array instead of
* allocating a new one.
*/
getBounds(outputArray?: Rect): Rect;
}
export interface ShapedTextOpts {
text: string;
font: Font;
leftToRight: boolean;
width: number;
}
/**
* See SkAnimatedImage.h for more information on this class.
*/
@ -1257,15 +1237,15 @@ export interface Canvas extends EmbindObject<Canvas> {
ambientColor: InputColor, spotColor: InputColor, flags: number): void;
/**
* Draw the given text at the location (x, y) using the provided paint and font. If non-shaped
* text is provided, the text will be drawn as is; no line-breaking, no ligatures, etc.
* @param str - either a string or pre-shaped text. Unicode text is supported.
* Draw the given text at the location (x, y) using the provided paint and font. The text will
* be drawn as is; no shaping, left-to-right, etc.
* @param str
* @param x
* @param y
* @param paint
* @param font
*/
drawText(str: string | ShapedText, x: number, y: number, paint: Paint, font: Font): void;
drawText(str: string, x: number, y: number, paint: Paint, font: Font): void;
/**
* Draws the given TextBlob at (x, y) using the current clip, current matrix, and the
@ -2892,17 +2872,6 @@ export interface ParagraphStyleConstructor {
new(ps: ParagraphStyle): ParagraphStyle;
}
/**
* This class is an abstraction around SkShaper.h
*/
export interface ShapedTextConstructor {
/**
* Return a ShapedText from the given options. See SkShaper.h for more.
* @param opts
*/
new (opts: ShapedTextOpts): ShapedText;
}
/**
* See SkColorFilter.h for more.
*/

View File

@ -603,62 +603,6 @@ bool ApplyStroke(SkPath& path, StrokeOpts opts) {
return p.getFillPath(path, &path, nullptr, opts.precision);
}
// Text Shaping abstraction
#ifndef SK_NO_FONTS
struct ShapedTextOpts {
SkFont font;
bool leftToRight;
std::string text;
SkScalar width;
};
std::unique_ptr<SkShaper> shaper;
static sk_sp<SkTextBlob> do_shaping(const ShapedTextOpts& opts, SkPoint* pt) {
SkTextBlobBuilderRunHandler builder(opts.text.c_str(), {0, 0});
if (!shaper) {
shaper = SkShaper::Make();
}
shaper->shape(opts.text.c_str(), opts.text.length(),
opts.font, opts.leftToRight,
opts.width, &builder);
*pt = builder.endPoint();
return builder.makeBlob();
}
// TODO(kjlubick) ShapedText is a very thin veneer around SkTextBlob - can probably remove it.
class ShapedText {
public:
ShapedText(ShapedTextOpts opts) : fOpts(opts) {}
SkRect getBounds() {
this->init();
return SkRect::MakeLTRB(0, 0, fOpts.width, fPoint.y());
}
SkTextBlob* blob() {
this->init();
return fBlob.get();
}
private:
const ShapedTextOpts fOpts;
SkPoint fPoint;
sk_sp<SkTextBlob> fBlob;
void init() {
if (!fBlob) {
fBlob = do_shaping(fOpts, &fPoint);
}
}
};
void drawShapedText(SkCanvas& canvas, ShapedText st, SkScalar x,
SkScalar y, const SkPaint& paint) {
canvas.drawTextBlob(st.blob(), x, y, paint);
}
#endif //SK_NO_FONTS
// This function is private, we call it in interface.js
void computeTonalColors(uintptr_t cPtrAmbi /* float * */, uintptr_t cPtrSpot /* float * */) {
// private methods accepting colors take pointers to floats already copied into wasm memory.
@ -1045,7 +989,6 @@ EMSCRIPTEN_BINDINGS(Skia) {
flags);
}))
#ifndef SK_NO_FONTS
.function("_drawShapedText", &drawShapedText)
.function("_drawSimpleText", optional_override([](SkCanvas& self, uintptr_t /* char* */ sptr,
size_t len, SkScalar x, SkScalar y, const SkFont& font,
const SkPaint& paint) {
@ -1236,14 +1179,6 @@ EMSCRIPTEN_BINDINGS(Skia) {
.function("setSubpixel", &SkFont::setSubpixel)
.function("setTypeface", &SkFont::setTypeface, allow_raw_pointers());
class_<ShapedText>("ShapedText")
.constructor<ShapedTextOpts>()
.function("_getBounds", optional_override([](ShapedText& self,
uintptr_t /* float* */ fPtr)->void {
SkRect* output = reinterpret_cast<SkRect*>(fPtr);
output[0] = self.getBounds();
}));
class_<SkFontMgr>("FontMgr")
.smart_ptr<sk_sp<SkFontMgr>>("sk_sp<FontMgr>")
.class_function("_fromData", optional_override([](uintptr_t /* uint8_t** */ dPtr,
@ -2019,14 +1954,6 @@ EMSCRIPTEN_BINDINGS(Skia) {
// object and does not require delete().
// https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#value-types
#ifndef SK_NO_FONTS
value_object<ShapedTextOpts>("ShapedTextOpts")
.field("font", &ShapedTextOpts::font)
.field("leftToRight", &ShapedTextOpts::leftToRight)
.field("text", &ShapedTextOpts::text)
.field("width", &ShapedTextOpts::width);
#endif
value_object<SimpleImageInfo>("ImageInfo")
.field("width", &SimpleImageInfo::width)
.field("height", &SimpleImageInfo::height)

View File

@ -82,7 +82,6 @@ var CanvasKit = {
_MakePicture: function() {},
_decodeAnimatedImage: function() {},
_decodeImage: function() {},
_drawShapedText: function() {},
_getShadowLocalBounds: function() {},
// The testing object is meant to expose internal functions
@ -186,14 +185,6 @@ var CanvasKit = {
ColorBuilder: function() {},
RectBuilder: function() {},
ShapedText: {
prototype: {
getBounds: function() {},
},
// private API (from C++ bindings)
_getBounds: function() {},
},
AnimatedImage: {
// public API (from C++ bindings)
decodeNextFrame: function() {},
@ -525,7 +516,6 @@ var CanvasKit = {
copy: function() {},
countPoints: function() {},
equals: function() {},
getBounds: function() {},
getFillType: function() {},
isEmpty: function() {},
isVolatile: function() {},
@ -553,6 +543,7 @@ var CanvasKit = {
computeTightBounds: function() {},
cubicTo: function() {},
dash: function() {},
getBounds: function() {},
getPoint: function() {},
lineTo: function() {},
moveTo: function() {},
@ -589,6 +580,7 @@ var CanvasKit = {
_computeTightBounds: function() {},
_cubicTo: function() {},
_dash: function() {},
_getBounds: function() {},
_getPoint: function() {},
_lineTo: function() {},
_moveTo: function() {},

View File

@ -1,21 +1,16 @@
CanvasKit._extraInitializations = CanvasKit._extraInitializations || [];
CanvasKit._extraInitializations.push(function() {
// str can be either a text string or a ShapedText object
CanvasKit.Canvas.prototype.drawText = function(str, x, y, paint, font) {
if (typeof str === 'string') {
// lengthBytesUTF8 and stringToUTF8Array are defined in the emscripten
// JS. See https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#stringToUTF8
var strLen = lengthBytesUTF8(str);
// Add 1 for null terminator, which we need when copying/converting, but can ignore
// when we call into Skia.
var strPtr = CanvasKit._malloc(strLen + 1);
stringToUTF8(str, strPtr, strLen + 1);
this._drawSimpleText(strPtr, strLen, x, y, font, paint);
CanvasKit._free(strPtr);
} else {
this._drawShapedText(str, x, y, paint);
}
// lengthBytesUTF8 and stringToUTF8Array are defined in the emscripten
// JS. See https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#stringToUTF8
var strLen = lengthBytesUTF8(str);
// Add 1 for null terminator, which we need when copying/converting, but can ignore
// when we call into Skia.
var strPtr = CanvasKit._malloc(strLen + 1);
stringToUTF8(str, strPtr, strLen + 1);
this._drawSimpleText(strPtr, strLen, x, y, font, paint);
CanvasKit._free(strPtr);
};
// Glyphs should be a Uint32Array of glyph ids, e.g. provided by Font.getGlyphIDs.
@ -171,19 +166,6 @@ CanvasKit._extraInitializations.push(function() {
return font;
};
// Clients can pass in a Float32Array with length 4 to this and the results
// will be copied into that array. Otherwise, a new TypedArray will be allocated
// and returned.
CanvasKit.ShapedText.prototype.getBounds = function(optionalOutputArray) {
this._getBounds(_scratchFourFloatsAPtr);
var ta = _scratchFourFloatsA['toTypedArray']();
if (optionalOutputArray) {
optionalOutputArray.set(ta);
return optionalOutputArray;
}
return ta.slice();
};
CanvasKit.TextBlob.MakeOnPath = function(str, path, font, initialOffset) {
if (!str || !str.length) {
Debug('ignoring 0 length string');

View File

@ -33,63 +33,6 @@ describe('Font Behavior', () => {
document.body.removeChild(container);
});
gm('text_shaping', (canvas) => {
const paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.BLUE);
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const fontMgr = CanvasKit.FontMgr.RefDefault();
const notoSerif = fontMgr.MakeTypefaceFromData(notoSerifFontBuffer);
const textPaint = new CanvasKit.Paint();
const textFont = new CanvasKit.Font(notoSerif, 20);
canvas.drawRect(CanvasKit.LTRBRect(30, 30, 200, 200), paint);
canvas.drawText('This text is not shaped, and overflows the boundary',
35, 50, textPaint, textFont);
const shapedText = new CanvasKit.ShapedText({
font: textFont,
leftToRight: true,
text: 'This text *is* shaped, and wraps to the right width.',
width: 160,
});
const textBoxX = 35;
const textBoxY = 55;
canvas.drawText(shapedText, textBoxX, textBoxY, textPaint);
const bounds = shapedText.getBounds();
bounds[0] += textBoxX; // left
bounds[2] += textBoxX; // right
bounds[1] += textBoxY; // top
bounds[3] += textBoxY; // bottom
canvas.drawRect(bounds, paint);
const SHAPE_TEST_TEXT = 'VAVAVAVAVAFIfi';
const textFont2 = new CanvasKit.Font(notoSerif, 60);
const shapedText2 = new CanvasKit.ShapedText({
font: textFont2,
leftToRight: true,
text: SHAPE_TEST_TEXT,
width: 600,
});
canvas.drawText('no kerning ↓', 10, 240, textPaint, textFont);
canvas.drawText(SHAPE_TEST_TEXT, 10, 300, textPaint, textFont2);
canvas.drawText(shapedText2, 10, 300, textPaint);
canvas.drawText('kerning ↑', 10, 390, textPaint, textFont);
paint.delete();
notoSerif.delete();
textPaint.delete();
textFont.delete();
shapedText.delete();
textFont2.delete();
shapedText2.delete();
fontMgr.delete();
});
gm('monospace_text_on_path', (canvas) => {
const paint = new CanvasKit.Paint();
paint.setAntiAlias(true);