[canvaskit] Canvas API for loading fonts
There's the barest hint of a "font manager" here.
Basically, nothing smart to deal with bold or fallbacks.
Maybe one day, we'll expose
SkTypeface* matchStyleCSS3(const SkFontStyle& pattern);
and do smart things for fallbacks, but for now that's not
in the immediate future.
Docs-Preview: https://skia.org/?cl=177067
Bug: skia:
Change-Id: Iaeabcbf5ff4511a01b37c16c983e447c25b0e3e7
Reviewed-on: https://skia-review.googlesource.com/c/177067
Reviewed-by: Ben Wagner <bungeman@google.com>
diff --git a/experimental/canvaskit/htmlcanvas/canvas2dcontext.js b/experimental/canvaskit/htmlcanvas/canvas2dcontext.js
index dc2856d..5a07152 100644
--- a/experimental/canvaskit/htmlcanvas/canvas2dcontext.js
+++ b/experimental/canvaskit/htmlcanvas/canvas2dcontext.js
@@ -6,6 +6,9 @@
this._paint.setStrokeMiter(10);
this._paint.setStrokeCap(CanvasKit.StrokeCap.Butt);
this._paint.setStrokeJoin(CanvasKit.StrokeJoin.Miter);
+ this._paint.setTextSize(10);
+ this._paint.setTypeface(null);
+ this._fontString = '10px monospace';
this._strokeStyle = CanvasKit.BLACK;
this._fillStyle = CanvasKit.BLACK;
@@ -22,6 +25,7 @@
this._imageFilterQuality = CanvasKit.FilterQuality.Low;
this._imageSmoothingEnabled = true;
+
this._paint.setStrokeWidth(this._strokeWidth);
this._paint.setBlendMode(this._globalCompositeOperation);
@@ -90,14 +94,19 @@
Object.defineProperty(this, 'font', {
enumerable: true,
- get: function(newStyle) {
- // TODO generate this
- return '10px sans-serif';
+ get: function() {
+ return this._fontString;
},
- set: function(newStyle) {
- var size = parseFontSize(newStyle);
- // TODO(kjlubick) styles, font name
- this._paint.setTextSize(size);
+ set: function(newFont) {
+ var tf = getTypeface(newFont);
+ if (tf) {
+ // tf is a "dict" according to closure, that is, the field
+ // names are not minified. Thus, we need to access it via
+ // bracket notation to tell closure not to minify these names.
+ this._paint.setTextSize(tf['sizePx']);
+ this._paint.setTypeface(tf['typeface']);
+ this._fontString = newFont;
+ }
}
});
@@ -926,26 +935,25 @@
CanvasKit.SkMatrix.invert(newState.ctm)
);
this._currentPath.transform(combined);
+ this._paint.delete();
+ this._paint = newState.paint;
this._lineDashList = newState.ldl;
this._strokeWidth = newState.sw;
- this._paint.setStrokeWidth(this._strokeWidth);
this._strokeStyle = newState.ss;
this._fillStyle = newState.fs;
- this._paint.setStrokeCap(newState.cap);
- this._paint.setStrokeJoin(newState.jn);
- this._paint.setStrokeMiter(newState.mtr);
this._shadowOffsetX = newState.sox;
this._shadowOffsetY = newState.soy;
this._shadowBlur = newState.sb;
this._shadowColor = newState.shc;
this._globalAlpha = newState.ga;
this._globalCompositeOperation = newState.gco;
- this._paint.setBlendMode(this._globalCompositeOperation);
this._lineDashOffset = newState.ldo;
this._imageSmoothingEnabled = newState.ise;
this._imageFilterQuality = newState.isq;
- //TODO: font, textAlign, textBaseline, direction
+ this._fontString = newState.fontstr;
+
+ //TODO: textAlign, textBaseline
// restores the clip and ctm
this._canvas.restore();
@@ -980,24 +988,23 @@
}
this._canvasStateStack.push({
- ctm: this._currentTransform.slice(),
- ldl: this._lineDashList.slice(),
- sw: this._strokeWidth,
- ss: ss,
- fs: fs,
- cap: this._paint.getStrokeCap(),
- jn: this._paint.getStrokeJoin(),
- mtr: this._paint.getStrokeMiter(),
- sox: this._shadowOffsetX,
- soy: this._shadowOffsetY,
- sb: this._shadowBlur,
- shc: this._shadowColor,
- ga: this._globalAlpha,
- ldo: this._lineDashOffset,
- gco: this._globalCompositeOperation,
- ise: this._imageSmoothingEnabled,
- isq: this._imageFilterQuality,
- //TODO: font, textAlign, textBaseline, direction
+ ctm: this._currentTransform.slice(),
+ ldl: this._lineDashList.slice(),
+ sw: this._strokeWidth,
+ ss: ss,
+ fs: fs,
+ sox: this._shadowOffsetX,
+ soy: this._shadowOffsetY,
+ sb: this._shadowBlur,
+ shc: this._shadowColor,
+ ga: this._globalAlpha,
+ ldo: this._lineDashOffset,
+ gco: this._globalCompositeOperation,
+ ise: this._imageSmoothingEnabled,
+ isq: this._imageFilterQuality,
+ paint: this._paint.copy(),
+ fontstr: this._fontString,
+ //TODO: textAlign, textBaseline
});
// Saves the clip
this._canvas.save();
diff --git a/experimental/canvaskit/htmlcanvas/font.js b/experimental/canvaskit/htmlcanvas/font.js
index 549208d..340bd00 100644
--- a/experimental/canvaskit/htmlcanvas/font.js
+++ b/experimental/canvaskit/htmlcanvas/font.js
@@ -1,38 +1,113 @@
// Functions dealing with parsing/stringifying fonts go here.
+var fontStringRegex = new RegExp(
+ '(italic|oblique|normal|)\\s*' + // style
+ '(small-caps|normal|)\\s*' + // variant
+ '(bold|bolder|lighter|[1-9]00|normal|)\\s*' + // weight
+ '([\\d\\.]+)' + // size
+ '(px|pt|pc|in|cm|mm|%|em|ex|ch|rem|q)' + // unit
+ // line-height is ignored here, as per the spec
+ '(.+)' // family
+ );
-var units = 'px|pt|pc|in|cm|mm|%|em|ex|ch|rem|q';
-var fontSizeRegex = new RegExp('([\\d\\.]+)(' + units + ')');
+function stripWhitespace(str) {
+ return str.replace(/^\s+|\s+$/, '');
+}
+
var defaultHeight = 16;
// Based off of node-canvas's parseFont
// returns font size in px, which represents the em width.
-function parseFontSize(fontStr) {
- // This is naive and doesn't account for line-height yet
- // (but neither does node-canvas's?)
- var fontSize = fontSizeRegex.exec(fontStr);
- if (!fontSize) {
- SkDebug('Could not parse font size' + fontStr);
- return 16;
+function parseFontString(fontStr) {
+
+ var font = fontStringRegex.exec(fontStr);
+ if (!font) {
+ SkDebug('Invalid font string ' + fontStr);
+ return null;
}
- var size = parseFloat(fontSize[1]);
- var unit = fontSize[2];
+
+ var size = parseFloat(font[4]);
+ var sizePx = defaultHeight;
+ var unit = font[5];
switch (unit) {
- case 'pt':
case 'em':
case 'rem':
- return size * 4/3;
+ sizePx = size * defaultHeight;
+ break;
+ case 'pt':
+ sizePx = size * 4/3;
+ break;
case 'px':
- return size;
+ sizePx = size;
+ break;
case 'pc':
- return size * 16;
+ sizePx = size * defaultHeight;
+ break;
case 'in':
- return size * 96;
+ sizePx = size * 96;
+ break;
case 'cm':
- return size * 96.0 / 2.54;
+ sizePx = size * 96.0 / 2.54;
+ break;
case 'mm':
- return size * (96.0 / 25.4);
+ sizePx = size * (96.0 / 25.4);
+ break;
case 'q': // quarter millimeters
- return size * (96.0 / 25.4 / 4);
+ sizePx = size * (96.0 / 25.4 / 4);
+ break;
case '%':
- return size * (defaultHeight / 75);
+ sizePx = size * (defaultHeight / 75);
+ break;
}
-}
\ No newline at end of file
+ return {
+ 'style': font[1],
+ 'variant': font[2],
+ 'weight': font[3],
+ 'sizePx': sizePx,
+ 'family': font[6].trim()
+ };
+}
+
+function getTypeface(fontstr) {
+ var descriptors = parseFontString(fontstr);
+ var typeface = getFromFontCache(descriptors);
+ descriptors['typeface'] = typeface;
+ return descriptors;
+}
+
+// null means use the default typeface (which is currently NotoMono)
+var fontCache = {
+ 'Noto Mono': {
+ '*': null, // is used if we have this font family, but not the right style/variant/weight
+ },
+ 'monospace': {
+ '*': null,
+ }
+};
+
+// descriptors is like https://developer.mozilla.org/en-US/docs/Web/API/FontFace/FontFace
+// The ones currently supported are family, style, variant, weight.
+function addToFontCache(typeface, descriptors) {
+ var key = (descriptors['style'] || 'normal') + '|' +
+ (descriptors['variant'] || 'normal') + '|' +
+ (descriptors['weight'] || 'normal');
+ var fam = descriptors['family'];
+ if (!fontCache[fam]) {
+ // preload with a fallback to this typeface
+ fontCache[fam] = {
+ '*': typeface,
+ };
+ }
+ fontCache[fam][key] = typeface;
+}
+
+function getFromFontCache(descriptors) {
+ var key = (descriptors['style'] || 'normal') + '|' +
+ (descriptors['variant'] || 'normal') + '|' +
+ (descriptors['weight'] || 'normal');
+ var fam = descriptors['family'];
+ if (!fontCache[fam]) {
+ return null;
+ }
+ return fontCache[fam][key] || fontCache[fam]['*'];
+}
+
+CanvasKit._testing['parseFontString'] = parseFontString;
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/htmlcanvas.js b/experimental/canvaskit/htmlcanvas/htmlcanvas.js
index b756b26..6f118e5 100644
--- a/experimental/canvaskit/htmlcanvas/htmlcanvas.js
+++ b/experimental/canvaskit/htmlcanvas/htmlcanvas.js
@@ -9,7 +9,8 @@
function HTMLCanvas(skSurface) {
this._surface = skSurface;
this._context = new CanvasRenderingContext2D(skSurface.getCanvas());
- this._imgs = [];
+ this._toCleanup = [];
+ this._fontmgr = CanvasKit.SkFontMgr.RefDefault();
// Data is either an ArrayBuffer, a TypedArray, or a Node Buffer
this.decodeImage = function(data) {
@@ -17,10 +18,20 @@
if (!img) {
throw 'Invalid input';
}
- this._imgs.push(img);
+ this._toCleanup.push(img);
return img;
}
+ this.loadFont = function(buffer, descriptors) {
+ var newFont = this._fontmgr.MakeTypefaceFromData(buffer);
+ if (!newFont) {
+ SkDebug('font could not be processed', descriptors);
+ return null;
+ }
+ this._toCleanup.push(newFont);
+ addToFontCache(newFont, descriptors);
+ }
+
// A normal <canvas> requires that clients call getContext
this.getContext = function(type) {
if (type === '2d') {
@@ -56,7 +67,7 @@
this.dispose = function() {
this._context._dispose();
- this._imgs.forEach(function(i) {
+ this._toCleanup.forEach(function(i) {
i.delete();
});
this._surface.dispose();