How to create tools for insert and replace text

212 views
Skip to first unread message

Matt Parizeau

unread,
Jul 28, 2020, 4:28:08 PM7/28/20
to pdfnet-w...@googlegroups.com
Q:

Is it possible to have tools like Adobe's insert text and replace text tools that create caret annotations?

A:

Yes, the following code will work with WebViewer 7.0. For older versions the allQuads index values will be 0-indexed, so add one to the page number.

const { Annotations, Tools, annotManager, docViewer } = instance;

// shared helper function that creates caret annotation at the end of the selection
const createCaretAnnotation = (quads, data) => {
  const lastQuad = quads[quads.length - 1];

  const quadHeight = Math.abs(lastQuad.y2 - lastQuad.y3);
  const caretSize = quadHeight / 2;

  const caret = new Annotations.CaretAnnotation();
  caret.PageNumber = data.pageNumber;
  // position center of caret at the edge of strikeout
  caret.X = lastQuad.x3 - (caretSize / 2);

  // center relative to the strikeout
  caret.Y = lastQuad.y2 - caretSize;
  caret.Width = caretSize;
  caret.Height = caretSize;
  caret.Author = annotManager.getCurrentUser();
  caret.StrokeColor = new Annotations.Color(0, 0, 255);

  annotManager.addAnnotation(caret);
  if (data.annotation) {
    annotManager.groupAnnotations(data.annotation, [caret]);
  }
  annotManager.redrawAnnotation(caret);
  return caret;
};

// Replace Text Tool
const ReplaceTextTool = function() {
  Tools.TextStrikeoutCreateTool.apply(this, arguments);
};

ReplaceTextTool.prototype = new Tools.TextStrikeoutCreateTool();

const replaceToolName = 'ReplaceTextTool';

const replaceTextTool = new ReplaceTextTool(docViewer);
replaceTextTool.on('annotationAdded', function(annotation) {
  // adds the caret annotation when the strikeout is added with the custom tool
  createCaretAnnotation(annotation.Quads, {
    annotation,
    pageNumber: annotation.PageNumber
  });
  instance.focusNote(annotation.Id);
});

instance.registerTool({
  toolName: replaceToolName,
  toolObject: replaceTextTool,
  buttonImage: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">' +
    '<path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/>' +
    '<path d="M0 0h24v24H0z" fill="none"/>' +
  '</svg>',
  buttonName: 'replaceTextToolButton',
  tooltip: 'Replace Text'
}, Annotations.CaretAnnotation);

instance.setHeaderItems(function(header) {
  const replaceTextButton = {
    type: 'toolButton',
    toolName: replaceToolName
  };
  header.push(replaceTextButton);
});


// Insert Text Tool
const InsertTextTool = function() {
  Tools.TextSelectTool.apply(this, arguments);
};

InsertTextTool.prototype = new Tools.TextSelectTool();

InsertTextTool.prototype.switchIn = function() {
  Tools.TextSelectTool.prototype.switchIn.apply(this, arguments);
  Tools.Tool.ENABLE_AUTO_SWITCH = false;
};

InsertTextTool.prototype.switchOut = function() {
  Tools.TextSelectTool.prototype.switchOut.apply(this, arguments);
  Tools.Tool.ENABLE_AUTO_SWITCH = true;
};

const insertToolName = 'InsertTextTool';

const insertTextTool = new InsertTextTool(docViewer);
insertTextTool.on('selectionComplete', (startLocation, allQuads) => {
  const selectedPageNumbers = Object.keys(allQuads);
  const pageNumber = selectedPageNumbers[selectedPageNumbers.length - 1];

  const caret = createCaretAnnotation(allQuads[pageNumber], { pageNumber });
  instance.focusNote(caret.Id);
});

instance.registerTool({
  toolName: insertToolName,
  toolObject: insertTextTool,
  buttonImage: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">' +
    '<path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/>' +
    '<path d="M0 0h24v24H0z" fill="none"/>' +
  '</svg>',
  buttonName: 'insertTextToolButton',
  tooltip: 'Insert Text'
}, Annotations.CaretAnnotation);

