Canvas RTL support

Canvas set x and y from top right instead of top left

In HTML <canvas>, the coordinate system is always by default:

  • (0,0) at the top-left corner

  • x increases to the right

  • y increases downward

If you want (0,0) to be the top-right corner instead, you need to transform the coordinate system with the canvas context’s transformation methods.

Here’s how you can do it:

javascript
const canvas = document.getElementById("myCanvas"); const ctx = canvas.getContext("2d"); // Move origin to top-right ctx.translate(canvas.width, 0); // Flip X-axis so it goes left instead of right ctx.scale(-1, 1); // Now (0,0) is top-right and X increases to the left ctx.fillStyle = "red"; ctx.fillRect(0, 0, 100, 50); // Draws a rectangle starting from top-right

How it works

  1. translate(canvas.width, 0) — Moves the origin from the left edge to the right edge.

  2. scale(-1, 1) — Flips the X-axis horizontally so positive X goes left.

You can still draw normally, but you’ll think of your X values as distances from the right edge now.

If you also wanted Y to increase upward (origin in bottom-right), you’d add another transformation:

javascript
ctx.translate(canvas.width, canvas.height); ctx.scale(-1, -1);

Do you want this transformation applied only temporarily for certain drawings, or should I show you a way to switch back and forth between top-left and top-right origins easily?

When drawing some persian text on canvas even though textAlign is set to right the position of the beginning character is not the same why is that?

Ah, yes — this is a common “gotcha” when drawing Persian (or Arabic) text on a <canvas> with textAlign = 'right'.

Even though Persian is right-to-left, canvas text rendering still bases its alignment on the entire text block width, not the actual glyph starting point.
This means:

  • ctx.textAlign = 'right' makes the end of the text line up with your given x coordinate.

  • The first visible character (on the right in Persian) may appear shifted if:

    • The font has extra padding/kerning on the right side.

    • The renderer includes invisible Unicode characters (like the RTL mark \u200F or zero-width joiners).

    • Certain characters have contextual shapes that start with a small offset.

Why this happens

In Persian (and Arabic):

  • Characters change shape depending on position in the word.

  • Some characters (like "ا" or "ب") have overhangs — parts that extend beyond the main bounding box.

  • Canvas measures and aligns based on font metrics, not glyph pixels, so the actual painted rightmost pixel may not exactly be at your alignment point.

How to fix it

  1. Trim invisible Unicode marks
    Make sure the string doesn’t contain \u200F, \u200E, \u202A, etc.

    js
    text = text.replace(/[\u200E\u200F\u202A-\u202E]/g, '');
  2. Use direction = 'rtl' in the context

    js
    ctx.direction = 'rtl'; ctx.textAlign = 'right';

    This ensures the layout engine treats the text as RTL from the start.

  3. Measure and adjust manually

    js
    const metrics = ctx.measureText(text); ctx.fillText(text, x + metrics.actualBoundingBoxRight, y);

    This aligns using the actual painted right side, not the theoretical one.

  4. Use a Persian-friendly font
    Some fonts have better bounding boxes for RTL scripts; web-safe Persian fonts or custom .ttf can help.


If you want, I can give you a small helper function that draws Persian text pixel-perfect aligned from the right edge — which will completely bypass the bounding box mismatch problem. That would make it look identical regardless of font quirks.

Is direction different than transforming the origin to top right?

Yes — direction and transforming the origin to the top-right are totally different concepts in <canvas> drawing.

Here’s the distinction:


1. ctx.direction

  • A text layout property that only affects how fillText / strokeText handle bidirectional scripts (like Persian, Arabic, Hebrew).

  • Possible values:

    • "ltr" → Treats text as left-to-right.

    • "rtl" → Treats text as right-to-left.

    • "inherit" → Uses the <canvas> element’s CSS direction.

  • Only influences the flow and alignment of text.

  • Doesn’t move or flip the entire coordinate system — X still grows to the right, Y still grows down.

Example:

js
ctx.direction = 'rtl'; ctx.textAlign = 'right'; ctx.fillText('سلام', 200, 50); // Aligns from the right as an RTL string

2. Transforming the origin to the top-right

  • A coordinate system transformation that changes where (0,0) is and how X/Y grow.

  • Example:

    js
    ctx.translate(canvas.width, 0); ctx.scale(-1, 1);

    Now:

    • (0,0) is top-right.

    • X grows to the left instead of the right.

  • Affects everything you draw — shapes, images, text, etc.


Key difference:

  • direction → tells the text engine how to arrange characters in a given string.

  • Transforming the origin → changes the entire canvas coordinate system for all drawing operations.


If your issue is that Persian text doesn’t start exactly where you expect, changing the origin won’t fix it — you’d want to tweak direction, clean invisible marks, or measure with ctx.measureText.

If you want, I can show you how to combine direction: 'rtl' with a manual bounding-box alignment so your Persian text pixel-perfectly starts at a fixed X coordinate.

When direction is set to rtl the first character is my x?

Not exactly — and this is where the subtlety comes in.

When you set:

js
ctx.direction = 'rtl'; ctx.textAlign = 'right';

and then do:

js
ctx.fillText("سلام", x, y);

the rightmost painted pixel of the text should be very close to your x coordinate.
But it’s not guaranteed to be exactly the first visible character because:

  1. Font metrics vs. actual pixels
    Canvas aligns based on the font’s reported bounding box, which can include padding or overhang beyond the visible letters.

  2. Invisible characters
    If your string starts/ends with a zero-width mark, non-breaking space, etc., the alignment point may shift.

  3. Contextual shaping
    Persian letters change shape depending on position, so the “first character” visually might extend past the theoretical alignment point.


A visual way to think about it:

