Tool to create links

415 views
Skip to first unread message

Matt Parizeau

unread,
Oct 22, 2014, 8:32:02 PM10/22/14
to pdfnet-w...@googlegroups.com
Q:

We want to have a tool, through which users can draw an overlay over the text through some interface and give reference to URL in case of external link or reference to page in case of internal link. Is this possible?

A:

One way you could do this would be to create a new custom tool that extends from a text annotation tool and this way it automatically handles the selection of text on the page. You could then customize the look of the annotation popup to allow users to enter the URL or create an internal link. Here is some sample code that you could put in a WebViewer config file. It will create a button in the annotation tab in the left side panel. With that tool you can highlight text and enter a URL in the box. You'll have to call the createLinks function from somewhere (e.g. button click) and after pressing it the annotations will turn into links you can click. You'll have to make more modifications to make internal links but this should get you started.

// custom tool that just marks the created annotation as being a link
var CustomLinkTool = function(docViewer) {
   
Tools.TextHighlightCreateTool.call(this, docViewer);
};

CustomLinkTool.prototype = new Tools.TextHighlightCreateTool();
CustomLinkTool.prototype.mouseLeftDown = function() {
   
Tools.TextHighlightCreateTool.prototype.mouseLeftDown.apply(this, arguments);

   
if (this.annotation) {
       
this.annotation.isLink = true;
   
}
};


$(document).on('documentLoaded', function() {
   
// add a button to the annotation tools list in the annotation tab
    $
("#toolModePicker").append('<li id="create-link" class="glyphicons image-icon" data-toolmode="CreateLink" title="Create Link"><img src="my_tool_image.png"/></li>');
    readerControl
.toolModeMap['CreateLink'] = CustomLinkTool;

   
var annotManager = readerControl.docViewer.GetAnnotationManager();
    annotManager
.on('annotationPopupCreated', function(e, annotation, $popupel, $textarea) {
       
if (annotation.isLink) {
           
// open the popup automatically after creating
            annotation
.getPopup().setOpen(true);
            annotManager
.RedrawAnnotation(annotation);
           
           
// customizations to popup here, for example
            $popupel
.find('.replyButton').remove();
            $popupel
.find('.popup-text').remove();
            $popupel
.find('.popup-subject').text('Create Link');
            $textarea
.attr('placeholder', 'Enter URL here...');
       
}
   
});
});


// function to call when you want to turn the annotations into actual links
// could call this from a button click handler for example
function createLinks() {
   
var doc = readerControl.docViewer.GetDocument();

   
var annotManager = readerControl.docViewer.GetAnnotationManager();
   
var linkAnnots = annotManager.GetAnnotationsList().filter(function(annot) {
       
return annot.isLink;
   
});

    linkAnnots
.forEach(function(annot) {
       
var pageIndex = annot.PageNumber - 1;
       
var pageLinks = doc.GetLinks(pageIndex);

       
// there could be more than one quad in the annotation (e.g. multiple lines)
        annot
.Quads.forEach(function(quad) {
           
var topLeft = doc.GetPDFCoordinates(pageIndex, quad.x1, quad.y1);
           
var bottomRight = doc.GetPDFCoordinates(pageIndex, quad.x3, quad.y3);
           
           
// set the URL to be the "contents" of the annotation which is the value in the text box
           
var externalLink = new CoreControls.Hyperlink([topLeft.x, topLeft.y, bottomRight.x, bottomRight.y], annot.getContents());
            pageLinks
.push(externalLink);
       
});
   
});

   
// find the pages that have new links on them and update those
    _
.uniq(linkAnnots.map(function(annot) {
       
return annot.PageNumber - 1;
   
})).forEach(function(pageIndex) {
        readerControl
.docViewer.UpdateLinks(pageIndex);
   
});

   
// switch to pan tool because links are disabled from clicking when in annotationt tool modes
    readerControl
.docViewer.SetToolMode(Tools.PanEditTool);

   
// if you want the annotations to be removed and the links to be shown
    annotManager
.DeleteAnnotations(linkAnnots);
}



