Browse Source

Merge pull request #196 from AllYearbooks/master

Pango support and build improvements.
v1.x
TJ Holowaychuk 13 years ago
parent
commit
7348752713
  1. 40
      binding.gyp
  2. 85
      configure
  3. 30
      examples/pango-glyphs.js
  4. 11
      examples/text.js
  5. 5
      src/Canvas.h
  6. 256
      src/CanvasRenderingContext2d.cc
  7. 20
      src/CanvasRenderingContext2d.h
  8. 12
      src/Image.cc
  9. 6
      src/init.cc
  10. 23
      util/has_lib.sh

40
binding.gyp

@ -1,9 +1,20 @@
{
'variables': {
'GTK_Root%': 'C:/GTK', # Set the location of GTK all-in-one bundle
'with_jpeg%': 'false',
'with_gif%': 'false'
},
'conditions': [
['OS=="win"', {
'variables': {
'GTK_Root%': 'C:/GTK', # Set the location of GTK all-in-one bundle
'with_jpeg%': 'false',
'with_gif%': 'false',
'with_pango%': 'false'
}
}, { # 'OS!="win"'
'variables': {
'with_jpeg%': '<!(./util/has_lib.sh jpeg)',
'with_gif%': '<!(./util/has_lib.sh gif)',
'with_pango%': '<!(./util/has_lib.sh pangocairo)'
}
}]
],
'targets': [
{
'target_name': 'canvas',
@ -34,7 +45,26 @@
'libraries': [
'-lpixman-1',
'-lcairo'
]
}],
['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)'
]
}]
]
}],
['with_jpeg=="true"', {
'defines': [

85
configure

@ -1,85 +0,0 @@
#!/usr/bin/env bash
#
# Build flags
#
cppflags=
#
# Check for <lib>
#
has_lib() {
local lib=$1
for dir in /lib /usr/lib /usr/local/lib /opt/local/lib; do
test -d $dir && ls $dir | grep $lib && return 0
done
return 1
}
#
# Check for <lib> and append to cppflags
#
has() {
local lib=$1
check $lib
has_lib $lib > /dev/null
if test $? -eq 0; then
found $lib
cppflags="$cppflags --with-$lib"
else
missing $lib
fi
}
#
# Check pkg-config for <lib>
#
has_pkg_config() {
local lib=$1
check $lib
pkg-config --libs cairo > /dev/null
if test $? -eq 0; then
found $lib
else
missing $lib
exit 1
fi
}
#
# Output check
#
check() {
printf " \e[90mcheck %s\e[m" $1
}
#
# Output found
#
found() {
printf "\r \e[32m✔ found \e[90m%s\e[m\n" $1
}
#
# Output missing
#
missing() {
printf "\r \e[31m✖ cannot find \e[90m%s\e[m\n" $1
}
# DO IT
echo
has gif
has jpeg
has_pkg_config cairo
echo
# TODO: output flags and get npm "install" script working

30
examples/pango-glyphs.js

@ -0,0 +1,30 @@
/**
* Module dependencies.
*/
var Canvas = require('../lib/canvas')
, canvas = new Canvas(400, 100)
, ctx = canvas.getContext('2d')
, fs = require('fs');
ctx.globalAlpha = 1;
ctx.font = 'normal 16px Impact';
ctx.textBaseline = 'top';
// Note this demo depends node-canvas being installed with pango support,
// and your system having installed fonts supporting the glyphs.
ctx.fillStyle = '#000';
ctx.fillText("English: Some text in Impact.", 10, 10);
ctx.fillText("Japanese: 図書館の中では、静かにする。", 10, 30);
ctx.fillText("Arabic: اللغة العربية هي أكثر اللغات تحدثا ضمن", 10, 50);
ctx.fillText("Korean: 모타는사라미 못하는 사람이", 10, 70);
var out = fs.createWriteStream(__dirname + '/pango-glyphs.png')
, stream = canvas.createPNGStream();
stream.on('data', function(chunk){
out.write(chunk);
});

11
examples/text.js

@ -33,9 +33,18 @@ ctx.strokeText("Wahoo", 50, 100);
ctx.fillStyle = '#000';
ctx.fillText("Wahoo", 49, 99);
var m = ctx.measureText("Wahoo");
ctx.strokeStyle = '#f00';
ctx.strokeRect(49 + m.actualBoundingBoxLeft,
99 - m.actualBoundingBoxAscent,
m.actualBoundingBoxRight - m.actualBoundingBoxLeft,
m.actualBoundingBoxAscent + m.actualBoundingBoxDescent);
var out = fs.createWriteStream(__dirname + '/text.png')
, stream = canvas.createPNGStream();
stream.on('data', function(chunk){
out.write(chunk);
});
});

5
src/Canvas.h

@ -12,7 +12,12 @@
#include <node.h>
#include <node_object_wrap.h>
#include <node_version.h>
#if HAVE_PANGO
#include <pango/pangocairo.h>
#else
#include <cairo/cairo.h>
#endif
using namespace v8;
using namespace node;

256
src/CanvasRenderingContext2d.cc

@ -45,6 +45,29 @@ 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.
*/
#define PANGO_LAYOUT_GET_METRICS(LAYOUT) pango_context_get_metrics( \
pango_layout_get_context(LAYOUT), \
pango_layout_get_font_description(LAYOUT), \
pango_context_get_language(pango_layout_get_context(LAYOUT)))
#endif
/*
* Initialize Context2d.
*/
@ -121,6 +144,9 @@ Context2d::Initialize(Handle<Object> 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;
@ -129,7 +155,7 @@ Context2d::Context2d(Canvas *canvas) {
state->textAlignment = -1;
state->fillPattern = state->strokePattern = NULL;
state->fillGradient = state->strokeGradient = NULL;
state->textBaseline = NULL;
state->textBaseline = TEXT_BASELINE_ALPHABETIC;
rgba_t transparent = { 0,0,0,1 };
rgba_t transparent_black = { 0,0,0,0 };
state->fill = transparent;
@ -137,6 +163,14 @@ 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
}
/*
@ -144,7 +178,15 @@ Context2d::Context2d(Canvas *canvas) {
*/
Context2d::~Context2d() {
while(stateno >= 0) free(states[stateno--]);
while(stateno >= 0) {
#if HAVE_PANGO
free(states[stateno]->fontFamily);
#endif
free(states[stateno--]);
}
#if HAVE_PANGO
g_object_unref(_layout);
#endif
cairo_destroy(_context);
}
@ -177,6 +219,9 @@ 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
state = states[stateno];
}
@ -188,9 +233,15 @@ void
Context2d::restoreState() {
if (0 == stateno) return;
// Olaf (2011-02-21): Free old state data
#if HAVE_PANGO
free(states[stateno]->fontFamily);
#endif
free(states[stateno]);
states[stateno] = NULL;
state = states[--stateno];
#if HAVE_PANGO
setFontFromState();
#endif
}
/*
@ -936,6 +987,8 @@ Context2d::GetTextDrawingMode(Local<String> prop, const AccessorInfo &info) {
mode = "path";
} else if (context->state->textDrawingMode == TEXT_DRAW_GLYPHS) {
mode = "glyph";
} else {
mode = "unknown";
}
return scope.Close(String::NewSymbol(mode));
}
@ -1519,6 +1572,53 @@ Context2d::StrokeText(const Arguments &args) {
void
Context2d::setTextPath(const char *str, double x, double y) {
#if HAVE_PANGO
PangoRectangle ink_rect, logical_rect;
PangoFontMetrics *metrics = NULL;
pango_layout_set_text(_layout, str, -1);
pango_cairo_update_layout(_context, _layout);
switch (state->textAlignment) {
// center
case 0:
pango_layout_get_pixel_extents(_layout, &ink_rect, &logical_rect);
x -= logical_rect.width / 2;
break;
// right
case 1:
pango_layout_get_pixel_extents(_layout, &ink_rect, &logical_rect);
x -= logical_rect.width;
break;
}
switch (state->textBaseline) {
case TEXT_BASELINE_ALPHABETIC:
metrics = PANGO_LAYOUT_GET_METRICS(_layout);
y -= pango_font_metrics_get_ascent(metrics) / PANGO_SCALE;
break;
case TEXT_BASELINE_MIDDLE:
metrics = PANGO_LAYOUT_GET_METRICS(_layout);
y -= (pango_font_metrics_get_ascent(metrics) + pango_font_metrics_get_descent(metrics))/(2.0 * PANGO_SCALE);
break;
case TEXT_BASELINE_BOTTOM:
metrics = PANGO_LAYOUT_GET_METRICS(_layout);
y -= (pango_font_metrics_get_ascent(metrics) + pango_font_metrics_get_descent(metrics)) / PANGO_SCALE;
break;
}
if (metrics) pango_font_metrics_unref(metrics);
cairo_move_to(_context, x, y);
if (state->textDrawingMode == TEXT_DRAW_PATHS) {
pango_cairo_layout_path(_context, _layout);
} else if (state->textDrawingMode == TEXT_DRAW_GLYPHS) {
pango_cairo_show_layout(_context, _layout);
}
#else
cairo_text_extents_t te;
cairo_font_extents_t fe;
@ -1567,6 +1667,8 @@ Context2d::setTextPath(const char *str, double x, double y) {
} else if (state->textDrawingMode == TEXT_DRAW_GLYPHS) {
cairo_show_text(_context, str);
}
#endif
}
/*
@ -1636,8 +1738,49 @@ Context2d::SetFont(const Arguments &args) {
double size = args[2]->NumberValue();
String::AsciiValue unit(args[3]);
String::AsciiValue family(args[4]);
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
#if HAVE_PANGO
if (strlen(*family) > 0) state_assign_fontFamily(context->state, *family);
if (size > 0) context->state->fontSize = size;
if (strlen(*style) > 0) {
if (0 == strcmp("italic", *style)) {
context->state->fontStyle = PANGO_STYLE_ITALIC;
} else if (0 == strcmp("oblique", *style)) {
context->state->fontStyle = PANGO_STYLE_OBLIQUE;
}
}
if (strlen(*weight) > 0) {
if (0 == strcmp("bold", *weight)) {
context->state->fontWeight = PANGO_WEIGHT_BOLD;
} else if (0 == strcmp("200", *weight)) {
context->state->fontWeight = PANGO_WEIGHT_ULTRALIGHT;
} else if (0 == strcmp("300", *weight)) {
context->state->fontWeight = PANGO_WEIGHT_LIGHT;
} else if (0 == strcmp("400", *weight)) {
context->state->fontWeight = PANGO_WEIGHT_NORMAL;
} else if (0 == strcmp("500", *weight)) {
context->state->fontWeight = PANGO_WEIGHT_MEDIUM;
} else if (0 == strcmp("600", *weight)) {
context->state->fontWeight = PANGO_WEIGHT_SEMIBOLD;
} else if (0 == strcmp("700", *weight)) {
context->state->fontWeight = PANGO_WEIGHT_BOLD;
} else if (0 == strcmp("800", *weight)) {
context->state->fontWeight = PANGO_WEIGHT_ULTRABOLD;
} else if (0 == strcmp("900", *weight)) {
context->state->fontWeight = PANGO_WEIGHT_HEAVY;
}
}
context->setFontFromState();
#else
cairo_t *ctx = context->context();
// Size
@ -1658,10 +1801,33 @@ Context2d::SetFont(const Arguments &args) {
}
cairo_select_font_face(ctx, *family, s, w);
#endif
return Undefined();
}
#if HAVE_PANGO
/*
* Sets PangoLayout options from the current font state
*/
void
Context2d::setFontFromState() {
PangoFontDescription *fd = pango_font_description_new();
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);
pango_layout_set_font_description(_layout, fd);
pango_font_description_free(fd);
}
#endif
/*
* Return the given text extents.
* TODO: Support for:
@ -1679,14 +1845,72 @@ Context2d::MeasureText(const Arguments &args) {
String::Utf8Value str(args[0]->ToString());
Local<Object> obj = Object::New();
#if HAVE_PANGO
PangoRectangle ink_rect, logical_rect;
PangoFontMetrics *metrics;
PangoLayout *layout = context->layout();
pango_layout_set_text(layout, *str, -1);
pango_cairo_update_layout(ctx, layout);
pango_layout_get_pixel_extents(layout, &ink_rect, &logical_rect);
metrics = PANGO_LAYOUT_GET_METRICS(layout);
double x_offset;
switch (context->state->textAlignment) {
case 0: // center
x_offset = logical_rect.width / 2;
break;
case 1: // right
x_offset = logical_rect.width;
break;
default: // left
x_offset = 0.0;
}
double y_offset;
switch (context->state->textBaseline) {
case TEXT_BASELINE_ALPHABETIC:
y_offset = -pango_font_metrics_get_ascent(metrics) / PANGO_SCALE;
break;
case TEXT_BASELINE_MIDDLE:
y_offset = -(pango_font_metrics_get_ascent(metrics) + pango_font_metrics_get_descent(metrics))/(2.0 * PANGO_SCALE);
break;
case TEXT_BASELINE_BOTTOM:
y_offset = -(pango_font_metrics_get_ascent(metrics) + pango_font_metrics_get_descent(metrics)) / PANGO_SCALE;
break;
default:
y_offset = 0.0;
}
obj->Set(String::New("width"), Number::New(logical_rect.width));
obj->Set(String::New("actualBoundingBoxLeft"),
Number::New(x_offset - PANGO_LBEARING(logical_rect)));
obj->Set(String::New("actualBoundingBoxRight"),
Number::New(x_offset + PANGO_RBEARING(logical_rect)));
obj->Set(String::New("actualBoundingBoxAscent"),
Number::New(-(y_offset+ink_rect.y)));
obj->Set(String::New("actualBoundingBoxDescent"),
Number::New((PANGO_DESCENT(ink_rect) + y_offset)));
obj->Set(String::New("emHeightAscent"),
Number::New(PANGO_ASCENT(logical_rect) - y_offset));
obj->Set(String::New("emHeightDescent"),
Number::New(PANGO_DESCENT(logical_rect) + y_offset));
obj->Set(String::New("alphabeticBaseline"),
Number::New((pango_font_metrics_get_ascent(metrics) / PANGO_SCALE)
+ y_offset));
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);
obj->Set(String::New("width"), Number::New(te.x_advance));
double x_offset;
switch (context->state->textAlignment) {
case 0: // center
@ -1699,9 +1923,6 @@ Context2d::MeasureText(const Arguments &args) {
x_offset = 0.0;
}
obj->Set(String::New("actualBoundingBoxLeft"), Number::New(x_offset - te.x_bearing));
obj->Set(String::New("actualBoundingBoxRight"), Number::New((te.x_bearing + te.width) - x_offset));
double y_offset;
switch (context->state->textBaseline) {
case TEXT_BASELINE_TOP:
@ -1717,12 +1938,21 @@ Context2d::MeasureText(const Arguments &args) {
default:
y_offset = 0.0;
}
obj->Set(String::New("actualBoundingBoxAscent"), Number::New(-(te.y_bearing + y_offset)));
obj->Set(String::New("actualBoundingBoxDescent"), Number::New(te.height + te.y_bearing + y_offset));
obj->Set(String::New("width"), Number::New(te.x_advance));
obj->Set(String::New("actualBoundingBoxLeft"),
Number::New(x_offset - te.x_bearing));
obj->Set(String::New("actualBoundingBoxRight"),
Number::New((te.x_bearing + te.width) - x_offset));
obj->Set(String::New("actualBoundingBoxAscent"),
Number::New(-(te.y_bearing + y_offset)));
obj->Set(String::New("actualBoundingBoxDescent"),
Number::New(te.height + te.y_bearing + y_offset));
obj->Set(String::New("emHeightAscent"), Number::New(fe.ascent - y_offset));
obj->Set(String::New("emHeightDescent"), Number::New(fe.descent + y_offset));
obj->Set(String::New("alphabeticBaseline"), Number::New(-y_offset));
obj->Set(String::New("alphabeticBaseline"), Number::New(y_offset));
#endif
return scope.Close(obj);
}

20
src/CanvasRenderingContext2d.h

@ -40,8 +40,20 @@ typedef struct {
double shadowOffsetX;
double shadowOffsetY;
canvas_draw_mode_t textDrawingMode;
#if HAVE_PANGO
PangoWeight fontWeight;
PangoStyle fontStyle;
double fontSize;
char *fontFamily;
#endif
} canvas_state_t;
#if HAVE_PANGO
void state_assign_fontFamily(canvas_state_t *state, const char *str);
#endif
class Context2d: public node::ObjectWrap {
public:
short stateno;
@ -134,11 +146,19 @@ class Context2d: public node::ObjectWrap {
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

12
src/Image.cc

@ -881,17 +881,21 @@ Image::loadJPEG(FILE *stream) {
buf = (uint8_t *) malloc(len);
if (!buf) return CAIRO_STATUS_NO_MEMORY;
fread(buf, len, 1, stream);
fclose(stream);
if ((DATA_IMAGE | DATA_MIME) == data_mode) {
if (fread(buf, len, 1, stream) != len) {
status = CAIRO_STATUS_READ_ERROR;
} else if ((DATA_IMAGE | DATA_MIME) == data_mode) {
status = loadJPEGFromBuffer(buf, len);
if (!status) status = assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
} else if (DATA_MIME == data_mode) {
status = decodeJPEGBufferIntoMimeSurface(buf, len);
} else {
status = CAIRO_STATUS_READ_ERROR;
}
fclose(stream);
free(buf);
#else
status = CAIRO_STATUS_READ_ERROR;
#endif
}

6
src/init.cc

@ -26,6 +26,12 @@ init (Handle<Object> target) {
Pattern::Initialize(target);
target->Set(String::New("cairoVersion"), String::New(cairo_version_string()));
#ifdef HAVE_JPEG
#ifdef LIBJPEG_TURBO_VERSION
#define JPEG_LIB_VERSION_MAJOR 8
#define JPEG_LIB_VERSION_MINOR 4
#endif
char jpeg_version[10];
snprintf(jpeg_version, 10, "%d%c", JPEG_LIB_VERSION_MAJOR, JPEG_LIB_VERSION_MINOR + 'a' - 1);
target->Set(String::New("jpegVersion"), String::New(jpeg_version));

23
util/has_lib.sh

@ -0,0 +1,23 @@
#!/usr/bin/env bash
has_lib() {
local lib=$1
# Try using ldconfig on linux systems
for LINE in `which ldconfig > /dev/null && ldconfig -p 2>/dev/null | grep lib$lib`; do
return 0
done
# Try just checking common library locations
for dir in /lib /usr/lib /usr/local/lib /opt/local/lib; do
test -d $dir && ls $dir | grep $lib && return 0
done
return 1
}
has_lib $1 > /dev/null
if test $? -eq 0; then
echo true
else
echo false
fi
Loading…
Cancel
Save