diff --git a/CHANGELOG.md b/CHANGELOG.md index 10aac0d31..1a4dab785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 42f331805..a15ba4c96 100644 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -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; } } @@ -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); @@ -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, diff --git a/test/canvas.test.js b/test/canvas.test.js index b824f8da2..b8c8fb9f1 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -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) }); }); diff --git a/test/public/tests.js b/test/public/tests.js index 31ee6aaca..aaa62f8ca 100644 --- a/test/public/tests.js +++ b/test/public/tests.js @@ -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) { @@ -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) +}