Package Exports
- medical-form-printer
- medical-form-printer/node
Readme
medical-form-printer
A schema-driven medical form print renderer that transforms structured form data into printable HTML and PDF documents. Designed for healthcare applications requiring professional document generation with support for complex layouts, smart pagination, and cross-environment consistency.
Table of Contents
- Features
- Installation
- Quick Start
- Design Philosophy
- Section Types
- API Reference
- CSS Isolation
- Examples
Features
- 🖨️ Dual Environment - Works seamlessly in both browser and Node.js
- 📄 Rich Section Types - Info grids, data tables, checkbox grids, signature areas, notes, and more
- 🎨 Theme Customization - Fully customizable fonts, colors, spacing, and sizing
- 📑 PDF Generation - High-fidelity PDF output via Puppeteer (Node.js)
- 🔗 PDF Merging - Combine multiple documents into a single PDF
- 📐 Smart Pagination - Automatic page breaks with header repetition and overflow handling
- 🔒 CSS Isolation - Embedded fonts and namespaced styles for consistent rendering
- 🔌 Extensible - Register custom section renderers for specialized content
- 📦 TypeScript First - Full type definitions with comprehensive JSDoc documentation
Installation
npm install medical-form-printerFor PDF generation in Node.js, install Puppeteer as a peer dependency:
npm install puppeteerQuick Start
Browser Usage
import { renderToHtml } from 'medical-form-printer'
const schema = {
pageSize: 'A4',
orientation: 'portrait',
header: {
hospital: 'Sample Hospital',
title: 'Patient Assessment Form',
},
sections: [
{
type: 'info-grid',
config: {
columns: 4,
rows: [{
cells: [
{ label: 'Name', field: 'name' },
{ label: 'Age', field: 'age' },
{ label: 'Date', field: 'date', type: 'date' },
{ label: 'Room', field: 'room' },
]
}]
}
}
]
}
const data = { name: 'Jane Doe', age: 28, date: '2024-01-15', room: 'A-101' }
const html = renderToHtml(schema, data)Node.js Usage (PDF)
import { renderToPdf } from 'medical-form-printer/node'
import fs from 'fs'
const pdfBuffer = await renderToPdf(schema, data)
fs.writeFileSync('form.pdf', pdfBuffer)Design Philosophy
Why Flat Sections Instead of Nested Components?
Many document rendering systems use deeply nested component hierarchies:
Document → Page → Container → Row → Cell → ElementWe deliberately chose a flat section-based model. Here's why:
1. Print Documents ≠ UI Components
Print documents are static output. A medical form doesn't need a <Button> that responds to clicks—it needs a checkbox symbol (☑/□) at the right position. Nested component trees add overhead without benefit.
2. Domain-Driven Design
Sections map directly to real-world medical form concepts:
| Section Type | Real-World Concept |
|---|---|
info-grid |
Patient demographics block |
table |
Nursing records log |
checkbox-grid |
Symptom checklist |
signature-area |
Approval signatures |
Medical staff think in these terms, not abstract "containers" and "elements".
3. Pagination-Friendly Architecture
Flat sections enable predictable pagination:
type MeasurableItemType =
| 'header' // Page header - measured once
| 'section' // Atomic block - never split
| 'table-header' // Repeats on continuation pages
| 'table-row' // Can be paginated individually
| 'signature' // Usually pinned to last page
| 'footer' // Page footer - measured once4. Schema Simplicity
// ❌ Nested approach (verbose)
{
type: 'container',
children: [{
type: 'container',
children: [
{ type: 'label', text: 'Name:' },
{ type: 'field', binding: 'name' }
]
}]
}
// ✅ Flat approach (concise)
{
type: 'info-grid',
config: {
rows: [{ cells: [{ label: 'Name', field: 'name' }] }]
}
}5. Simple Extensibility
registerSectionRenderer('vital-signs-chart', (config, data) => {
return '<div class="chart">...</div>'
})No abstract base classes or visitor patterns needed.
Trade-offs
This design optimizes for print document generation. For deeply nested layouts or interactive components, consider general-purpose HTML templating or UI frameworks.
Section Types
| Type | Description | Use Case |
|---|---|---|
info-grid |
Grid layout for key-value pairs | Patient demographics |
table |
Data table with columns | Nursing records |
checkbox-grid |
Grid of checkbox options | Symptom checklists |
signature-area |
Signature fields | Approvals |
notes |
Static text content | Instructions |
free-text |
Multi-line text input | Comments |
Info Grid
{
type: 'info-grid',
config: {
columns: 4,
rows: [{
cells: [
{ label: 'Name', field: 'name' },
{ label: 'Age', field: 'age', type: 'number' },
{ label: 'Status', field: 'status', type: 'checkbox', options: ['Active'] }
]
}]
}
}Table
{
type: 'table',
title: 'Nursing Records',
config: {
dataField: 'records',
columns: [
{ header: 'Date', field: 'date', type: 'date', width: '20%' },
{ header: 'Notes', field: 'notes' }
]
}
}Multi-Row Headers
Tables support complex multi-row headers with colspan and rowspan:
{
type: 'table',
title: 'Vital Signs',
config: {
dataField: 'vitalSigns',
columns: [
{ header: 'Date', field: 'date', type: 'date' },
{ header: 'Systolic', field: 'systolic', type: 'number' },
{ header: 'Diastolic', field: 'diastolic', type: 'number' },
{ header: 'Temperature', field: 'temperature', type: 'number' },
],
headerRows: [
{
cells: [
{ text: 'Date', rowspan: 2 },
{ text: 'Blood Pressure (mmHg)', colspan: 2 },
{ text: 'Temperature (℃)', rowspan: 2 },
]
},
{
cells: [
{ text: 'Systolic', field: 'systolic' },
{ text: 'Diastolic', field: 'diastolic' },
]
}
]
}
}Checkbox Grid
{
type: 'checkbox-grid',
config: {
field: 'symptoms',
columns: 4,
options: [
{ value: 'fever', label: 'Fever' },
{ value: 'headache', label: 'Headache' }
]
}
}Signature Area
{
type: 'signature-area',
config: {
fields: [
{ label: 'Patient', field: 'patientSig' },
{ label: 'Date', field: 'sigDate', type: 'date' }
]
}
}API Reference
Core Rendering
| Function | Description |
|---|---|
renderToHtml(schema, data, options?) |
Render to HTML string |
renderToIsolatedHtml(schema, data, options?) |
Render with CSS isolation |
renderToIsolatedFragment(schema, data, options?) |
Render isolated fragment for embedding |
PDF Generation (Node.js)
import { renderToPdf, mergePdfs } from 'medical-form-printer/node'
// Single PDF
const pdf = await renderToPdf(schema, data, { watermark: 'Draft' })
// Merge multiple documents
const merged = await mergePdfs([
{ schema: schema1, data: data1 },
{ schema: schema2, data: data2 }
])Pagination (Strategy Pattern)
import {
createDefaultPaginationContext,
SmartPaginationStrategy
} from 'medical-form-printer'
// Automatic strategy selection
const context = createDefaultPaginationContext()
const html = context.render(schema, data, { isolated: true })
// Or use specific strategy
const strategy = new SmartPaginationStrategy()
if (strategy.shouldApply(schema)) {
const html = strategy.render(schema, data)
}Custom Section Renderers
import { registerSectionRenderer, getSectionRenderer } from 'medical-form-printer'
registerSectionRenderer('custom-chart', (config, data, options) => {
return `<div class="chart">${config.title}</div>`
})Theme Customization
import { renderToHtml, mergeTheme, defaultTheme } from 'medical-form-printer'
const theme = mergeTheme(defaultTheme, {
colors: { primary: '#1a1a1a', border: '#333' },
fontSize: { body: '10pt', heading: '14pt' }
})
const html = renderToHtml(schema, data, { theme })Page Sizes & Units
import { PAGE_A4, PAGE_A5, PAGE_16K, mmToPx, pxToMm } from 'medical-form-printer'
// PAGE_A4: { width: 210, height: 297 } (mm)
const heightPx = mmToPx(297) // mm → pixelsFormatters
import { formatDate, formatBoolean, formatNumber } from 'medical-form-printer'
formatDate('2024-01-15') // '2024-01-15'
formatDate('2024-01-15', { format: 'YYYY年MM月DD日' }) // '2024年01月15日'
formatBoolean(true) // '✓'
formatNumber(1234.5, { decimals: 2 }) // '1234.50'CSS Isolation
For consistent cross-environment rendering:
import { renderToIsolatedHtml, CSS_NAMESPACE } from 'medical-form-printer'
const html = renderToIsolatedHtml(schema, data)
// CSS_NAMESPACE = 'mpr' (all classes prefixed with mpr-)Isolation mode provides:
- Namespaced CSS classes (
mpr-prefix) - Embedded Source Han Serif SC font
- CSS containment for predictable rendering
PrintSchema Structure
interface PrintSchema {
pageSize: 'A4' | 'A5' | '16K'
orientation: 'portrait' | 'landscape'
baseUnit?: number // Scaling factor (default: 1)
header: {
hospital: string
department?: string
title: string
}
sections: PrintSection[]
footer?: {
showPageNumber?: boolean
notes?: string
}
}Examples
See the examples directory:
- Browser Example - Vanilla HTML/JS
- Node.js Example - PDF generation
Storybook
npm run storybookContributing
See CONTRIBUTING.md