[api-minor] Simplify how the list of points are structured

Instead of sending to the main thread an array of Objects for a list of points (or quadpoints),
we'll send just a basic float buffer.
It should slightly improve performances (especially when cloning the data) and use slightly less memory.
This commit is contained in:
Calixte Denizet 2024-05-24 15:48:19 +02:00
parent 24e12d515d
commit 6fa98ac99f
5 changed files with 162 additions and 202 deletions

View file

@ -561,24 +561,16 @@ function getQuadPoints(dict, rect) {
return null; return null;
} }
const quadPointsLists = []; const newQuadPoints = new Float32Array(quadPoints.length);
for (let i = 0, ii = quadPoints.length / 8; i < ii; i++) { for (let i = 0, ii = quadPoints.length; i < ii; i += 8) {
// Each series of eight numbers represents the coordinates for one // Each series of eight numbers represents the coordinates for one
// quadrilateral in the order [x1, y1, x2, y2, x3, y3, x4, y4]. // quadrilateral in the order [x1, y1, x2, y2, x3, y3, x4, y4].
// Convert this to an array of objects with x and y coordinates. // Convert this to an array of objects with x and y coordinates.
let minX = Infinity, const [x1, y1, x2, y2, x3, y3, x4, y4] = quadPoints.slice(i, i + 8);
maxX = -Infinity, const minX = Math.min(x1, x2, x3, x4);
minY = Infinity, const maxX = Math.max(x1, x2, x3, x4);
maxY = -Infinity; const minY = Math.min(y1, y2, y3, y4);
for (let j = i * 8, jj = i * 8 + 8; j < jj; j += 2) { const maxY = Math.max(y1, y2, y3, y4);
const x = quadPoints[j];
const y = quadPoints[j + 1];
minX = Math.min(x, minX);
maxX = Math.max(x, maxX);
minY = Math.min(y, minY);
maxY = Math.max(y, maxY);
}
// The quadpoints should be ignored if any coordinate in the array // The quadpoints should be ignored if any coordinate in the array
// lies outside the region specified by the rectangle. The rectangle // lies outside the region specified by the rectangle. The rectangle
// can be `null` for markup annotations since their rectangle may be // can be `null` for markup annotations since their rectangle may be
@ -601,14 +593,9 @@ function getQuadPoints(dict, rect) {
// top right, bottom right and bottom left. To avoid inconsistency and // top right, bottom right and bottom left. To avoid inconsistency and
// broken rendering, we normalize all lists to put the quadpoints in the // broken rendering, we normalize all lists to put the quadpoints in the
// same standard order (see https://stackoverflow.com/a/10729881). // same standard order (see https://stackoverflow.com/a/10729881).
quadPointsLists.push([ newQuadPoints.set([minX, maxY, maxX, maxY, minX, minY, maxX, minY], i);
{ x: minX, y: maxY },
{ x: maxX, y: maxY },
{ x: minX, y: minY },
{ x: maxX, y: minY },
]);
} }
return quadPointsLists; return newQuadPoints;
} }
function getTransformMatrix(rect, bbox, matrix) { function getTransformMatrix(rect, bbox, matrix) {
@ -1661,18 +1648,23 @@ class MarkupAnnotation extends Annotation {
// If there are no quadpoints, the rectangle should be used instead. // If there are no quadpoints, the rectangle should be used instead.
// Convert the rectangle definition to a points array similar to how the // Convert the rectangle definition to a points array similar to how the
// quadpoints are defined. // quadpoints are defined.
pointsArray = [ pointsArray = Float32Array.from([
[ this.rectangle[0],
{ x: this.rectangle[0], y: this.rectangle[3] }, this.rectangle[3],
{ x: this.rectangle[2], y: this.rectangle[3] }, this.rectangle[2],
{ x: this.rectangle[0], y: this.rectangle[1] }, this.rectangle[3],
{ x: this.rectangle[2], y: this.rectangle[1] }, this.rectangle[0],
], this.rectangle[1],
]; this.rectangle[2],
this.rectangle[1],
]);
} }
for (const points of pointsArray) { for (let i = 0, ii = pointsArray.length; i < ii; i += 8) {
const [mX, MX, mY, MY] = pointsCallback(buffer, points); const [mX, MX, mY, MY] = pointsCallback(
buffer,
pointsArray.subarray(i, i + 8)
);
minX = Math.min(minX, mX); minX = Math.min(minX, mX);
maxX = Math.max(maxX, MX); maxX = Math.max(maxX, MX);
minY = Math.min(minY, mY); minY = Math.min(minY, mY);
@ -4083,10 +4075,10 @@ class LineAnnotation extends MarkupAnnotation {
"S" "S"
); );
return [ return [
points[0].x - borderWidth, points[0] - borderWidth,
points[1].x + borderWidth, points[2] + borderWidth,
points[3].y - borderWidth, points[7] - borderWidth,
points[1].y + borderWidth, points[3] + borderWidth,
]; ];
}, },
}); });
@ -4126,17 +4118,17 @@ class SquareAnnotation extends MarkupAnnotation {
strokeAlpha, strokeAlpha,
fillAlpha, fillAlpha,
pointsCallback: (buffer, points) => { pointsCallback: (buffer, points) => {
const x = points[2].x + this.borderStyle.width / 2; const x = points[4] + this.borderStyle.width / 2;
const y = points[2].y + this.borderStyle.width / 2; const y = points[5] + this.borderStyle.width / 2;
const width = points[3].x - points[2].x - this.borderStyle.width; const width = points[6] - points[4] - this.borderStyle.width;
const height = points[1].y - points[3].y - this.borderStyle.width; const height = points[3] - points[7] - this.borderStyle.width;
buffer.push(`${x} ${y} ${width} ${height} re`); buffer.push(`${x} ${y} ${width} ${height} re`);
if (fillColor) { if (fillColor) {
buffer.push("B"); buffer.push("B");
} else { } else {
buffer.push("S"); buffer.push("S");
} }
return [points[0].x, points[1].x, points[3].y, points[1].y]; return [points[0], points[2], points[7], points[3]];
}, },
}); });
} }
@ -4178,10 +4170,10 @@ class CircleAnnotation extends MarkupAnnotation {
strokeAlpha, strokeAlpha,
fillAlpha, fillAlpha,
pointsCallback: (buffer, points) => { pointsCallback: (buffer, points) => {
const x0 = points[0].x + this.borderStyle.width / 2; const x0 = points[0] + this.borderStyle.width / 2;
const y0 = points[0].y - this.borderStyle.width / 2; const y0 = points[1] - this.borderStyle.width / 2;
const x1 = points[3].x - this.borderStyle.width / 2; const x1 = points[6] - this.borderStyle.width / 2;
const y1 = points[3].y + this.borderStyle.width / 2; const y1 = points[7] + this.borderStyle.width / 2;
const xMid = x0 + (x1 - x0) / 2; const xMid = x0 + (x1 - x0) / 2;
const yMid = y0 + (y1 - y0) / 2; const yMid = y0 + (y1 - y0) / 2;
const xOffset = ((x1 - x0) / 2) * controlPointsDistance; const xOffset = ((x1 - x0) / 2) * controlPointsDistance;
@ -4200,7 +4192,7 @@ class CircleAnnotation extends MarkupAnnotation {
} else { } else {
buffer.push("S"); buffer.push("S");
} }
return [points[0].x, points[1].x, points[3].y, points[1].y]; return [points[0], points[2], points[7], points[3]];
}, },
}); });
} }
@ -4215,7 +4207,7 @@ class PolylineAnnotation extends MarkupAnnotation {
this.data.annotationType = AnnotationType.POLYLINE; this.data.annotationType = AnnotationType.POLYLINE;
this.data.hasOwnCanvas = this.data.noRotate; this.data.hasOwnCanvas = this.data.noRotate;
this.data.noHTML = false; this.data.noHTML = false;
this.data.vertices = []; this.data.vertices = null;
if ( if (
(typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) && (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) &&
@ -4233,12 +4225,7 @@ class PolylineAnnotation extends MarkupAnnotation {
if (!isNumberArray(rawVertices, null)) { if (!isNumberArray(rawVertices, null)) {
return; return;
} }
for (let i = 0, ii = rawVertices.length; i < ii; i += 2) { const vertices = (this.data.vertices = Float32Array.from(rawVertices));
this.data.vertices.push({
x: rawVertices[i],
y: rawVertices[i + 1],
});
}
if (!this.appearance) { if (!this.appearance) {
// The default stroke color is black. // The default stroke color is black.
@ -4251,11 +4238,11 @@ class PolylineAnnotation extends MarkupAnnotation {
// If the /Rect-entry is empty/wrong, create a fallback rectangle so that // If the /Rect-entry is empty/wrong, create a fallback rectangle so that
// we get similar rendering/highlighting behaviour as in Adobe Reader. // we get similar rendering/highlighting behaviour as in Adobe Reader.
const bbox = [Infinity, Infinity, -Infinity, -Infinity]; const bbox = [Infinity, Infinity, -Infinity, -Infinity];
for (const vertex of this.data.vertices) { for (let i = 0, ii = vertices.length; i < ii; i += 2) {
bbox[0] = Math.min(bbox[0], vertex.x - borderAdjust); bbox[0] = Math.min(bbox[0], vertices[i] - borderAdjust);
bbox[1] = Math.min(bbox[1], vertex.y - borderAdjust); bbox[1] = Math.min(bbox[1], vertices[i + 1] - borderAdjust);
bbox[2] = Math.max(bbox[2], vertex.x + borderAdjust); bbox[2] = Math.max(bbox[2], vertices[i] + borderAdjust);
bbox[3] = Math.max(bbox[3], vertex.y + borderAdjust); bbox[3] = Math.max(bbox[3], vertices[i + 1] + borderAdjust);
} }
if (!Util.intersect(this.rectangle, bbox)) { if (!Util.intersect(this.rectangle, bbox)) {
this.rectangle = bbox; this.rectangle = bbox;
@ -4267,14 +4254,13 @@ class PolylineAnnotation extends MarkupAnnotation {
strokeColor, strokeColor,
strokeAlpha, strokeAlpha,
pointsCallback: (buffer, points) => { pointsCallback: (buffer, points) => {
const vertices = this.data.vertices; for (let i = 0, ii = vertices.length; i < ii; i += 2) {
for (let i = 0, ii = vertices.length; i < ii; i++) {
buffer.push( buffer.push(
`${vertices[i].x} ${vertices[i].y} ${i === 0 ? "m" : "l"}` `${vertices[i]} ${vertices[i + 1]} ${i === 0 ? "m" : "l"}`
); );
} }
buffer.push("S"); buffer.push("S");
return [points[0].x, points[1].x, points[3].y, points[1].y]; return [points[0], points[2], points[7], points[3]];
}, },
}); });
} }
@ -4318,15 +4304,17 @@ class InkAnnotation extends MarkupAnnotation {
// the alternating horizontal and vertical coordinates, respectively, // the alternating horizontal and vertical coordinates, respectively,
// of each vertex. Convert this to an array of objects with x and y // of each vertex. Convert this to an array of objects with x and y
// coordinates. // coordinates.
this.data.inkLists.push([]);
if (!Array.isArray(rawInkLists[i])) { if (!Array.isArray(rawInkLists[i])) {
continue; continue;
} }
const inkList = new Float32Array(rawInkLists[i].length);
this.data.inkLists.push(inkList);
for (let j = 0, jj = rawInkLists[i].length; j < jj; j += 2) { for (let j = 0, jj = rawInkLists[i].length; j < jj; j += 2) {
const x = xref.fetchIfRef(rawInkLists[i][j]), const x = xref.fetchIfRef(rawInkLists[i][j]),
y = xref.fetchIfRef(rawInkLists[i][j + 1]); y = xref.fetchIfRef(rawInkLists[i][j + 1]);
if (typeof x === "number" && typeof y === "number") { if (typeof x === "number" && typeof y === "number") {
this.data.inkLists[i].push({ x, y }); inkList[j] = x;
inkList[j + 1] = y;
} }
} }
} }
@ -4342,12 +4330,12 @@ class InkAnnotation extends MarkupAnnotation {
// If the /Rect-entry is empty/wrong, create a fallback rectangle so that // If the /Rect-entry is empty/wrong, create a fallback rectangle so that
// we get similar rendering/highlighting behaviour as in Adobe Reader. // we get similar rendering/highlighting behaviour as in Adobe Reader.
const bbox = [Infinity, Infinity, -Infinity, -Infinity]; const bbox = [Infinity, Infinity, -Infinity, -Infinity];
for (const inkLists of this.data.inkLists) { for (const inkList of this.data.inkLists) {
for (const vertex of inkLists) { for (let i = 0, ii = inkList.length; i < ii; i += 2) {
bbox[0] = Math.min(bbox[0], vertex.x - borderAdjust); bbox[0] = Math.min(bbox[0], inkList[i] - borderAdjust);
bbox[1] = Math.min(bbox[1], vertex.y - borderAdjust); bbox[1] = Math.min(bbox[1], inkList[i + 1] - borderAdjust);
bbox[2] = Math.max(bbox[2], vertex.x + borderAdjust); bbox[2] = Math.max(bbox[2], inkList[i] + borderAdjust);
bbox[3] = Math.max(bbox[3], vertex.y + borderAdjust); bbox[3] = Math.max(bbox[3], inkList[i + 1] + borderAdjust);
} }
} }
if (!Util.intersect(this.rectangle, bbox)) { if (!Util.intersect(this.rectangle, bbox)) {
@ -4365,14 +4353,14 @@ class InkAnnotation extends MarkupAnnotation {
// curves in an implementation-dependent way. // curves in an implementation-dependent way.
// In order to simplify things, we utilize straight lines for now. // In order to simplify things, we utilize straight lines for now.
for (const inkList of this.data.inkLists) { for (const inkList of this.data.inkLists) {
for (let i = 0, ii = inkList.length; i < ii; i++) { for (let i = 0, ii = inkList.length; i < ii; i += 2) {
buffer.push( buffer.push(
`${inkList[i].x} ${inkList[i].y} ${i === 0 ? "m" : "l"}` `${inkList[i]} ${inkList[i + 1]} ${i === 0 ? "m" : "l"}`
); );
} }
buffer.push("S"); buffer.push("S");
} }
return [points[0].x, points[1].x, points[3].y, points[1].y]; return [points[0], points[2], points[7], points[3]];
}, },
}); });
} }
@ -4581,13 +4569,13 @@ class HighlightAnnotation extends MarkupAnnotation {
fillAlpha, fillAlpha,
pointsCallback: (buffer, points) => { pointsCallback: (buffer, points) => {
buffer.push( buffer.push(
`${points[0].x} ${points[0].y} m`, `${points[0]} ${points[1]} m`,
`${points[1].x} ${points[1].y} l`, `${points[2]} ${points[3]} l`,
`${points[3].x} ${points[3].y} l`, `${points[6]} ${points[7]} l`,
`${points[2].x} ${points[2].y} l`, `${points[4]} ${points[5]} l`,
"f" "f"
); );
return [points[0].x, points[1].x, points[3].y, points[1].y]; return [points[0], points[2], points[7], points[3]];
}, },
}); });
} }
@ -4709,11 +4697,11 @@ class UnderlineAnnotation extends MarkupAnnotation {
strokeAlpha, strokeAlpha,
pointsCallback: (buffer, points) => { pointsCallback: (buffer, points) => {
buffer.push( buffer.push(
`${points[2].x} ${points[2].y + 1.3} m`, `${points[4]} ${points[5] + 1.3} m`,
`${points[3].x} ${points[3].y + 1.3} l`, `${points[6]} ${points[7] + 1.3} l`,
"S" "S"
); );
return [points[0].x, points[1].x, points[3].y, points[1].y]; return [points[0], points[2], points[7], points[3]];
}, },
}); });
} }
@ -4745,11 +4733,11 @@ class SquigglyAnnotation extends MarkupAnnotation {
strokeColor, strokeColor,
strokeAlpha, strokeAlpha,
pointsCallback: (buffer, points) => { pointsCallback: (buffer, points) => {
const dy = (points[0].y - points[2].y) / 6; const dy = (points[1] - points[5]) / 6;
let shift = dy; let shift = dy;
let x = points[2].x; let x = points[4];
const y = points[2].y; const y = points[5];
const xEnd = points[3].x; const xEnd = points[6];
buffer.push(`${x} ${y + shift} m`); buffer.push(`${x} ${y + shift} m`);
do { do {
x += 2; x += 2;
@ -4757,7 +4745,7 @@ class SquigglyAnnotation extends MarkupAnnotation {
buffer.push(`${x} ${y + shift} l`); buffer.push(`${x} ${y + shift} l`);
} while (x < xEnd); } while (x < xEnd);
buffer.push("S"); buffer.push("S");
return [points[2].x, xEnd, y - 2 * dy, y + 2 * dy]; return [points[4], xEnd, y - 2 * dy, y + 2 * dy];
}, },
}); });
} }
@ -4790,13 +4778,13 @@ class StrikeOutAnnotation extends MarkupAnnotation {
strokeAlpha, strokeAlpha,
pointsCallback: (buffer, points) => { pointsCallback: (buffer, points) => {
buffer.push( buffer.push(
`${(points[0].x + points[2].x) / 2} ` + `${(points[0] + points[4]) / 2} ` +
`${(points[0].y + points[2].y) / 2} m`, `${(points[1] + points[5]) / 2} m`,
`${(points[1].x + points[3].x) / 2} ` + `${(points[2] + points[6]) / 2} ` +
`${(points[1].y + points[3].y) / 2} l`, `${(points[3] + points[7]) / 2} l`,
"S" "S"
); );
return [points[0].x, points[1].x, points[3].y, points[1].y]; return [points[0], points[2], points[7], points[3]];
}, },
}); });
} }

