PaperPony

Background colours missing from a PDF

The coloured header came out white, and the white text that sat on it is still there in a pale grey. That second half is the part worth noticing: nothing was removed. Chromium repainted the block and then recoloured the words on it so they would still be legible, which is why this reads as a washed-out document rather than a missing background.

What the file actually contains
printBackground: false, from the page's content stream

  1 1 1 rg                  -> 0 0 795 69 re  f     the header rectangle, white
  .6706 .6706 .6706 rg      -> BT /F4 14 Tf         the text, grey

Both objects are present, both are painted, and the colours are the ones an ink-saving printer would choose. With backgrounds on, the same two objects come out as written:

The same document, one setting changed
printBackground: true, the same two objects

  .3098 .2745 .898 rg       -> 0 0 795 69 re  f     the header rectangle, indigo
  1 1 1 rg                  -> BT /F4 14 Tf         the text, white

Measured on Chromium 151.0.7922.34 by reading the fill operators out of each PDF rather than by looking at a rasterised page, so "was it painted" has an exact answer.

Two settings, and the CSS wins

One of them lives in your renderer and one in the document, and the usual advice treats them as alternatives. They are not alternatives and they are not equal.

Every combination, measured
the same header, five renders

  printBackground   CSS                        header painted
  on                nothing                    yes
  on                print-color-adjust: exact  yes
  on                print-color-adjust: economy yes
  off               nothing                    NO
  off               print-color-adjust: exact  yes

Read the last row. The flag is off, the header is painted anyway, and the property in the stylesheet is the reason. An element that asks for exact colour is opted out of the adjustment entirely, whatever the renderer was told, so a document can be immune to a setting its author never sees.

That is worth knowing in both directions. If your backgrounds print when you wanted them not to, the flag is not where to look.

The value that sounds like the off switch does nothing

print-color-adjust: economy is the other half of the property and it reads like the way to ask for the pale version. Row three of the table above is that value with backgrounds on, and the header is painted exactly as it is without it.

It is the default rather than an instruction, so writing it out changes nothing. The renderer’s flag is the only thing that turns the adjustment on.

The prefix everybody still writes is not needed

Almost every answer to this question gives -webkit-print-color-adjust, often with the unprefixed spelling beside it as an afterthought. Each one on its own, with backgrounds off, so the property is the only thing that can save the header:

One declaration at a time
printBackground: false, and only the CSS differs

  -webkit-print-color-adjust: exact    header survives
  print-color-adjust: exact            header survives
  neither                              header dropped

Both work. Writing the prefixed form costs nothing and it is not doing anything a modern Chromium needs, so a stylesheet that carries only the standard spelling is not the reason your header is white.

What to put in the document

Name the elements whose colour carries meaning rather than reaching for the whole page. A status badge, a chart, a header band that identifies the document: those are the ones a reader loses something by seeing in grey.

On the elements, not on body
.header,
.badge,
.chart {
  print-color-adjust: exact;
}

Applying it to bodyworks and takes every background with it, including the ones that were only ever decoration. On a colour printer that is somebody else’s ink.

Reading it out of your own PDF

Looking at the page tells you it is white. This tells you which of the two settings did it, which is the part you cannot see. Every fill colour in the file, in order:

No tools to install
node -e "
const {inflateSync}=require('zlib'),b=require('fs').readFileSync('invoice.pdf');
let i=0;while(1){const s=b.indexOf('stream',i);if(s<0)break;let t=s+6;
if(b[t]===13)t++;if(b[t]===10)t++;const e=b.indexOf('endstream',t);if(e<0)break;
try{for(const m of inflateSync(b.subarray(t,e)).toString('latin1')
.matchAll(/([0-9.]+) ([0-9.]+) ([0-9.]+) rg/g))console.log(m[0]);}catch{}
i=e+9}"

A block that should be coloured and prints as 1 1 1 rg followed by a rectangle was adjusted rather than dropped, and grey text beside it confirms it: the adjustment moves both, and nothing else in a document produces that pair.

How we handle this

PaperPony renders PDFs from HTML through an API and turns backgrounds on by default, so an invoice with a coloured header arrives with the header. The setting is there to turn off, under Print Background Colours, for the documents somebody prints in volume.

The template guide covers the rest of the render options, and the invoice generator needs no account and runs the same renderer, which makes it the quickest way to see a coloured document come out of it.