JSPM

medical-form-printer

0.3.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 12
  • Score
    100M100P100Q39978F
  • License MIT

A medical form print renderer - render structured form data to printable HTML/PDF

Package Exports

  • medical-form-printer
  • medical-form-printer/node

Readme

medical-form-printer

npm version License: MIT Node.js Version

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

  • 🖨️ 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-printer

For PDF generation in Node.js, install Puppeteer as a peer dependency:

npm install puppeteer

Quick 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 → Element

We 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 once

4. 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 → pixels

Formatters

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:

Storybook

npm run storybook

Contributing

See CONTRIBUTING.md

License

MIT