Implement rendering square/circle annotations without appearance stream
This commit is contained in:
parent
8e74278b65
commit
fa6cebf045
@ -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];
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -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
|
||||
|
BIN
test/pdfs/annotation-square-circle-without-appearance.pdf
Normal file
BIN
test/pdfs/annotation-square-circle-without-appearance.pdf
Normal file
Binary file not shown.
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user