skia2/modules/canvaskit/font.js

201 lines
6.8 KiB
JavaScript
Raw Normal View History

CanvasKit._extraInitializations = CanvasKit._extraInitializations || [];
CanvasKit._extraInitializations.push(function() {
// str can be either a text string or a ShapedText object
CanvasKit.SkCanvas.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);
} else {
this._drawShapedText(str, x, y, paint);
}
}
// 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.HEAPU8.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;
}
// arguments should all be arrayBuffers or be an array of arrayBuffers.
CanvasKit.SkFontMgr.FromData = function() {
if (!arguments.length) {
SkDebug('Could not make SkFontMgr from no font sources');
return null;
}
var fonts = arguments;
if (fonts.length === 1 && Array.isArray(fonts[0])) {
fonts = arguments[0];
}
if (!fonts.length) {
SkDebug('Could not make SkFontMgr from no font sources');
return null;
}
var dPtrs = [];
var sizes = [];
for (var i = 0; i < fonts.length; i++) {
var data = new Uint8Array(fonts[i]);
[canvaskit] Fix infrequent crash in SkFontMgr.FromData The bug here is very subtle, as is the mitigation. Quick background on WASM memory, there is an object called wasmMemory (which might be hoisted into scope for CanvasKit's pre-js functions), of type WebAssembly.Memory which is a resizable ArrayBuffer. Emscripten provides the JS code to initialize this and handle size increases. Emscripten also provides TypedArray "views" into this buffer. These are called CanvasKit.HEAPU8, CanvasKit.HEAPF32, etc. When there is a call to CanvasKit._malloc, wasmMemory may be resized. If that happens, the previous TypedArray views become invalid. However, in the same call to _malloc, emscripten will refresh the views [1]. So, dealing with CanvasKit.HEAPU8 directly (quick aside, we never expect clients to mess with these views, only us in our glue JS code [e.g. interface.js]), should always be safe because if they were to be invalidated in a call to _malloc, the views would be refreshed before _malloc continues. The problem that existed before was when we were passing CanvasKit.HEAP* as a parameter to a function, in which the function would call _malloc before using the typed array parameter: //... let us suppose wasmMemory is backed by ArrayBuffer D copy1dArray(arr, HEAPU32); // The HEAPU32 TypedArray (backed by ArrayBuffer D) is stored // to a function parameter "dest" function copy1dArray(arr, dest, ptr) { // ... if (!ptr) { ptr = CanvasKit._malloc(arr.length * dest.BYTES_PER_ELEMENT); // Suppose _malloc needs to resize wasmMemory and is // now backed by ArrayBuffer E. // Note: The field CanvasKit.HEAPU32 is correctly backed // by ArrayBuffer E, but variable dest still points to a // TypedArray backed by ArrayBuffer D. } // dest.set will fail with a "neutered ArrayBuffer" error // because ArrayBuffer D is effectively gone (replaced by E). dest.set(arr, ptr / dest.BYTES_PER_ELEMENT); The fix here is to pass in the field name indicating the TypedArray view we want to write our data into instead of using the view itself as the parameter. [1] https://github.com/emscripten-core/emscripten/blob/e4271595539cf1ca81128280cdc72f7245e700a0/src/preamble.js#L344 Change-Id: I46cfb98f8bdf928b61690a5ced034a5961356398 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/294516 Reviewed-by: Nathaniel Nifong <nifong@google.com>
2020-06-05 11:13:48 +00:00
var dptr = copy1dArray(data, "HEAPU8");
dPtrs.push(dptr);
sizes.push(data.byteLength);
}
// Pointers are 32 bit unsigned ints
[canvaskit] Fix infrequent crash in SkFontMgr.FromData The bug here is very subtle, as is the mitigation. Quick background on WASM memory, there is an object called wasmMemory (which might be hoisted into scope for CanvasKit's pre-js functions), of type WebAssembly.Memory which is a resizable ArrayBuffer. Emscripten provides the JS code to initialize this and handle size increases. Emscripten also provides TypedArray "views" into this buffer. These are called CanvasKit.HEAPU8, CanvasKit.HEAPF32, etc. When there is a call to CanvasKit._malloc, wasmMemory may be resized. If that happens, the previous TypedArray views become invalid. However, in the same call to _malloc, emscripten will refresh the views [1]. So, dealing with CanvasKit.HEAPU8 directly (quick aside, we never expect clients to mess with these views, only us in our glue JS code [e.g. interface.js]), should always be safe because if they were to be invalidated in a call to _malloc, the views would be refreshed before _malloc continues. The problem that existed before was when we were passing CanvasKit.HEAP* as a parameter to a function, in which the function would call _malloc before using the typed array parameter: //... let us suppose wasmMemory is backed by ArrayBuffer D copy1dArray(arr, HEAPU32); // The HEAPU32 TypedArray (backed by ArrayBuffer D) is stored // to a function parameter "dest" function copy1dArray(arr, dest, ptr) { // ... if (!ptr) { ptr = CanvasKit._malloc(arr.length * dest.BYTES_PER_ELEMENT); // Suppose _malloc needs to resize wasmMemory and is // now backed by ArrayBuffer E. // Note: The field CanvasKit.HEAPU32 is correctly backed // by ArrayBuffer E, but variable dest still points to a // TypedArray backed by ArrayBuffer D. } // dest.set will fail with a "neutered ArrayBuffer" error // because ArrayBuffer D is effectively gone (replaced by E). dest.set(arr, ptr / dest.BYTES_PER_ELEMENT); The fix here is to pass in the field name indicating the TypedArray view we want to write our data into instead of using the view itself as the parameter. [1] https://github.com/emscripten-core/emscripten/blob/e4271595539cf1ca81128280cdc72f7245e700a0/src/preamble.js#L344 Change-Id: I46cfb98f8bdf928b61690a5ced034a5961356398 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/294516 Reviewed-by: Nathaniel Nifong <nifong@google.com>
2020-06-05 11:13:48 +00:00
var datasPtr = copy1dArray(dPtrs, "HEAPU32");
var sizesPtr = copy1dArray(sizes, "HEAPU32");
var fm = CanvasKit.SkFontMgr._fromData(datasPtr, sizesPtr, fonts.length);
// The SkFontMgr has taken ownership of the bytes we allocated in the for loop.
CanvasKit._free(datasPtr);
CanvasKit._free(sizesPtr);
return fm;
}
// fontData should be an arrayBuffer
CanvasKit.SkFontMgr.prototype.MakeTypefaceFromData = function(fontData) {
var data = new Uint8Array(fontData);
[canvaskit] Fix infrequent crash in SkFontMgr.FromData The bug here is very subtle, as is the mitigation. Quick background on WASM memory, there is an object called wasmMemory (which might be hoisted into scope for CanvasKit's pre-js functions), of type WebAssembly.Memory which is a resizable ArrayBuffer. Emscripten provides the JS code to initialize this and handle size increases. Emscripten also provides TypedArray "views" into this buffer. These are called CanvasKit.HEAPU8, CanvasKit.HEAPF32, etc. When there is a call to CanvasKit._malloc, wasmMemory may be resized. If that happens, the previous TypedArray views become invalid. However, in the same call to _malloc, emscripten will refresh the views [1]. So, dealing with CanvasKit.HEAPU8 directly (quick aside, we never expect clients to mess with these views, only us in our glue JS code [e.g. interface.js]), should always be safe because if they were to be invalidated in a call to _malloc, the views would be refreshed before _malloc continues. The problem that existed before was when we were passing CanvasKit.HEAP* as a parameter to a function, in which the function would call _malloc before using the typed array parameter: //... let us suppose wasmMemory is backed by ArrayBuffer D copy1dArray(arr, HEAPU32); // The HEAPU32 TypedArray (backed by ArrayBuffer D) is stored // to a function parameter "dest" function copy1dArray(arr, dest, ptr) { // ... if (!ptr) { ptr = CanvasKit._malloc(arr.length * dest.BYTES_PER_ELEMENT); // Suppose _malloc needs to resize wasmMemory and is // now backed by ArrayBuffer E. // Note: The field CanvasKit.HEAPU32 is correctly backed // by ArrayBuffer E, but variable dest still points to a // TypedArray backed by ArrayBuffer D. } // dest.set will fail with a "neutered ArrayBuffer" error // because ArrayBuffer D is effectively gone (replaced by E). dest.set(arr, ptr / dest.BYTES_PER_ELEMENT); The fix here is to pass in the field name indicating the TypedArray view we want to write our data into instead of using the view itself as the parameter. [1] https://github.com/emscripten-core/emscripten/blob/e4271595539cf1ca81128280cdc72f7245e700a0/src/preamble.js#L344 Change-Id: I46cfb98f8bdf928b61690a5ced034a5961356398 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/294516 Reviewed-by: Nathaniel Nifong <nifong@google.com>
2020-06-05 11:13:48 +00:00
var fptr = copy1dArray(data, "HEAPU8");
var font = this._makeTypefaceFromData(fptr, data.byteLength);
if (!font) {
SkDebug('Could not decode font data');
// We do not need to free the data since the C++ will do that for us
// when the font is deleted (or fails to decode);
return null;
}
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);
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
// 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 blob = CanvasKit.SkTextBlob._MakeFromText(strPtr, strLen - 1, font);
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;
}
});