From 0d3abd315acde1775694e02b365c676a50ed3367 Mon Sep 17 00:00:00 2001 From: Nick Doiron Date: Sat, 2 Aug 2025 10:33:45 -0500 Subject: [PATCH 1/5] attempt to show differences by ctx.lang --- index.d.ts | 1 + src/CanvasRenderingContext2d.cc | 25 ++++++++++++++++++++++++- src/CanvasRenderingContext2d.h | 3 +++ test/canvas.test.js | 18 ++++++++++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 43ff107d0..27ab0c341 100644 --- a/index.d.ts +++ b/index.d.ts @@ -291,6 +291,7 @@ export class CanvasRenderingContext2D { textAlign: CanvasTextAlign; canvas: Canvas; direction: 'ltr' | 'rtl'; + lang: string; } export class CanvasGradient { diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 15c5c80f5..b4afe79f3 100644 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -162,7 +162,8 @@ Context2d::Initialize(Napi::Env& env, Napi::Object& exports) { InstanceAccessor<&Context2d::GetFont, &Context2d::SetFont>("font", napi_default_jsproperty), InstanceAccessor<&Context2d::GetTextBaseline, &Context2d::SetTextBaseline>("textBaseline", napi_default_jsproperty), InstanceAccessor<&Context2d::GetTextAlign, &Context2d::SetTextAlign>("textAlign", napi_default_jsproperty), - InstanceAccessor<&Context2d::GetDirection, &Context2d::SetDirection>("direction", napi_default_jsproperty) + InstanceAccessor<&Context2d::GetDirection, &Context2d::SetDirection>("direction", napi_default_jsproperty), + InstanceAccessor<&Context2d::GetLanguage, &Context2d::SetLanguage>("lang", napi_default_jsproperty) }); exports.Set("CanvasRenderingContext2d", ctor); @@ -786,6 +787,28 @@ Context2d::SetDirection(const Napi::CallbackInfo& info, const Napi::Value& value state->direction = dir; } +/* + * Get language. + */ +Napi::Value +Context2d::GetLanguage(const Napi::CallbackInfo& info) { + return Napi::String::New(env, state->lang); +} + +/* + * Set language. + */ +void +Context2d::SetLanguage(const Napi::CallbackInfo& info, const Napi::Value& value) { + if (!value.IsString()) return; + + std::string lang = value.As(); + state->lang = lang; + + pango_context_set_language(pango_layout_get_context(_layout), pango_language_from_string(lang.c_str())); + pango_cairo_update_layout(context(), _layout); +} + /* * Put image data. * diff --git a/src/CanvasRenderingContext2d.h b/src/CanvasRenderingContext2d.h index 341e5936d..b96a70f0a 100644 --- a/src/CanvasRenderingContext2d.h +++ b/src/CanvasRenderingContext2d.h @@ -36,6 +36,7 @@ struct canvas_state_t { canvas_draw_mode_t textDrawingMode = TEXT_DRAW_PATHS; bool imageSmoothingEnabled = true; std::string direction = "ltr"; + std::string lang = ""; canvas_state_t() { fontDescription = pango_font_description_from_string("sans"); @@ -157,6 +158,7 @@ class Context2d : public Napi::ObjectWrap { Napi::Value GetFont(const Napi::CallbackInfo& info); Napi::Value GetTextBaseline(const Napi::CallbackInfo& info); Napi::Value GetTextAlign(const Napi::CallbackInfo& info); + Napi::Value GetLanguage(const Napi::CallbackInfo& info); void SetPatternQuality(const Napi::CallbackInfo& info, const Napi::Value& value); void SetImageSmoothingEnabled(const Napi::CallbackInfo& info, const Napi::Value& value); void SetGlobalCompositeOperation(const Napi::CallbackInfo& info, const Napi::Value& value); @@ -179,6 +181,7 @@ class Context2d : public Napi::ObjectWrap { void SetFont(const Napi::CallbackInfo& info, const Napi::Value& value); void SetTextBaseline(const Napi::CallbackInfo& info, const Napi::Value& value); void SetTextAlign(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetLanguage(const Napi::CallbackInfo& info, const Napi::Value& value); #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0) void BeginTag(const Napi::CallbackInfo& info); void EndTag(const Napi::CallbackInfo& info); diff --git a/test/canvas.test.js b/test/canvas.test.js index 01156d089..59bd5f564 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -486,6 +486,24 @@ describe('Canvas', function () { assert.equal(ctx.font, '15px Arial, sans-serif') }) + it('Context2d#lang=', function () { + const canvas1 = createCanvas(200, 200) + const ctx1 = canvas1.getContext('2d') + assert.equal(ctx1.lang, '') + ctx1.font = '20px sans-serif'; + ctx1.lang = 'ja' + assert.equal(ctx1.lang, 'ja') + ctx1.fillText('门', 10, 10) + + const canvas2 = createCanvas(200, 200) + const ctx2 = canvas2.getContext('2d') + ctx2.font = '20px sans-serif'; + ctx2.lang = 'zh' + ctx2.fillText('门', 10, 10) + + assert.notDeepEqual(ctx1.getImageData(10, 10, 40, 40), ctx2.getImageData(10, 10, 40, 40)) + }) + it('Context2d#lineWidth=', function () { const canvas = createCanvas(200, 200) const ctx = canvas.getContext('2d') From 9a0d5133ed17cb7f9ee94c06a67feba40316be30 Mon Sep 17 00:00:00 2001 From: Nick Doiron Date: Sun, 3 Aug 2025 19:16:16 -0500 Subject: [PATCH 2/5] use pango_context_get_language instead of stored lang state --- index.d.ts | 1 - src/CanvasRenderingContext2d.cc | 10 ++++++---- src/CanvasRenderingContext2d.h | 1 - 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/index.d.ts b/index.d.ts index 27ab0c341..43ff107d0 100644 --- a/index.d.ts +++ b/index.d.ts @@ -291,7 +291,6 @@ export class CanvasRenderingContext2D { textAlign: CanvasTextAlign; canvas: Canvas; direction: 'ltr' | 'rtl'; - lang: string; } export class CanvasGradient { diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index b4afe79f3..080b7d4b7 100644 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -792,7 +792,12 @@ Context2d::SetDirection(const Napi::CallbackInfo& info, const Napi::Value& value */ Napi::Value Context2d::GetLanguage(const Napi::CallbackInfo& info) { - return Napi::String::New(env, state->lang); + PangoLanguage* lang = pango_context_get_language(pango_layout_get_context(_layout)); + if (!lang) { + return Napi::String::New(env, ""); + } + std::string langCode = pango_language_to_string(lang); + return Napi::String::New(env, langCode); } /* @@ -803,10 +808,7 @@ Context2d::SetLanguage(const Napi::CallbackInfo& info, const Napi::Value& value) if (!value.IsString()) return; std::string lang = value.As(); - state->lang = lang; - pango_context_set_language(pango_layout_get_context(_layout), pango_language_from_string(lang.c_str())); - pango_cairo_update_layout(context(), _layout); } /* diff --git a/src/CanvasRenderingContext2d.h b/src/CanvasRenderingContext2d.h index b96a70f0a..e8f0e2d5d 100644 --- a/src/CanvasRenderingContext2d.h +++ b/src/CanvasRenderingContext2d.h @@ -36,7 +36,6 @@ struct canvas_state_t { canvas_draw_mode_t textDrawingMode = TEXT_DRAW_PATHS; bool imageSmoothingEnabled = true; std::string direction = "ltr"; - std::string lang = ""; canvas_state_t() { fontDescription = pango_font_description_from_string("sans"); From dc7eac9861df4cba90c7a37120369d41469ea7fa Mon Sep 17 00:00:00 2001 From: Nick Doiron Date: Sun, 17 Aug 2025 09:09:51 -0500 Subject: [PATCH 3/5] Revert "use pango_context_get_language instead of stored lang state" This reverts commit 9a0d5133ed17cb7f9ee94c06a67feba40316be30. --- index.d.ts | 1 + src/CanvasRenderingContext2d.cc | 10 ++++------ src/CanvasRenderingContext2d.h | 1 + 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/index.d.ts b/index.d.ts index 43ff107d0..27ab0c341 100644 --- a/index.d.ts +++ b/index.d.ts @@ -291,6 +291,7 @@ export class CanvasRenderingContext2D { textAlign: CanvasTextAlign; canvas: Canvas; direction: 'ltr' | 'rtl'; + lang: string; } export class CanvasGradient { diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 080b7d4b7..b4afe79f3 100644 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -792,12 +792,7 @@ Context2d::SetDirection(const Napi::CallbackInfo& info, const Napi::Value& value */ Napi::Value Context2d::GetLanguage(const Napi::CallbackInfo& info) { - PangoLanguage* lang = pango_context_get_language(pango_layout_get_context(_layout)); - if (!lang) { - return Napi::String::New(env, ""); - } - std::string langCode = pango_language_to_string(lang); - return Napi::String::New(env, langCode); + return Napi::String::New(env, state->lang); } /* @@ -808,7 +803,10 @@ Context2d::SetLanguage(const Napi::CallbackInfo& info, const Napi::Value& value) if (!value.IsString()) return; std::string lang = value.As(); + state->lang = lang; + pango_context_set_language(pango_layout_get_context(_layout), pango_language_from_string(lang.c_str())); + pango_cairo_update_layout(context(), _layout); } /* diff --git a/src/CanvasRenderingContext2d.h b/src/CanvasRenderingContext2d.h index e8f0e2d5d..b96a70f0a 100644 --- a/src/CanvasRenderingContext2d.h +++ b/src/CanvasRenderingContext2d.h @@ -36,6 +36,7 @@ struct canvas_state_t { canvas_draw_mode_t textDrawingMode = TEXT_DRAW_PATHS; bool imageSmoothingEnabled = true; std::string direction = "ltr"; + std::string lang = ""; canvas_state_t() { fontDescription = pango_font_description_from_string("sans"); From b5fcf9b95a16feb55f31e41717d9c476b3c6f413 Mon Sep 17 00:00:00 2001 From: Nick Doiron Date: Sun, 17 Aug 2025 09:51:39 -0500 Subject: [PATCH 4/5] Remove extra pango_cairo_update_layout Co-authored-by: Caleb Hearon --- src/CanvasRenderingContext2d.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index b4afe79f3..5402f5bff 100644 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -806,7 +806,6 @@ Context2d::SetLanguage(const Napi::CallbackInfo& info, const Napi::Value& value) state->lang = lang; pango_context_set_language(pango_layout_get_context(_layout), pango_language_from_string(lang.c_str())); - pango_cairo_update_layout(context(), _layout); } /* From f865df7a74dc78e8674e67a17bc9a3df9f165933 Mon Sep 17 00:00:00 2001 From: Nick Doiron Date: Sun, 17 Aug 2025 10:27:48 -0500 Subject: [PATCH 5/5] store language in state->lang until needed --- CHANGELOG.md | 1 + src/CanvasRenderingContext2d.cc | 10 +++++++--- src/CanvasRenderingContext2d.h | 1 + test/canvas.test.js | 21 ++------------------- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98cba4534..79119fecd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ project adheres to [Semantic Versioning](http://semver.org/). ================== ### Changed ### Added +* Added `ctx.lang` to set the ISO language code for text ### Fixed 3.1.2 diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 5402f5bff..2342a57e5 100644 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -43,7 +43,7 @@ constexpr double twoPi = M_PI * 2.; #define PANGO_LAYOUT_GET_METRICS(LAYOUT) pango_context_get_metrics( \ pango_layout_get_context(LAYOUT), \ pango_layout_get_font_description(LAYOUT), \ - pango_context_get_language(pango_layout_get_context(LAYOUT))) + pango_language_from_string(state->lang.c_str())) inline static bool checkArgs(const Napi::CallbackInfo&info, double *args, int argsNum, int offset = 0){ Napi::Env env = info.Env(); @@ -804,8 +804,6 @@ Context2d::SetLanguage(const Napi::CallbackInfo& info, const Napi::Value& value) std::string lang = value.As(); state->lang = lang; - - pango_context_set_language(pango_layout_get_context(_layout), pango_language_from_string(lang.c_str())); } /* @@ -2512,6 +2510,9 @@ Context2d::paintText(const Napi::CallbackInfo& info, bool stroke) { checkFonts(); pango_layout_set_text(layout, str.c_str(), -1); + if (state->lang != "") { + pango_context_set_language(pango_layout_get_context(_layout), pango_language_from_string(state->lang.c_str())); + } pango_cairo_update_layout(context(), layout); PangoDirection pango_dir = state->direction == "ltr" ? PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL; @@ -2824,6 +2825,9 @@ Context2d::MeasureText(const Napi::CallbackInfo& info) { checkFonts(); pango_layout_set_text(layout, str.Utf8Value().c_str(), -1); + if (state->lang != "") { + pango_context_set_language(pango_layout_get_context(_layout), pango_language_from_string(state->lang.c_str())); + } pango_cairo_update_layout(ctx, layout); // Normally you could use pango_layout_get_pixel_extents and be done, or use diff --git a/src/CanvasRenderingContext2d.h b/src/CanvasRenderingContext2d.h index b96a70f0a..1d9548895 100644 --- a/src/CanvasRenderingContext2d.h +++ b/src/CanvasRenderingContext2d.h @@ -62,6 +62,7 @@ struct canvas_state_t { fontDescription = pango_font_description_copy(other.fontDescription); font = other.font; imageSmoothingEnabled = other.imageSmoothingEnabled; + lang = other.lang; } ~canvas_state_t() { diff --git a/test/canvas.test.js b/test/canvas.test.js index 59bd5f564..4655c31c7 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -486,24 +486,6 @@ describe('Canvas', function () { assert.equal(ctx.font, '15px Arial, sans-serif') }) - it('Context2d#lang=', function () { - const canvas1 = createCanvas(200, 200) - const ctx1 = canvas1.getContext('2d') - assert.equal(ctx1.lang, '') - ctx1.font = '20px sans-serif'; - ctx1.lang = 'ja' - assert.equal(ctx1.lang, 'ja') - ctx1.fillText('门', 10, 10) - - const canvas2 = createCanvas(200, 200) - const ctx2 = canvas2.getContext('2d') - ctx2.font = '20px sans-serif'; - ctx2.lang = 'zh' - ctx2.fillText('门', 10, 10) - - assert.notDeepEqual(ctx1.getImageData(10, 10, 40, 40), ctx2.getImageData(10, 10, 40, 40)) - }) - it('Context2d#lineWidth=', function () { const canvas = createCanvas(200, 200) const ctx = canvas.getContext('2d') @@ -2508,7 +2490,8 @@ describe('Canvas', function () { ['patternQuality', 'best'], // ['quality', 'best'], // doesn't do anything, TODO remove ['textDrawingMode', 'glyph'], - ['antialias', 'gray'] + ['antialias', 'gray'], + ['lang', 'eu'] ] for (const [k, v] of state) {