diff --git a/CHANGELOG.md b/CHANGELOG.md index ed1b43c00..8aa4a39e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ project adheres to [Semantic Versioning](http://semver.org/). ### Added * Support for accessibility and links in PDFs +* `ctx.direction` is implemented: `'rtl'` or `'ltr'` set the base direction of text +* `ctx.textAlign` `'start'` and `'end'` are now `'right'` and `'left'` when `ctx.direction === 'rtl'` ### Fixed * Fix a crash in `getImageData` when the rectangle is entirely outside the canvas. ([#2024](https://github.com/Automattic/node-canvas/issues/2024)) diff --git a/index.d.ts b/index.d.ts index 4cce92c3e..43ff107d0 100644 --- a/index.d.ts +++ b/index.d.ts @@ -290,6 +290,7 @@ export class CanvasRenderingContext2D { textBaseline: CanvasTextBaseline; textAlign: CanvasTextAlign; canvas: Canvas; + direction: 'ltr' | 'rtl'; } export class CanvasGradient { diff --git a/src/Canvas.h b/src/Canvas.h index 5b039539a..eb19843e5 100644 --- a/src/Canvas.h +++ b/src/Canvas.h @@ -37,7 +37,6 @@ enum text_align_t : int8_t { TEXT_ALIGNMENT_LEFT = -1, TEXT_ALIGNMENT_CENTER = 0, TEXT_ALIGNMENT_RIGHT = 1, - // Currently same as LEFT and RIGHT without RTL support: TEXT_ALIGNMENT_START = -2, TEXT_ALIGNMENT_END = 2 }; diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index dfbcc17a0..d294a4b35 100644 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -161,7 +161,8 @@ Context2d::Initialize(Napi::Env& env, Napi::Object& exports) { InstanceAccessor<&Context2d::GetStrokeStyle, &Context2d::SetStrokeStyle>("strokeStyle", napi_default_jsproperty), 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::GetTextAlign, &Context2d::SetTextAlign>("textAlign", napi_default_jsproperty), + InstanceAccessor<&Context2d::GetDirection, &Context2d::SetDirection>("direction", napi_default_jsproperty) }); exports.Set("CanvasRenderingContext2d", ctor); @@ -230,6 +231,8 @@ Context2d::Context2d(const Napi::CallbackInfo& info) : Napi::ObjectWrapfontDescription); @@ -762,6 +765,27 @@ Context2d::AddPage(const Napi::CallbackInfo& info) { cairo_pdf_surface_set_size(canvas()->surface(), width, height); } +/* + * Get text direction. + */ +Napi::Value +Context2d::GetDirection(const Napi::CallbackInfo& info) { + return Napi::String::New(env, state->direction); +} + +/* + * Set text direction. + */ +void +Context2d::SetDirection(const Napi::CallbackInfo& info, const Napi::Value& value) { + if (!value.IsString()) return; + + std::string dir = value.As(); + if (dir != "ltr" && dir != "rtl") return; + + state->direction = dir; +} + /* * Put image data. * @@ -2451,6 +2475,9 @@ Context2d::paintText(const Napi::CallbackInfo&info, bool stroke) { pango_layout_set_text(layout, str.c_str(), -1); pango_cairo_update_layout(context(), layout); + PangoDirection pango_dir = state->direction == "ltr" ? PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL; + pango_context_set_base_dir(pango_layout_get_context(_layout), pango_dir); + if (argsNum == 3) { scaled_by = get_text_scale(layout, args[2]); cairo_save(context()); @@ -2522,18 +2549,26 @@ inline double getBaselineAdjustment(PangoLayout* layout, short baseline) { void Context2d::setTextPath(double x, double y) { PangoRectangle logical_rect; + text_align_t alignment = state->textAlignment; - switch (state->textAlignment) { + // Convert start/end to left/right based on direction + if (alignment == TEXT_ALIGNMENT_START) { + alignment = (state->direction == "rtl") ? TEXT_ALIGNMENT_RIGHT : TEXT_ALIGNMENT_LEFT; + } else if (alignment == TEXT_ALIGNMENT_END) { + alignment = (state->direction == "rtl") ? TEXT_ALIGNMENT_LEFT : TEXT_ALIGNMENT_RIGHT; + } + + switch (alignment) { case TEXT_ALIGNMENT_CENTER: pango_layout_get_pixel_extents(_layout, NULL, &logical_rect); x -= logical_rect.width / 2; break; - case TEXT_ALIGNMENT_END: case TEXT_ALIGNMENT_RIGHT: pango_layout_get_pixel_extents(_layout, NULL, &logical_rect); x -= logical_rect.width; break; - default: ; + default: // TEXT_ALIGNMENT_LEFT + break; } y -= getBaselineAdjustment(_layout, state->textBaseline); @@ -2687,13 +2722,12 @@ Napi::Value Context2d::GetTextAlign(const Napi::CallbackInfo& info) { const char* align; switch (state->textAlignment) { - default: - // TODO the default is supposed to be "start" case TEXT_ALIGNMENT_LEFT: align = "left"; break; case TEXT_ALIGNMENT_START: align = "start"; break; case TEXT_ALIGNMENT_CENTER: align = "center"; break; case TEXT_ALIGNMENT_RIGHT: align = "right"; break; case TEXT_ALIGNMENT_END: align = "end"; break; + default: align = "start"; } return Napi::String::New(env, align); } diff --git a/src/CanvasRenderingContext2d.h b/src/CanvasRenderingContext2d.h index a78788451..16cd42d34 100644 --- a/src/CanvasRenderingContext2d.h +++ b/src/CanvasRenderingContext2d.h @@ -31,10 +31,11 @@ struct canvas_state_t { cairo_filter_t patternQuality = CAIRO_FILTER_GOOD; float globalAlpha = 1.f; int shadowBlur = 0; - text_align_t textAlignment = TEXT_ALIGNMENT_LEFT; // TODO default is supposed to be START + text_align_t textAlignment = TEXT_ALIGNMENT_START; text_baseline_t textBaseline = TEXT_BASELINE_ALPHABETIC; canvas_draw_mode_t textDrawingMode = TEXT_DRAW_PATHS; bool imageSmoothingEnabled = true; + std::string direction = "ltr"; canvas_state_t() { fontDescription = pango_font_description_from_string("sans"); @@ -182,6 +183,8 @@ class Context2d : public Napi::ObjectWrap { void BeginTag(const Napi::CallbackInfo& info); void EndTag(const Napi::CallbackInfo& info); #endif + Napi::Value GetDirection(const Napi::CallbackInfo& info); + void SetDirection(const Napi::CallbackInfo& info, const Napi::Value& value); inline void setContext(cairo_t *ctx) { _context = ctx; } inline cairo_t *context(){ return _context; } inline Canvas *canvas(){ return _canvas; } diff --git a/test/canvas.test.js b/test/canvas.test.js index 48abb57b8..d33bda63f 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -569,8 +569,6 @@ describe('Canvas', function () { const canvas = createCanvas(200, 200) const ctx = canvas.getContext('2d') - assert.equal('left', ctx.textAlign) // default TODO wrong default - ctx.textAlign = 'start' assert.equal('start', ctx.textAlign) ctx.textAlign = 'center' assert.equal('center', ctx.textAlign)