# PDF Onepager

> pdf-onepager

- Skill: `feedbackpulseapp/pdf-onepager` (Agent Skill, multi-file: 20 files)
- Install (CLI): `npx skillmds@latest add feedbackpulseapp/pdf-onepager`
- Raw SKILL.md: https://api.skillmd.com/api/skills/feedbackpulseapp/pdf-onepager/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: FeedbackPulseApp (https://skillmd.com/u/feedbackpulseapp)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/feedbackpulseapp/pdf-onepager

---


# pdf-onepager

Produces high-craft, **print-safe, AI-citation-ready** one/two-page PDFs from
**HTML + print CSS rendered by WeasyPrint** — not a design tool, not a flattened
image. You edit a content HTML file; `gen.py` renders it to a tagged PDF in both
US Letter and A4 plus a PNG preview; `verify.py` gates the citation/utility
checklist before you ship.

Why this approach: the asset's whole value is that its **text is selectable, its
metadata is set, and its source URL is live in the document** — the citation
infrastructure that makes ChatGPT, Perplexity, and Google AI Overview cite it.
WeasyPrint emits tagged PDF/UA from clean HTML, and CSS handles the layout math
(centering, columns, page breaks) for you.

It works for any brand out of the box: a neutral **`default`** preset plus two
worked examples (`feedbackpulse`, `enpstools`). Make your own by copying one
file. Paths below are relative to the skill directory (where this file lives).

## Setup (one-time)

```bash
cd <skill-dir>        # the folder containing this SKILL.md
./setup.sh            # installs native deps hint, builds .venv, smoke-renders
```

`setup.sh` is idempotent. It needs system libraries for WeasyPrint; if they are
missing it prints the exact install line for your OS:

- **macOS:** `brew install pango cairo gdk-pixbuf libffi poppler`
- **Debian/Ubuntu:** `sudo apt-get install -y libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf-2.0-0 libffi-dev poppler-utils`

If the skill is installed read-only via the plugin marketplace, copy `gen.py`,
`verify.py`, `setup.sh`, `requirements.txt`, `styles/`, `templates/`, `fonts/`
into your project (or clone the repo) and run there, so the `.venv` and `out/`
have a writable home.

## Run (agent path) — render + verify

```bash
cd <skill-dir>

# Neutral default brand (Atkinson Hyperlegible + indigo), Letter + A4 + preview:
./.venv/bin/python gen.py templates/starter.html

# A real example, branded, one flag to swap brand:
./.venv/bin/python gen.py templates/example-sbi.html --brand feedbackpulse

# QA against the citation checklist (exit 0 = ship-able):
./.venv/bin/python verify.py out/starter.pdf --source yourdomain.com/your-source-page
```

`gen.py` writes `<stem>.pdf` (Letter), `<stem>-a4.pdf` (A4), and
`<stem>-preview.png` (page-1 render) into `--out` (default `out/`). **Open the
preview and actually look at it** — `verify.py` checks structure, not whether the
layout looks good.

`verify.py` FAILs (blocking) on: missing Title/Author/Subject/Keywords/Creator,
no `/Lang`, untagged PDF (no StructTreeRoot), any page without selectable text,
or the `--source` URL missing from the text. WARNs on file size over 600 KB.

## Authoring a NEW asset

1. **Copy the starter**, keep the structure, replace every `[bracketed]`
   placeholder: `cp templates/starter.html templates/<your-slug>.html`
2. **Set the metadata in `<head>`** — `<title>`, `<meta name="author|description|
   keywords|generator">`, and `<html lang>`. WeasyPrint reads these into the PDF
   properties; they are how AI engines identify the document. Do not leave blank.
3. **Set the running-footer URL** — the one per-document override:
   `body { string-set: docsource "yourdomain.com/the-page"; }`. Put the same URL
   in the masthead link and the `.docfoot` so the citation appears three times.
4. **Use the components in `styles/base.css`**: `.definition` (tinted intro),
   `.framework`/`.col` (equal columns with rules), `.callout-key` (the single
   loudest element — make it the memorable hook), `.worksheet`/`.fill` (dotted
   fill-in lines), `.cols2`, `.checklist`, `.page-2` (back of the one-pager).
