# Scene: Exam Paper

## Overview

Exam papers are among the most critical document types in education. Unlike general documents, they require high precision in layout, print compatibility, and subject-specific formatting. This specification covers the complete workflow from page framework to subject-specific features.

→ Universal prohibitions — see `references/common-rules.md`
→ **Note:** Exam papers use their OWN font/layout specs (not Profile A defaults). All text is pure black/white/grey for photocopy clarity.

---

## 1. Page Setup & Framework

### Paper Specifications

| Type | Paper | Orientation | Use Case |
|------|-------|-------------|----------|
| Practice / Unit quiz | A4 | Portrait | Daily practice, homework, quizzes |
| Formal exam | A3 | Landscape + 2-column | Midterm / final / standardized (requires OOXML) |
| Answer sheet | A4 | Portrait | Standalone answer card |

### Margins

```js
// A4 portrait — no seal line
page: { size: { width: 11906, height: 16838 },
  margin: { top: 850, bottom: 850, left: 1200, right: 1200 } }

// A4 portrait — with seal line (left binding area reserved)
page: { size: { width: 11906, height: 16838 },
  margin: { top: 850, bottom: 850, left: 2200, right: 850 } }

// A3 landscape dual-column (requires OOXML)
// ⚠️ A3 dual-column may render slightly differently in WPS vs Word. Test in both before batch printing.
page: { size: { width: 23812, height: 16838, orientation: PageOrientation.LANDSCAPE },
  margin: { top: 850, bottom: 850, left: 2200, right: 850 } }
```

### Section Handling

Different parts should use section breaks (`SectionType.NEXT_PAGE`):
- **Header area (full-width):** Title, instructions, score table (no columns)
- **Content area:** Questions (may use columns)
- **Composition / answer sheet:** Independent section, independent format
- **Attachment pages:** Large maps/diagrams for geography/biology can be separate pages

```js
sections: [
  { properties: { /* Header section — no columns */ }, children: [...] },
  { properties: { type: SectionType.CONTINUOUS, column: { count: 2, space: 720 } }, children: [...] },
  { properties: { type: SectionType.NEXT_PAGE }, children: [...] }, // Composition
]
```

### Template-First Principle

⚠️ **Build framework first, fill content second.** Before writing questions, determine:
1. Paper size + margins
2. Whether seal line is needed
3. Whether columns are used
4. Question type structure and point allocation
5. Whether composition grid / answer sheet is needed

---

## 2. Seal Line & Student Information Area

### When to Use Seal Line

| Scenario | Seal Line | Student Info Position |
|----------|-----------|---------------------|
| Formal standardized exam | ✅ Required | Left vertical info column |
| Midterm / Final | ✅ Recommended | Left vertical info column |
| Unit quiz | ❌ Optional | Header horizontal info row |
| Daily practice | ❌ Skip | Header horizontal info row |

### Seal Line Implementation

#### Method 1: Header horizontal prompt (simple)
```js
headers: { default: new Header({ children: [
  new Paragraph({ alignment: AlignmentType.CENTER,
    children: [new TextRun({
      text: ".............. Seal ...... Line ...... Do ...... Not ...... Answer ...... Inside ..............",
      size: 16, color: "999999", font: "SimSun" })] })
] }) }
```

#### Method 2: Vertical text box (OOXML advanced)
```xml
<w:txbxContent>
  <w:p><w:pPr><w:jc w:val="center"/></w:pPr>
    <w:r><w:rPr><w:sz w:val="18"/><w:color w:val="999999"/></w:rPr>
      <w:t>Name:________  Class:________  ID:________</w:t></w:r>
  </w:p>
  <w:p><w:r><w:rPr><w:sz w:val="16"/><w:color w:val="CCCCCC"/></w:rPr>
      <w:t>- - - - - - - - - Seal Line - - - - - - - - -</w:t></w:r>
  </w:p>
</w:txbxContent>
```

### Student Info Row

```js
// Horizontal info row (when no seal line) — borderless 3-column table
new Table({
  alignment: AlignmentType.CENTER, columnWidths: [2800, 2800, 2800],
  rows: [new TableRow({ children: [
    cell("Name: ______________"),
    cell("Class: ______________", AlignmentType.CENTER),
    cell("ID: ______________", AlignmentType.RIGHT),
  ] })]
})
```

Fill lines should be moderate length (10–14 underscore chars). Label order: Name → Class → Student ID.

