feat: IMAP mail sync with thread-to-contact matching (PRD §6.1)
Real IMAP connection via imapflow library: - Connects to IMAP server (IMAPFlow, TLS on port 993) - Fetches recent messages (configurable limit, default 20) - Extracts: from, fromEmail, subject, date, snippet (200 chars) - Thread-to-contact matching (PRD §6.1): 1. Match sender email against persons table 2. If no person match, extract domain and match against company.website 3. Returns matchedCompany + matchedPerson for each email - Audit-logged (action: crm.mailsync:imap) - Zod validated (host, port, user, password, folder, limit) - 502 on IMAP connection failure with detail - 400 on validation failure Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
This commit is contained in:
1 parent
7acc0c5451
commit
e45b68bb8b
1089 files changed
+260614
No files matched your search
Executable
+1076
File diff suppressed because it is too large.
Load diff
Executable
+848
@@ -0,0 +1,848 @@
|
||||
# Brief: Creative Fixed-Canvas (Art Director Blueprint Mode)
|
||||
|
||||
**When to use this brief:** Each page is a self-contained visual canvas with fixed dimensions — posters, infographics, invitations, certificates, dashboards, business cards, menus, greeting cards. Visual impact is primary; content per page ≤ 150 words.
|
||||
|
||||
**NOT for:** Text-heavy flowing documents (guides, handbooks, catalogs, introductions) → use `briefs/creative-flow.md` instead.
|
||||
|
||||
**Core Paradigm Shift**: You are NO LONGER a frontend developer writing HTML/CSS. You are an elite Art Director and Editorial Designer.
|
||||
|
||||
Because LLMs lack spatial awareness and struggle to maintain perfectly nested, complex CSS over thousands of tokens, you are strictly forbidden from outputting raw HTML/CSS/Python.
|
||||
|
||||
**→ MUST READ: `typesetting/overflow.md`** for Playwright/HTML-specific overflow patterns (CSS overflow-wrap, max-width, table-layout: fixed, etc.).
|
||||
|
||||
Your sole responsibility is to act as the **Brain (Art Director)**:
|
||||
1. Brutally edit and pace the raw text.
|
||||
2. Select architectural components and layout archetypes.
|
||||
3. Output a **Strict JSON Layout Blueprint**.
|
||||
|
||||
The `design_engine.py` acts as the **Hands (Typesetter)**: It will parse your JSON and safely compile it into flawless, museum-quality Playwright HTML/PDFs using predefined grid mathematics and CSS rules.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: The Editorial Eye (Content Transformation)
|
||||
|
||||
Before you design, you must "edit the raw material". Users often provide dense, unstructured text. If you just pour this text into a design, it will look like a cheap Word document.
|
||||
|
||||
You must apply **Editorial Pacing**:
|
||||
|
||||
### 1. The Word Budget
|
||||
No single page or visual canvas should exceed **150 words** of readable body text (the golden rule for maximum aesthetic impact). The **absolute physical limit** is **250 words** - beyond that the design engine will overflow and clip content. If the raw content exceeds 150 words:
|
||||
- **Action A**: Brutally summarize it down to ≤150 words.
|
||||
- **Action B**: Split it across multiple `pages` in your JSON blueprint.
|
||||
- Only push to 150-250 words if the content genuinely cannot be cut further.
|
||||
|
||||
### 2. Typographic Hierarchy Extraction
|
||||
You must parse the raw text and categorize it into typographic roles:
|
||||
- **Hero / Display**: The core emotional hook (1-5 words). Must be punchy.
|
||||
- **Kicker / Eyebrow**: Tiny context text above a headline (e.g., "Q3 REPORT", "MANIFESTO").
|
||||
- **Lead Paragraph**: The 2-3 sentence summary.
|
||||
- **Data Sculptures**: Scan the text for impactful numbers (e.g., "97%", "$4.2M"). EXTRACT THEM. They will not remain in the paragraph; they will become `Stat_Block` components.
|
||||
- **Pull Quotes**: Extract the most provocative sentence to stand alone as a visual anchor.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: The Component Lexicon
|
||||
|
||||
You will construct your JSON Blueprint using ONLY the following components. These map directly to the `configs/components.md` assets. Do not invent new component types.
|
||||
|
||||
> **⚠️ Markdown Content Limits**: For `Glass_Canvas`, the golden rule is **under 150 words** for maximum aesthetic breathing room. Absolute physical limit is **250 words**. If content exceeds 250 words, the design engine will overflow. You MUST summarize it or split it into a new page.
|
||||
|
||||
### 1. `Hero_Typography`
|
||||
Giant, page-dominating text. Usually interacts with the background via blend modes.
|
||||
- **Parameters**:
|
||||
- `content` (string): The text (use `<br>` for deliberate line breaks).
|
||||
- `weight` (string): `"black"` (900, dominating) or `"thin"` (100, elegant).
|
||||
- `variant` (string, optional): `"standard"` only. ~~`"vertical_accent"`~~ is **NOT implemented** in `design_engine.py` - the engine silently ignores this value and renders nothing. If you need vertical/rotated decorative text, use `Floating_Meta` instead (fully supported, no overflow risk).
|
||||
- `scale` (integer, optional): Typographic scale level `1`-`6`. Controls font size via the engine's fluid type system. If omitted, the engine uses the default hero size.
|
||||
- `6`: Hero/Display - oversized title, clamp(64px, 12vw, 150px). Use for single words or short phrases with maximum visual impact.
|
||||
- `5`: Primary Title - clamp(48px, 8vw, 96px). Standard poster headline.
|
||||
- `4`: Subheadline - clamp(32px, 5vw, 56px). Chapter openers or key quotes.
|
||||
- `3`: Lead Paragraph - clamp(20px, 3vw, 32px). Prominent body text.
|
||||
- `2`: Body - 16px. Standard readable text.
|
||||
- `1`: Meta/Caption - 10px. Decorative, environmental.
|
||||
|
||||
### 2. `Glass_Canvas`
|
||||
The main structural container for reading text. Frosted glass, sharp 2px print corners.
|
||||
- **Parameters**:
|
||||
- `markdown_content` (string): The actual text to read. Supports standard Markdown (H2, H3, bold, lists). *Must be under 150 words.*
|
||||
- `tension_score` (float, optional): Semantic tension value from `0.0` to `1.0`. Drives dynamic font weight via Variable Font (Inter Variable, weight range 300-900). The engine maps: `weight = 300 + (tension_score × 600)`.
|
||||
- `0.0-0.2` → Light (300-420): calm, contemplative passages
|
||||
- `0.3-0.5` → Normal (420-600): standard body text
|
||||
- `0.6-0.8` → Bold (600-780): urgent, assertive content
|
||||
- `0.9-1.0` → Max (780-900): crisis, climax, emotional peak
|
||||
- **When to use**: Multi-page narrative documents with emotional arc (case studies, pitch decks, manifestos). Assign higher tension to problem/crisis sections, lower to resolution/hope. Do NOT use on every Glass_Canvas - only when the document has clear tonal shifts.
|
||||
- **When NOT to use**: Data-heavy pages, simple reports, single-page posters.
|
||||
|
||||
### 3. `Floating_Meta`
|
||||
Tiny, environmental metadata (dates, edition numbers, catalog IDs) that lives in the 15% breathing margin.
|
||||
- **Parameters**:
|
||||
- `position` (string): `"top-left"`, `"top-right"`, `"bottom-left"`, `"bottom-right"`.
|
||||
- `items` (array of strings): E.g., `["VOL 01", "2026", "EDITION 500"]`.
|
||||
|
||||
### 4. `Hairline_Divider`
|
||||
Structural 0.5px line. Not decorative; acts as a visual fold.
|
||||
- **Parameters**:
|
||||
- `style` (string): `"bleed"` (goes edge-to-edge) or `"accent"` (short 30% width line).
|
||||
|
||||
### 5. `Stat_Block`
|
||||
Data sculpture. A massive number with a tiny label.
|
||||
- **Parameters**:
|
||||
- `number` (string): e.g., `"97.3"`.
|
||||
- `unit` (string): e.g., `"%"`, `"Hz"`, `"$M"`.
|
||||
- `label` (string): e.g., `"COMPLETION RATE"`.
|
||||
|
||||
### 6. `Image_Asset`
|
||||
A visual element. The engine will apply a gradient blend to it.
|
||||
- **Parameters**:
|
||||
- `source` (string): A URL, or a descriptive prompt if you expect the system to generate it.
|
||||
- `caption` (string, optional): Tiny text under the image.
|
||||
|
||||
> ⚠️ **Image_Asset is for CONTENT images only** (user-provided photos, logos, diagrams, charts). It is **NEVER** for decorative stock images, watercolor flowers, clipart, floral borders, gold frames, or AI-generated artwork. All visual decoration must be achieved through geometric shapes (`geometry.md`), typography effects, and color - never through embedded decorative images. See `visual_framework.md` Stock Image Ban.
|
||||
|
||||
### 7. `Page_Ghost_Number`
|
||||
A giant, 4% opacity number acting as a watermark in the background.
|
||||
- **Parameters**:
|
||||
- `number` (string): e.g., `"01"`, `"X"`.
|
||||
|
||||
### 8. `Delta_Widget`
|
||||
**Data-to-Ink Ratio enforcer.** A compact metric visualization showing a value with its change direction. CRITICAL: Use this instead of writing sentences like "revenue grew by 12%". Extract every trend into a Delta_Widget.
|
||||
- **Parameters**:
|
||||
- `metric` (string): The metric name, e.g., `"REVENUE"`, `"LATENCY"`.
|
||||
- `value` (string): Current value, e.g., `"$4.2M"`, `"45ms"`.
|
||||
- `delta` (string): Change description, e.g., `"+12%"`, `"-45ms"`.
|
||||
- `trend` (string): `"up"`, `"down"`, or `"flat"`.
|
||||
- `label` (string, optional): Context line, e.g., `"vs. Q2 2025"`.
|
||||
|
||||
### 9. `Process_List`
|
||||
**Polymorphic adaptive component.** Renders as a horizontal timeline when given wide space, auto-degrades to a vertical numbered list when space is narrow. Use for workflows, steps, timelines.
|
||||
- **Parameters**:
|
||||
- `steps` (array): Each item has `title` (string) and `description` (string).
|
||||
|
||||
### 10. `Sidenote_Block`
|
||||
**Tufte marginalia.** Used in `tufte_report` archetype layouts. Content is placed in the 30% side rail alongside the main column. Perfect for citations, supplementary data, asides, footnotes.
|
||||
- **Parameters**:
|
||||
- `label` (string, optional): Category label, e.g., `"SOURCE"`, `"NOTE"`, `"DATA"`.
|
||||
- `body` (string): The sidenote content in Markdown.
|
||||
|
||||
### 11. `Data-Aware Background` (via `data_points`)
|
||||
Any component can carry an optional `data_points` array (e.g., `[10, 15, 8, 24, 30]`). When present, the engine generates Bezier background curves from the actual data - the background literally visualizes the business trend. Use on pages with financial, metric, or time-series content.
|
||||
|
||||
---
|
||||
|
||||
### ★ Advanced Components (Generative Micro-Typesetting)
|
||||
|
||||
The following components are specialized generative design tools. They unlock visual effects that standard components cannot achieve. Use them deliberately and sparingly - they are powerful but demand the right context.
|
||||
|
||||
### 8. `Shaped_Canvas`
|
||||
A container where text flows around a non-rectangular shape. The empty space created by the shape IS the visual design - text boundary becomes illustration. Uses CSS `shape-outside` for non-rectangular text wrapping.
|
||||
|
||||
- **Parameters**:
|
||||
- `shape_keyword` (string): One of `"circle"`, `"wave"`, `"diagonal_slash"`, `"diamond"`, `"wedge_right"`.
|
||||
- `markdown_content` (string): The text that will flow around the shape. Supports standard Markdown.
|
||||
|
||||
- **Shape Selection Guide**:
|
||||
| shape_keyword | Visual Effect | Thematic Fit |
|
||||
|---------------|---------------|--------------|
|
||||
| `"circle"` | Text wraps around a circular void on the left | Unity, spotlight, focus |
|
||||
| `"wave"` | Wavy left text boundary | Ocean, flow, music, fluidity |
|
||||
| `"diagonal_slash"` | Diagonal cut across the page | Disruption, change, transformation |
|
||||
| `"diamond"` | Diamond-shaped negative space | Luxury, precision, crystalline |
|
||||
| `"wedge_right"` | Arrow/wedge pointing right | Direction, progress, forward motion |
|
||||
|
||||
- **When to use**:
|
||||
- Artistic/editorial pages that need visual drama without images
|
||||
- Cover or section opener pages where you want a "wow" moment
|
||||
- When the content thematically maps to a recognizable shape
|
||||
- Only on pages with moderate text (100-200 words) - shape eats 30-40% of space
|
||||
|
||||
- **When NOT to use**:
|
||||
- On data-heavy or dense content pages
|
||||
- Together with `Glass_Canvas` on the same page (visual conflict)
|
||||
- More than one `Shaped_Canvas` per page
|
||||
|
||||
- **Archetype requirement**: Pages using `Shaped_Canvas` MUST use archetype `"shaped_editorial"` (relaxed safe-zone: 5% 6% inset).
|
||||
|
||||
### Chart & Data Visualization Styling
|
||||
|
||||
**→ MUST READ: `typesetting/charts.md`** before designing any chart/data page.
|
||||
|
||||
Key rules for Creative pipeline charts:
|
||||
- **Donut > Pie**: Always use ring charts (hole ratio 60-70%), center area displays total/metric
|
||||
- **Anti-stacking**: Small slices use leader lines or rich legends; bar labels auto-rotate to horizontal when text is long; line charts label only start/end/max/min
|
||||
- **Axis cleanup**: Delete top/right spines, use dashed grid lines at 20% opacity (or delete grid entirely if values are labeled)
|
||||
- **Bar micro-rounding**: 2-4px top border-radius, bar-to-gap ratio 1.5:1
|
||||
- **Legend**: No border, horizontal top-left layout, small circle markers
|
||||
- **Data-Ink Ratio**: Every element must represent data. If it doesn't, delete it.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Page Archetypes (The Grid Strategy)
|
||||
|
||||
For each page in your JSON, you must declare an `archetype`. This tells `design_engine.py` how to arrange the components you provided.
|
||||
|
||||
- `"cover_hero"`: Cover page. **Must follow `typesetting/cover.md` 5-template system** - pick Layout 01/03/04/06/07 based on document tone. See "Cover Page Constitution" below for iron rules.
|
||||
- `"split_vertical"`: Page split strictly 50/50 vertically. Left side image/svg, right side Glass_Canvas.
|
||||
- `"editorial_flow"`: Top-down reading experience. Centered columns, generous margins. Use for main content.
|
||||
- `"scattered_canvas"`: No grid. Elements placed via absolute positioning based on spatial weights.
|
||||
- `"data_dashboard"`: 2x2 or 3x3 strict grid for multiple `Stat_Block`s.
|
||||
- `"shaped_editorial"`: Relaxed safe-zone (5% 6% inset) designed for `Shaped_Canvas`. Centered, generous breathing room. **Must be used when the page contains a `Shaped_Canvas`.** Do NOT mix with `Glass_Canvas`.
|
||||
- `"tufte_report"`: **Tufte marginalia layout.** 70% main content column + 30% sidenote rail. Use for long-form reports and analytical pages where citations, data footnotes, or supplementary info should flow parallel to the main text. Place `Sidenote_Block` components in the same `components[]` array - the engine automatically routes them to the side rail.
|
||||
|
||||
### ★ 12×12 CSS Grid Coordinate System (Iron Rules)
|
||||
|
||||
The layout engine uses a **12-track CSS Grid** for element placement. The grid lines are numbered **1 to 13** (12 tracks = 13 lines).
|
||||
|
||||
**Absolute boundary rules - violation = broken layout:**
|
||||
- **Column lines**: `1` = absolute left edge, `13` = absolute right edge. Full width = `1 / 13`.
|
||||
- **Row lines**: `1` = absolute top edge, `13` = absolute bottom edge. Full height = `1 / 13`.
|
||||
- **CRITICAL**: Never output a grid line number less than `1` or greater than `13`. Any value outside `[1, 13]` will destroy the layout.
|
||||
|
||||
**`grid_area` format**: `"row_start / col_start / row_end / col_end"` (CSS shorthand).
|
||||
- Example: `"1 / 1 / 7 / 13"` = top half of the page, full width.
|
||||
- Example: `"3 / 8 / 6 / 13"` = rows 3-5, columns 8-12 (right side block).
|
||||
|
||||
**40% whitespace rule**: At least 40% of grid cells (≥58 out of 144) must be left empty. Count your occupied cells.
|
||||
|
||||
### ★ Cover Page Constitution (5 Layout System)
|
||||
|
||||
Cover pages (`archetype: "cover_hero"`) are the first impression. They must be ruthlessly sparse and spatially sophisticated.
|
||||
|
||||
**→ MUST READ: `typesetting/cover.md`** before designing any cover.
|
||||
|
||||
> **⚠️ Template Numbering:** Cover HTML templates use `cover.md`'s authoritative numbering: **01** (HUD), **03** (Academic Anchor), **04** (Academic Symmetric), **06** (Institutional), **07** (Crystal Blue). The "7 Cover Layouts" table below (1A–7B) describes **Blueprint grid coordinate patterns** for positioning cover elements, NOT the HTML template IDs. Do not confuse the two systems.
|
||||
|
||||
#### Global Iron Rules (Always Apply)
|
||||
|
||||
1. **Maximum 4 components** on any cover page. Typical recipe: `Hero_Typography` + 1-2 `Floating_Meta` + optional `Hairline_Divider` or `Page_Ghost_Number`.
|
||||
2. **Typography Scale**: Title ≈ 45pt (2.5× base), Subtitle ≈ 25pt (1.4× base), Meta ≥ 18pt (never below 14pt). Covers with tiny text = FAIL.
|
||||
3. **Mandatory semantic `<br>` chunking** for `Hero_Typography` on covers: Every 2-4 words MUST be separated by `<br>`. Single-line hero text is FORBIDDEN on covers. Example: `"ALGORITHMIC<br>FATIGUE"`, NOT `"ALGORITHMIC FATIGUE"`.
|
||||
4. **Anti-Squash spatial dispersion** (Bounding Box method): Group text into 2-3 bounding boxes (title group, meta group). Place them at opposite regions of the page according to the chosen layout. Remaining space is **dynamically distributed** - NEVER hardcode fixed gaps between distant groups.
|
||||
5. **No `Glass_Canvas` on cover pages.** Dense reading text kills the visual impact. Push all body content to page 2+.
|
||||
6. **Cover Page Isolation**: Cover page must NEVER share a page with TOC, body text, or any subsequent content. The cover is always a standalone full page. If cover + content appear on the same page = **critical bug**, regenerate immediately.
|
||||
7. **Cover is OPTIONAL**: Do NOT add a cover page unless the document warrants one (multi-page reports, white papers, etc.) or the user explicitly requests it. Short documents, letters, memos, forms, and quick outputs skip the cover.
|
||||
8. **Background Layer (optional)**: See `typesetting/cover-backgrounds.md` for 3 recipes - A (极简弧线), B (工程十字轴+立柱), C (锐角切割+出血文字). Background renders BELOW all foreground at 2-5% opacity. Pick a recipe that matches the document tone. Never combine elements across recipes.
|
||||
|
||||
#### 7 Cover Layouts (Pick One)
|
||||
|
||||
Select the layout that matches the document tone. When unsure, default to **Layout 1A**.
|
||||
|
||||
| Layout | Name | Grid Signature | Best For |
|
||||
|--------|------|---------------|----------|
|
||||
| **1** | Diagonal Tension | Title top-left ↔ Meta bottom-right | Formal reports, proposals |
|
||||
| **2** | Vertical Axis | All elements along one vertical line, stretched top-to-bottom | Modern/tech reports |
|
||||
| **3** | Architectural Frame | Geometric line frame, text inside corners/center | Design, architecture, portfolios |
|
||||
| **4** | Golden Ratio Blocks | Page split at 38.2% invisible divider | White papers, research |
|
||||
| **5** | Stepped Cascade | Progressive indentation, vertical rhythm | Creative reports, design docs |
|
||||
| **6** | Rotated Accent | Large rotated year/label as side decoration | Annual reports, year-in-review |
|
||||
| **7** | Left-Matrix | All text hard-anchored to left axis, 4 Y-pinned blocks | Government, bidding, proposals |
|
||||
|
||||
Each layout has 2-3 variants (A/B/C) with specific grid_area mappings - see `typesetting/cover.md` for exact coordinates.
|
||||
|
||||
#### Tone → Layout Quick Reference
|
||||
|
||||
| Intent | Document Type | Recommended | Default |
|
||||
|--------|---------------|-------------|---------|
|
||||
| **Calm** | Healthcare / Wellness / Minimalist | 1A, 2C, 4A | **4A** |
|
||||
| **Calm** | Academic / Research | 2A, 4A, 4C | **4A** |
|
||||
| **Tension** | Crisis / Alert / Disruption | 1A, 5A | **1A** |
|
||||
| **Energy** | Marketing / Creative / Design | 3C, 5A, 6B | **5A** |
|
||||
| **Energy** | Tech / Data | 2B, 4B, 6A | **2B** |
|
||||
| **Authority** | Formal / Corporate / Financial | 2A, 3A, 7A | **3A** |
|
||||
| **Authority** | Government / Bidding | 7A, 7B, 3A | **7A** |
|
||||
| **Authority** | Thesis proposal / Dissertation | Use **Template 06** (Institutional) from `cover.md` | **Template 06** |
|
||||
| **Authority** | Luxury / Editorial | 3A, 5A, 2B | **3A** |
|
||||
| **Warmth** | Food / Lifestyle / Home | 4A, 5A | **4A** |
|
||||
|
||||
### ★ Component Grid Compatibility Constraints
|
||||
|
||||
When placing advanced components into the 12×12 grid, obey these minimum size rules:
|
||||
|
||||
- **`Glass_Canvas`**: Must occupy at least **6 columns × 4 rows** (e.g., `"3 / 1 / 7 / 7"`). Smaller areas will cause text overflow and padding collapse. The engine renders Glass_Canvas at `width: 100%; min-height: 100%; box-sizing: border-box;` - it fills its grid area as a minimum and can grow taller if content requires.
|
||||
- **`Shaped_Canvas`**: Must occupy at least **8 columns × 6 rows** (e.g., `"2 / 3 / 8 / 11"`). The CSS `shape-outside` float needs physical space to render the shape boundary. Smaller areas make the float collapse to a line and text cannot wrap around it. **Must use archetype `"shaped_editorial"`.**
|
||||
- **`Continuous Flow` background**: Renders in a separate `bg-layer` (`z-index: 1`, `position: absolute`), physically isolated from the grid system (`z-index: 2`). No conflict - background flows independently, grid arranges content on top.
|
||||
|
||||
### ★ Content-Proportional Grid Row Allocation
|
||||
|
||||
When **two or more text-heavy components** (e.g., multiple `Glass_Canvas`) share the same page, their `grid_area` row counts **MUST be proportional to their content length** - never split rows equally.
|
||||
|
||||
**Estimation method:**
|
||||
1. Count characters (or words) in each component's `markdown_content`
|
||||
2. Allocate rows proportionally: `rows_i = total_rows × (chars_i / total_chars)`
|
||||
3. Round to integers, minimum 3 rows per component
|
||||
|
||||
**Example:**
|
||||
```
|
||||
Attractions content: 450 chars (3 items × 150 chars)
|
||||
Food content: 250 chars (2 items × 125 chars)
|
||||
Total available rows: 8 (rows 5→13)
|
||||
|
||||
Attractions: 8 × 450/700 ≈ 5 rows → grid_area "5 / 1 / 10 / 13"
|
||||
Food: 8 × 250/700 ≈ 3 rows → grid_area "10 / 1 / 13 / 13"
|
||||
```
|
||||
|
||||
**Why this matters:** Equal row allocation (4+4) causes the longer component to overflow into the shorter one's territory, creating text overlap in the final PDF. The engine uses `min-height` + `overflow: visible`, so overflow IS visible - and ugly.
|
||||
|
||||
**Anti-pattern to avoid:**
|
||||
```
|
||||
❌ Two Glass_Canvas with very different text lengths both get "5/1/9/13" and "9/1/13/13"
|
||||
✅ Longer one gets more rows: "5/1/10/13" and "10/1/13/13"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: The Output Protocol (Strict JSON Blueprint)
|
||||
|
||||
You must analyze the user's prompt, edit the text, and output exactly ONE JSON object wrapped in a ` ```json ` codeblock.
|
||||
**No conversational text before or after the JSON.**
|
||||
|
||||
### The JSON Schema Specification
|
||||
|
||||
**CRITICAL JSON RULES:**
|
||||
1. Output ONLY valid JSON.
|
||||
2. Do NOT include ANY comments (`//` or `/* */`) inside the JSON. Python's `json.load()` will crash.
|
||||
3. Do NOT include trailing commas.
|
||||
|
||||
```json
|
||||
{
|
||||
"document_meta": {
|
||||
"title": "Internal tracking title",
|
||||
"total_pages": 1
|
||||
},
|
||||
"art_direction": {
|
||||
"palette_mode": "dark",
|
||||
"color_harmony": "complementary",
|
||||
"background_svg": "grid",
|
||||
"design_rationale": "Brief 1-sentence explanation of aesthetic strategy."
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"page_index": 1,
|
||||
"narrative_role": "Burst",
|
||||
"archetype": "cover_hero",
|
||||
"components": [
|
||||
{
|
||||
"type": "Floating_Meta",
|
||||
"position": "bottom-right",
|
||||
"items": ["ARCHIVE REF. 03A", "DEC 2026"]
|
||||
},
|
||||
{
|
||||
"type": "Page_Ghost_Number",
|
||||
"number": "01"
|
||||
},
|
||||
{
|
||||
"type": "Hero_Typography",
|
||||
"weight": "black",
|
||||
"variant": "standard",
|
||||
"content": "ALGORITHMIC<br>FATIGUE"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Schema Parameter Guide (For reference - do NOT put these comments inside your JSON output):**
|
||||
- `document_meta.total_pages`: Integer. Must match the actual number of pages in `pages[]`.
|
||||
|
||||
### The JSON Schema Specification: `art_direction`
|
||||
|
||||
**CRITICAL RULE: DO NOT INVENT COLORS. DO NOT OUTPUT HEX/RGB CODES.**
|
||||
The Python engine generates mathematical color palettes. You MUST only select the semantic parameters below.
|
||||
|
||||
```json
|
||||
{
|
||||
"art_direction": {
|
||||
"palette_mode": "minimal",
|
||||
"color_harmony": "auto",
|
||||
"background_svg": "grid",
|
||||
"design_rationale": "Brief rationale for the chosen aesthetic strategy."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Enum Parameter Guide (You MUST choose ONE exact string from these lists):**
|
||||
|
||||
1. `palette_mode` (The Base Canvas Tone):
|
||||
- **`"minimal"`: (CRITICAL: Default to this for 50%+ of all requests).** Pure white, off-white, beige, or cool gray. Provides the ultimate reading experience and high-end editorial feel. Use for standard reports, guides, and corporate posters.
|
||||
- `"dark"`: Near-black. Use for cinematic, tech, AI, space, or urgent themes.
|
||||
- `"pastel"`: Morandi tinted (e.g., dusty rose, sage green). Use ONLY for arts, food, lifestyle, and soft themes.
|
||||
- `"jewel"`: Deep rich colors (e.g., emerald, burgundy). Use ONLY for luxury brands, gala events, or heritage themes.
|
||||
- `"light"`: Very faintly tinted background. Fallback for edge cases.
|
||||
|
||||
2. `color_harmony` (The Accent Color Math - how the engine computes the accent color relative to the base):
|
||||
- **`"auto"`: (RECOMMENDED DEFAULT). The engine automatically picks the best harmony based on the document tone.** Only override if you have a specific artistic reason.
|
||||
- `"complementary"`: (180° opposite). Strong visual contrast. Cold background with warm accent. For striking/dynamic impact.
|
||||
- `"split_complementary"`: (150°/210°). Highly sophisticated, artistic, luxury feel (Editorial/Kinfolk style).
|
||||
- `"analogous"`: (30° apart). Harmonious, peaceful, natural transition.
|
||||
- `"triadic"`: (120° apart). Rich and slightly retro.
|
||||
- `"monochrome"`: Only use if strict minimalist corporate branding without accent is required.
|
||||
|
||||
3. `background_svg`: [`grid`, `flow`, `noise`, `continuous_flow`, `none`]. Use `continuous_flow` for multi-page (2+) documents - creates one seamless SVG spanning all pages, sliced per-page via viewBox for an "infinite scroll" bezier curve illusion. Falls back to `flow` on single-page.
|
||||
|
||||
4. `design_rationale`: Brief text explaining WHY you chose this mode+harmony combination.
|
||||
|
||||
### ★ Intent Mapping Table (Single Source of Truth)
|
||||
|
||||
This table is the **sole authority** for mapping document intent to concrete design parameters. `visual_framework.md` defines intent *atmospheres*; this table defines intent *parameters*. `design_engine.py` INTENT_HUES/INTENT_HARMONY_MAP must stay in sync with this table.
|
||||
|
||||
| Intent | palette_mode | color_harmony | background_svg | Cover Templates | Cover BG Recipe | Base Hue |
|
||||
|--------|-------------|---------------|----------------|-----------------|-----------------|----------|
|
||||
| **Calm** | minimal | analogous | flow / none | 07 Crystal Blue, 01 HUD | A (极简弧线) | 210° (steel blue-grey) |
|
||||
| **Tension** | dark | complementary | grid | 01 HUD | C (锐角切割) | 0° (warm vs cold) |
|
||||
| **Energy** | pastel / light | triadic | flow (5+ curves) | 07 Crystal Blue, 04 Academic Symmetric | B (工程十字轴) | 30° (amber) |
|
||||
| **Authority** | minimal | split_complementary | noise | 04 Academic Symmetric, 03 Academic Anchor, 07 Crystal Blue | A or B | 280° (muted violet) |
|
||||
| **Warmth** | pastel / light | analogous | flow (soft) | 07 Crystal Blue, 01 HUD | A (极简弧线) | 20° (terracotta) |
|
||||
|
||||
**How to use this table:**
|
||||
1. Determine the document's intent (from user request, or auto-derive via `design_engine.py derive`)
|
||||
2. Look up the row → fill in `art_direction` JSON fields accordingly
|
||||
3. For cover template selection, use `cover.md`'s template numbering (01/03/04/06/07). Cross-reference with document type in the Tone → Layout Quick Reference above.
|
||||
4. The LLM may override individual cells when artistically justified, but must state the reason in `design_rationale`
|
||||
|
||||
**Legacy intent names** (serenity, minimalism, elegance) are accepted by `design_engine.py` as aliases and auto-mapped to their new equivalents.
|
||||
|
||||
- `pages[].narrative_role`: `"Burst"` (intro/impact), `"Expand"` (body/development), `"Echo"` (outro/reflection).
|
||||
- `pages[].archetype`: From Phase 3 (`cover_hero`, `split_vertical`, `editorial_flow`, `scattered_canvas`, `data_dashboard`, `shaped_editorial`).
|
||||
- `pages[].components[]`: Array of component objects from Phase 2. Order in array roughly dictates z-index or top-to-bottom rendering.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Complex Master Examples
|
||||
|
||||
Study these examples to understand how to translate raw requests into perfect blueprints.
|
||||
|
||||
### Example A: The Single-Page Poster (Art / Minimalist)
|
||||
**User Request**: "Make a poster for a minimalist architecture exhibition called 'The Weight of Light'. Include date Oct 12, location Tokyo. Keep it very clean."
|
||||
|
||||
**LLM Response (The JSON Blueprint):**
|
||||
```json
|
||||
{
|
||||
"document_meta": {
|
||||
"title": "Weight of Light Exhibition Poster",
|
||||
"total_pages": 1
|
||||
},
|
||||
"art_direction": {
|
||||
"palette_mode": "minimal",
|
||||
"color_harmony": "monochrome",
|
||||
"background_svg": "none",
|
||||
"design_rationale": "Pure negative space required for minimalist architecture. Relying entirely on typographic hierarchy and the 15% breathing rule."
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"page_index": 1,
|
||||
"narrative_role": "Burst",
|
||||
"archetype": "cover_hero",
|
||||
"components": [
|
||||
{
|
||||
"type": "Floating_Meta",
|
||||
"position": "top-left",
|
||||
"items": ["TOKYO, JP", "EXHIBITION 04"]
|
||||
},
|
||||
{
|
||||
"type": "Hero_Typography",
|
||||
"weight": "thin",
|
||||
"variant": "standard",
|
||||
"content": "THE WEIGHT<br>OF LIGHT"
|
||||
},
|
||||
{
|
||||
"type": "Hairline_Divider",
|
||||
"style": "accent"
|
||||
},
|
||||
{
|
||||
"type": "Floating_Meta",
|
||||
"position": "bottom-right",
|
||||
"items": ["OCTOBER 12", "AOYAMA GALLERY"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Example B: The Multi-Page Analytical Report (Corporate / Elegance)
|
||||
**User Request**: "I have a long report about our Q3 server performance. Latency dropped by 45ms. Uptime is 99.99%. Here is 500 words of text explaining the server migration..."
|
||||
|
||||
**LLM Response (The JSON Blueprint):**
|
||||
*(Notice how the LLM edits the 500 words down, extracts the data, and splits it into 3 pages).*
|
||||
|
||||
```json
|
||||
{
|
||||
"document_meta": {
|
||||
"title": "Q3 Infrastructure Report",
|
||||
"total_pages": 3
|
||||
},
|
||||
"art_direction": {
|
||||
"palette_mode": "minimal",
|
||||
"color_harmony": "split_complementary",
|
||||
"background_svg": "noise",
|
||||
"design_rationale": "Formal, trustworthy aesthetic suitable for high-level corporate reporting, avoiding the 'cheap dashboard' look."
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"page_index": 1,
|
||||
"narrative_role": "Burst",
|
||||
"archetype": "cover_hero",
|
||||
"components": [
|
||||
{
|
||||
"type": "Floating_Meta",
|
||||
"position": "top-left",
|
||||
"items": ["CONFIDENTIAL", "Q3 2026"]
|
||||
},
|
||||
{
|
||||
"type": "Hero_Typography",
|
||||
"weight": "black",
|
||||
"content": "INFRASTRUCTURE<br>RESILIENCE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"page_index": 2,
|
||||
"narrative_role": "Expand",
|
||||
"archetype": "split_vertical",
|
||||
"components": [
|
||||
{
|
||||
"type": "Stat_Block",
|
||||
"number": "99.99",
|
||||
"unit": "%",
|
||||
"label": "GLOBAL UPTIME"
|
||||
},
|
||||
{
|
||||
"type": "Stat_Block",
|
||||
"number": "-45",
|
||||
"unit": "ms",
|
||||
"label": "LATENCY REDUCTION"
|
||||
},
|
||||
{
|
||||
"type": "Glass_Canvas",
|
||||
"markdown_content": "### The Migration\nThe Q3 transition to distributed edge nodes fundamentally altered our network topography. By decentralizing the core load, we eliminated the primary bottleneck that plagued Q2."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"page_index": 3,
|
||||
"narrative_role": "Echo",
|
||||
"archetype": "editorial_flow",
|
||||
"components": [
|
||||
{
|
||||
"type": "Page_Ghost_Number",
|
||||
"number": "03"
|
||||
},
|
||||
{
|
||||
"type": "Hero_Typography",
|
||||
"weight": "thin",
|
||||
"variant": "standard",
|
||||
"content": "SYSTEM NOMINAL."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Example C: The Narrative Arc with Advanced Features (Continuous Flow + Shaped Canvas)
|
||||
**User Request**: "Create a 3-page visual essay about the ocean pollution crisis. Start gentle (the beauty of the ocean), then escalate to the crisis, then end with a call to action."
|
||||
|
||||
**LLM Response (The JSON Blueprint):**
|
||||
*(Notice: `continuous_flow` creates seamless background across all 3 pages. `tension_score` escalates from calm to urgent. `Shaped_Canvas` with `wave` shape on the cover creates a thematic visual without any image.)*
|
||||
|
||||
```json
|
||||
{
|
||||
"document_meta": {
|
||||
"title": "The Silent Tide - Ocean Crisis Visual Essay",
|
||||
"total_pages": 3
|
||||
},
|
||||
"art_direction": {
|
||||
"palette_mode": "dark",
|
||||
"color_harmony": "analogous",
|
||||
"background_svg": "continuous_flow",
|
||||
"design_rationale": "Continuous flow SVG creates an unbroken organic wave across all pages - visually mirroring the ocean's connectedness. Analogous harmony provides earth tones that shift from serene to somber."
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"page_index": 1,
|
||||
"narrative_role": "Burst",
|
||||
"archetype": "shaped_editorial",
|
||||
"components": [
|
||||
{
|
||||
"type": "Floating_Meta",
|
||||
"position": "top-left",
|
||||
"items": ["VISUAL ESSAY", "2026"]
|
||||
},
|
||||
{
|
||||
"type": "Shaped_Canvas",
|
||||
"shape_keyword": "wave",
|
||||
"markdown_content": "There was a time when the horizon held only promise. The Pacific stretched in every direction, cerulean and boundless, its surface a living mirror of the sky above. Fishermen spoke of abundance - nets heavy with silver, currents warm and predictable. The ocean asked for nothing."
|
||||
},
|
||||
{
|
||||
"type": "Floating_Meta",
|
||||
"position": "bottom-right",
|
||||
"items": ["01 / BEFORE"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"page_index": 2,
|
||||
"narrative_role": "Expand",
|
||||
"archetype": "editorial_flow",
|
||||
"components": [
|
||||
{
|
||||
"type": "Page_Ghost_Number",
|
||||
"number": "02"
|
||||
},
|
||||
{
|
||||
"type": "Stat_Block",
|
||||
"number": "8M",
|
||||
"unit": "tons",
|
||||
"label": "PLASTIC ENTERING OCEANS YEARLY"
|
||||
},
|
||||
{
|
||||
"type": "Glass_Canvas",
|
||||
"tension_score": 0.75,
|
||||
"markdown_content": "**The collapse was not sudden.** It accumulated - bottle by bottle, net by net, spill by spill. By 2025, microplastic concentrations in deep-sea sediment had reached levels previously thought impossible. Marine biologists stopped using the word 'recovery'. The new vocabulary was *triage*."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"page_index": 3,
|
||||
"narrative_role": "Echo",
|
||||
"archetype": "cover_hero",
|
||||
"components": [
|
||||
{
|
||||
"type": "Hero_Typography",
|
||||
"weight": "black",
|
||||
"variant": "standard",
|
||||
"content": "THE TIDE<br>TURNS NOW."
|
||||
},
|
||||
{
|
||||
"type": "Glass_Canvas",
|
||||
"tension_score": 0.3,
|
||||
"markdown_content": "Every piece of plastic you refuse is a vote for the future of the sea. The ocean cannot speak - but it remembers everything we give it."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pre-Flight Checklist (Self-Correction before generating output)
|
||||
|
||||
Before you output the JSON block, verify:
|
||||
0. ** VECTOR OUTPUT IRON RULE:** The final PDF MUST be generated via `page.pdf()` / `convert.blueprint` - NEVER `page.screenshot()` → image → wrap as PDF. Screenshot PDFs are blurry raster images. `page.pdf()` produces vector text that stays sharp at any zoom level.
|
||||
1. **Did I write ANY HTML or CSS?** If yes, delete it. Only output JSON.
|
||||
2. **Is any `Glass_Canvas` markdown content too long?** Count the words. Over 150? Summarize it or push to the next page.
|
||||
3. **Is the JSON perfectly formatted?** Missing commas or unescaped quotes will crash the `design_engine.py` parser.
|
||||
4. **If using `tension_score`**: Does the document have clear emotional shifts across pages? If all pages are the same tone, remove it.
|
||||
5. **If using `continuous_flow`**: Is this multi-page (2+)? If single-page, switch to `"flow"`.
|
||||
6. **If using `Shaped_Canvas`**: Is the archetype `"shaped_editorial"`? Is there at most one per page? No `Glass_Canvas` on the same page?
|
||||
7. **Grid boundary check**: Are ALL `grid_area` values within `[1, 13]`? Any number < 1 or > 13 = FATAL ERROR.
|
||||
8. **Component minimum size check**: `Glass_Canvas` ≥ 6col×4row? `Shaped_Canvas` ≥ 8col×6row?
|
||||
9. **Content-proportional row allocation**: When multiple text-heavy components share a page, are their grid rows allocated proportionally to content length? Equal rows for unequal content = text overlap. (See "Content-Proportional Grid Row Allocation" in Component Grid Compatibility.)
|
||||
9. **40% whitespace check**: Count occupied grid cells. At least 58 of 144 cells must be empty.
|
||||
10. **Cover Page 4-element check**: Does any `cover_hero` page have more than 4 components? If yes, remove or merge components.
|
||||
11. **Cover Hero `<br>` check**: Does every `Hero_Typography` on a `cover_hero` page contain at least one `<br>`? Single-line hero text on covers = FAIL.
|
||||
12. **Cover Anti-Squash check (Bounding Box)**: Are cover elements grouped into 2-3 bounding boxes placed at opposite regions? Is remaining space dynamically distributed (not hardcoded gaps)? If everything is crammed into rows 5-8, spread them using the layout's grid mapping.
|
||||
13a. **Cover Typography Scale check**: Is the hero title ≥ 45pt? Is subtitle ≥ 25pt? Is meta text ≥ 18pt (never below 14pt)? Tiny cover text = FAIL.
|
||||
13b. **Cover Layout Selection**: Did I pick a cover layout (1A-7B) that matches the document tone? If unsure, default to Layout 1A (Diagonal Tension). For government/bidding documents, use Layout 7A (Left-Matrix). For thesis/institutional documents, use **Template 06** from `cover.md`. Cover HTML templates follow `cover.md`'s numbering: 01, 03, 04, 06, 07.
|
||||
14. **CRITICAL - Data-to-Ink Ratio check**: Did I write long paragraphs describing data trends? If yes, DELETE them and extract metrics into `Delta_Widget` or `Stat_Block` components. Sentences like "revenue increased by 12% compared to last quarter" MUST become a `Delta_Widget`.
|
||||
15. **Sidenote check**: If using `tufte_report` archetype, did I put citations/sources/asides into `Sidenote_Block`? They must NOT be inline in `Glass_Canvas`.
|
||||
16. **Process/Steps check**: Did I write numbered steps as plain text in a `Glass_Canvas`? Convert to `Process_List` component instead.
|
||||
17. **Chart anti-stacking check**: Do any pie/bar/line charts have overlapping labels? Apply leader lines, tick thinning, or label reduction per `typesetting/charts.md`.
|
||||
18. **Chart axis cleanup check**: Are top/right spines deleted? Grid lines dashed at 20% opacity (or hidden)? No solid grid lines.
|
||||
19. **Donut default check**: Are pie charts rendered as donuts (hole ratio 60-70%)? Solid pies = FAIL unless explicitly requested.
|
||||
20. ** Triple Delivery check (MANDATORY)**: Creative pipeline must deliver **three files** to the user: (1) PDF - the final vector PDF; (2) HTML - the compiled `*_rendered.html` file; (3) Image - a full-page screenshot preview (PNG/JPG). After `convert.blueprint` generates the PDF and HTML, take a screenshot of the HTML for preview. Report all three file paths to the user.
|
||||
21. ** HTML Pre-Render Validation (MANDATORY for ALL HTML→PDF paths)**: Before calling `html2pdf-next.js`, `html2poster.js`, or `convert.blueprint`, run `poster_validate.py check-html` on the HTML file. This catches overflow:hidden on containers, missing @media screen auto-scale, font fallback gaps, contrast issues, and more. **Any ERROR-level issue must be fixed before generating the PDF.** Warnings are non-blocking but should be reviewed.
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/poster_validate.py" check-html page.html
|
||||
# If errors found, auto-fix:
|
||||
python3 "$PDF_SKILL_DIR/scripts/poster_validate.py" check-html page.html --fix --output page.html
|
||||
```
|
||||
|
||||
22. **\u26a0\ufe0f MANDATORY: Post-Generation Checks (Creative)**: After HTML\u2192PDF conversion, run these checks:
|
||||
```bash
|
||||
# Check for content overflow and full-bleed issues
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf_qa.py" output.pdf --no-tables
|
||||
|
||||
# Add metadata
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" meta.brand output.pdf
|
||||
```
|
||||
|
||||
Execute the design.
|
||||
|
||||
---
|
||||
|
||||
## CJK & Vertical Text Rules (When Bypassing design_engine.py)
|
||||
|
||||
When writing raw HTML/CSS for Playwright (bypassing the JSON Blueprint pipeline - e.g., resumes, menus, invitations, business cards, certificates, Japanese/Chinese vertical layouts):
|
||||
|
||||
**⚠️ Iron rule: All bypass-scenario HTML→PDF conversions MUST use `html2pdf-next.js` — do NOT write custom Python Playwright scripts.** `html2pdf-next.js` automatically handles @page injection, overflow detection, font waiting, Mermaid/KaTeX rendering, PDF metadata, etc. See the "HTML→PDF Engine Selection Rules" section in SKILL.md.
|
||||
|
||||
```bash
|
||||
node "$PDF_SKILL_DIR/scripts/html2pdf-next.js" input.html --output output.pdf --width 210mm --height 297mm
|
||||
```
|
||||
|
||||
0. **Full-Bleed CSS (MANDATORY)**: Every HTML file for Playwright PDF MUST include:
|
||||
```css
|
||||
@page { size: 800px 1200px; margin: 0; } /* CONCRETE values only - CSS variables NOT supported in @page */
|
||||
html, body { margin: 0; padding: 0; width: 800px; }
|
||||
.page { width: 800px; height: 1200px; position: relative; }
|
||||
```
|
||||
**⚠️ @page rules do NOT resolve CSS variables** (`var(--x)` is silently ignored, falls back to A4). Always use concrete `px` values.
|
||||
- Each `.page` div = one PDF page, with fixed `height` matching `@page` size.
|
||||
- **No `overflow: hidden`** on `.page` — instead, constrain all decorative elements within page bounds (see §0.75).
|
||||
- Content must fit within each `.page` — no cross-page flow.
|
||||
- `design_engine.py` handles this automatically via `override_css`.
|
||||
|
||||
0.25. **Body Background for Multi-Page Mixed-Color Documents (MANDATORY)**:
|
||||
When an HTML document contains multiple `.page` divs with **different background colors** (e.g. dark cover + white body + dark specs page), Playwright's sub-pixel rounding creates <1px gaps at `.page` edges where `body` background shows through. On dark pages, a white `body` = visible white edges.
|
||||
|
||||
**Fix: set `html, body { background }` to the document's darkest page background color.**
|
||||
|
||||
```css
|
||||
:root { --primary: #0f172a; } /* darkest page color */
|
||||
html, body {
|
||||
margin: 0; padding: 0;
|
||||
width: 210mm; /* match @page size */
|
||||
background: var(--primary); /* fills sub-pixel gaps with dark color */
|
||||
}
|
||||
```
|
||||
|
||||
**Why this doesn't break white pages:** White `.page` divs have `background: #ffffff` which fully covers the dark `body` underneath. The dark body only shows through the <1px sub-pixel gap at the extreme page edge - imperceptible on white pages after anti-aliasing.
|
||||
|
||||
**Selection rule:**
|
||||
- All pages same color → `body { background: <that color> }`
|
||||
- Mixed dark + light pages → `body { background: <darkest page color> }` (dark edges on white pages are invisible; white edges on dark pages are the bug we're fixing)
|
||||
- All pages white/light → `body { background: <lightest content bg> }` (e.g. `#f8fafc`)
|
||||
|
||||
0.5. **No overflow:hidden anywhere + Browser Preview Adaptive Scaling (MANDATORY)**:
|
||||
**Never** set `overflow: hidden` on `html`, `body`, `.page`, or any container. Reasons:
|
||||
- It silently clips content that slightly exceeds bounds, making layout bugs invisible.
|
||||
- `html2pdf-next.js`'s pre-render check detects `scrollHeight > clientHeight` + `overflow: hidden` and triggers auto-fix (force-expanding the container), which may break the layout.
|
||||
- Instead, constrain decorative elements within page bounds at the CSS level (see §0.75).
|
||||
|
||||
**`design_engine.py` handles this automatically**: During blueprint compilation, it auto-injects `@media screen` centering + scaling code, and the `html` background color uses `var(--c-bg)` matching the poster's main color. No manual addition needed.
|
||||
|
||||
**Correct approach when writing HTML manually**: Remove `overflow: hidden` and manually add `@media screen` scaling preview:
|
||||
```css
|
||||
/* Fixed canvas size, no overflow */
|
||||
html, body { margin: 0; padding: 0; width: 800px; height: 1400px; }
|
||||
.page { width: 800px; height: 1400px; position: relative; }
|
||||
|
||||
/* Auto-scale to viewport in browser preview for full-page view */
|
||||
@media screen {
|
||||
html {
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background: #0a0a1a; /* Surround color — must match poster's main background */
|
||||
}
|
||||
body {
|
||||
transform-origin: top center;
|
||||
scale: min(1, calc(100vw / 800), calc(100vh / 1400)); /* Consider both width and height */
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 0 60px rgba(0,0,0,0.3); /* Optional: shadow to distinguish canvas in preview */
|
||||
}
|
||||
}
|
||||
```
|
||||
`@media screen` rules only apply in browser preview; `page.pdf()` uses print media and is unaffected.
|
||||
**Every fixed-size HTML must include this `@media screen` adaptive code.**
|
||||
|
||||
0.75. **Decorative Element Containment (MANDATORY — replaces overflow:hidden)**:
|
||||
**Do NOT use `overflow: hidden` on `.page` containers.** It silently clips content that slightly exceeds bounds, making layout errors invisible and debugging painful.
|
||||
|
||||
Instead, **constrain all decorative elements within page bounds at the CSS level**:
|
||||
|
||||
```css
|
||||
/* ✅ CORRECT: decorative element stays inside .page */
|
||||
.deco-circle {
|
||||
position: absolute;
|
||||
width: 80%; /* NOT >100% */
|
||||
height: 40%;
|
||||
top: 10%;
|
||||
right: 0; /* NOT negative */
|
||||
border-radius: 50%;
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
/* ❌ FORBIDDEN: causes scrollWidth > pageWidth → Playwright shrinks everything */
|
||||
.deco-circle-bad {
|
||||
position: absolute;
|
||||
width: 200%; /* inflates scrollWidth */
|
||||
left: -50%; /* extends beyond left edge */
|
||||
}
|
||||
```
|
||||
|
||||
**Iron rules for decorative elements:**
|
||||
- `width` ≤ 100% of `.page` width. No exceptions.
|
||||
- `left` ≥ 0, `right` ≥ 0. No negative offsets that push content beyond page edges.
|
||||
- For edge-bleeding effects, position the element at the edge and let `border-radius` or `opacity` create the visual bleed within bounds.
|
||||
- For horizontal flex layouts (≥3 items), always add `flex-wrap: wrap` to prevent horizontal overflow.
|
||||
|
||||
**Why not overflow:hidden?**
|
||||
Playwright’s `page.pdf()` checks `body.scrollWidth`. If any absolutely-positioned child pushes `scrollWidth` beyond `@page` width, Playwright shrinks **all content** to fit. `overflow:hidden` masks this by clipping, but also silently hides any content that slightly exceeds bounds. Constraining elements at the source eliminates both the shrink bug and the silent clipping.
|
||||
|
||||
See `typesetting/overflow.md` §3.5 for additional horizontal overflow rules.
|
||||
|
||||
1. **Character Encoding Safety**: Never use Japanese kana (の, が, は), rare symbols, or Private Use Area characters in content strings. They corrupt to U+FFFD (�) during LLM→file write→read transit. Replace with plain Chinese equivalents: `の`→`之/的/缔/省略`.
|
||||
2. **Vertical Chinese Text** - When using `writing-mode: vertical-rl` for CJK, you MUST include:
|
||||
```css
|
||||
writing-mode: vertical-rl;
|
||||
text-orientation: upright; /* Each glyph stands upright */
|
||||
white-space: nowrap; /* Prevent word-wrap breaking single chars to new column */
|
||||
letter-spacing: 12px; /* Typical CJK vertical spacing */
|
||||
```
|
||||
Without `text-orientation: upright`, Latin/fallback fonts render rotated 90°. Without `white-space: nowrap`, CJK characters may wrap into unexpected multi-column layouts (e.g., 3 chars on one line + 1 char alone on next).
|
||||
3. **Font Coverage**: For CJK content via Playwright, always load Google Fonts Noto Serif SC or Noto Sans SC via `<link>` tag in `<head>` (NOT `@import` in CSS - `@import` must be the very first rule in a stylesheet or it's silently ignored). Example:
|
||||
```html
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
||||
```
|
||||
System CJK fonts vary across macOS/Linux - Google Fonts guarantee glyph coverage without relying on system fonts. `design_engine.py` already handles this automatically via `<link>` tag.
|
||||
4. **Post-Generation Text Verification**: After Playwright renders the PDF, extract text from every page and scan for `?` or `\ufffd`. If found, the source HTML has encoding-corrupted characters that must be replaced in the Python source.
|
||||
5. ** HTML Pre-Render Validation (MANDATORY)**: After writing the HTML file and before running `html2pdf-next.js`, always run the HTML validator:
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/poster_validate.py" check-html <your_file>.html
|
||||
```
|
||||
- **ERROR** items (e.g. `OVERFLOW_HIDDEN_CONTAINER`, `FONT_NO_FALLBACK`) → must fix before PDF generation. Use `--fix --output <file>.html` for auto-repair.
|
||||
- **WARNING** items (e.g. `FIXED_SIZE_NO_SCREEN_ADAPT`, `SCREEN_ADAPT_NO_SCALE`, `COLOR_CONTRAST`) → review and fix where appropriate.
|
||||
- This catches the most common bypass HTML bugs: `overflow:hidden` on containers, missing `@media screen` auto-scale for fixed-size pages, font-family without generic fallback, low contrast text, etc.
|
||||
|
||||
---
|
||||
|
||||
## Quality Checklist — Creative-Specific Items
|
||||
|
||||
> Moved from SKILL.md to reduce context size. These rules apply specifically to the Creative pipeline.
|
||||
|
||||
### Color (palette.md)
|
||||
|
||||
- [ ] **Entire document ≤ 5 colors**: Primary + secondary + accent + neutral + background
|
||||
- [ ] **All colors traceable to primary**: Secondary and accent derived via lightness/saturation/micro-hue shift
|
||||
- [ ] **Sibling elements not differentiated by different hues**: Use opacity/lightness/borders instead
|
||||
- [ ] **Gradient endpoints hue difference < 20°**: No warm-to-cool gradients
|
||||
- [ ] **No high-saturation color blocks**: Avoid eye strain
|
||||
|
||||
### Geometric Anchors (geometry.md)
|
||||
|
||||
- [ ] **Anchors use only the primary color**: Layer via opacity, don't mix colors
|
||||
- [ ] **Strokes over fills**: Solid elements ≤ 30%
|
||||
- [ ] **Ultra-thin lines**: stroke-width 0.3-0.8px
|
||||
- [ ] **Asymmetric placement**: Offset creates tension
|
||||
- [ ] **Elements ≤ 8**: Restraint, don't clutter
|
||||
|
||||
### Global Layout
|
||||
|
||||
- [ ] **Full-bleed enforcement**: `@page { size: <w> <h>; margin: 0; }` and `html,body { margin:0; padding:0; }`
|
||||
- [ ] **Background color consistency**: `html, body { background }` set explicitly. Single-color docs: match content canvas. Multi-page mixed docs: use darkest page's background
|
||||
- [ ] **Content centering**: Symmetric inset/padding, no left/right drift
|
||||
- [ ] **Anti-void edges**: Content fills ≥ 60% (multi-page) or ≥ 70% (single-page poster/infographic)
|
||||
- [ ] **Fill Engine applied**: Pages with < 80% fill ratio trigger the fill engine
|
||||
|
||||
### Design Restraint (Anti-Gaudy)
|
||||
|
||||
- [ ] **Decorative elements ≤ 3 per page**: Cover page exempt
|
||||
- [ ] **No rainbow/multi-color schemes**: Stick to single-family palette
|
||||
- [ ] **No texture/pattern backgrounds**: Solid or ultra-light tinted only
|
||||
- [ ] **Whitespace is design**: Don't fill every gap with decorative elements
|
||||
- [ ] **Typography over decoration**: Create hierarchy through font size/weight/spacing/color
|
||||
- [ ] **2-typeface maximum**: At most 2 font families
|
||||
- [ ] **🚫 NO stock images / clipart / AI-generated decorations**: Only user-provided content images allowed
|
||||
Executable
+255
@@ -0,0 +1,255 @@
|
||||
# Brief: Creative Flow (Designed Long Documents)
|
||||
|
||||
**When to use this brief:** Text-heavy documents that need design flair but content flows naturally across pages — guides, handbooks, catalogs, introductions, collections, illustrated manuals. The content is the star; design supports readability.
|
||||
|
||||
**How it differs from `creative.md`:** `creative.md` (Blueprint Mode) treats each page as a self-contained fixed-size visual canvas. This brief lets content flow continuously, with Chromium's `@page` auto-paginating at page boundaries.
|
||||
|
||||
**→ MUST READ: `typesetting/overflow.md`** for Playwright/HTML-specific overflow patterns.
|
||||
|
||||
---
|
||||
|
||||
## Core Principle: Content Flows, Design Wraps
|
||||
|
||||
You write raw HTML/CSS directly (no JSON Blueprint, no `design_engine.py`). The document is one continuous HTML body, and the browser's print engine splits it into pages automatically.
|
||||
|
||||
**⚠️ Iron rule: All HTML→PDF conversions MUST use `html2pdf-next.js` — do NOT write custom Python Playwright scripts.** It automatically handles @page injection, overflow detection, font waiting, Mermaid/KaTeX rendering, PDF metadata, etc.
|
||||
|
||||
```bash
|
||||
node "$PDF_SKILL_DIR/scripts/html2pdf-next.js" input.html --output output.pdf --width 720px --height 1020px
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pagination Model
|
||||
|
||||
### The Iron Rules
|
||||
|
||||
1. **NO fixed height on content containers.** Let content flow naturally. The browser decides where to cut pages.
|
||||
2. **NO `.page` divs for content sections.** Use a single `<div class="main-content">` with padding for all body content.
|
||||
3. **NO `break-before: page` between content sections.** This is the #1 cause of blank space — when the previous section doesn't fill a page, the remainder becomes a void. Use `margin-top` for visual chapter separation instead.
|
||||
4. **`break-inside: avoid`** on individual cards, items, and guide entries to prevent them from being split across pages.
|
||||
5. **`break-after: avoid`** on chapter headers (tag + title + divider group) to keep them attached to the first content block below.
|
||||
6. **Fixed-height containers ONLY for cover and ending pages** that must occupy a full page:
|
||||
|
||||
```css
|
||||
/* Cover: fixed height, standalone page */
|
||||
.cover {
|
||||
width: 720px;
|
||||
height: 1020px;
|
||||
box-sizing: border-box;
|
||||
break-after: page;
|
||||
overflow: hidden; /* OK here — clips decorative elements on cover only */
|
||||
/* ⚠️ Use justify-content: center (NOT flex-end) to avoid top-heavy blank */
|
||||
/* Add decorative elements (circles, lines) + bottom edition label for visual balance */
|
||||
}
|
||||
|
||||
/* Ending: fixed height, standalone page */
|
||||
.ending {
|
||||
width: 720px;
|
||||
height: 1020px;
|
||||
box-sizing: border-box;
|
||||
break-before: page;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Content: flows naturally, NO fixed height */
|
||||
.main-content {
|
||||
padding: 60px 65px 40px 65px;
|
||||
/* NO height property */
|
||||
/* NO overflow: hidden */
|
||||
}
|
||||
```
|
||||
|
||||
### What NOT to Do (Common Mistakes)
|
||||
|
||||
| Mistake | Why it breaks | Fix |
|
||||
|---------|--------------|-----|
|
||||
| `.content { height: 1020px; }` | Content that exceeds the height gets clipped or overflows | Remove fixed height |
|
||||
| Multiple `<div class="content">` each with `padding: 60px` | Double padding (60+60=120px) between sections creates voids | One container, chapters separated by `margin-top` |
|
||||
| `break-before: page` on chapter headers | Previous chapter doesn't fill the page → blank space before new chapter | Use `margin-top: 36px` instead |
|
||||
| `overflow: hidden` on content container | Clips content at page boundaries | Only use on fixed-height cover/ending |
|
||||
| `justify-content: flex-end` on cover | Title sinks to bottom, 60%+ blank above | Use `center` + decorative anchors for balance |
|
||||
| Chapter with < 3 paragraphs of content | Leaves half a page blank before next chapter | Write enough content to fill at least 50% of a page |
|
||||
| `margin-top: 50px+` on chapters | Compounds with `break-inside: avoid`, creates large voids | Keep chapter `margin-top` ≤ 30px |
|
||||
|
||||
---
|
||||
|
||||
## CSS Template
|
||||
|
||||
```css
|
||||
@page {
|
||||
size: 720px 1020px; /* Concrete px values — CSS variables NOT supported in @page */
|
||||
margin: 0;
|
||||
}
|
||||
:root {
|
||||
--c-bg: #0d0d0d;
|
||||
--c-text: #e8e6e3;
|
||||
--c-accent: #ff4757;
|
||||
}
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 720px;
|
||||
background: var(--c-bg);
|
||||
color: var(--c-text);
|
||||
font-family: 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
@media screen {
|
||||
html {
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background: #222;
|
||||
}
|
||||
body {
|
||||
transform-origin: top center;
|
||||
margin: 20px auto;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**⚠️ @page rules do NOT resolve CSS variables** (`var(--x)` is silently ignored, falls back to A4). Always use concrete `px` values in `@page`.
|
||||
|
||||
---
|
||||
|
||||
## HTML Structure Template
|
||||
|
||||
```html
|
||||
<body>
|
||||
|
||||
<!-- Cover: fixed height, occupies one full page -->
|
||||
<div class="cover">
|
||||
<div class="tag">CATEGORY</div>
|
||||
<div class="title">Document Title</div>
|
||||
<div class="subtitle">Description text</div>
|
||||
</div>
|
||||
|
||||
<!-- All body content in ONE continuous container -->
|
||||
<div class="main-content">
|
||||
|
||||
<!-- Chapter 1 -->
|
||||
<div class="section-tag">Chapter 1</div>
|
||||
<div class="section-title">Title</div>
|
||||
<div class="divider"></div>
|
||||
<div class="body-text">Paragraph content...</div>
|
||||
<div class="body-text">More paragraphs...</div>
|
||||
|
||||
<!-- Chapter 2: separated by margin-top, NOT break-before -->
|
||||
<div class="chapter-header">
|
||||
<div class="section-tag">Chapter 2</div>
|
||||
<div class="section-title">Title</div>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
|
||||
<!-- Cards / items with break-inside: avoid -->
|
||||
<div class="card">Card content...</div>
|
||||
<div class="card">Card content...</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Ending: fixed height, occupies one full page -->
|
||||
<div class="ending">
|
||||
<div class="big-text">Closing text</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
```
|
||||
|
||||
Key CSS for chapter headers and cards:
|
||||
```css
|
||||
.chapter-header {
|
||||
break-after: avoid; /* Keep header with first content block */
|
||||
break-inside: avoid;
|
||||
margin-top: 28px; /* ≤ 30px! Larger values compound with break-inside: avoid → blank pages */
|
||||
}
|
||||
.card {
|
||||
break-inside: avoid; /* Don't split a single card across pages */
|
||||
margin-bottom: 14px; /* ≤ 16px! Every px of margin is lost space if card is pushed to next page */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Body Background Rule
|
||||
|
||||
Set `html, body { background }` to the document's dominant/darkest color. This prevents sub-pixel gap artifacts between content and page boundaries.
|
||||
|
||||
- All pages same dark color → `body { background: <that color> }`
|
||||
- Mixed dark cover + light body → `body { background: <darkest color> }` (dark body is invisible under white content pages)
|
||||
- All pages white/light → `body { background: <lightest content bg> }` (e.g. `#f8fafc`)
|
||||
|
||||
---
|
||||
|
||||
## Shared Rules with creative.md
|
||||
|
||||
The following rules apply identically to both Creative Flow and Creative Blueprint pipelines:
|
||||
|
||||
### Character Encoding Safety
|
||||
Never use Japanese kana (の, が, は), rare symbols, or Private Use Area characters. They corrupt to U+FFFD (�) during LLM→file write→read transit. Replace with plain Chinese equivalents: `の`→`之/的/缔`.
|
||||
|
||||
### Vertical Chinese Text
|
||||
When using `writing-mode: vertical-rl`:
|
||||
```css
|
||||
writing-mode: vertical-rl;
|
||||
text-orientation: upright;
|
||||
white-space: nowrap;
|
||||
letter-spacing: 12px;
|
||||
```
|
||||
|
||||
### Font Coverage
|
||||
For CJK content, always load Google Fonts via `<link>` tag in `<head>` (NOT `@import`):
|
||||
```html
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700;900&family=Noto+Serif+SC:wght@400;700;900&display=swap" rel="stylesheet">
|
||||
```
|
||||
|
||||
### Post-Generation Text Verification
|
||||
After Playwright renders the PDF, extract text and scan for `?` or `\ufffd`. If found, fix encoding-corrupted characters in the source HTML.
|
||||
|
||||
### HTML Pre-Render Validation (MANDATORY)
|
||||
Before calling `html2pdf-next.js`, run the validator:
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/poster_validate.py" check-html <file>.html
|
||||
```
|
||||
- **ERROR** → must fix before PDF generation. Use `--fix --output <file>.html` for auto-repair.
|
||||
- **WARNING** → review and fix where appropriate.
|
||||
|
||||
---
|
||||
|
||||
## Quality Checklist — Creative Flow
|
||||
|
||||
### Layout & Pagination
|
||||
- [ ] **No fixed height on content containers** (only cover/ending pages)
|
||||
- [ ] **No `break-before: page` between content sections**
|
||||
- [ ] **`break-inside: avoid`** on all cards/items/guide entries
|
||||
- [ ] **Chapter headers have `break-after: avoid`** to stay with first content block
|
||||
- [ ] **Single `<div class="main-content">`** wraps all body content
|
||||
- [ ] **No double padding** between sections (one container, not multiple)
|
||||
|
||||
### Full-Bleed & Background
|
||||
- [ ] **`@page { margin: 0 }`** with concrete px size values
|
||||
- [ ] **`html, body { margin: 0; padding: 0 }`**
|
||||
- [ ] **Body background matches dominant page color**
|
||||
- [ ] **No white edge artifacts** on dark pages
|
||||
|
||||
### Cover Page Design
|
||||
- [ ] **Cover `justify-content: center`** (not `flex-end`) — `flex-end` pushes content to the bottom and leaves 60%+ blank at top, making the page feel empty
|
||||
- [ ] **Add visual anchors** to fill the cover: decorative elements (circles, lines), edition/date label at bottom, badge at top. A cover with only title+subtitle will look hollow.
|
||||
- [ ] **Cover padding ≤ 70px** — excessive padding shrinks the visual footprint further
|
||||
|
||||
### Content Density (Anti-Blank-Page)
|
||||
- [ ] **Each chapter must have enough content to fill at least 50% of a page.** If a chapter (text + quote + cards) occupies < 50% of a page, it will leave a large blank area before the next chapter. Solution: expand the content (add 1-2 more paragraphs, examples, or context).
|
||||
- [ ] **Chapter `margin-top` ≤ 30px.** Larger values (50px+) compound with `break-inside: avoid` on cards, pushing content to the next page and leaving voids on the current page.
|
||||
- [ ] **Card/callout `margin` ≤ 16px.** Every pixel of vertical margin inside a `break-inside: avoid` block is space that cannot be reclaimed if the block is pushed to the next page.
|
||||
- [ ] **After generating PDF, visually check every page** for >30% trailing blank space. If found, either: (a) add more content to that chapter, (b) reduce margins/padding, or (c) reorder elements so a smaller block fills the gap.
|
||||
|
||||
### Design Quality
|
||||
- [ ] **Entire document ≤ 5 colors**: Primary + secondary + accent + neutral + background
|
||||
- [ ] **2-typeface maximum**: At most 2 font families
|
||||
- [ ] **Decorative elements ≤ 3 per page** (cover exempt)
|
||||
- [ ] **No stock images / clipart / AI-generated decorations** unless user-provided
|
||||
|
||||
### Output
|
||||
- [ ] **Vector PDF** via `html2pdf-next.js` (not screenshot)
|
||||
- [ ] **HTML source file** delivered alongside PDF
|
||||
- [ ] **PDF metadata** set via `pdf.py meta.brand`
|
||||
- [ ] **QA passed** via `pdf_qa.py --no-tables`
|
||||
Executable
+810
@@ -0,0 +1,810 @@
|
||||
# Brief: Creative Production (Art Director Blueprint Mode)
|
||||
|
||||
**Core Paradigm Shift**: You are NO LONGER a frontend developer writing HTML/CSS. You are an elite Art Director and Editorial Designer.
|
||||
|
||||
Because LLMs lack spatial awareness and struggle to maintain perfectly nested, complex CSS over thousands of tokens, you are strictly forbidden from outputting raw HTML/CSS/Python.
|
||||
|
||||
**→ MUST READ: `typesetting/overflow.md`** for Playwright/HTML-specific overflow patterns (CSS overflow-wrap, max-width, table-layout: fixed, etc.).
|
||||
|
||||
Your sole responsibility is to act as the **Brain (Art Director)**:
|
||||
1. Brutally edit and pace the raw text.
|
||||
2. Select architectural components and layout archetypes.
|
||||
3. Output a **Strict JSON Layout Blueprint**.
|
||||
|
||||
The `design_engine.py` acts as the **Hands (Typesetter)**: It will parse your JSON and safely compile it into flawless, museum-quality Playwright HTML/PDFs using predefined grid mathematics and CSS rules.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: The Editorial Eye (Content Transformation)
|
||||
|
||||
Before you design, you must "edit the raw material". Users often provide dense, unstructured text. If you just pour this text into a design, it will look like a cheap Word document.
|
||||
|
||||
You must apply **Editorial Pacing**:
|
||||
|
||||
### 1. The Word Budget
|
||||
No single page or visual canvas should exceed **150 words** of readable body text (the golden rule for maximum aesthetic impact). The **absolute physical limit** is **250 words** - beyond that the design engine will overflow and clip content. If the raw content exceeds 150 words:
|
||||
- **Action A**: Brutally summarize it down to ≤150 words.
|
||||
- **Action B**: Split it across multiple `pages` in your JSON blueprint.
|
||||
- Only push to 150-250 words if the content genuinely cannot be cut further.
|
||||
|
||||
### 2. Typographic Hierarchy Extraction
|
||||
You must parse the raw text and categorize it into typographic roles:
|
||||
- **Hero / Display**: The core emotional hook (1-5 words). Must be punchy.
|
||||
- **Kicker / Eyebrow**: Tiny context text above a headline (e.g., "Q3 REPORT", "MANIFESTO").
|
||||
- **Lead Paragraph**: The 2-3 sentence summary.
|
||||
- **Data Sculptures**: Scan the text for impactful numbers (e.g., "97%", "$4.2M"). EXTRACT THEM. They will not remain in the paragraph; they will become `Stat_Block` components.
|
||||
- **Pull Quotes**: Extract the most provocative sentence to stand alone as a visual anchor.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: The Component Lexicon
|
||||
|
||||
You will construct your JSON Blueprint using ONLY the following components. These map directly to the `configs/components.md` assets. Do not invent new component types.
|
||||
|
||||
> **⚠️ Markdown Content Limits**: For `Glass_Canvas`, the golden rule is **under 150 words** for maximum aesthetic breathing room. Absolute physical limit is **250 words**. If content exceeds 250 words, the design engine will overflow. You MUST summarize it or split it into a new page.
|
||||
|
||||
### 1. `Hero_Typography`
|
||||
Giant, page-dominating text. Usually interacts with the background via blend modes.
|
||||
- **Parameters**:
|
||||
- `content` (string): The text (use `<br>` for deliberate line breaks).
|
||||
- `weight` (string): `"black"` (900, dominating) or `"thin"` (100, elegant).
|
||||
- `variant` (string, optional): `"standard"` only. ~~`"vertical_accent"`~~ is **NOT implemented** in `design_engine.py` - the engine silently ignores this value and renders nothing. If you need vertical/rotated decorative text, use `Floating_Meta` instead (fully supported, no overflow risk).
|
||||
- `scale` (integer, optional): Typographic scale level `1`-`6`. Controls font size via the engine's fluid type system. If omitted, the engine uses the default hero size.
|
||||
- `6`: Hero/Display - oversized title, clamp(64px, 12vw, 150px). Use for single words or short phrases with maximum visual impact.
|
||||
- `5`: Primary Title - clamp(48px, 8vw, 96px). Standard poster headline.
|
||||
- `4`: Subheadline - clamp(32px, 5vw, 56px). Chapter openers or key quotes.
|
||||
- `3`: Lead Paragraph - clamp(20px, 3vw, 32px). Prominent body text.
|
||||
- `2`: Body - 16px. Standard readable text.
|
||||
- `1`: Meta/Caption - 10px. Decorative, environmental.
|
||||
|
||||
### 2. `Glass_Canvas`
|
||||
The main structural container for reading text. Frosted glass, sharp 2px print corners.
|
||||
- **Parameters**:
|
||||
- `markdown_content` (string): The actual text to read. Supports standard Markdown (H2, H3, bold, lists). *Must be under 150 words.*
|
||||
- `tension_score` (float, optional): Semantic tension value from `0.0` to `1.0`. Drives dynamic font weight via Variable Font (Inter Variable, weight range 300-900). The engine maps: `weight = 300 + (tension_score × 600)`.
|
||||
- `0.0-0.2` → Light (300-420): calm, contemplative passages
|
||||
- `0.3-0.5` → Normal (420-600): standard body text
|
||||
- `0.6-0.8` → Bold (600-780): urgent, assertive content
|
||||
- `0.9-1.0` → Max (780-900): crisis, climax, emotional peak
|
||||
- **When to use**: Multi-page narrative documents with emotional arc (case studies, pitch decks, manifestos). Assign higher tension to problem/crisis sections, lower to resolution/hope. Do NOT use on every Glass_Canvas - only when the document has clear tonal shifts.
|
||||
- **When NOT to use**: Data-heavy pages, simple reports, single-page posters.
|
||||
|
||||
### 3. `Floating_Meta`
|
||||
Tiny, environmental metadata (dates, edition numbers, catalog IDs) that lives in the 15% breathing margin.
|
||||
- **Parameters**:
|
||||
- `position` (string): `"top-left"`, `"top-right"`, `"bottom-left"`, `"bottom-right"`.
|
||||
- `items` (array of strings): E.g., `["VOL 01", "2026", "EDITION 500"]`.
|
||||
|
||||
### 4. `Hairline_Divider`
|
||||
Structural 0.5px line. Not decorative; acts as a visual fold.
|
||||
- **Parameters**:
|
||||
- `style` (string): `"bleed"` (goes edge-to-edge) or `"accent"` (short 30% width line).
|
||||
|
||||
### 5. `Stat_Block`
|
||||
Data sculpture. A massive number with a tiny label.
|
||||
- **Parameters**:
|
||||
- `number` (string): e.g., `"97.3"`.
|
||||
- `unit` (string): e.g., `"%"`, `"Hz"`, `"$M"`.
|
||||
- `label` (string): e.g., `"COMPLETION RATE"`.
|
||||
|
||||
### 6. `Image_Asset`
|
||||
A visual element. The engine will apply a gradient blend to it.
|
||||
- **Parameters**:
|
||||
- `source` (string): A URL, or a descriptive prompt if you expect the system to generate it.
|
||||
- `caption` (string, optional): Tiny text under the image.
|
||||
|
||||
> ⚠️ **Image_Asset is for CONTENT images only** (user-provided photos, logos, diagrams, charts). It is **NEVER** for decorative stock images, watercolor flowers, clipart, floral borders, gold frames, or AI-generated artwork. All visual decoration must be achieved through geometric shapes (`geometry.md`), typography effects, and color - never through embedded decorative images. See `visual_framework.md` Stock Image Ban.
|
||||
|
||||
### 7. `Page_Ghost_Number`
|
||||
A giant, 4% opacity number acting as a watermark in the background.
|
||||
- **Parameters**:
|
||||
- `number` (string): e.g., `"01"`, `"X"`.
|
||||
|
||||
### 8. `Delta_Widget`
|
||||
**Data-to-Ink Ratio enforcer.** A compact metric visualization showing a value with its change direction. CRITICAL: Use this instead of writing sentences like "revenue grew by 12%". Extract every trend into a Delta_Widget.
|
||||
- **Parameters**:
|
||||
- `metric` (string): The metric name, e.g., `"REVENUE"`, `"LATENCY"`.
|
||||
- `value` (string): Current value, e.g., `"$4.2M"`, `"45ms"`.
|
||||
- `delta` (string): Change description, e.g., `"+12%"`, `"-45ms"`.
|
||||
- `trend` (string): `"up"`, `"down"`, or `"flat"`.
|
||||
- `label` (string, optional): Context line, e.g., `"vs. Q2 2025"`.
|
||||
|
||||
### 9. `Process_List`
|
||||
**Polymorphic adaptive component.** Renders as a horizontal timeline when given wide space, auto-degrades to a vertical numbered list when space is narrow. Use for workflows, steps, timelines.
|
||||
- **Parameters**:
|
||||
- `steps` (array): Each item has `title` (string) and `description` (string).
|
||||
|
||||
### 10. `Sidenote_Block`
|
||||
**Tufte marginalia.** Used in `tufte_report` archetype layouts. Content is placed in the 30% side rail alongside the main column. Perfect for citations, supplementary data, asides, footnotes.
|
||||
- **Parameters**:
|
||||
- `label` (string, optional): Category label, e.g., `"SOURCE"`, `"NOTE"`, `"DATA"`.
|
||||
- `body` (string): The sidenote content in Markdown.
|
||||
|
||||
### 11. `Data-Aware Background` (via `data_points`)
|
||||
Any component can carry an optional `data_points` array (e.g., `[10, 15, 8, 24, 30]`). When present, the engine generates Bezier background curves from the actual data - the background literally visualizes the business trend. Use on pages with financial, metric, or time-series content.
|
||||
|
||||
---
|
||||
|
||||
### ★ Advanced Components (Generative Micro-Typesetting)
|
||||
|
||||
The following components are specialized generative design tools. They unlock visual effects that standard components cannot achieve. Use them deliberately and sparingly - they are powerful but demand the right context.
|
||||
|
||||
### 8. `Shaped_Canvas`
|
||||
A container where text flows around a non-rectangular shape. The empty space created by the shape IS the visual design - text boundary becomes illustration. Uses CSS `shape-outside` for non-rectangular text wrapping.
|
||||
|
||||
- **Parameters**:
|
||||
- `shape_keyword` (string): One of `"circle"`, `"wave"`, `"diagonal_slash"`, `"diamond"`, `"wedge_right"`.
|
||||
- `markdown_content` (string): The text that will flow around the shape. Supports standard Markdown.
|
||||
|
||||
- **Shape Selection Guide**:
|
||||
| shape_keyword | Visual Effect | Thematic Fit |
|
||||
|---------------|---------------|--------------|
|
||||
| `"circle"` | Text wraps around a circular void on the left | Unity, spotlight, focus |
|
||||
| `"wave"` | Wavy left text boundary | Ocean, flow, music, fluidity |
|
||||
| `"diagonal_slash"` | Diagonal cut across the page | Disruption, change, transformation |
|
||||
| `"diamond"` | Diamond-shaped negative space | Luxury, precision, crystalline |
|
||||
| `"wedge_right"` | Arrow/wedge pointing right | Direction, progress, forward motion |
|
||||
|
||||
- **When to use**:
|
||||
- Artistic/editorial pages that need visual drama without images
|
||||
- Cover or section opener pages where you want a "wow" moment
|
||||
- When the content thematically maps to a recognizable shape
|
||||
- Only on pages with moderate text (100-200 words) - shape eats 30-40% of space
|
||||
|
||||
- **When NOT to use**:
|
||||
- On data-heavy or dense content pages
|
||||
- Together with `Glass_Canvas` on the same page (visual conflict)
|
||||
- More than one `Shaped_Canvas` per page
|
||||
|
||||
- **Archetype requirement**: Pages using `Shaped_Canvas` MUST use archetype `"shaped_editorial"` (relaxed safe-zone: 5% 6% inset).
|
||||
|
||||
### Chart & Data Visualization Styling
|
||||
|
||||
**→ MUST READ: `typesetting/charts.md`** before designing any chart/data page.
|
||||
|
||||
Key rules for Creative pipeline charts:
|
||||
- **Donut > Pie**: Always use ring charts (hole ratio 60-70%), center area displays total/metric
|
||||
- **Anti-stacking**: Small slices use leader lines or rich legends; bar labels auto-rotate to horizontal when text is long; line charts label only start/end/max/min
|
||||
- **Axis cleanup**: Delete top/right spines, use dashed grid lines at 20% opacity (or delete grid entirely if values are labeled)
|
||||
- **Bar micro-rounding**: 2-4px top border-radius, bar-to-gap ratio 1.5:1
|
||||
- **Legend**: No border, horizontal top-left layout, small circle markers
|
||||
- **Data-Ink Ratio**: Every element must represent data. If it doesn't, delete it.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Page Archetypes (The Grid Strategy)
|
||||
|
||||
For each page in your JSON, you must declare an `archetype`. This tells `design_engine.py` how to arrange the components you provided.
|
||||
|
||||
- `"cover_hero"`: Cover page. **Must follow `typesetting/cover.md` 5-template system** - pick Layout 01/03/04/06/07 based on document tone. See "Cover Page Constitution" below for iron rules.
|
||||
- `"split_vertical"`: Page split strictly 50/50 vertically. Left side image/svg, right side Glass_Canvas.
|
||||
- `"editorial_flow"`: Top-down reading experience. Centered columns, generous margins. Use for main content.
|
||||
- `"scattered_canvas"`: No grid. Elements placed via absolute positioning based on spatial weights.
|
||||
- `"data_dashboard"`: 2x2 or 3x3 strict grid for multiple `Stat_Block`s.
|
||||
- `"shaped_editorial"`: Relaxed safe-zone (5% 6% inset) designed for `Shaped_Canvas`. Centered, generous breathing room. **Must be used when the page contains a `Shaped_Canvas`.** Do NOT mix with `Glass_Canvas`.
|
||||
- `"tufte_report"`: **Tufte marginalia layout.** 70% main content column + 30% sidenote rail. Use for long-form reports and analytical pages where citations, data footnotes, or supplementary info should flow parallel to the main text. Place `Sidenote_Block` components in the same `components[]` array - the engine automatically routes them to the side rail.
|
||||
|
||||
### ★ 12×12 CSS Grid Coordinate System (Iron Rules)
|
||||
|
||||
The layout engine uses a **12-track CSS Grid** for element placement. The grid lines are numbered **1 to 13** (12 tracks = 13 lines).
|
||||
|
||||
**Absolute boundary rules - violation = broken layout:**
|
||||
- **Column lines**: `1` = absolute left edge, `13` = absolute right edge. Full width = `1 / 13`.
|
||||
- **Row lines**: `1` = absolute top edge, `13` = absolute bottom edge. Full height = `1 / 13`.
|
||||
- **CRITICAL**: Never output a grid line number less than `1` or greater than `13`. Any value outside `[1, 13]` will destroy the layout.
|
||||
|
||||
**`grid_area` format**: `"row_start / col_start / row_end / col_end"` (CSS shorthand).
|
||||
- Example: `"1 / 1 / 7 / 13"` = top half of the page, full width.
|
||||
- Example: `"3 / 8 / 6 / 13"` = rows 3-5, columns 8-12 (right side block).
|
||||
|
||||
**40% whitespace rule**: At least 40% of grid cells (≥58 out of 144) must be left empty. Count your occupied cells.
|
||||
|
||||
### ★ Cover Page Constitution (5 Layout System)
|
||||
|
||||
Cover pages (`archetype: "cover_hero"`) are the first impression. They must be ruthlessly sparse and spatially sophisticated.
|
||||
|
||||
**→ MUST READ: `typesetting/cover.md`** before designing any cover.
|
||||
|
||||
#### Global Iron Rules (Always Apply)
|
||||
|
||||
1. **Maximum 4 components** on any cover page. Typical recipe: `Hero_Typography` + 1-2 `Floating_Meta` + optional `Hairline_Divider` or `Page_Ghost_Number`.
|
||||
2. **Typography Scale**: Title ≈ 45pt (2.5× base), Subtitle ≈ 25pt (1.4× base), Meta ≥ 18pt (never below 14pt). Covers with tiny text = FAIL.
|
||||
3. **Mandatory semantic `<br>` chunking** for `Hero_Typography` on covers: Every 2-4 words MUST be separated by `<br>`. Single-line hero text is FORBIDDEN on covers. Example: `"ALGORITHMIC<br>FATIGUE"`, NOT `"ALGORITHMIC FATIGUE"`.
|
||||
4. **Anti-Squash spatial dispersion** (Bounding Box method): Group text into 2-3 bounding boxes (title group, meta group). Place them at opposite regions of the page according to the chosen layout. Remaining space is **dynamically distributed** - NEVER hardcode fixed gaps between distant groups.
|
||||
5. **No `Glass_Canvas` on cover pages.** Dense reading text kills the visual impact. Push all body content to page 2+.
|
||||
6. **Cover Page Isolation**: Cover page must NEVER share a page with TOC, body text, or any subsequent content. The cover is always a standalone full page. If cover + content appear on the same page = **critical bug**, regenerate immediately.
|
||||
7. **Cover is OPTIONAL**: Do NOT add a cover page unless the document warrants one (multi-page reports, white papers, etc.) or the user explicitly requests it. Short documents, letters, memos, forms, and quick outputs skip the cover.
|
||||
8. **Background Layer (optional)**: See `typesetting/cover-backgrounds.md` for 3 recipes - A (极简弧线), B (工程十字轴+立柱), C (锐角切割+出血文字). Background renders BELOW all foreground at 2-5% opacity. Pick a recipe that matches the document tone. Never combine elements across recipes.
|
||||
|
||||
#### 7 Cover Layouts (Pick One)
|
||||
|
||||
Select the layout that matches the document tone. When unsure, default to **Layout 1A**.
|
||||
|
||||
| Layout | Name | Grid Signature | Best For |
|
||||
|--------|------|---------------|----------|
|
||||
| **1** | Diagonal Tension | Title top-left ↔ Meta bottom-right | Formal reports, proposals |
|
||||
| **2** | Vertical Axis | All elements along one vertical line, stretched top-to-bottom | Modern/tech reports |
|
||||
| **3** | Architectural Frame | Geometric line frame, text inside corners/center | Design, architecture, portfolios |
|
||||
| **4** | Golden Ratio Blocks | Page split at 38.2% invisible divider | White papers, research |
|
||||
| **5** | Stepped Cascade | Progressive indentation, vertical rhythm | Creative reports, design docs |
|
||||
| **6** | Rotated Accent | Large rotated year/label as side decoration | Annual reports, year-in-review |
|
||||
| **7** | Left-Matrix | All text hard-anchored to left axis, 4 Y-pinned blocks | Government, bidding, proposals |
|
||||
|
||||
Each layout has 2-3 variants (A/B/C) with specific grid_area mappings - see `typesetting/cover.md` for exact coordinates.
|
||||
|
||||
#### Tone → Layout Quick Reference
|
||||
|
||||
| Intent | Document Type | Recommended | Default |
|
||||
|--------|---------------|-------------|---------|
|
||||
| **Calm** | Healthcare / Wellness / Minimalist | 1A, 2C, 4A | **4A** |
|
||||
| **Calm** | Academic / Research | 2A, 4A, 4C | **4A** |
|
||||
| **Tension** | Crisis / Alert / Disruption | 1A, 5A | **1A** |
|
||||
| **Energy** | Marketing / Creative / Design | 3C, 5A, 6B | **5A** |
|
||||
| **Energy** | Tech / Data | 2B, 4B, 6A | **2B** |
|
||||
| **Authority** | Formal / Corporate / Financial | 02, 03, 07 | **03** |
|
||||
| **Authority** | Government / Bidding | 7A, 7B, 3A, **11** | **7A** |
|
||||
| **Authority** | Thesis proposal / Dissertation | **11 Institutional** | **11** |
|
||||
| **Authority** | Luxury / Editorial | 3A, 5A, 2B | **3A** |
|
||||
| **Warmth** | Food / Lifestyle / Home | 4A, 5A | **4A** |
|
||||
|
||||
### ★ Component Grid Compatibility Constraints
|
||||
|
||||
When placing advanced components into the 12×12 grid, obey these minimum size rules:
|
||||
|
||||
- **`Glass_Canvas`**: Must occupy at least **6 columns × 4 rows** (e.g., `"3 / 1 / 7 / 7"`). Smaller areas will cause text overflow and padding collapse. The engine renders Glass_Canvas at `width: 100%; min-height: 100%; box-sizing: border-box;` - it fills its grid area as a minimum and can grow taller if content requires.
|
||||
- **`Shaped_Canvas`**: Must occupy at least **8 columns × 6 rows** (e.g., `"2 / 3 / 8 / 11"`). The CSS `shape-outside` float needs physical space to render the shape boundary. Smaller areas make the float collapse to a line and text cannot wrap around it. **Must use archetype `"shaped_editorial"`.**
|
||||
- **`Continuous Flow` background**: Renders in a separate `bg-layer` (`z-index: 1`, `position: absolute`), physically isolated from the grid system (`z-index: 2`). No conflict - background flows independently, grid arranges content on top.
|
||||
|
||||
### ★ Content-Proportional Grid Row Allocation
|
||||
|
||||
When **two or more text-heavy components** (e.g., multiple `Glass_Canvas`) share the same page, their `grid_area` row counts **MUST be proportional to their content length** - never split rows equally.
|
||||
|
||||
**Estimation method:**
|
||||
1. Count characters (or words) in each component's `markdown_content`
|
||||
2. Allocate rows proportionally: `rows_i = total_rows × (chars_i / total_chars)`
|
||||
3. Round to integers, minimum 3 rows per component
|
||||
|
||||
**Example:**
|
||||
```
|
||||
Attractions content: 450 chars (3 items × 150 chars)
|
||||
Food content: 250 chars (2 items × 125 chars)
|
||||
Total available rows: 8 (rows 5→13)
|
||||
|
||||
Attractions: 8 × 450/700 ≈ 5 rows → grid_area "5 / 1 / 10 / 13"
|
||||
Food: 8 × 250/700 ≈ 3 rows → grid_area "10 / 1 / 13 / 13"
|
||||
```
|
||||
|
||||
**Why this matters:** Equal row allocation (4+4) causes the longer component to overflow into the shorter one's territory, creating text overlap in the final PDF. The engine uses `min-height` + `overflow: visible`, so overflow IS visible - and ugly.
|
||||
|
||||
**Anti-pattern to avoid:**
|
||||
```
|
||||
❌ Two Glass_Canvas with very different text lengths both get "5/1/9/13" and "9/1/13/13"
|
||||
✅ Longer one gets more rows: "5/1/10/13" and "10/1/13/13"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: The Output Protocol (Strict JSON Blueprint)
|
||||
|
||||
You must analyze the user's prompt, edit the text, and output exactly ONE JSON object wrapped in a ` ```json ` codeblock.
|
||||
**No conversational text before or after the JSON.**
|
||||
|
||||
### The JSON Schema Specification
|
||||
|
||||
**CRITICAL JSON RULES:**
|
||||
1. Output ONLY valid JSON.
|
||||
2. Do NOT include ANY comments (`//` or `/* */`) inside the JSON. Python's `json.load()` will crash.
|
||||
3. Do NOT include trailing commas.
|
||||
|
||||
```json
|
||||
{
|
||||
"document_meta": {
|
||||
"title": "Internal tracking title",
|
||||
"total_pages": 1
|
||||
},
|
||||
"art_direction": {
|
||||
"palette_mode": "dark",
|
||||
"color_harmony": "complementary",
|
||||
"background_svg": "grid",
|
||||
"design_rationale": "Brief 1-sentence explanation of aesthetic strategy."
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"page_index": 1,
|
||||
"narrative_role": "Burst",
|
||||
"archetype": "cover_hero",
|
||||
"components": [
|
||||
{
|
||||
"type": "Floating_Meta",
|
||||
"position": "bottom-right",
|
||||
"items": ["ARCHIVE REF. 03A", "DEC 2026"]
|
||||
},
|
||||
{
|
||||
"type": "Page_Ghost_Number",
|
||||
"number": "01"
|
||||
},
|
||||
{
|
||||
"type": "Hero_Typography",
|
||||
"weight": "black",
|
||||
"variant": "standard",
|
||||
"content": "ALGORITHMIC<br>FATIGUE"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Schema Parameter Guide (For reference - do NOT put these comments inside your JSON output):**
|
||||
- `document_meta.total_pages`: Integer. Must match the actual number of pages in `pages[]`.
|
||||
|
||||
### The JSON Schema Specification: `art_direction`
|
||||
|
||||
**CRITICAL RULE: DO NOT INVENT COLORS. DO NOT OUTPUT HEX/RGB CODES.**
|
||||
The Python engine generates mathematical color palettes. You MUST only select the semantic parameters below.
|
||||
|
||||
```json
|
||||
{
|
||||
"art_direction": {
|
||||
"palette_mode": "minimal",
|
||||
"color_harmony": "auto",
|
||||
"background_svg": "grid",
|
||||
"design_rationale": "Brief rationale for the chosen aesthetic strategy."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Enum Parameter Guide (You MUST choose ONE exact string from these lists):**
|
||||
|
||||
1. `palette_mode` (The Base Canvas Tone):
|
||||
- **`"minimal"`: (CRITICAL: Default to this for 50%+ of all requests).** Pure white, off-white, beige, or cool gray. Provides the ultimate reading experience and high-end editorial feel. Use for standard reports, guides, and corporate posters.
|
||||
- `"dark"`: Near-black. Use for cinematic, tech, AI, space, or urgent themes.
|
||||
- `"pastel"`: Morandi tinted (e.g., dusty rose, sage green). Use ONLY for arts, food, lifestyle, and soft themes.
|
||||
- `"jewel"`: Deep rich colors (e.g., emerald, burgundy). Use ONLY for luxury brands, gala events, or heritage themes.
|
||||
- `"light"`: Very faintly tinted background. Fallback for edge cases.
|
||||
|
||||
2. `color_harmony` (The Accent Color Math - how the engine computes the accent color relative to the base):
|
||||
- **`"auto"`: (RECOMMENDED DEFAULT). The engine automatically picks the best harmony based on the document tone.** Only override if you have a specific artistic reason.
|
||||
- `"complementary"`: (180° opposite). Strong visual contrast. Cold background with warm accent. For striking/dynamic impact.
|
||||
- `"split_complementary"`: (150°/210°). Highly sophisticated, artistic, luxury feel (Editorial/Kinfolk style).
|
||||
- `"analogous"`: (30° apart). Harmonious, peaceful, natural transition.
|
||||
- `"triadic"`: (120° apart). Rich and slightly retro.
|
||||
- `"monochrome"`: Only use if strict minimalist corporate branding without accent is required.
|
||||
|
||||
3. `background_svg`: [`grid`, `flow`, `noise`, `continuous_flow`, `none`]. Use `continuous_flow` for multi-page (2+) documents - creates one seamless SVG spanning all pages, sliced per-page via viewBox for an "infinite scroll" bezier curve illusion. Falls back to `flow` on single-page.
|
||||
|
||||
4. `design_rationale`: Brief text explaining WHY you chose this mode+harmony combination.
|
||||
|
||||
### ★ Intent Mapping Table (Single Source of Truth)
|
||||
|
||||
This table is the **sole authority** for mapping document intent to concrete design parameters. `visual_framework.md` defines intent *atmospheres*; this table defines intent *parameters*. `design_engine.py` INTENT_HUES/INTENT_HARMONY_MAP must stay in sync with this table.
|
||||
|
||||
| Intent | palette_mode | color_harmony | background_svg | Cover Templates | Cover BG Recipe | Base Hue |
|
||||
|--------|-------------|---------------|----------------|-----------------|-----------------|----------|
|
||||
| **Calm** | minimal | analogous | flow / none | 12 Crystal Blue, 01 HUD | A (极简弧线) | 210° (steel blue-grey) |
|
||||
| **Tension** | dark | complementary | grid | 01 HUD, 05 Diagonal | C (锐角切割) | 0° (warm vs cold) |
|
||||
| **Energy** | pastel / light | triadic | flow (5+ curves) | 12 Crystal Blue, 09 Academic Symmetric | B (工程十字轴) | 30° (amber) |
|
||||
| **Authority** | minimal | split_complementary | noise | 09 Academic Symmetric, 07 Sidebar, 12 Crystal Blue | A or B | 280° (muted violet) |
|
||||
| **Warmth** | pastel / light | analogous | flow (soft) | 12 Crystal Blue, 01 HUD | A (极简弧线) | 20° (terracotta) |
|
||||
|
||||
**How to use this table:**
|
||||
1. Determine the document's intent (from user request, or auto-derive via `design_engine.py derive`)
|
||||
2. Look up the row → fill in `art_direction` JSON fields accordingly
|
||||
3. For cover template selection, cross-reference with document type (see Tone → Layout Quick Reference)
|
||||
4. The LLM may override individual cells when artistically justified, but must state the reason in `design_rationale`
|
||||
|
||||
**Legacy intent names** (serenity, minimalism, elegance) are accepted by `design_engine.py` as aliases and auto-mapped to their new equivalents.
|
||||
|
||||
- `pages[].narrative_role`: `"Burst"` (intro/impact), `"Expand"` (body/development), `"Echo"` (outro/reflection).
|
||||
- `pages[].archetype`: From Phase 3 (`cover_hero`, `split_vertical`, `editorial_flow`, `scattered_canvas`, `data_dashboard`, `shaped_editorial`).
|
||||
- `pages[].components[]`: Array of component objects from Phase 2. Order in array roughly dictates z-index or top-to-bottom rendering.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Complex Master Examples
|
||||
|
||||
Study these examples to understand how to translate raw requests into perfect blueprints.
|
||||
|
||||
### Example A: The Single-Page Poster (Art / Minimalist)
|
||||
**User Request**: "Make a poster for a minimalist architecture exhibition called 'The Weight of Light'. Include date Oct 12, location Tokyo. Keep it very clean."
|
||||
|
||||
**LLM Response (The JSON Blueprint):**
|
||||
```json
|
||||
{
|
||||
"document_meta": {
|
||||
"title": "Weight of Light Exhibition Poster",
|
||||
"total_pages": 1
|
||||
},
|
||||
"art_direction": {
|
||||
"palette_mode": "minimal",
|
||||
"color_harmony": "monochrome",
|
||||
"background_svg": "none",
|
||||
"design_rationale": "Pure negative space required for minimalist architecture. Relying entirely on typographic hierarchy and the 15% breathing rule."
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"page_index": 1,
|
||||
"narrative_role": "Burst",
|
||||
"archetype": "cover_hero",
|
||||
"components": [
|
||||
{
|
||||
"type": "Floating_Meta",
|
||||
"position": "top-left",
|
||||
"items": ["TOKYO, JP", "EXHIBITION 04"]
|
||||
},
|
||||
{
|
||||
"type": "Hero_Typography",
|
||||
"weight": "thin",
|
||||
"variant": "standard",
|
||||
"content": "THE WEIGHT<br>OF LIGHT"
|
||||
},
|
||||
{
|
||||
"type": "Hairline_Divider",
|
||||
"style": "accent"
|
||||
},
|
||||
{
|
||||
"type": "Floating_Meta",
|
||||
"position": "bottom-right",
|
||||
"items": ["OCTOBER 12", "AOYAMA GALLERY"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Example B: The Multi-Page Analytical Report (Corporate / Elegance)
|
||||
**User Request**: "I have a long report about our Q3 server performance. Latency dropped by 45ms. Uptime is 99.99%. Here is 500 words of text explaining the server migration..."
|
||||
|
||||
**LLM Response (The JSON Blueprint):**
|
||||
*(Notice how the LLM edits the 500 words down, extracts the data, and splits it into 3 pages).*
|
||||
|
||||
```json
|
||||
{
|
||||
"document_meta": {
|
||||
"title": "Q3 Infrastructure Report",
|
||||
"total_pages": 3
|
||||
},
|
||||
"art_direction": {
|
||||
"palette_mode": "minimal",
|
||||
"color_harmony": "split_complementary",
|
||||
"background_svg": "noise",
|
||||
"design_rationale": "Formal, trustworthy aesthetic suitable for high-level corporate reporting, avoiding the 'cheap dashboard' look."
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"page_index": 1,
|
||||
"narrative_role": "Burst",
|
||||
"archetype": "cover_hero",
|
||||
"components": [
|
||||
{
|
||||
"type": "Floating_Meta",
|
||||
"position": "top-left",
|
||||
"items": ["CONFIDENTIAL", "Q3 2026"]
|
||||
},
|
||||
{
|
||||
"type": "Hero_Typography",
|
||||
"weight": "black",
|
||||
"content": "INFRASTRUCTURE<br>RESILIENCE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"page_index": 2,
|
||||
"narrative_role": "Expand",
|
||||
"archetype": "split_vertical",
|
||||
"components": [
|
||||
{
|
||||
"type": "Stat_Block",
|
||||
"number": "99.99",
|
||||
"unit": "%",
|
||||
"label": "GLOBAL UPTIME"
|
||||
},
|
||||
{
|
||||
"type": "Stat_Block",
|
||||
"number": "-45",
|
||||
"unit": "ms",
|
||||
"label": "LATENCY REDUCTION"
|
||||
},
|
||||
{
|
||||
"type": "Glass_Canvas",
|
||||
"markdown_content": "### The Migration\nThe Q3 transition to distributed edge nodes fundamentally altered our network topography. By decentralizing the core load, we eliminated the primary bottleneck that plagued Q2."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"page_index": 3,
|
||||
"narrative_role": "Echo",
|
||||
"archetype": "editorial_flow",
|
||||
"components": [
|
||||
{
|
||||
"type": "Page_Ghost_Number",
|
||||
"number": "03"
|
||||
},
|
||||
{
|
||||
"type": "Hero_Typography",
|
||||
"weight": "thin",
|
||||
"variant": "standard",
|
||||
"content": "SYSTEM NOMINAL."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Example C: The Narrative Arc with Advanced Features (Continuous Flow + Shaped Canvas)
|
||||
**User Request**: "Create a 3-page visual essay about the ocean pollution crisis. Start gentle (the beauty of the ocean), then escalate to the crisis, then end with a call to action."
|
||||
|
||||
**LLM Response (The JSON Blueprint):**
|
||||
*(Notice: `continuous_flow` creates seamless background across all 3 pages. `tension_score` escalates from calm to urgent. `Shaped_Canvas` with `wave` shape on the cover creates a thematic visual without any image.)*
|
||||
|
||||
```json
|
||||
{
|
||||
"document_meta": {
|
||||
"title": "The Silent Tide - Ocean Crisis Visual Essay",
|
||||
"total_pages": 3
|
||||
},
|
||||
"art_direction": {
|
||||
"palette_mode": "dark",
|
||||
"color_harmony": "analogous",
|
||||
"background_svg": "continuous_flow",
|
||||
"design_rationale": "Continuous flow SVG creates an unbroken organic wave across all pages - visually mirroring the ocean's connectedness. Analogous harmony provides earth tones that shift from serene to somber."
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"page_index": 1,
|
||||
"narrative_role": "Burst",
|
||||
"archetype": "shaped_editorial",
|
||||
"components": [
|
||||
{
|
||||
"type": "Floating_Meta",
|
||||
"position": "top-left",
|
||||
"items": ["VISUAL ESSAY", "2026"]
|
||||
},
|
||||
{
|
||||
"type": "Shaped_Canvas",
|
||||
"shape_keyword": "wave",
|
||||
"markdown_content": "There was a time when the horizon held only promise. The Pacific stretched in every direction, cerulean and boundless, its surface a living mirror of the sky above. Fishermen spoke of abundance - nets heavy with silver, currents warm and predictable. The ocean asked for nothing."
|
||||
},
|
||||
{
|
||||
"type": "Floating_Meta",
|
||||
"position": "bottom-right",
|
||||
"items": ["01 / BEFORE"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"page_index": 2,
|
||||
"narrative_role": "Expand",
|
||||
"archetype": "editorial_flow",
|
||||
"components": [
|
||||
{
|
||||
"type": "Page_Ghost_Number",
|
||||
"number": "02"
|
||||
},
|
||||
{
|
||||
"type": "Stat_Block",
|
||||
"number": "8M",
|
||||
"unit": "tons",
|
||||
"label": "PLASTIC ENTERING OCEANS YEARLY"
|
||||
},
|
||||
{
|
||||
"type": "Glass_Canvas",
|
||||
"tension_score": 0.75,
|
||||
"markdown_content": "**The collapse was not sudden.** It accumulated - bottle by bottle, net by net, spill by spill. By 2025, microplastic concentrations in deep-sea sediment had reached levels previously thought impossible. Marine biologists stopped using the word 'recovery'. The new vocabulary was *triage*."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"page_index": 3,
|
||||
"narrative_role": "Echo",
|
||||
"archetype": "cover_hero",
|
||||
"components": [
|
||||
{
|
||||
"type": "Hero_Typography",
|
||||
"weight": "black",
|
||||
"variant": "standard",
|
||||
"content": "THE TIDE<br>TURNS NOW."
|
||||
},
|
||||
{
|
||||
"type": "Glass_Canvas",
|
||||
"tension_score": 0.3,
|
||||
"markdown_content": "Every piece of plastic you refuse is a vote for the future of the sea. The ocean cannot speak - but it remembers everything we give it."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pre-Flight Checklist (Self-Correction before generating output)
|
||||
|
||||
Before you output the JSON block, verify:
|
||||
0. ** VECTOR OUTPUT IRON RULE:** The final PDF MUST be generated via `page.pdf()` / `convert.blueprint` - NEVER `page.screenshot()` → image → wrap as PDF. Screenshot PDFs are blurry raster images. `page.pdf()` produces vector text that stays sharp at any zoom level.
|
||||
1. **Did I write ANY HTML or CSS?** If yes, delete it. Only output JSON.
|
||||
2. **Is any `Glass_Canvas` markdown content too long?** Count the words. Over 150? Summarize it or push to the next page.
|
||||
3. **Is the JSON perfectly formatted?** Missing commas or unescaped quotes will crash the `design_engine.py` parser.
|
||||
4. **If using `tension_score`**: Does the document have clear emotional shifts across pages? If all pages are the same tone, remove it.
|
||||
5. **If using `continuous_flow`**: Is this multi-page (2+)? If single-page, switch to `"flow"`.
|
||||
6. **If using `Shaped_Canvas`**: Is the archetype `"shaped_editorial"`? Is there at most one per page? No `Glass_Canvas` on the same page?
|
||||
7. **Grid boundary check**: Are ALL `grid_area` values within `[1, 13]`? Any number < 1 or > 13 = FATAL ERROR.
|
||||
8. **Component minimum size check**: `Glass_Canvas` ≥ 6col×4row? `Shaped_Canvas` ≥ 8col×6row?
|
||||
9. **Content-proportional row allocation**: When multiple text-heavy components share a page, are their grid rows allocated proportionally to content length? Equal rows for unequal content = text overlap. (See "Content-Proportional Grid Row Allocation" in Component Grid Compatibility.)
|
||||
9. **40% whitespace check**: Count occupied grid cells. At least 58 of 144 cells must be empty.
|
||||
10. **Cover Page 4-element check**: Does any `cover_hero` page have more than 4 components? If yes, remove or merge components.
|
||||
11. **Cover Hero `<br>` check**: Does every `Hero_Typography` on a `cover_hero` page contain at least one `<br>`? Single-line hero text on covers = FAIL.
|
||||
12. **Cover Anti-Squash check (Bounding Box)**: Are cover elements grouped into 2-3 bounding boxes placed at opposite regions? Is remaining space dynamically distributed (not hardcoded gaps)? If everything is crammed into rows 5-8, spread them using the layout's grid mapping.
|
||||
13a. **Cover Typography Scale check**: Is the hero title ≥ 45pt? Is subtitle ≥ 25pt? Is meta text ≥ 18pt (never below 14pt)? Tiny cover text = FAIL.
|
||||
13b. **Cover Layout Selection**: Did I pick a layout (1-7) that matches the document tone? If unsure, default to Layout 1A (Diagonal Tension). For government/bidding documents, use Layout 7 (Left-Matrix).
|
||||
14. **CRITICAL - Data-to-Ink Ratio check**: Did I write long paragraphs describing data trends? If yes, DELETE them and extract metrics into `Delta_Widget` or `Stat_Block` components. Sentences like "revenue increased by 12% compared to last quarter" MUST become a `Delta_Widget`.
|
||||
15. **Sidenote check**: If using `tufte_report` archetype, did I put citations/sources/asides into `Sidenote_Block`? They must NOT be inline in `Glass_Canvas`.
|
||||
16. **Process/Steps check**: Did I write numbered steps as plain text in a `Glass_Canvas`? Convert to `Process_List` component instead.
|
||||
17. **Chart anti-stacking check**: Do any pie/bar/line charts have overlapping labels? Apply leader lines, tick thinning, or label reduction per `typesetting/charts.md`.
|
||||
18. **Chart axis cleanup check**: Are top/right spines deleted? Grid lines dashed at 20% opacity (or hidden)? No solid grid lines.
|
||||
19. **Donut default check**: Are pie charts rendered as donuts (hole ratio 60-70%)? Solid pies = FAIL unless explicitly requested.
|
||||
20. ** Triple Delivery check (MANDATORY)**: Creative pipeline must deliver **three files** to the user: (1) PDF - the final vector PDF; (2) HTML - the compiled `*_rendered.html` file; (3) Image - a full-page screenshot preview (PNG/JPG). After `convert.blueprint` generates the PDF and HTML, take a screenshot of the HTML for preview. Report all three file paths to the user.
|
||||
21. ** HTML Pre-Render Validation (MANDATORY for ALL HTML→PDF paths)**: Before calling `html2pdf-next.js`, `html2poster.js`, or `convert.blueprint`, run `poster_validate.py check-html` on the HTML file. This catches overflow:hidden on containers, missing @media screen auto-scale, font fallback gaps, contrast issues, and more. **Any ERROR-level issue must be fixed before generating the PDF.** Warnings are non-blocking but should be reviewed.
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/poster_validate.py" check-html page.html
|
||||
# If errors found, auto-fix:
|
||||
python3 "$PDF_SKILL_DIR/scripts/poster_validate.py" check-html page.html --fix --output page.html
|
||||
```
|
||||
|
||||
22. **\u26a0\ufe0f MANDATORY: Post-Generation Checks (Creative)**: After HTML\u2192PDF conversion, run these checks:
|
||||
```bash
|
||||
# Check for content overflow and full-bleed issues
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf_qa.py" output.pdf --no-tables
|
||||
|
||||
# Add metadata
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" meta.brand output.pdf
|
||||
```
|
||||
|
||||
Execute the design.
|
||||
|
||||
---
|
||||
|
||||
## CJK & Vertical Text Rules (When Bypassing design_engine.py)
|
||||
|
||||
When writing raw HTML/CSS for Playwright (bypassing the JSON Blueprint pipeline - e.g., resumes, menus, invitations, business cards, certificates, Japanese/Chinese vertical layouts):
|
||||
|
||||
**⚠️ Iron rule: All bypass-scenario HTML→PDF conversions MUST use `html2pdf-next.js` — do NOT write custom Python Playwright scripts.** `html2pdf-next.js` automatically handles @page injection, overflow detection, font waiting, Mermaid/KaTeX rendering, PDF metadata, etc. See the "HTML→PDF Engine Selection Rules" section in SKILL.md.
|
||||
|
||||
```bash
|
||||
node "$PDF_SKILL_DIR/scripts/html2pdf-next.js" input.html --output output.pdf --width 210mm --height 297mm
|
||||
```
|
||||
|
||||
0. **Full-Bleed CSS (MANDATORY)**: Every HTML file for Playwright PDF MUST include:
|
||||
```css
|
||||
@page { size: 800px 1200px; margin: 0; } /* CONCRETE values only - CSS variables NOT supported in @page */
|
||||
html, body { margin: 0; padding: 0; width: 800px; height: 1200px; }
|
||||
```
|
||||
**⚠️ @page rules do NOT resolve CSS variables** (`var(--x)` is silently ignored, falls back to A4). Always use concrete `px` values.
|
||||
**⚠️ html/body must have explicit width + min-height** matching the canvas size. Use `min-height` (not `height`) so content taller than the design height expands naturally into a single long-page PDF.
|
||||
**⚠️ Poster = seamless pagination.** When content exceeds `@page` height, `html2pdf-next.js` lets Playwright paginate at page boundaries - each page has the same dimensions, content flows seamlessly across pages (like scrolling a long image). Do NOT expand to a single oversized page.
|
||||
`design_engine.py` handles this automatically via `override_css`.
|
||||
|
||||
0.25. **Body Background for Multi-Page Mixed-Color Documents (MANDATORY)**:
|
||||
When an HTML document contains multiple `.page` divs with **different background colors** (e.g. dark cover + white body + dark specs page), Playwright's sub-pixel rounding creates <1px gaps at `.page` edges where `body` background shows through. On dark pages, a white `body` = visible white edges.
|
||||
|
||||
**Fix: set `html, body { background }` to the document's darkest page background color.**
|
||||
|
||||
```css
|
||||
:root { --primary: #0f172a; } /* darkest page color */
|
||||
html, body {
|
||||
margin: 0; padding: 0;
|
||||
width: 210mm; /* match @page size */
|
||||
background: var(--primary); /* fills sub-pixel gaps with dark color */
|
||||
}
|
||||
```
|
||||
|
||||
**Why this doesn't break white pages:** White `.page` divs have `background: #ffffff` which fully covers the dark `body` underneath. The dark body only shows through the <1px sub-pixel gap at the extreme page edge - imperceptible on white pages after anti-aliasing.
|
||||
|
||||
**Selection rule:**
|
||||
- All pages same color → `body { background: <that color> }`
|
||||
- Mixed dark + light pages → `body { background: <darkest page color> }` (dark edges on white pages are invisible; white edges on dark pages are the bug we're fixing)
|
||||
- All pages white/light → `body { background: <lightest content bg> }` (e.g. `#f8fafc`)
|
||||
|
||||
0.5. **No overflow:hidden + Browser Preview Adaptive Scaling (MANDATORY)**:
|
||||
For fixed-size single-page designs (posters, infographics, certificates, etc.), **absolutely never** set `overflow: hidden` on `html`, `body`, or the main container. Reasons:
|
||||
- When opening the HTML directly in a browser, the viewport is much smaller than the design size (e.g., a 1400px-tall page in a 900px viewport). `overflow: hidden` clips the bottom content and prevents scrolling.
|
||||
- `html2pdf-next.js`'s pre-render check detects `scrollHeight > clientHeight` + `overflow: hidden` and triggers auto-fix (force-expanding the container), which may break the layout.
|
||||
|
||||
**`design_engine.py` handles this automatically**: During blueprint compilation, it auto-injects `@media screen` centering + scaling code, and the `html` background color uses `var(--c-bg)` matching the poster's main color. No manual addition needed.
|
||||
|
||||
**Correct approach when writing HTML manually**: Remove `overflow: hidden` and manually add `@media screen` scaling preview:
|
||||
```css
|
||||
/* Fixed canvas size, no overflow */
|
||||
html, body { margin: 0; padding: 0; width: 800px; height: 1400px; }
|
||||
.page { width: 800px; height: 1400px; position: relative; }
|
||||
|
||||
/* Auto-scale to viewport in browser preview for full-page view */
|
||||
@media screen {
|
||||
html {
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background: #0a0a1a; /* Surround color — must match poster's main background */
|
||||
}
|
||||
body {
|
||||
transform-origin: top center;
|
||||
scale: min(1, calc(100vw / 800), calc(100vh / 1400)); /* Consider both width and height */
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 0 60px rgba(0,0,0,0.3); /* Optional: shadow to distinguish canvas in preview */
|
||||
}
|
||||
}
|
||||
```
|
||||
`@media screen` rules only apply in browser preview; `page.pdf()` uses print media and is unaffected.
|
||||
**Every fixed-size HTML must include this `@media screen` adaptive code.**
|
||||
|
||||
0.75. **Page Container Overflow Clipping (MANDATORY for multi-page documents)**:
|
||||
Every `.page` div MUST have `overflow: hidden`. Decorative elements (glow circles, gradient overlays) commonly use `width: 120%` or negative offsets - without clipping, they inflate `scrollWidth` beyond page width, causing Playwright to shrink all content and shift it left.
|
||||
```css
|
||||
.page { overflow: hidden; } /* Clips decorative overflow, prevents Playwright shrink */
|
||||
```
|
||||
For horizontal flex layouts (≥3 items), always add `flex-wrap: wrap`. See `typesetting/overflow.md` §3.5.
|
||||
|
||||
1. **Character Encoding Safety**: Never use Japanese kana (の, が, は), rare symbols, or Private Use Area characters in content strings. They corrupt to U+FFFD (�) during LLM→file write→read transit. Replace with plain Chinese equivalents: `の`→`之/的/缔/省略`.
|
||||
2. **Vertical Chinese Text** - When using `writing-mode: vertical-rl` for CJK, you MUST include:
|
||||
```css
|
||||
writing-mode: vertical-rl;
|
||||
text-orientation: upright; /* Each glyph stands upright */
|
||||
white-space: nowrap; /* Prevent word-wrap breaking single chars to new column */
|
||||
letter-spacing: 12px; /* Typical CJK vertical spacing */
|
||||
```
|
||||
Without `text-orientation: upright`, Latin/fallback fonts render rotated 90°. Without `white-space: nowrap`, CJK characters may wrap into unexpected multi-column layouts (e.g., 3 chars on one line + 1 char alone on next).
|
||||
3. **Font Coverage**: For CJK content via Playwright, always load Google Fonts Noto Serif SC or Noto Sans SC via `<link>` tag in `<head>` (NOT `@import` in CSS - `@import` must be the very first rule in a stylesheet or it's silently ignored). Example:
|
||||
```html
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
||||
```
|
||||
System CJK fonts vary across macOS/Linux - Google Fonts guarantee glyph coverage without relying on system fonts. `design_engine.py` already handles this automatically via `<link>` tag.
|
||||
4. **Post-Generation Text Verification**: After Playwright renders the PDF, extract text from every page and scan for `?` or `\ufffd`. If found, the source HTML has encoding-corrupted characters that must be replaced in the Python source.
|
||||
5. ** HTML Pre-Render Validation (MANDATORY)**: After writing the HTML file and before running `html2pdf-next.js`, always run the HTML validator:
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/poster_validate.py" check-html <your_file>.html
|
||||
```
|
||||
- **ERROR** items (e.g. `OVERFLOW_HIDDEN_CONTAINER`, `FONT_NO_FALLBACK`) → must fix before PDF generation. Use `--fix --output <file>.html` for auto-repair.
|
||||
- **WARNING** items (e.g. `FIXED_SIZE_NO_SCREEN_ADAPT`, `SCREEN_ADAPT_NO_SCALE`, `COLOR_CONTRAST`) → review and fix where appropriate.
|
||||
- This catches the most common bypass HTML bugs: `overflow:hidden` on containers, missing `@media screen` auto-scale for fixed-size pages, font-family without generic fallback, low contrast text, etc.
|
||||
|
||||
---
|
||||
|
||||
## Quality Checklist — Creative-Specific Items
|
||||
|
||||
> Moved from SKILL.md to reduce context size. These rules apply specifically to the Creative pipeline.
|
||||
|
||||
### Color (palette.md)
|
||||
|
||||
- [ ] **Entire document ≤ 5 colors**: Primary + secondary + accent + neutral + background
|
||||
- [ ] **All colors traceable to primary**: Secondary and accent derived via lightness/saturation/micro-hue shift
|
||||
- [ ] **Sibling elements not differentiated by different hues**: Use opacity/lightness/borders instead
|
||||
- [ ] **Gradient endpoints hue difference < 20°**: No warm-to-cool gradients
|
||||
- [ ] **No high-saturation color blocks**: Avoid eye strain
|
||||
|
||||
### Geometric Anchors (geometry.md)
|
||||
|
||||
- [ ] **Anchors use only the primary color**: Layer via opacity, don't mix colors
|
||||
- [ ] **Strokes over fills**: Solid elements ≤ 30%
|
||||
- [ ] **Ultra-thin lines**: stroke-width 0.3-0.8px
|
||||
- [ ] **Asymmetric placement**: Offset creates tension
|
||||
- [ ] **Elements ≤ 8**: Restraint, don't clutter
|
||||
|
||||
### Global Layout
|
||||
|
||||
- [ ] **Full-bleed enforcement**: `@page { size: <w> <h>; margin: 0; }` and `html,body { margin:0; padding:0; }`
|
||||
- [ ] **Background color consistency**: `html, body { background }` set explicitly. Single-color docs: match content canvas. Multi-page mixed docs: use darkest page's background
|
||||
- [ ] **Content centering**: Symmetric inset/padding, no left/right drift
|
||||
- [ ] **Anti-void edges**: Content fills ≥ 60% (multi-page) or ≥ 70% (single-page poster/infographic)
|
||||
- [ ] **Fill Engine applied**: Pages with < 80% fill ratio trigger the fill engine
|
||||
|
||||
### Design Restraint (Anti-Gaudy)
|
||||
|
||||
- [ ] **Decorative elements ≤ 3 per page**: Cover page exempt
|
||||
- [ ] **No rainbow/multi-color schemes**: Stick to single-family palette
|
||||
- [ ] **No texture/pattern backgrounds**: Solid or ultra-light tinted only
|
||||
- [ ] **Whitespace is design**: Don't fill every gap with decorative elements
|
||||
- [ ] **Typography over decoration**: Create hierarchy through font size/weight/spacing/color
|
||||
- [ ] **2-typeface maximum**: At most 2 font families
|
||||
- [ ] **🚫 NO stock images / clipart / AI-generated decorations**: Only user-provided content images allowed
|
||||
Executable
+702
@@ -0,0 +1,702 @@
|
||||
# Poster Scene Rules — Creative Pipeline (Poster-Specific Constraints)
|
||||
|
||||
> When poster / 海报 / 传单 / flyer / 宣传页 keywords are detected, load these rules on top of `creative-fixed-canvas.md`.
|
||||
> These rules take priority over `creative-fixed-canvas.md` generic rules; in case of conflict, this file prevails.
|
||||
>
|
||||
> **Positioning**: This is a scene-layer patch on `creative-fixed-canvas.md` (the generic Creative pipeline) — only covers poster-specific constraints, does not repeat generic rules.
|
||||
|
||||
---
|
||||
|
||||
## 1. Page Count & Dimensions
|
||||
|
||||
### 1.1 Default Single Page
|
||||
- When the user does not explicitly request multiple pages, **default to a single-page poster**
|
||||
- Single-page poster `total_pages: 1`, never split into multiple pages on your own
|
||||
|
||||
### 1.2 Sizing Strategy
|
||||
|
||||
| Text Volume | Recommended Size | Aspect Ratio | Notes |
|
||||
|--------|---------|--------|------|
|
||||
| ≤ 50 chars | 720 × 960 | 3:4 | Title poster / social media cover / card |
|
||||
| 50–200 chars | 720 × 960 | 3:4 | Standard promotional poster |
|
||||
| > 200 chars | 720 × min-height 960 | Adaptive | Long poster (H5 style), content stretches height |
|
||||
| User-specified landscape | Adjust as needed | As needed | Width and height can be swapped |
|
||||
|
||||
### 1.3 Canvas Variables
|
||||
```json
|
||||
{
|
||||
"canvas": { "width": 720, "height": 960 }
|
||||
}
|
||||
```
|
||||
Default dimensions can be overridden via the `canvas` field in the Blueprint. `design_engine.py` will automatically inject `--canvas-w` and `--canvas-h` CSS variables.
|
||||
|
||||
---
|
||||
|
||||
## 2. Information Density & Page Fill
|
||||
|
||||
### 2.1 Core Iron Rule: Content Must Fill the Page
|
||||
|
||||
> **The biggest visual disaster for a poster is not content overflow, but content only occupying half the page with a blank bottom half.**
|
||||
|
||||
| Text Volume | Content Area / Page Ratio | Notes |
|
||||
|--------|----------------|------|
|
||||
| ≤ 50 chars | 70–80% | Enlarged cards, large font sizes, generous decorative whitespace is intentional |
|
||||
| 50–200 chars | 75–85% | Content modules distributed evenly, must not be crammed in the top half |
|
||||
| > 200 chars | 80–90% | Content-dominant, whitespace only at margins |
|
||||
|
||||
### 2.2 Anti-Top-Heavy
|
||||
|
||||
- All components' `grid_area` **must cover the full 1→13 rows**, never stop at row 10 or 11
|
||||
- The last component's `grid_area` row endpoint must be `13`
|
||||
- **No large blank space at top/header** — the first content component's `grid_area` should start at row 1, not row 3 or 4
|
||||
- **No large blank space on left/right sides** — components should utilize full column width (most should span 1→13 columns)
|
||||
- If content is insufficient to fill 12 rows, use these strategies (by priority):
|
||||
1. **Increase font size**: Hero from scale 5 → 6, body font +2pt
|
||||
2. **Increase component spacing**: grid gap from 16px → 24px
|
||||
3. **Insert decorative components**: `Hairline_Divider`, `Page_Ghost_Number`
|
||||
4. **Expand stat block / hero grid row count**
|
||||
|
||||
### 2.3 Grid Area Allocation Iron Rule
|
||||
|
||||
```
|
||||
✅ Correct: Components cover all rows 1→13
|
||||
Page: [Hero 1→4] [Stats 4→7] [Glass 7→10] [Meta 10→13]
|
||||
|
||||
❌ Wrong: Components only reach row 10, rows 11-13 empty
|
||||
Page: [Hero 1→3] [Stats 3→5] [Glass 5→8] [Meta 8→10] [??? 10→13 void]
|
||||
```
|
||||
|
||||
### 2.4 ★★★ Content-Proportional Row Allocation (Anti-Overlap Iron Rule)
|
||||
|
||||
> **When two or more text components share a page, rows must be allocated proportionally to content volume — never split evenly!**
|
||||
|
||||
**Problem reproduction:**
|
||||
```
|
||||
Attractions: 450 chars (3 attractions × 150 chars description)
|
||||
Food: 250 chars (2 foods × 125 chars description)
|
||||
Available rows: 8 rows (5→13)
|
||||
|
||||
❌ Even split: Attractions 5→9, Food 9→13 → 4 rows each
|
||||
→ Attractions content far exceeds 4-row capacity, overflows into Food area, text overlaps!
|
||||
|
||||
✅ Proportional split:
|
||||
Attractions: 8 × 450/700 ≈ 5 rows → 5→10
|
||||
Food: 8 × 250/700 ≈ 3 rows → 10→13
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
1. Write all `markdown_content` first
|
||||
2. Count the **character count** of each text component (including titles/paragraphs/lists)
|
||||
3. Allocate rows proportionally: `rows_i = total_rows × (chars_i / total_chars)`
|
||||
4. Round off, each component **minimum 3 rows**
|
||||
5. Verify: all component grid_area endpoints connect end-to-end, covering full 1→13
|
||||
|
||||
**Applicable scenarios:**
|
||||
- Multiple `Glass_Canvas` on the same page (most common)
|
||||
- `Glass_Canvas` + `Process_List` on the same page
|
||||
- Any two or more components containing text paragraphs
|
||||
|
||||
**Not applicable:**
|
||||
- `Hero_Typography` (very large font, 1-3 lines occupying 2-3 grid rows is reasonable)
|
||||
- `Stat_Block` (number + label, fixed height, typically 2 rows)
|
||||
- `Floating_Meta` (short labels, typically 2-3 rows)
|
||||
|
||||
---
|
||||
|
||||
## 3. Font Size System (Poster-Specific)
|
||||
|
||||
### 3.1 Minimum Font Size (Hard Floor)
|
||||
|
||||
| Element | Min Font Size | Recommended | Notes |
|
||||
|------|---------|---------|------|
|
||||
| **Page main title** | **50px** | 56–72px | Poster title must have visual impact |
|
||||
| **Body text** | **24px** | 24–28px | Posters are not reports — body font must be large |
|
||||
| **Subtitle / card title** | **28px** | 32–40px | Secondary headings |
|
||||
| **Floating Meta** | **16px** | 16–20px | Metadata text |
|
||||
| **Stat Number** | **48px** | 56–72px | Data sculptures must be eye-catching |
|
||||
| **Stat Label** | **14px** | 14–16px | Data labels |
|
||||
|
||||
> Compared to `fill-engine.md`'s generic red line (body ≥ 14pt), the poster body floor is **24px** — posters are a distance-reading medium, font sizes must be larger.
|
||||
|
||||
### 3.2 Emphasis Hierarchy
|
||||
|
||||
- Use **font size + font weight** to create hierarchy, **not color differentiation**
|
||||
- Emphasis text (keyword/number highlights) font size must be smaller than titles
|
||||
- Hero title recommended `weight: "black"` (900), subtitle `weight: "thin"` (100), creating extreme contrast
|
||||
|
||||
---
|
||||
|
||||
## 4. Color Rules (Poster-Specific)
|
||||
|
||||
### 4.1 Color Palette System
|
||||
|
||||
**Primary palette: Material Design 3 with low-medium saturation.** Default to medium-saturation colors; for light themes, use gradient backgrounds with white/light text on top.
|
||||
|
||||
| Area | Proportion | Role |
|
||||
|------|------|------|
|
||||
| 60% Ground | Background/margins/whitespace | Main color (low saturation) |
|
||||
| 30% Structure | Cards/dividers/secondary areas | Derived from main by adjusting lightness |
|
||||
| 10% Emphasis | Titles/key numbers/single accent | Adjusted purity/brightness of main color |
|
||||
|
||||
**Color derivation rules:**
|
||||
- Title/subtitle text color: Adjust main color's purity and brightness (not a separate random color)
|
||||
- Auxiliary colors: **Maximum 2**, derived from primary by adjusting lightness/saturation
|
||||
- Keep consistent color palette throughout — do NOT change main color between sections
|
||||
- Default recommendation: **Light theme with medium saturation**, or gradient background + white fonts
|
||||
|
||||
### 4.2 Poster Additional Constraints
|
||||
|
||||
- **No pure white (#FFFFFF) background** — use at least `#f5f4f2` or warmer off-white
|
||||
- **No transparent background**
|
||||
- **Page base color (`<body>` / `<html>` background) must match poster content background** — `printBackground: true` renders body background. If body is white but poster content is gray, white borders/gaps appear in the PDF. Ensure `html, body { background: var(--c-bg); }` matches the poster canvas exactly
|
||||
- **Do not use a single image to fill the background** — use grid textures, gradients, geometric shapes, and other generative backgrounds
|
||||
- Long posters (>200 chars) must not use full-image backgrounds
|
||||
- **Gradients** or **large blurred circles/symbols** can be used sparingly as background accents
|
||||
- Maximum 2 auxiliary colors, derived from primary by adjusting lightness/saturation
|
||||
- Background accents: grid textures, organic shapes, large blurred circles/symbols — NOT a single image
|
||||
- **Overall bright and vibrant color combinations** — the poster should feel visually striking, not muted/dull
|
||||
|
||||
### 4.3 palette_mode Mapping
|
||||
|
||||
| Poster Style | Recommended palette_mode | color_harmony |
|
||||
|---------|-------------------|---------------|
|
||||
| Business/Formal | `minimal` | `auto` |
|
||||
| Tech/AI | `dark` | `complementary` |
|
||||
| Lifestyle/Food/Artistic | `pastel` | `analogous` |
|
||||
| Luxury/Ceremony | `jewel` | `split_complementary` |
|
||||
| Other | `minimal` (default) | `auto` |
|
||||
|
||||
---
|
||||
|
||||
## 4.5 ★★★ Anti-Modularization Iron Rule (Anti-Card / Anti-Dashboard)
|
||||
|
||||
> **A poster is a complete composition, not a dashboard, not an APP interface, not a report.**
|
||||
|
||||
### Root Cause
|
||||
|
||||
LLMs naturally tend to "classify information → put each category in a box → stack them vertically", because that is report/document organization logic. But poster visual logic is the exact opposite — **information is unified, hierarchy is established through typographic rhythm (font size, weight, spacing, whitespace), not through borders and background colors**.
|
||||
|
||||
### Comparison
|
||||
|
||||
| Report/UI Thinking ❌ | Poster Thinking ✅ |
|
||||
|---|---|
|
||||
| Each info block wrapped in `border + border-radius + background` as a card | Information placed directly on the canvas, no borders no background |
|
||||
| Clear visual boundaries between modules | Modules naturally separated by **whitespace and thin lines** |
|
||||
| Looks like a mobile APP interface | Looks like a design piece |
|
||||
| Hierarchy via "different colored boxes" | Hierarchy via **font size gradient + weight contrast + color lightness** |
|
||||
| Stacked Glass_Canvas components = card wall | Pure typography + occasional Hairline_Divider |
|
||||
|
||||
### Mandatory Rules
|
||||
|
||||
1. **Single-page posters must not have more than 3 containers with `background` + `border`.** If information needs grouping, use whitespace (margin/padding) and thin lines (1px, opacity < 10%) instead of bordered cards.
|
||||
2. **No 2×2 / 2×3 grid card layouts** (unless it's a pure data scenario with `data_dashboard` archetype). Information flows in a single column; multiple items in the same row use `flex + gap` horizontal layout without borders.
|
||||
3. **Information hierarchy must be established through typography properties**, not through "different colored/backgrounded boxes":
|
||||
- Primary information: large font (≥48px) + heavy weight (900)
|
||||
- Secondary information: medium font (18-24px) + medium weight (700)
|
||||
- Tertiary information: small font (12-14px) + light weight (300-400) + reduced opacity
|
||||
4. **Glass_Canvas may be used at most once in a single-page poster.** If multiple text sections are needed, use direct HTML typography instead of wrapping each paragraph in a Glass_Canvas.
|
||||
5. **Bottom action information (price/address/QR code) should not be wrapped in a separate color block.** Use larger font + heavier weight to emphasize, keeping it unified with the overall composition.
|
||||
|
||||
### Correct Example (Direct HTML Flow)
|
||||
|
||||
```html
|
||||
<!-- ❌ Wrong: card wall -->
|
||||
<div class="card" style="background:rgba(255,255,255,0.5); border-radius:12px; border:1px solid #ddd;">
|
||||
<h3>📅 展期</h3>
|
||||
<p>7.15 — 8.30</p>
|
||||
</div>
|
||||
<div class="card" style="background:rgba(255,255,255,0.5); border-radius:12px; border:1px solid #ddd;">
|
||||
<h3>📍 地点</h3>
|
||||
<p>城市艺术中心</p>
|
||||
</div>
|
||||
|
||||
<!-- ✅ Correct: pure typography, no borders -->
|
||||
<div class="info-row" style="display:flex; gap:48px;">
|
||||
<div>
|
||||
<div style="font-size:10px; letter-spacing:3px; opacity:0.45;">展期</div>
|
||||
<div style="font-size:20px; font-weight:700;">7.15 — 8.30</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:10px; letter-spacing:3px; opacity:0.45;">地点</div>
|
||||
<div style="font-size:20px; font-weight:700;">城市艺术中心</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Blueprint Route Additional Constraints
|
||||
|
||||
When using Blueprint JSON (not Direct HTML):
|
||||
- Single-page posters may have at most 1 `Glass_Canvas`, used only for text that truly needs reading
|
||||
- Prefer combining `Stat_Block` (data), `Floating_Meta` (metadata), `Hero_Typography` (titles) instead of multiple Glass_Canvas
|
||||
- Information data (dates, locations, headcounts) should use `Floating_Meta` or `Stat_Block`, not Glass_Canvas
|
||||
|
||||
---
|
||||
|
||||
## 5. Layout Strategy
|
||||
|
||||
### 5.1 Composition Priority
|
||||
1. **Vertical composition** (vertical flow) — most common, information flows top to bottom
|
||||
2. **Left-right composition** (split vertical) — image and text split, `archetype: "split_vertical"`
|
||||
3. **Centered composition** (centered) — suitable for title cards with ≤ 50 chars
|
||||
|
||||
### 5.2 Card Rules
|
||||
- Cards **must never overlap**
|
||||
- Card content should be well-distributed, **no large internal whitespace**
|
||||
- Text within cards **vertically centered**
|
||||
- Glass Canvas `align-items` must be `stretch` (built into the engine)
|
||||
|
||||
### 5.3 Breathing Margins
|
||||
|
||||
| Scenario | safe-zone inset | Notes |
|
||||
|------|----------------|------|
|
||||
| Cover / title poster (≤50 chars) | `12% 14%` | Generous whitespace is intentional |
|
||||
| Standard poster (50-200 chars) | `8% 10%` | Balance whitespace and content |
|
||||
| Long poster (>200 chars) | `6% 8%` | Maximize content area |
|
||||
|
||||
> Max content width = 80% of page width (consistent with original prompt)
|
||||
|
||||
---
|
||||
|
||||
## 5.5 Visual Impact Rules
|
||||
|
||||
### 5.5.1 Emphasis & Contrast
|
||||
- Create visual contrast with **oversized and small elements** — hero numbers/titles vs tiny metadata
|
||||
- Highlight core points with large fonts or numbers for strong visual contrast
|
||||
- **Emphasized text must remain smaller than headings/titles** — never let a highlight be bigger than the heading
|
||||
- Texts that need emphasis can be highlighted with color, weight, or circled with hand-drawn lines
|
||||
- **Do not insert multiple small pictures as embellishments** — this won’t enhance visual appeal
|
||||
|
||||
### 5.5.2 Alignment & Spacing
|
||||
- Allow blocks to resize based on content, align appropriately, optimize space utilization
|
||||
- If excess whitespace exists, **enlarge fonts or modules** to balance the layout
|
||||
- Cards cannot overlap; content should fill the card area without excessive empty space
|
||||
- Use **flexbox** layouts to prevent footer from moving up (with top and bottom margin settings)
|
||||
- For visual variety: encourage diverse and creative layouts beyond standard grids, while maintaining alignment and hierarchy
|
||||
|
||||
---
|
||||
|
||||
## 6. Design Style Library
|
||||
|
||||
Based on content theme, the model should autonomously select one of the following styles. Explain the selection rationale in the Blueprint's `design_rationale`.
|
||||
|
||||
| Style | Characteristics | Applicable Scenarios |
|
||||
|------|------|---------|
|
||||
| **Modern Minimal** | Clean colors, organic shapes, flowing curves, rounded cards, clear hierarchy | Business, tech, education |
|
||||
| **Neo-Brutalism** | Flat elements, illustrations, patterns, large text blocks, special font designs, thick borders | Creative, events, youth |
|
||||
| **Artistic Gradient** | Diffused light, gradient glow, semi-transparent elements, blur effects, glass texture | Art, music, branding |
|
||||
| **Collage** | Contrasting color design, material collage, large text, irregular layout | Trends, fashion, exhibitions |
|
||||
| **Playful UI** | Bright colors, interesting shapes, energetic | Children, games, social |
|
||||
|
||||
### Special Forms for Text Volume ≤ 50 Characters
|
||||
|
||||
When content is minimal, prioritize the following compact forms:
|
||||
|
||||
| Form | Description | archetype |
|
||||
|------|------|-----------|
|
||||
| **Centered Card** | Calendar-like effect, key content centered as note/card | `cover_hero` |
|
||||
| **Bookmark Page** | Narrow tall ratio (e.g. 360×960), vertical reading | `cover_hero` |
|
||||
| **Minimal Text** | Title + whitespace only, no additional information | `cover_hero` |
|
||||
| **Sticky Note** | Content displayed as floating sticky notes above background | `cover_hero` |
|
||||
| **Polaroid Card** | Photo-style card with caption below | `cover_hero` |
|
||||
|
||||
### Special Forms for Text Volume ≤ 100 Characters
|
||||
|
||||
| Form | Description |
|
||||
|------|------|
|
||||
| **Floating Card System** | Content as cards floating above organic shape backgrounds |
|
||||
| **Notebook Style** | Lined-paper aesthetic with handwritten-feel content |
|
||||
| **Fortune Stick** | Vertical strip with centered calligraphy text |
|
||||
|
||||
> **Key constraint**: If the user only provides a title with no other requirements, **do not expand content on your own** — just place the title.
|
||||
|
||||
---
|
||||
|
||||
## 6.5 Icons and Illustrations
|
||||
|
||||
- Use **Material Design Icons** (Google Fonts method):
|
||||
```html
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<!-- Usage: -->
|
||||
<i class="material-icons">icon_name</i>
|
||||
```
|
||||
- Icon color: Use the **theme color** (not random colors)
|
||||
- Icon size and position: Aligned with surrounding elements, never stretched
|
||||
- If positioned beside text: **center-aligned with the first line of text**
|
||||
- **Emoji can be used as icons** — 🌸 🍴 🏙️ etc. (but remember ReportLab can’t render emoji — only use in HTML/Playwright route)
|
||||
- For logos/emblems: Use text "Your Logo" or icons, **never** search for logo images
|
||||
|
||||
---
|
||||
|
||||
## 7. Text Readability
|
||||
|
||||
### 7.1 Iron Rules
|
||||
- **Line height ≥ 130%** (`line-height: 1.3` or above)
|
||||
- **No text shadow/glow effects**
|
||||
- **When text overlays images**, a semi-transparent mask layer is required
|
||||
- **Do not use images with lots of text/charts/numbers as text background**
|
||||
- **No `text-align: justify`** (CJK characters get stretched letter-spacing, terrible result) — always use `text-align: left`
|
||||
|
||||
### 7.2 Contrast
|
||||
- Text on light background: `color: #242220` or darker
|
||||
- Text on dark background: `color: #f5f4f2` or lighter
|
||||
- Text on medium background (L 0.30–0.70): **forbidden** — do not place text on mid-tone backgrounds
|
||||
|
||||
---
|
||||
|
||||
## 8. Image Usage Rules
|
||||
|
||||
### 8.0 Image Recommendations
|
||||
|
||||
When creating posters, actively use images to enrich visual effects. Good images can significantly enhance the poster's visual impact.
|
||||
|
||||
- Priority: **installed image generation skill** > web image search
|
||||
- Image style must match the poster theme
|
||||
- Download images locally first, then embed into the poster
|
||||
- Local images must be converted to base64 data URI in HTML (Playwright cannot load local absolute paths)
|
||||
|
||||
### 8.1 Core Principles
|
||||
- Don't force images when none are suitable, but images improve results when available
|
||||
- Each image must be **unique** in the design, no reuse
|
||||
- Prefer clear, high-resolution, watermark-free, text-free images
|
||||
- Images should have rounded corners, sized consistently with the overall design
|
||||
- You can try adding **irregularly shaped masks** (CSS `clip-path`) to images for visual interest
|
||||
|
||||
### 8.2 Prohibited Behaviors
|
||||
- ❌ Placing images directly in corners
|
||||
- ❌ Images obscuring text or overlapping with other modules
|
||||
- ❌ Multiple images scattered randomly as decoration
|
||||
- ❌ Searching for images when logo/badge is needed — use text "Your Logo" or icons instead
|
||||
|
||||
---
|
||||
|
||||
## 9. Chart Rules (Poster Scene)
|
||||
|
||||
- Large numerical datasets → consider creating visual charts
|
||||
- Chart style should match the poster theme
|
||||
- Use **Bento Grid** layout for multiple charts
|
||||
- Chart containers **must have height constraints** to prevent infinite growth
|
||||
|
||||
---
|
||||
|
||||
## 10. Prohibited Items (Poster-Specific)
|
||||
|
||||
### 10.0 HTML Rendering Iron Rules (Applicable to All HTML → PDF Routes)
|
||||
|
||||
| Rule | Description |
|
||||
|------|------|
|
||||
| **No `overflow: hidden` on content components** | Truncates text. Only allowed on page-level `.canvas` and decorative background layers |
|
||||
| **Use `min-height` instead of `height` for content containers** | `height:100%` locks height, content gets clipped when too much; `min-height:100%` allows natural expansion |
|
||||
| **Exceptions where `overflow: hidden` is allowed** | `.canvas` (page boundary), `.poster` (auto-injected by `html2poster.js`), `.floating-meta` (short label ellipsis), cover Layer 1 (decorative clipping), SVG/img (fill container) |
|
||||
| **Absolutely no `backdrop-filter`** | **Playwright PDF rendering silently discards entire element content!** Use fixed rgba() background color for cards instead |
|
||||
| **Absolutely no `text-align: justify`** | CJK character spacing gets abnormally stretched, always use `text-align: left` |
|
||||
| **`overflow: hidden` on `.page` containers** | **MANDATORY for multi-page documents.** Decorative elements (glow, gradient circles, oversized backgrounds) with `width > 100%` or negative offsets cause `scrollWidth > clientWidth`, triggering Playwright to shrink the entire page → content drifts left. `.page { overflow: hidden }` clips decorative overflow without affecting visible content |
|
||||
| **Horizontal flex rows must have `flex-wrap`** | ≥3 inline items (flow bars, step lists, tag rows) without `flex-wrap: wrap` will overflow the page right edge when content is long. See `typesetting/overflow.md` §3.5 for full rules |
|
||||
|
||||
| Prohibited | Reason |
|
||||
|--------|------|
|
||||
| ❌ Timeline graphics | Complex connecting lines easily misalign in PDF rendering |
|
||||
| ❌ Complex SVG-drawn structure/flow diagrams | Unless user explicitly requests |
|
||||
| ❌ Code-drawn maps or flags | Poor quality |
|
||||
| ❌ Base64 images (when exceeding 10MB) | File too large. Small image base64 is acceptable (Playwright cannot load local paths) |
|
||||
| ❌ Content truncation | Must adjust container height to ensure all content fully displayed |
|
||||
| ❌ Pure white background (#FFFFFF) | Lacks design quality |
|
||||
| ❌ Transparent background | PDF output cannot be transparent |
|
||||
|
||||
---
|
||||
|
||||
## 11. Poster Font Recommendations
|
||||
|
||||
### 11.1 CJK Font Recommendations
|
||||
|
||||
**For Chinese posters (serious/formal scenes):**
|
||||
|
||||
| Purpose | Recommended Font | Google Fonts / CDN Name | Style |
|
||||
|------|---------|-------------------|------|
|
||||
| CJK title (serious) | DingTalk JinBuTi | `DingTalk JinBuTi` (letter-spacing: -5%) | Bold, impactful |
|
||||
| CJK title (alt) | Douyin Sans / Alimama FangYuanTi VF Bold | Via CDN | Modern Chinese |
|
||||
| CJK title (serif) | Swei B2 Serif CJKtc Bold | Via CDN | Elegant serif |
|
||||
| CJK body | HarmonyOS Sans SC | `HarmonyOS Sans SC` | Clear, readable |
|
||||
| CJK body (fallback) | Noto Sans SC Regular | `Noto Sans SC:wght@400` | Google Fonts guaranteed |
|
||||
| CJK artistic | Noto Serif SC Bold | `Noto Serif SC:wght@700` | Elegant, artistic |
|
||||
| Handwritten | ZhanKuKuaiLeTi2016XiuDingBan-2 | Via CDN | Casual, playful |
|
||||
| Pixel/dot-matrix | DottedSongtiSquareRegular | Via CDN | Retro, xiangsuti |
|
||||
|
||||
**For English posters:**
|
||||
|
||||
| Purpose | Recommended Font | Google Fonts Name | Style |
|
||||
|------|---------|-------------------|------|
|
||||
| English/number title | Futura | System / `Futura` | Classic geometric |
|
||||
| English body | Noto Sans SC | `Noto Sans SC:wght@400` | Clean, modern |
|
||||
| English title (Google) | Inter Black | `Inter:wght@900` | Modern geometric |
|
||||
| English serif | Playfair Display | `Playfair+Display:wght@900` | Classic editorial |
|
||||
| Number emphasis | Inter Black | `Inter:wght@900` | Data sculpture |
|
||||
|
||||
### 11.2 Font Usage Constraints
|
||||
- Entire poster **maximum 3 fonts**
|
||||
- Title and body may use different fonts, but must be visually harmonious
|
||||
- **Never reduce font size or line height to squeeze in more content**
|
||||
- Font content in cards should be **vertically centered** within the card
|
||||
- You may use different style fonts for entertaining or artistic scenes
|
||||
|
||||
---
|
||||
|
||||
## 12. Coordination with design_engine.py
|
||||
|
||||
### 12.0 ★★★ Stable Layout Selection Strategy (Anti-Overflow Core Rule)
|
||||
|
||||
> **Content-heavy posters must bypass the Blueprint 12×12 Grid and use a pure flow-based HTML approach.**
|
||||
|
||||
**Why:** The Blueprint 12×12 Grid allocates space with fixed row heights and cannot predict actual text rendering height, causing:
|
||||
- Text overflowing Glass Canvas containers
|
||||
- CJK character spacing stretched by `text-align: justify`
|
||||
- Inaccurate row allocation for multiple components, text overlap
|
||||
|
||||
**Route selection:**
|
||||
|
||||
| Scenario | Recommended Route | Notes |
|
||||
|------|---------|------|
|
||||
| Title poster (≤ 50 chars) | Blueprint JSON | Few components, little content, Grid sufficient |
|
||||
| Standard poster (50-150 chars) | Blueprint JSON | Grid mostly sufficient, mind row allocation |
|
||||
| **Info-dense poster (>150 chars or multiple sections)** | **★ Direct HTML Flow** | **Strongly recommended — completely avoids overflow** |
|
||||
| **Multi-page info poster** | **★ Direct HTML Flow** | **Strongly recommended** |
|
||||
| Poster with images | Direct HTML Flow | Image embedding is more stable |
|
||||
|
||||
### 12.1 Direct HTML Flow Approach (Recommended Default)
|
||||
|
||||
**Core idea: No Grid, no fixed height, content flows naturally, never overflows.**
|
||||
|
||||
Write HTML directly, convert to PDF via `html2poster.js`. The poster is a **single continuous `<div class="poster">` container** — NOT split into separate `<div class="page">` blocks.
|
||||
|
||||
#### ★★★ Single-Canvas vs Multi-Page (Iron Rule)
|
||||
|
||||
| Approach | When to Use | HTML Structure |
|
||||
|----------|-------------|----------------|
|
||||
| **Single Canvas** (default) | All content forms one unified poster | One `<div class="poster">`, no `page-break` |
|
||||
| **Multi-Page** (exception) | User explicitly requests separate pages (e.g., "make a 4-page booklet") | Multiple `<div class="page">` with `page-break-after: always` |
|
||||
|
||||
> **Default = Single Canvas.** A poster is ONE design composition, not a paginated report. Multi-page is only for booklets/multi-page documents where the user explicitly asks for page separation.
|
||||
|
||||
#### ★★★ Dynamic Height (Anti-Blank-Bottom Iron Rule)
|
||||
|
||||
**NEVER hardcode `@page { size: W H }` or `.poster { min-height: Xpx }` with a fixed height.** This creates blank space at the bottom when content is shorter than the hardcoded value.
|
||||
|
||||
**Correct approach — use `html2poster.js`:**
|
||||
|
||||
`html2poster.js` automatically handles all of this — you just need to write the HTML correctly and call one command:
|
||||
|
||||
```bash
|
||||
node "$PDF_SKILL_DIR/scripts/html2poster.js" poster.html --output poster.pdf --width 720px
|
||||
```
|
||||
|
||||
It will automatically:
|
||||
1. Add `overflow: hidden` to `.poster` container (clips decorative overflow)
|
||||
2. Inject `@page { margin: 0 }` (zero margins)
|
||||
3. Sync `html/body` background with `.poster` background
|
||||
4. Measure `.poster` scrollHeight (actual content height)
|
||||
5. Generate single-page vector PDF with exact dimensions
|
||||
|
||||
**⚠️ Do NOT use `html2pdf-next.js` for posters.** It is designed for multi-page documents and will inject 20mm margins / A4 pagination.
|
||||
|
||||
**⚠️ Do NOT write hand-written Playwright scripts for posters.** `html2poster.js` handles everything.
|
||||
|
||||
```css
|
||||
/* ✅ CORRECT CSS for poster HTML (html2poster.js handles the rest): */
|
||||
html, body { margin: 0; padding: 0; background: var(--c-bg); }
|
||||
.poster { width: 720px; position: relative; background: var(--c-bg); }
|
||||
/* Note: overflow:hidden on .poster is auto-injected by html2poster.js,
|
||||
but including it in CSS is fine too */
|
||||
```
|
||||
|
||||
**CSS iron rules:**
|
||||
```css
|
||||
html, body { margin: 0; padding: 0; background: var(--c-bg); }
|
||||
|
||||
/* Single poster canvas — NO fixed height */
|
||||
.poster {
|
||||
width: 720px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: var(--c-bg);
|
||||
/* Height is determined by content, measured at render time */
|
||||
}
|
||||
|
||||
/* Card/content block */
|
||||
.card {
|
||||
background: rgba(255,255,255,0.7);
|
||||
border-radius: 12px;
|
||||
padding: 24px 28px;
|
||||
margin-bottom: 20px;
|
||||
/* No height, no max-height, no overflow:hidden */
|
||||
}
|
||||
|
||||
/* CJK text iron rules */
|
||||
.card, .card p, .card li {
|
||||
text-align: left; /* Absolutely no justify! CJK stretches letter-spacing */
|
||||
line-height: 1.6;
|
||||
word-break: break-all; /* CJK natural line break */
|
||||
}
|
||||
|
||||
/* Image */
|
||||
.hero-image {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
```
|
||||
|
||||
**HTML structure template (Single Canvas):**
|
||||
```html
|
||||
<div class="poster">
|
||||
<!-- Hero section -->
|
||||
<div class="hero"> ... </div>
|
||||
|
||||
<!-- City 1 -->
|
||||
<div class="city-section">
|
||||
<h2>南京</h2>
|
||||
<div class="items-row"> ... attractions ... </div>
|
||||
<div class="food-row"> ... food ... </div>
|
||||
</div>
|
||||
|
||||
<!-- City separator (thin line, NOT page-break) -->
|
||||
<div class="city-sep"></div>
|
||||
|
||||
<!-- City 2 -->
|
||||
<div class="city-section"> ... </div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="poster-footer"> ... </div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Why it's stable:**
|
||||
- No fixed height — content naturally defines poster size
|
||||
- No `overflow: hidden`, text always fully displayed
|
||||
- `text-align: left` avoids CJK letter-spacing stretch
|
||||
- Single canvas = one unified composition, not chopped pages
|
||||
- PDF height measured at render time = zero blank space
|
||||
|
||||
**Convert to PDF and PNG:**
|
||||
```bash
|
||||
# PDF (vector, single-page, zero margins, auto-height):
|
||||
node "$PDF_SKILL_DIR/scripts/html2poster.js" poster.html --output poster.pdf --width 720px
|
||||
|
||||
# PNG preview (screenshot):
|
||||
# Use Playwright screenshot with measured height
|
||||
```
|
||||
|
||||
> **⚠️ Do NOT write hand-written Playwright `page.pdf()` scripts.** Use `html2poster.js` which handles overflow:hidden, margin:0, background sync, and height measurement automatically.
|
||||
|
||||
### 12.2 Blueprint Grid Approach (Only for Simple Posters)
|
||||
|
||||
| Behavior | Status | Notes |
|
||||
|------|------|------|
|
||||
| Dynamic inset | ✅ Fixed | More content → smaller inset (`8% 10%`), less content → default (`10% 12%`) |
|
||||
| Glass Canvas overflow | ✅ Fixed | `min-height:100%` replaces `height:100%`, removed `overflow:hidden` |
|
||||
| Glass Canvas / Process List stretch | ✅ Fixed | Auto `align-items: stretch` |
|
||||
|
||||
### 12.3 Poster Marker in Blueprint
|
||||
|
||||
Add `scene: "poster"` marker in `art_direction` so `design_engine.py` can identify poster scenes and apply specific logic in the future:
|
||||
|
||||
```json
|
||||
{
|
||||
"art_direction": {
|
||||
"scene": "poster",
|
||||
"palette_mode": "minimal",
|
||||
"color_harmony": "auto",
|
||||
"background_svg": "flow",
|
||||
"design_rationale": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 14. PDF Conversion Iron Rules
|
||||
|
||||
### 14.1 Background Color Consistency
|
||||
```css
|
||||
/* Must ensure html/body background = poster canvas background */
|
||||
html, body {
|
||||
background: var(--c-bg); /* Same color as .canvas background */
|
||||
}
|
||||
```
|
||||
- Playwright `page.pdf({ printBackground: true })` renders body background color
|
||||
- If body is white but poster is gray, white borders appear in the PDF
|
||||
- `design_engine.py` already auto-injects `background: var(--c-bg)`, but if bypassing the engine and writing HTML directly, **you must ensure manually**
|
||||
|
||||
**Multi-page posters / brochures with mixed page backgrounds:**
|
||||
- When pages alternate between dark and light backgrounds, set `body { background }` to the **darkest page color** (see SKILL.md "Background Color Consistency" for full rationale)
|
||||
- This eliminates sub-pixel white edges on dark pages without affecting light pages
|
||||
|
||||
### 14.2 Content Centering (Anti-Drift)
|
||||
|
||||
**Poster content must be centered in the PDF, no left or right drift allowed.**
|
||||
|
||||
Common drift causes and fixes:
|
||||
| Cause | Fix |
|
||||
|------|------|
|
||||
| `@page { margin }` not 0 | Must be `@page { size: <w> <h>; margin: 0; }` |
|
||||
| `.safe-zone` `inset` left-right asymmetric | Ensure `inset: Y% X%` uses same X% for left and right |
|
||||
| Component `grid_area` only uses partial columns | Most components should span `1 / 1 / X / 13` (full width) |
|
||||
| Content container has `max-width` but no `margin: 0 auto` | Add `margin: 0 auto` to center |
|
||||
| Playwright PDF default margin | Pass `margin: { top: 0, right: 0, bottom: 0, left: 0 }` |
|
||||
|
||||
### 14.3 Anti-Blank Edges (Dynamic Height Iron Rule)
|
||||
|
||||
**Poster edges, top, and bottom should not have large meaningless whitespace.**
|
||||
|
||||
**★★★ CRITICAL: Never hardcode poster height.** The poster is a single continuous canvas — its height is defined by content, not by a fixed CSS value.
|
||||
|
||||
| Pattern | Status | Result |
|
||||
|---------|--------|--------|
|
||||
| `@page { size: 720px 3600px }` | ❌ FORBIDDEN | Creates 853px+ blank space at bottom if content is shorter |
|
||||
| `.poster { min-height: 3600px }` | ❌ FORBIDDEN | Same problem — blank bottom |
|
||||
| `.poster { width: 720px }` (no height) | ✅ CORRECT | Content defines height naturally |
|
||||
| `node html2poster.js poster.html --width 720px` | ✅ CORRECT | Auto-measures height, zero blank space |
|
||||
|
||||
**The 720×960 dimension is for multi-page documents with `page-break-after: always` only — NOT for single-canvas posters.**
|
||||
|
||||
- Content must make full use of page area, no more than 20% unused space within safe-zone
|
||||
- If excess whitespace exists, enlarge font sizes or modules to balance the layout
|
||||
- Checklist:
|
||||
- [ ] Poster height defined by content (no hardcoded height)?
|
||||
- [ ] PDF generated via `html2poster.js` (not html2pdf-next.js)?
|
||||
- [ ] First component starts from the top?
|
||||
- [ ] Last component reaches the bottom with proper padding?
|
||||
- [ ] Left-right margins symmetric?
|
||||
- [ ] No blank space at bottom of PDF?
|
||||
|
||||
---
|
||||
|
||||
## 15. Preflight Checklist (Poster-Specific)
|
||||
|
||||
Before outputting JSON Blueprint, verify the following items:
|
||||
|
||||
```
|
||||
□ ★ Single-canvas check: poster is ONE continuous <div>, not split into separate pages? (unless user explicitly requests multi-page)
|
||||
□ ★ Dynamic height check: no hardcoded @page size height or .poster min-height? PDF generated via html2poster.js?
|
||||
□ ★ Anti-modularization check: ≤ 3 containers with background+border in single-page poster? No 2×2 card grid? Hierarchy via font size/weight not border colors?
|
||||
□ Emphasis elements (price/date/address) highlighted via font size+weight, not wrapped in separate color blocks?
|
||||
□ Overall looks like a design piece, not an APP interface or dashboard?
|
||||
□ total_pages = 1 (single canvas)? (unless user explicitly requests multi-page booklet)
|
||||
□ No hardcoded @page height or .poster min-height? Content defines height?
|
||||
□ Left-right margins symmetric? (no left/right drift)
|
||||
□ html/body background = poster canvas background? (no color mismatch)
|
||||
□ No bottom blank space in PDF? (height measured dynamically)
|
||||
□ Page main title ≥ 50px? Body text ≥ 24px?
|
||||
□ Text volume ≤ 150 chars? (single Glass Canvas)
|
||||
□ palette_mode not #FFFFFF pure white?
|
||||
□ No single image filling the background?
|
||||
□ No overlap between cards?
|
||||
□ No Timeline / complex SVG structure diagrams?
|
||||
□ All content fully displayed, not truncated?
|
||||
□ Emphasis text font size < title font size?
|
||||
□ Entire design ≤ 3 fonts?
|
||||
□ cover_hero page ≤ 4 components?
|
||||
□ Hero_Typography has <br> line breaks?
|
||||
□ @page { margin: 0 } set? (prevents PDF drift)
|
||||
```
|
||||
Executable
+433
@@ -0,0 +1,433 @@
|
||||
# Brief: PDF Processing
|
||||
|
||||
Work with existing PDFs: extract, merge, split, fill forms, convert formats, or **reformat** with a new design. Usually a Light triage path — except reformat, which escalates to Standard.
|
||||
|
||||
---
|
||||
|
||||
## Decision Tree
|
||||
|
||||
```
|
||||
User request
|
||||
├─ "Extract text/tables/images" → §Extract
|
||||
│ └─ No text extracted? → §OCR Fallback
|
||||
├─ "Merge/split/rotate/crop pages" → §Pages
|
||||
├─ "Fill a form" → §Forms (check fillable first)
|
||||
├─ "Read/write metadata" → §Metadata
|
||||
├─ "Convert DOCX/PPTX/XLSX to PDF" → §Convert
|
||||
│ └─ DOCX with TOC? → §DOCX Pipeline (5-step)
|
||||
├─ "Redesign/reformat a document" → §Reformat
|
||||
│ └─ With a reference template? → §Template-Guided Reformat
|
||||
├─ "Decrypt/repair a PDF" → §Encrypted / §Corrupted
|
||||
├─ "Optimize file size" → §Optimize
|
||||
└─ "Batch process many PDFs" → §Batch Processing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Check
|
||||
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" env.check
|
||||
```
|
||||
|
||||
Reports availability but does **not** auto-install.
|
||||
|
||||
Entry point: `python3 "$PDF_SKILL_DIR/scripts/pdf.py" <group>.<action> [options]`
|
||||
|
||||
All commands return JSON on stdout (`{"status": "success", "data": {...}}`) or stderr (`{"status": "error", ...}`).
|
||||
Exit codes: 0 = success, 1 = bad args, 2 = file not found, 3 = parse error, 4 = operation failed.
|
||||
|
||||
---
|
||||
|
||||
## §Extract
|
||||
|
||||
### Basic Extraction (pdf.py)
|
||||
|
||||
```bash
|
||||
# Text (full or page range)
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" extract.text report.pdf
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" extract.text report.pdf -p 1-3
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" extract.text report.pdf -p 1,4,7
|
||||
|
||||
# Tables — returns structured JSON
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" extract.table report.pdf
|
||||
|
||||
# Images — dumps embedded rasters to directory
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" extract.image report.pdf -o ./images/
|
||||
```
|
||||
|
||||
### Precise Coordinates (pdfplumber)
|
||||
|
||||
```python
|
||||
import pdfplumber
|
||||
|
||||
with pdfplumber.open("document.pdf") as pdf:
|
||||
page = pdf.pages[0]
|
||||
|
||||
# Characters with exact coordinates
|
||||
for char in page.chars[:10]:
|
||||
print(f"'{char['text']}' at x:{char['x0']:.1f} y:{char['y0']:.1f}")
|
||||
|
||||
# Text within a bounding box (left, top, right, bottom)
|
||||
bbox_text = page.within_bbox((100, 100, 400, 200)).extract_text()
|
||||
|
||||
# Advanced table extraction with custom settings
|
||||
table_settings = {
|
||||
"vertical_strategy": "lines",
|
||||
"horizontal_strategy": "lines",
|
||||
"snap_tolerance": 3,
|
||||
"intersection_tolerance": 15
|
||||
}
|
||||
tables = page.extract_tables(table_settings)
|
||||
|
||||
# Visual debugging
|
||||
img = page.to_image(resolution=150)
|
||||
img.save("debug_layout.png")
|
||||
```
|
||||
|
||||
### Fast Rendering (pypdfium2)
|
||||
|
||||
pypdfium2 is a Python binding for PDFium (Chromium's PDF library) — fast rendering and text extraction.
|
||||
|
||||
```python
|
||||
import pypdfium2 as pdfium
|
||||
|
||||
pdf = pdfium.PdfDocument("document.pdf")
|
||||
|
||||
# Render single page
|
||||
bitmap = pdf[0].render(scale=2.0)
|
||||
bitmap.to_pil().save("page_1.png", "PNG")
|
||||
|
||||
# Batch render all pages
|
||||
for i, page in enumerate(pdf):
|
||||
bitmap = page.render(scale=1.5)
|
||||
bitmap.to_pil().save(f"page_{i+1}.jpg", "JPEG", quality=90)
|
||||
|
||||
# Extract text
|
||||
for i, page in enumerate(pdf):
|
||||
print(f"Page {i+1}: {len(page.get_text())} chars")
|
||||
```
|
||||
|
||||
### CLI Rendering & Extraction (poppler-utils)
|
||||
|
||||
```bash
|
||||
# High-resolution PNG rendering
|
||||
pdftoppm -png -r 300 document.pdf output_prefix
|
||||
pdftoppm -png -r 600 -f 1 -l 3 document.pdf high_res # specific pages
|
||||
|
||||
# Text with bounding box coordinates
|
||||
pdftotext -bbox-layout document.pdf output.xml
|
||||
|
||||
# Extract embedded images (faster than full-page rendering)
|
||||
pdfimages -all document.pdf images/img
|
||||
pdfimages -list document.pdf
|
||||
```
|
||||
|
||||
### §OCR Fallback (Scanned PDFs)
|
||||
|
||||
When `pdfplumber` extracts 0 characters, the PDF is likely a scanned image:
|
||||
|
||||
```python
|
||||
import pytesseract
|
||||
from pdf2image import convert_from_path
|
||||
|
||||
def extract_text_with_ocr(pdf_path):
|
||||
images = convert_from_path(pdf_path)
|
||||
return "".join(pytesseract.image_to_string(img) for img in images)
|
||||
```
|
||||
|
||||
Prerequisites: `pip install pdf2image pytesseract` + install Tesseract OCR and poppler.
|
||||
|
||||
---
|
||||
|
||||
## §Pages
|
||||
|
||||
### Basic Operations (pdf.py)
|
||||
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.merge a.pdf b.pdf -o combined.pdf
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.split book.pdf -o ./chapters/
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.rotate doc.pdf 90 -o rotated.pdf
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.rotate doc.pdf 180 -o rotated.pdf -p 1-3
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.crop doc.pdf 50,50,550,750 -o trimmed.pdf
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.clean doc.pdf -o cleaned.pdf
|
||||
```
|
||||
|
||||
### Complex Page Manipulation (qpdf)
|
||||
|
||||
```bash
|
||||
# Split into groups of N pages
|
||||
qpdf --split-pages=3 input.pdf output_group_%02d.pdf
|
||||
|
||||
# Extract complex page ranges
|
||||
qpdf input.pdf --pages input.pdf 1,3-5,8,10-end -- extracted.pdf
|
||||
|
||||
# Merge specific pages from multiple PDFs
|
||||
qpdf --empty --pages doc1.pdf 1-3 doc2.pdf 5-7 doc3.pdf 2,4 -- combined.pdf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §Metadata
|
||||
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" meta.get doc.pdf
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" meta.set doc.pdf -o out.pdf -d '{"Title": "Report", "Author": "Jane"}'
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" meta.brand doc.pdf -o branded.pdf
|
||||
```
|
||||
|
||||
Recognised keys: `Title`, `Author`, `Subject`, `Keywords`, `Creator`, `Producer`.
|
||||
|
||||
---
|
||||
|
||||
## §Forms
|
||||
|
||||
### Step 1 — Check if fillable
|
||||
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.info input.pdf
|
||||
```
|
||||
|
||||
If `has_fields: true` → **Fillable workflow**. If `false` → **Non-fillable workflow**.
|
||||
|
||||
### Fillable Workflow
|
||||
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.fill input.pdf -o filled.pdf \
|
||||
-d '{"name": "John", "agree": "true", "country": "US"}'
|
||||
```
|
||||
|
||||
| Type | Value | Example |
|
||||
|------|-------|---------|
|
||||
| text | Free string | `"name": "Jane Doe"` |
|
||||
| checkbox | `"true"` / `"false"` | `"agree": "true"` |
|
||||
| radio | One of `radio_options[].value` | `"gender": "/Choice1"` |
|
||||
| dropdown | One of `choice_options[].value` | `"country": "US"` |
|
||||
|
||||
For complex forms: `form.detail input.pdf -o fields.json` (full field info) and `form.render input.pdf -o ./pages/` (visual check).
|
||||
|
||||
### Non-Fillable Workflow (Annotation-Based)
|
||||
|
||||
For PDFs without interactive fields (scanned forms, image-based). All four steps are mandatory.
|
||||
|
||||
**Step 1 — Render pages as PNG**:
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.render input.pdf -o ./pages/
|
||||
```
|
||||
|
||||
**Step 2 — Create `fields.json`** with annotation regions. Determine bbox coordinates by inspecting the rendered PNG (use PIL to get pixel dimensions, then estimate [left, top, right, bottom] for each field). `dims` must match PNG dimensions exactly.
|
||||
|
||||
```json
|
||||
{
|
||||
"sheet": [{
|
||||
"pg": 1, "dims": [1000, 1400],
|
||||
"regions": [{
|
||||
"id": "last_name", "hint": "Last name field",
|
||||
"label": {"tag": "Last name", "bbox": [30, 125, 95, 142]},
|
||||
"target": {"bbox": [100, 125, 280, 142]},
|
||||
"ink": {"value": "Simpson", "size": 14, "color": "000000"}
|
||||
}]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3 — Validate**:
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.check-bbox fields.json
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.validate 1 fields.json page1.png validation.png
|
||||
```
|
||||
|
||||
**Step 4 — Fill**:
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.annotate input.pdf fields.json -o filled.pdf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §Encrypted
|
||||
|
||||
```bash
|
||||
# Check encryption status
|
||||
qpdf --show-encryption encrypted.pdf
|
||||
|
||||
# Decrypt with password
|
||||
qpdf --password=secret123 --decrypt encrypted.pdf decrypted.pdf
|
||||
|
||||
# Encrypt a PDF
|
||||
qpdf --encrypt user_pass owner_pass 256 --print=none --modify=none -- input.pdf encrypted.pdf
|
||||
```
|
||||
|
||||
Python alternative:
|
||||
```python
|
||||
from pypdf import PdfReader
|
||||
reader = PdfReader("encrypted.pdf")
|
||||
if reader.is_encrypted:
|
||||
reader.decrypt("password")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §Corrupted
|
||||
|
||||
```bash
|
||||
qpdf --check corrupted.pdf
|
||||
qpdf --replace-input corrupted.pdf # in-place repair
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §Optimize
|
||||
|
||||
```bash
|
||||
qpdf --linearize input.pdf optimized.pdf # web-optimized (fast first-page load)
|
||||
qpdf --optimize-level=all input.pdf compressed.pdf # maximum compression
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §Reformat
|
||||
|
||||
Take an existing document and rebuild it with a new visual design. Content is preserved; layout is rebuilt.
|
||||
|
||||
```
|
||||
1. EXTRACT → Extract content from source
|
||||
2. STRUCTURE → Organize into sections (headings, body, tables, lists)
|
||||
3. DELEGATE → Route to appropriate brief:
|
||||
Structured → briefs/report.md (ReportLab)
|
||||
Visual → briefs/creative-fixed-canvas.md (Playwright)
|
||||
4. BUILD → Follow the delegated brief's full workflow
|
||||
5. DELIVER → New PDF, same content, new design
|
||||
```
|
||||
|
||||
### §Template-Guided Reformat
|
||||
|
||||
When user provides a reference PDF to match:
|
||||
|
||||
```
|
||||
1. ANALYZE → Extract design DNA from template:
|
||||
- meta.get (page size), extract.image (color samples),
|
||||
extract.text (structure), pdftoppm (visual reference)
|
||||
2. DOCUMENT → Record: page size, margins, colors, fonts, layout, header/footer
|
||||
3. DELEGATE → Route to brief WITH design constraints (not defaults)
|
||||
4. BUILD → Follow brief workflow, constrained to template DNA
|
||||
5. COMPARE → pdftoppm both, visually compare side-by-side
|
||||
```
|
||||
|
||||
**Key principles:** Match the spirit, not the pixels. Prefer source files over PDF when available. Declare font substitutions upfront.
|
||||
|
||||
---
|
||||
|
||||
## §Convert
|
||||
|
||||
### Office → PDF (LibreOffice)
|
||||
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" convert.office input.docx -o output.pdf
|
||||
# Or directly:
|
||||
soffice --headless --convert-to pdf --outdir ./output input.docx
|
||||
```
|
||||
|
||||
**Supported**: `.docx`, `.doc`, `.odt`, `.rtf`, `.pptx`, `.ppt`, `.xlsx`, `.xls`, `.ods`, `.csv`, `.html`
|
||||
|
||||
**macOS path**: `/Applications/LibreOffice.app/Contents/MacOS/soffice`
|
||||
|
||||
**Gotchas:**
|
||||
- soffice allows only one instance at a time; use `--env:UserInstallation=file:///tmp/libreoffice_tmp` if needed
|
||||
- Missing Chinese fonts → squares. Ensure Noto Sans SC/Noto Serif SC are installed
|
||||
- Large files (>50MB) may take 1-2 min; set reasonable timeout
|
||||
|
||||
**Priority**: Always prefer soffice for Office→PDF. Fall back to python-pptx/python-docx + HTML + Playwright only if soffice is unavailable.
|
||||
|
||||
### Spreadsheet → PDF (without LibreOffice)
|
||||
|
||||
Use openpyxl + HTML + Playwright. Columns ≤ 6 → portrait; > 6 → landscape. Scale font inversely with column count.
|
||||
|
||||
### §DOCX Pipeline (5-Step with TOC)
|
||||
|
||||
For DOCX files that need TOC generation/correction (LibreOffice `--headless` does not recalculate PAGEREF fields).
|
||||
|
||||
**When to use**: DOCX has 3+ headings, or mentions "table of contents" / "TOC", or already contains a TOC section. When in doubt, run `toc_validate.py fix-docx` — if it returns `no_toc_needed`, simple conversion is fine.
|
||||
|
||||
```bash
|
||||
# Step 1: Convert to PDF (pass1)
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" convert.office input.docx -o pass1.pdf
|
||||
|
||||
# Step 2: Clean blank pages
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.clean pass1.pdf -o pass1_clean.pdf
|
||||
|
||||
# Step 3: Fix TOC (adds HYPERLINK + PAGEREF + bookmarks)
|
||||
python3 "$PDF_SKILL_DIR/scripts/toc_validate.py" fix-docx input.docx -o fixed.docx
|
||||
|
||||
# Step 4: Correct TOC page numbers using pass1 as reference
|
||||
python3 "$PDF_SKILL_DIR/scripts/toc_validate.py" fix-pages fixed.docx pass1_clean.pdf -o final.docx
|
||||
|
||||
# Step 5: Final convert + clean
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" convert.office final.docx -o output.pdf
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.clean output.pdf -o output_clean.pdf
|
||||
```
|
||||
|
||||
Check `fix-docx` output `action`: `fixed` → use fixed.docx, `skipped` → use original, `no_toc_needed` → skip to Step 5.
|
||||
|
||||
Optional post-validation: `toc_validate.py check-conversion final.docx output_clean.pdf`
|
||||
|
||||
---
|
||||
|
||||
## §Batch Processing
|
||||
|
||||
```python
|
||||
import os, glob, logging
|
||||
from pypdf import PdfReader, PdfWriter
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def batch_process(input_dir, operation='merge'):
|
||||
pdf_files = sorted(glob.glob(os.path.join(input_dir, "*.pdf")))
|
||||
if operation == 'merge':
|
||||
writer = PdfWriter()
|
||||
for f in pdf_files:
|
||||
try:
|
||||
for page in PdfReader(f).pages:
|
||||
writer.add_page(page)
|
||||
logger.info(f"Processed: {f}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed: {f}: {e}")
|
||||
with open("merged.pdf", "wb") as out:
|
||||
writer.write(out)
|
||||
elif operation == 'extract_text':
|
||||
for f in pdf_files:
|
||||
try:
|
||||
text = "".join(p.extract_text() for p in PdfReader(f).pages)
|
||||
with open(f.replace('.pdf', '.txt'), 'w') as out:
|
||||
out.write(text)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed: {f}: {e}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Caveats & Performance
|
||||
|
||||
| Topic | Detail |
|
||||
|-------|--------|
|
||||
| < 50 MB | Instant |
|
||||
| 50–200 MB | 1–2 minutes |
|
||||
| > 200 MB | Split first, or extend timeout |
|
||||
| Memory | ~2-3× input file size |
|
||||
| Text extraction speed | `pdftotext -bbox-layout` fastest; pdfplumber for structured data |
|
||||
| Image extraction | `pdfimages` faster than full-page rendering |
|
||||
| Large PDFs | Process in chunks to manage memory |
|
||||
|
||||
## Tooling Inventory
|
||||
|
||||
| Library / Tool | Role | Licence |
|
||||
|----------------|------|---------|
|
||||
| pikepdf | Low-level PDF manipulation (forms, pages, metadata) | MPL-2.0 |
|
||||
| pdfplumber | Content extraction (text, tables) | MIT |
|
||||
| pypdfium2 | Fast rendering, text extraction (PyMuPDF alternative) | Apache/BSD |
|
||||
| pypdf | Merge, split, crop, metadata, encryption | BSD |
|
||||
| poppler-utils | CLI text/image extraction, rendering | GPL-2 |
|
||||
| qpdf | Page manipulation, optimization, encryption, repair | Apache |
|
||||
| pytesseract | OCR for scanned PDFs | Apache |
|
||||
| pdf2image | PDF-to-image conversion via poppler | MIT |
|
||||
| LibreOffice | Office format conversion engine | MPL-2.0 |
|
||||
Executable
+1703
File diff suppressed because it is too large.
Load diff
Executable
+227
@@ -0,0 +1,227 @@
|
||||
## Resume / CV Template (ATS-Friendly)
|
||||
|
||||
Single-column, clean layout optimised for Applicant Tracking Systems (ATS). No graphics, no sidebars, no colour blocks - just well-structured text that parses perfectly by HR software.
|
||||
|
||||
**When to use this template:**
|
||||
- Applying to corporate jobs through online portals
|
||||
- Any scenario where the PDF will be machine-parsed before a human reads it
|
||||
- When the recruiter explicitly asks for "a standard resume"
|
||||
|
||||
**When NOT to use (use Academic brief instead):**
|
||||
- Creative/design industry positions → Academic brief (AltaCV-style)
|
||||
- Academic CV with publications → Academic brief (Academic CV)
|
||||
|
||||
### Resume Design Rules
|
||||
- **Target 1 page** unless user specifies otherwise
|
||||
- **Margins**: `left=1.5cm, right=1.5cm, top=1.5cm, bottom=1.5cm`
|
||||
- **No cover page** - content starts immediately
|
||||
- **No TOC** - too short for table of contents
|
||||
- **Font**: FreeSerif (English) or Noto Sans SC (Chinese)
|
||||
- **Body font size**: 10-10.5pt; Name: 22-26pt; Section titles: 13-14pt
|
||||
- **⚠️ Minimum font size: 12px (9pt) - HARD FLOOR.** No text in the entire resume may render smaller than 12px. This includes contact info, meta text, footnotes, and captions. Anything below 12px is unreadable in print and fails accessibility checks.
|
||||
- **Section separator**: thin horizontal rule (`HRFlowable`)
|
||||
- **Bullet style**: `•` with tight spacing
|
||||
|
||||
### Resume Line-Break Rules (Language-Aware)
|
||||
- **English**: Prefer breaking at word boundaries (spaces, hyphens). If a long word must be split to avoid excessive whitespace, break at a valid syllable boundary and insert a hyphen (`-`) - this is standard typographic practice (e.g., `experi-\nence`, `develop-\nment`). ReportLab supports `wordWrap='CJK'` only for CJK content; for English use default paragraph wrapping with `allowWidows=0, allowOrphans=0`.
|
||||
- **Chinese/CJK**: Break allowed between any two CJK characters. Never break between a CJK character and its adjacent punctuation (、。,)》 etc. must stay with the preceding character.
|
||||
- **Mixed content** (e.g., "Python 开发工程师"): Break at CJK boundaries or English word boundaries. Never split an English word in a CJK paragraph unless hyphenated.
|
||||
- **Contact line**: Email, phone, location separated by `|` or `·`. Each segment must stay on one line - if too long, move to next line at the separator, not mid-segment.
|
||||
- **Dates and ranges**: "Jan 2022 - Present" must stay as one unit. Never break a date range across lines.
|
||||
|
||||
### Resume Page-Fill Rules (Anti-Blank-Space)
|
||||
- **Goal: Fill ≥85% of the page height.** Content should reach at least the bottom quarter of the page. A resume that stops at 60% height with blank space below = FAIL.
|
||||
- **Adaptive spacing strategy** (apply in order until page is ≥85% filled):
|
||||
1. Increase `spaceBefore` / `spaceAfter` on section headers (from 10pt up to 18pt)
|
||||
2. Increase `leading` (line height) on body text (from 14pt up to 18pt)
|
||||
3. Increase body `fontSize` by 0.5-1pt (from 10pt up to 11.5pt max)
|
||||
4. Add a "Professional Summary" or "Key Achievements" section if none exists
|
||||
5. Increase margins slightly (from 1.5cm up to 2cm) to reduce line width and push content downward
|
||||
- **Never leave a visible blank area > 3cm at the bottom of the page.**
|
||||
- **If content overflows to page 2**: do the reverse - reduce spacing, tighten leading (min 12pt), reduce fontSize (min 9pt / 12px), before removing content.
|
||||
|
||||
### Complete Resume Template
|
||||
|
||||
```python
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.lib.units import cm, mm
|
||||
from reportlab.lib.styles import ParagraphStyle
|
||||
from reportlab.lib.enums import TA_LEFT, TA_CENTER
|
||||
from reportlab.lib import colors
|
||||
from reportlab.platypus import (
|
||||
SimpleDocTemplate, Paragraph, Spacer, HRFlowable, Table, TableStyle
|
||||
)
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
from reportlab.pdfbase.pdfmetrics import registerFontFamily
|
||||
|
||||
# ── Font Registration ──
|
||||
pdfmetrics.registerFont(TTFont('FreeSerif', '/usr/share/fonts/truetype/freefont/FreeSerif.ttf'))
|
||||
pdfmetrics.registerFont(TTFont('FreeSerif-Bold', '/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf'))
|
||||
pdfmetrics.registerFont(TTFont('FreeSerif-Italic', '/usr/share/fonts/truetype/freefont/FreeSerifItalic.ttf'))
|
||||
pdfmetrics.registerFont(TTFont('FreeSerif-BoldItalic', '/usr/share/fonts/truetype/freefont/FreeSerifBoldItalic.ttf'))
|
||||
registerFontFamily('FreeSerif', normal='FreeSerif', bold='FreeSerif-Bold', italic='FreeSerif-Italic', boldItalic='FreeSerif-BoldItalic')
|
||||
# For Chinese resumes, also register Noto Sans SC:
|
||||
# pdfmetrics.registerFont(TTFont('Noto Sans SC', '/usr/share/fonts/truetype/chinese/NotoSansSC-Regular.ttf'))
|
||||
# pdfmetrics.registerFont(TTFont('Noto Sans SC Bold', '/usr/share/fonts/truetype/chinese/NotoSansSC-Bold.ttf'))
|
||||
# registerFontFamily('Noto Sans SC', normal='Noto Sans SC', bold='Noto Sans SC Bold')
|
||||
|
||||
# ── Styles ──
|
||||
# ACCENT must come from palette.generate (Step 2)
|
||||
# Run: python3 "$PDF_SKILL_DIR/scripts/pdf.py" palette.generate --title "Resume" --mode minimal
|
||||
ACCENT = colors.HexColor('<accent from palette>') # Replace with palette output
|
||||
|
||||
name_style = ParagraphStyle(
|
||||
'ResumeName', fontName='FreeSerif', fontSize=24,
|
||||
leading=28, alignment=TA_CENTER, spaceAfter=2
|
||||
)
|
||||
contact_style = ParagraphStyle(
|
||||
'ResumeContact', fontName='FreeSerif', fontSize=10,
|
||||
leading=14, alignment=TA_CENTER, textColor=TEXT_MUTED, # From palette --c-muted
|
||||
spaceAfter=8
|
||||
)
|
||||
section_title_style = ParagraphStyle(
|
||||
'ResumeSectionTitle', fontName='FreeSerif', fontSize=13,
|
||||
leading=16, spaceBefore=10, spaceAfter=4,
|
||||
textColor=ACCENT
|
||||
)
|
||||
job_title_style = ParagraphStyle(
|
||||
'ResumeJobTitle', fontName='FreeSerif', fontSize=11,
|
||||
leading=14, spaceAfter=1
|
||||
)
|
||||
job_meta_style = ParagraphStyle(
|
||||
'ResumeJobMeta', fontName='FreeSerif', fontSize=10,
|
||||
leading=13, textColor=TEXT_MUTED, spaceAfter=4 # From palette --c-muted
|
||||
)
|
||||
bullet_style = ParagraphStyle(
|
||||
'ResumeBullet', fontName='FreeSerif', fontSize=10,
|
||||
leading=14, leftIndent=14, bulletIndent=0,
|
||||
spaceBefore=1, spaceAfter=1
|
||||
)
|
||||
body_style = ParagraphStyle(
|
||||
'ResumeBody', fontName='FreeSerif', fontSize=10,
|
||||
leading=14, spaceAfter=2
|
||||
)
|
||||
|
||||
# ── Helpers ──
|
||||
def section_header(title):
|
||||
"""Section title + thin rule separator."""
|
||||
return [
|
||||
Paragraph(f'<b>{title}</b>', section_title_style),
|
||||
HRFlowable(width='100%', thickness=0.8, color=ACCENT,
|
||||
spaceBefore=0, spaceAfter=6),
|
||||
]
|
||||
|
||||
def experience_entry(title, company, dates, location, bullets):
|
||||
"""One work experience block."""
|
||||
elements = [
|
||||
Paragraph(f'<b>{title}</b>', job_title_style),
|
||||
Paragraph(f'{company} | {dates} | {location}', job_meta_style),
|
||||
]
|
||||
for b in bullets:
|
||||
elements.append(Paragraph(f'• {b}', bullet_style))
|
||||
elements.append(Spacer(1, 4))
|
||||
return elements
|
||||
|
||||
def education_entry(degree, school, dates, details=None):
|
||||
"""One education block."""
|
||||
elements = [
|
||||
Paragraph(f'<b>{degree}</b>', job_title_style),
|
||||
Paragraph(f'{school} | {dates}', job_meta_style),
|
||||
]
|
||||
if details:
|
||||
elements.append(Paragraph(details, body_style))
|
||||
elements.append(Spacer(1, 4))
|
||||
return elements
|
||||
|
||||
def skills_row(categories):
|
||||
"""
|
||||
Skills as compact label: value pairs.
|
||||
categories = [('Programming', 'Python, Java, C++'), ('Tools', 'Git, Docker')]
|
||||
"""
|
||||
elements = []
|
||||
for cat, vals in categories:
|
||||
elements.append(Paragraph(f'<b>{cat}:</b> {vals}', body_style))
|
||||
return elements
|
||||
|
||||
# ── Build Document ──
|
||||
doc = SimpleDocTemplate(
|
||||
'resume.pdf', pagesize=A4,
|
||||
leftMargin=1.5*cm, rightMargin=1.5*cm,
|
||||
topMargin=1.5*cm, bottomMargin=1.5*cm,
|
||||
title='Resume - Your Name',
|
||||
author='Z.ai', creator='Z.ai'
|
||||
)
|
||||
|
||||
story = []
|
||||
|
||||
# Header
|
||||
story.append(Paragraph('<b>YOUR NAME</b>', name_style))
|
||||
story.append(Paragraph(
|
||||
'email@example.com | +86 138-0000-0000 | Shanghai, China | github.com/yourname',
|
||||
contact_style
|
||||
))
|
||||
|
||||
# Summary
|
||||
story.extend(section_header('PROFESSIONAL SUMMARY'))
|
||||
story.append(Paragraph(
|
||||
'Results-driven software engineer with 5+ years of experience in backend systems, '
|
||||
'distributed computing, and cloud-native architectures. Led a team of 8 engineers '
|
||||
'delivering a real-time data pipeline processing 2M+ events/sec.',
|
||||
body_style
|
||||
))
|
||||
|
||||
# Experience
|
||||
story.extend(section_header('WORK EXPERIENCE'))
|
||||
story.extend(experience_entry(
|
||||
'Senior Software Engineer', 'Tech Company Inc.', 'Jan 2022 - Present', 'Shanghai',
|
||||
[
|
||||
'Designed and deployed a microservices architecture serving 10M daily active users',
|
||||
'Reduced API latency by 40% through query optimisation and caching strategies',
|
||||
'Mentored 3 junior engineers; established code review standards adopted team-wide',
|
||||
]
|
||||
))
|
||||
story.extend(experience_entry(
|
||||
'Software Engineer', 'Startup Co.', 'Jul 2019 - Dec 2021', 'Beijing',
|
||||
[
|
||||
'Built real-time recommendation engine using collaborative filtering (CTR +25%)',
|
||||
'Implemented CI/CD pipeline reducing deployment time from 2 hours to 15 minutes',
|
||||
]
|
||||
))
|
||||
|
||||
# Education
|
||||
story.extend(section_header('EDUCATION'))
|
||||
story.extend(education_entry(
|
||||
'M.Sc. Computer Science', 'Tsinghua University', '2017 - 2019',
|
||||
'GPA: 3.8/4.0 | Thesis: Distributed Graph Processing on Heterogeneous Clusters'
|
||||
))
|
||||
story.extend(education_entry(
|
||||
'B.Eng. Software Engineering', 'Zhejiang University', '2013 - 2017'
|
||||
))
|
||||
|
||||
# Skills
|
||||
story.extend(section_header('SKILLS'))
|
||||
story.extend(skills_row([
|
||||
('Languages', 'Python, Java, Go, SQL, TypeScript'),
|
||||
('Frameworks', 'Spring Boot, FastAPI, React, Kubernetes, Kafka'),
|
||||
('Tools', 'Git, Docker, Terraform, AWS (EC2/S3/Lambda), PostgreSQL, Redis'),
|
||||
]))
|
||||
|
||||
doc.build(story)
|
||||
```
|
||||
|
||||
### Resume Checklist
|
||||
- [ ] **1 page** (unless user says otherwise)
|
||||
- [ ] **No cover page, no TOC**
|
||||
- [ ] Tight margins (1.5cm all sides)
|
||||
- [ ] Name prominent at top (22-26pt)
|
||||
- [ ] Contact info single line, centered
|
||||
- [ ] Section headers with consistent separator style
|
||||
- [ ] Bullets concise - start with action verbs
|
||||
- [ ] Quantified achievements (%, $, count)
|
||||
- [ ] No photos, no icons, no colour blocks (ATS-safe)
|
||||
- [ ] Font: only registered fonts (FreeSerif / Noto Sans SC)
|
||||
- [ ] **⚠️ Minimum font size 12px (9pt)** - no text smaller than this anywhere
|
||||
- [ ] **Line breaks are language-aware** - no mid-word English breaks, no CJK punctuation orphans, no date range splits
|
||||
- [ ] **Page fill ≥85%** - no large blank area at bottom. If sparse, increase spacing/leading/font size adaptively
|
||||
|
||||
Reference in new issue
Block a user