Skip to content

Commit

Permalink
support ctx.direction and textAlign start/end
Browse files Browse the repository at this point in the history
  • Loading branch information
chearon committed Jan 15, 2025
1 parent 0b2edc1 commit a8d549c
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ export class CanvasRenderingContext2D {
textBaseline: CanvasTextBaseline;
textAlign: CanvasTextAlign;
canvas: Canvas;
direction: 'ltr' | 'rtl';
}

export class CanvasGradient {
Expand Down
1 change: 0 additions & 1 deletion src/Canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down
46 changes: 40 additions & 6 deletions src/CanvasRenderingContext2d.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -230,6 +231,8 @@ Context2d::Context2d(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Context2
pango_context_set_round_glyph_positions(pango_layout_get_context(_layout), FALSE);
#endif

pango_layout_set_auto_dir(_layout, FALSE);

states.emplace();
state = &states.top();
pango_layout_set_font_description(_layout, state->fontDescription);
Expand Down Expand Up @@ -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<Napi::String>();
if (dir != "ltr" && dir != "rtl") return;

state->direction = dir;
}

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

0 comments on commit a8d549c

Please sign in to comment.