# Common Rules

Shared rules referenced by all scene files. Scene-specific overrides take precedence.

## Default Page Layout

A4 portrait. Unless the scene specifies otherwise, use:

| Property | Value | Twips |
|----------|-------|-------|
| Page width | 21.0 cm | 11906 |
| Page height | 29.7 cm | 16838 |
| Top margin | 2.54 cm | 1440 |
| Bottom margin | 2.54 cm | 1440 |
| Left margin | 3.0 cm | 1701 |
| Right margin | 2.5 cm | 1417 |

```js
page: {
  size: { width: 11906, height: 16838, orientation: PageOrientation.PORTRAIT },
  margin: { top: 1440, bottom: 1440, left: 1701, right: 1417 },
}
```

**Scene overrides:**
- **Official doc (GB/T 9704 red-header):** top 2098, bottom 1984, left 1588, right 1474
- **Exam:** top/bottom 1134 (2 cm), left/right 1134 (2 cm)

## Default Font Specifications

Two font profiles exist. Each scene declares which profile it uses.

### Profile A: Formal (report, academic, contract, official-doc, exam)

| Element | CN Font | EN Font | Size | Notes |
|---------|---------|---------|------|-------|
| H1 | SimHei | Times New Roman | 16 pt (size: 32) | Bold, centered |
| H2 | SimHei | Times New Roman | 15 pt (size: 30) | Bold |
| H3 | SimHei | Times New Roman | 14 pt (size: 28) | Bold |
| Body | SimSun | Times New Roman | 12 pt (size: 24) | |
| Caption | SimSun | Times New Roman | 10.5 pt (size: 21) | |

- Text color: always **pure black `"000000"`** (never dark-blue-grey)
- First-line indent: **480 twips** (2 chars at SimSun 12pt)
- Line spacing: **312** (1.3x).
- **Color routing for non-report documents**: When the document is a short-form text (essay, evaluation, letter, speech, application, reflection, etc.) rather than a structured report/whitepaper/proposal/consulting deliverable, heading color MUST use pure black `"000000"` instead of `palette.primary`. Colored headings are reserved for documents that need brand/professional identity (reports with covers, whitepapers, proposals, consulting deliverables).

### Profile B: Visual (resume, copywriting)

| Element | CN Font | EN Font | Size |
|---------|---------|---------|------|
| Name/Title | Microsoft YaHei | Calibri | Varies |
| Body | Microsoft YaHei | Calibri | 10–11 pt |
| Caption | Microsoft YaHei | Calibri | 9 pt |

- First-line indent: **420 twips** (2 chars at YaHei)
- Color: per design-system palette

### Official-Doc Font Override (GB/T 9704)

When `needsRedHeader() = true`:

| Element | Font | Size |
|---------|------|------|
| Red header org name | STXiaoBiaoSong (or SimSun bold) | 26 pt (size: 52) |
| Title | STXiaoBiaoSong (or SimHei) | 22 pt (size: 44) |
| Body | FangSong | 16 pt (size: 32) |
| Section heading | FangSong_GB2312 bold (or HeiTi) | 16 pt (size: 32) |

- Line spacing: **560** (28 pt fixed)
- First-line indent: **640 twips** (2 chars at FangSong 16pt)

## Chinese Font Size Reference

| Name | Points | Half-points (size:) |
|------|--------|---------------------|
| Chu Hao (initial) | 42 | 84 |
| Xiao Chu | 36 | 72 |
| Yi Hao (1st) | 26 | 52 |
| Xiao Yi | 24 | 48 |
| Er Hao (2nd) | 22 | 44 |
| Xiao Er | 18 | 36 |
| San Hao (3rd) | 16 | 32 |
| Xiao San | 15 | 30 |
| Si Hao (4th) | 14 | 28 |
| Xiao Si | 12 | 24 |
| Wu Hao (5th) | 10.5 | 21 |
| Xiao Wu | 9 | 18 |
| Liu Hao (6th) | 7.5 | 15 |

## Placeholder Convention

When required information is missing, use standardized placeholders so users can Find & Replace in Word.