---

## 3. Paper Header & Title Area

### Structure

```
School name (16pt SimHei, centered)
Exam title (14pt SimHei, centered) — e.g., "2025–2026 Academic Year Second Semester Midterm"
Subject title (14pt SimHei, centered) — e.g., "Grade 7 Mathematics"
Student info row
Instructions (10pt SimSun, centered, grey)
Score table (as needed)
```

### Font Specifications

| Element | Font | Size | Style |
|---------|------|------|-------|
| School name | SimHei | 16pt (size:32) | Bold, centered |
| Exam title | SimHei | 14pt (size:28) | Bold, centered |
| Subject title | SimHei | 14pt (size:28) | Bold, centered |
| Instructions | SimSun | 10pt (size:20) | Grey 333333, centered |
| Student info | SimSun | 10.5pt (size:21) | Normal |

### Instructions Content

Should include: total score, exam duration, answer method, special requirements (e.g., calculator allowed).

### Score Table
- Header row: light grey background F0F0F0, centered
- Columns: Question type | Section names... | Total
- Rows: Points | Section points... | Total points
- Row: Score | blank... | blank
- Table centered, 80% page width

⚠️ **Header area should not be too full** — title + info + instructions + score table should not exceed 1/3 of the page.

---

## 4. Content Layout Rules

### Color Palette

```js
// Exam papers use only black/white/grey for clear photocopying
const C = {
  title: "000000", body: "000000", section: "333333",
  seal: "999999", answerLine: "CCCCCC", headerBg: "F0F0F0", gridLine: "DDDDDD",
};
```

### Column Usage

| Subject / Question Type | Recommendation |
|------------------------|----------------|
| Math multiple choice + fill-in | ✅ Suitable for columns |
| Physics multiple choice | ✅ Suitable for columns |
| Chinese reading / composition | ❌ Not suitable |
| English cloze / reading | ❌ Not suitable |
| History source-based | ❌ Not suitable |
| Geography map reading | ❌ Not suitable |

### Question Numbering

Entire paper uses consistent three-level numbering:
- **Major sections:** I, II, III, IV... (Chinese: 一、二、三、四…)
- **Questions:** 1. 2. 3. ... (Arabic + period)
- **Sub-questions:** (1) (2) (3) ... (parenthesized)

⚠️ **No extra symbols before question numbers** (no `•`, `▸`, `▪`, `-`, `*`). The number itself is the only marker. **Never use docx numbering/bullet list styles** for question numbers — must use plain TextRun manual numbering.

```js
// ✅ Correct — plain TextRun manual numbering
new Paragraph({ spacing: { before: 120, after: 60, line: 360 },
  children: [new TextRun({ text: `${i+1}. ${question}`, size: 21, font: { eastAsia: "SimSun" } })] })

// ❌ Wrong — numbering causes Word to add bullets
new Paragraph({ numbering: { reference: "xxx", level: 0 }, // ← Forbidden!
  children: [new TextRun({ text: question })] })
```

### Question Spacing

```js
sectionTitle: { before: 300, after: 150 }  // Major section headers
question: { before: 120, after: 80 }       // Between questions
subQuestion: { before: 60, after: 40 }     // Between sub-questions
```

### Page Break Control

⚠️ Key principles:
- **Question stem and answer area must not split** across pages
- **Source material and questions on same page**
- **Figures adjacent to their questions**
- **Avoid orphan lines** — question stem, options, answer area appear as a group

```js
new Paragraph({ keepNext: true, keepLines: true, children: [...] })
```

⚠️ **Answer question page break rule (mandatory):**

Complete combination (stem + figure + answer lines) must be considered as a unit. If remaining space cannot fit stem + figure + at least 3 answer lines, push entire question to next page.

Use `keepNext: true` to chain: stem → figure → first 3 answer lines.

---

## 5. Font & Paragraph Standards

### Underline Formatting for "Underlined Parts" (Mandatory)

When a question references "underlined part" (划线部分), the relevant text MUST use actual underline formatting (`underline: { type: UnderlineType.SINGLE }`). **Never** show "划线部分为 XXX" as plain text annotation — the underline must be visually rendered.

