Skip to content
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.
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 @@ -19,6 +19,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)
* Fix signed/unsigned comparison warning introduced in 2.6.0, and function cast warnings with GCC8+
* Fix to compile without JPEG support (#1593).
* Fix compile errors with cairo
Expand Down
22 changes: 16 additions & 6 deletions src/CanvasRenderingContext2d.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2398,21 +2398,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 @@ -2439,7 +2448,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 @@ -2680,8 +2688,10 @@ NAN_METHOD(Context2d::MeasureText) {
x_offset = 0.0;
}

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

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