← Tools Portal

THE BLACKCAT PDF Studio

# THE BLACKCAT PDF Studio

## Purpose

Turn plain text or markdown into a polished, branded PDF: proposals,
one-pagers, memos. Paste content, pick a brand, press one button, get a PDF.

## Launch

```bash
scripts/open-pdf-studio.command
```

Then open `http://127.0.0.1:8778`.

## Usage

1. Paste the content (markdown or plain text) into the big box. You can also
   drag a `.md` / `.txt` file onto it to load it.
2. Pick a brand (BLACKCAT, NDC, or Neutral).
3. Press **"Generar PDF"**. Preview it inline, download it, or open the folder.

Advanced (optional): a document title (renders large at the top) and a custom
output filename.

## Brands

Three presets are hardcoded in `brands.py` (palette + Google Fonts + header /
footer):

- **blackcat** - magenta `#D90467`, near-black `#1A1718`, warm-white, warm-gray.
  Rubik headings + Open Sans body. Magenta headings on light paper, near-black
  header band with a magenta hairline.
- **ndc** - navy `#152864`, green `#008237`, lightblue `#0092CD`, orange
  `#F8AA00`, warm grey. Montserrat throughout. Clean institutional layout:
  navy top rule + navy wordmark, navy/green/lightblue heading hierarchy.
- **neutral** - sober grays, no logo, system typography. For generic docs.

## Engine

Markdown -> self-contained branded HTML -> Chrome headless PDF. The markdown
parser is a small in-house one (headings `#`/`##`/`###`, `**bold**`,
`*italic*`, `` `code` ``, `[links](url)`, `-`/`1.` lists, `---` rules,
blank-line paragraphs). No external markdown dependency.

### Chrome quirk (already solved - do not rediscover)

`--print-to-pdf` writes the file but the Chrome process can hang, and in
**Chrome 149** the CLI `--no-pdf-header-footer` flag no longer works (the PDF
comes out with a `file://` path and date in the footer). So this Studio drives
Chrome over **CDP `Page.printToPDF`** instead:

- Chrome is launched with `Popen` (`--headless=new --remote-debugging-port` +
  a throwaway `--user-data-dir`), we poll `/json/version` until it is up, then
  render and **always terminate the process** in a `finally` (no orphan left).
- `displayHeaderFooter=false` (no file path / date footer), `printBackground=
  true` (brand fills print), CDP `paperWidth/Height` + `margin*` give real
  per-page A4 margins with no header/footer.
- `websockets.connect(..., max_size=None)` because the base64 PDF exceeds the
  1 MB default frame size.

See the `rendering-branded-pdfs` skill for the full recipe.

## Endpoints

- `GET /api/health` - `{ chromeReady, brands }` (chromeReady = Google Chrome.app
  is installed).
- `GET /api/brands` - brand list for the UI cards.
- `POST /api/generate` - `{ content, brand, title?, outputName? }` -> async job.
  Rejects empty content (400) and unknown brands (400).
- `GET /api/status` - job snapshot (progress + step + result).
- `GET /api/download/<file>` - serve a generated PDF.
- `POST /api/open-folder` - reveal `output/` in Finder.

## Notes

- Generated PDFs live in `output/` (git-ignored, local only).
- Requires Google Chrome installed at the standard `/Applications` path.
- All rendering happens on this Mac; nothing is uploaded.