**Format:** Always use full-width brackets `【 】`.

| Type | Format | Example |
|------|--------|---------|
| General field | `【field name】` | Name: 【company name】 |
| Monetary amount | `【RMB in words: yuan (lowercase: ¥)】` | Amount: 【RMB in words】 |
| Date field | `【____/____/____】` | Signing date: 【____/____/____】 |
| Long text | `【Please fill in: ______】` | Delivery criteria: 【Please fill in: ______】 |
| Attachment ref | `【See Appendix 1: ______】` | |

**Rules:**
1. Placeholder format must be consistent throughout the entire document
2. Each placeholder must specify exactly what is needed (never use vague "TBD" or "to be completed")
3. Never hard-code unconfirmed critical facts; use a placeholder instead
4. Never use sloppy expressions like "to be refined", "omitted", "user fills in later"

## Title Orphan Prevention (All Scenes)

Body headings (H1/H2/H3) and cover titles must avoid leaving 1–2 characters alone on the last line. This rule applies to ALL document types.

**For cover titles:** Always use `calcTitleLayout()` + `splitTitleLines()` from `design-system.md` — these handle orphan prevention automatically (merges ≤2-char last lines into the previous line).

**For body headings (H1/H2/H3):** When a heading text is long enough to wrap, apply the same `splitTitleLines()` logic. If the heading would cause a single-character orphan in Word's auto-wrapping, manually split into multiple `TextRun` elements with a `Break` (soft line break) at a semantic boundary.

```js
const { Break } = require("docx");

// Check if heading needs manual line break to prevent orphan
function buildHeadingRuns(text, maxCharsPerLine, runProps) {
  // If text fits in one line, no action needed
  if (text.length <= maxCharsPerLine) {
    return [new TextRun({ text, ...runProps })];
  }
  // Use splitTitleLines to find semantic break points
  const lines = splitTitleLines(text, maxCharsPerLine);
  const runs = [];
  for (let i = 0; i < lines.length; i++) {
    if (i > 0) runs.push(new TextRun({ break: 1, ...runProps, text: "" })); // soft line break
    runs.push(new TextRun({ text: lines[i], ...runProps }));
  }
  return runs;
}
```

**Estimation for maxCharsPerLine:** For centered headings, estimate available width = page width - left margin - right margin. For SimHei at a given pt size, each CJK char ≈ pt × 20 twips wide. Divide available width by char width to get `maxCharsPerLine`.

---

## Undefined / Null Value Prevention (Mandatory)

Generated code MUST guard against outputting literal `undefined`, `null`, `NaN`, or empty strings for any visible text field. This is a **hard requirement** — these are never acceptable in a delivered document.

```js
// ✅ MANDATORY: Safe text helper — use for ALL user-facing text values
function safeText(value, placeholder) {
  if (value === undefined || value === null || value === "" || String(value) === "NaN" || String(value) === "undefined") {
    return placeholder || "【Please fill in】";
  }
  return String(value);
}

// Usage:
new TextRun({ text: safeText(config.contact, "【Contact person】") })
new TextRun({ text: safeText(row.phone, "【Phone number】") })
```

**Rules:**
1. Every `TextRun` displaying user-provided or config-derived data MUST use `safeText()` or equivalent guard
2. If a field is optional and not provided, use `【Please fill in: field_name】` placeholder (full-width brackets)
3. Table cells with missing data: show `【Please fill in】`, never leave as empty string or undefined
4. This applies to ALL scenes — contracts, reports, academic, exams, etc.

---

## WPS / Office Word Compatibility (Mandatory)

Generated .docx files must render consistently in both Microsoft Office Word and WPS Office. The following OOXML features have known compatibility issues — avoid or use carefully.

### Features to AVOID (high incompatibility risk)