instance.setHeaderItems(function(header) {
  const insertTextButton = {
    type: 'toolButton',
    toolName: insertToolName
  };
  header.push(insertTextButton);
});


Tomasz Poradowski

unread,
Aug 12, 2020, 5:49:35 PM8/12/20
to PDFTron WebViewer
Hi Matt,

Thanks for this sample code. To make it correctly work in 7.0 - call to registerTool()  must include third parameter, which is a "customAnnotationCheckFunc" - a function that determines if given annotation was created by the tool that is being registered. Without it - showing annotation panel crashes the viewer.
There is also one more thing missing (or something has changed in 7.0?) - after adding one of those annotations user should be redirected to annotation panel to enter the text (to be inserted/replaced). How to achieve that?

Regards,
Tomasz Poradowski

};

// Replace Text Tool
const ReplaceTextTool = function() {
  Tools.TextStrikeoutCreateTool.apply(this, arguments);
};

ReplaceTextTool.prototype = new Tools.TextStrikeoutCreateTool();

const replaceToolName = 'ReplaceTextTool';

const replaceTextTool = new ReplaceTextTool(docViewer);
replaceTextTool.on('annotationAdded', function(annotation) {
  // adds the caret annotation when the strikeout is added with the custom tool
  createCaretAnnotation(annotation.Quads, {
    annotation,
    pageNumber: annotation.PageNumber
  });
});

instance.registerTool({
  toolName: replaceToolName,
  toolObject: replaceTextTool,
  buttonImage: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">' +
    '<path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/>' +
    '<path d="M0 0h24v24H0z" fill="none"/>' +
  '</svg>',
  buttonName: 'replaceTextToolButton',
  tooltip: 'Replace Text'
});

instance.setHeaderItems(function(header) {
  const replaceTextButton = {
    type: 'toolButton',
    toolName: replaceToolName
  };
  header.push(replaceTextButton);
});


// Insert Text Tool
const InsertTextTool = function() {
  Tools.TextSelectTool.apply(this, arguments);
};

InsertTextTool.prototype = new Tools.TextSelectTool();

InsertTextTool.prototype.switchIn = function() {
  Tools.TextSelectTool.prototype.switchIn.apply(this, arguments);
  Tools.Tool.ENABLE_AUTO_SWITCH = false;
};

InsertTextTool.prototype.switchOut = function() {
  Tools.TextSelectTool.prototype.switchOut.apply(this, arguments);
  Tools.Tool.ENABLE_AUTO_SWITCH = true;
};

const insertToolName = 'InsertTextTool';

const insertTextTool = new InsertTextTool(docViewer);
insertTextTool.on('selectionComplete', (startLocation, allQuads) => {
  const selectedPageNumbers = Object.keys(allQuads);
  const pageNumber = selectedPageNumbers[selectedPageNumbers.length - 1];

  createCaretAnnotation(allQuads[pageNumber], { pageNumber });
});