View file

@ -528,10 +528,12 @@ class AnnotationElement {
return; return;
} }
const [rectBlX, rectBlY, rectTrX, rectTrY] = this.data.rect; const [rectBlX, rectBlY, rectTrX, rectTrY] = this.data.rect.map(x =>
Math.fround(x)
);
if (quadPoints.length === 1) { if (quadPoints.length === 8) {
const [, { x: trX, y: trY }, { x: blX, y: blY }] = quadPoints[0]; const [trX, trY, blX, blY] = quadPoints.subarray(2, 6);
if ( if (
rectTrX === trX && rectTrX === trX &&
rectTrY === trY && rectTrY === trY &&
@ -578,7 +580,11 @@ class AnnotationElement {
clipPath.setAttribute("clipPathUnits", "objectBoundingBox"); clipPath.setAttribute("clipPathUnits", "objectBoundingBox");
defs.append(clipPath); defs.append(clipPath);
for (const [, { x: trX, y: trY }, { x: blX, y: blY }] of quadPoints) { for (let i = 2, ii = quadPoints.length; i < ii; i += 8) {
const trX = quadPoints[i];
const trY = quadPoints[i + 1];
const blX = quadPoints[i + 2];
const blY = quadPoints[i + 3];
const rect = svgFactory.createElement("rect"); const rect = svgFactory.createElement("rect");
const x = (blX - rectBlX) / width; const x = (blX - rectBlX) / width;
const y = (rectTrY - trY) / height; const y = (rectTrY - trY) / height;
@ -2716,8 +2722,13 @@ class PolylineAnnotationElement extends AnnotationElement {
// Create an invisible polyline with the same points that acts as the // Create an invisible polyline with the same points that acts as the
// trigger for the popup. Only the polyline itself should trigger the // trigger for the popup. Only the polyline itself should trigger the
// popup, not the entire container. // popup, not the entire container.
const data = this.data; const {
const { width, height } = getRectDims(data.rect); data: { rect, vertices, borderStyle, popupRef },
} = this;
if (!vertices) {
return this.container;
}
const { width, height } = getRectDims(rect);
const svg = this.svgFactory.create( const svg = this.svgFactory.create(
width, width,
height, height,
@ -2729,10 +2740,10 @@ class PolylineAnnotationElement extends AnnotationElement {
// calculated from a bottom left origin, so transform the polyline // calculated from a bottom left origin, so transform the polyline
// coordinates to a top left origin for the SVG element. // coordinates to a top left origin for the SVG element.
let points = []; let points = [];
for (const coordinate of data.vertices) { for (let i = 0, ii = vertices.length; i < ii; i += 2) {
const x = coordinate.x - data.rect[0]; const x = vertices[i] - rect[0];
const y = data.rect[3] - coordinate.y; const y = rect[3] - vertices[i + 1];
points.push(x + "," + y); points.push(`${x},${y}`);
} }
points = points.join(" "); points = points.join(" ");
@ -2742,7 +2753,7 @@ class PolylineAnnotationElement extends AnnotationElement {
polyline.setAttribute("points", points); polyline.setAttribute("points", points);
// Ensure that the 'stroke-width' is always non-zero, since otherwise it // Ensure that the 'stroke-width' is always non-zero, since otherwise it
// won't be possible to open/close the popup (note e.g. issue 11122). // won't be possible to open/close the popup (note e.g. issue 11122).
polyline.setAttribute("stroke-width", data.borderStyle.width || 1); polyline.setAttribute("stroke-width", borderStyle.width || 1);
polyline.setAttribute("stroke", "transparent"); polyline.setAttribute("stroke", "transparent");
polyline.setAttribute("fill", "transparent"); polyline.setAttribute("fill", "transparent");
@ -2751,7 +2762,7 @@ class PolylineAnnotationElement extends AnnotationElement {
// Create the popup ourselves so that we can bind it to the polyline // Create the popup ourselves so that we can bind it to the polyline
// instead of to the entire container (which is the default). // instead of to the entire container (which is the default).
if (!data.popupRef && this.hasPopupData) { if (!popupRef && this.hasPopupData) {
this._createPopup(); this._createPopup();
} }
@ -2811,23 +2822,25 @@ class InkAnnotationElement extends AnnotationElement {
// Create an invisible polyline with the same points that acts as the // Create an invisible polyline with the same points that acts as the
// trigger for the popup. // trigger for the popup.
const data = this.data; const {
const { width, height } = getRectDims(data.rect); data: { rect, inkLists, borderStyle, popupRef },
} = this;
const { width, height } = getRectDims(rect);
const svg = this.svgFactory.create( const svg = this.svgFactory.create(
width, width,
height, height,
/* skipDimensions = */ true /* skipDimensions = */ true
); );
for (const inkList of data.inkLists) { for (const inkList of inkLists) {
// Convert the ink list to a single points string that the SVG // Convert the ink list to a single points string that the SVG
// polyline element expects ("x1,y1 x2,y2 ..."). PDF coordinates are // polyline element expects ("x1,y1 x2,y2 ..."). PDF coordinates are
// calculated from a bottom left origin, so transform the polyline // calculated from a bottom left origin, so transform the polyline
// coordinates to a top left origin for the SVG element. // coordinates to a top left origin for the SVG element.
let points = []; let points = [];
for (const coordinate of inkList) { for (let i = 0, ii = inkList.length; i < ii; i += 2) {
const x = coordinate.x - data.rect[0]; const x = inkList[i] - rect[0];
const y = data.rect[3] - coordinate.y; const y = rect[3] - inkList[i + 1];
points.push(`${x},${y}`); points.push(`${x},${y}`);
} }
points = points.join(" "); points = points.join(" ");
@ -2837,13 +2850,13 @@ class InkAnnotationElement extends AnnotationElement {
polyline.setAttribute("points", points); polyline.setAttribute("points", points);
// Ensure that the 'stroke-width' is always non-zero, since otherwise it // Ensure that the 'stroke-width' is always non-zero, since otherwise it
// won't be possible to open/close the popup (note e.g. issue 11122). // won't be possible to open/close the popup (note e.g. issue 11122).
polyline.setAttribute("stroke-width", data.borderStyle.width || 1); polyline.setAttribute("stroke-width", borderStyle.width || 1);
polyline.setAttribute("stroke", "transparent"); polyline.setAttribute("stroke", "transparent");
polyline.setAttribute("fill", "transparent"); polyline.setAttribute("fill", "transparent");
// Create the popup ourselves so that we can bind it to the polyline // Create the popup ourselves so that we can bind it to the polyline
// instead of to the entire container (which is the default). // instead of to the entire container (which is the default).
if (!data.popupRef && this.hasPopupData) { if (!popupRef && this.hasPopupData) {
this._createPopup(); this._createPopup();
} }

View file

@ -670,7 +670,7 @@ class HighlightEditor extends AnnotationEditor {
} }
const [pageWidth, pageHeight] = this.pageDimensions; const [pageWidth, pageHeight] = this.pageDimensions;
const boxes = this.#boxes; const boxes = this.#boxes;
const quadPoints = new Array(boxes.length * 8); const quadPoints = new Float32Array(boxes.length * 8);
let i = 0; let i = 0;
for (const { x, y, width, height } of boxes) { for (const { x, y, width, height } of boxes) {
const sx = x * pageWidth; const sx = x * pageWidth;

View file

@ -303,7 +303,20 @@ async function getSerialized(page, filter = undefined) {
const values = await page.evaluate(() => { const values = await page.evaluate(() => {
const { map } = const { map } =
window.PDFViewerApplication.pdfDocument.annotationStorage.serializable; window.PDFViewerApplication.pdfDocument.annotationStorage.serializable;
return map ? [...map.values()] : []; if (!map) {
return [];
}
const vals = Array.from(map.values());
for (const value of vals) {
for (const [k, v] of Object.entries(value)) {
// Puppeteer don't serialize typed array correctly, so we convert them
// to arrays.
if (ArrayBuffer.isView(v)) {
value[k] = Array.from(v);
}
}
}
return vals;
}); });
return filter ? values.map(filter) : values; return filter ? values.map(filter) : values;
} }

View file

@ -261,24 +261,11 @@ describe("annotation", function () {
it("should process quadpoints in the standard order", function () { it("should process quadpoints in the standard order", function () {
rect = [10, 10, 20, 20]; rect = [10, 10, 20, 20];
dict.set( const quadPoints = [
"QuadPoints", 10, 20, 20, 20, 10, 10, 20, 10, 11, 19, 19, 19, 11, 11, 19, 11,
[10, 20, 20, 20, 10, 10, 20, 10, 11, 19, 19, 19, 11, 11, 19, 11] ];
); dict.set("QuadPoints", quadPoints);
expect(getQuadPoints(dict, rect)).toEqual([ expect(getQuadPoints(dict, rect)).toEqual(Float32Array.from(quadPoints));
[
{ x: 10, y: 20 },
{ x: 20, y: 20 },
{ x: 10, y: 10 },
{ x: 20, y: 10 },
],
[
{ x: 11, y: 19 },
{ x: 19, y: 19 },
{ x: 11, y: 11 },
{ x: 19, y: 11 },
],
]);
}); });
it("should normalize and process quadpoints in non-standard orders", function () { it("should normalize and process quadpoints in non-standard orders", function () {
@ -296,14 +283,9 @@ describe("annotation", function () {
for (const nonStandardOrder of nonStandardOrders) { for (const nonStandardOrder of nonStandardOrders) {
dict.set("QuadPoints", nonStandardOrder); dict.set("QuadPoints", nonStandardOrder);
expect(getQuadPoints(dict, rect)).toEqual([ expect(getQuadPoints(dict, rect)).toEqual(
[ Float32Array.from([10, 20, 20, 20, 10, 10, 20, 10])
{ x: 10, y: 20 }, );
{ x: 20, y: 20 },
{ x: 10, y: 10 },
{ x: 20, y: 10 },
],
]);
} }
}); });
}); });
@ -1382,14 +1364,9 @@ describe("annotation", function () {
idFactoryMock idFactoryMock
); );
expect(data.annotationType).toEqual(AnnotationType.LINK); expect(data.annotationType).toEqual(AnnotationType.LINK);
expect(data.quadPoints).toEqual([ expect(data.quadPoints).toEqual(
[ Float32Array.from([10, 20, 20, 20, 10, 10, 20, 10])
{ x: 10, y: 20 }, );
{ x: 20, y: 20 },
{ x: 10, y: 10 },
{ x: 20, y: 10 },
],
]);
}); });
}); });
@ -4330,7 +4307,8 @@ describe("annotation", function () {
const inkDict = new Dict(); const inkDict = new Dict();
inkDict.set("Type", Name.get("Annot")); inkDict.set("Type", Name.get("Annot"));
inkDict.set("Subtype", Name.get("Ink")); inkDict.set("Subtype", Name.get("Ink"));
inkDict.set("InkList", [[1, 1, 1, 2, 2, 2, 3, 3]]); const inkList = [1, 1, 1, 2, 2, 2, 3, 3];
inkDict.set("InkList", [inkList]);
const inkRef = Ref.get(142, 0); const inkRef = Ref.get(142, 0);
const xref = new XRefMock([{ ref: inkRef, data: inkDict }]); const xref = new XRefMock([{ ref: inkRef, data: inkDict }]);
@ -4343,22 +4321,16 @@ describe("annotation", function () {
); );
expect(data.annotationType).toEqual(AnnotationType.INK); expect(data.annotationType).toEqual(AnnotationType.INK);
expect(data.inkLists.length).toEqual(1); expect(data.inkLists.length).toEqual(1);
expect(data.inkLists[0]).toEqual([ expect(data.inkLists[0]).toEqual(Float32Array.from(inkList));
{ x: 1, y: 1 },
{ x: 1, y: 2 },
{ x: 2, y: 2 },
{ x: 3, y: 3 },
]);
}); });
it("should handle multiple ink lists", async function () { it("should handle multiple ink lists", async function () {
const inkDict = new Dict(); const inkDict = new Dict();
inkDict.set("Type", Name.get("Annot")); inkDict.set("Type", Name.get("Annot"));
inkDict.set("Subtype", Name.get("Ink")); inkDict.set("Subtype", Name.get("Ink"));
inkDict.set("InkList", [ const inkList0 = [1, 1, 1, 2];
[1, 1, 1, 2], const inkList1 = [3, 3, 4, 5];
[3, 3, 4, 5], inkDict.set("InkList", [inkList0, inkList1]);
]);
const inkRef = Ref.get(143, 0); const inkRef = Ref.get(143, 0);
const xref = new XRefMock([{ ref: inkRef, data: inkDict }]); const xref = new XRefMock([{ ref: inkRef, data: inkDict }]);
@ -4371,14 +4343,8 @@ describe("annotation", function () {
); );
expect(data.annotationType).toEqual(AnnotationType.INK); expect(data.annotationType).toEqual(AnnotationType.INK);
expect(data.inkLists.length).toEqual(2); expect(data.inkLists.length).toEqual(2);
expect(data.inkLists[0]).toEqual([ expect(data.inkLists[0]).toEqual(Float32Array.from(inkList0));
{ x: 1, y: 1 }, expect(data.inkLists[1]).toEqual(Float32Array.from(inkList1));
{ x: 1, y: 2 },
]);
expect(data.inkLists[1]).toEqual([
{ x: 3, y: 3 },
{ x: 4, y: 5 },
]);
}); });
it("should create a new Ink annotation", async function () { it("should create a new Ink annotation", async function () {
@ -4605,14 +4571,9 @@ describe("annotation", function () {
idFactoryMock idFactoryMock
); );
expect(data.annotationType).toEqual(AnnotationType.HIGHLIGHT); expect(data.annotationType).toEqual(AnnotationType.HIGHLIGHT);
expect(data.quadPoints).toEqual([ expect(data.quadPoints).toEqual(
[ Float32Array.from([10, 20, 20, 20, 10, 10, 20, 10])
{ x: 10, y: 20 }, );
{ x: 20, y: 20 },
{ x: 10, y: 10 },
{ x: 20, y: 10 },
],
]);
}); });
it("should set quadpoints to null when empty", async function () { it("should set quadpoints to null when empty", async function () {
@ -4742,7 +4703,7 @@ describe("annotation", function () {
thickness: 3.14, thickness: 3.14,
quadPoints: null, quadPoints: null,
outlines: { outlines: {
outline: Float64Array.from([ outline: Float32Array.from([
NaN, NaN,
NaN, NaN,
8, 8,
@ -4756,7 +4717,7 @@ describe("annotation", function () {
14, 14,
15, 15,
]), ]),
points: [Float64Array.from([16, 17, 18, 19])], points: [Float32Array.from([16, 17, 18, 19])],
}, },
}, },
] ]
@ -4805,7 +4766,7 @@ describe("annotation", function () {
thickness: 3.14, thickness: 3.14,
quadPoints: null, quadPoints: null,
outlines: { outlines: {
outline: Float64Array.from([ outline: Float32Array.from([
NaN, NaN,
NaN, NaN,
8, 8,
@ -4819,7 +4780,7 @@ describe("annotation", function () {
14, 14,
15, 15,
]), ]),
points: [Float64Array.from([16, 17, 18, 19])], points: [Float32Array.from([16, 17, 18, 19])],
}, },
}, },
] ]
@ -4882,14 +4843,9 @@ describe("annotation", function () {
idFactoryMock idFactoryMock
); );
expect(data.annotationType).toEqual(AnnotationType.UNDERLINE); expect(data.annotationType).toEqual(AnnotationType.UNDERLINE);
expect(data.quadPoints).toEqual([ expect(data.quadPoints).toEqual(
[ Float32Array.from([10, 20, 20, 20, 10, 10, 20, 10])
{ x: 10, y: 20 }, );
{ x: 20, y: 20 },
{ x: 10, y: 10 },
{ x: 20, y: 10 },
],
]);
}); });
}); });
@ -4929,14 +4885,9 @@ describe("annotation", function () {
idFactoryMock idFactoryMock
); );
expect(data.annotationType).toEqual(AnnotationType.SQUIGGLY); expect(data.annotationType).toEqual(AnnotationType.SQUIGGLY);
expect(data.quadPoints).toEqual([ expect(data.quadPoints).toEqual(
[ Float32Array.from([10, 20, 20, 20, 10, 10, 20, 10])
{ x: 10, y: 20 }, );
{ x: 20, y: 20 },
{ x: 10, y: 10 },
{ x: 20, y: 10 },
],
]);
}); });
}); });
@ -4976,14 +4927,9 @@ describe("annotation", function () {
idFactoryMock idFactoryMock
); );
expect(data.annotationType).toEqual(AnnotationType.STRIKEOUT); expect(data.annotationType).toEqual(AnnotationType.STRIKEOUT);
expect(data.quadPoints).toEqual([ expect(data.quadPoints).toEqual(
[ Float32Array.from([10, 20, 20, 20, 10, 10, 20, 10])
{ x: 10, y: 20 }, );
{ x: 20, y: 20 },
{ x: 10, y: 10 },
{ x: 20, y: 10 },
],
]);
}); });
}); });
}); });