| Feature | Issue | Alternative |
|---------|-------|------------|
| **Text-character decorative lines** (e.g., `───`, `━━━`, `═══`, `——————`) | Character-drawn lines depend on font metrics and rendering engine — they appear different widths/lengths in MS Office vs WPS, often truncated or misaligned. They cannot span a controlled width. | **Always use paragraph borders** (`border.top`, `border.bottom`) for horizontal decorative lines. Paragraph borders render consistently across engines and respect indent for precise width control. See recipe R2 for correct implementation. |
| **Default table borders on cover wrapper tables** (forgetting `allNoBorders`) | docx-js default table borders are `single/auto/sz=4`. On the 16838-high cover wrapper, these borders add ~8 twips of extra height per edge. MS Office includes border thickness in height calculation, causing content to overflow by a few twips → **blank page 2**. WPS is more lenient and may absorb the overflow. | **Every cover wrapper table MUST explicitly set `borders: allNoBorders`** (all 6 border positions = NONE). Never rely on defaults. Define the `allNoBorders` constant and use it consistently. |
| `verticalAlign: "center"` or `"bottom"` in exact-height TableRow | WPS ignores vertical alignment in exact-height rows; content may clip or shift | Use `verticalAlign: "top"` + `spacing.before` to position content. Avoid `margins.top`/`margins.bottom` in exact-height cells — they reduce available height unpredictably across engines |
| `characterSpacing` (large values) | WPS renders differently from Word; letter spacing may collapse or expand | Keep `characterSpacing` ≤ 80; for cover English labels, test both renderers |
| `margins.top`/`margins.bottom` inside exact-height cells | MS Office and WPS calculate remaining height differently when cell margins are present | Use `spacing.before` on the first paragraph for vertical positioning; only use `margins.left`/`margins.right` |
| Complex nested Tables inside exact-height cells | WPS height calculation differs from Word; content may overflow or clip | Wrap everything in a single 16838 outer wrapper cell (R1 architecture). Nested tables inside are acceptable when the outer wrapper provides a safety net |
| Large font without explicit `spacing.line` | Paragraph inherits small line spacing from document default (e.g., 560tw for body); font taller than line height → top of characters clipped | Always set `spacing: { line: fontPt * 23, lineRule: "atLeast" }` on paragraphs with font size > body text |
| `ShadingType.SOLID` | WPS shows solid black instead of intended color | Always use `ShadingType.CLEAR` |
| OOXML raw XML for columns (`w:cols`) | WPS column rendering may differ | Use only when explicitly needed (A3 exam papers); test output |
| `titlePage: true` with complex headers/footers | WPS may not properly suppress first-page header/footer | Use separate sections instead of titlePage flag |
| Tab stops for alignment | WPS tab width may differ from Word | Use borderless Tables for alignment instead |

### Features that are SAFE (consistent rendering)

| Feature | Notes |
|---------|-------|
| Borderless Tables for layout | Both renderers handle well |
| `ShadingType.CLEAR` with fill color | Consistent |
| `rule: "exact"` on single-level TableRow | Works in both (avoid with nested Tables) |
| Paragraph borders (left, bottom, etc.) | Consistent |
| `spacing.before` / `spacing.after` | Consistent |
| Standard fonts (SimHei, SimSun, YaHei, TNR, Calibri) | Available on both platforms |
| `PageBreak` inside Paragraph | Consistent |
| Section breaks (`SectionType.NEXT_PAGE`) | Consistent |

### Mandatory Compatibility Checks (Post-Generation)

Add to quality self-check:
- [ ] No `ShadingType.SOLID` anywhere (search codebase)
- [ ] No `verticalAlign: "center"` or `"bottom"` in exact-height rows
- [ ] No tab-stop alignment for party info or data alignment (use Tables)
- [ ] Covers use the 16838 outer wrapper architecture (R1 pattern) with `spacing.before` for positioning; no `margins.top`/`margins.bottom` in exact-height cells
- [ ] **Cover section margin = `{ top: 0, bottom: 0, left: 0, right: 0 }`** — non-zero margins cause wrapper to shrink away from page edges
- [ ] **Cover wrapper row has `height: { value: 16838, rule: "exact" }`** — without this, content overflows or leaves whitespace
- [ ] **Cover is in a separate section from body content** — cover and body must not share a section
- [ ] **Cover wrapper table uses explicit `allNoBorders`** — never rely on default table borders (causes blank page 2 in MS Office)
- [ ] **No text-character decorative lines** (`───`, `━━━`, `═══`, `——————`) — use paragraph borders instead
- [ ] `characterSpacing` values ≤ 80 throughout
- [ ] TOC: follow `references/toc.md` checklist (heading style, TableOfContents element, PageBreak, post-processing script)
- [ ] All tables use `WidthType.PERCENTAGE` for column widths (WPS tblGrid bug; if DXA is unavoidable, set `columnWidths` explicitly)

