Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ project adheres to [Semantic Versioning](http://semver.org/).
* Fix BMP issues. (#1497)
* Update typings to support jpg and addPage on NodeCanvasRenderingContext2D (#1509)
* Fix assertion failure when using Visual Studio Code debugger to inspect Image prototype (#1534)
* Tweak text baseline positioning to be as close as possible to browser canvas (#1562)

2.6.1
==================
Expand Down
22 changes: 16 additions & 6 deletions src/CanvasRenderingContext2d.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2402,21 +2402,30 @@ NAN_METHOD(Context2d::StrokeText) {
*/
inline double getBaselineAdjustment(PangoLayout* layout, short baseline) {
PangoRectangle logical_rect;
pango_layout_line_get_extents(pango_layout_get_line(layout, 0), NULL, &logical_rect);

PangoLayout* measureLayout = pango_layout_copy(layout);
pango_layout_set_text(measureLayout, "gjĮ測試ÅÊ", -1);
pango_layout_line_get_extents(pango_layout_get_line(measureLayout, 0), NULL, &logical_rect);
double scale = 1.0 / PANGO_SCALE;
double ascent = scale * pango_layout_get_baseline(layout);
double ascent = scale * pango_layout_get_baseline(measureLayout);
double descent = scale * logical_rect.height - ascent;
// 0.072 is a constant that has been chosen comparing the canvas output
// if some code change, this constant can be changed too to keep results aligned
double correction_factor = scale * logical_rect.height * 0.072;

switch (baseline) {
case TEXT_BASELINE_ALPHABETIC:
return ascent;
case TEXT_BASELINE_IDEOGRAPHIC:
return ascent + correction_factor;
case TEXT_BASELINE_MIDDLE:
return (ascent + descent) / 2.0;
case TEXT_BASELINE_BOTTOM:
return ascent + descent;
return (ascent + descent) - correction_factor;
case TEXT_BASELINE_HANGING:
return correction_factor * 3.0;
case TEXT_BASELINE_TOP:
default:
return 0;
return correction_factor;
}
}

Expand All @@ -2443,7 +2452,6 @@ Context2d::setTextPath(double x, double y) {
x -= logical_rect.width;
break;
}

y -= getBaselineAdjustment(_layout, state->textBaseline);

cairo_move_to(_context, x, y);
Expand Down Expand Up @@ -2684,8 +2692,10 @@ NAN_METHOD(Context2d::MeasureText) {
x_offset = 0.0;
}

// are those two line useful?
cairo_matrix_t matrix;
cairo_get_matrix(ctx, &matrix);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

matrix does not seem to be used in the function. Is cairo_get_matrix having some side effect that is necessary for the code below to work?

Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like it is used for alphabeticBaseline below

double y_offset = getBaselineAdjustment(layout, context->state->textBaseline);

Nan::Set(obj,
Expand Down
2 changes: 1 addition & 1 deletion test/canvas.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -947,7 +947,7 @@ describe('Canvas', function () {
assert.ok(metrics.alphabeticBaseline > 0) // ~4-5
assert.ok(metrics.actualBoundingBoxAscent > 0)
// On the baseline or slightly above
assert.ok(metrics.actualBoundingBoxDescent <= 0)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

i tweaked the baseline offset, so i expect some text measurement to change slightly. Unsure if this is ok or not. Any thought anyone?

assert.ok(metrics.actualBoundingBoxDescent <= 1)
});
});

Expand Down
171 changes: 159 additions & 12 deletions test/public/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -981,79 +981,217 @@ tests['textAlign center'] = function (ctx) {
tests['textBaseline alphabetic'] = function (ctx) {
ctx.strokeStyle = '#666'
ctx.strokeRect(0, 0, 200, 200)
ctx.lineTo(0, 100)
ctx.moveTo(0, 50)
ctx.lineTo(200, 50)
ctx.moveTo(0, 100)
ctx.lineTo(200, 100)
ctx.moveTo(0, 150)
ctx.lineTo(200, 150)
ctx.stroke()

ctx.font = 'normal 20px Arial'
ctx.textBaseline = 'alphabetic'
ctx.textAlign = 'center'
ctx.font = 'normal 30px Arial'
ctx.fillText('alphabetic', 100, 50)
ctx.font = 'normal 30px Verdana'
ctx.fillText('alphabetic', 100, 100)
ctx.font = 'normal 30px "Courier New"'
ctx.fillText('alphabetic', 100, 150)
}

tests['textBaseline top'] = function (ctx) {
ctx.strokeStyle = '#666'
ctx.strokeRect(0, 0, 200, 200)
ctx.lineTo(0, 100)
ctx.moveTo(0, 50)
ctx.lineTo(200, 50)
ctx.moveTo(0, 100)
ctx.lineTo(200, 100)
ctx.moveTo(0, 150)
ctx.lineTo(200, 150)
ctx.stroke()

ctx.font = 'normal 20px Arial'
ctx.textBaseline = 'top'
ctx.textAlign = 'center'
ctx.font = 'normal 30px Arial'
ctx.fillText('top', 100, 50)
ctx.font = 'normal 30px Verdana'
ctx.fillText('top', 100, 100)
ctx.font = 'normal 30px "Courier New"'
ctx.fillText('top', 100, 150)
}

tests['textBaseline hanging'] = function (ctx) {
ctx.strokeStyle = '#666'
ctx.strokeRect(0, 0, 200, 200)
ctx.lineTo(0, 100)
ctx.moveTo(0, 50)
ctx.lineTo(200, 50)
ctx.moveTo(0, 100)
ctx.lineTo(200, 100)
ctx.moveTo(0, 150)
ctx.lineTo(200, 150)
ctx.stroke()

ctx.font = 'normal 20px Arial'
ctx.textBaseline = 'hanging'
ctx.textAlign = 'center'
ctx.font = 'normal 30px Arial'
ctx.fillText('hanging', 100, 50)
ctx.font = 'normal 30px Verdana'
ctx.fillText('hanging', 100, 100)
ctx.font = 'normal 30px "Courier New"'
ctx.fillText('hanging', 100, 150)
}

tests['textBaseline middle'] = function (ctx) {
ctx.strokeStyle = '#666'
ctx.strokeRect(0, 0, 200, 200)
ctx.lineTo(0, 100)
ctx.moveTo(0, 50)
ctx.lineTo(200, 50)
ctx.moveTo(0, 100)
ctx.lineTo(200, 100)
ctx.moveTo(0, 150)
ctx.lineTo(200, 150)
ctx.stroke()

ctx.font = 'normal 20px Arial'
ctx.textBaseline = 'middle'
ctx.textAlign = 'center'
ctx.font = 'normal 30px Arial'
ctx.fillText('middle', 100, 50)
ctx.font = 'normal 30px Verdana'
ctx.fillText('middle', 100, 100)
ctx.font = 'normal 30px "Courier New"'
ctx.fillText('middle', 100, 150)
}

tests['textBaseline ideographic'] = function (ctx) {
ctx.strokeStyle = '#666'
ctx.strokeRect(0, 0, 200, 200)
ctx.lineTo(0, 100)
ctx.moveTo(0, 50)
ctx.lineTo(200, 50)
ctx.moveTo(0, 100)
ctx.lineTo(200, 100)
ctx.moveTo(0, 150)
ctx.lineTo(200, 150)
ctx.stroke()

ctx.font = 'normal 20px Arial'
ctx.textBaseline = 'ideographic'
ctx.textAlign = 'center'
ctx.font = 'normal 30px Arial'
ctx.fillText('ideographic', 100, 50)
ctx.font = 'normal 30px Verdana'
ctx.fillText('ideographic', 100, 100)
ctx.font = 'normal 30px "Courier New"'
ctx.fillText('ideographic', 100, 150)
}

tests['textBaseline bottom'] = function (ctx) {
ctx.strokeStyle = '#666'
ctx.strokeRect(0, 0, 200, 200)
ctx.lineTo(0, 100)
ctx.moveTo(0, 50)
ctx.lineTo(200, 50)
ctx.moveTo(0, 100)
ctx.lineTo(200, 100)
ctx.moveTo(0, 150)
ctx.lineTo(200, 150)
ctx.stroke()

ctx.font = 'normal 20px Arial'
ctx.textBaseline = 'bottom'
ctx.textAlign = 'center'
ctx.font = 'normal 30px Arial'
ctx.fillText('bottom', 100, 50)
ctx.font = 'normal 30px Verdana'
ctx.fillText('bottom', 100, 100)
ctx.font = 'normal 30px "Courier New"'
ctx.fillText('bottom', 100, 150)
}

tests['textBaseline alphabetic with scale'] = function (ctx) {
ctx.strokeStyle = '#666'
ctx.scale(1, 4)
ctx.lineWidth = 0.25
ctx.strokeRect(0, 0, 200, 50)
ctx.lineTo(0, 25)
ctx.lineTo(200, 25)
ctx.stroke()

ctx.font = 'normal 30px Arial'
ctx.textBaseline = 'alphabetic'
ctx.textAlign = 'center'
ctx.fillText('alphabetic', 100, 25)
}

tests['textBaseline top with scale'] = function (ctx) {
ctx.strokeStyle = '#666'
ctx.scale(1, 4)
ctx.lineWidth = 0.25
ctx.strokeRect(0, 0, 200, 50)
ctx.lineTo(0, 25)
ctx.lineTo(200, 25)
ctx.stroke()

ctx.font = 'normal 30px Arial'
ctx.textBaseline = 'top'
ctx.textAlign = 'center'
ctx.fillText('top', 100, 25)
}

tests['textBaseline hanging with scale'] = function (ctx) {
ctx.strokeStyle = '#666'
ctx.scale(1, 4)
ctx.lineWidth = 0.25
ctx.strokeRect(0, 0, 200, 50)
ctx.lineTo(0, 25)
ctx.lineTo(200, 25)
ctx.stroke()

ctx.font = 'normal 30px Arial'
ctx.textBaseline = 'hanging'
ctx.textAlign = 'center'
ctx.fillText('hanging', 100, 25)
}

tests['textBaseline middle with scale'] = function (ctx) {
ctx.strokeStyle = '#666'
ctx.scale(1, 4)
ctx.lineWidth = 0.25
ctx.strokeRect(0, 0, 200, 50)
ctx.lineTo(0, 25)
ctx.lineTo(200, 25)
ctx.stroke()

ctx.font = 'normal 30px Arial'
ctx.textBaseline = 'middle'
ctx.textAlign = 'center'
ctx.fillText('middle', 100, 25)
}

tests['textBaseline ideographic with scale'] = function (ctx) {
ctx.strokeStyle = '#666'
ctx.scale(1, 4)
ctx.lineWidth = 0.25
ctx.strokeRect(0, 0, 200, 50)
ctx.lineTo(0, 25)
ctx.lineTo(200, 25)
ctx.stroke()

ctx.font = 'normal 30px Arial'
ctx.textBaseline = 'ideographic'
ctx.textAlign = 'center'
ctx.fillText('ideographic', 100, 25)
}

tests['textBaseline bottom with scale'] = function (ctx) {
ctx.strokeStyle = '#666'
ctx.scale(1, 4)
ctx.lineWidth = 0.25
ctx.strokeRect(0, 0, 200, 50)
ctx.lineTo(0, 25)
ctx.lineTo(200, 25)
ctx.stroke()

ctx.font = 'normal 30px Arial'
ctx.textBaseline = 'bottom'
ctx.textAlign = 'center'
ctx.fillText('bottom', 100, 25)
}

tests['font size px'] = function (ctx) {
Expand Down Expand Up @@ -2506,3 +2644,12 @@ tests['transformed drawimage'] = function (ctx) {
ctx.transform(1.2, 1, 1.8, 1.3, 0, 0)
ctx.drawImage(ctx.canvas, 0, 0)
}

tests['#1544 text scaling issue'] = function (ctx) {
ctx.font = '24px Verdana'
ctx.fillStyle = 'red'
ctx.textAlign = 'left'
ctx.textBaseline = 'top'
ctx.scale(1, 4)
ctx.fillText('2020', 20, 20)
}