h1
The program already had a default note for headings. Size 2em. Bold. Block. Your file said 18pt. 18pt replaces 2em. Bold stays, because you never mentioned weight.
Then a later pass reads "18pt" and stores the number 18.
Imagine you wrote a tiny web page. A heading that says Invoice. A blue card that says Hello. You want a PDF. A PDF is a file a viewer draws, like a still picture of a page. This program draws that picture itself. It does not open Chrome. It does not call another program named wkhtmltopdf.
Words I will use, once, so they stop being mystery:
<h1> name the parts.Save this as sample.html.
<h1>Invoice</h1>
<div class="card">Hello</div>
/* in a style block */
h1 { font-size: 18pt; }
.card { background-color: blue; color: white; padding: 12pt; }
blue is four letters. It is not paint yet. By the end it must be a filled rectangle on paper.
Think of a kitchen line. Each station does one job and passes a tray. Nobody skips ahead to drawing until the tray has sizes on it.
load
Read the file. Do not look at tags yet. If the letters are not UTF-8, stop. UTF-8 is the usual way computers store text.
parse
Walk the text once. Open tags go on a stack, like plates. Close tags take a plate off. You get a tree. The CSS is still just writing stuck under <style>.
collect
Find the style block. Split it into two notes. One for h1. One for .card. A class is a name you pin on a tag, here card.
match
The h1 note only sticks to the heading. The .card note only sticks to the div. The heading never hears the word blue.
winner map
Each node gets a map. A map is a list of "this property, this winning text." On the heading the font size is still the string "18pt". On the card, color is still the string "blue".
numbers
Look up blue in a color table. You get 0, 0, 255. Store that as 0, 0, 1. Look up white. You get 1, 1, 1. 12pt is already 12. That 12 is padding. Padding is empty space inside the box, like bubble wrap. It is not paint.
layout
Give each box an x, a y, a width, a height. Y starts at the top and grows down, like reading a page. Write a to-do list of drawing jobs. One job is "fill a blue rectangle." One job is "write Hello in white."
paint
A PDF page counts Y from the bottom. Flip the box. Write tiny commands. rg sets the crayon. re traces a rectangle. f fills it.
write
Put a %PDF-1.4 header on top. Compress the drawing. Add a table of contents for the file itself so a viewer can find page 1. If you asked for an archive stamp, add that stamp here. The drawing jobs did not change.
Parse starts a fake root named #document. Then it pushes tags. sample.html becomes:
#document
html
body
h1 "Invoice"
div.card "Hello"
internal/html/html.go:118
A script tag would sit here as dead text. Nothing runs it. If a page is an empty Angular shell, the PDF is empty too. There is no little browser inside.
The fight over which style note stays is called the cascade. The function is cascadeRaw. It runs once per node. That is why the heading keeps 18pt and never turns blue.
The program already had a default note for headings. Size 2em. Bold. Block. Your file said 18pt. 18pt replaces 2em. Bold stays, because you never mentioned weight.
Then a later pass reads "18pt" and stores the number 18.
Three notes land as text. background-color: "blue". color: "white". Padding split into four sides of "12pt".
A later pass walks that same list. Blue becomes fill. White becomes letter color. Padding shrinks the inside of the box. Hello sits in that inside. Padding never becomes a crayon command.
Each matching note gets a score of three numbers: (a, b, c). Compare them left to right, like a sports ranking.
| Number | In English | In sample.html |
|---|---|---|
| a | How many ids, like #box | 0 and 0. No ids. |
| b | How many classes, like .card | .card has 1. h1 has 0. |
| c | How many tag names, like h1 | h1 has 1. .card has 0. |
So the heading match is (0, 0, 1). The card match is (0, 1, 0). Class beats tag. That only matters if both notes try to set the same property on the same node. They do not. Different whiteboards.
If you later write h1 { color: black } and .card { color: white }, the div is still white. The heading never matched .card.
Above that score there is a ranking:
(0, 0, 0).The color table is a dictionary. blue is 0 red, 0 green, 255 blue. Computers often store that as 0 to 1, so 255 becomes 1. White is 1, 1, 1.
| Note | Becomes | Job |
|---|---|---|
| background-color blue | fill 0, 0, 1 | Paint the card's rectangle. |
| color white | letter fill 1, 1, 1 | Paint the letters of Hello. Not a rectangle. |
| padding 12pt | four sides of 12 | Push Hello inward. No crayon. |
Three notes. Three jobs. One walk of the whiteboard.
namedColorTable "blue" and "white": internal/css/values.go:664-666. Padding used as geometry: internal/layout/layout_flow.go:11-18.Layout does not draw yet. It writes orders.
| Order | Means |
|---|---|
| write Invoice at 18pt, black | From the heading. |
| fill a rectangle, color 0, 0, 1 | From the card background. |
| write Hello, white | From the card text, sitting in the 12-point air. |
The fill is tucked under the text on purpose, so you see letters on blue, not blue on letters.
Layout and PDF do not share the same zero for Y. Layout puts zero at the top of the writing area and counts downward, like line numbers on a page. PDF puts zero at the bottom of the paper and counts upward. Same blue box on screen. Two different numbers for its place.
A PDF rectangle command looks like x y w h re. That is not the word red. re means rectangle. It wants the bottom-left corner, then width, then height.
The fill job stored the top-left. So the code adds the height first, to get the bottom. Then it flips.
x = leftMargin + layoutX
y = pageHeight - topMargin - (layoutY + height)
w = width
h = height
then: x y w h re
then: f (fill that shape)
canvasToPDF: internal/layout/paint.go:853-856. drawFill passes op.Y+op.H at paint.go:862. Rect writes " re": internal/pdf/content.go:288. Fill writes "f": content.go:293. Color writes " rg": content.go:228.
A4 paper is about 595 by 842 points. Default empty border around the page is 10 millimetres, about 28 points. If a teaching box sat at layout X=0, Y=40, width 500, height 36:
x = 28 + 0
y = 842 - 28 - (40 + 36) = 738
28 738 500 36 re
Your real Invoice heading will push that Y down a bit. The sums stay the same kind of sums.
Letters are different. Their Y is already the line they sit on, not the top of a box. The flip still happens. The extra "+ height" does not.
Blue is gone as a word. The stream says 0 0 1 rg (set fill color to blue), then x y w h re (trace the rectangle; re means rectangle, not red), then f (fill that shape with the color). Without f you would have a path and a color and still see no blue card. White Hello later says 1 1 1 rg and draws the letters.
The drawing is the same for an invoice and for an archive file. The last station can glue on extra labels.
The file starts %PDF-1.4. A viewer draws it. A checker that wants an official archive mark will say no. That is the default.
You pass a profile flag. The writer adds extra packets. Archive wants embedded fonts and a color recipe. Accessible wants a title, picture descriptions, and a map of "this is a heading, this is a background so do not read it aloud." Changing only the version number on line one is not that stamp.
If you skip the stamp, the blue rectangle is still drawn. If you ask for accessible, that same rectangle is marked "background, ignore." Hello is still Hello.
A file of tags becomes a tree. Style notes stick to matching nodes. Each node keeps one winning text per property. Those texts become numbers. Numbers become box sizes and a list of drawing jobs. Jobs become crayon commands on a page that counts from the floor. The file around those commands can carry a stamp, or not.
If you can say that out loud, you have the html to pdf converter.