Browse Source

Merge pull request #715 from chearon/prefer-pango

Require Pango over "toy" Cairo font methods. Redo the custom font API
master
Linus Unnebäck 8 years ago
committed by GitHub
parent
commit
7704d8dd7b
  1. 26
      Readme.md
  2. 81
      binding.gyp
  3. 21
      examples/font.js
  4. 28
      lib/canvas.js
  5. 39
      lib/context2d.js
  6. 195
      src/Canvas.cc
  7. 26
      src/Canvas.h
  8. 287
      src/CanvasRenderingContext2d.cc
  9. 29
      src/CanvasRenderingContext2d.h
  10. 113
      src/FontFace.cc
  11. 32
      src/FontFace.h
  12. 2
      src/ImageData.h
  13. 12
      src/init.cc
  14. 251
      src/register_font.cc
  15. 5
      src/register_font.h
  16. 10
      test/canvas.test.js

26
Readme.md

@ -27,16 +27,16 @@
$ npm install canvas
```
Unless previously installed you'll _need_ __Cairo__. For system-specific installation view the [Wiki](https://github.com/Automattic/node-canvas/wiki/_pages).
Unless previously installed you'll _need_ __Cairo__ and __Pango__. For system-specific installation view the [Wiki](https://github.com/Automattic/node-canvas/wiki/_pages).
You can quickly install the dependencies by using the command for your OS:
OS | Command
----- | -----
OS X | `brew install pkg-config cairo libpng jpeg giflib`
OS X | `brew install pkg-config cairo pango libpng jpeg giflib`
Ubuntu | `sudo apt-get install libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++`
Fedora | `sudo yum install cairo cairo-devel cairomm-devel libjpeg-turbo-devel pango pango-devel pangomm pangomm-devel giflib-devel`
Solaris | `pkgin install cairo pkg-config xproto renderproto kbproto xextproto`
Solaris | `pkgin install cairo pango pkg-config xproto renderproto kbproto xextproto`
Windows | [Instructions on our wiki](https://github.com/Automattic/node-canvas/wiki/Installation---Windows)
**El Capitan users:** If you have recently updated to El Capitan and are experiencing trouble when compiling, run the following command: `xcode-select --install`. Read more about the problem [on Stack Overflow](http://stackoverflow.com/a/32929012/148072).
@ -190,6 +190,26 @@ canvas.toDataURL('image/jpeg', {opts...}, function(err, jpeg){ }); // see Canvas
canvas.toDataURL('image/jpeg', quality, function(err, jpeg){ }); // spec-following; quality from 0 to 1
```
### Canvas.registerFont for bundled fonts
It can be useful to use a custom font file if you are distributing code that uses node-canvas and a specific font. Or perhaps you are using it to do automated tests and you want the renderings to be the same across operating systems regardless of what fonts are installed.
To do that, you should use `Canvas.registerFont`.
**You need to call it before the Canvas is created**
```javascript
Canvas.registerFont('comicsans.ttf', {family: 'Comic Sans'});
var canvas = new Canvas(500, 500),
ctx = canvas.getContext('2d');
ctx.font = '12px "Comic Sans"';
ctx.fillText(250, 10, 'Everyone hates this font :(');
```
The second argument is an object with properties that resemble the CSS properties that are specified in `@font-face` rules. You must specify at least `family`. `weight`, and `style` are optional (and default to "normal").
### CanvasRenderingContext2D#patternQuality
Given one of the values below will alter pattern (gradients, images, etc) render quality, defaults to _good_.

81
binding.gyp

