Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable automatic URL linking #19110

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
18 changes: 16 additions & 2 deletions web/annotation_layer_builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
// eslint-disable-next-line max-len
/** @typedef {import("../src/display/editor/tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */

import { AnnotationLayer } from "pdfjs-lib";
import { AnnotationLayer, Util } from "pdfjs-lib";
import { PresentationModeState } from "./ui_utils.js";

/**
Expand Down Expand Up @@ -97,7 +97,7 @@ class AnnotationLayerBuilder {
* @returns {Promise<void>} A promise that is resolved when rendering of the
* annotations is complete.
*/
async render(viewport, options, intent = "display") {
async render(viewport, options, intent = "display", linkAnnotations) {
if (this.div) {
if (this._cancelled || !this.annotationLayer) {
return;
Expand All @@ -119,6 +119,20 @@ class AnnotationLayerBuilder {
return;
}

const uniqueLinks = linkAnnotations.filter(link => {
for (const annotation of annotations) {
if (
annotation.subtype === "Link" &&
annotation.url === link.url &&
Util.intersect(annotation.rect, link.rect) !== null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to avoid some corner case bug, maybe a better way to do would be to compute the ratio area(intersection) / area(annotation.rect) and if it's greater than a threshold then consider that links are very likely the same.

) {
return false;
}
}
return true;
});
annotations.push(...uniqueLinks);

// Create an annotation layer div and render the annotations
// if there is at least one annotation.
const div = (this.div = document.createElement("div"));
Expand Down
1 change: 1 addition & 0 deletions web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ const PDFViewerApplication = {
abortSignal: this._globalAbortController.signal,
enableHWA,
supportsPinchToZoom: this.supportsPinchToZoom,
enableAutolinking: AppOptions.get("enableAutolinking"),
});
this.pdfViewer = pdfViewer;

Expand Down
6 changes: 6 additions & 0 deletions web/app_options.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ const defaultOptions = {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
},
enableAutolinking: {
// TODO: remove it when unnecessary.
/** @type {boolean} */
value: false,
kind: OptionKind.VIEWER,
},
externalLinkRel: {
/** @type {string} */
value: "noopener noreferrer nofollow",
Expand Down
94 changes: 94 additions & 0 deletions web/autolinker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { createValidAbsoluteUrl, Util } from "../src/shared/util.js";
import { getOriginalIndex, normalize } from "./pdf_find_controller.js";

class Autolinker {
static #urlRegex =
/\b(?:https?:\/\/|mailto:|www.)(?:[[\S--\[]--\p{P}]|\/|[\p{P}--\[]+[[\S--\[]--\p{P}])+/gmv;
Dismissed Show dismissed Hide dismissed

static #addLinkAnnotations(url, index, length, pdfPageView) {
// TODO refactor out the logic for a single match from this function
const convertedMatch = pdfPageView._textHighlighter._convertMatches(
[index],
[length]
)[0];

const range = new Range();
range.setStart(
pdfPageView._textHighlighter.textDivs[convertedMatch.begin.divIdx]
.firstChild,
convertedMatch.begin.offset
);
range.setEnd(
pdfPageView._textHighlighter.textDivs[convertedMatch.end.divIdx]
.firstChild,
convertedMatch.end.offset
);

const pageBox = pdfPageView.textLayer.div.getBoundingClientRect();
const linkAnnotations = [];
for (const linkBox of range.getClientRects()) {
if (linkBox.width === 0 || linkBox.height === 0) {
continue;
}

const bottomLeft = pdfPageView.getPagePoint(
linkBox.left - pageBox.left,
linkBox.top - pageBox.top
);
const topRight = pdfPageView.getPagePoint(
linkBox.left - pageBox.left + linkBox.width,
linkBox.top - pageBox.top + linkBox.height
);

const rect = Util.normalizeRect([
bottomLeft[0],
bottomLeft[1],
topRight[0],
topRight[1],
]);

linkAnnotations.push({
unsafeUrl: url,
url,
rect,
annotationType: 2,
rotation: 0,
// This is just the default for AnnotationBorderStyle. At some point we
// should switch to something better like `new LinkAnnotation` here.
borderStyle: {
width: 1,
rawWidth: 1,
style: 1, // SOLID
dashArray: [3],
horizontalCornerRadius: 0,
verticalCornerRadius: 0,
},
});
}
return linkAnnotations;
}

static processLinks(pdfPageView) {
const [text, diffs] = normalize(
pdfPageView._textHighlighter.textContentItemsStr.join("\n")
);
const matches = text.matchAll(Autolinker.#urlRegex);
const links = [];
for (const match of matches) {
const url = createValidAbsoluteUrl(match[0]);
if (url) {
const [index, length] = getOriginalIndex(
diffs,
match.index,
match[0].length
);
links.push(
...this.#addLinkAnnotations(url.href, index, length, pdfPageView)
);
}
}
return links;
}
}

export { Autolinker };
2 changes: 1 addition & 1 deletion web/pdf_find_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -1171,4 +1171,4 @@ class PDFFindController {
}
}

export { FindState, PDFFindController };
export { FindState, getOriginalIndex, normalize, PDFFindController };
15 changes: 13 additions & 2 deletions web/pdf_page_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
import { AnnotationEditorLayerBuilder } from "./annotation_editor_layer_builder.js";
import { AnnotationLayerBuilder } from "./annotation_layer_builder.js";
import { AppOptions } from "./app_options.js";
import { Autolinker } from "./autolinker.js";
import { DrawLayerBuilder } from "./draw_layer_builder.js";
import { GenericL10n } from "web-null_l10n";
import { SimpleLinkService } from "./pdf_link_service.js";
Expand Down Expand Up @@ -120,6 +121,8 @@ class PDFPageView {

#enableHWA = false;

#enableAutolinking = false;

#hasRestrictedScaling = false;

#isEditing = false;
Expand Down Expand Up @@ -148,6 +151,8 @@ class PDFPageView {
regularAnnotations: true,
};

#linkAnnotations = [];

#layers = [null, null, null, null];

/**
Expand Down Expand Up @@ -177,6 +182,7 @@ class PDFPageView {
options.maxCanvasPixels ?? AppOptions.get("maxCanvasPixels");
this.pageColors = options.pageColors || null;
this.#enableHWA = options.enableHWA || false;
this.#enableAutolinking = options.enableAutolinking || false;

this.eventBus = options.eventBus;
this.renderingQueue = options.renderingQueue;
Expand Down Expand Up @@ -399,7 +405,8 @@ class PDFPageView {
await this.annotationLayer.render(
this.viewport,
{ structTreeLayer: this.structTreeLayer },
"display"
"display",
this.#linkAnnotations
);
} catch (ex) {
console.error("#renderAnnotationLayer:", ex);
Expand Down Expand Up @@ -1086,9 +1093,13 @@ class PDFPageView {
viewport.rawDims
);

this.#renderTextLayer();
const textLayerP = this.#renderTextLayer();

if (this.annotationLayer) {
if (this.#enableAutolinking) {
await textLayerP;
this.#linkAnnotations = Autolinker.processLinks(this);
}
await this.#renderAnnotationLayer();
}

Expand Down
4 changes: 4 additions & 0 deletions web/pdf_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ class PDFViewer {

#enableNewAltTextWhenAddingImage = false;

#enableAutolinking = false;

#eventAbortController = null;

#mlManager = null;
Expand Down Expand Up @@ -321,6 +323,7 @@ class PDFViewer {
this.#mlManager = options.mlManager || null;
this.#enableHWA = options.enableHWA || false;
this.#supportsPinchToZoom = options.supportsPinchToZoom !== false;
this.#enableAutolinking = options.enableAutolinking || false;

this.defaultRenderingQueue = !options.renderingQueue;
if (
Expand Down Expand Up @@ -990,6 +993,7 @@ class PDFViewer {
l10n: this.l10n,
layerProperties: this._layerProperties,
enableHWA: this.#enableHWA,
enableAutolinking: this.#enableAutolinking,
});
this._pages.push(pageView);
}
Expand Down
Loading