```js
// ✅ Correct — actual underline on the referenced text
new Paragraph({ children: [
  new TextRun({ text: "1. It is ", size: 21, font: { ascii: "Times New Roman" } }),
  new TextRun({ text: "a butterfly", size: 21, font: { ascii: "Times New Roman" },
    underline: { type: UnderlineType.SINGLE, color: "000000" } }),
  new TextRun({ text: ". (Ask about the underlined part)", size: 21, font: { ascii: "Times New Roman" } }),
]})

// ❌ Wrong — underlined part described as annotation text
new TextRun({ text: "1. It is a butterfly. (对划线部分提问) 注：划线部分为 a butterfly" })
```

### Font Hierarchy

| Element | Font | Size | Style |
|---------|------|------|-------|
| Section title | SimHei | 11pt (size:22) | Bold |
| Question content | SimSun | 10.5pt (size:21) | Normal |
| Points annotation | SimSun | 10pt (size:20) | In parentheses |
| Reading material | KaiTi/SimSun | 10.5pt (size:21) | KaiTi to differentiate |
| Notes/source | SimSun | 9pt (size:18) | Grey 666666 |
| Seal line | SimSun | 8pt (size:16) | Grey 999999 |
| Page number | SimSun | 9pt (size:18) | Centered |

### Line Spacing
```js
line: 360  // ~1.5x for readability
answerLine: 500  // Answer line spacing for writing room
```

### Paragraph Rules
- ⚠️ **Never use consecutive returns for whitespace** — use `spacing.before/after`
- Chinese questions use Chinese punctuation; English materials use English punctuation
- Mixed CN/EN: use Times New Roman or Calibri for English text

---

## 6. Multiple Choice Layout

### Core Rule

⚠️ **Options must NEVER be aligned with spaces!** Must use borderless tables.

### Option Layout — Borderless Table

```js
// Short options: 4 columns in 1 row
new Table({
  columnWidths: [2200, 2200, 2200, 2200],
  rows: [new TableRow({ children: ["A","B","C","D"].map((label, i) =>
    new TableCell({ borders: NBs, width: { size: 2200, type: WidthType.DXA },
      margins: { top: 0, bottom: 0, left: 60, right: 60 },
      children: [new Paragraph({ spacing: { before: 0, after: 0 },
        children: [new TextRun({ text: `${label}. ${options[i]}`, size: 21, font: "SimSun" })] })]
    })
  ) })]
})
// Medium options: 2 columns, 2 rows
// Long options: 1 column, 4 rows
```

### Option Length Detection
```js
function getOptionLayout(options) {
  const maxLen = Math.max(...options.map(o => o.length));
  if (maxLen <= 6) return "4col";
  if (maxLen <= 15) return "2col";
  return "1col";
}
```

---

## 7. Fill-in-the-Blank Layout

```js
// Blank line length matches expected answer:
// Short answer (number/word): 8 underscores
// Medium (phrase): 14 underscores
// Long (sentence): 20 underscores
new Paragraph({ spacing: { before: 140, after: 80, line: 400 },
  children: [new TextRun({ text: `${num}. Question text ________________.`, size: 21, font: "SimSun" })] })
```

⚠️ Fill-in lines must not break across lines — if line is too long, put the blank on the next line.

---

## 8. Short Answer / Problem-Solving Layout

### Question + Points
```js
new Paragraph({ spacing: { before: 200, after: 60, line: 360 }, keepNext: true,
  children: [new TextRun({ text: `${num}. (${points} pts) ${question}`, size: 21, font: "SimSun" })] })
```

### Answer Lines
```js
// Light grey answer lines (CCCCCC), NOT black
// ⚠️ Answer lines are ONLY for writing space within each question — never as dividers between questions
function answerLines(count) {
  return Array(count).fill(null).map(() =>
    new Paragraph({ spacing: { before: 0, after: 0, line: 500 },
      borders: { bottom: { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" } },
      children: [new TextRun({ text: " ", size: 21 })] })
  );
}
```

⚠️ **Separation between questions:**

Use **only spacing** (`spacing.before: 200`) for visual separation between questions. **Forbidden:**
- ❌ Grey horizontal lines (borders)
- ❌ Color block dividers (Table-simulated separators)
- ❌ Symbol dividers (e.g., `───────`)
- ❌ Any visual separator decoration

### Answer Space vs. Points

| Points | Suggested Lines | Description |
|--------|----------------|-------------|
| 2–4 | 3–4 lines | Simple calculation / short answer |
| 5–8 | 6–8 lines | Medium problem |
| 10–12 | 8–10 lines | Complex problem |
| 14–20 | 10–14 lines | Comprehensive / essay question |

