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:
javascriptconst 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
translate(canvas.width, 0)
— Moves the origin from the left edge to the right
edge.
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:
javascriptctx.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?
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.
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.
Trim invisible Unicode marks
Make sure the string doesn’t contain
\u200F
,
\u200E
,
\u202A
,
etc.
jstext = text.replace(/[\u200E\u200F\u202A-\u202E]/g, '');
Use
direction = 'rtl'
in the context
jsctx.direction = 'rtl'; ctx.textAlign = 'right';
This ensures the layout engine treats the text as RTL from the start.
Measure and adjust manually
jsconst metrics = ctx.measureText(text); ctx.fillText(text, x + metrics.actualBoundingBoxRight, y);
This aligns using the actual painted right side, not the theoretical one.
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.
Yes —
direction
and
transforming the origin to the top-right are
totally different concepts
in
<canvas>
drawing.
Here’s the distinction:
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:
jsctx.direction = 'rtl'; ctx.textAlign = 'right'; ctx.fillText('سلام', 200, 50); // Aligns from the right as an RTL string
A
coordinate system transformation
that changes where
(0,0)
is
and how X/Y grow.
Example:
jsctx.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.
Not exactly — and this is where the subtlety comes in.
When you set:
jsctx.direction = 'rtl'; ctx.textAlign = 'right';
and then do:
jsctx.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:
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.
Invisible characters
—
If your string starts/ends with a zero-width mark,
non-breaking space, etc., the alignment point may
shift.
Contextual shaping
—
Persian letters change shape depending on position, so
the “first character” visually might extend past the
theoretical alignment point.
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.
You can measure the text and compensate:
jsfunction 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.
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.
When you run:
jsconst 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
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:
jsctx.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);
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
.
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
.
@font-face
(Recommended)
Add the font in your CSS:
css@font-face { font-family: 'MyPersianFont'; src: url('/fonts/MyPersianFont.woff2') format('woff2'); }
In your JavaScript, wait until the font has loaded before drawing:
jsconst canvas = document.getElementById("myCanvas"); const ctx = canvas.getContext("2d"); document.fonts.load('48px MyPersianFont').then(() => { ctx.font = "48px MyPersianFont"; ctx.fillText("سلام دنیا", 150, 100); });
FontFace
API (JS-only)
Useful if you want to load a font dynamically without touching CSS.
jsconst 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); });
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.
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";
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>
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:
jsctx.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.