Browse Source

support @font-face style attrs in registerFont

* registerFont is now implemented as a static member function of the
  Canvas class itself. there is a static list of fonts registered
  on the class, which are picked up by setting the font when necessary.
* register_font will fill in its second arg with a PangoFontDescription
  that should resolve to the file specified
* to do that, freetype is now required again
master
Caleb Hearon 9 years ago
committed by Caleb Hearon
parent
commit
9bdcc5d958
  1. 8
      binding.gyp
  2. 13
      lib/canvas.js
  3. 129
      src/Canvas.cc
  4. 17
      src/Canvas.h
  5. 44
      src/CanvasRenderingContext2d.cc
  6. 23
      src/init.cc
  7. 185
      src/register_font.cc
  8. 4
      src/register_font.h

8
binding.gyp

@ -4,11 +4,13 @@
'variables': {
'GTK_Root%': 'C:/GTK', # Set the location of GTK all-in-one bundle
'with_jpeg%': 'false',
'with_freetype%': 'false',
'with_gif%': 'false'
}
}, { # 'OS!="win"'
'variables': {
'with_jpeg%': '<!(./util/has_lib.sh jpeg)',
'with_freetype%': '<!(./util/has_lib.sh freetype)',
'with_gif%': '<!(./util/has_lib.sh gif)'
}
}]
@ -101,12 +103,14 @@
'<!@(pkg-config pixman-1 --libs)',
'<!@(pkg-config cairo --libs)',
'<!@(pkg-config libpng --libs)',
'<!@(pkg-config pangocairo --libs)'
'<!@(pkg-config pangocairo --libs)',
'<!@(pkg-config freetype2 --libs)'
],
'include_dirs': [
'<!@(pkg-config cairo --cflags-only-I | sed s/-I//g)',
'<!@(pkg-config libpng --cflags-only-I | sed s/-I//g)',
'<!@(pkg-config pangocairo --cflags-only-I | sed s/-I//g)'
'<!@(pkg-config pangocairo --cflags-only-I | sed s/-I//g)',
'<!@(pkg-config freetype2 --cflags-only-I | sed s/-I//g)'
]
}],
['with_jpeg=="true"', {

13
lib/canvas.js

@ -14,7 +14,6 @@ var canvas = require('./bindings')
, Canvas = canvas.Canvas
, Image = canvas.Image
, cairoVersion = canvas.cairoVersion
, registerFont = canvas.registerFont
, Context2d = require('./context2d')
, PNGStream = require('./pngstream')
, PDFStream = require('./pdfstream')
@ -39,7 +38,17 @@ exports.version = packageJson.version;
* Register custom font
*/
exports.registerFont = registerFont;
exports.registerFont = function(src, fontFace) {
var weight, style, family;
if (typeof fontFace === 'object') {
weight = fontFace.weight;
style = fontFace.style;
family = fontFace.family;
}
this._registerFont(src, weight, style, family);
};
/**
* Cairo version.

129
src/Canvas.cc

@ -9,6 +9,7 @@
#include <string.h>
#include <node_buffer.h>
#include <node_version.h>
#include <glib.h>
#include <cairo-pdf.h>
#include <cairo-svg.h>
@ -16,6 +17,7 @@
#include "PNG.h"
#include "CanvasRenderingContext2d.h"
#include "closure.h"
#include "register_font.h"
#ifdef HAVE_JPEG
#include "JPEGStream.h"
@ -58,6 +60,9 @@ Canvas::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Nan::SetTemplate(proto, "PNG_FILTER_PAETH", Nan::New<Uint32>(PNG_FILTER_PAETH));
Nan::SetTemplate(proto, "PNG_ALL_FILTERS", Nan::New<Uint32>(PNG_ALL_FILTERS));
// Class methods
Nan::SetMethod(ctor, "_registerFont", RegisterFont);
Nan::Set(target, Nan::New("Canvas").ToLocalChecked(), ctor->GetFunction());
}
@ -565,6 +570,41 @@ NAN_METHOD(Canvas::StreamJPEGSync) {
#endif
NAN_METHOD(Canvas::RegisterFont) {
FontFace face;
if (!info[0]->IsString()) {
return Nan::ThrowError("Wrong argument type");
}
String::Utf8Value filePath(info[0]);
if (!register_font((unsigned char*) *filePath, &face.target_desc)) {
Nan::ThrowError("Could not load font to the system's font host");
} else {
PangoFontDescription* d = pango_font_description_copy(face.target_desc);
if (!info[1]->Equals(Nan::Null())) {
String::Utf8Value weight(info[1]->ToString());
pango_font_description_set_weight(d, Canvas::GetWeightFromCSSString(*weight));
}
if (!info[2]->Equals(Nan::Null())) {
String::Utf8Value style(info[2]->ToString());
pango_font_description_set_style(d, Canvas::GetStyleFromCSSString(*style));
}
if (!info[3]->Equals(Nan::Null())) {
String::Utf8Value family(info[3]->ToString());
pango_font_description_set_family(d, *family);
}
face.user_desc = d;
_font_face_list.push_back(face);
}
}
/*
* Initialize cairo surface.
*/
@ -616,6 +656,95 @@ Canvas::~Canvas() {
}
}
std::vector<FontFace>
_init_font_face_list() {
std::vector<FontFace> x;
return x;
}
std::vector<FontFace> Canvas::_font_face_list = _init_font_face_list();
/*
* Get a PangoStyle from a CSS string (like "italic")
*/
PangoStyle
Canvas::GetStyleFromCSSString(char *style) {
PangoStyle s = PANGO_STYLE_NORMAL;
if (strlen(style) > 0) {
if (0 == strcmp("italic", style)) {
s = PANGO_STYLE_ITALIC;
} else if (0 == strcmp("oblique", style)) {
s = PANGO_STYLE_OBLIQUE;
}
}
return s;
}
/*
* Get a PangoWeight from a CSS string ("bold", "100", etc)
*/
PangoWeight
Canvas::GetWeightFromCSSString(char *weight) {
PangoWeight w = PANGO_WEIGHT_NORMAL;
if (strlen(weight) > 0) {
if (0 == strcmp("bold", weight)) {
w = PANGO_WEIGHT_BOLD;
} else if (0 == strcmp("100", weight)) {
w = PANGO_WEIGHT_THIN;
} else if (0 == strcmp("200", weight)) {
w = PANGO_WEIGHT_ULTRALIGHT;
} else if (0 == strcmp("300", weight)) {
w = PANGO_WEIGHT_LIGHT;
} else if (0 == strcmp("400", weight)) {
w = PANGO_WEIGHT_NORMAL;
} else if (0 == strcmp("500", weight)) {
w = PANGO_WEIGHT_MEDIUM;
} else if (0 == strcmp("600", weight)) {
w = PANGO_WEIGHT_SEMIBOLD;
} else if (0 == strcmp("700", weight)) {
w = PANGO_WEIGHT_BOLD;
} else if (0 == strcmp("800", weight)) {
w = PANGO_WEIGHT_ULTRABOLD;
} else if (0 == strcmp("900", weight)) {
w = PANGO_WEIGHT_HEAVY;
}
}
return w;
}
/*
* Tries to find a matching font given to registerFont
*/
PangoFontDescription *
Canvas::FindCustomFace(PangoFontDescription *desc) {
PangoFontDescription* best_match = NULL;
PangoFontDescription* best_match_target = NULL;
std::vector<FontFace>::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
&& pango_font_description_better_match(desc, best_match, f.user_desc)) {
best_match = f.user_desc;
best_match_target = f.target_desc;
}
++it;
}
return best_match_target;
}
/*
* Re-alloc the surface, destroying the previous.
*/

