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.
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
npm install medical-form-printer
# yarn
yarn add medical-form-printer
# pnpm
pnpm add medical-form-printer
# bun
bun add 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 printSchema = {
pageSize: 'A4',
orientation: 'portrait',
header: {
hospital: 'Sample Hospital',
department: 'Postpartum Care Center',
title: 'Patient Assessment Form',
},
sections: [
{
type: 'info-grid',
config: {
columns: 4,
rows: [
{
cells: [
{ label: 'Name', field: 'name', type: 'text' },
{ label: 'Age', field: 'age', type: 'number' },
{ label: 'Date', field: 'admissionDate', type: 'date' },
{ label: 'Room', field: 'roomNumber', type: 'text' },
]
}
]
}
}
],
footer: {
showPageNumber: true
}
}
const formData = {
name: 'Jane Doe',
age: 28,
admissionDate: '2024-01-15',
roomNumber: 'A-101'
}
// Render to HTML
const html = renderToHtml(printSchema, formData, {
watermark: 'Internal Use Only'
})
// Display in iframe or div
document.getElementById('preview').innerHTML = htmlNode.js Usage (PDF Generation)
import { renderToPdf, mergePdfs } from 'medical-form-printer/node'
import fs from 'fs'
// Generate single PDF
const pdfBuffer = await renderToPdf(printSchema, formData, {
watermark: 'Confidential'
})
fs.writeFileSync('assessment.pdf', pdfBuffer)
// Merge multiple forms into one PDF
const mergedPdf = await mergePdfs([
{ schema: maternalSchema, data: maternalData },
{ schema: newbornSchema, data: newbornData },
])
fs.writeFileSync('complete-record.pdf', mergedPdf)API Reference
Core Rendering
renderToHtml(schema, data, options?)
Renders a print schema with form data to an HTML string.
import { renderToHtml } from 'medical-form-printer'
const html = renderToHtml(printSchema, formData, {
theme: customTheme,
watermark: 'Draft',
watermarkOpacity: 0.1
})Parameters:
schema: PrintSchema- The print schema defining layout and sectionsdata: FormData- The form data to renderoptions?: RenderOptions- Optional rendering configuration
Returns: string - Complete HTML document
renderToIsolatedHtml(schema, data, options?)
Renders with CSS isolation for consistent cross-environment styling.
import { renderToIsolatedHtml } from 'medical-form-printer'
const html = renderToIsolatedHtml(printSchema, formData, {
watermark: 'Internal Use Only'
})All content is wrapped in an isolation container with:
- Namespaced CSS classes (prefixed with
mpr-) - Embedded Source Han Serif SC font
- Style containment for predictable rendering
renderToIsolatedFragment(schema, data, options?)
Renders an isolated HTML fragment for embedding in existing pages.
import { renderToIsolatedFragment } from 'medical-form-printer'
const fragment = renderToIsolatedFragment(printSchema, formData)
document.getElementById('preview').innerHTML = fragmentPDF Generation (Node.js)
renderToPdf(schema, data, options?)
Generates a PDF buffer from a print schema.
import { renderToPdf } from 'medical-form-printer/node'
const pdfBuffer = await renderToPdf(printSchema, formData, {
watermark: 'Confidential',
pdfOptions: {
format: 'A4',
printBackground: true
}
})Parameters:
schema: PrintSchema- The print schemadata: FormData- The form dataoptions?: RenderOptions & { pdfOptions?: PdfOptions }- Rendering and PDF options
Returns: Promise<Buffer> - PDF file buffer
mergePdfs(documents, options?)
Merges multiple documents into a single PDF.
import { mergePdfs } from 'medical-form-printer/node'
const mergedPdf = await mergePdfs([
{ schema: schema1, data: data1 },
{ schema: schema2, data: data2 },
], {
watermark: 'Complete Record'
})Custom Section Renderers
registerSectionRenderer(type, renderer)
Registers a custom section renderer for specialized content.
import { registerSectionRenderer } from 'medical-form-printer'
registerSectionRenderer('vital-signs-chart', (config, data, options) => {
const values = data[config.dataField] || []
return `
<div class="vital-signs-chart">
<h3>${config.title}</h3>
<!-- Custom chart rendering -->
</div>
`
})getSectionRenderer(type)
Retrieves a registered section renderer.
import { getSectionRenderer } from 'medical-form-printer'
const renderer = getSectionRenderer('info-grid')Pagination
renderPaginatedHtml(config)
Renders multi-page content with smart pagination.
import {
renderPaginatedHtml,
calculatePageBreaks,
PAGE_A4
} from 'medical-form-printer'
const pageBreaks = calculatePageBreaks(measuredItems, {
pageHeight: PAGE_A4.height,
headerHeight: 60,
footerHeight: 40,
repeatTableHeaders: true
})
const html = renderPaginatedHtml({
schema: printSchema,
data: formData,
pageBreakResult: pageBreaks,
measuredItems: items,
config: {
isolated: true,
showHeaderOnEachPage: true,
continuationSuffix: '(continued)'
}
})Page Size Presets
import { PAGE_A4, PAGE_A5, PAGE_16K, PAGE_PRESETS } from 'medical-form-printer'
// PAGE_A4: { width: 210, height: 297 } (mm)
// PAGE_A5: { width: 148, height: 210 } (mm)
// PAGE_16K: { width: 185, height: 260 } (mm)Unit Conversion
import { mmToPx, pxToMm, mmToPt, ptToMm } from 'medical-form-printer'
const heightPx = mmToPx(297) // 297mm → pixels
const heightMm = pxToMm(1123) // pixels → mmStyling
generateCss(theme?)
Generates CSS styles for print rendering.
import { generateCss, defaultTheme } from 'medical-form-printer'
const css = generateCss(defaultTheme)generateIsolatedCss(theme?)
Generates isolated CSS with embedded fonts and namespaced classes.
import { generateIsolatedCss } from 'medical-form-printer'
const css = generateIsolatedCss()
// Includes @font-face, isolation container, and all component stylesTheme Customization
import { renderToHtml, mergeTheme, defaultTheme } from 'medical-form-printer'
const customTheme = mergeTheme(defaultTheme, {
fonts: {
body: '"Microsoft YaHei", "PingFang SC", sans-serif',
heading: '"Microsoft YaHei", "PingFang SC", sans-serif'
},
colors: {
primary: '#1a1a1a',
border: '#333333',
background: '#ffffff'
},
fontSize: {
body: '10pt',
heading: '14pt',
small: '8pt'
},
spacing: {
section: '12pt',
cell: '4pt'
}
})
const html = renderToHtml(schema, data, { theme: customTheme })Formatters
import {
formatDate,
formatBoolean,
formatNumber,
formatValue,
isChecked
} from 'medical-form-printer'
formatDate('2024-01-15') // '2024-01-15'
formatDate('2024-01-15', { format: 'YYYY年MM月DD日' }) // '2024年01月15日'
formatBoolean(true) // '✓'
formatBoolean(false) // '✗'
formatNumber(1234.5, { decimals: 2 }) // '1234.50'
isChecked('yes', ['yes', 'true']) // trueHTML Builder Utilities
import {
HtmlBuilder,
h,
fragment,
when,
each,
escapeHtml
} from 'medical-form-printer'
// Fluent HTML building
const html = h('div', { class: 'container' },
h('h1', {}, 'Title'),
when(showContent, () => h('p', {}, 'Content')),
each(items, (item) => h('li', {}, item.name))
)
// Safe HTML escaping
const safe = escapeHtml('<script>alert("xss")</script>')PrintSchema Structure
interface PrintSchema {
pageSize: 'A4' | 'A5' | '16K'
orientation: 'portrait' | 'landscape'
header: {
hospital: string
department?: string
title: string
subtitle?: string
}
sections: PrintSection[]
footer?: {
showPageNumber?: boolean
pageNumberFormat?: string
notes?: string
}
}Section Types
| Type | Description | Use Case |
|---|---|---|
info-grid |
Grid layout for key-value pairs | Patient demographics, basic info |
table |
Data table with columns | Nursing records, medication logs |
checkbox-grid |
Grid of checkbox options | Assessment checklists, symptoms |
signature-area |
Signature fields with labels | Approvals, acknowledgments |
notes |
Static text content | Instructions, disclaimers |
free-text |
Multi-line text input | Comments, observations |
Info Grid Section
{
type: 'info-grid',
config: {
columns: 4,
rows: [
{
cells: [
{ label: 'Name', field: 'name', type: 'text' },
{ label: 'Age', field: 'age', type: 'number', span: 1 },
{ label: 'Date', field: 'date', type: 'date' },
{ label: 'Status', field: 'status', type: 'checkbox', options: ['Active'] }
]
}
]
}
}Table Section
{
type: 'table',
title: 'Nursing Records',
config: {
dataField: 'nursingRecords',
columns: [
{ header: 'Date', field: 'date', type: 'date', width: '15%' },
{ header: 'Time', field: 'time', type: 'text', width: '10%' },
{ header: 'Temperature', field: 'temperature', type: 'number', width: '15%' },
{ header: 'Notes', field: 'notes', type: 'text' }
]
}
}Checkbox Grid Section
{
type: 'checkbox-grid',
title: 'Symptoms Assessment',
config: {
field: 'symptoms',
columns: 4,
options: [
{ value: 'fever', label: 'Fever' },
{ value: 'headache', label: 'Headache' },
{ value: 'fatigue', label: 'Fatigue' },
{ value: 'nausea', label: 'Nausea' }
]
}
}Signature Area Section
{
type: 'signature-area',
config: {
fields: [
{ label: 'Patient Signature', field: 'patientSignature' },
{ label: 'Nurse Signature', field: 'nurseSignature' },
{ label: 'Date', field: 'signatureDate', type: 'date' }
]
}
}CSS Isolation
For consistent rendering across different environments, use isolation mode:
import {
renderToIsolatedHtml,
CSS_NAMESPACE,
ISOLATION_ROOT_CLASS,
namespaceClass,
namespaceClasses
} from 'medical-form-printer'
// CSS_NAMESPACE = 'mpr'
// ISOLATION_ROOT_CLASS = 'mpr-root'
// Namespace utilities
namespaceClass('header') // 'mpr-header'
namespaceClasses(['header', 'footer']) // ['mpr-header', 'mpr-footer']Isolation mode provides:
- All classes prefixed with
mpr-namespace - Embedded Source Han Serif SC font (subset for CJK support)
- CSS containment (
contain: layout style) - Consistent rendering regardless of host page styles
Examples
See the examples directory for complete working examples:
- Browser Example - Vanilla HTML/JS usage
- Node.js Example - PDF generation with file output
Storybook
Interactive component documentation is available via Storybook:
npm run storybookContributing
Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
License
MIT © 2024