Skip to content

Set pango language through ctx.lang #2526

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

Merged
merged 5 commits into from
Aug 17, 2025
Merged
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 @@ -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
Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ export class CanvasRenderingContext2D {
textAlign: CanvasTextAlign;
canvas: Canvas;
direction: 'ltr' | 'rtl';
lang: string;
}

export class CanvasGradient {
Expand Down
30 changes: 28 additions & 2 deletions src/CanvasRenderingContext2d.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -786,6 +787,25 @@ 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);
Copy link

Choose a reason for hiding this comment

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

Should this call pango_context_get_language() for consistency with PANGO_LAYOUT_GET_METRICS? This would avoid the need to maintain lang as parallel state in canvas_state_t.

pango_context_get_language(pango_layout_get_context(LAYOUT)))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

makes sense, done ✅

Copy link
Collaborator

Choose a reason for hiding this comment

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

With the second commit, I don't think ctx.save() and ctx.restore() will update ctx.lang, will it? It needs to be on the canvas_state_t.

Copy link

Choose a reason for hiding this comment

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

In that case, should PANGO_LAYOUT_GET_METRICS use the lang field instead of pango_context_get_language()?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The layout should be up-to-date there, but good call looking at that. I'll start a review with my thoughts...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

maybe the better way to get the language before it is lazily committed?

if (state->lang == "") { // unchanged
  pango_context_get_language()
} else {
  pango_language_from_string(state->lang)
}

}

/*
* Set language.
*/
void
Context2d::SetLanguage(const Napi::CallbackInfo& info, const Napi::Value& value) {
if (!value.IsString()) return;

std::string lang = value.As<Napi::String>();
state->lang = lang;
}

/*
* Put image data.
*
Expand Down Expand Up @@ -2490,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;
Expand Down Expand Up @@ -2802,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
Expand Down
4 changes: 4 additions & 0 deletions src/CanvasRenderingContext2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -61,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() {
Expand Down Expand Up @@ -157,6 +159,7 @@ class Context2d : public Napi::ObjectWrap<Context2d> {
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);
Expand All @@ -179,6 +182,7 @@ class Context2d : public Napi::ObjectWrap<Context2d> {
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);
Expand Down
3 changes: 2 additions & 1 deletion test/canvas.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2490,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) {
Expand Down