---

## 9. Source-Based / Reading Question Layout

### Material vs. Question Separation

```js
// Material area — indented + KaiTi to differentiate
new Paragraph({ indent: { left: 420, right: 420 }, spacing: { before: 100, after: 100, line: 380 },
  children: [new TextRun({ text: materialText, size: 21, font: "KaiTi" })] })
// Source attribution
new Paragraph({ alignment: AlignmentType.RIGHT, indent: { right: 420 },
  children: [new TextRun({ text: "— from \"XXX\"", size: 18, color: "666666", font: "SimSun" })] })
```

### Key Principles
- Material title, source, body, and notes use different fonts
- Long materials: increase line spacing (line: 380–400)
- Material and corresponding questions on same page
- Sub-question numbers (1)(2)(3) clearly correspond to material
- **Data tables in materials MUST use proper docx `Table` objects** — never render tabular data as Markdown plain text (`| col | col |`). This includes statistics tables, climate data tables, comparison tables, and any structured data within question materials. Use bordered tables (see § 13 Table Usage Standards) with appropriate header row styling.

---

## 10. Composition / Writing Area

### Grid Count Calculation

⚠️ **Grid count must exceed required word count by 20–30%** (for title, paragraph indents, line breaks).

| Required Words | Min Grid Count | Recommended Layout |
|---------------|---------------|-------------------|
| 400 | 500 | 25 rows × 20 cols |
| 600 | 750 | 38 rows × 20 cols |
| 800 | 1000 | 50 rows × 20 cols |
| 1000 | 1250 | 63 rows × 20 cols |

```js
function calcGridSize(requiredWords, colsPerRow = 20) {
  const totalCells = Math.ceil(requiredWords * 1.25);
  const rows = Math.ceil(totalCells / colsPerRow);
  return { rows, colsPerRow, totalCells: rows * colsPerRow };
}
```

### Chinese Composition Grid

```js
function compositionGrid(rows, colsPerRow) {
  const cellSize = Math.floor(8800 / colsPerRow);
  return new Table({
    columnWidths: Array(colsPerRow).fill(cellSize),
    rows: Array(rows).fill(null).map(() =>
      new TableRow({
        height: { value: cellSize, rule: HeightRule.EXACT },
        children: Array(colsPerRow).fill(null).map(() =>
          new TableCell({ borders: thinBs("DDDDDD"), width: { size: cellSize, type: WidthType.DXA },
            children: [new Paragraph({ children: [] })] })
        )
      })
    )
  });
}
```

### English Writing Area (Horizontal Lines) — MANDATORY for English Writing Questions

⚠️ **Every English writing/composition question MUST include ruled horizontal lines.** A blank area without lines is FORBIDDEN — students need lines to write on.

```js
function writingLines(count) {
  return Array(count).fill(null).map(() =>
    new Paragraph({ spacing: { before: 0, after: 0, line: 560 },
      borders: { bottom: { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" } },
      children: [new TextRun({ text: " ", size: 21 })] })
  );
}
```

**Line count by word requirement:**
| Required Words | Lines |
|---------------|-------|
| ≤50 | 8 |
| 50–80 | 10 |
| 80–120 | 12 |
| 120+ | 15 |

**Rules:**
1. Lines must appear immediately after the writing prompt paragraph
2. Line color: light grey `CCCCCC` (print-friendly, not visually heavy)
3. Line spacing: `line: 560` (provides adequate writing room)
4. Chinese composition uses grid (`compositionGrid`), English uses lines (`writingLines`) — never mix them up
```

### Composition Area Requirements
- Independent section or clear separation
- Title space reserved (for self-chosen topics)
- Word count prompt visible ("No fewer than 800 words" / "About 120 words")
- Grid/line colors light — must not interfere with writing
- Pages continuous, not split

---

## 11. Answer Key (参考答案)

### Output Rules

1. **Default (user does not request answers in the same file):** Generate the answer key as a **separate .docx file** (e.g., `exam.docx` + `exam_answers.docx`). This prevents students from accidentally seeing answers.
2. **User explicitly requests answers in the same file:** Place the answer key on an **independent page** using `SectionType.NEXT_PAGE`. Answer key MUST NOT appear on the same page as any exam question.

### Separate File Format (Default)

The answer key file should include:
- Title: "《{exam title}》参考答案" (SimHei, 14pt/size:28, bold, centered)
- Same question numbering as the exam
- Concise answers (letter choices, key words, short solutions)
- Font: SimSun 10.5pt (size: 21)

### Same File Format (When User Requests)

```js
// Answer key as a separate section — MUST use SectionType.NEXT_PAGE
{
  properties: { type: SectionType.NEXT_PAGE,
    page: { margin: { top: 850, bottom: 850, left: 1200, right: 1200 } } },
  children: [
    new Paragraph({
      alignment: AlignmentType.CENTER, spacing: { after: 300 },
      children: [new TextRun({ text: "参考答案", size: 28, bold: true,
        font: { eastAsia: "SimHei" } })],
    }),
    // ... answer content paragraphs
  ],
}
```

### Rules
1. ⚠️ **Never place answer content directly after the last question without a page/section break**
2. Answer content should be concise — no answer lines, no grid, plain text only
3. Calculation/proof questions: show key steps, not just final answer
4. If the exam has figures, answers may reference "see Figure X" without re-embedding

---

## 12. Figures & Illustrations

### Image Insertion
```js
new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 100, after: 60 },
  children: [new ImageRun({ data: imageBuffer, transformation: { width: 300, height: 200 }, type: "png" })] })
new Paragraph({ alignment: AlignmentType.CENTER, spacing: { after: 100 },
  children: [new TextRun({ text: "(Figure 1)", size: 18, color: "666666", font: "SimSun" })] })