prakhar jaiswal

unread,
Feb 26, 2016, 1:23:00 PM2/26/16
to PDFTron WebViewer
Hi Matt,

Could you help with a sample implementation that goes with the latest version of API. Thanks in advance
Message has been deleted

Matt Bojey

unread,
Feb 26, 2016, 3:07:54 PM2/26/16
to PDFTron WebViewer
// custom tool that just marks the created annotation as being a link
var CustomLinkTool = function(docViewer) {
   
Tools.TextHighlightCreateTool.call(this, docViewer);
};

CustomLinkTool.prototype = new Tools.TextHighlightCreateTool();
CustomLinkTool.prototype.mouseLeftDown = function() {
   
Tools.TextHighlightCreateTool.prototype.mouseLeftDown.apply(this, arguments);


   
if (this.annotation) {

       
this.annotation.isMyLink = true;

   
}
};

$
(document).on('documentLoaded', function() {
   
// add a button to the annotation tools list in the annotation tab

    $
('#overflowToolsContainer').prepend('<span id="create-link" class="glyphicons image-icon" data-toolmode="CreateLink" title="Create Link"><img src="triangle-tool.png"/></span>');
    readerControl
.toolModeMap['CreateLink'] = new CustomLinkTool(readerControl.docViewer);


   
var annotManager = readerControl.docViewer.getAnnotationManager();

});

// function to call when you want to turn the annotations into actual links
// could call this from a button click handler for example
function createLinks() {

   
var doc = readerControl.docViewer.getDocument();

   
var annotManager = readerControl.docViewer.getAnnotationManager();
   
var linkAnnots = annotManager.getAnnotationsList().filter(function(annot) {
       
return annot.isMyLink;
   
});

   
var linkArr = [];
    linkAnnots
.forEach(function(annot) {

       
// there could be more than one quad in the annotation (e.g. multiple lines)
        annot
.Quads.forEach(function(quad) {

           
var options = {};
            options
.rect = [Math.min(quad.x1, quad.x3), Math.min(quad.y1, quad.y3), Math.max(quad.x1, quad.x3), Math.max(quad.y1, quad.y3)]
           
var link = new Annotations.Link(options);
            link
.PageNumber = annot.PageNumber;
           
// This is set from whatever the contents of the annotation are in the Notes Panel at the time this function is called.
            link
.addAction('U', new Actions.URI({uri: annot.getContents()}))
            linkArr
.push(link);
       
});
   
});
    annotManager
.addAnnotations(linkArr);

   
// Redraw all the new annotations
    annotManager
.drawAnnotationsFromList(linkArr);


   
// switch to pan tool because links are disabled from clicking when in annotationt tool modes

    readerControl
.docViewer.setToolMode(Tools.PanEditTool);


   
// if you want the annotations to be removed and the links to be shown

    annotManager
.deleteAnnotations(linkAnnots);
}

This will create a link to whatever the text is in the notes panel with WebViewer 2.1.

Matt

prakhar jaiswal

unread,
Feb 29, 2016, 1:22:52 PM2/29/16
to PDFTron WebViewer
Thanks a lot Matt for the swift response!


On Thursday, 23 October 2014 06:02:02 UTC+5:30, Matt Parizeau wrote:

prakhar jaiswal

unread,
Mar 2, 2016, 11:56:02 AM3/2/16
to PDFTron WebViewer
Hi Matt,

