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;
|
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.
|
* Get field data for usage in JS sandbox.
|
||||||
*
|
*
|
||||||
@ -3250,6 +3301,10 @@ class FreeTextAnnotation extends MarkupAnnotation {
|
|||||||
this.data.annotationType = AnnotationType.FREETEXT;
|
this.data.annotationType = AnnotationType.FREETEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hasTextContent() {
|
||||||
|
return !!this.appearance;
|
||||||
|
}
|
||||||
|
|
||||||
static createNewDict(annotation, xref, { apRef, ap }) {
|
static createNewDict(annotation, xref, { apRef, ap }) {
|
||||||
const { color, fontSize, rect, rotation, user, value } = annotation;
|
const { color, fontSize, rect, rotation, user, value } = annotation;
|
||||||
const freetext = new Dict(xref);
|
const freetext = new Dict(xref);
|
||||||
|
@ -578,30 +578,56 @@ class Page {
|
|||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAnnotationsData(intent) {
|
async getAnnotationsData(handler, task, intent) {
|
||||||
return this._parsedAnnotations.then(function (annotations) {
|
const annotations = await this._parsedAnnotations;
|
||||||
const annotationsData = [];
|
if (annotations.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
if (annotations.length === 0) {
|
const textContentPromises = [];
|
||||||
return annotationsData;
|
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) {
|
if (annotation.hasTextContent && isVisible) {
|
||||||
// Get the annotation even if it's hidden because
|
if (!partialEvaluator) {
|
||||||
// JS can change its display.
|
partialEvaluator = new PartialEvaluator({
|
||||||
if (
|
xref: this.xref,
|
||||||
intentAny ||
|
handler,
|
||||||
(intentDisplay && annotation.viewable) ||
|
pageIndex: this.pageIndex,
|
||||||
(intentPrint && annotation.printable)
|
idFactory: this._localIdFactory,
|
||||||
) {
|
fontCache: this.fontCache,
|
||||||
annotationsData.push(annotation.data);
|
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() {
|
get annotations() {
|
||||||
|
@ -536,7 +536,18 @@ class WorkerMessageHandler {
|
|||||||
|
|
||||||
handler.on("GetAnnotations", function ({ pageIndex, intent }) {
|
handler.on("GetAnnotations", function ({ pageIndex, intent }) {
|
||||||
return pdfManager.getPage(pageIndex).then(function (page) {
|
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
|
parameters.data.richText?.str
|
||||||
);
|
);
|
||||||
super(parameters, { isRenderable, ignoreBorder: true });
|
super(parameters, { isRenderable, ignoreBorder: true });
|
||||||
|
this.textContent = parameters.data.textContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
this.container.className = "freeTextAnnotation";
|
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) {
|
if (!this.data.hasPopup) {
|
||||||
this._createPopup(null, this.data);
|
this._createPopup(null, this.data);
|
||||||
}
|
}
|
||||||
|
@ -48,3 +48,13 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 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,
|
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 () {
|
describe("InkAnnotation", function () {
|
||||||
|
@ -271,3 +271,18 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 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