|
|
@ -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); |
|
|
|
} |
|
|
|