```js
// ✅ Correct — percentage widths, WPS-safe
new Table({
  width: { size: 100, type: WidthType.PERCENTAGE },
  rows: [new TableRow({ children: [
    new TableCell({ width: { size: 30, type: WidthType.PERCENTAGE }, children: [...] }),
    new TableCell({ width: { size: 70, type: WidthType.PERCENTAGE }, children: [...] }),
  ]})],
});

// ❌ WRONG — DXA widths cause WPS tblGrid mismatch (all gridCol=100)
new TableCell({ width: { size: 3000, type: WidthType.DXA }, ... })
```

---

## Universal Prohibitions

These apply to ALL scenes. Scene files may add scene-specific prohibitions.

1. **No outlines-only** — always produce a complete, finished document
2. **No chat-style output** — the document must not read like a conversation or explanation
3. **No fake TOC / page numbers / headers** — use proper docx-js structures
4. **No excessive blank lines** to pad layout
5. **No dirty formatting** — no stray annotations, template fragments, broken hyperlinks, garbled markers
6. **No sloppy placeholders** — "TBD", "omitted", "略", "to be refined" are forbidden; use proper `【】` placeholders
7. **No fabricated data** — do not invent statistics, citations, legal references, or facts to appear professional
8. **No inconsistent heading/numbering** — one numbering system per document, no level-skipping
9. **No Markdown artifacts** — no `#`, `**`, `-` list markers, `>` blockquotes, and **no Markdown table syntax** (`| col1 | col2 |`, `|---|---|`) in the final docx. Any tabular data MUST be rendered as a proper docx `Table` object — never as plain-text pipe-delimited lines. This applies to ALL scenes including exam paper data tables, report statistics, and academic result tables.
10. **No bullet-list documents** — body text must be proper paragraphs, not endless bullet points

## Letter / Correspondence Format (Universal)

When generating any letter-style document (invitation letter, thank-you letter, cover letter, recommendation letter, English essay in letter format, etc.), the following layout rules apply regardless of scene:

1. **Complimentary close and sender name MUST be right-aligned** — e.g., "Yours sincerely,", "Best regards,", "Yours,", and the sender name below it must use `alignment: AlignmentType.RIGHT`
2. **Date** — if placed at the top of the letter, right-aligned; if at the bottom, right-aligned with the closing
3. **Salutation** ("Dear Mr. Smith," / "Dear Mike,") — left-aligned, followed by a blank line or `spacing.after`
4. **Body paragraphs** — left-aligned (English) or justified (CJK), with appropriate `spacing.after` between paragraphs

```js
// ✅ Correct — closing and sender right-aligned
new Paragraph({ alignment: AlignmentType.RIGHT, spacing: { before: 400 },
  children: [new TextRun({ text: "Yours sincerely,", size: 24 })] }),
new Paragraph({ alignment: AlignmentType.RIGHT,
  children: [new TextRun({ text: "Li Hua", size: 24 })] }),

// ❌ WRONG — closing left-aligned (default)
new Paragraph({
  children: [new TextRun({ text: "Yours sincerely," })] }),
```

## Quality Self-Check (Universal)

→ See **SKILL.md § Post-Generation — Two-Layer Verification** for the complete checklist.

Scene files add scene-specific checks on top of that universal checklist.

## Execution Priority

When rules conflict, follow this precedence (highest first):

1. **User-provided template or explicit instructions** — always override defaults
2. **Scene-specific rules** — override common rules and design-system defaults
3. **Common rules** (this file) — override design-system aesthetic defaults
4. **Design-system defaults** — baseline aesthetics

## Cover Recipes

See `references/design-system.md` for the 7 validated cover recipes (R1–R7) and 14 color palettes.