17
src/Canvas.h

@ -13,9 +13,11 @@
#include <node_object_wrap.h>
#include <node_version.h>
#include <pango/pangocairo.h>
#include <vector>
#include <cairo.h>
#include <nan.h>
using namespace node;
using namespace v8;
@ -38,6 +40,16 @@ 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;
};
/*
* Canvas.
*/
@ -60,6 +72,7 @@ class Canvas: public Nan::ObjectWrap {
static NAN_METHOD(StreamPNGSync);
static NAN_METHOD(StreamPDFSync);
static NAN_METHOD(StreamJPEGSync);
static NAN_METHOD(RegisterFont);
static Local<Value> Error(cairo_status_t status);
#if NODE_VERSION_AT_LEAST(0, 6, 0)
static void ToBufferAsync(uv_work_t *req);
@ -74,6 +87,9 @@ class Canvas: public Nan::ObjectWrap {
EIO_ToBuffer(eio_req *req);
static int EIO_AfterToBuffer(eio_req *req);
#endif
static PangoWeight GetWeightFromCSSString(char *weight);
static PangoStyle GetStyleFromCSSString(char *style);
static PangoFontDescription* FindCustomFace(PangoFontDescription *desc);
inline bool isPDF(){ return CANVAS_TYPE_PDF == type; }
inline bool isSVG(){ return CANVAS_TYPE_SVG == type; }
@ -89,6 +105,7 @@ class Canvas: public Nan::ObjectWrap {
~Canvas();
cairo_surface_t *_surface;
void *_closure;
static std::vector<FontFace> _font_face_list;
};
#endif

44
src/CanvasRenderingContext2d.cc

@ -1834,47 +1834,21 @@ NAN_METHOD(Context2d::SetFont) {
PangoFontDescription *desc = pango_font_description_copy(context->state->fontDescription);
pango_font_description_free(context->state->fontDescription);
context->state->fontDescription = desc;
if (strlen(*family) > 0) pango_font_description_set_family(desc, *family);
pango_font_description_set_style(desc, Canvas::GetStyleFromCSSString(*style));
pango_font_description_set_weight(desc, Canvas::GetWeightFromCSSString(*weight));
if (size > 0) pango_font_description_set_absolute_size(desc, size * PANGO_SCALE);
if (strlen(*family) > 0) pango_font_description_set_family(desc, *family);
PangoStyle s = PANGO_STYLE_NORMAL;
if (strlen(*style) > 0) {
if (0 == strcmp("italic", *style)) {
s = PANGO_STYLE_ITALIC;
} else if (0 == strcmp("oblique", *style)) {
s = PANGO_STYLE_OBLIQUE;
}
PangoFontDescription *target_desc;
if ((target_desc = Canvas::FindCustomFace(desc))) {
pango_font_description_free(desc);
desc = pango_font_description_copy(target_desc);
}
pango_font_description_set_style(desc, s);
PangoWeight w = PANGO_WEIGHT_NORMAL;
if (strlen(*weight) > 0) {
if (0 == strcmp("bold", *weight)) {
w = PANGO_WEIGHT_BOLD;
} else if (0 == strcmp("200", *weight)) {
w = PANGO_WEIGHT_ULTRALIGHT;
} else if (0 == strcmp("300", *weight)) {
w = PANGO_WEIGHT_LIGHT;
} else if (0 == strcmp("400", *weight)) {
w = PANGO_WEIGHT_NORMAL;
} else if (0 == strcmp("500", *weight)) {
w = PANGO_WEIGHT_MEDIUM;
} else if (0 == strcmp("600", *weight)) {
w = PANGO_WEIGHT_SEMIBOLD;
} else if (0 == strcmp("700", *weight)) {
w = PANGO_WEIGHT_BOLD;
} else if (0 == strcmp("800", *weight)) {
w = PANGO_WEIGHT_ULTRABOLD;
} else if (0 == strcmp("900", *weight)) {
w = PANGO_WEIGHT_HEAVY;
}
}
if (size > 0) pango_font_description_set_absolute_size(desc, size * PANGO_SCALE);
pango_font_description_set_weight(desc, w);
context->state->fontDescription = desc;
pango_layout_set_font_description(context->_layout, desc);
}

23
src/init.cc

@ -6,31 +6,22 @@
//
#include <stdio.h>
#include <pango/pango.h>
#include <glib.h>
#include "Canvas.h"
#include "Image.h"
#include "ImageData.h"
#include "CanvasGradient.h"
#include "CanvasPattern.h"
#include "CanvasRenderingContext2d.h"
#include "register_font.h"
#include <ft2build.h>
#include FT_FREETYPE_H
// Compatibility with Visual Studio versions prior to VS2015
#if defined(_MSC_VER) && _MSC_VER < 1900
#define snprintf _snprintf
#endif
NAN_METHOD(register_font_js) {
if (!info[0]->IsString()) {
return Nan::ThrowError("Wrong argument type");
}
String::Utf8Value filePath(info[0]);
if (!register_font((unsigned char*) *filePath)) {
Nan::ThrowError("Could not load font to the system's font host");
}
}
NAN_MODULE_INIT(init) {
Canvas::Initialize(target);
Image::Initialize(target);
@ -39,8 +30,6 @@ NAN_MODULE_INIT(init) {
Gradient::Initialize(target);
Pattern::Initialize(target);
Nan::SetMethod(target, "registerFont", register_font_js);
target->Set(Nan::New<String>("cairoVersion").ToLocalChecked(), Nan::New<String>(cairo_version_string()).ToLocalChecked());
#ifdef HAVE_JPEG
@ -78,6 +67,10 @@ NAN_MODULE_INIT(init) {
target->Set(Nan::New<String>("gifVersion").ToLocalChecked(), Nan::New<String>(GIF_LIB_VERSION).ToLocalChecked());
#endif
#endif
char freetype_version[10];
snprintf(freetype_version, 10, "%d.%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
target->Set(Nan::New<String>("freetypeVersion").ToLocalChecked(), Nan::New<String>(freetype_version).ToLocalChecked());
}
NODE_MODULE(canvas,init);

185
src/register_font.cc

@ -3,7 +3,6 @@
#include <pango/pango.h>
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <CoreText/CoreText.h>
#elif defined(_WIN32)
#include <windows.h>
@ -11,8 +10,186 @@
#include <fontconfig/fontconfig.h>
#endif
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_TRUETYPE_TABLES_H
#include FT_SFNT_NAMES_H
#include FT_TRUETYPE_IDS_H
#ifndef FT_SFNT_OS2
#define FT_SFNT_OS2 ft_sfnt_os2
#endif
// OSX seems to read the strings in MacRoman encoding and ignore Unicode entries.
// You can verify this by opening a TTF with both Unicode and Macroman on OSX.
// It uses the MacRoman name, while Fontconfig and Windows use Unicode
#ifdef __APPLE__
#define PREFERRED_PLATFORM_ID TT_PLATFORM_MACINTOSH
#define PREFERRED_ENCODING_ID TT_MAC_ID_ROMAN
#else
#define PREFERRED_PLATFORM_ID TT_PLATFORM_MICROSOFT
#define PREFERRED_ENCODING_ID TT_MS_ID_UNICODE_CS
#endif
#define IS_PREFERRED_ENC(X) \
X.platform_id == PREFERRED_PLATFORM_ID && X.encoding_id == PREFERRED_ENCODING_ID
/*
* Return a UTF-8 encoded string given a TrueType name buf+len
* and its platform and encoding
*/
char *
to_utf8(FT_Byte* buf, FT_UInt len, FT_UShort pid, FT_UShort eid) {
size_t ret_len = len * 4; // max chars in a utf8 string
char *ret = (char*)malloc(ret_len + 1); // utf8 string + null
if (!ret) return NULL;
// In my testing of hundreds of fonts from the Google Font repo, the two types
// of fonts are TT_PLATFORM_MICROSOFT with TT_MS_ID_UNICODE_CS encoding, or
// TT_PLATFORM_MACINTOSH with TT_MAC_ID_ROMAN encoding. Usually both, never neither
char const *fromcode;
if (pid == TT_PLATFORM_MACINTOSH && eid == TT_MAC_ID_ROMAN) {
fromcode = "MAC";
} else if (pid == TT_PLATFORM_MICROSOFT && eid == TT_MS_ID_UNICODE_CS) {
fromcode = "UTF-16BE";
} else {
free(ret);
return NULL;
}
GIConv cd = g_iconv_open("UTF-8", fromcode);
if (cd == (GIConv)-1) {
free(ret);
return NULL;
}
size_t inbytesleft = len;
size_t outbytesleft = ret_len;
size_t n_converted = g_iconv(cd, (char**)&buf, &inbytesleft, &ret, &outbytesleft);
ret -= ret_len - outbytesleft; // rewind the pointers to their
buf -= len - inbytesleft; // original starting positions
if (n_converted == (size_t)-1) {
free(ret);
return NULL;
} else {
ret[ret_len - outbytesleft] = '\0';
return ret;
}
}
/*
* Find a family name in the face's name table, preferring the one the
* system, fall back to the other
*/
char *
get_family_name(FT_Face face) {
FT_SfntName name;
char *utf8name = NULL;
for (unsigned i = 0; i < FT_Get_Sfnt_Name_Count(face); ++i) {
FT_Get_Sfnt_Name(face, i, &name);
if (name.name_id == TT_NAME_ID_FONT_FAMILY) {
char *utf8candidate = to_utf8(name.string, name.string_len, name.platform_id, name.encoding_id);
if (utf8candidate) {
if (utf8name) free(utf8name);
if (IS_PREFERRED_ENC(name)) {
return utf8candidate;
} else {
utf8name = utf8candidate;
}
}
}
}
return utf8name;
}
PangoWeight
get_pango_weight(FT_UShort weight) {
switch (weight) {
case 100: return PANGO_WEIGHT_THIN;
case 200: return PANGO_WEIGHT_ULTRALIGHT;
case 300: return PANGO_WEIGHT_LIGHT;
case 350: return PANGO_WEIGHT_SEMILIGHT;
case 380: return PANGO_WEIGHT_BOOK;
case 400: return PANGO_WEIGHT_NORMAL;
case 500: return PANGO_WEIGHT_MEDIUM;
case 600: return PANGO_WEIGHT_SEMIBOLD;
case 700: return PANGO_WEIGHT_BOLD;
case 800: return PANGO_WEIGHT_ULTRABOLD;
case 900: return PANGO_WEIGHT_HEAVY;
case 1000: return PANGO_WEIGHT_ULTRAHEAVY;
default: return PANGO_WEIGHT_NORMAL;
}
}
PangoStretch
get_pango_stretch(FT_UShort width) {
switch (width) {
case 1: return PANGO_STRETCH_ULTRA_CONDENSED;
case 2: return PANGO_STRETCH_EXTRA_CONDENSED;
case 3: return PANGO_STRETCH_CONDENSED;
case 4: return PANGO_STRETCH_SEMI_CONDENSED;
case 5: return PANGO_STRETCH_NORMAL;
case 6: return PANGO_STRETCH_SEMI_EXPANDED;
case 7: return PANGO_STRETCH_EXPANDED;
case 8: return PANGO_STRETCH_EXTRA_EXPANDED;
case 9: return PANGO_STRETCH_ULTRA_EXPANDED;
default: return PANGO_STRETCH_NORMAL;
}
}
PangoStyle
get_pango_style(FT_Long flags) {
if (flags & FT_STYLE_FLAG_ITALIC) {
return PANGO_STYLE_ITALIC;
} else {
return PANGO_STYLE_NORMAL;
}
}
PangoFontDescription *
get_pango_font_description(unsigned char* filepath) {
FT_Library library;
FT_Face face;
PangoFontDescription *desc = pango_font_description_new();
if (!FT_Init_FreeType(&library) && !FT_New_Face(library, (const char*)filepath, 0, &face)) {
TT_OS2 *table = (TT_OS2*)FT_Get_Sfnt_Table(face, FT_SFNT_OS2);
char *family = get_family_name(face);
if (family) pango_font_description_set_family_static(desc, family);
pango_font_description_set_weight(desc, get_pango_weight(table->usWeightClass));
pango_font_description_set_stretch(desc, get_pango_stretch(table->usWidthClass));
pango_font_description_set_style(desc, get_pango_style(face->style_flags));
FT_Done_Face(face);
return desc;
}
pango_font_description_free(desc);
return NULL;
}
/*
* Register font with the OS
*/
bool
register_font(unsigned char *filepath) {
register_font(unsigned char *filepath, PangoFontDescription **desc) {
bool success;
#ifdef __APPLE__
@ -23,9 +200,11 @@ register_font(unsigned char *filepath) {
#else
success = FcConfigAppFontAddFile(FcConfigGetCurrent(), (FcChar8 *)(filepath));
#endif
if (!success) return false;
*desc = get_pango_font_description(filepath);
// Tell Pango to throw away the current FontMap and create a new one. This
// has the effect of registering the new font in Pango by re-looking up all
// font families.

4
src/register_font.h

@ -1,2 +1,4 @@
bool register_font(unsigned char *filepath);
#include <pango/pango.h>
bool register_font(unsigned char *filepath, PangoFontDescription** desc);

Loading…
Cancel
Save