You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

21 KiB

PDF Engine Lab - Comprehensive Architecture Plan

Overview

Project Goal: Build a complete visual PDF document editor and generation system as a Single Page Application (SPA).

The system enables users to design PDF documents visually using blocks, layouts, and composition rules without writing code.


System Architecture

flowchart TB
    subgraph SPA[Single Page Application - SvelteKit]
        subgraph UI[Visual Editor UI]
            Canvas[Document Canvas]
            Toolbar[Block Toolbar]
            PropsPanel[Properties Panel]
            Preview[PDF Preview]
        end
        
        subgraph Core[Core Engine]
            DocModel[Document Model]
            LayoutEngine[Layout Engine]
            Paginator[Paginator]
        end
        
        subgraph Renderers[Renderer Layer]
            CanvasRender[Canvas Renderer]
            PDFRender[PDF Renderer]
        end
    end
    
    UI --> DocModel
    DocModel --> LayoutEngine
    LayoutEngine --> Paginator
    Paginator --> Renderers
    CanvasRender --> Canvas
    PDFRender --> Preview

Directory Structure

src/
├── lib/
│   ├── core/                      # Core engine - framework agnostic
│   │   ├── model/                 # Document model types and utilities
│   │   │   ├── types.ts           # Node types, interfaces
│   │   │   ├── node.ts            # Node creation/manipulation
│   │   │   ├── tree.ts            # Tree traversal utilities
│   │   │   └── index.ts
│   │   │
│   │   ├── layout/                # Layout engine
│   │   │   ├── engine.ts          # Main layout calculation
│   │   │   ├── constraints.ts     # Size constraints
│   │   │   ├── flex.ts            # Flex-like layout logic
│   │   │   └── index.ts
│   │   │
│   │   ├── pagination/            # Pagination system
│   │   │   ├── paginator.ts       # Page break logic
│   │   │   ├── fragmentation.ts   # Node splitting across pages
│   │   │   └── index.ts
│   │   │
│   │   └── index.ts
│   │
│   ├── components/                # Document component definitions
│   │   ├── primitives/            # Basic building blocks
│   │   │   ├── text.ts
│   │   │   ├── image.ts
│   │   │   ├── spacer.ts
│   │   │   └── divider.ts
│   │   │
│   │   ├── containers/            # Layout containers
│   │   │   ├── row.ts
│   │   │   ├── column.ts
│   │   │   └── stack.ts
│   │   │
│   │   ├── complex/               # Complex components
│   │   │   ├── table.ts
│   │   │   ├── box.ts
│   │   │   └── page.ts
│   │   │
│   │   ├── registry.ts            # Component registration system
│   │   └── index.ts
│   │
│   ├── renderer/                  # Rendering layer
│   │   ├── types.ts               # Renderer interfaces
│   │   ├── canvas/                # Canvas/HTML preview renderer
│   │   │   ├── renderer.ts
│   │   │   ├── text.ts
│   │   │   └── index.ts
│   │   │
│   │   ├── jspdf/                 # jsPDF renderer
│   │   │   ├── renderer.ts
│   │   │   ├── text.ts
│   │   │   ├── shapes.ts
│   │   │   └── index.ts
│   │   │
│   │   └── index.ts
│   │
│   ├── editor/                    # Visual editor - Svelte components
│   │   ├── canvas/                # Document canvas
│   │   │   ├── Canvas.svelte
│   │   │   ├── Page.svelte
│   │   │   └── NodeRenderer.svelte
│   │   │
│   │   ├── toolbar/               # Block insertion toolbar
│   │   │   ├── Toolbar.svelte
│   │   │   ├── BlockButton.svelte
│   │   │   └── LayoutPicker.svelte
│   │   │
│   │   ├── panels/                # Side panels
│   │   │   ├── PropertiesPanel.svelte
│   │   │   ├── TreePanel.svelte
│   │   │   └── StylesPanel.svelte
│   │   │
│   │   ├── interactions/          # User interactions
│   │   │   ├── selection.ts
│   │   │   ├── dragdrop.ts
│   │   │   └── history.ts         # Undo/redo
│   │   │
│   │   └── index.ts
│   │
│   ├── stores/                    # Svelte stores for state
│   │   ├── document.ts            # Document state
│   │   ├── selection.ts           # Selection state
│   │   ├── history.ts             # Undo/redo state
│   │   └── index.ts
│   │
│   └── utils/                     # Utility functions
│       ├── math.ts
│       ├── colors.ts
│       └── index.ts
│
├── routes/
│   └── +page.svelte               # Main SPA entry point
│
└── app.html

