How to create custom SelectionModel similar to the default 8 control handles

PDF.js Express Version

Detailed description of issue
I want to create a custom selection model based on the default 8 point selection model which is associated to FreeTextAnnotation. I need only 1 bottom right control handle, have seen the custom annotation sample of triangle but could do much. Please guide.

Expected behaviour
{Provide a screenshot or description of the expected behaviour}

Does your issue happen with every document, or just one?
{Answer here}

Link to document
{Provide a link to the document in question if possible}

Code snippet
{Provide a relevant code snippet}

Hello, I’m Ron, an automated tech support bot :robot:

While you wait for one of our customer support representatives to get back to you, please check out some of these documentation pages:



Just wanted to let you know we have not forgot about your question. I’m trying to find the best way to accomodate this use case - I will send you some code today. Selection models are pretty complex.

Thanks for your patience,

Hi there,

Sorry for the delay again. Very busy this time of month!

Here is some code that creates a custom selection model with only one control point on the bottom right. Admittedly, our APIs for this kind of thing aren’t the best so this code is a bit gross, but we will be working on improving these APIs in the future.

  const { Annotations, annotManager } = instance;

  const CustomFreeTextControlHandle = function (annotation) {
    this.annotation = annotation;

  CustomFreeTextControlHandle.prototype = new Annotations.ControlHandle();

  CustomFreeTextControlHandle.prototype.getDimensions = function (annotation, _, zoom) {
    let x = annotation.X + annotation.Width;
    let y = annotation.Y + annotation.Height;

    // Use the default width and height
    const width = Annotations.ControlHandle.handleWidth / zoom;
    const height = Annotations.ControlHandle.handleHeight / zoom;
    // adjust for the control handle's own width and height
    x -= width * 0.5;
    y -= height * 0.5;
    return new Annotations.Rect(x, y, x + width, y + height);

  CustomFreeTextControlHandle.prototype.move = function (annotation, deltaX, deltaY, fromPoint, toPoint) {
    annotation.Width += deltaX
    annotation.Height += deltaY

    const x1 = annotation.X;
    const x2 = annotation.X + annotation.Width

    const y1 = annotation.Y;
    const y2 = annotation.Y + annotation.Height;

    const rect = new Annotations.Rect(x1, y1, x2, y2);
    return true;

  const CustomFreeTextSelectionModel = function (annotation, canModify) {, annotation, canModify);
    if (canModify) {
      const controlHandles = this.getControlHandles();
      controlHandles.push(new CustomFreeTextControlHandle(annotation));
  CustomFreeTextSelectionModel.prototype = new Annotations.SelectionModel();

  Annotations.FreeTextAnnotation.prototype.selectionModel = CustomFreeTextSelectionModel;

  annotManager.on('annotationChanged', (annots, action)  => {
    if (action === 'add') {
      const freeText = annots.filter(annot => annot instanceof Annotations.FreeTextAnnotation && annot.getIntent() !== Annotations.FreeTextAnnotation.Intent['FreeTextCallout'])
      for (const annot of freeText) {
        annot.selectionModel = CustomFreeTextSelectionModel

Let me know if you have any questions.


Thanks Logan, exactly what I needed.

1 Like