Rasterex APINPM Package/docs/components/annotation/markup

Markup

Add clarity to review cycles using text, callouts, highlighter, and freehand drawing brushes.

Overview

Markup tools allow users to add annotations, highlight logical areas of interest, or sketch freehand feedback directly on the Canvas.

Use the tools API surface to activate annotation drawing modes and configure styling properties (line widths, border colors, opacity) programmatically.

  • Text & Callouts: Rectangle text borders with direct leader pointer arrows.
  • Highlighters: Semi-transparent overlays for technical document overlays and area highlights.
  • Freehand Sketching: Multi-point custom sketches for capturing handwriting or rough field notes.

Quick Examples

Activate tools and apply styles using the viewer.tools.set method:

Activate text tool

// Activate Text annotation tool
await viewer.tools.set({
  action: "TEXT",
  enabled: true
});

Activate with custom stroke styles

await viewer.tools.set({
  action: "PAINT_FREEHAND",
  enabled: true,
  style: {
    strokeColor: "#dc2626",
    strokeWidth: 3,
    opacity: 0.8
  }
});

Clear active tool

// Disable a specific tool action
await viewer.tools.set({
  action: "PAINT_FREEHAND",
  enabled: false
});

// Reset and return viewer cursor to default selection mode
viewer.tools.clear();

Supported Markup Actions

The SDK accepts these standard annotation action strings:

  • `TEXT`: Rectangle border containing editable text.
  • `CALLOUT`: Text callout box connected to an adjustable pointer line.
  • `PAINT_HIGHLIGHTER`: Rectangular highlighting brush.
  • `PAINT_FREEHAND`: Custom multi-point handwriting curves.
  • `ERASE`: Selective annotation erase selector.

Vanilla Integration

Copy these project setup files to configure mounting, tool selection clicks, and tool clearing:

import { createViewer } from "@rasterex/viewer";
import "./style.css";

const viewer = createViewer({
  container: "#viewer"
});

const statusEl = document.querySelector("#status");

function setStatus(message) {
  if (statusEl) statusEl.textContent = message;
}

async function startViewer() {
  await viewer.mount();
  await viewer.ready();
  
  await viewer.documents.open({
    url: "https://pdfobject.com/pdf/sample.pdf",
    displayName: "sample.pdf"
  });

  setStatus("Viewer ready");
}

async function enableAnnotation(action) {
  setStatus(`Activating ${action}...`);
  try {
    const result = await viewer.tools.set({
      action,
      enabled: true,
      style: {
        strokeColor: "#dc2626",
        fillColor: "#fee2e2",
        strokeWidth: 2
      }
    });

    if (result.success) {
      setStatus(`${action} activated`);
    } else {
      setStatus(`Failed: ${result.error}`);
    }
  } catch (error) {
    setStatus("Tool activation error");
  }
}

document.querySelectorAll("[data-action]").forEach((button) => {
  button.addEventListener("click", () => {
    enableAnnotation(button.dataset.action).catch(console.error);
  });
});

document.querySelector("#clear-btn")?.addEventListener("click", () => {
  viewer.tools.clear();
  setStatus("Tools cleared, selection mode active");
});

startViewer().catch(console.error);
Vanilla Integration

React Integration

Trigger text, callout, and sketch annotation workflows inside a React component:

import { useEffect, useRef, useState } from "react";
import { createViewer, type RasterexViewer, type ToolAction } from "@rasterex/viewer";
import "./style.css";

const toolsList: Array<{ label: string; action: ToolAction }> = [
  { label: "Text", action: "TEXT" },
  { label: "Freehand", action: "PAINT_FREEHAND" },
  { label: "Highlighter", action: "PAINT_HIGHLIGHTER" }
];

export function AnnotationExample() {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const viewerRef = useRef<RasterexViewer | null>(null);
  const [activeTool, setActiveTool] = useState<ToolAction | null>(null);
  const [status, setStatus] = useState("Starting viewer...");

  useEffect(() => {
    if (!containerRef.current) return undefined;

    const viewer = createViewer({ container: containerRef.current });
    viewerRef.current = viewer;

    async function start(): Promise<void> {
      await viewer.mount();
      await viewer.ready();
      await viewer.documents.open({
        url: "https://pdfobject.com/pdf/sample.pdf",
        displayName: "sample.pdf"
      });
      setStatus("Viewer ready");
    }

    void start().catch(console.error);

    return () => {
      viewer.destroy();
      viewerRef.current = null;
    };
  }, []);

  async function activateTool(action: ToolAction): Promise<void> {
    setStatus(`Activating ${action}...`);
    const result = await viewerRef.current?.tools.set({
      action,
      enabled: true,
      style: {
        strokeColor: "#dc2626",
        fillColor: "#fee2e2",
        strokeWidth: 2
      }
    });

    if (result?.success) {
      setActiveTool(action);
      setStatus(`${action} activated`);
    } else {
      setStatus(`Failed: ${result?.error ?? "error"}`);
    }
  }

  function clearTools(): void {
    viewerRef.current?.tools.clear();
    setActiveTool(null);
    setStatus("Tools cleared, selection mode active");
  }

  return (
    <div>
      <div className="toolbar">
        {toolsList.map((tool) => (
          <button
            key={tool.action}
            type="button"
            aria-pressed={activeTool === tool.action}
            onClick={() => void activateTool(tool.action)}
          >
            {tool.label}
          </button>
        ))}
        <button type="button" onClick={clearTools}>Clear tools</button>
      </div>
      <div ref={containerRef} style={{ height: 600 }} />
      <pre>{status}</pre>
    </div>
  );
}
React Integration