Core Document Model

Node Types

// src/lib/core/model/types.ts

/** Base node structure */
interface BaseNode {
  id: string
  type: string
  props?: Record<string, unknown>
  style?: NodeStyle
  children?: Node[]
}

/** Style properties - CSS-inspired */
interface NodeStyle {
  // Layout
  width?: Dimension
  height?: Dimension
  minWidth?: Dimension
  maxWidth?: Dimension
  minHeight?: Dimension
  minHeight?: Dimension
  
  // Spacing
  margin?: Spacing
  padding?: Spacing
  
  // Flex container
  direction?: 'row' | 'column'
  justify?: 'start' | 'center' | 'end' | 'between' | 'around'
  align?: 'start' | 'center' | 'end' | 'stretch'
  gap?: number
  
  // Visual
  backgroundColor?: string
  borderColor?: string
  borderWidth?: number
  borderRadius?: number
  
  // Typography
  fontFamily?: string
  fontSize?: number
  fontWeight?: string
  color?: string
  textAlign?: 'left' | 'center' | 'right'
  lineHeight?: number
}

type Dimension = number | string  // 100 or '100%' or 'auto'
type Spacing = number | [number] | [number, number] | [number, number, number, number]

/** Union of all node types */
type Node = 
  | ContainerNode
  | TextNode
  | ImageNode
  | SpacerNode
  | DividerNode
  | TableNode
  | BoxNode

Component Definitions

// Container nodes
interface ContainerNode extends BaseNode {
  type: 'row' | 'column' | 'stack'
  children: Node[]
}

// Primitive nodes
interface TextNode extends BaseNode {
  type: 'text'
  props: {
    value: string
  }
}

interface ImageNode extends BaseNode {
  type: 'image'
  props: {
    src: string
    alt?: string
  }
}

interface SpacerNode extends BaseNode {
  type: 'spacer'
  props?: {
    size?: number
  }
}

interface DividerNode extends BaseNode {
  type: 'divider'
  props?: {
    thickness?: number
    style?: 'solid' | 'dashed' | 'dotted'
  }
}

// Complex nodes
interface TableNode extends BaseNode {
  type: 'table'
  props: {
    columns: TableColumn[]
    data: Record<string, unknown>[]
  }
}

interface BoxNode extends BaseNode {
  type: 'box'
  children: Node[]
}

Layout Engine Architecture

flowchart LR
    subgraph Input
        DocTree[Document Tree]
        PageSize[Page Size]
    end
    
    subgraph LayoutEngine[Layout Engine]
        Constraints[Constraint Solver]
        FlexLayout[Flex Layout]
        Measure[Text Measurement]
    end
    
    subgraph Output
        LayoutTree[Layout Tree]
        Boxes[Positioned Boxes]
    end
    
    DocTree --> Constraints
    PageSize --> Constraints
    Constraints --> FlexLayout
    FlexLayout --> Measure
    Measure --> LayoutTree
    LayoutTree --> Boxes

Layout Algorithm

// src/lib/core/layout/engine.ts

interface LayoutContext {
  pageWidth: number
  pageHeight: number
  pageMargins: [number, number, number, number]
}

interface LayoutBox {
  nodeId: string
  x: number
  y: number
  width: number
  height: number
  children?: LayoutBox[]
}

function calculateLayout(node: Node, context: LayoutContext): LayoutBox {
  // 1. Resolve constraints (%, auto, fixed)
  // 2. Calculate available space
  // 3. Layout children based on container type
  // 4. Return positioned box tree
}

Pagination System

flowchart TB
    Content[Content Tree]
    Layout[Layout Boxes]
    
    subgraph Pagination[Pagination Process]
        Check{Fits in page?}
        AddPage[Add new page]
        Fragment[Fragment node]
        Continue[Continue layout]
    end
    
    Pages[Paginated Pages]
    
    Content --> Layout
    Layout --> Check
    Check -->|Yes| Continue
    Check -->|No| Fragment
    Fragment --> AddPage
    AddPage --> Continue
    Continue --> Pages

