Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Render small-caps font variant #1611

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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
==================
Expand Down
Binary file added examples/crimsonFont/Crimson-Bold.ttf
Binary file not shown.
Binary file added examples/crimsonFont/Crimson-BoldItalic.ttf
Binary file not shown.
Binary file added examples/crimsonFont/Crimson-Italic.ttf
Binary file not shown.
Binary file added examples/crimsonFont/Crimson-Roman.ttf
Binary file not shown.
Binary file added examples/crimsonFont/Crimson-Semibold.ttf
Binary file not shown.
Binary file added examples/crimsonFont/Crimson-SemiboldItalic.ttf
Binary file not shown.
2 changes: 2 additions & 0 deletions index.js
Expand Up @@ -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
}
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -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": {
Expand Down
20 changes: 20 additions & 0 deletions src/CanvasRenderingContext2d.cc
Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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();
}
Expand All @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't quite rhyme with how we handle the font description on the state (continued below)

state = states[stateno];
}
}
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -2499,6 +2507,7 @@ NAN_GETTER(Context2d::GetFont) {

/*
* Set font:
* - variant
* - weight
* - style
* - size
Expand All @@ -2523,6 +2532,7 @@ NAN_SETTER(Context2d::SetFont) {
if (mparsed->IsUndefined()) return;
Local<Object> font = Nan::To<Object>(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<double>(Nan::Get(font, Nan::New("size").ToLocalChecked()).ToLocalChecked()).FromMaybe(0);
Expand All @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little more code, but you could instead free the attr list on the stack first, then set the a fresh attr list on the layout and stack here. That way it's a little more like the code above and we're not mutating objects on the state.

#endif

context->_font.Reset(value);
}

Expand Down
1 change: 1 addition & 0 deletions src/CanvasRenderingContext2d.h
Expand Up @@ -37,6 +37,7 @@ typedef struct {
double shadowOffsetY;
canvas_draw_mode_t textDrawingMode;
PangoFontDescription *fontDescription;
PangoAttrList *textAttributes;
bool imageSmoothingEnabled;
} canvas_state_t;

Expand Down
4 changes: 4 additions & 0 deletions src/init.cc
Expand Up @@ -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<String>("freetypeVersion").ToLocalChecked(), Nan::New<String>(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<String>("pangoVersion").ToLocalChecked(), Nan::New<String>(pango_version).ToLocalChecked()).Check();
}

NODE_MODULE(canvas, init);
34 changes: 34 additions & 0 deletions test/canvas.test.js
Expand Up @@ -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:
Expand Down Expand Up @@ -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');
Expand Down