Implement rendering square/circle annotations without appearance stream

This commit is contained in:
Tim van der Meij 2021-02-21 17:10:35 +01:00
parent 8e74278b65
commit fa6cebf045
No known key found for this signature in database
GPG Key ID: 8C3FD2925A5F2762
4 changed files with 150 additions and 31 deletions

View File

@ -192,6 +192,33 @@ class AnnotationFactory {
}
}
function getRgbColor(color) {
const rgbColor = new Uint8ClampedArray(3);
if (!Array.isArray(color)) {
return rgbColor;
}
switch (color.length) {
case 0: // Transparent, which we indicate with a null value
return null;
case 1: // Convert grayscale to RGB
ColorSpace.singletons.gray.getRgbItem(color, 0, rgbColor, 0);
return rgbColor;
case 3: // Convert RGB percentages to RGB
ColorSpace.singletons.rgb.getRgbItem(color, 0, rgbColor, 0);
return rgbColor;
case 4: // Convert CMYK to RGB
ColorSpace.singletons.cmyk.getRgbItem(color, 0, rgbColor, 0);
return rgbColor;
default:
return rgbColor;
}
}
function getQuadPoints(dict, rect) {
if (!dict.has("QuadPoints")) {
return null;
@ -462,36 +489,7 @@ class Annotation {
* 4 (CMYK) elements
*/
setColor(color) {
const rgbColor = new Uint8ClampedArray(3);
if (!Array.isArray(color)) {
this.color = rgbColor;
return;
}
switch (color.length) {
case 0: // Transparent, which we indicate with a null value
this.color = null;
break;
case 1: // Convert grayscale to RGB
ColorSpace.singletons.gray.getRgbItem(color, 0, rgbColor, 0);
this.color = rgbColor;
break;
case 3: // Convert RGB percentages to RGB
ColorSpace.singletons.rgb.getRgbItem(color, 0, rgbColor, 0);
this.color = rgbColor;
break;
case 4: // Convert CMYK to RGB
ColorSpace.singletons.cmyk.getRgbItem(color, 0, rgbColor, 0);
this.color = rgbColor;
break;
default:
this.color = rgbColor;
break;
}
this.color = getRgbColor(color);
}
/**
@ -929,7 +927,22 @@ class MarkupAnnotation extends Annotation {
buffer.push(`${fillColor[0]} ${fillColor[1]} ${fillColor[2]} rg`);
}
for (const points of this.data.quadPoints) {
let pointsArray = this.data.quadPoints;
if (!pointsArray) {
// If there are no quadpoints, the rectangle should be used instead.
// Convert the rectangle definition to a points array similar to how the
// quadpoints are defined.
pointsArray = [
[
{ x: this.rectangle[0], y: this.rectangle[3] },
{ x: this.rectangle[2], y: this.rectangle[3] },
{ x: this.rectangle[0], y: this.rectangle[1] },
{ x: this.rectangle[2], y: this.rectangle[1] },
],
];
}
for (const points of pointsArray) {
const [mX, MX, mY, MY] = pointsCallback(buffer, points);
minX = Math.min(minX, mX);
maxX = Math.max(maxX, MX);
@ -2278,6 +2291,43 @@ class SquareAnnotation extends MarkupAnnotation {
super(parameters);
this.data.annotationType = AnnotationType.SQUARE;
if (!this.appearance) {
// The default stroke color is black.
const strokeColor = this.color
? Array.from(this.color).map(c => c / 255)
: [0, 0, 0];
// The default fill color is transparent.
let fillColor = null;
let interiorColor = parameters.dict.getArray("IC");
if (interiorColor) {
interiorColor = getRgbColor(interiorColor);
fillColor = interiorColor
? Array.from(interiorColor).map(c => c / 255)
: null;
}
this._setDefaultAppearance({
xref: parameters.xref,
extra: `${this.borderStyle.width} w`,
strokeColor,
fillColor,
pointsCallback: (buffer, points) => {
const x = points[2].x + this.borderStyle.width / 2;
const y = points[2].y + this.borderStyle.width / 2;
const width = points[3].x - points[2].x - this.borderStyle.width;
const height = points[1].y - points[3].y - this.borderStyle.width;
buffer.push(`${x} ${y} ${width} ${height} re`);
if (fillColor) {
buffer.push("B");
} else {
buffer.push("S");
}
return [points[0].x, points[1].x, points[3].y, points[1].y];
},
});
}
}
}
@ -2286,6 +2336,67 @@ class CircleAnnotation extends MarkupAnnotation {
super(parameters);
this.data.annotationType = AnnotationType.CIRCLE;
if (!this.appearance) {
// The default stroke color is black.
const strokeColor = this.color
? Array.from(this.color).map(c => c / 255)
: [0, 0, 0];
// The default fill color is transparent.
let fillColor = null;
let interiorColor = parameters.dict.getArray("IC");
if (interiorColor) {
interiorColor = getRgbColor(interiorColor);
fillColor = interiorColor
? Array.from(interiorColor).map(c => c / 255)
: null;
}
// Circles are approximated by Bézier curves with four segments since
// there is no circle primitive in the PDF specification. For the control
// points distance, see https://stackoverflow.com/a/27863181.
const controlPointsDistance = (4 / 3) * Math.tan(Math.PI / (2 * 4));
this._setDefaultAppearance({
xref: parameters.xref,
extra: `${this.borderStyle.width} w`,
strokeColor,
fillColor,
pointsCallback: (buffer, points) => {
const x0 = points[0].x + this.borderStyle.width / 2;
const y0 = points[0].y - this.borderStyle.width / 2;
const x1 = points[3].x - this.borderStyle.width / 2;
const y1 = points[3].y + this.borderStyle.width / 2;
const xMid = x0 + (x1 - x0) / 2;
const yMid = y0 + (y1 - y0) / 2;
const xOffset = ((x1 - x0) / 2) * controlPointsDistance;
const yOffset = ((y1 - y0) / 2) * controlPointsDistance;
buffer.push(`${xMid} ${y1} m`);
buffer.push(
`${xMid + xOffset} ${y1} ${x1} ${yMid + yOffset} ${x1} ${yMid} c`
);
buffer.push(
`${x1} ${yMid - yOffset} ${xMid + xOffset} ${y0} ${xMid} ${y0} c`
);
buffer.push(
`${xMid - xOffset} ${y0} ${x0} ${yMid - yOffset} ${x0} ${yMid} c`
);
buffer.push(
`${x0} ${yMid + yOffset} ${xMid - xOffset} ${y1} ${xMid} ${y1} c`
);
buffer.push("h");
if (fillColor) {
buffer.push("B");
} else {
buffer.push("S");
}
return [points[0].x, points[1].x, points[3].y, points[1].y];
},
});
}
}
}

View File

@ -395,6 +395,7 @@
!annotation-line.pdf
!bug1669099.pdf
!annotation-square-circle.pdf
!annotation-square-circle-without-appearance.pdf
!annotation-stamp.pdf
!annotation-fileattachment.pdf
!annotation-text-widget.pdf

View File

@ -4724,6 +4724,13 @@
"type": "eq",
"annotations": true
},
{ "id": "annotation-square-circle-without-appearance",
"file": "pdfs/annotation-square-circle-without-appearance.pdf",
"md5": "5b2a5e1e918137993d26a0cd8b0947f6",
"rounds": 1,
"annotations": true,
"type": "eq"
},
{ "id": "annotation-stamp",
"file": "pdfs/annotation-stamp.pdf",
"md5": "0a04d7ce1ad103cb3c033d26855d6ec7",