Fragmentation Strategy

// src/lib/core/pagination/fragmentation.ts

interface FragmentResult {
  first: Node | null   // Part that fits on current page
  second: Node | null  // Part that goes to next page
}

function canFragment(node: Node): boolean {
  // Text: yes
  // Table: yes
  // Container: yes (if children can be split)
  // Image: no
}

function fragmentNode(node: Node, availableHeight: number): FragmentResult {
  // Split node across pages
}

Renderer Abstraction

// src/lib/renderer/types.ts

interface RenderContext {
  pageWidth: number
  pageHeight: number
  currentPage: number
}

interface Renderer {
  name: string
  
  // Document lifecycle
  beginDocument(context: RenderContext): void
  endDocument(): void
  
  // Page lifecycle
  beginPage(pageNumber: number): void
  endPage(): void
  
  // Drawing primitives
  drawText(text: string, box: LayoutBox, style: TextStyle): void
  drawRect(box: LayoutBox, style: RectStyle): void
  drawImage(src: string, box: LayoutBox): void
  drawLine(from: Point, to: Point, style: LineStyle): void
  
  // Output
  getOutput(): unknown  // PDF blob, canvas, etc.
}

Visual Editor Architecture

flowchart TB
    subgraph EditorState[Editor State - Svelte Stores]
        DocStore[Document Store]
        SelectStore[Selection Store]
        HistoryStore[History Store]
    end
    
    subgraph UI[Editor UI Components]
        Canvas[Document Canvas]
        Toolbar[Block Toolbar]
        PropsPanel[Properties Panel]
        TreeView[Tree View]
    end
    
    subgraph Interactions[User Interactions]
        Selection[Selection Manager]
        DragDrop[Drag and Drop]
        Keyboard[Keyboard Shortcuts]
    end
    
    DocStore --> Canvas
    SelectStore --> PropsPanel
    SelectStore --> Canvas
    
    Canvas --> Selection
    Toolbar --> DragDrop
    DragDrop --> DocStore
    
    UI --> Interactions
    Interactions --> EditorState

Editor Stores

// src/lib/stores/document.ts
import { writable } from 'svelte/store'

interface DocumentState {
  root: Node
  pageSettings: PageSettings
  dirty: boolean
}

function createDocumentStore() {
  const { subscribe, set, update } = writable<DocumentState>({
    root: createEmptyDocument(),
    pageSettings: { size: 'A4', margins: [40, 40, 40, 40] },
    dirty: false
  })
  
  return {
    subscribe,
    setRoot: (node: Node) => update(s => ({ ...s, root: node, dirty: true })),
    updateNode: (id: string, updates: Partial<Node>) => { /* ... */ },
    insertNode: (parent: string, node: Node, index?: number) => { /* ... */ },
    removeNode: (id: string) => { /* ... */ },
    moveNode: (id: string, newParent: string, newIndex: number) => { /* ... */ }
  }
}

// src/lib/stores/selection.ts
interface SelectionState {
  selectedId: string | null
  hoveredId: string | null
}

// src/lib/stores/history.ts
interface HistoryState {
  past: DocumentState[]
  future: DocumentState[]
}

Phase-by-Phase Implementation Roadmap

Phase 1: Foundation

Goal: Core document model and basic PDF export

Deliverables:

  • Document Model Types (src/lib/core/model/types.ts)

    • Define all Node interfaces
    • Style type definitions
    • Type guards for node types
  • Node Utilities (src/lib/core/model/node.ts)

    • createNode() factory function
    • cloneNode() deep clone
    • traverseTree() iterator
    • findNodeById() lookup
  • Basic Layout Engine (src/lib/core/layout/)

    • Constraint resolution (%, px, auto)
    • Row layout algorithm
    • Column layout algorithm
    • Text measurement
  • jsPDF Renderer (src/lib/renderer/jspdf/)

    • Implement Renderer interface
    • Text rendering
    • Rectangle rendering
    • Image rendering
    • PDF output generation
  • Component Registry (src/lib/components/registry.ts)

    • Register primitive components
    • Component metadata (icon, label, defaults)
  • Basic Test Document

    • Create sample document tree
    • Generate PDF output

