๐Ÿ–จ๏ธ Chapter 89: HTML & CSS for Print & Paged Media

Dynamic Page Numbers & Running Headers

Mastering CSS Paged Media margin boxes, dynamic page numbering counters, running headers with string-set(), and cross-engine pagination strategies.

LEARNING OBJECTIVES โŒต
  • Map and utilize the 16 margin boxes defined in the W3C CSS Paged Media Module Level 3.
  • Implement dynamic page numbering using counter(page) and counter(pages).
  • Understand the string-set() property and string() function for dynamic running chapter titles.
  • Navigate the real-world rendering landscape: CSS Paged Media engines (PrinceXML, WeasyPrint) vs. Headless Chromium template headers/footers.
๐ŸŽฌ INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
๐ŸŒ
1. Input
Directives & Tags
โš™๏ธ
2. Parse
Tokenizer & AST
๐ŸŒณ
3. Layout
Box Model & Flow
๐ŸŽจ
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

๐Ÿ“– The Mental Model & Story (Intuitive Foundation)

Pick up any published textbook or official corporate quarterly filing. Look at the perimeter outside the main reading canvas:

  • At the top left of every even page: "Chapter 4: Distributed Consensus".
  • At the top right of every odd page: "Section 4.2: Raft Algorithm Mechanics".
  • At the bottom center or bottom right: "Page 142 of 350".
  • On the very first page of the chapter: The top header is completely blank, and the page number is suppressed or rendered in Roman numerals ("Page iv").

In traditional print design, the margins are not just empty dead spaceโ€”they are populated by 16 specialized Margin Boxes that surround the central content area like a picture frame. The CSS Paged Media Module provides CSS at-rules that target each margin box directly and populate them with dynamic data without modifying the HTML DOM.

+------------------------------------------------------------------------------------+
|                         THE 16 CSS PAGE MARGIN BOXES                               |
+------------------------------------------------------------------------------------+
| @top-left-corner |  @top-left  |  @top-center  |  @top-right  |  @top-right-corner  |
|------------------+-------------+---------------+--------------+--------------------|
| @left-top        |                                            | @right-top         |
|------------------|                                            |--------------------|
| @left-middle     |                 PAGE CONTENT               | @right-middle      |
|------------------|                    AREA                    |--------------------|
| @left-bottom     |                                            | @right-bottom      |
|------------------+-------------+---------------+--------------+--------------------|
| @bottom-left-crn | @bottom-left| @bottom-center|@bottom-right | @bottom-right-crn  |
+------------------------------------------------------------------------------------+

Technical Deep Dive & Specifications

1. The 16 Margin Boxes & Content Injection

Inside an @page rule, you can define margin box rules using standard CSS pseudo-declarations:

@page {
  size: A4 portrait;
  margin: 25mm 20mm;

  /* Top Left: Organization Name */
  @top-left {
    content: "Acme Cloud Infrastructure Corp.";
    font-family: sans-serif;
    font-size: 8pt;
    color: #64748b;
  }

  /* Top Right: Running Section Title */
  @top-right {
    content: "Confidential & Proprietary";
    font-family: sans-serif;
    font-size: 8pt;
    font-weight: bold;
    color: #dc2626;
  }

  /* Bottom Center: Page Counter */
  @bottom-center {
    content: "Page " counter(page) " of " counter(pages);
    font-family: sans-serif;
    font-size: 9pt;
  }
}

2. The Built-in CSS Page Counters

The CSS Paged Media specification specifies two native counters:

  1. counter(page): Returns the current physical page number (1-indexed).
  2. counter(pages): Returns the total number of pages in the printed document.
/* Custom counter formatting */
@page {
  @bottom-right {
    /* Formats: decimal (1, 2, 3), lower-roman (i, ii, iii), upper-roman (I, II, III), lower-alpha (a, b, c) */
    content: "Sheet " counter(page, upper-roman);
  }
}

/* Suppress counters on the cover page */
@page :first {
  @top-left { content: none; }
  @top-right { content: none; }
  @bottom-center { content: none; }
}

3. Dynamic Running Headers with string-set()

To pull text dynamically from the HTML content (such as the current <h2> heading) and print it inside @top-right, the specification defines string-set():

/* Capture the text content of the active h2 heading */
h2 {
  string-set: chapter-title content();
}

/* Inject the captured text into the margin box */
@page {
  @top-right {
    content: string(chapter-title);
    font-style: italic;
  }
}

4. Real-World Browser Support vs. Headless PDF Engines

Understanding the browser implementation reality is crucial for senior engineers:

+-----------------------------------------------------------------------------------+
|               CSS PAGED MEDIA MARGIN BOX ENGINE COMPATIBILITY                      |
+-----------------------------------------------------------------------------------+
  Feature                      PrinceXML / WeasyPrint    Chromium / Puppeteer / Safari
  ---------------------------------------------------------------------------------
  @page { margin }             โœ… Full Support           โœ… Full Support
  @top-left, @bottom-right     โœ… Full Support           โŒ Limited / No Native CSS
  counter(page), counter(pages)โœ… Full Support           โŒ (Use Puppeteer Templates)
  string-set()                 โœ… Full Support           โŒ Not in WebKit/Blink