Cover recipe selection: `selectCoverRecipe(docType, industry, titleLength)` — defined in `references/design-system.md` (authoritative source).

---

## Cover Title Layout Rules (Mandatory)

These rules apply to ALL cover recipes (R1–R7). They prevent the most common cover quality issues: title overflow, content spilling to page 2, and mid-word line breaks.

### Rule 1: Always use `calcTitleLayout()`

Every cover MUST call `calcTitleLayout(title, availableWidth)` from `design-system.md` to determine:
- **Font size** (dynamically calculated, never hardcoded above 40pt)
- **Line breaks** (semantically split, never mid-word)

**Forbidden:** Passing the full title as a single long TextRun and letting Word auto-wrap. This causes uncontrolled line breaks at arbitrary character positions.

### Rule 2: No single-character orphan lines

If the last line of a title contains only 1–2 characters, merge it into the previous line. The `splitTitleLines()` function handles this automatically.

### Rule 3: No mid-word breaks for CJK text

Line breaks must occur at semantic boundaries: after particles (e.g., de/yu/he/ji/zhi), punctuation, connectors, spaces, or underscores. Never split a compound term (e.g., a 4-character term like a management specification must not be split into 3+1 characters).

For mixed Chinese+English titles (e.g., "基于Transformer架构的..."), use `estimateTextWidth()` instead of character count for line break calculation. Chinese characters are ~2× wider than English characters at the same font size.

### Rule 4: Maximum 3 title lines on cover

Cover titles must not exceed 3 lines. If the title is too long, reduce font size (down to minimum 24pt) before adding more lines. If it still exceeds 3 lines at 24pt, force 3 lines with longer line lengths.

### Rule 5: Always use `calcCoverSpacing()` for whitespace

Spacing values (`spacing.before`) in cover elements must be dynamically calculated, not hardcoded. Fixed values like `before: 4500` assume a specific title length and will cause overflow with longer titles.

### Rule 6: Cover height budget validation

Before generating, verify that total content height stays within 15638 twips (16838 page height minus 1200 twips safety margin — MS Office renders large fonts taller than calculated). Each recipe in `design-system.md` includes height budget annotations — verify during generation.

### Rule 7: R5 meta info table (academic covers)

Academic cover meta info must use a 2-column table with **percentage widths only** (NOT DXA — WPS breaks with DXA widths):
- **Table width:** adaptive 55–75% of page, calculated by `calcR5MetaLayout()` in `design-system.md`. Table is centered via `alignment: CENTER`.
- **Label column:** adaptive 25–45% of table width, **LEFT aligned**, plain text label + "：". NO full-width space padding, NO right-alignment, NO distributed alignment.
- **Value column:** remaining percentage, **LEFT aligned**, `bottom border single sz=4` = fixed-length underline (same length for all rows regardless of value text length).
- **Label column borders:** none (NO bottom border on label cells).
- ⚠️ Do NOT use DXA widths, full-width space padding (`\u3000`), spacer columns, or tab stops — these render inconsistently between MS Office and WPS.

### Rule 8: Large font paragraphs must set explicit line spacing

When a paragraph uses a font size larger than the document body text (e.g., cover titles at 36pt+), it **MUST** set explicit `spacing.line` to prevent clipping. Without it, the paragraph inherits the document/style default line spacing (often 560 twips for body text), which is smaller than the font height → the top of characters gets clipped.

**Formula:** `spacing.line = Math.ceil(fontPt * 23)` with `lineRule: "atLeast"`

**Example:** A 36pt title needs `spacing: { line: 828, lineRule: "atLeast" }`. Without this, the inherited `line=560` clips the top 160 twips of the text.

This applies to ALL large-font paragraphs (cover titles, chapter headings, decorative text), not just covers.

### Rule 9: Every TextRun on a colored background MUST set explicit `color`

⚠️ **CRITICAL:** When a TextRun is inside a cell/area with a dark or colored background (shading), it **MUST** explicitly set the `color` property. Omitting `color` defaults to black (`#000000`), which is invisible on dark backgrounds.

