boxes
360 rounded, bordered, filled boxes
all 2 pages · p95 18.8 ms
benchmark · pure typescript · run it yourself
Six documents, four engines, every comparison held to the same text on the same pages. jasy is a PDF engine in pure TypeScript - no headless browser, no WASM, no JVM - and it is what @jasy/vue and @jasy/nuxt render with.
Flexbox layout, in pure TypeScript.
Works the document out itself - line breaks, pagination, kerning, SVG. No headless browser, no WASM, no JVM.
Flexbox layout, as React components.
The same job through Yoga and a React tree. The closest comparison on this page.
Declarative layout, one document object.
Works it out too, with its own column and table model. Kerns, and takes SVG markup directly.
A drawing API. No layout.
You work out every coordinate and every page break. That is why it is quick here, and why two cases are blank.
Both figures are geometric means, which is the correct average of ratios. jsPDF is not in them - it draws where these lay out, and an average across the two would mean nothing. Its comparison has its own section below, losses included.
Bars are to scale within a case, against its slowest engine. Every pairing was checked for the same words on the same pages before a ratio was printed.
360 rounded, bordered, filled boxes
all 2 pages · p95 18.8 ms
40 charts x 24 bars, drawn with the imperative pen
all 4 pages · p95 4.7 ms
a report: repeating header/footer, 24 paragraphs, 90 table rows
all 4 pages · p95 19.4 ms
120 vector marks - paths, strokes, joins
all 1 page · p95 7.6 ms
180 flowing paragraphs - line breaking and pagination
all 13 pages · p95 60.1 ms
120 paragraphs in an embedded TrueType - real kerning + ligatures
all 7 pages · p95 36.3 ms
where jasy is behind
It draws where the others lay out, and drawing is cheaper. There is no defect on either side - you are the one doing the layout. In the report case that is 73 lines of jsPDF against 41 of jasy: every y coordinate, every page break, and the header redrawn after each new page. Both are on this page; read them rather than take the count.
That is the whole trade. You pay roughly forty milliseconds on thirteen pages, and stop writing that loop. Two of the six documents it cannot produce at all - the SVG case needs a second library, and it never kerns, so the same words do not land in the same places.
what keeps this honest
So here is every rule this harness enforces - including the two that cost us the numbers we would rather be printing. They stay in.
Same words, same count, same pages - checked before a ratio is printed, and the runner refuses to print one otherwise. It has earned its keep twice, both times on our own mistakes: a case that handed react-pdf a cell too short for its line, so it drew no labels at all while the page count still matched; and a header spacing we had set on one side only, which put a whole body 8pt lower.
A benchmark that repeats one string measures caching, not layout. Ours did, and the repetition helped react-pdf far more than us - on the same machine and the same day, the flowing-text case came out 45% in its favour with one repeated paragraph and 6% with 180 different ones. Real documents have no identical paragraphs, so neither do these.
The same word means different things in each of them: a line height multiplies the font size in jasy, the font's own natural height in pdfmake, and is not inherited into nested text at all in react-pdf. None of that is a fault - it just has to be converted, or the documents break in different places. Every conversion is pinned in the case with the reason beside it.
jasy keeps two lines of a paragraph together at a page break; jsPDF has no such rule. So it is turned off in the report case, on our side and react-pdf's. Handicapping ourselves is what makes it the same document.
The harness, the six documents and the raw JSON behind this page are in the repository. It pins the versions it compares against, and prints the machine it ran on.
git clone https://github.com/jasy-pdf/jasy
cd jasy/bench
pnpm install
pnpm benchYour document is not among the six? That is the fair objection to any benchmark, and the answer is a pull request - a case is one file with one function per engine.
One machine, one day, six documents. Your numbers will differ - which is the point of publishing the harness rather than only the result.