|
| 1 | +--- |
| 2 | +title: Document |
| 3 | +description: Universal document rendering for Pyreon — one template, 14 output formats. |
| 4 | +--- |
| 5 | + |
| 6 | +`@pyreon/document` renders a single document template to 14+ output formats — HTML, PDF, DOCX, email, XLSX, PPTX, Slack, Teams, Discord, Telegram, Notion, Confluence, WhatsApp, Google Chat, SVG, Markdown, plain text, and CSV. Heavy renderers are lazy-loaded. Custom formats are pluggable. |
| 7 | + |
| 8 | +<PackageBadge name="@pyreon/document" href="/docs/document" /> |
| 9 | + |
| 10 | +## Installation |
| 11 | + |
| 12 | +```package-install |
| 13 | +@pyreon/document |
| 14 | +``` |
| 15 | + |
| 16 | +## Quick Start — Builder Pattern |
| 17 | + |
| 18 | +```tsx |
| 19 | +import { createDocument } from '@pyreon/document' |
| 20 | + |
| 21 | +const doc = createDocument({ title: 'Sales Report' }) |
| 22 | + .heading('Q4 Sales Report') |
| 23 | + .text('Revenue grew 25% quarter over quarter.') |
| 24 | + .table({ |
| 25 | + columns: ['Region', 'Revenue', 'Growth'], |
| 26 | + rows: [ |
| 27 | + ['US', '$1.2M', '+30%'], |
| 28 | + ['EU', '$800K', '+15%'], |
| 29 | + ['APAC', '$500K', '+40%'], |
| 30 | + ], |
| 31 | + striped: true, |
| 32 | + headerStyle: { background: '#1a1a2e', color: '#fff' }, |
| 33 | + }) |
| 34 | + .text('Total: $2.5M', { bold: true, align: 'right' }) |
| 35 | + |
| 36 | +// Export to any format |
| 37 | +await doc.toPdf() // PDF buffer |
| 38 | +await doc.toDocx() // Word document |
| 39 | +await doc.toEmail() // Outlook-safe HTML |
| 40 | +await doc.toSlack() // Slack Block Kit JSON |
| 41 | +await doc.toNotion() // Notion blocks |
| 42 | +await doc.download('report.pdf') // Browser download |
| 43 | +``` |
| 44 | + |
| 45 | +## Quick Start — JSX Pattern |
| 46 | + |
| 47 | +```tsx |
| 48 | +import { Document, Page, Heading, Text, Table, Button, render } from '@pyreon/document' |
| 49 | + |
| 50 | +function Invoice({ data }) { |
| 51 | + return ( |
| 52 | + <Document title={`Invoice #${data.id}`}> |
| 53 | + <Page size="A4" margin={40}> |
| 54 | + <Heading>Invoice #{data.id}</Heading> |
| 55 | + <Text color="#666">{data.date}</Text> |
| 56 | + <Table |
| 57 | + columns={[ |
| 58 | + { header: 'Item', width: '50%' }, |
| 59 | + { header: 'Qty', align: 'center' }, |
| 60 | + { header: 'Price', align: 'right' }, |
| 61 | + ]} |
| 62 | + rows={data.items.map(i => [i.name, i.qty, `$${i.price}`])} |
| 63 | + striped |
| 64 | + /> |
| 65 | + <Text bold align="right" size={18}>Total: ${data.total}</Text> |
| 66 | + <Button href={data.payUrl} background="#4f46e5" align="center"> |
| 67 | + Pay Now |
| 68 | + </Button> |
| 69 | + </Page> |
| 70 | + </Document> |
| 71 | + ) |
| 72 | +} |
| 73 | + |
| 74 | +// Same template → any format |
| 75 | +const pdf = await render(<Invoice data={invoiceData} />, 'pdf') |
| 76 | +const email = await render(<Invoice data={invoiceData} />, 'email') |
| 77 | +const docx = await render(<Invoice data={invoiceData} />, 'docx') |
| 78 | +``` |
| 79 | + |
| 80 | +## Output Formats |
| 81 | + |
| 82 | +### Documents |
| 83 | + |
| 84 | +| Format | Method | Library | Lazy | |
| 85 | +|---|---|---|---| |
| 86 | +| HTML | `render(doc, 'html')` | Built-in | No | |
| 87 | +| PDF | `render(doc, 'pdf')` | pdfmake (~300KB) | Yes | |
| 88 | +| DOCX | `render(doc, 'docx')` | docx (~100KB) | Yes | |
| 89 | +| XLSX | `render(doc, 'xlsx')` | exceljs (~500KB) | Yes | |
| 90 | +| PPTX | `render(doc, 'pptx')` | pptxgenjs (~200KB) | Yes | |
| 91 | +| SVG | `render(doc, 'svg')` | Built-in | No | |
| 92 | + |
| 93 | +### Communication |
| 94 | + |
| 95 | +| Format | Method | Output | |
| 96 | +|---|---|---| |
| 97 | +| Email | `render(doc, 'email')` | Outlook-safe table-based HTML with VML buttons | |
| 98 | +| Slack | `render(doc, 'slack')` | Block Kit JSON | |
| 99 | +| Teams | `render(doc, 'teams')` | Adaptive Cards JSON | |
| 100 | +| Discord | `render(doc, 'discord')` | Embed JSON | |
| 101 | +| Telegram | `render(doc, 'telegram')` | HTML subset | |
| 102 | +| WhatsApp | `render(doc, 'whatsapp')` | Formatted text (`*bold*`, `_italic_`) | |
| 103 | +| Google Chat | `render(doc, 'google-chat')` | Card V2 JSON | |
| 104 | + |
| 105 | +### Knowledge Bases |
| 106 | + |
| 107 | +| Format | Method | Output | |
| 108 | +|---|---|---| |
| 109 | +| Notion | `render(doc, 'notion')` | Block JSON for Notion API | |
| 110 | +| Confluence/Jira | `render(doc, 'confluence')` | Atlassian Document Format (ADF) | |
| 111 | + |
| 112 | +### Data |
| 113 | + |
| 114 | +| Format | Method | Output | |
| 115 | +|---|---|---| |
| 116 | +| Markdown | `render(doc, 'md')` | Markdown with pipe tables | |
| 117 | +| Plain text | `render(doc, 'text')` | Aligned ASCII tables | |
| 118 | +| CSV | `render(doc, 'csv')` | Comma-separated values | |
| 119 | + |
| 120 | +## Primitives |
| 121 | + |
| 122 | +| Primitive | Description | |
| 123 | +|---|---| |
| 124 | +| `<Document>` | Root container with metadata (title, author) | |
| 125 | +| `<Page>` | Page with size, orientation, margin, header, footer | |
| 126 | +| `<Section>` | Layout container with direction, gap, padding, background | |
| 127 | +| `<Row>` / `<Column>` | Horizontal layout | |
| 128 | +| `<Heading>` | Headings h1–h6 | |
| 129 | +| `<Text>` | Paragraph with bold, italic, color, size, align | |
| 130 | +| `<Link>` | Hyperlink | |
| 131 | +| `<Image>` | Image with width, height, alt, caption | |
| 132 | +| `<Table>` | Data table with columns, rows, striped, bordered, headerStyle | |
| 133 | +| `<List>` / `<ListItem>` | Ordered or unordered list | |
| 134 | +| `<Code>` | Code block with language | |
| 135 | +| `<Divider>` | Horizontal rule | |
| 136 | +| `<Spacer>` | Vertical gap | |
| 137 | +| `<Button>` | CTA button (VML in email, styled link in PDF) | |
| 138 | +| `<Quote>` | Block quote | |
| 139 | +| `<PageBreak>` | Force page break (PDF/DOCX) | |
| 140 | + |
| 141 | +## Table Options |
| 142 | + |
| 143 | +```tsx |
| 144 | +<Table |
| 145 | + columns={[ |
| 146 | + { header: 'Name', width: '50%' }, |
| 147 | + { header: 'Price', align: 'right', width: '25%' }, |
| 148 | + { header: 'Qty', align: 'center', width: '25%' }, |
| 149 | + ]} |
| 150 | + rows={[['Widget', '$10', '5'], ['Gadget', '$20', '3']]} |
| 151 | + striped // alternating row colors |
| 152 | + bordered // cell borders |
| 153 | + keepTogether // avoid page breaks within table (PDF) |
| 154 | + headerStyle={{ background: '#1a1a2e', color: '#fff' }} |
| 155 | + caption="Order Items" |
| 156 | +/> |
| 157 | +``` |
| 158 | + |
| 159 | +## Chart and Flow Integration |
| 160 | + |
| 161 | +Embed charts and flow diagrams from other Pyreon packages: |
| 162 | + |
| 163 | +```tsx |
| 164 | +// Builder pattern |
| 165 | +const doc = createDocument() |
| 166 | + .heading('Dashboard') |
| 167 | + .chart(chartInstance, { width: 500, height: 300 }) |
| 168 | + .flow(flowInstance, { width: 600, height: 400 }) |
| 169 | + |
| 170 | +// Charts use instance.getDataURL() → PNG |
| 171 | +// Flow diagrams use instance.toSVG() → SVG |
| 172 | +``` |
| 173 | + |
| 174 | +## Custom Renderers |
| 175 | + |
| 176 | +```tsx |
| 177 | +import { registerRenderer } from '@pyreon/document' |
| 178 | + |
| 179 | +registerRenderer('thermal', { |
| 180 | + async render(node, options) { |
| 181 | + // Walk node tree → ESC/POS commands for receipt printers |
| 182 | + return escPosBuffer |
| 183 | + }, |
| 184 | +}) |
| 185 | + |
| 186 | +await render(doc, 'thermal') |
| 187 | +``` |
| 188 | + |
| 189 | +## Browser Download |
| 190 | + |
| 191 | +```tsx |
| 192 | +import { download } from '@pyreon/document' |
| 193 | + |
| 194 | +// File extension determines format |
| 195 | +await download(doc, 'report.pdf') |
| 196 | +await download(doc, 'report.docx') |
| 197 | +await download(doc, 'data.xlsx') |
| 198 | +await download(doc, 'slides.pptx') |
| 199 | +``` |
| 200 | + |
| 201 | +## Render Options |
| 202 | + |
| 203 | +```tsx |
| 204 | +await render(doc, 'html', { |
| 205 | + direction: 'rtl', // RTL text direction |
| 206 | + fonts: { // Custom PDF fonts |
| 207 | + MyFont: { normal: 'path/to/font.ttf' }, |
| 208 | + }, |
| 209 | +}) |
| 210 | +``` |
| 211 | + |
| 212 | +## Security |
| 213 | + |
| 214 | +All renderers include built-in sanitization: |
| 215 | + |
| 216 | +- **CSS injection** — `sanitizeColor()` validates all color/background values |
| 217 | +- **XML injection** — `sanitizeXmlColor()` validates hex colors for DOCX/PPTX |
| 218 | +- **Protocol validation** — `sanitizeHref()` blocks `javascript:`, `vbscript:` in all links |
| 219 | +- **Image validation** — `sanitizeImageSrc()` validates image sources |
0 commit comments