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

Text baseline alignment issues #1562

Open
wants to merge 9 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
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);

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