Success Criteria:

  • Can define a document as JSON
  • Can render Text, Row, Column to PDF
  • PDF output is correctly positioned

Phase 2: Visual Editor Core

Goal: Basic visual editing capabilities

Deliverables:

  • Document Store (src/lib/stores/document.ts)

    • State management for document tree
    • CRUD operations for nodes
  • Selection Store (src/lib/stores/selection.ts)

    • Track selected/hovered nodes
  • Canvas Component (src/lib/editor/canvas/)

    • Page.svelte - Single page representation
    • Canvas.svelte - Document container
    • NodeRenderer.svelte - Render nodes to HTML preview
  • Block Toolbar (src/lib/editor/toolbar/)

    • Toolbar.svelte - Component toolbar
    • BlockButton.svelte - Insert button for each block type
    • LayoutPicker.svelte - Predefined layout templates
  • Properties Panel (src/lib/editor/panels/)

    • PropertiesPanel.svelte - Edit selected node props
    • Basic text/spacing inputs
  • Canvas Renderer (src/lib/renderer/canvas/)

    • HTML-based preview renderer
    • Visual selection indicators

Success Criteria:

  • Can see document in browser
  • Can insert new blocks
  • Can select and edit basic properties
  • Preview updates in real-time

Phase 3: Advanced Editing

Goal: Professional editing experience

Deliverables:

  • Drag and Drop (src/lib/editor/interactions/dragdrop.ts)

    • Reorder nodes within container
    • Move nodes between containers
    • Visual drop indicators
  • History/Undo-Redo (src/lib/stores/history.ts)

    • Command pattern implementation
    • Undo/redo stack
    • Keyboard shortcuts (Ctrl+Z, Ctrl+Y)
  • Tree Panel (src/lib/editor/panels/TreePanel.svelte)

    • Hierarchical document view
    • Expand/collapse containers
    • Click to select
  • Styles Panel (src/lib/editor/panels/StylesPanel.svelte)

    • Margin/padding controls
    • Typography controls
    • Color pickers
  • Keyboard Shortcuts

    • Delete selected (Delete/Backspace)
    • Copy/paste nodes (Ctrl+C, Ctrl+V)
    • Duplicate (Ctrl+D)
  • Context Menu

    • Right-click on selected node
    • Cut, copy, paste, delete, duplicate

Success Criteria:

  • Drag and drop works smoothly
  • Undo/redo functions correctly
  • All style properties editable
  • Keyboard shortcuts work

Phase 4: Pagination & Complex Components

Goal: Production-ready document generation

Deliverables:

  • Pagination Engine (src/lib/core/pagination/)

    • Page break calculation
    • Content fragmentation
    • Text splitting across pages
    • Table row pagination
  • Table Component (src/lib/components/complex/table.ts)

    • Define table structure
    • Header row repeat
    • Cell layout
    • Border handling
  • Box Component (src/lib/components/complex/box.ts)

    • Container with visual styling
    • Border, background, padding
  • Multi-page Preview

    • Show all pages in canvas
    • Page navigation
    • Page thumbnails
  • Page Settings

    • Size selection (A4, A5, Letter, etc.)
    • Margin configuration
    • Orientation (portrait/landscape)
  • Header/Footer

    • Repeat on each page
    • Page numbers
    • Custom content

Success Criteria:

  • Long documents paginate correctly
  • Tables split across pages properly
  • Headers/footers appear on all pages
  • PDF output matches preview

Phase 5: Extensibility & AI

Goal: Platform capabilities

Deliverables:

  • Plugin System

    • Custom component registration
    • Custom renderer registration
    • Plugin lifecycle hooks
  • AI Integration Hooks

    • Document generation from prompt
    • Smart layout suggestions
    • Content transformation
  • Template System

    • Save/load document templates
    • Template variables
    • Data binding
  • Export Options

    • PDF quality settings
    • PDF metadata (title, author)
    • Multiple renderer support
  • Import/Export

    • JSON document export
    • JSON document import
    • Document validation

Success Criteria:

  • Plugins can extend functionality
  • AI can generate document structures
  • Templates work with dynamic data
  • Multiple export formats available

Technical Decisions

