Comparing Typst packages for QR code rendering

Published

When looking for packages on Typst Universe, you can find several packages for creating QR codes. I have recently needed to generate EPC QR codes (for making SEPA bank transfers), had to look at my options, and decided I'd write up the results.

This is not the most thorough investigation one could make, but I did quickly look at performance, quality, ability to generate non-QR codes, as well as license and usage in the package ecosystem – at least as far as it was easy to tell from the above Universe searches.

The Contestants

Searching for both "QR" and "codes", the following dedicated (QR) code generation packages can be found:

Cades

cades is based qrcode-svg, a JS library running in Typst via Jogs.


        
#import "@preview/cades:0.3.1": qr-code

        


        
#qr-code(text, width: 3cm)

        
#import "@preview/cades:0.3.1": qr-code

        


        
#qr-code(text, width: 3cm)

License: MIT

Codetastic

codetastic was released (and last updated) in September 2023. Other than QR codes, it can generate UPC-A and a number of EAN bar codes. What makes this one a bit special is that it seems to be implemented in pure Typst instead of bundling a preexisting library for generating the codes.


        
#import "@preview/codetastic:0.2.2": qrcode

        


        
#qrcode(text, width: 3cm, quiet-zone: 0)

        
#import "@preview/codetastic:0.2.2": qrcode

        


        
#qrcode(text, width: 3cm, quiet-zone: 0)

License: MIT

Rustycure

rustycure is again a pure QR code generator. It is newer, released in 2025, and based on the Rust qrcode crate. It can output raw SVG bytes in addition to ready-to-consume images; that may be useful if you plan to, for some reason, process the image with something like grayness.

Unlike the other packages, rustycure wraps its QR codes with a "quiet zone" by default, i.e. a region without content to make sure there is surrounding contrast.


        
#import "@preview/rustycure:0.2.0": qr-code

        


        
#qr-code(text, width: 3cm, quiet-zone: false)

        
#import "@preview/rustycure:0.2.0": qr-code

        


        
#qr-code(text, width: 3cm, quiet-zone: false)

License: EUPL-1.2

Tiaoma

tiaoma is the package I had mostly been using so far. It is based on the Zint C library.

There are lots of customization options available through Zint, however I found that discovering them in the documentation was not super easy; one reason here is that these options are given as a single options dictionary argument, so the function signature does not directly list the options. On the other hand, I have to mention that I simply lack the familiarity with the other packages, to make similar judgments for them.


        
#import "@preview/tiaoma:0.3.0" : qrcode

        


        
#qrcode(text, width: 3cm)

        
#import "@preview/tiaoma:0.3.0" : qrcode

        


        
#qrcode(text, width: 3cm)

License: MIT

Zebra

zebra was only released in February 2026 and like rustycure, it is based on the qrcode Rust crate. Other than QR codes, it also supports data matrix codes via the so-named crate. Also, props to the author for having a small comparison of the packages int heir README as well.


        
#import "@preview/zebra:0.1.0": qrcode

        


        
#qrcode(text, width: 3cm)

        
#import "@preview/zebra:0.1.0": qrcode

        


        
#qrcode(text, width: 3cm)

License: MIT

Output quality

Thanks to the Zebra author for bringing this to my attention: of course, not all QR code generators automatically produce the same quality! Among the packages I was looking at, I was able to identify three options:

  • Basic: QR codes consist of squares; these are drawn separately. This can lead to edges between filled areas in the rendered PDF, especially at high zoom levels.

    The cades and codetastic packages fall into this category.

  • Single path: Squares are drawn, but combined into a single path. This gives renderers a better chance at merging these, and indeed in my tests, I could not spot any edges. (Disclaimer: I was not able to fully confirm this behavior, but this was my conclusion from zebra's description and output quality.)

    The rustycure package falls into this category.

  • Optimized path: Squares are combined into a more complex shape, and that shape is output as a single path. The Zebra README contains an illustrative diagram.

    Both tiaoma and zebra do this.

One thing to note too is that not all these generators output the same image for the same data! I did not investigate further here, but this is probably due to different default error correction levels and/or compression strategies.

Ecosystem usage

QR codes contain data, and various packages support specific data standards. What other packages are there, and what generator do they use under the hood?

Payment/finance QR codes

Based on rustycure:

  • sepay: Generate EPC QR codes for SEPA credit transfers (MIT)

    • invoice-pro: DIN 5008 compliant invoice template (MIT)

Based on tiaoma:

Doing its own thing:

Misc QR codes

Based on tiaoma:

  • materialize: Matter setup codes for smart home devices (MIT)

Performance

Now the big one: how fast are these? The implementations range from interpreted Javascript over pure Typst all the way to native Rust and C; how much difference does it make?

Each package was run three times with time (and the user time was taken); for rustycure, I actually performed four runs because the first run was a bit slower while the others were very consistent, so I think this was an outlier and it is excluded from the numbers below. Each run contained 50 QR codes containing progressively longer lorem texts in a grid. The runs actually compiled the full PDF, so there is a bit of SSD I/O in these numbers.

I don't have extensive benchmarking experience, so take the exact numbers with a grain of salt – you can find the test document and results at https://codeberg.org/ensko/blog/src/branch/main/content/blog/assets/009. However, I think the proportions speak a fairly clear language:

Package Mean time (s) Standard Deviation (s)
tiaoma 1.37 0.002
rustycure 3.41 0.011
zebra 3.87 0.001
codetastic 11.35 0.008
cades 238.03 5.651

Tiaoma, the C implementation, is solidly in first place. Rustycure and zebra come soon after, with zebra trading a bit of performance for better output quality, it seems. Codetastic, the pure Typst implementation, takes considerably longer, and Cades, the Javascript implementation, tested my patience!

Conclusion

The Typst QR code landscape is surprisingly diverse: C, Rust, Typst, and Javascript are all represented. Despite the new contenders, it seems that tiaoma is still at the top of the pack! I came into this looking for a payment QR code package, and ended up with the sepay package, which is based on rustycure. It could be faster if it used tiaoma, but the speed is nevertheless acceptable.

I appreciated zebra's README, which gave me valuable hints that I should cover quality in this post, and I also like that they explicitly focused on this aspect. By that README, tiaoma is still a top choice in that regard, so there is no need to compromise if you want top performance.

Finally – no shade to this early package, stresstesting Typst's Javascript capabilities – but if you use cades in your Typst documents, you can definitely do better!

Thanks for reading! And if I missed any packages in my research, let me know on the forum or on Discord.