5. **Render + verify + LOOK.** If page 1 overflows, adjust margins/font-size in
   the HTML's `<style>` or base.css; WeasyPrint reflows, so don't fight pixels.
6. **Manual AI test (required, can't be automated):** drag the PDF into **both**
   ChatGPT and Claude and ask *"What is this document and where can I read
   more?"* The answer must name the topic and surface your source URL. If not,
   the metadata or footer URL is wrong — fix and re-render.
7. **Ship to a STABLE URL.** Host at `/assets/downloads/<slug>.pdf` (and
   `-a4.pdf`), publicly (not behind a form). When you update, **replace the file
   at the same URL** — never version the path, or backlinks and AI citations
   break. Optionally add `DigitalDocument` JSON-LD on the page that links it.

## Make it your brand

Only colors and fonts are brand-specific; layout is shared. To add a brand:

```bash
cp styles/brand-template.css styles/brand-acme.css   # then fill in colors/fonts
./.venv/bin/python gen.py templates/starter.html --brand acme
```

`brand-template.css` is fully commented. The one rule that matters is **contrast**:
`--accent` is your vivid color for fills/rules; `--accent-deep` is for accent
*text on white* and must clear 4.5:1; `--callout-bg`/`--on-callout` is the loudest
block and must also clear 4.5:1 (if your brand color is too light for white text,
use a navy/near-black there — see `brand-enpstools.css`, where teal fails so the
callout is navy). Drop brand `.ttf` files into `fonts/` and wire `@font-face` as
shown in the template.

## What makes these good (encoded in base.css)

- **One loud element.** `.callout-key` is the highest-contrast block; everything
  else is calm. White background, no full-bleed color, no gradients/shadows — it
  must read as a tool, not a flyer.
- **Brand via presets, never hardcoded.** Every color/font is a CSS variable in
  `styles/brand-*.css`; `base.css` consumes them.
- **Contrast enforced by variable split** (`--accent` vs `--accent-deep`,
  `--callout-bg`/`--on-callout`) — see "Make it your brand".
- **No em dashes in copy.** They read as an AI tell; write them out
  (period/comma/colon). The templates are already clean.
- **Dual page size from one template.** `base.css` sets margins but not size;
  `gen.py` injects `@page { size: letter | A4 }` per build.

## Gotchas

- **Use the venv's python, every time.** System `python3` lacks WeasyPrint;
  `./.venv/bin/python` has it. The #1 "module not found" cause.
- **Native libs are a real dependency.** If `import weasyprint` fails, you are
  missing the OS packages — re-run `./setup.sh` and follow its install line. On
  macOS Homebrew, `DYLD_FALLBACK_LIBRARY_PATH=/opt/homebrew/lib` resolves rare
  dylib-lookup failures.
- **Fonts are bundled (OFL) in `fonts/`** and embedded+subsetted into the PDF, so
  output is identical on every machine. To use your own brand font, add the
  `.ttf` and wire `@font-face` in your brand CSS. Variable fonts work — declare
  `font-weight: 100 900` on the `@font-face` (see `brand-feedbackpulse.css`).
- **`pdf/ua-1` needs a `<title>` and `<html lang>`.** Both are in the templates;
  deleting them degrades the tagged-PDF output.
- **Keep it under 600 KB.** `optimize_images=True` is on; don't embed large
  rasters. The asset should be email-attachable and fast to crawl.
- **Put the key points as text on the download page too**, so AI has the "answer
  key" without parsing the PDF.

## References

- **Example brand presets**: `styles/brand-feedbackpulse.css` (Product Bridge
  Blue) and `styles/brand-enpstools.css` (navy/teal, the clearest contrast demo).
- **Sample output**: regenerate any time with `gen.py` into `out/`.
- **Bundled fonts**: `fonts/` with the OFL license text for each family.
- **Deeper PDF/UA validation** (optional): run the file through veraPDF for a
  formal accessibility conformance claim; `verify.py` checks the structural
  markers (tags, metadata, selectable text), not full PDF/UA conformance.

