skia2/modules/canvaskit/paragraph.js

191 lines
7.5 KiB
JavaScript
Raw Normal View History

(function(CanvasKit){
CanvasKit._extraInitializations = CanvasKit._extraInitializations || [];
CanvasKit._extraInitializations.push(function() {
CanvasKit.Paragraph.prototype.getRectsForRange = function(start, end, hStyle, wStyle) {
/**
* This is bytes, but we'll want to think about them as float32s
* @type {Float32Array}
*/
var floatArray = this._getRectsForRange(start, end, hStyle, wStyle);
if (!floatArray || !floatArray.length) {
return [];
}
var ret = [];
for (var i = 0; i < floatArray.length; i+=5) {
var r = CanvasKit.LTRBRect(floatArray[i], floatArray[i+1], floatArray[i+2], floatArray[i+3]);
if (floatArray[i+4] === 0) {
r['direction'] = CanvasKit.TextDirection.RTL;
} else {
r['direction'] = CanvasKit.TextDirection.LTR;
}
ret.push(r);
}
CanvasKit._free(floatArray.byteOffset);
return ret;
}
// Registers the font (provided as an arrayBuffer) with the alias `family`.
CanvasKit.TypefaceFontProvider.prototype.registerFont = function(font, family) {
var typeface = CanvasKit.SkFontMgr.RefDefault().MakeTypefaceFromData(font);
if (!typeface) {
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;
}
var familyPtr = cacheOrCopyString(family);
this._registerFont(typeface, familyPtr);
}
// These helpers fill out all fields, because emscripten complains if we
// have undefined and it expects, for example, a float.
CanvasKit.ParagraphStyle = function(s) {
// Use [''] to tell closure not to minify the names
// TODO(kjlubick): strutStyle
s['disableHinting'] = s['disableHinting'] || false;
if (s['ellipsis']) {
var str = s['ellipsis'];
s['_ellipsisPtr'] = cacheOrCopyString(str);
s['_ellipsisLen'] = lengthBytesUTF8(str) + 1; // add 1 for the null terminator.
} else {
s['_ellipsisPtr'] = nullptr;
s['_ellipsisLen'] = 0;
}
s['heightMultiplier'] = s['heightMultiplier'] || 0;
s['maxLines'] = s['maxLines'] || 0;
s['textAlign'] = s['textAlign'] || CanvasKit.TextAlign.Start;
s['textDirection'] = s['textDirection'] || CanvasKit.TextDirection.LTR;
s['textStyle'] = CanvasKit.TextStyle(s['textStyle']);
return s;
};
function fontStyle(s) {
s = s || {};
// Can't check for falsey as 0 width means "invisible".
if (s['weight'] === undefined) {
s['weight'] = CanvasKit.FontWeight.Normal;
}
s['width'] = s['width'] || CanvasKit.FontWidth.Normal;
s['slant'] = s['slant'] || CanvasKit.FontSlant.Upright;
return s;
}
CanvasKit.TextStyle = function(s) {
// Use [''] to tell closure not to minify the names
if (!s['color']) {
s['color'] = CanvasKit.BLACK;
}
s['decoration'] = s['decoration'] || 0;
s['decorationThickness'] = s['decorationThickness'] || 0;
s['fontSize'] = s['fontSize'] || 0;
s['fontStyle'] = fontStyle(s['fontStyle']);
return s;
};
// returns a pointer to a place on the heap that has an array
// of char* (effectively a char**). For now, this does the naive thing
// and depends on the string being null-terminated. This should be used
// for simple, well-formed things (e.g. font-families), not arbitrary
// text that should be drawn. If we need this to handle more complex
// strings, it should return two pointers, a pointer of the
// string array and a pointer to an array of the strings byte lengths.
function naiveCopyStrArray(strings) {
if (!strings || !strings.length) {
return nullptr;
}
var sPtrs = [];
for (var i = 0; i < strings.length; i++) {
var strPtr = cacheOrCopyString(strings[i]);
sPtrs.push(strPtr);
}
[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
return copy1dArray(sPtrs, "HEAPU32");
}
// maps string -> malloc'd pointer
var stringCache = {};
// cacheOrCopyString copies a string from JS into WASM on the heap and returns the pointer
// to the memory of the string. It is expected that a caller to this helper will *not* free
// that memory, so it is cached. Thus, if a future call to this function with the same string
// will return the cached pointer, preventing the memory usage from growing unbounded (in
// a normal use case).
function cacheOrCopyString(str) {
if (stringCache[str]) {
return stringCache[str];
}
// Add 1 for null terminator, which we need when copying/converting
var strLen = lengthBytesUTF8(str) + 1;
var strPtr = CanvasKit._malloc(strLen);
stringToUTF8(str, strPtr, strLen);
stringCache[str] = strPtr;
return strPtr;
}
// These scratch arrays are allocated once to copy the color data into, which saves us
// having to free them after every invocation.
var scratchForegroundColorPtr = CanvasKit._malloc(4 * 4); // room for 4 32bit floats
var scratchBackgroundColorPtr = CanvasKit._malloc(4 * 4); // room for 4 32bit floats
function copyArrays(textStyle) {
// These color fields were arrays, but will set to WASM pointers before we pass this
// object over the WASM interface.
textStyle['_colorPtr'] = copyColorToWasm(textStyle['color']);
textStyle['_foregroundColorPtr'] = nullptr; // nullptr is 0, from helper.js
textStyle['_backgroundColorPtr'] = nullptr;
if (textStyle['foregroundColor']) {
textStyle['_foregroundColorPtr'] = copyColorToWasm(textStyle['foregroundColor'], scratchForegroundColorPtr);
}
if (textStyle['backgroundColor']) {
textStyle['_backgroundColorPtr'] = copyColorToWasm(textStyle['backgroundColor'], scratchBackgroundColorPtr);
}
if (Array.isArray(textStyle['fontFamilies']) && textStyle['fontFamilies'].length) {
textStyle['_fontFamiliesPtr'] = naiveCopyStrArray(textStyle['fontFamilies']);
textStyle['_fontFamiliesLen'] = textStyle['fontFamilies'].length;
} else {
textStyle['_fontFamiliesPtr'] = nullptr;
textStyle['_fontFamiliesLen'] = 0;
SkDebug('no font families provided, text may draw wrong or not at all');
}
}
function freeArrays(textStyle) {
// The font family strings will get copied to a vector on the C++ side, which is owned by
// the text style.
CanvasKit._free(textStyle['_fontFamiliesPtr']);
}
CanvasKit.ParagraphBuilder.Make = function(paragraphStyle, fontManager) {
copyArrays(paragraphStyle['textStyle']);
var result = CanvasKit.ParagraphBuilder._Make(paragraphStyle, fontManager);
freeArrays(paragraphStyle['textStyle']);
return result;
};
CanvasKit.ParagraphBuilder.MakeFromFontProvider = function(paragraphStyle, fontProvider) {
copyArrays(paragraphStyle['textStyle']);
var result = CanvasKit.ParagraphBuilder._MakeFromFontProvider(paragraphStyle, fontProvider);
freeArrays(paragraphStyle['textStyle']);
return result;
};
CanvasKit.ParagraphBuilder.prototype.pushStyle = function(textStyle) {
copyArrays(textStyle);
this._pushStyle(textStyle);
freeArrays(textStyle);
}
CanvasKit.ParagraphBuilder.prototype.pushPaintStyle = function(textStyle, fg, bg) {
copyArrays(textStyle);
this._pushPaintStyle(textStyle, fg, bg);
freeArrays(textStyle);
}
});
}(Module)); // When this file is loaded in, the high level object is "Module";