diff --git a/CHANGELOG.md b/CHANGELOG.md index 92e63fbfa..967960cc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ project adheres to [Semantic Versioning](http://semver.org/). * Fix compile errors with cairo * Fix Image#complete if the image failed to load. * Upgrade node-pre-gyp to v0.15.0 to use latest version of needle to fix error when downloading prebuilds. +* The small-caps variant setting is now honored if included in a font string 2.6.1 ================== diff --git a/examples/crimsonFont/Crimson-Bold.ttf b/examples/crimsonFont/Crimson-Bold.ttf new file mode 100755 index 000000000..8399d75fc Binary files /dev/null and b/examples/crimsonFont/Crimson-Bold.ttf differ diff --git a/examples/crimsonFont/Crimson-BoldItalic.ttf b/examples/crimsonFont/Crimson-BoldItalic.ttf new file mode 100755 index 000000000..d9b4af4c3 Binary files /dev/null and b/examples/crimsonFont/Crimson-BoldItalic.ttf differ diff --git a/examples/crimsonFont/Crimson-Italic.ttf b/examples/crimsonFont/Crimson-Italic.ttf new file mode 100755 index 000000000..b3afa6b7f Binary files /dev/null and b/examples/crimsonFont/Crimson-Italic.ttf differ diff --git a/examples/crimsonFont/Crimson-Roman.ttf b/examples/crimsonFont/Crimson-Roman.ttf new file mode 100755 index 000000000..ab0f0ed6c Binary files /dev/null and b/examples/crimsonFont/Crimson-Roman.ttf differ diff --git a/examples/crimsonFont/Crimson-Semibold.ttf b/examples/crimsonFont/Crimson-Semibold.ttf new file mode 100755 index 000000000..0b92fd02f Binary files /dev/null and b/examples/crimsonFont/Crimson-Semibold.ttf differ diff --git a/examples/crimsonFont/Crimson-SemiboldItalic.ttf b/examples/crimsonFont/Crimson-SemiboldItalic.ttf new file mode 100755 index 000000000..4d20e036b Binary files /dev/null and b/examples/crimsonFont/Crimson-SemiboldItalic.ttf differ diff --git a/index.js b/index.js index c54bfa36b..e9c9cbf1f 100644 --- a/index.js +++ b/index.js @@ -81,6 +81,8 @@ module.exports = { gifVersion: bindings.gifVersion ? bindings.gifVersion.replace(/[^.\d]/g, '') : undefined, /** freetype version. */ freetypeVersion: bindings.freetypeVersion, + /** pango version. */ + pangoVersion: bindings.pangoVersion, /** rsvg version. */ rsvgVersion: bindings.rsvgVersion } diff --git a/package.json b/package.json index 46567b3ec..ae8bddc18 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "express": "^4.16.3", "mocha": "^5.2.0", "pixelmatch": "^4.0.2", + "semver": "^7.3.2", "standard": "^12.0.1" }, "engines": { diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 774612708..b953a956d 100644 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -199,6 +199,7 @@ Context2d::Context2d(Canvas *canvas) { Context2d::~Context2d() { while(stateno >= 0) { pango_font_description_free(states[stateno]->fontDescription); + pango_attr_list_unref(states[stateno]->textAttributes); free(states[stateno--]); } g_object_unref(_layout); @@ -213,6 +214,7 @@ Context2d::~Context2d() { void Context2d::resetState(bool init) { if (!init) { pango_font_description_free(state->fontDescription); + pango_attr_list_unref(states[stateno]->textAttributes); } state->shadowBlur = 0; @@ -235,6 +237,8 @@ void Context2d::resetState(bool init) { 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); + state->textAttributes = pango_attr_list_new(); + pango_layout_set_attributes(_layout, state->textAttributes); _resetPersistentHandles(); } @@ -258,6 +262,8 @@ Context2d::save() { states[++stateno] = (canvas_state_t *) malloc(sizeof(canvas_state_t)); memcpy(states[stateno], state, sizeof(canvas_state_t)); states[stateno]->fontDescription = pango_font_description_copy(states[stateno-1]->fontDescription); + states[stateno]->textAttributes = pango_attr_list_copy(states[stateno-1]->textAttributes); + pango_layout_set_attributes(_layout, states[stateno]->textAttributes); state = states[stateno]; } } @@ -271,10 +277,12 @@ Context2d::restore() { if (stateno > 0) { cairo_restore(_context); pango_font_description_free(states[stateno]->fontDescription); + pango_attr_list_unref(states[stateno]->textAttributes); free(states[stateno]); states[stateno] = NULL; state = states[--stateno]; pango_layout_set_font_description(_layout, state->fontDescription); + pango_layout_set_attributes(_layout, state->textAttributes); } } @@ -2499,6 +2507,7 @@ NAN_GETTER(Context2d::GetFont) { /* * Set font: + * - variant * - weight * - style * - size @@ -2523,6 +2532,7 @@ NAN_SETTER(Context2d::SetFont) { if (mparsed->IsUndefined()) return; Local font = Nan::To(mparsed).ToLocalChecked(); + Nan::Utf8String variant(Nan::Get(font, Nan::New("variant").ToLocalChecked()).ToLocalChecked()); Nan::Utf8String weight(Nan::Get(font, Nan::New("weight").ToLocalChecked()).ToLocalChecked()); Nan::Utf8String style(Nan::Get(font, Nan::New("style").ToLocalChecked()).ToLocalChecked()); double size = Nan::To(Nan::Get(font, Nan::New("size").ToLocalChecked()).ToLocalChecked()).FromMaybe(0); @@ -2547,6 +2557,16 @@ NAN_SETTER(Context2d::SetFont) { context->state->fontDescription = sys_desc; pango_layout_set_font_description(context->_layout, sys_desc); + #if PANGO_VERSION >= PANGO_VERSION_ENCODE(1, 37, 1) + PangoAttribute *features; + if (strlen(*variant) > 0 && strcmp("small-caps", *variant) == 0) { + features = pango_attr_font_features_new("smcp 1, onum 1"); + } else { + features = pango_attr_font_features_new(""); + } + pango_attr_list_change(context->state->textAttributes, features); + #endif + context->_font.Reset(value); } diff --git a/src/CanvasRenderingContext2d.h b/src/CanvasRenderingContext2d.h index 59d6f6d11..a29d83b75 100644 --- a/src/CanvasRenderingContext2d.h +++ b/src/CanvasRenderingContext2d.h @@ -37,6 +37,7 @@ typedef struct { double shadowOffsetY; canvas_draw_mode_t textDrawingMode; PangoFontDescription *fontDescription; + PangoAttrList *textAttributes; bool imageSmoothingEnabled; } canvas_state_t; diff --git a/src/init.cc b/src/init.cc index 816ba5837..2a4045840 100644 --- a/src/init.cc +++ b/src/init.cc @@ -87,6 +87,10 @@ NAN_MODULE_INIT(init) { char freetype_version[10]; snprintf(freetype_version, 10, "%d.%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); Nan::Set(target, Nan::New("freetypeVersion").ToLocalChecked(), Nan::New(freetype_version).ToLocalChecked()).Check(); + + char pango_version[10]; + snprintf(pango_version, 10, "%d.%d.%d", PANGO_VERSION_MAJOR, PANGO_VERSION_MINOR, PANGO_VERSION_MICRO); + Nan::Set(target, Nan::New("pangoVersion").ToLocalChecked(), Nan::New(pango_version).ToLocalChecked()).Check(); } NODE_MODULE(canvas, init); diff --git a/test/canvas.test.js b/test/canvas.test.js index b28a33990..7bde6892d 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -11,10 +11,12 @@ const createImageData = require('../').createImageData const loadImage = require('../').loadImage const parseFont = require('../').parseFont const registerFont = require('../').registerFont +const pangoVersion = require('../').pangoVersion const assert = require('assert') const os = require('os') const Readable = require('stream').Readable +const semver = require('semver') describe('Canvas', function () { // Run with --expose-gc and uncomment this line to help find memory problems: @@ -446,6 +448,38 @@ describe('Canvas', function () { assert.equal(ctx.font, '15px Arial, sans-serif') }); + it('Context2d#font=small-caps', function () { + if (!semver.satisfies(pangoVersion, '>=1.37.1')){ + this.skip(); + } + + registerFont('./examples/crimsonFont/Crimson-Bold.ttf', {family: 'Crimson'}) + let canvas = createCanvas(200, 200), + ctx = canvas.getContext('2d'); + + // here is where the dot will appear above a lower-case 'i' (and be missing for small-caps) + let offsetX = 15, offsetY = -115; + + ctx.font = "180px Crimson"; + ctx.fillText('i', 20, 180); + let lcDottedI = ctx.getImageData(20 + offsetX, 180 + offsetY, 20, 20).data; + assert.equal(lcDottedI.some(p => p > 0), true); + + ctx.save() + + ctx.font = "small-caps 180px Crimson"; + ctx.fillText('i', 80, 180); + let scEmptySpace = ctx.getImageData(80 + offsetX, 180 + offsetY, 20, 20).data; + assert.equal(scEmptySpace.every(p => p == 0), true); + + ctx.restore() + + ctx.fillText('i', 140, 180); + let lcDottedAgain = ctx.getImageData(140 + offsetX, 180 + offsetY, 20, 20).data; + assert.equal(lcDottedAgain.some(p => p > 0), true); + }); + + it('Context2d#lineWidth=', function () { var canvas = createCanvas(200, 200) , ctx = canvas.getContext('2d');