2025: jspdf doesn't support Open Type features to this date
This is also reported in the github issue section: "Custom font does not get rendered properly in the PDF".
Workaround: try pdfkit.js
Admittedly, unsatisfying to switch the library but perhaps the easiest solution.
pdfkit support Open Type feature settings in text renderings.
You can add them as an object to the text properties like so:
// write text
const text = "نگار شکار";
const [x, y] = [10, 60];
// add your content to the document here, as usual
doc.fontSize(100);
// define opentype feaures
const features = {
features: {
liga: true,
cswh: true
}
};
doc.text(text, x, y, features);
Working example
/**
* init pdfkit
* and create blob for rendering
*/
(async () => {
const doc = new PDFDocument();
// pipe the document to a blob
const stream = doc.pipe(blobStream());
// load custom font and register
let fontUrl =
"https://cdn.jsdelivr.net/npm/@raha.group/[email protected]/IranNastaliq.regular.ttf";
const font = await fetch(fontUrl);
const buffer = await font.arrayBuffer();
doc.registerFont("CustomFont", buffer);
doc.font("CustomFont");
// write text
const text = "نگار شکار";
const [x, y] = [10, 60];
// add your content to the document here, as usual
doc.fontSize(100);
// define opentype feaures
const features = {
features: {
liga: true,
cswh: true
}
};
doc.text(text, x, y, features);
// get a blob when you're done
doc.end();
// get objectURL for display or download
stream.on("finish", async function() {
let blob = stream.toBlob("application/pdf");
let objectURL = await URL.createObjectURL(blob);
// set download link
btnDownload.href = objectURL
// render via pdf.js
renderPDF(objectURL);
});
})();
<!-- pdfkit helper -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/js/pdfkit.standalone.min.js"></script>
<!-- blob helper -->
<script src="https://github.com/devongovett/blob-stream/releases/download/v0.1.3/blob-stream.js"></script>
<!-- pdf.js: just for displaying PDF -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<style>
canvas {
border: 1px solid #000
}
</style>
<p><a id="btnDownload" href="" download="file.pdf">Download</a></p>
<canvas id="pdfCanvas" style="border:1px solid #ccc; width:100%;"></canvas>
<script>
// render pdf blob to pdf.js viewer instance
async function renderPDF(url) {
const canvas = document.getElementById("pdfCanvas");
const ctx = canvas.getContext("2d");
// Load PDF
const pdf = await pdfjsLib.getDocument(url).promise;
// Get first page
const page = await pdf.getPage(1);
// Prepare viewport
const viewport = page.getViewport({
scale: 1.5
});
canvas.width = viewport.width;
canvas.height = viewport.height;
// Render
await page.render({
canvasContext: ctx,
viewport
}).promise;
}
</script>
In the above example I'm rendering the pdf output via pdf.js merely for the sake of illustration (we can't render PDFs in iframes/snippets) – you don't need this for the PDF creating itself.
Emulating substitution features?
Sorry not possible. The main problem: OT Feature substitutions replace certain glyphs (so only the text representation) while the text/string data is unchanged.
To apply substitutions the text shaping engine must support Open Type features. The glyph swapping happens via internal glyph ID mapping – we can't access these in a browser e.g via JS without a full blown font parsing or shaping library.
So there is no way to change the input string to show alternative glyphs as these alternate designs can't be accessed by a unicode point.
img.src = 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg"><foreignObject width="' + canvas.width + '" height="' + canvas.height + '">' + parseandserialize('نگار') + '</foreignObject></svg>');