Initial commit
This commit is contained in:
1058
skills/pdf/briefs/academic.md
Executable file
1058
skills/pdf/briefs/academic.md
Executable file
File diff suppressed because it is too large
Load Diff
770
skills/pdf/briefs/creative.md
Executable file
770
skills/pdf/briefs/creative.md
Executable file
@@ -0,0 +1,770 @@
|
||||
# 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.
|
||||
|
||||
**→ Overflow prevention**: See `typesetting/overflow.md` for the Playwright/HTML-specific 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
|
||||
|
||||
**→ Full spec: `typesetting/charts.md`** - read it 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` 7-template system** - pick Layout 1-7 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 (7 Layout System)
|
||||
|
||||
Cover pages (`archetype: "cover_hero"`) are the first impression. They must be ruthlessly sparse and spatially sophisticated.
|
||||
|
||||
**→ Full spec: `typesetting/cover.md`** - read it 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 | 04 Museum, 01 HUD, 02 Corporate | 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) | 05 Diagonal, 06 Swiss Grid, 03 Monolith | B (工程十字轴) | 30° (amber) |
|
||||
| **Authority** | minimal | split_complementary | noise | 03 Monolith, 07 Sidebar, 02 Corporate | A or B | 280° (muted violet) |
|
||||
| **Warmth** | pastel / light | analogous | flow (soft) | 04 Museum, 05 Diagonal | 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 (<28>) 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.
|
||||
702
skills/pdf/briefs/poster.md
Executable file
702
skills/pdf/briefs/poster.md
Executable file
@@ -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.md`.
|
||||
> These rules take priority over `creative.md` generic rules; in case of conflict, this file prevails.
|
||||
>
|
||||
> **Positioning**: This is a scene-layer patch on `creative.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 | PingFang HK | System | 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)
|
||||
```
|
||||
284
skills/pdf/briefs/process-advanced.md
Executable file
284
skills/pdf/briefs/process-advanced.md
Executable file
@@ -0,0 +1,284 @@
|
||||
# Process Brief: Advanced Reference
|
||||
|
||||
Edge-case tools and techniques. **Load only when the main `process.md` doesn't cover the task.**
|
||||
|
||||
```
|
||||
Edge case
|
||||
├─ No text extracted from PDF → §OCR Fallback
|
||||
├─ PDF is encrypted/locked → §Encrypted PDFs
|
||||
├─ PDF is corrupted/damaged → §Corrupted PDFs
|
||||
├─ Need precise text coordinates → §pdfplumber Advanced
|
||||
├─ Need fast rendering → §pypdfium2
|
||||
├─ Advanced page extraction → §poppler-utils Advanced
|
||||
├─ Complex page ranges / merge → §qpdf Page Manipulation
|
||||
├─ Optimize file size → §qpdf Optimization
|
||||
├─ Batch process many PDFs → §Batch Processing
|
||||
└─ Memory issues with large PDF → §Performance Optimization
|
||||
```
|
||||
|
||||
For basic operations (extract, merge, split, fill forms, convert), go back to `process.md`.
|
||||
|
||||
---
|
||||
|
||||
## §pypdfium2 (Apache/BSD License)
|
||||
|
||||
A Python binding for PDFium (Chromium's PDF library). Excellent for fast rendering and text extraction — serves as a PyMuPDF replacement.
|
||||
|
||||
#### Render PDF to Images
|
||||
```python
|
||||
import pypdfium2 as pdfium
|
||||
|
||||
pdf = pdfium.PdfDocument("document.pdf")
|
||||
|
||||
# Render single page
|
||||
page = pdf[0]
|
||||
bitmap = page.render(scale=2.0, rotation=0)
|
||||
img = bitmap.to_pil()
|
||||
img.save("page_1.png", "PNG")
|
||||
|
||||
# Batch render all pages
|
||||
for i, page in enumerate(pdf):
|
||||
bitmap = page.render(scale=1.5)
|
||||
img = bitmap.to_pil()
|
||||
img.save(f"page_{i+1}.jpg", "JPEG", quality=90)
|
||||
```
|
||||
|
||||
#### Extract Text with pypdfium2
|
||||
```python
|
||||
import pypdfium2 as pdfium
|
||||
|
||||
pdf = pdfium.PdfDocument("document.pdf")
|
||||
for i, page in enumerate(pdf):
|
||||
text = page.get_text()
|
||||
print(f"Page {i+1}: {len(text)} chars")
|
||||
```
|
||||
|
||||
## §pdfplumber Advanced Features
|
||||
|
||||
#### Extract Text with Precise Coordinates
|
||||
```python
|
||||
import pdfplumber
|
||||
|
||||
with pdfplumber.open("document.pdf") as pdf:
|
||||
page = pdf.pages[0]
|
||||
|
||||
# All characters with coordinates
|
||||
for char in page.chars[:10]:
|
||||
print(f"'{char['text']}' at x:{char['x0']:.1f} y:{char['y0']:.1f}")
|
||||
|
||||
# Extract text within a specific bounding box (left, top, right, bottom)
|
||||
bbox_text = page.within_bbox((100, 100, 400, 200)).extract_text()
|
||||
```
|
||||
|
||||
#### Advanced Table Extraction with Custom Settings
|
||||
```python
|
||||
import pdfplumber
|
||||
|
||||
with pdfplumber.open("complex_table.pdf") as pdf:
|
||||
page = pdf.pages[0]
|
||||
|
||||
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")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §poppler-utils Advanced Features
|
||||
|
||||
### Extract Text with Bounding Box Coordinates
|
||||
```bash
|
||||
pdftotext -bbox-layout document.pdf output.xml
|
||||
```
|
||||
|
||||
### Advanced Image Conversion
|
||||
```bash
|
||||
# High-resolution PNG
|
||||
pdftoppm -png -r 300 document.pdf output_prefix
|
||||
|
||||
# Specific page range
|
||||
pdftoppm -png -r 600 -f 1 -l 3 document.pdf high_res_pages
|
||||
|
||||
# JPEG with quality setting
|
||||
pdftoppm -jpeg -jpegopt quality=85 -r 200 document.pdf jpeg_output
|
||||
```
|
||||
|
||||
### Extract Embedded Images
|
||||
```bash
|
||||
pdfimages -all document.pdf images/img
|
||||
pdfimages -list document.pdf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §qpdf Advanced Features
|
||||
|
||||
### Complex Page Manipulation
|
||||
```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
|
||||
```
|
||||
|
||||
### PDF Optimization and Repair
|
||||
```bash
|
||||
qpdf --linearize input.pdf optimized.pdf
|
||||
qpdf --optimize-level=all input.pdf compressed.pdf
|
||||
qpdf --check input.pdf
|
||||
qpdf --fix-qdf damaged.pdf repaired.pdf
|
||||
```
|
||||
|
||||
### Encryption and Decryption
|
||||
```bash
|
||||
qpdf --encrypt user_pass owner_pass 256 --print=none --modify=none -- input.pdf encrypted.pdf
|
||||
qpdf --show-encryption encrypted.pdf
|
||||
qpdf --password=secret123 --decrypt encrypted.pdf decrypted.pdf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §Encrypted PDFs
|
||||
|
||||
```python
|
||||
from pypdf import PdfReader
|
||||
|
||||
try:
|
||||
reader = PdfReader("encrypted.pdf")
|
||||
if reader.is_encrypted:
|
||||
reader.decrypt("password")
|
||||
except Exception as e:
|
||||
print(f"Failed to decrypt: {e}")
|
||||
```
|
||||
|
||||
Or via qpdf:
|
||||
```bash
|
||||
qpdf --password=secret123 --decrypt encrypted.pdf decrypted.pdf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §Corrupted PDFs
|
||||
|
||||
```bash
|
||||
qpdf --check corrupted.pdf
|
||||
qpdf --replace-input corrupted.pdf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §OCR Fallback for 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)
|
||||
text = ""
|
||||
for i, image in enumerate(images):
|
||||
text += pytesseract.image_to_string(image)
|
||||
return text
|
||||
```
|
||||
|
||||
Prerequisites: `pip install pdf2image pytesseract` + install Tesseract OCR and poppler.
|
||||
|
||||
---
|
||||
|
||||
## §Batch Processing with Error Handling
|
||||
|
||||
```python
|
||||
import os, glob
|
||||
from pypdf import PdfReader, PdfWriter
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def batch_process_pdfs(input_dir, operation='merge'):
|
||||
pdf_files = glob.glob(os.path.join(input_dir, "*.pdf"))
|
||||
|
||||
if operation == 'merge':
|
||||
writer = PdfWriter()
|
||||
for pdf_file in pdf_files:
|
||||
try:
|
||||
reader = PdfReader(pdf_file)
|
||||
for page in reader.pages:
|
||||
writer.add_page(page)
|
||||
logger.info(f"Processed: {pdf_file}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed: {pdf_file}: {e}")
|
||||
continue
|
||||
with open("batch_merged.pdf", "wb") as output:
|
||||
writer.write(output)
|
||||
|
||||
elif operation == 'extract_text':
|
||||
for pdf_file in pdf_files:
|
||||
try:
|
||||
reader = PdfReader(pdf_file)
|
||||
text = "".join(page.extract_text() for page in reader.pages)
|
||||
output_file = pdf_file.replace('.pdf', '.txt')
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(text)
|
||||
logger.info(f"Extracted: {pdf_file}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed: {pdf_file}: {e}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §Performance Optimization
|
||||
|
||||
### Text Extraction
|
||||
- `pdftotext -bbox-layout` is fastest for plain text
|
||||
- Use pdfplumber for structured data and tables
|
||||
- Avoid `pypdf.extract_text()` for very large documents
|
||||
|
||||
### Image Extraction
|
||||
- `pdfimages` is much faster than rendering entire pages
|
||||
- Use low resolution for previews, high resolution for final output
|
||||
|
||||
### Memory Management for Large PDFs
|
||||
```python
|
||||
def process_large_pdf(pdf_path, chunk_size=10):
|
||||
reader = PdfReader(pdf_path)
|
||||
total_pages = len(reader.pages)
|
||||
|
||||
for start_idx in range(0, total_pages, chunk_size):
|
||||
end_idx = min(start_idx + chunk_size, total_pages)
|
||||
writer = PdfWriter()
|
||||
for i in range(start_idx, end_idx):
|
||||
writer.add_page(reader.pages[i])
|
||||
with open(f"chunk_{start_idx//chunk_size}.pdf", "wb") as output:
|
||||
writer.write(output)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Extended 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 |
|
||||
319
skills/pdf/briefs/process.md
Executable file
319
skills/pdf/briefs/process.md
Executable file
@@ -0,0 +1,319 @@
|
||||
# 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
|
||||
├─ "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
|
||||
└─ Edge cases (OCR, encrypt, batch) → load briefs/process-advanced.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Check
|
||||
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" env.check
|
||||
```
|
||||
|
||||
Reports availability but does **not** auto-install. Required: Python 3, pikepdf, pdfplumber.
|
||||
|
||||
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
|
||||
|
||||
```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 with page/rows/cols/data
|
||||
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/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §Pages
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §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`.
|
||||
|
||||
`meta.brand` adds standard branding metadata (producer, creator) in one step.
|
||||
|
||||
---
|
||||
|
||||
## §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
|
||||
# Inspect fields
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.info input.pdf
|
||||
|
||||
# Fill (auto-maps "true"/"false" for checkboxes)
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.fill input.pdf -o filled.pdf \
|
||||
-d '{"name": "John", "agree": "true", "country": "US"}'
|
||||
```
|
||||
|
||||
**Value rules:**
|
||||
|
||||
| Type | Value | Example |
|
||||
|------|-------|---------|
|
||||
| text | Free string | `"name": "Jane Doe"` |
|
||||
| checkbox | `"true"` / `"false"` (auto-converts to PDF states) | `"agree": "true"` |
|
||||
| radio | One of `radio_options[].value` | `"gender": "/Choice1"` |
|
||||
| dropdown | One of `choice_options[].value` | `"country": "US"` |
|
||||
|
||||
For complex forms, use `form.detail` and `form.render` for deeper inspection:
|
||||
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.detail input.pdf -o fields.json # full field info (types, options, defaults)
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.render input.pdf -o ./pages/ # render pages as PNG for 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** (required):
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.render input.pdf -o ./pages/
|
||||
```
|
||||
|
||||
**Step 2 — Create `fields.json`** with annotation regions.
|
||||
|
||||
To determine bbox coordinates: open the rendered PNG in an image viewer or use Python (`from PIL import Image; img = Image.open('page.png'); print(img.size)`) to get pixel dimensions. Then estimate [left, top, right, bottom] in pixels for each field by inspecting the image. The `dims` field must match the 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"}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Schema: `pg` = 1-based page, `dims` = [w,h] in pixels, `label.bbox` / `target.bbox` = [left, top, right, bottom], `ink` = {value, size?, color?, font?}. Label and target boxes must NOT intersect.
|
||||
|
||||
**Step 3 — Validate bounding boxes** (required):
|
||||
```bash
|
||||
# Auto-check for intersections
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.check-bbox fields.json
|
||||
|
||||
# Visual validation (red=target, blue=label)
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.validate 1 fields.json page1.png validation.png
|
||||
```
|
||||
|
||||
Fix any issues, regenerate, re-check. Red rectangles must only cover input areas.
|
||||
|
||||
**Step 4 — Fill via annotations**:
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.annotate input.pdf fields.json -o filled.pdf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §Reformat
|
||||
|
||||
Take an existing document and rebuild it with a new visual design. Content is preserved; layout, typography, and visual treatment are rebuilt from scratch.
|
||||
|
||||
```
|
||||
1. EXTRACT → Extract content from source (extract.text / extract.table / read directly)
|
||||
2. STRUCTURE → Organize into sections (headings, body, tables, lists)
|
||||
3. DELEGATE → Route to appropriate brief:
|
||||
Structured → briefs/report.md (ReportLab)
|
||||
Visual → briefs/creative.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:
|
||||
- python3 "$PDF_SKILL_DIR/scripts/pdf.py" meta.get template.pdf (page size)
|
||||
- python3 "$PDF_SKILL_DIR/scripts/pdf.py" extract.image template.pdf (color samples)
|
||||
- python3 "$PDF_SKILL_DIR/scripts/pdf.py" extract.text template.pdf (text structure)
|
||||
- pdftoppm -png -r 150 template.pdf preview (visual reference)
|
||||
2. DOCUMENT → Record: page size, margins, colors, fonts, layout grid,
|
||||
header/footer pattern, decorative elements
|
||||
3. DELEGATE → Route to brief WITH design constraints (not brief 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 — exact replication from PDF is impractical
|
||||
- Prefer original source files (.docx/.html/.tex) over PDF when available
|
||||
- Declare font substitutions upfront; don't silently fall back
|
||||
- Template provides design direction, not content — never leak placeholder text
|
||||
|
||||
---
|
||||
|
||||
## §Convert
|
||||
|
||||
### Office → PDF (LibreOffice)
|
||||
|
||||
**Simple conversion** (no TOC needed):
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" convert.office input.docx -o output.pdf
|
||||
```
|
||||
|
||||
**When to use the 5-step DOCX Pipeline instead**: If the DOCX has (or should have) a Table of Contents, always use §DOCX Pipeline below. Signs: the document has 3+ headings, or the user mentions "table of contents" / "TOC", or the document already contains a TOC section. When in doubt, run `python3 "$PDF_SKILL_DIR/scripts/toc_validate.py" fix-docx input.docx -o fixed.docx` — if it returns `no_toc_needed`, a simple conversion is fine.
|
||||
|
||||
Or directly:
|
||||
```bash
|
||||
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; close existing LibreOffice windows or use `--env:UserInstallation=file:///tmp/libreoffice_tmp`
|
||||
- Missing Chinese fonts → squares. Ensure SimHei/SimSun are installed.
|
||||
- Large files (>50MB) may take 1-2 min; set reasonable timeout
|
||||
- soffice HTML→PDF is inferior to Playwright for complex CSS
|
||||
|
||||
**Priority**: Always prefer soffice for Office→PDF (preserves themes, layouts, master slides). Only fall back to python-pptx/python-docx + HTML + Playwright if soffice is unavailable — fidelity will be lower.
|
||||
|
||||
### Fallback: Spreadsheet → PDF without LibreOffice
|
||||
|
||||
Use openpyxl + HTML + Playwright. Let data shape drive layout:
|
||||
|
||||
| Factor | Decision |
|
||||
|--------|----------|
|
||||
| Columns ≤ 6 | Portrait |
|
||||
| Columns > 6 | Landscape |
|
||||
| Font size | Scale inversely with column count |
|
||||
| Styling | Follow user requirements or source file style; if unspecified, use defaults from `typesetting/palette.md` |
|
||||
|
||||
### §DOCX Pipeline (5-Step with TOC)
|
||||
|
||||
For DOCX files that need TOC generation/correction. Required because LibreOffice `--headless` does not recalculate PAGEREF fields.
|
||||
|
||||
```
|
||||
Step 1: soffice → Convert original DOCX to PDF (pass1)
|
||||
Step 2: pages.clean → Remove blank pages from pass1
|
||||
Step 3: fix-docx → Add/fix TOC with HYPERLINK + PAGEREF + bookmarks
|
||||
Step 4: fix-pages → Correct TOC page numbers using pass1 as reference
|
||||
Step 5: soffice → Convert final DOCX to PDF + pages.clean
|
||||
```
|
||||
|
||||
**Step 1 — Pass 1 Convert**:
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" convert.office input.docx -o pass1.pdf
|
||||
```
|
||||
|
||||
**Step 2 — Clean Blank Pages**:
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.clean pass1.pdf -o pass1_clean.pdf
|
||||
```
|
||||
If `blank_pages_removed == 0`, use pass1.pdf directly.
|
||||
|
||||
**Step 3 — Fix TOC**:
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/toc_validate.py" fix-docx input.docx -o fixed.docx
|
||||
```
|
||||
|
||||
Auto-detects and fixes: placeholder TOC, stale TOC (>50% drift), empty TOC, missing TOC (≥3 headings). Each entry gets `<w:hyperlink>` + `PAGEREF` + bookmarks for clickable PDF navigation.
|
||||
|
||||
Check output `action` field: `fixed` → use fixed.docx, `skipped` → use original, `no_toc_needed` → skip to Step 5 with pass1 PDF.
|
||||
|
||||
**Step 4 — Fix Page Numbers**:
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/toc_validate.py" fix-pages fixed.docx pass1_clean.pdf -o final.docx
|
||||
```
|
||||
|
||||
Corrects PAGEREF display text using actual page positions from pass1 + TOC page offset.
|
||||
|
||||
**Step 5 — Final Convert + Clean**:
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
### Post-Conversion Validation (Optional)
|
||||
|
||||
```bash
|
||||
python3 "$PDF_SKILL_DIR/scripts/toc_validate.py" check-conversion final.docx output_clean.pdf
|
||||
```
|
||||
|
||||
Issues caught: `CONV_TOC_LOST` (TOC disappeared), `CONV_HINT_LEAKED` (placeholder text in PDF), `CONV_HEADING_DRIFT` (heading count mismatch).
|
||||
|
||||
---
|
||||
|
||||
## Caveats
|
||||
|
||||
| Topic | Detail |
|
||||
|-------|--------|
|
||||
| Encrypted PDFs | Not supported. User must decrypt externally first. |
|
||||
| < 50 MB | Instant |
|
||||
| 50–200 MB | 1–2 minutes |
|
||||
| > 200 MB | Split first, or extend timeout |
|
||||
| Memory | ~2-3× input file size |
|
||||
| Merge failure | Partial output may remain; delete and retry |
|
||||
| Split failure | Some page files may exist; inspect output dir |
|
||||
| Form fill | Original never modified; always writes new file |
|
||||
|
||||
For edge cases (OCR, batch processing, poppler-utils, qpdf, performance tuning), load `briefs/process-advanced.md`.
|
||||
1659
skills/pdf/briefs/report.md
Executable file
1659
skills/pdf/briefs/report.md
Executable file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user