For some reason in the above sample, the link URI tag in the XFDF format is being published with attribute Name="URI", even though the highlight annotation which we extended to create the link annotation get the URI properly. Sample below:
<?xml version="1.0" encoding="UTF-8" ?><xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve"><pdf-info xmlns="http://www.pdftron.com/pdfinfo" version="1" /><fields /><annots><highlight subject="Highlight" page="0" rect="199.265046,323.35272,391.735841,419.331765" flags="print" name="b1acedde-c8aa-38dc-2dbc-81a37ea422eb" title="admin" date="D:20160302081944+05'30'" color="#FFFF00" opacity="0.25" creationdate="D:20160302081934+05'30'" coords="199.27,419.33,391.74,419.33,199.27,323.35,391.74,323.35"><contents-richtext><body xmlns="http://www.w3.org/1999/xhtml" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/" xfa:spec="2.0.2" xfa:APIVersion="Acrobat:10.1.3"><p dir="ltr"><span dir="ltr" style="font-size:10.0pt;text-align:left;color:#000000;font-weight:normal;font-style:normal">http://www.google.co.in</span></p></body></contents-richtext><contents>http://www.google.co.in</contents></highlight><link subject="Annotation" page="0" rect="199.265046,323.35272,391.735841,419.331765" flags="print" name="a5d1b837-9c09-c2f0-16db-ef5219210800" date="D:20160302081947+05'30'" color="#000000"><OnActivation><Action Trigger="U"><URI Name="URI"/></Action></OnActivation></link></annots><pages><defmtx matrix="1,0,0,-1,0,792" /></pages></xfdf>

Is this the normal behavior for trial version or am I doing something wrong here?

One more question, is there a documentation on how PDFTron attributes are being mapped to XFDF counterparts?

For eg: in the above sample snippet 'uri' attribute maps to 'Name' (I assumed it since, I could not find a documentation explaining what are option attributes for Actions.URI constructor)

link.addAction('U', new Actions.URI({uri: annot.getContents()}))



...

Matt Bojey

unread,
Mar 2, 2016, 2:20:09 PM3/2/16
to PDFTron WebViewer
Can you send the exact code you are using?  It looks like you have made some changes from the code I provided, the highlight annotation should not be there if you are using the code I provided.  

Here is a link to the XFDF spec https://partners.adobe.com/public/developer/en/xml/XFDF_Spec_3.0.pdf that should help you find out what parameters we are using.  We don't have any documentation on the mapping between what we use and the spec.

Matt
...

prakhar jaiswal

unread,
Mar 2, 2016, 2:58:15 PM3/2/16
to PDFTron WebViewer
Config.js:

(function(){
    'use strict';
    var toolname = 'createlink';

    $(document).on('viewerLoaded', function(){ 

        console.log("Viewer loaded");
        var docViewer = readerControl.getDocumentViewer(), annotManager = docViewer.getAnnotationManager();
        // Default stroke color for freehandtool
        readerControl.toolModeMap[PDFTron.WebViewer.ToolMode.AnnotationCreateFreeHand].defaults.StrokeColor = new Annotations.Color(255, 0, 0);
        // Wrap it in timeout in order for notespanel to have time to bootup and start listening to events
        setTimeout(function(){
                readerControl.showNotesPanel(true);
                readerControl.fireEvent('notesPanelVisibilityChanged', true);
        }, 250);
    // function load_custom_link_aka_hotspotting_annotation_types(){
        // custom tool that just marks the created annotation as being a link
        var CustomLinkTool = function(docViewer) {
            Tools.TextHighlightCreateTool.call(this, docViewer);
        };

        CustomLinkTool.prototype = new Tools.TextHighlightCreateTool();
        CustomLinkTool.prototype.mouseLeftDown = function() {
            Tools.TextHighlightCreateTool.prototype.mouseLeftDown.apply(this, arguments);

            if (this.annotation) {
                this.annotation.isMyLink = true;
            }
        };

        var CustomLinkAnnotation = function(){
            console.log("Annotation instance created");
            this.Subject = "Hotspot Area";
            Annotations.MarkupAnnotation.call(this);
            
        };
        CustomLinkAnnotation.prototype  = $.extend(new Annotations.MarkupAnnotation(), {
            elementName : toolname,
            serialize: function(element, pageMatrix) {
                console.log("serialize invoked");
                var el = Annotations.MarkupAnnotation.prototype.serialize.call(this, element, pageMatrix);
                $(el).attr("ismylink", true);
                return el;
            },
            deserialize: function(element, pageMatrix) {
                console.log("deserialize invoked");
                Annotations.MarkupAnnotation.prototype.deserialize.call(this, element, pageMatrix);
                // this.isMyLink = $(element).attr("ismylink");
                this.isMyLink = true;
            }
        });

        $(document).on('documentLoaded', function() {
            var annotManager = readerControl.docViewer.getAnnotationManager();
            console.log("Document loaded");
            annotManager.registerAnnotationType("createlink", CustomLinkAnnotation);
            
            readerControl.toolModeMap[toolname] = new CustomLinkTool(readerControl.docViewer);
            window.ControlUtils.userPreferences.registerTool(readerControl.toolModeMap[toolname], toolname, CustomLinkAnnotation);

            $('#overflowToolsContainer').prepend('<span id="create-link" class="annotTool glyphicons link" data-toolmode="' + toolname + '" title="Create Link"></span>');
        });

    });

})();