instance.registerTool({
  toolName: insertToolName,
  toolObject: insertTextTool,
  buttonImage: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">' +
    '<path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/>' +
    '<path d="M0 0h24v24H0z" fill="none"/>' +
  '</svg>',
  buttonName: 'insertTextToolButton',
  tooltip: 'Insert Text'

Matt Parizeau

unread,
Aug 12, 2020, 9:32:21 PM8/12/20
to PDFTron WebViewer
Hi Tomasz,

Thanks for the catch on the customAnnotationCheckFunc. I've updated it to include a second parameter to registerTool but we'll also look into fixing that error since it shouldn't error out just because the parameter wasn't added since it's supposed to be optional.

I've also updated the code snippet in the original post so that it calls instance.focusNote to open the notes panel to enter text https://www.pdftron.com/api/web/WebViewerInstance.html#focusNote

Let me know if that works for you.

Matt Parizeau
Software Developer
PDFTron Systems Inc.
Message has been deleted

Daniel Martín García

unread,
Jan 25, 2021, 2:31:23 PM1/25/21
to PDFTron WebViewer
Hi Matt,

I hope you are well.

I have tried to use both tools but the issue is if the page rotation is not 0 degrees, they are not working properly. Is there any way to support those cases?

Thanks

EIDA Software Solutions Limited trading as EIDA Solutions.
Registered Office: Suite One, The Avenue Beacon Court, Bracken Road, Sandyford, Co. Dublin, Ireland
Registered Number: 490710 Ireland

DISCLAIMER: The information contained in this email and in any attachments is confidential and is designated solely for the attention and use of the intended recipient(s). If you are not the intended recipient(s) of this email, you must not use, disclose, copy, distribute or retain this message, the attachment(s) or any part thereof. If you believe that you have received this email in error, please notify the sender. Please also delete all copies of this email and any attachment(s) from your computer system.

Unless expressly stated, this email is not intended to create any contractual relationship. If this email is not sent in the course of the senders employment or fulfillment of his/her duties to EIDA Solutions, EIDA Solutions accepts no liability whatsoever for the content of this message or any attachment(s). Any views or opinions expressed are solely those of the author and do not necessarily represent those of "EIDA Solutions Limited"

Matt Parizeau

unread,
Jan 27, 2021, 4:31:16 PM1/27/21
to PDFTron WebViewer
Hi Daniel,

Thanks for pointing out the issue with rotation. Here's an updated implementation of createCaretAnnotation that takes into account the rotation of the text. Let me know if it's working on your end.

const createCaretAnnotation = (quads, data) => {
  const lastQuad = quads[quads.length - 1];

  const maxY = Math.max(lastQuad.y1, lastQuad.y2, lastQuad.y3, lastQuad.y4);
  const minY = Math.min(lastQuad.y1, lastQuad.y2, lastQuad.y3, lastQuad.y4);

  const maxX = Math.max(lastQuad.x1, lastQuad.x2, lastQuad.x3, lastQuad.x4);
  const minX = Math.min(lastQuad.x1, lastQuad.x2, lastQuad.x3, lastQuad.x4);

  let textRotation;
  if (lastQuad.x1 === minX && lastQuad.y1 === maxY) {
    textRotation = 0;
  } else if (lastQuad.x1 === maxX && lastQuad.y1 === maxY) {
    textRotation = 90;
  } else if (lastQuad.x1 === maxX && lastQuad.y1 === minY) {
    textRotation = 180;
  } else if (lastQuad.x1 === minX && lastQuad.y1 === minY) {
    textRotation = 270;
  }

  const caret = new Annotations.CaretAnnotation();
  let quadHeight;

  if (textRotation === 0 || textRotation === 180) {
    quadHeight = maxY - minY;
  } else {
    quadHeight = maxX - minX;
  }
  const caretSize = quadHeight / 2;
  caret.PageNumber = data.pageNumber;
  // position center of caret at the edge of strikeout
  // center relative to the strikeout
  if (textRotation === 0) {
    caret.X = maxX - (caretSize / 2);
    caret.Y = maxY - caretSize;
  } else if (textRotation === 90) {
    caret.X = maxX - caretSize;
    caret.Y = minY - (caretSize / 2);
  } else if (textRotation === 180) {
    caret.X = minX - (caretSize / 2);
    caret.Y = minY;
  } else if (textRotation === 270) {
    caret.X = minX;
    caret.Y = maxY - (caretSize / 2);
  }

  caret.Width = caretSize;
  caret.Height = caretSize;
  caret.Author = annotManager.getCurrentUser();
  caret.StrokeColor = new Annotations.Color(0, 0, 255);
  caret.Rotation = textRotation;

  annotManager.addAnnotation(caret);
  if (data.annotation) {
    annotManager.groupAnnotations(data.annotation, [caret]);
  }
  annotManager.redrawAnnotation(caret);
  return caret;
};

Matt Parizeau
Software Developer
PDFTron Systems Inc.

Reply all
Reply to author
Forward
0 new messages