RTL(Right to Left) in the PDF
In my previous client project, there was a requirement to download the data shown on UI into PDF. Required formatting of PDF was different than what user see on the webpage. We used PDFKit to write custom PDF. Now the new requirement was to support the Arabic language in the PDF.
The main thing to understand is that Arabic is written from right to left. Also, it should be shown in PDF or any UI from Right to Left.
Unluckily PDFKit does not support RTL. This blog is about what which libraries tried and what we used in the end.
1) PDFKit
PDFKit is a js library used for creating PDF at client side.
In PDFKit, adding Arabic font is a bit difficult as it doesn’t read the font file on client-side. We used XMLHttpRequest to make an async call to fetch the font. Alternatively, you can use the Axios library to make an async call.
var xhr = new XMLHttpRequest();
xhr.open('GET', `/static/fonts/Arabic-webfont.woff`, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function (e) {
if (this.status == 200) {
doc.registerFont('arabic', xhr.response);
}
}
Now Arabic data was rendering in the PDF. But it was Left to Right. Also, words were in reverse order. ex. “I love coding.” would become “coding. love I”
On stackoverflow, there was a suggestion to reverse the words
dataString.split(' ').reverse().join(' ')
This trick is not useful as rendered content might look RTL but it will render Bottom to Top for paragraphs.
2) jsPDF
jsPDF is another js library which is used for creating pdf on the client as well as server-side. It has implemented the BiDi algorithm which is essential to support RTL. In nutshell, jsPDF is supporting RTL, partially. Why partially, because it works great for a sentence containing only Arabic script. But if there is a sentence containing Arabic and English ex. “عربى hello اشخاص”
will render words in the reverse direction. There are two ways to generate PDF using jsPDF
a) Write custom template
This approach was not feasible due to the problem mentioned above.
b) Use html2canvas + jsPDF
In this approach, the whole web page is rendered on the canvas and then attached as an image in the PDF. As it renders webpage as it is, Arabic text looks proper if RTL is implemented in the webpage. The downside of this approach is that it does not give any flexibility to have custom PDF. Also, the size of the PDF increases drastically. For my webpage, the size was 28MB whereas with the latter approach it was 500KB.
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';html2canvas(document.body).then(canvas => {
let pdf = new jsPDF('p', 'mm', 'a4');
pdf.addImage(canvas.toDataURL('image/png'), 'PNG', 0, 0, 211, 298);
pdf.save("sample.pdf");
});
3) html2pdf
This library is wrapper which uses html2canvas + jsPDF. Working is same as 2.b approach. This library has a simple code. You don’t have to provide the size of the page, coordinates of image placement etc. The size of PDF generated using this library was 1.8MB which is acceptable. But still, you have less freedom towards customizing PDF. its good for creating read-only PDF where you don’t want to edit or copy anything from PDF.
4) @media print rule
This approach uses the browser’s built-in feature to save PDF via print popup.
You have to add additional CSS if you want to customize what content you want to show and how you want to show.
//If you don't want to show header and footer in PDF.@media print {
.header, .footer {
display: none;
}
}//To set width 100% of element with class content@media print {
.content {
width: 100%
}
}
This approach gives you great freedom to generate custom PDF. It creates PDF which is editable also, content can be copied. You can keep some elements visible only when media is print
. But with this approach, you have to compromise with user experience. At most, you can have a button which will open print popup.
Few of the challenges and solutions
1) Background color not visible in the PDF
//Add this two properties in your CSS@media print {
body {
color-adjust: exact;
-webkit-print-color-adjust: exact;
background: white;
}
}
2) In chrome, shadow behind text prints as solid color without any blur or transparency when text-shadow
CSS property is used.
.block {
-webkit-print-color-adjust:exact;
-webkit-filter:opacity(1);
}
In the end, we used 4th option. We provided button to the user which opens popup to print the page. User had to manually select download as PDF option present on popup. With this approach, you have to compromise with user experience. We had to trade-off UX over providing custom PDF.