```

### Key Principles
- Images set as inline (default) to prevent floating
- Resolution sufficient for print clarity
- **B&W print compatible:** images must remain distinguishable when printed in grayscale
- Figure numbers and captions complete
- Figures adjacent to corresponding questions
- Maps must have: scale bar, north arrow, legend
- Coordinate graphs must have: axis labels, tick marks, units

### ⚠️ Figure-Text Order (Strictly Enforced)

**For questions with figures, element order must be:**
```
1. Question stem (keepNext: true)
2. Figure (centered, keepNext: true)
3. Answer lines / answer area
```

**Forbidden:** answer lines between stem and figure, or figure after answer lines.

### Figure Content Matching
- **Figures must be semantically consistent with question stem:** if question says "triangle ABC", figure must label vertices A, B, C
- Geometry annotations must match described angles, side lengths
- Function graphs must mark key points mentioned in the question
- Physics experiment diagrams must match described apparatus
- Figure width: geometry ≤ 50% page width, data/experiment ≤ 70%

### ⚠️ Figure Diversity Rule (Mandatory)

**No duplicate figures in the entire paper.** Even if two questions involve the same type (e.g., both triangles), each must have a distinct figure:
1. Different labels (different vertex letters, angles, side lengths)
2. Different shapes (acute vs. right vs. obtuse triangle)
3. Different styling (if applicable)

If using matplotlib, each call must use **different parameters and data** — never copy the same generation code.

### Subject-Specific Figure Requirements

| Subject | Common Types | Special Requirements |
|---------|-------------|---------------------|
| Math | Geometry, functions, coordinates | No distortion, clear labels |
| Physics | Circuits, mechanics, apparatus | Standard symbols, correct arrows |
| Chemistry | Apparatus, molecular structures | Reagent names labeled |
| Biology | Cell, organ, ecosystem diagrams | Labels not too small |
| Geography | Maps, contour lines, statistics | Legend + scale + north arrow |

---

## 13. Formulas & Special Symbols

### Formulas
Math/physics/chemistry formulas use **LaTeX → docx-js Math mapping** (see `references/math-formulas.md`):
- Basic (fractions, sub/superscript, roots) → docx-js Math components
- Complex (3+ nesting, matrices) → matplotlib PNG fallback
- Never hand-type Unicode formula approximations

### Common Unicode Math Symbols
```
× ÷ ± ∓ ≠ ≈ ≤ ≥ ∞ √ ∑ ∏ ∫ ∂ ∆ ∇
α β γ δ ε θ λ μ π σ φ ω
⊂ ⊃ ∈ ∉ ∪ ∩ ∅ ∀ ∃
→ ← ↑ ↓ ⇒ ⇔  ° ′ ″ ‰  ² ³ ⁴ ⁿ ₁ ₂ ₃
```

### Chemical Formulas
Subscripts/superscripts must be correct: H₂O, CO₂, Fe₂O₃, Ca(OH)₂
Reaction arrows: → ⇌ ↑ ↓

---

## 14. Table Usage Standards

### Borderless Tables (for alignment)
For: option alignment, info rows, question number + points alignment
```js
const NB = { style: BorderStyle.NONE, size: 0, color: "FFFFFF" };
const NBs = { top: NB, bottom: NB, left: NB, right: NB };
```

### Bordered Tables (for data display)
For: score tables, data tables, statistics
```js
const thinB = (c="000000") => ({ style: BorderStyle.SINGLE, size: 1, color: c });
const thinBs = (c="000000") => ({ top: thinB(c), bottom: thinB(c), left: thinB(c), right: thinB(c) });
```

### Table Standards
- Cell padding moderate (margins: top/bottom 40–60, left/right 60–80)
- Consistent border thickness
- Header row: light grey F0F0F0 background
- Avoid cross-page tables
- Tables centered (`alignment: AlignmentType.CENTER`)

---

## 15. Headers & Footers

### Page Numbers
```js
footers: { default: new Footer({ children: [
  new Paragraph({ alignment: AlignmentType.CENTER,
    children: [
      new TextRun({ children: [PageNumber.CURRENT], size: 18, font: "SimSun" }),
    ] })
] }) }
```

⚠️ **Denominator FORBIDDEN** — never use `PageNumber.TOTAL_PAGES` or "Page X of Y". Show only current page number.

### Headers
- May contain seal line prompt or subject name
- Small font (8–9pt), grey color (999999)
- Should not be visually heavy — must not compete with content

---

## 16. Subject-Specific Standards

### Chinese Language
- Reading, classical poetry, composition: **no columns**
- Poetry preserves original line breaks
- Classical text needs annotation area (smaller font, indented)
- Composition grid in independent section, grid count via `calcGridSize` (800 words → 50×20 = 1000 cells)
- Dictation questions: horizontal lines, moderate length
- Reading materials: use KaiTi to differentiate

### Mathematics
- Multiple choice, fill-in: suitable for neat layout
- Formulas: Unicode symbols or OOXML
- Geometry/function graphs must be clear, undistorted
- Problem-solving: sufficient working space
- Coordinate graphs: labeled axes, tick marks

### English
- English font: Times New Roman, moderate character spacing
- Cloze: numbers in text, options after passage
- Reading comprehension: material + questions as groups
- Writing area: horizontal lines, not grid
- Listening (if any): numbers aligned with options

### Physics / Chemistry / Biology
- Experiment/apparatus diagrams must be clear and accurate
- Unit symbols standardized (m/s, kg, mol/L, etc.)
- Chemical formula subscripts correct
- Calculation and experiment analysis: sufficient answer space
- Biology structure diagrams: labels not too small

### History / Politics
- Source-based questions are lengthy — **no columns**
- Dates, figures, events clearly labeled
- Essay questions: more whitespace than multiple choice
- Historical sources cite provenance
- Chart materials in logical order

### Geography
- Maps are the focus — must be clear
- Legend, scale bar, north arrow required
- Map and question close together — avoid page turns
- Map reading questions: balance figure and text space
- Contour line values clearly labeled

---

## Final Review Checklist

After generating an exam paper, check every item:

- [ ] Question numbers sequential, points correct, total correct
- [ ] Question stems match options / materials / illustrations one-to-one
- [ ] **Figures come after stem, before answer area** (strict order)
- [ ] **Figure content matches question semantics** (labels, symbols match)
- [ ] **Composition grid count ≥ required words × 1.25** (800 words → at least 1000 cells)
- [ ] Options aligned with borderless tables (not spaces)
- [ ] No wrong pages, missing pages, **no extra blank pages**
- [ ] Images / tables / formulas positioned correctly
- [ ] **No Markdown table syntax in document** — all data tables use proper docx Table objects
- [ ] Fonts, sizes, line spacing consistent
- [ ] Answer space matches difficulty and point value
- [ ] Clear when printed in B&W
- [ ] Subject-specific layout handled properly
- [ ] Seal line / page numbers / headers formatted correctly
- [ ] Header info complete (school, subject, duration, total score)
- [ ] **No extra PageBreak at end of last section**
- [ ] **Answer key is either a separate file (default) or on a separate page (if user requested in same file)** — never on the same page as questions