**Common mistake:** Subtitle or meta text on R1/R2/R4 dark cover blocks without `color` → appears as invisible black text on dark bg.

**Rule:** For any TextRun inside a shaded cell:
- Use `P.cover.titleColor` for title text
- Use `P.cover.subtitleColor` for subtitle text
- Use `P.cover.metaColor` for meta info text
- Use `P.cover.footerColor` for footer text
- **NEVER** rely on default color when background is not white

### Rule 10: Page number API nesting and 3-section numbering

⚠️ **CRITICAL:** Page number settings MUST be nested inside `page.pageNumbers`:

```js
// ❌ WRONG — docx-js ignores top-level pageNumberStart/pageNumberFormatType
properties: { pageNumberStart: 1, pageNumberFormatType: NumberFormat.DECIMAL }

// ✅ CORRECT
properties: { page: { pageNumbers: { start: 1, formatType: NumberFormat.DECIMAL } } }
```

**Standard page numbering (5-zone convention):**

All multi-section documents MUST follow this five-zone page numbering scheme unless the user explicitly requests otherwise.

| Zone | Section | pageNumbers | Footer instrText | Notes |
|------|---------|-------------|-----------------|-------|
| 1. Cover | Title page | None (no footer) | — | Always logical page 1, but number is **hidden** |
| 2. Front matter | Abstract, TOC, Preface | `{ start: 1, formatType: UPPER_ROMAN }` | `PAGE \* ROMAN \* MERGEFORMAT` | Separate Roman numeral sequence (i, ii, iii…) |
| 3. Body | Main content | `{ start: 1, formatType: DECIMAL }` | `PAGE \* arabic \* MERGEFORMAT` | **Resets to 1** |
| 4. Appendix | Appendices (A, B, C…) | Continues body (no reset) | Same as body | No section break needed unless different headers required |
| 5. References | Bibliography | Continues body (no reset) | Same as body | If body ends on p.42, references continue from p.43 |

**Key rules:**
0. **NEVER use "Page X of Y" denominator format.** Footer must show only the current page number (e.g., `1`, `2`, `iii`). Do NOT display total page count. No `Page 3 of 12`, no `3 / 12`, no `第3页/共12页`. Just the bare number. `PageNumber.TOTAL_PAGES` / `NUMPAGES` is **FORBIDDEN** in footers.
1. **Cover is always page 1 internally** but the page number is never displayed. Suppress footer in cover section.
2. **Front matter uses independent Roman numerals** starting at `i`. This sequence is separate from the body.
3. **Body resets to Arabic 1.** The first page of main content is always page `1`.
4. **Appendix and references continue the body sequence.** No reset between body → appendix → references.
5. **Documents without front matter** skip zone 2 (cover hidden, body starts at Arabic 1).
6. **Documents without cover** start body (or front matter) at page 1 directly.
7. **Short documents (≤3 pages):** simple Arabic 1, 2, 3 throughout, no cover/frontmatter distinction.
8. **Single-page documents** (certificates, letters): no page numbering at all.

**3-section docx-js implementation (for documents with TOC):**

At minimum, implement zones 1–3 as separate docx sections:

```js
// Section 1: Cover — no page number
properties: { page: { /* no pageNumbers */ } }
// No footer children, or empty footer

// Section 2: Front matter — Roman numerals
properties: { page: { pageNumbers: { start: 1, formatType: NumberFormat.UPPER_ROMAN } } }
// Footer: PAGE \* ROMAN \* MERGEFORMAT

// Section 3: Body — Arabic, reset to 1
properties: { page: { pageNumbers: { start: 1, formatType: NumberFormat.DECIMAL } } }
// Footer: PAGE \* arabic \* MERGEFORMAT

// Appendix and References: same section as body (continues numbering)
// Only create a new section if different header/footer content is needed
```

**Post-processing required** (WPS compatibility):
1. Remove empty `<w:pgNumType/>` from cover section XML
2. Patch footer instrText: replace bare `PAGE` with format-specific `PAGE \* ROMAN` or `PAGE \* arabic`

See `toc.md` § Page Number API for full details.
