Pipeline Overview
The PaperEngine.render() call executes a multi-stage pipeline:
PaperDocument JSON
→ Document validation (Zod schema + structural checks)
→ Z-index flattening
→ Layout computation (Yoga flexbox)
→ Typography shaping (HarfBuzz / fontkit)
→ OOXML serialization (XML generation)
→ Compatibility analysis
→ Archive assembly (ZIP packaging)
→ Structural validation (optional)
→ Structural repair (optional)
→ Quality report generation
→ Buffer output
Each stage can fail with a typed PaperError that includes a code and phase field for programmatic handling.
Engine Methods
The PaperEngine object exposes several methods at different levels of detail:
render()
The primary method. Returns a Buffer containing valid PPTX bytes.
const buffer = await PaperEngine.render(doc, {
signal: abortController.signal,
onProgress: (slideIndex, totalSlides) => {
console.log(`Rendering slide ${slideIndex + 1}/${totalSlides}`);
},
});
renderStream()
Same as render() but returns a Node.js Readable stream instead of a buffer.
renderWithQualityReport()
Returns both the PPTX buffer and a full QualityReport:
const { pptx, qualityReport } = await PaperEngine.renderWithQualityReport(doc, undefined, {
outputMode: "editable_preferred",
validationMode: "structural",
});
renderWithPreviews()
Returns the PPTX, slide preview images (as PNG buffers), layout trees, and quality report:
const { pptx, previews, layoutTrees, qualityReport } =
await PaperEngine.renderWithPreviews(doc, { width: 1920 });
preflight()
A dry-run that validates the document and produces a QualityReport without generating a PPTX file. Useful for checking compatibility before committing to a full render.
const report = await PaperEngine.preflight(doc, {
outputMode: "strict_editable",
});
if (!report.contractPassed) {
console.error("Document would violate the editability contract");
}
Render Options
The EngineRenderOptions interface controls render behavior:
interface EngineRenderOptions {
// Cancellation
signal?: AbortSignal;
onProgress?: (slideIndex: number, totalSlides: number) => void;
// Quality contract
outputMode?: "strict_editable" | "editable_preferred" | "visual_safe";
validationMode?: "none" | "structural" | "desktop_async" | "desktop_blocking";
repairMode?: "none" | "structural";
maxFallbackLevel?: "native_editable" | "native_anchored" | "alternate_content" | "visual_fallback";
}
Output Modes
| Mode | Behavior |
|---|---|
strict_editable | All slides must be fully editable in PowerPoint. Fails if any require fallback. |
editable_preferred | Default. Allows native_anchored fallback for complex slides. |
visual_safe | Allows visual-only fallback. Maximizes visual fidelity over editability. |
Validation Modes
| Mode | What it checks |
|---|---|
none | No post-render validation. Fastest. |
structural | Validates the PPTX ZIP archive structure: content types, relationship IDs, XML well-formedness. |
desktop_async | Submits to a PowerPoint desktop oracle (hosted API only). |
desktop_blocking | Same as async but blocks until the desktop check completes (hosted API only). |
Repair Mode
When repairMode is set to "structural", the engine runs a post-render repair pass that fixes common OOXML issues (duplicate relationship IDs, missing content types, malformed XML attribute ordering). The repair summary is included in the quality report.
Cancellation
Pass an AbortSignal to cancel a render mid-flight:
const controller = new AbortController();
setTimeout(() => controller.abort(), 30000);
try {
const buffer = await PaperEngine.render(doc, { signal: controller.signal });
} catch (err) {
if (err instanceof PaperError && err.code === "RENDER_CANCELLED") {
console.log("Render was cancelled");
}
}
The engine checks the signal between stages and at the mutex acquisition step. A cancelled render throws PaperError with code RENDER_CANCELLED.
Quality Reports
The QualityReport returned by preflight(), renderWithQualityReport(), and renderWithPreviews() contains:
| Field | Type | Description |
|---|---|---|
documentVerdict | QualityDocumentVerdict | native_editable, editable_with_constraints, visual_fallback, or rejected |
editabilityScore | number (0-100) | Per-slide editability aggregate |
deckScore | number (0-100) | Overall quality score (editability, risk, fallbacks) |
repairRisk | `"low" | "medium" |
findings | QualityFinding[] | Actionable issues with codes, severity, and fix hints |
slideReports | SlideQualityReport[] | Per-slide compatibility verdict and font info |
contractPassed | boolean | Whether the output meets the requested outputMode |
structuralValidation | StructuralValidationSummary | Results of ZIP/XML structural checks |
repairSummary | RepairSummary | Actions taken during structural repair |
PaperError
All engine errors are instances of PaperError with structured fields:
class PaperError extends Error {
readonly code: PaperErrorCode;
readonly phase: ErrorPhase;
readonly slideIndex?: number;
readonly nodeId?: string;
}
Error codes: VALIDATION_FAILED, RESOURCE_LIMIT_EXCEEDED, RENDER_CANCELLED, WASM_INIT_FAILED, FONT_NOT_FOUND, MEDIA_FETCH_FAILED, MEDIA_CORRUPT, RENDER_TIMEOUT, COMPATIBILITY_CONTRACT_VIOLATION, STRUCTURAL_VALIDATION_FAILED, FEATURE_REQUIRES_UPGRADE.
Error phases: validation, compilation, layout, typography, media, chart, serialization, archive, wasm-init, font, template.
Concurrency
The engine uses an internal RenderMutex -- only one render executes at a time per process. Additional calls queue up and execute sequentially. The mutex respects AbortSignal, so queued renders can be cancelled before they start.
Lite vs Full Mode
The createEngine() factory supports two modes:
import { createEngine } from "@paperjsx/core/engine";
const lite = createEngine({ mode: "lite" });
const full = createEngine({ mode: "full" });
| Feature | Full | Lite |
|---|---|---|
| Templates (.potx) | Yes | No (throws FEATURE_REQUIRES_UPGRADE) |
| All chart types | Yes | 6 basic types |
| HarfBuzz shaping | Yes | fontkit only |
| Canvas previews | Yes | No |
| AutoFit | Yes | No |