[!IMPORTANT]

  • In Pure CSS Engines (PrinceXML, WeasyPrint, Paged.js, Typeset.sh): Margin boxes (@top-center, @bottom-right, counter(page)) work natively via CSS.
  • In Chromium / Puppeteer / Playwright: You generate running headers and dynamic page counts using HTML/CSS template parameters in the PDF generation API: page.pdf({ displayHeaderFooter: true, headerTemplate: '...', footerTemplate: '...' }) (Mastered in Lesson 89.8).
  • In Paged.js (Polyfill for standard browsers): Translates W3C margin boxes into simulated DOM elements in standard Chrome/Firefox.

๐Ÿ’ป Interactive Code Playground

Below is a complete, runnable HTML document demonstrating standard CSS Paged Media margin boxes, counter formatting, and cover page suppression.

Starter Code

Line-by-Line Code Breakdown

  • Lines 8โ€“13 (@page): Establishes A4 geometry with $30\text{mm}$ top/bottom margins to allocate vertical breathing room for margin boxes.
  • Lines 16โ€“24 (@top-left): Renders company branding with a subtle bottom divider line (border-bottom: 0.5pt solid #cbd5e1).
  • Lines 36โ€“43 (@bottom-left): Injects red security classification text with a top divider line.
  • Lines 45โ€“53 (@bottom-right): Generates dynamic page counts: "Page " counter(page) " of " counter(pages).
  • Lines 57โ€“63 (@page :first): Suppresses headers and footers on the cover page using content: none; border: none;.

Expected Browser Render Output

  • Screen View: Displays three clean, stacked document pages with realistic dropshadows.
  • In Paged Media Engines (PrinceXML / Paged.js / Print Preview):
    • Page 1 has no running headers or footers.
    • Page 2 displays top header ("ACME CORP..." and "PROJECT TITAN") and bottom footer ("CONFIDENTIAL..." and "Page 2 of 3").
    • Page 3 displays identical top headers and "Page 3 of 3".

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL playground.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45ยฐC
INSPECTING DOM: VALID
TAGS: SCANNING...

๐Ÿ‹๏ธ Hands-On Exercise

๐ŸŽฏ The Challenge: Corporate Compliance Margin Boxes

Scenario: You are configuring an automated legal compliance report. The legal department requires:

  1. Standard letter portrait with 25mm top/bottom and 20mm left/right margins.
  2. The @top-center box must display "RESTRICTED LEGAL DISCLOSURE" in 8pt bold uppercase.
  3. The @bottom-right box must display the current page in lowercase Roman numerals (counter(page, lower-roman)).
  4. The @page :first cover page must suppress all headers and footers.

๐Ÿ Starter Code Sandbox

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
STARTER CODE SANDBOX exercise.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45ยฐC
INSPECTING DOM: VALID
TAGS: SCANNING...

โš ๏ธ Common Pitfalls

  1. Expecting @top-center to work natively in standard Chrome window.print(): Standard consumer desktop Chrome does not currently implement CSS Paged Media margin boxes in its native print dialog. To render CSS margin boxes, you must use a dedicated PDF engine (PrinceXML, WeasyPrint), the Paged.js polyfill, or Puppeteer's headerTemplate/footerTemplate options.
  2. Inadequate Margins for Margin Boxes: If you declare @top-center { content: "Header"; } but set @page { margin-top: 5mm; }, the header text will overlap with your <body> content. Allocate at least 20mm to 30mm for margins when using margin boxes.
  3. Using JavaScript document.write(pageNumber): JavaScript has zero awareness of physical page breaks or pagination counts in the client DOM. Never try to calculate physical page numbers using JavaScript DOM nodes; rely strictly on CSS counters or headless PDF API templates.

๐Ÿ’ก Pro Tips

  1. Use Paged.js for Browser-Based Previewing: If you want to render W3C CSS Paged Media margin boxes directly in standard web browsers, include <script src="https://unpkg.com/pagedjs/dist/paged.polyfill.js"></script>. It parses @page margin boxes and paginates standard HTML on the fly.
  2. Leverage counter-increment for Custom Section Counters: You can define custom sub-counters for appendices or figure numbers:
    figure { counter-increment: figure-count; }
    figcaption::before { content: "Figure " counter(figure-count) ": "; font-weight: bold; }
    
  3. Format Page Counters for Formal Documents: Use the second argument of counter() to adapt numbers to different document types: counter(page, lower-roman) for prefaces, counter(page, decimal) for body content, and counter(page, upper-alpha) for appendices.

๐Ÿ“Œ Key Takeaways

  • The W3C CSS Paged Media Module defines 16 distinct margin boxes surrounding the central page content area.
  • Dynamic page numbering is declared via content: counter(page) and content: counter(pages).
  • The string-set() property captures dynamic HTML heading text and injects it into running headers with string().
  • Dedicated print engines (PrinceXML, WeasyPrint) support margin boxes natively, while Chromium pipelines use Puppeteer header/footer HTML templates.
  • Always suppress headers and footers on the cover page using @page :first { @top-left { content: none; } }.
  • --
โญ LEARN: HTML ๐ŸŒŸ โš”๏ธ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

How many distinct margin boxes are defined around the perimeter of the page area in the W3C CSS Paged Media specification?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

Which CSS expression outputs "Page 3 of 12" in a CSS Paged Media margin box?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

Why does running window.print() in standard Google Chrome fail to render CSS @top-center { content: "Header"; } declarations?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP