QR in real CMYK / grayscale

Is there any way to make QR code cmyk(0%, 0%, 0%, 100%)?

I tried packages rustycure, cades, tiaoma, and told them to make with dark-color/ color/ fg-color: cmyk(0%,0%,0%,100%), but in final PDF I get composite color cmyk(71%, 68%, 64%, 73%).

I cannot give this file to press: small shifts of colors will make QR unreadable.

See Profile-based Color Management for Preview and PDF export · Issue #3143 · typst/typst · GitHub.

I do not understand how it can help.
When I use #text(fill: cmyk(0%,0%,0%,100%)), I get the result I need in PDF.
But this does not happen with QR code.

I have an explanation for tiaoma, cades, and rustycure. All these packages use some libraries to generate SVG images, but the SVG standard only supports RGB colors.

For example, tiaoma uses zint to generate an SVG, If you click into tiaoma/0.3.0/lib.typ, you’ll find the following code.

if type(c) == color {
  return c.to-hex().slice(1)
} 

#cmyk(0%, 0%, 0%, 100%).to-hex() is #221f2, and it becomes cmyk(71%, 68%, 64%, 73%) at last.

Code to inspect the SVG
#let svg-bytes = qrcode(
  "Lorem",
  options: (fg-color: cmyk(0%, 0%, 0%, 100%)),
).source
#raw(str(svg-bytes), lang: "svg")

According to SVG 2 CMYK colors and CMYK JPEG output? · Issue #766 · linebender/resvg · GitHub posted in 2024, rendering SVG files with CMYK colorspace is not possible at present, and probably will not be possible in the next 3-5 years, if ever.

Edit: I see you’ve created Can't get exact CMYK in typst-generated PDF · Issue #25 · Enter-tainer/zint-wasi · GitHub. Better put a link here the next time.

3 Likes

Use backticks when including code snippets. How to post in the Questions category - #7 by Andrew

There are some issues with CMYK that Typst outputs, so it’s not possible to do this right now, IIUC. Based on QR in real CMYK / grayscale - #4 by Y.D.X, the situation is even worse.

Is it possible to use something like #image(icc: "DotGain10.icc", qr-code(...)) to get pure cmyk-black QR?

It is possible to get the path (set of rectangles) from SVG, parse it and draw it again in CMYK.

#import "@preview/tiaoma:0.3.0" : qrcode
#let black_cmyk_qrcode(text, width: 1.5cm, color: cmyk(0%, 0%, 0%, 100%)) = {
  let svg-bytes = qrcode(text).source
  let qr_path = str(svg-bytes).match(regex("<path d=\"([^\"]*)")).captures.at(0) // get the path part of code from SVG
  let qr_rectangles_text = qr_path.matches(regex("M(\d+) (\d+)h(\d+)v(\d+)h-\d+Z"))
  let qr_rectangles_coord = ()
  for qr in qr_rectangles_text {
    qr_rectangles_coord.push(("x", "y", "width", "height").zip(qr.captures.map(x => int(x))).to-dict())
  }
  let tiles = calc.max(..qr_rectangles_coord.map(x => (x.x + x.width)))
  let tile_size = width/tiles
  rect(width:width, height: width, inset: 0mm, stroke: 0pt,
    {
    for r in qr_rectangles_coord {
        place(dx: r.x*tile_size, dy: r.y*tile_size, rect(height: r.height*tile_size, width: r.width*tile_size, fill: color))
    }}
  )
}
1 Like

@Y.D.X, @Andrew I found the solution: QR in real CMYK / grayscale - #7 by pantlmn

1 Like

I doubt… It looks like that the conversion between color spaces is lossy. Say, cmyk(0%, 0%, 0%, 100%).rgb().cmyk() is not K, and black.cmyk().rgb() does not equal #black.rgb() either.

Some libraries are capable of drawing QR codes with Unicode characters (like ▀█ ▀▀▀▀▀██▀▀▄). This might be a feasible workaround at present.

To reiterate from the issue discussion:

Solution is cmyk(50%, 50%, 50%, 100%) which turns into #000001. You can’t get printers to print #000000 because that requires you to spray carbon nanotubes onto the printing surface :crazy_face:

Tested cmyk(50%, 50%, 50%, 100%) though and it works as expected. Using pure K color will vary depending on viewer, but it’s incorrect to show it as rich black anyway - so if they do so, they’re lacking.

1 Like