From 377ada5fd8a469ff04624711c1c6f04897b77ae3 Mon Sep 17 00:00:00 2001 From: Caleb Hearon Date: Tue, 2 Aug 2016 00:09:46 +0000 Subject: [PATCH] support family name lists, both system and custom * so you can now specify ctx.font = 'custom1, arial'; etc and the later fonts will be used for glyphs that aren't in the first ones * i'm now calling them sys_desc and user_desc to distinguish the description that matches the font on the system vs the description the user passes to Canvas.registerFont * cleaned up some style --- lib/context2d.js | 4 +- src/Canvas.cc | 67 +++++++++++++++++++++------------ src/Canvas.h | 8 ++-- src/CanvasRenderingContext2d.cc | 13 +++---- test/canvas.test.js | 10 ++--- 5 files changed, 59 insertions(+), 43 deletions(-) diff --git a/lib/context2d.js b/lib/context2d.js index df06888..3ecb379 100644 --- a/lib/context2d.js +++ b/lib/context2d.js @@ -77,7 +77,9 @@ var parseFont = exports.parseFont = function(str){ font.style = captures[2] || 'normal'; font.size = parseFloat(captures[3]); font.unit = captures[4]; - font.family = captures[5].replace(/["']/g, '').split(',')[0].trim(); + font.family = captures[5].replace(/["']/g, '').split(',').map(function (family) { + return family.trim(); + }).join(','); // TODO: dpi // TODO: remaining unit conversion diff --git a/src/Canvas.cc b/src/Canvas.cc index 17c2bd6..6dd5ba3 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -584,10 +584,10 @@ NAN_METHOD(Canvas::RegisterFont) { String::Utf8Value filePath(info[0]); - if (!register_font((unsigned char*) *filePath, &face.target_desc)) { + if (!register_font((unsigned char *) *filePath, &face.sys_desc)) { Nan::ThrowError("Could not load font to the system's font host"); } else { - PangoFontDescription* d = pango_font_description_new(); + PangoFontDescription *d = pango_font_description_new(); if (!info[1]->IsObject()) { Nan::ThrowError(GENERIC_FACE_ERROR); @@ -597,9 +597,9 @@ NAN_METHOD(Canvas::RegisterFont) { Local weight_prop = Nan::New("weight").ToLocalChecked(); Local style_prop = Nan::New("style").ToLocalChecked(); - const char* family; - const char* weight = "normal"; - const char* style = "normal"; + const char *family; + const char *weight = "normal"; + const char *style = "normal"; Local family_val = desc->Get(family_prop); if (family_val->IsString()) { @@ -633,9 +633,9 @@ NAN_METHOD(Canvas::RegisterFont) { pango_font_description_set_style(d, Canvas::GetStyleFromCSSString(style)); pango_font_description_set_family(d, family); - free((char*)family); - if (desc->HasOwnProperty(weight_prop)) free((char*)weight); - if (desc->HasOwnProperty(style_prop)) free((char*)style); + free((char *)family); + if (desc->HasOwnProperty(weight_prop)) free((char *)weight); + if (desc->HasOwnProperty(style_prop)) free((char *)style); face.user_desc = d; _font_face_list.push_back(face); @@ -757,31 +757,48 @@ Canvas::GetWeightFromCSSString(const char *weight) { } /* - * Tries to find a matching font given to registerFont + * Given a user description, return a description that will select the + * font either from the system or @font-face */ PangoFontDescription * -Canvas::FindCustomFace(PangoFontDescription *desc) { - PangoFontDescription* best_match = NULL; - PangoFontDescription* best_match_target = NULL; - std::vector::iterator it = _font_face_list.begin(); - - while (it != _font_face_list.end()) { - FontFace f = *it; - - if (g_ascii_strcasecmp(pango_font_description_get_family(desc), - pango_font_description_get_family(f.user_desc)) == 0) { - - if (best_match == NULL || pango_font_description_better_match(desc, best_match, f.user_desc)) { - best_match = f.user_desc; - best_match_target = f.target_desc; +Canvas::ResolveFontDescription(const PangoFontDescription *desc) { + FontFace best; + PangoFontDescription *ret = NULL; + + // One of the user-specified families could map to multiple SFNT family names + // if someone registered two different fonts under the same family name. + // https://drafts.csswg.org/css-fonts-3/#font-style-matching + char **families = g_strsplit(pango_font_description_get_family(desc), ",", -1); + GString *resolved_families = g_string_new(""); + + for (int i = 0; families[i]; ++i) { + GString *renamed_families = g_string_new(""); + std::vector::iterator it = _font_face_list.begin(); + + for (; it != _font_face_list.end(); ++it) { + if (g_ascii_strcasecmp(families[i], pango_font_description_get_family(it->user_desc)) == 0) { + if (renamed_families->len) g_string_append(renamed_families, ","); + g_string_append(renamed_families, pango_font_description_get_family(it->sys_desc)); + + if (i == 0 && (best.user_desc == NULL || pango_font_description_better_match(desc, best.user_desc, it->user_desc))) { + best = *it; + } } } - ++it; + if (resolved_families->len) g_string_append(resolved_families, ","); + g_string_append(resolved_families, renamed_families->len ? renamed_families->str : families[i]); + g_string_free(renamed_families, true); } - return best_match_target; + ret = pango_font_description_copy(best.sys_desc ? best.sys_desc : desc); + pango_font_description_set_family_static(ret, resolved_families->str); + + g_strfreev(families); + g_string_free(resolved_families, false); + + return ret; } /* diff --git a/src/Canvas.h b/src/Canvas.h index b782e7b..9411a18 100644 --- a/src/Canvas.h +++ b/src/Canvas.h @@ -40,14 +40,14 @@ typedef enum { CANVAS_TYPE_SVG } canvas_type_t; -/** +/* * FontFace describes a font file in terms of one PangoFontDescription that * will resolve to it and one that the user describes it as (like @font-face) */ class FontFace { public: - PangoFontDescription *target_desc; - PangoFontDescription *user_desc; + PangoFontDescription *sys_desc = NULL; + PangoFontDescription *user_desc = NULL; }; /* @@ -89,7 +89,7 @@ class Canvas: public Nan::ObjectWrap { #endif static PangoWeight GetWeightFromCSSString(const char *weight); static PangoStyle GetStyleFromCSSString(const char *style); - static PangoFontDescription* FindCustomFace(PangoFontDescription *desc); + static PangoFontDescription *ResolveFontDescription(const PangoFontDescription *desc); inline bool isPDF(){ return CANVAS_TYPE_PDF == type; } inline bool isSVG(){ return CANVAS_TYPE_SVG == type; } diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 5d404ae..c570541 100755 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -1840,17 +1840,14 @@ NAN_METHOD(Context2d::SetFont) { if (strlen(*family) > 0) pango_font_description_set_family(desc, *family); - PangoFontDescription *target_desc; - if ((target_desc = Canvas::FindCustomFace(desc))) { - pango_font_description_free(desc); - desc = pango_font_description_copy(target_desc); - } + PangoFontDescription *sys_desc = Canvas::ResolveFontDescription(desc); + pango_font_description_free(desc); - if (size > 0) pango_font_description_set_absolute_size(desc, size * PANGO_SCALE); + if (size > 0) pango_font_description_set_absolute_size(sys_desc, size * PANGO_SCALE); - context->state->fontDescription = desc; + context->state->fontDescription = sys_desc; - pango_layout_set_font_description(context->_layout, desc); + pango_layout_set_font_description(context->_layout, sys_desc); } /* diff --git a/test/canvas.test.js b/test/canvas.test.js index 1dbd204..b6c678d 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -44,15 +44,15 @@ describe('Canvas', function () { , '20px monospace' , { size: 20, unit: 'px', family: 'monospace' } , '50px Arial, sans-serif' - , { size: 50, unit: 'px', family: 'Arial' } + , { size: 50, unit: 'px', family: 'Arial,sans-serif' } , 'bold italic 50px Arial, sans-serif' - , { style: 'italic', weight: 'bold', size: 50, unit: 'px', family: 'Arial' } + , { style: 'italic', weight: 'bold', size: 50, unit: 'px', family: 'Arial,sans-serif' } , '50px Helvetica , Arial, sans-serif' - , { size: 50, unit: 'px', family: 'Helvetica' } + , { size: 50, unit: 'px', family: 'Helvetica,Arial,sans-serif' } , '50px "Helvetica Neue", sans-serif' - , { size: 50, unit: 'px', family: 'Helvetica Neue' } + , { size: 50, unit: 'px', family: 'Helvetica Neue,sans-serif' } , '50px "Helvetica Neue", "foo bar baz" , sans-serif' - , { size: 50, unit: 'px', family: 'Helvetica Neue' } + , { size: 50, unit: 'px', family: 'Helvetica Neue,foo bar baz,sans-serif' } , "50px 'Helvetica Neue'" , { size: 50, unit: 'px', family: 'Helvetica Neue' } , 'italic 20px Arial'