Vue Integration

Trigger text, callout, and sketch annotation workflows inside a Vue composition component:

<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
import { createViewer, type RasterexViewer, type ToolAction } from "@rasterex/viewer";
import "./style.css";

const containerRef = ref<HTMLElement | null>(null);
const viewerRef = shallowRef<RasterexViewer | null>(null);
const activeTool = ref<ToolAction | null>(null);
const status = ref("Starting viewer...");

const toolsList: Array<{ label: string; action: ToolAction }> = [
  { label: "Text", action: "TEXT" },
  { label: "Freehand", action: "PAINT_FREEHAND" },
  { label: "Highlighter", action: "PAINT_HIGHLIGHTER" }
];

onMounted(async () => {
  if (!containerRef.value) return;

  const viewer = createViewer({ container: containerRef.value });
  viewerRef.value = viewer;

  try {
    await viewer.mount();
    await viewer.ready();
    await viewer.documents.open({
      url: "https://pdfobject.com/pdf/sample.pdf",
      displayName: "sample.pdf"
    });
    status.value = "Viewer ready";
  } catch (error) {
    status.value = "Viewer failed to start";
  }
});

onBeforeUnmount(() => {
  viewerRef.value?.destroy();
  viewerRef.value = null;
});

async function activateTool(action: ToolAction) {
  status.value = `Activating ${action}...`;
  try {
    const result = await viewerRef.value?.tools.set({
      action,
      enabled: true,
      style: {
        strokeColor: "#dc2626",
        fillColor: "#fee2e2",
        strokeWidth: 2
      }
    });

    if (result?.success) {
      activeTool.value = action;
      status.value = `${action} activated`;
    } else {
      status.value = `Failed: ${result?.error ?? "error"}`;
    }
  } catch (error) {
    status.value = "Tool error";
  }
}

function clearTools() {
  viewerRef.value?.tools.clear();
  activeTool.value = null;
  status.value = "Tools cleared, selection mode active";
}
</script>

<template>
  <div>
    <div class="toolbar">
      <button
        v-for="tool in toolsList"
        :key="tool.action"
        type="button"
        :aria-pressed="activeTool === tool.action"
        @click="activateTool(tool.action)"
      >
        {{ tool.label }}
      </button>
      <button type="button" @click="clearTools">Clear tools</button>
    </div>
    <div ref="containerRef" style="height: 600px" />
    <pre>{{ status }}</pre>
  </div>
</template>
Vue Integration

Angular Integration

Trigger text, callout, and sketch annotation workflows inside an Angular component structure:

import {
  AfterViewInit,
  Component,
  ElementRef,
  OnDestroy,
  ViewChild
} from "@angular/core";
import { createViewer, type RasterexViewer, type ToolAction } from "@rasterex/viewer";

@Component({
  selector: "app-annotation",
  templateUrl: "./annotation.component.html"
})
export class AnnotationComponent implements AfterViewInit, OnDestroy {
  @ViewChild("viewerContainer", { static: true })
  viewerContainer!: ElementRef<HTMLElement>;

  status = "Starting viewer...";
  activeTool: ToolAction | null = null;

  toolsList: Array<{ label: string; action: ToolAction }> = [
    { label: "Text", action: "TEXT" },
    { label: "Freehand", action: "PAINT_FREEHAND" },
    { label: "Highlighter", action: "PAINT_HIGHLIGHTER" }
  ];

  private viewer: RasterexViewer | null = null;

  async ngAfterViewInit(): Promise<void> {
    const viewer = createViewer({
      container: this.viewerContainer.nativeElement
    });

    this.viewer = viewer;

    try {
      await viewer.mount();
      await viewer.ready();
      await viewer.documents.open({
        url: "https://pdfobject.com/pdf/sample.pdf",
        displayName: "sample.pdf"
      });
      this.status = "Viewer ready";
    } catch (error) {
      console.error(error);
      this.status = "Viewer failed to start";
    }
  }

  async activateTool(action: ToolAction): Promise<void> {
    this.status = `Activating ${action}...`;
    const result = await this.viewer?.tools.set({
      action,
      enabled: true,
      style: {
        strokeColor: "#dc2626",
        fillColor: "#fee2e2",
        strokeWidth: 2
      }
    });

    if (result?.success) {
      this.activeTool = action;
      this.status = `${action} activated`;
    } else {
      this.status = result?.error ?? "Tool activation failed";
    }
  }

  clearTools(): void {
    this.viewer?.tools.clear();
    this.activeTool = null;
    this.status = "Tools cleared, selection mode active";
  }

  ngOnDestroy(): void {
    this.viewer?.destroy();
  }
}
Angular Integration

Live Preview

Test the markup tools in real-time.

Interactive Workbench

Live Canvas Preview

Launch the interactive preview to explore the Canvas capabilities. Use the dashboard within the modal to test different tools and configurations.

Additional Resources

Connect markup tools with core canvas setup steps.