If textAlign = 'right' and direction = 'rtl':

| ← x coordinate س ل ا م

Your x coordinate is the intended right edge of the whole string.
In a perfect world, that means the start of س is at x.
In reality, س might overhang to the right by a few pixels or have spacing that makes it not perfectly flush.


How to ensure first visible pixel = x

You can measure the text and compensate:

js
function drawPersianText(ctx, text, x, y) { ctx.direction = 'rtl'; ctx.textAlign = 'right'; const metrics = ctx.measureText(text); const offset = metrics.actualBoundingBoxRight; ctx.fillText(text, x + offset, y); }

Here, metrics.actualBoundingBoxRight accounts for overhang so that your given x really matches the first painted pixel.


If you like, I can show you a debug snippet that draws a guide line at x so you can see how far off your first Persian character is.
That makes this behavior really obvious.

What is actualBoungdingBoxRight

actualBoundingBoxRight is one of the extra measurements you get from ctx.measureText(text) in HTML canvas.

It represents:

The distance in pixels from the text’s alignment point to the rightmost painted pixel of the glyphs.


How it fits in

When you run:

js
const metrics = ctx.measureText("سلام"); console.log(metrics);

you get an object with properties like:

  • width → the advance width of the text (how far the cursor would move if you typed it)

  • actualBoundingBoxLeft → pixels from the alignment point to the leftmost painted pixel

  • actualBoundingBoxRight → pixels from the alignment point to the rightmost painted pixel

  • actualBoundingBoxAscent → height above baseline

  • actualBoundingBoxDescent → height below baseline


In RTL text

If direction = 'rtl' and textAlign = 'right':

  • Your x is considered the right alignment point.

  • actualBoundingBoxRight will usually be a small positive number if letters overhang to the right of that point, or 0 if they don’t.

Example:

js
ctx.direction = 'rtl'; ctx.textAlign = 'right'; ctx.font = "48px sans-serif"; const metrics = ctx.measureText("سلام"); console.log(metrics.actualBoundingBoxRight); // e.g. 2.5 pixels // If you want the first visible pixel exactly at x: ctx.fillText("سلام", 200 + metrics.actualBoundingBoxRight, 100);

Why it matters for Persian

Some Persian letters (like "س") have curves that extend to the right of the baseline point the browser thinks is the “end” of the text block.
actualBoundingBoxRight lets you measure and compensate for that so your x really matches the first visible pixel.


If you want, I can make you a small visual demo where we draw a vertical line at x so you can see the difference before and after using actualBoundingBoxRight.

How to load a font to use on canvas?

You can load a custom font for <canvas> text in two main ways — just like you would for normal HTML text — and then use it in ctx.font.


1️⃣ Using CSS @font-face (Recommended)

  1. Add the font in your CSS:

    css
    @font-face { font-family: 'MyPersianFont'; src: url('/fonts/MyPersianFont.woff2') format('woff2'); }
  2. In your JavaScript, wait until the font has loaded before drawing:

    js
    const canvas = document.getElementById("myCanvas"); const ctx = canvas.getContext("2d"); document.fonts.load('48px MyPersianFont').then(() => { ctx.font = "48px MyPersianFont"; ctx.fillText("سلام دنیا", 150, 100); });

2️⃣ Using the FontFace API (JS-only)

Useful if you want to load a font dynamically without touching CSS.

js
const font = new FontFace('MyPersianFont', 'url(/fonts/MyPersianFont.woff2)'); font.load().then((loadedFont) => { document.fonts.add(loadedFont); const canvas = document.getElementById("myCanvas"); const ctx = canvas.getContext("2d"); ctx.font = "48px MyPersianFont"; ctx.fillText("سلام دنیا", 150, 100); });

Important Notes for Canvas

  • Always wait for the font to load before drawing. If you don’t, canvas will render with a fallback font and won’t automatically update when the font is ready.

  • For Persian/Arabic, choose a font that supports all the characters — otherwise letters may render broken or in a fallback font.

  • You can use Google Fonts (many Persian fonts are available) with either CSS <link> or the FontFace API.


If you want, I can give you an HTML + Canvas Persian text example using a Google Fonts Persian typeface like Vazirmatn, so you can copy-paste and test instantly.

How to then make it bold?

Once the font is loaded, you make text bold in canvas the same way you would in CSS — by setting the ctx.font string to include a weight.

The format is:

[font-style] [font-variant] [font-weight] [font-size] [font-family]

At minimum, you want:

ctx.font = "bold 48px MyPersianFont";

Example with a loaded font

html
<canvas id="myCanvas" width="400" height="200"></canvas> <script> // Load the font const font = new FontFace('MyPersianFont', 'url(/fonts/MyPersianFont.woff2)'); font.load().then((loadedFont) => { document.fonts.add(loadedFont); const canvas = document.getElementById("myCanvas"); const ctx = canvas.getContext("2d"); // Bold text ctx.font = "bold 48px MyPersianFont"; ctx.fillText("سلام دنیا", 200, 100); }); </script>

Important for bold:

  • If the font file does not include a bold weight, the browser will simulate bold by thickening strokes. This can look okay, but for Persian text you’ll get better results with an actual bold font file.

  • If your font family has multiple weights (e.g., Regular, Bold), you can load each with @font-face or FontFace and give them distinct names:

    css
    @font-face { font-family: 'MyPersianFontBold'; font-weight: bold; src: url('/fonts/MyPersianFont-Bold.woff2') format('woff2'); }

    Then in canvas:

    js
    ctx.font = "48px MyPersianFontBold";

If you’d like, I can show you how to load both regular and bold Persian fonts and switch between them in the same canvas drawing, so you get true bold rather than fake thickening.