Aspect Decision Rationale
State Management Svelte Stores Built-in, reactive, simple
Layout Model Flex-like Familiar, powerful, proven
Document Format JSON Serializable, AI-compatible
PDF Library jsPDF Mature, works in browser
Component Model Registry pattern Extensible, decoupled
History Command pattern Clean undo/redo

❌ Anti-Patterns to Avoid

Anti-Pattern Why Avoid
Absolute positioning Breaks responsiveness, pagination
Coupling engine to jsPDF Limits future renderer options
Storing UI state in document Mixes concerns
Imperative rendering Hard to maintain, debug
Skipping TypeScript Complex domain needs types

Component Registry Design

// src/lib/components/registry.ts

interface ComponentDefinition {
  type: string
  category: 'container' | 'primitive' | 'complex'
  label: string
  icon: string
  defaultProps?: Record<string, unknown>
  defaultStyle?: NodeStyle
  allowsChildren?: boolean
  canFragment?: boolean
}

const componentRegistry = new Map<string, ComponentDefinition>()

function registerComponent(definition: ComponentDefinition) {
  componentRegistry.set(definition.type, definition)
}

function getComponent(type: string): ComponentDefinition | undefined {
  return componentRegistry.get(type)
}

function getComponentsByCategory(category: string): ComponentDefinition[] {
  return Array.from(componentRegistry.values())
    .filter(c => c.category === category)
}

// Register built-in components
registerComponent({
  type: 'text',
  category: 'primitive',
  label: 'Text',
  icon: 'type',
  defaultProps: { value: 'New text' },
  canFragment: true
})

registerComponent({
  type: 'row',
  category: 'container',
  label: 'Row',
  icon: 'columns',
  allowsChildren: true,
  defaultStyle: { direction: 'row', gap: 8 }
})

Layout Templates

// Predefined layout structures for quick insertion

const layoutTemplates = [
  {
    name: 'Two Columns',
    icon: 'layout-two-columns',
    node: {
      type: 'row',
      style: { gap: 16 },
      children: [
        { type: 'column', children: [] },
        { type: 'column', children: [] }
      ]
    }
  },
  {
    name: 'Header + Content',
    icon: 'layout-header',
    node: {
      type: 'column',
      children: [
        { 
          type: 'box', 
          style: { padding: 16, backgroundColor: '#f0f0f0' },
          children: [{ type: 'text', props: { value: 'Header' } }]
        },
        { type: 'column', children: [] }
      ]
    }
  },
  {
    name: 'Invoice Layout',
    icon: 'file-text',
    node: {
      type: 'column',
      style: { gap: 24 },
      children: [
        { type: 'text', props: { value: 'INVOICE' }, style: { fontSize: 24, fontWeight: 'bold' } },
        {
          type: 'row',
          style: { justify: 'between' },
          children: [
            { type: 'column', children: [{ type: 'text', props: { value: 'From:' } }] },
            { type: 'column', children: [{ type: 'text', props: { value: 'To:' } }] }
          ]
        },
        { type: 'table', props: { columns: [], data: [] } },
        {
          type: 'row',
          style: { justify: 'end' },
          children: [{ type: 'text', props: { value: 'Total: $0.00' } }]
        }
      ]
    }
  }
]

Testing Strategy

Unit Tests

  • Document model operations
  • Layout calculations
  • Pagination logic
  • Node fragmentation

Integration Tests

  • Full render pipeline
  • Editor interactions
  • Store updates

Visual Regression Tests

  • PDF output comparison
  • Canvas preview comparison

Performance Considerations

  1. Lazy Layout Calculation

    • Only calculate layout for visible pages
    • Cache layout results
  2. Efficient Updates

    • Incremental layout recalculation
    • Debounced property updates
  3. Large Documents

    • Virtual scrolling in tree view
    • Chunked pagination

Next Steps

  1. Review this plan - Confirm architecture decisions
  2. Prioritize Phase 1 - Begin with document model and basic rendering
  3. Set up testing - Establish test patterns early
  4. Create component registry - Foundation for extensibility

Questions for Clarification

  1. Should the editor support collaborative editing in the future?
  2. Are there specific PDF features required (forms, annotations, digital signatures)?
  3. What level of styling granularity is needed for text (inline styles vs blocks)?
  4. Should templates support conditional content and loops for data binding?

Powered by TurnKey Linux.