And on temporary basis I overrode the saveAnnotations method of readerControl.js to invoke  the specified create_links method as IIFE:

(function() {
            console.log("Create links invoked");
            var doc = readerControl.docViewer.getDocument();

            var annotManager = readerControl.docViewer.getAnnotationManager();
            var isURI = function(string){
                return string && string.match("^http"); //For testing purpose, should do for now
            };
            var linkAnnots = annotManager.getAnnotationsList().filter(function(annot) {
                return annot.isMyLink;
            });

            console.log("Filtered highlight annotations with link: ", linkAnnots);

            var linkArr = [];
            var linkAdded = false;
            linkAnnots.forEach(function(annot) {

                if(isURI(annot.getContents())){
                // there could be more than one quad in the annotation (e.g. multiple lines)
                    annot.Quads.forEach(function(quad) {

                        var options = {};
                        options.rect = [Math.min(quad.x1, quad.x3), Math.min(quad.y1, quad.y3), Math.max(quad.x1, quad.x3), Math.max(quad.y1, quad.y3)]
                        var link = new Annotations.Link(options);
                        link.PageNumber = annot.PageNumber;
                        // This is set from whatever the contents of the annotation are in the Notes Panel at the time this function is called.
                        console.log("Url to which link is being created: ",annot.getContents() );
                        link.addAction('U', new Actions.URI({uri: annot.getContents()}))
                        linkArr.push(link);
                    });
                    linkAdded =true;
                    console.log("link added");
                }else{
                    console.log("Was not a  proper uri")
                }
            });
            
            if(linkAdded){    
                annotManager.drawAnnotationsFromList(linkArr);
                annotManager.addAnnotations(linkArr);
                readerControl.docViewer.setToolMode(Tools.PanEditTool);
                annotManager.deleteAnnotations(linkAnnots);
            }
        })();


Heads-up: I had commented out the line to persist the highlight annotation as well:
annotManager.deleteAnnotations(linkAnnots);
Not sure, if impacts the link annotation anyway. I tested with uncommented line but the behavior remains the same i.e. the URI Name attribute is still not populated.

Off note: Thanks for helping out to so much extent. Appreciate it from the core. Loving the responsiveness in the forum!

Matt Bojey

unread,
Mar 2, 2016, 3:32:50 PM3/2/16
to PDFTron WebViewer
When I use the code you provided and run 
readerControl.docViewer.getAnnotationManager().exportAnnotations()
in the console I get the expected XFDF.  Are you using something else to generate the XFDF?  If you use this it should work and have the URI provided as the 'name' attribute on the link annotation.

Matt
...
Reply all
Reply to author
Forward
0 new messages