9

I am trying to use the fillText() method on an HTML5 Canvas' 2d Context to draw a string written in Arabic. It works perfectly, until I put a punctuation mark at the end of the string. Then the punctuation mark appears on the wrong side of the string (at the beginning, rather than the end, as if it were a ltr not rtl string). I played with the Context.textAlign property, but that seems to concern only the way the string is drawn relative to the specified position, not the actual direction of the text. Does anyone know of a solution to this?

Thanks.

Update: The answer I found is to add a "dir" attribute to the canvas element on the page. For example,

<canvas dir="rtl">

However, I still don't know how to change the dir attribute for individual strings sent to fillText. Any ideas?

user1145886
  • 171
  • 2
  • 6
  • Sorry for jumping the gun. I found the solution in the spec: http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality Apparently, one must set the "dir" element on the canvas to "rtl", ie, "". – user1145886 Jan 22 '12 at 14:12
  • Please add your solution as an answer. Thanks! – Adam Lear Jan 23 '12 at 05:20
  • what font do you use? What is the name of the font? – Zelter Ady Apr 03 '12 at 12:32

4 Answers4

10

You don't need to set the direction for each individual string. See the following example which also shows the proper use of implicit bidi control marks for proper display order:

<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>

  <body>
    <canvas id="myCanvas" width="700" dir="rtl" height="250" style="border:1px solid #d3d3d3;">
      Your browser does not support the HTML5 canvas tag.
    </canvas>

    <script type="text/javascript" charset="utf-8">
      var c = document.getElementById("myCanvas");
      var cArabic = c.getContext("2d");
      cArabic.font="25px Arial";

      // Simple Sentence with punctuation.
      var str1 = "این یک آزمایش است.";
      // Few sentences with punctuation and numerals. 
      var str2 = "۱ آزمایش. 2 آزمایش، سه آزمایش & Foo آزمایش!";
      // Needs implicit bidi marks to display correctly.
      var str3 = "آزمایش برای Foo Ltd. و Bar Inc. باشد که آزموده شود.";
      // Implicit bidi marks added; "Foo Ltd.&lrm; و Bar Inc.&lrm;"
      var str4 = "آزمایش برای Foo Ltd.‎ و Bar Inc.‎ باشد که آزموده شود.";

      cArabic.fillText(str1, 600, 60);
      cArabic.fillText(str2, 600, 100);
      cArabic.fillText(str3, 600, 140);
      cArabic.fillText(str4, 600, 180);
    </script>

  </body>
</html>

And here is the output: as rendered by Chrome <code>26.0.1410.64 m</code> on MS Windows XP SP3

Shervin
  • 1,936
  • 17
  • 27
  • Would you happen to know how to connect Arabic letters if you're drawing them one at a time in canvas? – sq1020 Oct 01 '13 at 19:05
  • This should be transparent to the developer. Text layout engine should take care of it. Do you have any specific scenario where it doesn't work? – Shervin Oct 01 '13 at 19:12
  • The approach I'm following is similar to this: http://stackoverflow.com/questions/7665342/can-i-do-by-character-text-color-in-html5-canvas -- Conceptually, the first solution works well but when using Arabic letters, they do not connect in this case because you're invoking context.fillText once for each letter instead of the entire string. – sq1020 Oct 01 '13 at 20:31
  • I see. That workaround doesn't conceptually work for joining scripts as a whole (Arabic being one). If it's immensely crucial, you can imitate the joining behaviour and color every joining run of letter forms separately in one color (which is kind of re-inventing some part of a text rendering engine). – Shervin Oct 01 '13 at 21:56
4

The problem is that the '.' character is 'directional neutral character', it means that it get the direction from the next strong directional character, if there is no character afterwards, it get it from the container. In your case it is the latter one. One solution, as already found, is to make the container right-to-left. But there are still two other solutions :

1- Put another invisible strong right-to-left directional character after the '.'. Fortunately the unicode has one special character for that which is "right-to-left mark" (U+200F)

2- Put the "right-to-left override character" (U+202E) before the text, so after this mark any neutral character get the right-to-left direction. You can end the override by adding "pop directional formatting" character (U+202C).

I changed the @shervin's sample code to demonstrate it.

<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>

  <body>
    <canvas id="myCanvas" width="700" height="250" style="border:1px solid #d3d3d3;">
      Your browser does not support the HTML5 canvas tag.
    </canvas>

    <script type="text/javascript" charset="utf-8">
      var c = document.getElementById("myCanvas");
      var cArabic = c.getContext("2d");
      cArabic.font="20px Arial";

      //option 1 : adding right-to-left mark at the end to put the '.' between two strong right to left charachters, so it will treated as right to left.
      var str1 = "این یک آزمایش است." + "\u200F";

      //option 2 : adding right-to-left override at the begining of the text so every neutral charachter afterward becomes righ to left.
      var str2;
      str2 = "\u202E";
      str2+= "این یک آزمایش است.";
      //           you might want to finish the override by 'pop directional formatting' charachter
      str2 += "\u202C";

      cArabic.fillText(str1, 200, 30);
      cArabic.fillText(str2, 200, 70);
    </script>

  </body>
</html>

Result image

There are other solutions. You can find them in unicode specification in this page. They are, "direction isolation" (U+2067) which has rendering problem in Chrome. And "direction embedding" with U+202B, which is similar to direction override.

karianpour
  • 704
  • 5
  • 15
3

Setting the dir option is for the whole canvas - for individual strings that behave badly, you might consider manually adding an RTL marker (U+200F) after the quote.

Ansari
  • 8,168
  • 2
  • 23
  • 34
0

You can do it with that:
ctx.textAlign="end";
or
ctx.textAlign="right"

And to make it again left-to-right:
ctx.textAlign="start";

mansim
  • 727
  • 7
  • 21