@ -4,16 +4,12 @@
'variables': {
'GTK_Root%': 'C:/GTK', # Set the location of GTK all-in-one bundle
'with_jpeg%': 'false',
'with_gif%': 'false',
'with_pango%': 'false',
'with_freetype%': 'false'
'with_gif%': 'false'
}
}, { # 'OS!="win"'
'variables': {
'with_jpeg%': '<!(./util/has_lib.sh jpeg)',
'with_gif%': '<!(./util/has_lib.sh gif)',
'with_pango%': '<!(./util/has_lib.sh pango)',
'with_freetype%': '<!(./util/has_lib.sh freetype)'
'with_gif%': '<!(./util/has_lib.sh gif)'
}
}]
],
@ -26,12 +22,21 @@
'copies': [{
'destination': '<(PRODUCT_DIR)',
'files': [
'<(GTK_Root)/bin/zlib1.dll',
'<(GTK_Root)/bin/libintl-8.dll',
'<(GTK_Root)/bin/libpng14-14.dll',
'<(GTK_Root)/bin/libpangocairo-1.0-0.dll',
'<(GTK_Root)/bin/libpango-1.0-0.dll',
'<(GTK_Root)/bin/libpangoft2-1.0-0.dll',
'<(GTK_Root)/bin/libpangowin32-1.0-0.dll',
'<(GTK_Root)/bin/libcairo-2.dll',
'<(GTK_Root)/bin/libexpat-1.dll',
'<(GTK_Root)/bin/libfontconfig-1.dll',
'<(GTK_Root)/bin/libfreetype-6.dll',
'<(GTK_Root)/bin/libpng14-14.dll',
'<(GTK_Root)/bin/zlib1.dll',
'<(GTK_Root)/bin/libglib-2.0-0.dll',
'<(GTK_Root)/bin/libgobject-2.0-0.dll',
'<(GTK_Root)/bin/libgmodule-2.0-0.dll',
'<(GTK_Root)/bin/libgthread-2.0-0.dll',
'<(GTK_Root)/bin/libexpat-1.dll'
]
}]
}]
@ -48,17 +53,27 @@
'src/color.cc',
'src/Image.cc',
'src/ImageData.cc',
'src/register_font.cc',
'src/init.cc'
],
'conditions': [
['OS=="win"', {
'libraries': [
'-l<(GTK_Root)/lib/cairo.lib',
'-l<(GTK_Root)/lib/libpng.lib'
'-l<(GTK_Root)/lib/libpng.lib',
'-l<(GTK_Root)/lib/pangocairo-1.0.lib',
'-l<(GTK_Root)/lib/pango-1.0.lib',
'-l<(GTK_Root)/lib/freetype.lib',
'-l<(GTK_Root)/lib/glib-2.0.lib',
'-l<(GTK_Root)/lib/gobject-2.0.lib'
],
'include_dirs': [
'<(GTK_Root)/include',
'<(GTK_Root)/include/cairo',
'<(GTK_Root)/include/pango-1.0',
'<(GTK_Root)/include/glib-2.0',
'<(GTK_Root)/include/freetype2',
'<(GTK_Root)/lib/glib-2.0/include'
],
'defines': [
'_USE_MATH_DEFINES' # for M_PI
@ -87,49 +102,15 @@
'libraries': [
'<!@(pkg-config pixman-1 --libs)',
'<!@(pkg-config cairo --libs)',
'<!@(pkg-config libpng --libs)'
'<!@(pkg-config libpng --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)'
]
}],
['with_freetype=="true"', {
'defines': [
'HAVE_FREETYPE'
],
'sources': [
'src/FontFace.cc'
],
'conditions': [
['OS=="win"', {
# No support for windows right now.
}, { # 'OS!="win"'
'include_dirs': [ # tried to pass through cflags but failed.
# Need to include the header files of cairo AND freetype.
# Looking up the includes of cairo does both.
'<!@(pkg-config cairo --cflags-only-I | sed s/-I//g)'
]
}]
]
}],
['with_pango=="true"', {
'defines': [
'HAVE_PANGO'
],
'conditions': [
['OS=="win"', {
'libraries': [
'-l<(GTK_Root)/lib/pangocairo.lib'
]
}, { # 'OS!="win"'
'include_dirs': [ # tried to pass through cflags but failed
'<!@(pkg-config pangocairo --cflags-only-I | sed s/-I//g)'
],
'libraries': [
'<!@(pkg-config pangocairo --libs)'
]
}]
'<!@(pkg-config libpng --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"', {

21
examples/font.js

@ -2,27 +2,22 @@ var fs = require('fs')
var path = require('path')
var Canvas = require('..')
var Font = Canvas.Font
if (!Font) {
throw new Error('Need to compile with font support')
}
function fontFile (name) {
return path.join(__dirname, '/pfennigFont/', name)
}
var pfennigFont = new Font('pfennigFont', fontFile('Pfennig.ttf'))
pfennigFont.addFace(fontFile('PfennigBold.ttf'), 'bold')
pfennigFont.addFace(fontFile('PfennigItalic.ttf'), 'normal', 'italic')
pfennigFont.addFace(fontFile('PfennigBoldItalic.ttf'), 'bold', 'italic')
// Pass each font, including all of its individual variants if there are any, to
// `registerFont`. When you set `ctx.font`, refer to the styles and the family
// name as it is embedded in the TTF. If you aren't sure, open the font in
// FontForge and visit Element -> Font Information and copy the Family Name
Canvas.registerFont(fontFile('Pfennig.ttf'), {family: 'pfennigFont'})
Canvas.registerFont(fontFile('PfennigBold.ttf'), {family: 'pfennigFont', weight: 'bold'})
Canvas.registerFont(fontFile('PfennigItalic.ttf'), {family: 'pfennigFont', style: 'italic'})
Canvas.registerFont(fontFile('PfennigBoldItalic.ttf'), {family: 'pfennigFont', weight: 'bold', style: 'italic'})
var canvas = new Canvas(320, 320)
var ctx = canvas.getContext('2d')
// Tell the ctx to use the font.
ctx.addFont(pfennigFont)
ctx.font = 'normal normal 50px Helvetica'
ctx.fillText('Quo Vaids?', 0, 70)

28
lib/canvas.js

@ -18,7 +18,6 @@ var canvas = require('./bindings')
, PNGStream = require('./pngstream')
, PDFStream = require('./pdfstream')
, JPEGStream = require('./jpegstream')
, FontFace = canvas.FontFace
, fs = require('fs')
, packageJson = require("../package.json")
, FORMATS = ['image/png', 'image/jpeg'];
@ -76,28 +75,13 @@ exports.JPEGStream = JPEGStream;
exports.Image = Image;
exports.ImageData = canvas.ImageData;
if (FontFace) {
var Font = function Font(name, path, idx) {
this.name = name;
this._faces = {};
this.addFace(path, 'normal', 'normal', idx);
};
Font.prototype.addFace = function(path, weight, style, idx) {
style = style || 'normal';
weight = weight || 'normal';
var face = new FontFace(path, idx || 0);
this._faces[weight + '-' + style] = face;
};
Font.prototype.getFace = function(weightStyle) {
return this._faces[weightStyle] || this._faces['normal-normal'];
};
/**
* Resolve paths for registerFont
*/
exports.Font = Font;
}
Canvas.registerFont = function(src, fontFace){
return Canvas._registerFont(fs.realpathSync(src), fontFace);
};
/**
* Context2d implementation.

39
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
@ -235,19 +237,6 @@ Context2d.prototype.__defineGetter__('strokeStyle', function(){
return this.lastStrokeStyle || this.strokeColor;
});
/**
* Register `font` for usage.
*
* @param {Font} font
* @api public
*/
Context2d.prototype.addFont = function(font) {
this._fonts = this._fonts || {};
if (this._fonts[font.name]) return;
this._fonts[font.name] = font;
};
/**
* Set font.
*
@ -261,22 +250,12 @@ Context2d.prototype.__defineSetter__('font', function(val){
var font;
if (font = parseFont(val)) {
this.lastFontString = val;
var fonts = this._fonts;
if (fonts && fonts[font.family]) {
var fontObj = fonts[font.family];
var type = font.weight + '-' + font.style;
var fontFace = fontObj.getFace(type);
this._setFontFace(fontFace, font.size);
} else {
this._setFont(
font.weight
, font.style
, font.size
, font.unit
, font.family);
}
this._setFont(
font.weight
, font.style
, font.size
, font.unit
, font.family);
}
}
});

195
src/Canvas.cc

@ -4,22 +4,30 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
//
#include "Canvas.h"
#include "PNG.h"
#include "CanvasRenderingContext2d.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <node_buffer.h>
#include <node_version.h>
#include <glib.h>
#include <cairo-pdf.h>
#include <cairo-svg.h>
#include "Canvas.h"
#include "PNG.h"
#include "CanvasRenderingContext2d.h"
#include "closure.h"
#include "register_font.h"
#ifdef HAVE_JPEG
#include "JPEGStream.h"
#endif
#define GENERIC_FACE_ERROR \
"The second argument to registerFont is required, and should be an object " \
"with at least a family (string) and optionally weight (string/number) " \
"and style (string)."
Nan::Persistent<FunctionTemplate> Canvas::constructor;
/*
@ -57,6 +65,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());
}
@ -564,6 +575,77 @@ NAN_METHOD(Canvas::StreamJPEGSync) {
#endif
char *
str_value(Local<Value> val, const char *fallback, bool can_be_number) {
if (val->IsString() || (can_be_number && val->IsNumber())) {
return g_strdup(*String::Utf8Value(val));
} else if (fallback) {
return g_strdup(fallback);
} else {
return NULL;
}
}
NAN_METHOD(Canvas::RegisterFont) {
if (!info[0]->IsString()) {
return Nan::ThrowError("Wrong argument type");
} else if (!info[1]->IsObject()) {
return Nan::ThrowError(GENERIC_FACE_ERROR);
}
String::Utf8Value filePath(info[0]);
PangoFontDescription *sys_desc = get_pango_font_description((unsigned char *) *filePath);
if (!sys_desc) return Nan::ThrowError("Could not parse font file");
PangoFontDescription *user_desc = pango_font_description_new();
// now check the attrs, there are many ways to be wrong
Local<Object> js_user_desc = info[1]->ToObject();
Local<String> family_prop = Nan::New<String>("family").ToLocalChecked();
Local<String> weight_prop = Nan::New<String>("weight").ToLocalChecked();
Local<String> style_prop = Nan::New<String>("style").ToLocalChecked();
char *family = str_value(js_user_desc->Get(family_prop), NULL, false);
char *weight = str_value(js_user_desc->Get(weight_prop), "normal", true);
char *style = str_value(js_user_desc->Get(style_prop), "normal", false);
if (family && weight && style) {
pango_font_description_set_weight(user_desc, Canvas::GetWeightFromCSSString(weight));
pango_font_description_set_style(user_desc, Canvas::GetStyleFromCSSString(style));
pango_font_description_set_family(user_desc, family);
std::vector<FontFace>::iterator it = _font_face_list.begin();
FontFace *already_registered = NULL;
for (; it != _font_face_list.end() && !already_registered; ++it) {
if (pango_font_description_equal(it->sys_desc, sys_desc)) {
already_registered = &(*it);
}
}
if (already_registered) {
pango_font_description_free(already_registered->user_desc);
already_registered->user_desc = user_desc;
} else if (register_font((unsigned char *) *filePath)) {
FontFace face;
face.user_desc = user_desc;
face.sys_desc = sys_desc;
_font_face_list.push_back(face);
} else {
pango_font_description_free(user_desc);
Nan::ThrowError("Could not load font to the system's font host");
}
} else {
pango_font_description_free(user_desc);
Nan::ThrowError(GENERIC_FACE_ERROR);
}
g_free(family);
g_free(weight);
g_free(style);
}
/*
* Initialize cairo surface.
*/
@ -615,6 +697,113 @@ 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(const 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(const 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;
}
/*
* Given a user description, return a description that will select the
* font either from the system or @font-face
*/
PangoFontDescription *
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<FontFace>::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;
}
}
}
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);
}
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;
}
/*
* Re-alloc the surface, destroying the previous.
*/

26
src/Canvas.h

@ -8,21 +8,18 @@
#ifndef __NODE_CANVAS_H__
#define __NODE_CANVAS_H__
#include <v8.h>
#include <node.h>
#include <v8.h>
#include <node_object_wrap.h>
#include <node_version.h>
#if HAVE_PANGO
#include <pango/pangocairo.h>
#else
#include <vector>
#include <cairo.h>
#endif
#include <nan.h>
using namespace v8;
using namespace node;
using namespace v8;
/*
* Maxmimum states per context.
@ -43,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 *sys_desc = NULL;
PangoFontDescription *user_desc = NULL;
};
/*
* Canvas.
*/
@ -65,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);
@ -79,6 +87,9 @@ class Canvas: public Nan::ObjectWrap {
EIO_ToBuffer(eio_req *req);
static int EIO_AfterToBuffer(eio_req *req);
#endif
static PangoWeight GetWeightFromCSSString(const char *weight);
static PangoStyle GetStyleFromCSSString(const char *style);
static PangoFontDescription *ResolveFontDescription(const PangoFontDescription *desc);
inline bool isPDF(){ return CANVAS_TYPE_PDF == type; }
inline bool isSVG(){ return CANVAS_TYPE_SVG == type; }
@ -94,6 +105,7 @@ class Canvas: public Nan::ObjectWrap {
~Canvas();
cairo_surface_t *_surface;
void *_closure;
static std::vector<FontFace> _font_face_list;
};
#endif

287
src/CanvasRenderingContext2d.cc

@ -6,11 +6,11 @@
//
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <limits>
#include <vector>
#include <algorithm>
#include "Canvas.h"
#include "Point.h"
#include "Image.h"
@ -19,10 +19,6 @@
#include "CanvasGradient.h"
#include "CanvasPattern.h"
#ifdef HAVE_FREETYPE
#include "FontFace.h"
#endif
// Windows doesn't support the C99 names for these
#ifdef _MSC_VER
#define isnan(x) _isnan(x)
@ -63,18 +59,6 @@ enum {
, TEXT_BASELINE_HANGING
};
#if HAVE_PANGO
/*
* State helper function
*/
void state_assign_fontFamily(canvas_state_t *state, const char *str) {
free(state->fontFamily);
state->fontFamily = strndup(str, 100);
}
/*
* Simple helper macro for a rather verbose function call.
*/
@ -84,8 +68,6 @@ void state_assign_fontFamily(canvas_state_t *state, const char *str) {
pango_layout_get_font_description(LAYOUT), \
pango_context_get_language(pango_layout_get_context(LAYOUT)))
#endif
/*
* Initialize Context2d.
*/
@ -135,9 +117,6 @@ Context2d::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Nan::SetPrototypeMethod(ctor, "setLineDash", SetLineDash);
Nan::SetPrototypeMethod(ctor, "getLineDash", GetLineDash);
Nan::SetPrototypeMethod(ctor, "_setFont", SetFont);
#ifdef HAVE_FREETYPE
Nan::SetPrototypeMethod(ctor, "_setFontFace", SetFontFace);
#endif
Nan::SetPrototypeMethod(ctor, "_setFillColor", SetFillColor);
Nan::SetPrototypeMethod(ctor, "_setStrokeColor", SetStrokeColor);
Nan::SetPrototypeMethod(ctor, "_setFillPattern", SetFillPattern);
@ -171,9 +150,7 @@ Context2d::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Context2d::Context2d(Canvas *canvas) {
_canvas = canvas;
_context = cairo_create(canvas->surface());
#if HAVE_PANGO
_layout = pango_cairo_create_layout(_context);
#endif
cairo_set_line_width(_context, 1);
state = states[stateno = 0] = (canvas_state_t *) malloc(sizeof(canvas_state_t));
state->shadowBlur = 0;
@ -190,14 +167,9 @@ Context2d::Context2d(Canvas *canvas) {
state->shadow = transparent_black;
state->patternQuality = CAIRO_FILTER_GOOD;
state->textDrawingMode = TEXT_DRAW_PATHS;
#if HAVE_PANGO
state->fontWeight = PANGO_WEIGHT_NORMAL;
state->fontStyle = PANGO_STYLE_NORMAL;
state->fontSize = 10;
state->fontFamily = NULL;
state_assign_fontFamily(state, "sans serif");
setFontFromState();
#endif
state->fontDescription = pango_font_description_from_string("sans serif");
pango_font_description_set_absolute_size(state->fontDescription, 10 * PANGO_SCALE);
pango_layout_set_font_description(_layout, state->fontDescription);
}
/*
@ -206,14 +178,10 @@ Context2d::Context2d(Canvas *canvas) {
Context2d::~Context2d() {
while(stateno >= 0) {
#if HAVE_PANGO
free(states[stateno]->fontFamily);
#endif
pango_font_description_free(states[stateno]->fontDescription);
free(states[stateno--]);
}
#if HAVE_PANGO
g_object_unref(_layout);
#endif
cairo_destroy(_context);
}
@ -246,9 +214,7 @@ Context2d::saveState() {
if (stateno == CANVAS_MAX_STATES) return;
states[++stateno] = (canvas_state_t *) malloc(sizeof(canvas_state_t));
memcpy(states[stateno], state, sizeof(canvas_state_t));
#if HAVE_PANGO
states[stateno]->fontFamily = strndup(state->fontFamily, 100);
#endif
states[stateno]->fontDescription = pango_font_description_copy(states[stateno-1]->fontDescription);
state = states[stateno];
}
@ -259,16 +225,11 @@ Context2d::saveState() {
void
Context2d::restoreState() {
if (0 == stateno) return;
// Olaf (2011-02-21): Free old state data
#if HAVE_PANGO
free(states[stateno]->fontFamily);
#endif
pango_font_description_free(states[stateno]->fontDescription);
free(states[stateno]);
states[stateno] = NULL;
state = states[--stateno];
#if HAVE_PANGO
setFontFromState();
#endif
pango_layout_set_font_description(_layout, state->fontDescription);
}
/*
@ -1770,8 +1731,6 @@ NAN_METHOD(Context2d::StrokeText) {
void
Context2d::setTextPath(const char *str, double x, double y) {
#if HAVE_PANGO
PangoRectangle ink_rect, logical_rect;
PangoFontMetrics *metrics = NULL;
@ -1814,59 +1773,6 @@ Context2d::setTextPath(const char *str, double x, double y) {
} else if (state->textDrawingMode == TEXT_DRAW_GLYPHS) {
pango_cairo_show_layout(_context, _layout);
}
#else
cairo_text_extents_t te;
cairo_font_extents_t fe;
// Alignment
switch (state->textAlignment) {
// center
case 0:
// Olaf (2011-02-19): te.x_bearing does not concern the alignment
cairo_text_extents(_context, str, &te);
x -= te.width / 2;
break;
// right
case 1:
// Olaf (2011-02-19): te.x_bearing does not concern the alignment
cairo_text_extents(_context, str, &te);
x -= te.width;
break;
}
// Baseline approx
switch (state->textBaseline) {
case TEXT_BASELINE_TOP:
case TEXT_BASELINE_HANGING:
// Olaf (2011-02-26): fe.ascent approximates the distance between
// the top of the em square and the alphabetic baseline
cairo_font_extents(_context, &fe);
y += fe.ascent;
break;
case TEXT_BASELINE_MIDDLE:
// Olaf (2011-02-26): fe.ascent approximates the distance between
// the top of the em square and the alphabetic baseline
cairo_font_extents(_context, &fe);
y += (fe.ascent - fe.descent)/2;
break;
case TEXT_BASELINE_BOTTOM:
// Olaf (2011-02-26): we need to know the distance between the alphabetic
// baseline and the bottom of the em square
cairo_font_extents(_context, &fe);
y -= fe.descent;
break;
}
cairo_move_to(_context, x, y);
if (state->textDrawingMode == TEXT_DRAW_PATHS) {
cairo_text_path(_context, str);
} else if (state->textDrawingMode == TEXT_DRAW_GLYPHS) {
cairo_show_text(_context, str);
}
#endif
}
/*
@ -1901,35 +1807,6 @@ NAN_METHOD(Context2d::MoveTo) {
, info[1]->NumberValue());
}
/*
* Set font face.
*/
#ifdef HAVE_FREETYPE
NAN_METHOD(Context2d::SetFontFace) {
// Ignore invalid args
if (!info[0]->IsObject()
|| !info[1]->IsNumber())
return Nan::ThrowTypeError("Expected object and number");
Local<Object> obj = info[0]->ToObject();
if (!Nan::New(FontFace::constructor)->HasInstance(obj))
return Nan::ThrowTypeError("FontFace expected");
FontFace *face = Nan::ObjectWrap::Unwrap<FontFace>(obj);
double size = info[1]->NumberValue();
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
cairo_t *ctx = context->context();
cairo_set_font_size(ctx, size);
cairo_set_font_face(ctx, face->cairoFace());
return;
}
#endif
/*
* Set font:
* - weight
@ -1955,95 +1832,24 @@ NAN_METHOD(Context2d::SetFont) {
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
#if HAVE_PANGO
if (strlen(*family) > 0) state_assign_fontFamily(context->state, *family);
if (size > 0) context->state->fontSize = size;
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;
}
}
context->state->fontStyle = 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;
}
}
context->state->fontWeight = w;
context->setFontFromState();
#else
cairo_t *ctx = context->context();
// Size
cairo_set_font_size(ctx, size);
// Style
cairo_font_slant_t s = CAIRO_FONT_SLANT_NORMAL;
if (0 == strcmp("italic", *style)) {
s = CAIRO_FONT_SLANT_ITALIC;
} else if (0 == strcmp("oblique", *style)) {
s = CAIRO_FONT_SLANT_OBLIQUE;
}
// Weight
cairo_font_weight_t w = CAIRO_FONT_WEIGHT_NORMAL;
if (0 == strcmp("bold", *weight)) {
w = CAIRO_FONT_WEIGHT_BOLD;
}
cairo_select_font_face(ctx, *family, s, w);
PangoFontDescription *desc = pango_font_description_copy(context->state->fontDescription);
pango_font_description_free(context->state->fontDescription);
#endif
}
pango_font_description_set_style(desc, Canvas::GetStyleFromCSSString(*style));
pango_font_description_set_weight(desc, Canvas::GetWeightFromCSSString(*weight));
#if HAVE_PANGO
if (strlen(*family) > 0) pango_font_description_set_family(desc, *family);
/*
* Sets PangoLayout options from the current font state
*/
PangoFontDescription *sys_desc = Canvas::ResolveFontDescription(desc);
pango_font_description_free(desc);
void
Context2d::setFontFromState() {
PangoFontDescription *fd = pango_font_description_new();
if (size > 0) pango_font_description_set_absolute_size(sys_desc, size * PANGO_SCALE);
pango_font_description_set_family(fd, state->fontFamily);
pango_font_description_set_absolute_size(fd, state->fontSize * PANGO_SCALE);
pango_font_description_set_style(fd, state->fontStyle);
pango_font_description_set_weight(fd, state->fontWeight);
context->state->fontDescription = sys_desc;
pango_layout_set_font_description(_layout, fd);
pango_font_description_free(fd);
pango_layout_set_font_description(context->_layout, sys_desc);
}
#endif
/*
* Return the given text extents.
* TODO: Support for:
@ -2058,8 +1864,6 @@ NAN_METHOD(Context2d::MeasureText) {
String::Utf8Value str(info[0]->ToString());
Local<Object> obj = Nan::New<Object>();
#if HAVE_PANGO
PangoRectangle ink_rect, logical_rect;
PangoFontMetrics *metrics;
PangoLayout *layout = context->layout();
@ -2117,61 +1921,6 @@ NAN_METHOD(Context2d::MeasureText) {
pango_font_metrics_unref(metrics);
#else
cairo_text_extents_t te;
cairo_font_extents_t fe;
cairo_text_extents(ctx, *str, &te);
cairo_font_extents(ctx, &fe);
double x_offset;
switch (context->state->textAlignment) {
case 0: // center
x_offset = te.width / 2;
break;
case 1: // right
x_offset = te.width;
break;
default: // left
x_offset = 0.0;
}
double y_offset;
switch (context->state->textBaseline) {
case TEXT_BASELINE_TOP:
case TEXT_BASELINE_HANGING:
y_offset = fe.ascent;
break;
case TEXT_BASELINE_MIDDLE:
y_offset = (fe.ascent - fe.descent)/2;
break;
case TEXT_BASELINE_BOTTOM:
y_offset = -fe.descent;
break;
default:
y_offset = 0.0;
}
obj->Set(Nan::New<String>("width").ToLocalChecked(),
Nan::New<Number>(te.x_advance));
obj->Set(Nan::New<String>("actualBoundingBoxLeft").ToLocalChecked(),
Nan::New<Number>(x_offset - te.x_bearing));
obj->Set(Nan::New<String>("actualBoundingBoxRight").ToLocalChecked(),
Nan::New<Number>((te.x_bearing + te.width) - x_offset));
obj->Set(Nan::New<String>("actualBoundingBoxAscent").ToLocalChecked(),
Nan::New<Number>(-(te.y_bearing + y_offset)));
obj->Set(Nan::New<String>("actualBoundingBoxDescent").ToLocalChecked(),
Nan::New<Number>(te.height + te.y_bearing + y_offset));
obj->Set(Nan::New<String>("emHeightAscent").ToLocalChecked(),
Nan::New<Number>(fe.ascent - y_offset));
obj->Set(Nan::New<String>("emHeightDescent").ToLocalChecked(),
Nan::New<Number>(fe.descent + y_offset));
obj->Set(Nan::New<String>("alphabeticBaseline").ToLocalChecked(),
Nan::New<Number>(y_offset));
#endif
info.GetReturnValue().Set(obj);
}

29
src/CanvasRenderingContext2d.h

@ -8,17 +8,13 @@
#ifndef __NODE_CONTEXT2D_H__
#define __NODE_CONTEXT2D_H__
#include <vector>
#include <pango/pangocairo.h>
#include "color.h"
#include "Canvas.h"
#include "CanvasGradient.h"
#ifdef HAVE_FREETYPE
#include <ft2build.h>
#include <cairo-ft.h>
#include FT_FREETYPE_H
#endif
#include <vector>
using namespace std;
typedef enum {
@ -49,19 +45,10 @@ typedef struct {
double shadowOffsetX;
double shadowOffsetY;
canvas_draw_mode_t textDrawingMode;
#if HAVE_PANGO
PangoWeight fontWeight;
PangoStyle fontStyle;
double fontSize;
char *fontFamily;
#endif
PangoFontDescription *fontDescription;
} canvas_state_t;
#if HAVE_PANGO
void state_assign_fontFamily(canvas_state_t *state, const char *str);
#endif
class Context2d: public Nan::ObjectWrap {
public:
@ -91,9 +78,6 @@ class Context2d: public Nan::ObjectWrap {
static NAN_METHOD(FillText);
static NAN_METHOD(StrokeText);
static NAN_METHOD(SetFont);
#ifdef HAVE_FREETYPE
static NAN_METHOD(SetFontFace);
#endif
static NAN_METHOD(SetFillColor);
static NAN_METHOD(SetStrokeColor);
static NAN_METHOD(SetFillPattern);
@ -166,20 +150,15 @@ class Context2d: public Nan::ObjectWrap {
void stroke(bool preserve = false);
void save();
void restore();
#if HAVE_PANGO
void setFontFromState();
inline PangoLayout *layout(){ return _layout; }
#endif
private:
~Context2d();
Canvas *_canvas;
cairo_t *_context;
cairo_path_t *_path;
#if HAVE_PANGO
PangoLayout *_layout;
#endif
};
#endif

113
src/FontFace.cc

@ -1,113 +0,0 @@
//
// FontFace.cc
//
// Copyright (c) 2012 Julian Viereck <julian.viereck@gmail.com>
//
#include "FontFace.h"
#include <fontconfig/fontconfig.h>
Nan::Persistent<FunctionTemplate> FontFace::constructor;
/*
* Destroy ft_face.
*/
FontFace::~FontFace() {
// Decrement extra reference count added in ::New(...).
// Once there is no reference left to crFace, cairo will release the
// free type font face as well.
cairo_font_face_destroy(_crFace);
}
/*
* Initialize FontFace.
*/
void
FontFace::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Nan::HandleScope scope;
// Constructor
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(FontFace::New);
constructor.Reset(ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(Nan::New("FontFace").ToLocalChecked());
// Prototype
Nan::Set(target, Nan::New("FontFace").ToLocalChecked(), ctor->GetFunction());
}
/*
* Initialize a new FontFace object.
*/
FT_Library library; /* handle to library */
bool FontFace::_initLibrary = true;
static cairo_user_data_key_t key;
/*
* Initialize a new FontFace.
*/
NAN_METHOD(FontFace::New) {
if (!info.IsConstructCall()) {
return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
}
if (!info[0]->IsString()
|| !info[1]->IsNumber()) {
return Nan::ThrowError("Wrong argument types passed to FontFace constructor");
}
String::Utf8Value filePath(info[0]);
int faceIdx = int(info[1]->NumberValue());
FT_Face ftFace;
FT_Error ftError;
cairo_font_face_t *crFace;
if (_initLibrary) {
_initLibrary = false;
ftError = FT_Init_FreeType(&library);
if (ftError) {
return Nan::ThrowError("Could not load library");
}
}
// Create new freetype font face.
ftError = FT_New_Face(library, *filePath, faceIdx, &ftFace);
if (ftError) {
return Nan::ThrowError("Could not load font file");
}
#if HAVE_PANGO
// Load the font file in fontconfig
FcBool ok = FcConfigAppFontAddFile(FcConfigGetCurrent(), (FcChar8 *)(*filePath));
if (!ok) {
return Nan::ThrowError("Could not load font in FontConfig");
}
#endif
// Create new cairo font face.
crFace = cairo_ft_font_face_create_for_ft_face(ftFace, 0);
// If the cairo font face is released, release the FreeType font face as well.
int status = cairo_font_face_set_user_data (crFace, &key,
ftFace, (cairo_destroy_func_t) FT_Done_Face);
if (status) {
cairo_font_face_destroy (crFace);
FT_Done_Face (ftFace);
return Nan::ThrowError("Failed to setup cairo font face user data");
}
// Explicit reference count the cairo font face. Otherwise the font face might
// get released by cairo although the JS font face object is still alive.
cairo_font_face_reference(crFace);
FontFace *face = new FontFace(crFace);
face->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}

32
src/FontFace.h

@ -1,32 +0,0 @@
//
// FontFace.h
//
// Copyright (c) 2012 Julian Viereck <julian.viereck@gmail.com>
//
#ifndef __NODE_TRUE_TYPE_FONT_FACE_H__
#define __NODE_TRUE_TYPE_FONT_FACE_H__
#include "Canvas.h"
#include <ft2build.h>
#include <cairo-ft.h>
#include FT_FREETYPE_H
class FontFace: public Nan::ObjectWrap {
public:
static Nan::Persistent<FunctionTemplate> constructor;
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
static NAN_METHOD(New);
FontFace(cairo_font_face_t *crFace)
:_crFace(crFace) {}
inline cairo_font_face_t *cairoFace(){ return _crFace; }
private:
~FontFace();
cairo_font_face_t *_crFace;
static bool _initLibrary;
};
#endif

2
src/ImageData.h

@ -10,7 +10,7 @@
#include "Canvas.h"
#include <stdlib.h>
#include "v8.h"
#include <v8.h>
class ImageData: public Nan::ObjectWrap {
public:

12
src/init.cc

@ -6,17 +6,16 @@
//
#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"
#ifdef HAVE_FREETYPE
#include "FontFace.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#endif
// Compatibility with Visual Studio versions prior to VS2015
#if defined(_MSC_VER) && _MSC_VER < 1900
@ -30,9 +29,6 @@ NAN_MODULE_INIT(init) {
Context2d::Initialize(target);
Gradient::Initialize(target);
Pattern::Initialize(target);
#ifdef HAVE_FREETYPE
FontFace::Initialize(target);
#endif
target->Set(Nan::New<String>("cairoVersion").ToLocalChecked(), Nan::New<String>(cairo_version_string()).ToLocalChecked());
#ifdef HAVE_JPEG
@ -72,11 +68,9 @@ NAN_MODULE_INIT(init) {
#endif
#endif
#ifdef HAVE_FREETYPE
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());
#endif
}
NODE_MODULE(canvas,init);

251
src/register_font.cc

@ -0,0 +1,251 @@
#include <pango/pangocairo.h>
#include <pango/pango-fontmap.h>
#include <pango/pango.h>
#ifdef __APPLE__
#include <CoreText/CoreText.h>
#elif defined(_WIN32)
#include <windows.h>
#else
#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
#define GET_NAME_RANK(X) \
(IS_PREFERRED_ENC(X) ? 1 : 0) + (X.name_id == TT_NAME_ID_PREFERRED_FAMILY ? 1 : 0)
/*
* 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
*/
typedef struct _NameDef {
const char *buf;
int rank; // the higher the more desirable
} NameDef;
gint
_name_def_compare(gconstpointer a, gconstpointer b) {
return ((NameDef*)a)->rank > ((NameDef*)b)->rank ? -1 : 1;
}
// Some versions of GTK+ do not have this, particualrly the one we
// currently link to in node-canvas's wiki
void
_free_g_list_item(gpointer data, gpointer user_data) {
NameDef *d = (NameDef *)data;
free((void *)(d->buf));
}
void
_g_list_free_full(GList *list) {
g_list_foreach(list, _free_g_list_item, NULL);
g_list_free(list);
}
char *
get_family_name(FT_Face face) {
FT_SfntName name;
GList *list = NULL;
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 || name.name_id == TT_NAME_ID_PREFERRED_FAMILY) {
char *buf = to_utf8(name.string, name.string_len, name.platform_id, name.encoding_id);
if (buf) {
NameDef *d = (NameDef*)malloc(sizeof(NameDef));
d->buf = (const char*)buf;
d->rank = GET_NAME_RANK(name);
list = g_list_insert_sorted(list, (gpointer)d, _name_def_compare);
}
}
}
GList *best_def = g_list_first(list);
if (best_def) utf8name = (char*) strdup(((NameDef*)best_def->data)->buf);
if (list) _g_list_free_full(list);
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;
#if PANGO_VERSION >= PANGO_VERSION_ENCODE(1, 36, 7)
case 350: return PANGO_WEIGHT_SEMILIGHT;
#endif
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;
}
}
/*
* Return a PangoFontDescription that will resolve to the font file
*/
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);
if (table) {
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) {
bool success;
#ifdef __APPLE__
CFURLRef filepathUrl = CFURLCreateFromFileSystemRepresentation(NULL, filepath, strlen((char*)filepath), false);
success = CTFontManagerRegisterFontsForURL(filepathUrl, kCTFontManagerScopeProcess, NULL);
#elif defined(_WIN32)
success = AddFontResourceEx((LPCSTR)filepath, FR_PRIVATE, 0) != 0;
#else
success = FcConfigAppFontAddFile(FcConfigGetCurrent(), (FcChar8 *)(filepath));
#endif
if (!success) return false;
// 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.
pango_cairo_font_map_set_default(NULL);
return true;
}

5
src/register_font.h

@ -0,0 +1,5 @@
#include <pango/pango.h>
PangoFontDescription *get_pango_font_description(unsigned char *filepath);
bool register_font(unsigned char *filepath);

10
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'

Loading…
Cancel
Save