Merge pull request #15267 from calixteman/freetext_a11y
[Annotation] Add a div containing the text of a FreeText annotation (bug 1780375)
This commit is contained in:
commit
b985eaa98c
@ -941,6 +941,57 @@ class Annotation {
|
||||
return null;
|
||||
}
|
||||
|
||||
get hasTextContent() {
|
||||
return false;
|
||||
}
|
||||
|
||||
async extractTextContent(evaluator, task, viewBox) {
|
||||
if (!this.appearance) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resources = await this.loadResources(
|
||||
["ExtGState", "Font", "Properties", "XObject"],
|
||||
this.appearance
|
||||
);
|
||||
|
||||
const text = [];
|
||||
const buffer = [];
|
||||
const sink = {
|
||||
desiredSize: Math.Infinity,
|
||||
ready: true,
|
||||
|
||||
enqueue(chunk, size) {
|
||||
for (const item of chunk.items) {
|
||||
buffer.push(item.str);
|
||||
if (item.hasEOL) {
|
||||
text.push(buffer.join(""));
|
||||
buffer.length = 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
await evaluator.getTextContent({
|
||||
stream: this.appearance,
|
||||
task,
|
||||
resources,
|
||||
includeMarkedContent: true,
|
||||
combineTextItems: true,
|
||||
sink,
|
||||
viewBox,
|
||||
});
|
||||
this.reset();
|
||||
|
||||
if (buffer.length) {
|
||||
text.push(buffer.join(""));
|
||||
}
|
||||
|
||||
if (text.length > 0) {
|
||||
this.data.textContent = text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field data for usage in JS sandbox.
|
||||
*
|
||||
@ -3250,6 +3301,10 @@ class FreeTextAnnotation extends MarkupAnnotation {
|
||||
this.data.annotationType = AnnotationType.FREETEXT;
|
||||
}
|
||||
|
||||
get hasTextContent() {
|
||||
return !!this.appearance;
|
||||
}
|
||||
|
||||
static createNewDict(annotation, xref, { apRef, ap }) {
|
||||
const { color, fontSize, rect, rotation, user, value } = annotation;
|
||||
const freetext = new Dict(xref);
|
||||
|
@ -578,30 +578,56 @@ class Page {
|
||||
return tree;
|
||||
}
|
||||
|
||||
getAnnotationsData(intent) {
|
||||
return this._parsedAnnotations.then(function (annotations) {
|
||||
const annotationsData = [];
|
||||
async getAnnotationsData(handler, task, intent) {
|
||||
const annotations = await this._parsedAnnotations;
|
||||
if (annotations.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (annotations.length === 0) {
|
||||
return annotationsData;
|
||||
const textContentPromises = [];
|
||||
const annotationsData = [];
|
||||
let partialEvaluator;
|
||||
|
||||
const intentAny = !!(intent & RenderingIntentFlag.ANY),
|
||||
intentDisplay = !!(intent & RenderingIntentFlag.DISPLAY),
|
||||
intentPrint = !!(intent & RenderingIntentFlag.PRINT);
|
||||
|
||||
for (const annotation of annotations) {
|
||||
// Get the annotation even if it's hidden because
|
||||
// JS can change its display.
|
||||
const isVisible = intentAny || (intentDisplay && annotation.viewable);
|
||||
if (isVisible || (intentPrint && annotation.printable)) {
|
||||
annotationsData.push(annotation.data);
|
||||
}
|
||||
const intentAny = !!(intent & RenderingIntentFlag.ANY),
|
||||
intentDisplay = !!(intent & RenderingIntentFlag.DISPLAY),
|
||||
intentPrint = !!(intent & RenderingIntentFlag.PRINT);
|
||||
|
||||
for (const annotation of annotations) {
|
||||
// Get the annotation even if it's hidden because
|
||||
// JS can change its display.
|
||||
if (
|
||||
intentAny ||
|
||||
(intentDisplay && annotation.viewable) ||
|
||||
(intentPrint && annotation.printable)
|
||||
) {
|
||||
annotationsData.push(annotation.data);
|
||||
if (annotation.hasTextContent && isVisible) {
|
||||
if (!partialEvaluator) {
|
||||
partialEvaluator = new PartialEvaluator({
|
||||
xref: this.xref,
|
||||
handler,
|
||||
pageIndex: this.pageIndex,
|
||||
idFactory: this._localIdFactory,
|
||||
fontCache: this.fontCache,
|
||||
builtInCMapCache: this.builtInCMapCache,
|
||||
standardFontDataCache: this.standardFontDataCache,
|
||||
globalImageCache: this.globalImageCache,
|
||||
options: this.evaluatorOptions,
|
||||
});
|
||||
}
|
||||
textContentPromises.push(
|
||||
annotation
|
||||
.extractTextContent(partialEvaluator, task, this.view)
|
||||
.catch(function (reason) {
|
||||
warn(
|
||||
`getAnnotationsData - ignoring textContent during "${task.name}" task: "${reason}".`
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
return annotationsData;
|
||||
});
|
||||
}
|
||||
|
||||
await Promise.all(textContentPromises);
|
||||
return annotationsData;
|
||||
}
|
||||
|
||||
get annotations() {
|
||||
|
@ -536,7 +536,18 @@ class WorkerMessageHandler {
|
||||
|
||||
handler.on("GetAnnotations", function ({ pageIndex, intent }) {
|
||||
return pdfManager.getPage(pageIndex).then(function (page) {
|
||||
return page.getAnnotationsData(intent);
|
||||
const task = new WorkerTask(`GetAnnotations: page ${pageIndex}`);
|
||||
startWorkerTask(task);
|
||||
|
||||
return page.getAnnotationsData(handler, task, intent).then(
|
||||
data => {
|
||||
finishWorkerTask(task);
|
||||
return data;
|
||||
},
|
||||
reason => {
|
||||
finishWorkerTask(task);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1932,11 +1932,23 @@ class FreeTextAnnotationElement extends AnnotationElement {
|
||||
parameters.data.richText?.str
|
||||
);
|
||||
super(parameters, { isRenderable, ignoreBorder: true });
|
||||
this.textContent = parameters.data.textContent;
|
||||
}
|
||||
|
||||
render() {
|
||||
this.container.className = "freeTextAnnotation";
|
||||
|
||||
if (this.textContent) {
|
||||
const content = document.createElement("div");
|
||||
content.className = "annotationTextContent";
|
||||
for (const line of this.textContent) {
|
||||
const lineSpan = document.createElement("span");
|
||||
lineSpan.textContent = line;
|
||||
content.append(lineSpan);
|
||||
}
|
||||
this.container.append(content);
|
||||
}
|
||||
|
||||
if (!this.data.hasPopup) {
|
||||
this._createPopup(null, this.data);
|
||||
}
|
||||
|
@ -48,3 +48,13 @@
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.annotationLayer .annotationTextContent {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.4;
|
||||
background-color: transparent;
|
||||
color: red;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
@ -4101,6 +4101,35 @@ describe("annotation", function () {
|
||||
OPS.endAnnotation,
|
||||
]);
|
||||
});
|
||||
|
||||
it("should extract the text from a FreeText annotation", async function () {
|
||||
partialEvaluator.xref = new XRefMock();
|
||||
const task = new WorkerTask("test FreeText text extraction");
|
||||
const freetextAnnotation = (
|
||||
await AnnotationFactory.printNewAnnotations(partialEvaluator, task, [
|
||||
{
|
||||
annotationType: AnnotationEditorType.FREETEXT,
|
||||
rect: [12, 34, 56, 78],
|
||||
rotation: 0,
|
||||
fontSize: 10,
|
||||
color: [0, 0, 0],
|
||||
value: "Hello PDF.js\nWorld !",
|
||||
},
|
||||
])
|
||||
)[0];
|
||||
|
||||
await freetextAnnotation.extractTextContent(partialEvaluator, task, [
|
||||
-Infinity,
|
||||
-Infinity,
|
||||
Infinity,
|
||||
Infinity,
|
||||
]);
|
||||
|
||||
expect(freetextAnnotation.data.textContent).toEqual([
|
||||
"Hello PDF.js",
|
||||
"World !",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("InkAnnotation", function () {
|
||||
|
@ -271,3 +271,18 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.annotationLayer .annotationTextContent {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
color: transparent;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.annotationLayer .annotationTextContent span {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user