Architecture

Understanding InkLayer’s layered architecture is essential for deep usage and extensibility. This chapter explains each layer’s responsibilities, data flow, and design decisions.

Layered Architecture Overview

┌─────────────────────────────────────────────┐
│                User API Layer               │
│   PdfAnnotator / PdfViewer Components       │
│   (React: FC + Provider, Vue: SFC + Slots)  │
├─────────────────────────────────────────────┤
│           Extension System                  │
│   Toolbar / Sidebar / SelectionBar          │
│   Painter (Konva rendering engine)          │
│   Editor (14 annotation type editors)       │
│   Transform (coordinate codec)              │
│   Store (state mgmt: Zustand / Pinia)       │
├─────────────────────────────────────────────┤
│              Core Abstraction               │
│   Annotation Core (framework-agnostic)      │
│   Adapter (Konva / PDF.js adapter interfaces)│
│   Integration (save/load/import/export)     │
│   Store Mapper (bidirectional format mapping)│
├─────────────────────────────────────────────┤
│            Infrastructure Layer              │
│   PDF.js (~4.3) - PDF rendering engine     │
│   Konva (9.0)   - Canvas 2D graphics       │
│   pdf-lib       - PDF manipulation          │
│   ExcelJS       - Excel export             │
└─────────────────────────────────────────────┘

Layer Responsibilities

1. User API Layer

The top-level developer-facing interface, providing ready-to-use components:

  • PdfAnnotator: Full annotator with toolbar + editing + sidebar
  • PdfViewer: Lightweight viewer without annotation features
  • React: Functional Component + Context Provider pattern
  • Vue: Single File Component + Provide/Inject + Slots pattern

2. Extension System Layer

Pluggable functional extensions that users can customize or replace:

ModuleResponsibilityCustomizable?
ToolbarAnnotation tool selector (highlight, pen, rectangle…)✅ Fully replaceable
SidebarAnnotation list panel, search panel✅ Fully replaceable
SelectionBarText selection popup action bar⚠️ Partial customization
PainterKonva annotation drawing engine❌ Core low-level
Editor14 annotation type create/edit logic⚠️ Extensible
StoreAnnotation state management (Zustand / Pinia)⚠️ Readable

3. Core Abstraction Layer

Fully decoupled from UI frameworks and rendering engines — InkLayer’s most important design:

  • Annotation Core: Framework-agnostic annotation data model with complete type system
  • Adapter: Rendering adapter interface mapping Annotation to specific rendering engines (Konva)
  • Integration: Storage formats, import/export, format compatibility layer
  • Store Mapper: Bidirectional mapping between runtime state and persisted data

Design highlight: React and Vue versions share exactly the same Core code. This means the annotation model you learn in one package directly applies to the other. Bug fixes in Core benefit both frameworks simultaneously.

4. Infrastructure Layer

DependencyVersionPurpose
PDF.js~4.3.136PDF parsing, page rendering, text content extraction
Konva^9.0.0Canvas-based 2D graphics, annotation rendering
pdf-lib^1.17.1PDF manipulation (create, modify, export annotations to PDF)
ExcelJS^4.4.0Annotation data export to Excel
web-highlighter^0.7.4Web text highlight selection

Framework Binding Comparison

DimensionReact (inklayer-react)Vue (inklayer-vue)
Component PatternFunctional ComponentSFC (Single File Component)
State ManagementZustandPinia
ContextReact ContextVue Provide/Inject
UI LibraryRadix UI Themesshadcn-vue (reka-ui)
StylingSASS/SCSSTailwind CSS 4 + CVA
i18ni18next + react-i18nextvue-i18n
Iconsreact-icons@lucide/vue
Template CustomizationRender Props / childrenNamed Slots
Programmatic AccessRef + imperativeHandleRef + defineExpose

Data Flow

InkLayer’s data flow is unidirectional, propagating outward from Core:

Write path:
  User action → Konva Node create/modify
    → Painter Editor processes
    → Adapter.extract() produces Annotation
    → Store.commit() updates state
    → Integration serializes → Backend persist

Read path:
  Backend data → Integration.parse() deserialize
    → Annotation[] objects
    → Store.load() populates state
    → Adapter.render() generates Konva Node
    → Canvas renders to display

Dual-Framework Shared Design

InkLayer’s two packages share the exact same core module. This is achieved through:

  1. Core module zero-dependency: annotation.core.ts, adapters/, integration.ts import no React/Vue code
  2. Adapter pattern decoupling: Konva rendering is isolated via AnnotationRendererAdapter interface
  3. Separate Store implementations: Zustand and Pinia each implement IAnnotationStore
  4. Separate UI bindings: Toolbar, sidebar, and other UI components are implemented per-framework

Custom Extensions

Register a Custom Annotation Adapter

import { AdapterRegistry } from 'inklayer-react/core'

const registry = AdapterRegistry.getInstance()

// Register adapter for custom annotation type
registry.register('custom-kind', {
  render(annotation, context) {
    // Custom render logic
  },
  update(node, annotation, context) {
    // Custom update logic
  },
  extract(node, context) {
    // Custom extraction logic
  }
})

Custom Toolbar (React)

<PdfAnnotator
  url="/doc.pdf"
  actions={({ save, annotations }) => (
    <button onClick={save}>
      Save ({annotations.length} annotations)
    </button>
  )}
/>

Custom Sidebar (Vue)

<PdfAnnotator url="/doc.pdf">
  <template #sidebar-header>
    <MyCustomHeader />
  </template>
  <template #sidebar-content>
    <MyCustomAnnotationList :annotations="store.annotations" />
  </template>
</PdfAnnotator>