Class: Sakusei::PageChromeTranslator

Inherits:
Object
  • Object
show all
Defined in:
lib/sakusei/page_chrome_translator.rb

Overview

Builds the live-preview’s page chrome from the style pack’s PDF configuration, matching what Puppeteer renders for the PDF as closely as possible.

Source of truth is ‘pdf_options.headerTemplate` / `footerTemplate` / `format` / `margin` inside `config.js`. We evaluate `config.js` with Node to expand JS template literals (e.g. `$wordmarkDataUri`), then translate the resulting HTML strings into running elements + @page CSS.

Strategy: Puppeteer’s headerTemplate / footerTemplate are arbitrary HTML (with images, flex layouts, multi-column structures). CSS @page margin boxes only accept a content string, not arbitrary HTML — so we use CSS Paged Media’s running-element mechanism instead:

.sakusei-running-header { position: running(sk_header); }
@page { @top-center { content: element(sk_header); } }

Placeholder rewrites (matching Puppeteer’s chrome semantics):

  • <span class=“pageNumber”></span> → <span class=“sk-pn”></span>, with .sk-pn::after { content: counter(page); }

  • <span class=“totalPages”></span> → <span class=“sk-tp”></span>, similar

  • <span class=“title”></span> → “” (Puppeteer leaves it blank by default)

  • <span class=“date”></span> → today’s date (static at build time)

  • <span class=“url”></span> → “”

  • <img src=“rel/path”> → <img src=“/__pack/rel/path”> so the browser can fetch images from the style-pack directory. Data URIs and absolute URLs pass through unchanged.

Returns { css:, html: } — caller injects css into <head>, prepends html into <body>.

Constant Summary collapse

HEADER_RUNNING_NAME =
'sk_header'
'sk_footer'
HEADER_CLASS =
'sakusei-running-header'
'sakusei-running-footer'
PAGE_NUMBER_CLASS =
'sk-pn'
TOTAL_PAGES_CLASS =
'sk-tp'
NODE_EXTRACT_SCRIPT =
<<~JS.freeze
  try {
    const c = require(process.argv[1]);
    const o = (c && c.pdf_options) || {};
    const enabled = o.displayHeaderFooter !== false;
    process.stdout.write(JSON.stringify({
      format: o.format || null,
      margin: o.margin || null,
      headerTemplate: enabled ? (o.headerTemplate || null) : null,
      footerTemplate: enabled ? (o.footerTemplate || null) : null
    }));
  } catch (e) {
    process.stderr.write(String(e && e.stack || e));
    process.exit(2);
  }
JS

Instance Method Summary collapse

Constructor Details

#initialize(style_pack) ⇒ PageChromeTranslator

Returns a new instance of PageChromeTranslator.



63
64
65
# File 'lib/sakusei/page_chrome_translator.rb', line 63

def initialize(style_pack)
  @style_pack = style_pack
end

Instance Method Details

#buildObject



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/sakusei/page_chrome_translator.rb', line 67

def build
  return empty_result unless @style_pack
  opts = read_pdf_options

  # Extract chrome strip backgrounds from the templates BEFORE we strip them.
  # Puppeteer's PDF chrome uses a `position: fixed; top: -10cm; bottom: -10cm`
  # div that breaks out to fill the full chrome strip. paged.js's running
  # elements live inside the @top-center / @bottom-center margin boxes which
  # are constrained to the page's content width, so the same trick covers the
  # whole page (wrong) instead of just the strip. We replicate the strip via
  # .pagedjs_page::before / ::after pseudo-elements with the extracted color.
  header_bg = extract_chrome_bg(opts[:header_template])
  footer_bg = extract_chrome_bg(opts[:footer_template])

  header_html = transform_template(opts[:header_template])
  footer_html = transform_template(opts[:footer_template])

  html = +''
  html << wrap_running(:header, header_html) if header_html && !header_html.empty?
  html << wrap_running(:footer, footer_html) if footer_html && !footer_html.empty?

  { css: build_css(opts, header_html, footer_html, header_bg, footer_bg), html: html }
end