Html2canvas: Right to Left Languages (like Arabic).

Created on 1 Oct 2015  路  19Comments  路  Source: niklasvh/html2canvas

Words in (RTL Languages) are improperly displayed on the Canvas).

Unicode

Most helpful comment

I had the same problem on 0.5.0-alpha1 which was solved by replacing !hasUnicode by hasUnicode .
Thanks.

All 19 comments

In NodeParser.prototype.paintText = function(container) {
I commented the !hasUnicode, so the problem was solved and the arabic characters appear as continous.
the main problem in arabic was that, textList was set as splitted characters instead of splitted words.

var textList = (!this.options.letterRendering || noLetterSpacing(container))
//&& !hasUnicode(container.node.data)
? getWords(characters)
: characters.map(function (character) {
return punycode.ucs2.encode([character]);
});

I had the same problem on 0.5.0-alpha1 which was solved by replacing !hasUnicode by hasUnicode .
Thanks.

@oorabi thanks, fixed the problem by removing "!hasUnicode" on line 2321, html2canvas 0.5.0-alpha1

Just to share some of my findings.

As oorabi already mentioned the problem with Arabic (and some other languages) is that they have logical (char-by-char) presentation and visual presentation (where following chars may effect to the presentation of previous chars). This issue can be solved by removing the check !hasUnicode, since that forced anything that contains non ASCII chars to be handled char by char.

But that is not the whole problem, since they do mix ASCII with the Arabic, for example colons, brackets etc. But those should be presented at the left side of the word that they are following. In order to do this we need to split the ASCII chars to own words. I did this by checking if the script category changes (i didn't care about others than Arabic for now).

    var previousScriptCategory = null;

    while(characters.length) {
        if(previousScriptCategory == null) {
            previousScriptCategory = getUtf8ScriptCategory(characters[i]);
        }
        if (isWordBoundary(characters[i]) === onWordBoundary || getUtf8ScriptCategory(characters[i]) !== previousScriptCategory) {
            word = characters.splice(0, i);
            if (word.length) {
                words.push(window.html2canvas.punycode.ucs2.encode(word));
            }
            onWordBoundary =! onWordBoundary;
            i = 0;
            previousScriptCategory = null;
        } else {
            i++;
        }
        ...
function getUtf8ScriptCategory(char) {
    if(char >= 0x0600 && char <= 0x06ff) {
        return "arabic";
    } else {
        return "default"
    }
}

But it doesn't end there. We still have problem of brackets, sure the previous splits them to separate words, but we are still rendering from right to left so they get rendered the wrong way. So my take was to make another hack for them.

    //If right-to-left, we should swap brackets
    for(var j = 0; j < words.length; j++) {
        for(var k = 0; k < words[j].length; k++) {
            if(words[j][k] == "(" && isRtl(words[j+1])) words[j] = words[j].replaceAt(k,")")
            else if(words[j][k] == ")" && isRtl(words[j-1])) words[j] = words[j].replaceAt(k,"(")
            else if(words[j][k] == "[" && isRtl(words[j+1])) words[j] = words[j].replaceAt(k,"]")
            else if(words[j][k] == "]" && isRtl(words[j-1])) words[j] = words[j].replaceAt(k,"[")
            else if(words[j][k] == "{" && isRtl(words[j+1])) words[j] = words[j].replaceAt(k,"}")
            else if(words[j][k] == "}" && isRtl(words[j-1])) words[j] = words[j].replaceAt(k,"{")
        }
    }
    return words;
function isRtl(string) {
    if((/[\u0600-\u06ff]/).test(string)) { //Arabic
        return true;
    }
    return false;
}

This is really a hacky way around the issue, but I hope someone can propose more proper way to handle these issues.

+1 Same issue here. Please fix this

n-a-g-r-o-m's solutions is very good, but I had to make some modifications to work on my project.

// If right-to-left, we should swap brackets
for(var j = 0; j < words.length; j++) {
        for(var k = 0; k < words[j].length; k++) {
            if     (words[j][k] == "(" /*&& isRtl(words[j+1])*/) words[j] = replaceAtIndex(words[j],k,")")
            else if(words[j][k] == ")" /*&& isRtl(words[j-1])*/) words[j] = replaceAtIndex(words[j],k,"(")
            else if(words[j][k] == "[" /*&& isRtl(words[j+1])*/) words[j] = replaceAtIndex(words[j],k,"]")
            else if(words[j][k] == "]" /*&& isRtl(words[j-1])*/) words[j] = replaceAtIndex(words[j],k,"[")
            else if(words[j][k] == "{" /*&& isRtl(words[j+1])*/) words[j] = replaceAtIndex(words[j],k,"}")
            else if(words[j][k] == "}" /*&& isRtl(words[j-1])*/) words[j] = replaceAtIndex(words[j],k,"{")
        }
    }
function replaceAtIndex(s, index, char) {
    s = s.substr(0, index) + char + s.substr(index + 1);
    return s;
}

@vali-mint I am using 0.4.1 and having the same issue of brackets & Commas, I tried the code above but i cant make it done. can you tell me where exactly i need to put this code?

Hi @n-a-g-r-o-m
Do you have a solution on the latest version v1.0.0-alpha.12?
I'm struggeling from a while
Many thanks
Regards

@hadihashem Sorry I drop using this long time ago, so unfortunately I can't help.

same problem with me Arabic language :( any help please

@hadihashem Do you have a solution ?

@oorabi did you find solution ?

I have a text overlapping problem when Arabic content is set to RTL. I notice this issue has been opened for 4 years. Any update on this?

@EdiWang did you find any solution?
all the solutions I found are for previous versions which have problems in other ways :confused:

@abdelaziz321 unfortunately, no. Currently, I have to let my users for Arabic content use screenshot extension like FireShot to work around the issue, this is very awkward.

I really don't know why this worked, but It did when using these dependencies together:

"dependencies": {
    "html2canvas": "^1.0.0-alpha.12",
    "js-html2pdf": "^1.1.4",
    "jspdf": "^1.5.3"
}

and in one of my components:

<template>
  <v-card>
    <v-toolbar dark color="accent" dense flat>
      <v-toolbar-title class="white--text">胤亘丕毓丞 丕賱賰丕乇賳賷賴</v-toolbar-title>
    </v-toolbar>

    <v-card-text class="pa-4">
      <div id="card-to-be-printed" style="position: relative; width: 9cm">
        <div class="mb-5 pb-5">
          <img src="@/assets/images/card/front.png" alt="front card" style="width: 100%">
          <p
            class="student-name"
          >{{ student.name }}</p>

          <barcode
            class="student-barcode"
            :value="student.barcode"
            format="CODE128"
          >禺胤兀 賮賷 丕賱亘丕乇賰賵丿</barcode>
        </div>
        <img src="@/assets/images/card/back.png" alt="back card" style="width: 100%">
      </div>
    </v-card-text>

    <v-card-actions style="background-color: #f7f7f7">
      <v-spacer></v-spacer>
      <v-btn color="accent" class="white--text" elevation="1" @click.native="download(student)">鬲丨賲賷賱 pdf</v-btn>
    </v-card-actions>
  </v-card>
</template>


<script>
import Html2Pdf from "js-html2pdf";

export default {
  name: 'PrintCardDialog',

  methods: {
    download(student) {
      const card = document.querySelector('#card-to-be-printed');

      card.style.fontFamily = 'Cairo';

      const options = {
        margin: [15, 20, 15, 20],
        filename: `student_${student.id}.pdf`,
        image: { type: 'png' },
        html2canvas: { logging: false },
        jsPDF: { orientation: 'p' }
      };

      let exporter = new Html2Pdf(card, options);

      exporter.getPdf(true).then(pdf => {
          this.$emit('success', student);
      });      
    }
  }
};
</script>

and here is the result

Screenshot from 2019-11-12 06-46-11

hope it works for you @EdiWang

I fixed this issue simply by changing the css root direction * {direction: rtl}

@MohammedYoussefSoliman can you please give more details!

Was this page helpful?
0 / 5 - 0 ratings