> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/annotation/customize/customize-rendering.md).

# Customize annotation rendering

Customize annotation rendering in WebViewer using setCustomDrawHandler API. Change how annotations are visually represented on screen for both standard and custom annotations. The Apryse Web SDK strea

The PDF specification defines a standard set of annotations, but not how they appear on screen, as that is left to the document viewers. WebViewer lets you change how annotations are rendered when viewing them using the [setCustomDrawHandler](https://sdk.apryse.com/api/web/Core.Annotations.html#.setCustomDrawHandler) API. You can apply this to both standard and custom annotations in WebViewer.

If you're using [custom annotations](/web/annotation/customize/creating-custom-annotations.md), they already have support for custom rendering. However, implementing one may be too much if you only want to change how annotations look, especially the standard set. This API will allow you to change how the annotations render without having to implement one.

{% hint style="info" %}
**Info:** The `setCustomDrawHandler` and `restoreDraw` APIs are available in WebViewer 7.1+. The [custom line and fill style APIs](#add-custom-line-and-fill-styles) require WebViewer 12.2.0+.
{% endhint %}

## Change annotation rendering

To change how annotations are rendered, you can use the [setCustomDrawHandler](https://sdk.apryse.com/api/web/Core.Annotations.html#.setCustomDrawHandler) API.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
Annotations.setCustomDrawHandler(Annotations.RectangleAnnotation, function(ctx, pageMatrix, rotation, options) {
    options.originalDraw(ctx, pageMatrix, rotation);
    ctx.save();
    ctx.fillStyle = '#000000';
    ctx.fillText('Hello', this.X, this.Y + 12);
    ctx.restore();
});
```

{% endcode %}

**Related links**

* [Annotations.setCustomDrawHandler](https://sdk.apryse.com/api/web/Core.Annotations.html#.setCustomDrawHandler)
  {% endtab %}
  {% endtabs %}

<div align="left"><figure><img src="https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2FAElXhQNDMw7uZHkVrGwh%2F056b1f8d3383436a1309125dccf96f2402bdeaba-226x211.png?alt=media&#x26;token=7f18712d-d986-45b2-8aa0-5e6038233ef5" alt="Rectangle annotation with the text &#x22;Hello&#x22; drawn in its top-left corner by a custom draw handler"><figcaption><p>Rectangle annotation drawn with the custom draw handler</p></figcaption></figure></div>

Alongside the annotation class you're targeting, you must provide a new custom [draw function](https://sdk.apryse.com/api/web/Core.Annotations.html#.CustomAnnotationDrawHandler) to the API to perform the rendering. It takes the regular parameters you would expect from an annotation [draw](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#draw) function with an additional `options` parameter. If you are using an arrow function (bound function), then you can find the annotation through the `options` parameter.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
// Draw handler signature
function(ctx, pageMatrix, rotation, options) {
  ...
}
```

{% endcode %}

**Related links**

* [CustomAnnotationDrawHandler](https://sdk.apryse.com/api/web/Core.Annotations.html#.CustomAnnotationDrawHandler)
  {% endtab %}
  {% endtabs %}

Note that you get the annotation being drawn as well as the original annotation's draw function as part of the `options` parameter. This lets you reference the annotation and run the original draw function if needed.

### Control appearances in other viewers

Although this works with annotations in WebViewer, other viewers will render annotations according to their own rendering. By default, [setCustomDrawHandler](https://sdk.apryse.com/api/web/Core.Annotations.html#.setCustomDrawHandler) will generate an appearance for the annotation so it will look similar in other viewers. If you want to turn this off, perhaps for changing rendering at certain times or if it's unnecessary, you can set the `generateAppearance` option in the third parameter of the API.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
Annotations.setCustomDrawHandler(Annotations.RectangleAnnotation, function(ctx, pageMatrix, rotation, options) {
    ...
}, {
  generateAppearance: false,
  canvasMultiplier: 2, // Increase appearance quality at the cost of memory
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Restore custom annotation rendering

When customizing annotation rendering, the original draw function is preserved. Calling the [setCustomDrawHandler](https://sdk.apryse.com/api/web/Core.Annotations.html#.setCustomDrawHandler) API again will not overwrite the original draw function. This lets you restore the annotation's original behavior with the [restoreDraw](https://sdk.apryse.com/api/web/Core.Annotations.html#.restoreDraw) API.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
Annotations.restoreDraw(Annotations.RectangleAnnotation);
```

{% endcode %}

**Related links**

* [Annotations.restoreDraw](https://sdk.apryse.com/api/web/Core.Annotations.html#.restoreDraw)
  {% endtab %}
  {% endtabs %}

## Add custom line and fill styles

{% hint style="info" %}
**Info:** The APIs in this section are available in WebViewer 12.2.0+.
{% endhint %}

In addition to custom annotation renderings, APIs allow customized line styling on existing line annotation tools and customized fill styling on existing shape annotation tools. These styles will appear in the style panel on the selected tools as icons (if available) or text if icons are omitted.

Any custom line styles will appear in the dropdowns under the style heading on line tools. Custom fill styles will appear under the fill section in a new dropdown under the style heading.

<div align="left"><figure><img src="https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2FkRIBKfQYc6eKDTw8FYVI%2F59c964d6f0ed2604a848451a34e40f965e822148-954x870.png?alt=media&#x26;token=69d96738-f5d0-4711-9870-4a42310b86d1" alt="Style panels for the Rectangle and Line tools, with custom fill styles in the Fill Style dropdown and custom line styles in the Line Middle dropdown" height="400" width="400"><figcaption><p>Custom fill and line styles in the style panel</p></figcaption></figure></div>

### Register line styles

To register custom line styles, you can use the [registerCustomLineStyle](https://sdk.apryse.com/api/web/UI.html#.registerCustomLineStyle__anchor) API.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
// Registers custom middle line styling that
// displays repeating Hs along the line
UI.registerCustomLineStyle({
  key: 'h-pattern-text',
  section: 'middle',
  appliesTo: ['all'],
  title: 'H Pattern (Text)',
  svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 24"><line x1="4" y1="12" x2="76" y2="12" stroke="currentColor" stroke-width="1.5"/><text x="16" y="14" font-size="8" text-anchor="middle" fill="currentColor">H</text><text x="32" y="14" font-size="8" text-anchor="middle" fill="currentColor">H</text><text x="48" y="14" font-size="8" text-anchor="middle" fill="currentColor">H</text><text x="64" y="14" font-size="8" text-anchor="middle" fill="currentColor">H</text></svg>',
  drawHandler: (ctx, pageMatrix, rotation, { annotation, originalDraw }) => {
    originalDraw(ctx, pageMatrix);

    const path = annotation.getPath?.();
    const segments = path?.length >= 2
      ? path.slice(1).map((endPt, index) => ({
        startPt: path[index],
        endPt,
      }))
      : [{
        startPt: annotation.getStartPoint?.() ?? annotation.Start,
        endPt: annotation.getEndPoint?.() ?? annotation.End,
      }].filter(({ startPt, endPt }) => startPt && endPt);

    const spacing = 18;
    const fontSize = Math.max(10, (annotation.StrokeThickness ?? 1) * 4);
    const color = annotation.StrokeColor?.toHexString?.() ?? '#000000';

    ctx.save();
    try {
      ctx.globalAlpha = annotation.Opacity ?? 1;
      ctx.fillStyle = color;
      ctx.font = `${fontSize}px Arial`;
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';

      segments.forEach(({ startPt, endPt }) => {
        const dx = endPt.x - startPt.x;
        const dy = endPt.y - startPt.y;
        const length = Math.hypot(dx, dy);

        if (!length) {
          return;
        }

        const unitX = dx / length;
        const unitY = dy / length;

        for (let distance = 0; distance <= length; distance += spacing) {
          ctx.fillText(
            'H',
            startPt.x + unitX * distance,
            startPt.y + unitY * distance,
          );
        }
      });
    } finally {
      ctx.restore();
    }
  },
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

<div align="left"><figure><img src="https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2FR2GuegpvaNvonDittfAA%2Ffd6bcc23f2cb1ce0ef756b484b5361e41a81b921-402x214.png?alt=media&#x26;token=b8039591-28df-4c15-9c1b-9d8f666e970e" alt="Two line annotations with a repeating letter H drawn along each line"><figcaption><p>Line annotations with the H pattern line style</p></figcaption></figure></div>

This API takes in a key that is tied to the custom line style and a custom draw handler responsible for drawing the custom line styling.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
// Custom line style draw handler signature
function (ctx, pageMatrix, rotation, { annotation, originalDraw }) {
...
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

Alongside these two required parameters, the API also takes options to set which sections of the line the style applies to (`section`), which line tools this style can appear on (`appliesTo`), the title that will be shown in the style dropdown (`title`), an SVG string to display in the dropdown (`svg`), and extra bounds padding (`padding`). Re-registering a new custom style with a previously used and active key will override the key's previously associated handler and options and replace them with the new draw handler and options.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
// Registers an end diamond line style that only applies to line annotations
UI.registerCustomLineStyle({
  key: 'custom-end-diamond',
  section: 'end',
  title: 'Diamond End',
  appliesTo: ['line'],
  svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 20"><path d="M32 10 L23 3 L14 10 L23 17 Z" fill="currentColor"/></svg>',
  padding: (annotation) =>
    Math.max(12, (annotation.StrokeThickness ?? 1) * 7),
  drawHandler: (
    ctx,
    pageMatrix,
    rotation,
    { annotation, originalDraw, activeSections },
  ) => {
    if (!activeSections.includes('middle')) {
      originalDraw(ctx, pageMatrix);
    }

    const startPt =
      annotation.getStartPoint?.() ?? annotation.Start;
    const endPt =
      annotation.getEndPoint?.() ?? annotation.End;

    if (!startPt || !endPt) {
      return;
    }

    const angle = Math.atan2(
      endPt.y - startPt.y,
      endPt.x - startPt.x,
    );
    const thickness = annotation.StrokeThickness ?? 1;
    const size = Math.max(8, thickness * 5);

    ctx.save();
    try {
      ctx.globalAlpha = annotation.Opacity ?? 1;
      ctx.strokeStyle =
        annotation.StrokeColor?.toHexString?.() ?? '#000000';
      ctx.fillStyle = ctx.strokeStyle;
      ctx.lineWidth = Math.max(1.5, thickness);
      ctx.lineJoin = 'round';

      ctx.save();
      ctx.translate(endPt.x, endPt.y);
      ctx.rotate(angle);
      ctx.beginPath();
      ctx.moveTo(0, 0);
      ctx.lineTo(-size * 0.65, -size * 0.55);
      ctx.lineTo(-size * 1.3, 0);
      ctx.lineTo(-size * 0.65, size * 0.55);
      ctx.closePath();
      ctx.fill();
      ctx.restore();
    } finally {
      ctx.restore();
    }
  },
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

<div align="left"><figure><img src="https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2FktrCTvlsFzRe6kife8PP%2Ff182287a9b94c74f8ad2e8008d39091a1e4113fc-368x174.png?alt=media&#x26;token=1805c754-b00e-4964-a351-130c09e4ba16" alt="Two line annotations ending in a filled diamond"><figcaption><p>Line annotations with the diamond end style</p></figcaption></figure></div>

### Register fill styles

To register custom fill styles, you can use the [registerCustomFillStyle](https://sdk.apryse.com/api/web/UI.html#.registerCustomFillStyle__anchor) API.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
// Registers a dot fill style for all shape annotation tools
UI.registerCustomFillStyle({
  key: 'fill-dots',
  title: 'Dots',
  appliesTo: ['all'],
  svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="4" cy="4" r="2" fill="currentColor"/><circle cx="16" cy="16" r="2" fill="currentColor"/></svg>',
  drawHandler: (ctx, pageMatrix, rotation, { annotation, originalDrawWithoutFill }) => {
    originalDrawWithoutFill();
    ctx.save();
    ctx.fillStyle = annotation.FillColor.toString();
    const { X, Y, Width, Height } = annotation;
    for (let x = X + 4; x < X + Width; x += 12) {
      for (let y = Y + 4; y < Y + Height; y += 12) {
        ctx.beginPath();
        ctx.arc(x, y, 2, 0, 2 * Math.PI);
        ctx.fill();
      }
    }
    ctx.restore();
  },
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

<div align="left"><figure><img src="https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2FMfvnO4FNHkuneg1QdP6I%2Fc059fcdeac78afcdbc4d53e05f1d15dffd5289e5-348x396.png?alt=media&#x26;token=f6770d8f-c90d-486d-9ef1-6885cc00aed7" alt="Rectangle, ellipse, and triangle annotations filled with a grid of dots"><figcaption><p>Shape annotations with custom dotted fill style</p></figcaption></figure></div>

This API takes a key tied to the custom fill style and a custom draw handler that draws the custom fill styling within the shape annotation.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
// Signature for fill draw handler
function (ctx, pageMatrix, rotation, { annotation, originalDraw, originalDrawWithoutFill, clipToShape }) {
...
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

Alongside these two required parameters, the API also takes options to set which type of shape annotation tool the style applies to, the title that will be shown in the style dropdown, an SVG string that will be displayed in the dropdown, a flag to use a custom function for clipping the fill to shape, and the actual custom function to be used in place of the default fill clipping function. Re-registering a new custom style with a previously used and active key will override the key's previously associated handler and options and replace them with the new draw handler and options.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
// Registers a custom cross fill hatch with blue and orange lines
UI.registerCustomFillStyle({
  key: 'crosshatch-fill',
  appliesTo: ['rectangle', 'ellipse', 'polygon'],
  title: 'Crosshatch Fill',
  svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 24"><path d="M-4 24L20 0M8 24L32 0M20 24L44 0M-4 0L20 24M8 0L32 24M20 0L44 24" stroke="currentColor" stroke-width="2"/></svg>',
  drawHandler: (ctx, pageMatrix, rotation, { annotation, originalDrawWithoutFill, clipToShape }) => {
    originalDrawWithoutFill();
    ctx.save();
    clipToShape();
    ctx.globalAlpha = annotation.Opacity ?? 1;
    ctx.lineWidth = Math.max(1, (annotation.StrokeThickness || 1) / 2);
    ctx.translate(annotation.X + annotation.Width / 2, annotation.Y + annotation.Height / 2);
    ctx.rotate((rotation * Math.PI) / 180);
    
    const extent = Math.hypot(annotation.Width, annotation.Height);
    const spacing = 10;
    [
      { angle: Math.PI / 4, color: '#2A6F97' },
      { angle: -Math.PI / 4, color: '#E76F51' },
    ].forEach(({ angle, color }) => {
      ctx.save();
      ctx.rotate(angle);
      ctx.strokeStyle = color;
      ctx.beginPath();
      for (let offset = -extent; offset <= extent; offset += spacing) {
        ctx.moveTo(offset, -extent);
        ctx.lineTo(offset, extent);
      }
      ctx.stroke();
      ctx.restore();
    });
    ctx.restore();
  },
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

<div align="left"><figure><img src="https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2FA0XdD3ZsdJmOwlItQXqA%2F71fedff32415b1de413a1d9d7fbf4f96a0ee5077-462x378.png?alt=media&#x26;token=1b1a9f38-1b18-4a4a-9af8-a19d433ead54" alt="Rectangle, ellipse, and triangle annotations filled with a blue and orange diagonal crosshatch"><figcaption><p>Shape annotations with the crosshatch fill style</p></figcaption></figure></div>

### Remove custom styles

To unregister currently registered custom line and fill styles, you can use the [unregisterCustomLineStyle](https://sdk.apryse.com/api/web/UI.html#.unregisterCustomLineStyle__anchor) and [unregisterCustomFillStyle](https://sdk.apryse.com/api/web/UI.html#.unregisterCustomFillStyle__anchor) APIs, respectively, which will clear the registered custom styles and their associated draw handlers.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
// Unregisters H Line Style
UI.unregisterCustomLineStyle('h-pattern-text');

// Unregisters Dot Fill Style
UI.unregisterCustomFillStyle('fill-dots');
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Next steps

Check out how to [customize](/web/annotation/customize/customize-serialization.md) the serialization/deserialization behavior of annotations to save your custom properties.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.apryse.com/web/annotation/customize/customize-rendering.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
