Skip to content

Commit

Permalink
Rotate annotations based on the MK::R value (bug 1675139)
Browse files Browse the repository at this point in the history
- it aims to fix: https://bugzilla.mozilla.org/show_bug.cgi?id=1675139;
- An annotation can be rotated (counterclockwise);
- the rotation can be set in using JS.
  • Loading branch information
calixteman committed Jun 19, 2022
1 parent 54777b4 commit b4e3f79
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 21 deletions.
122 changes: 104 additions & 18 deletions src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,9 +430,11 @@ class Annotation {
this.setColor(dict.getArray("C"));
this.setBorderStyle(dict);
this.setAppearance(dict);
this.setBorderAndBackgroundColors(dict.get("MK"));

this._hasOwnCanvas = false;
const MK = dict.get("MK");
this.setBorderAndBackgroundColors(MK);
this.setRotation(MK);

this._streams = [];
if (this.appearance) {
this._streams.push(this.appearance);
Expand All @@ -445,12 +447,14 @@ class Annotation {
color: this.color,
backgroundColor: this.backgroundColor,
borderColor: this.borderColor,
rotation: this.rotation,
contentsObj: this._contents,
hasAppearance: !!this.appearance,
id: params.id,
modificationDate: this.modificationDate,
rect: this.rectangle,
subtype: params.subtype,
hasOwnCanvas: false,
};

if (params.collectFields) {
Expand Down Expand Up @@ -704,6 +708,47 @@ class Annotation {
}
}

setRotation(mk) {
this.rotation = 0;
if (mk instanceof Dict) {
let angle = mk.get("R") || 0;
if (Number.isInteger(angle) && angle !== 0) {
// If the angle is negative then angle % 360 is still negative.
angle %= 360;
angle = angle >= 0 ? angle : 360 + angle;
if (angle % 90 === 0) {
this.rotation = angle;
}
}
}
}

getRotationMatrix(annotationStorage) {
const storageEntry = annotationStorage
? annotationStorage.get(this.data.id)
: undefined;
let rotation = storageEntry && storageEntry.rotation;
rotation = rotation === undefined ? this.rotation : rotation;

if (rotation === 0) {
return [1, 0, 0, 1, 0, 0];
}

const width = this.data.rect[2] - this.data.rect[0];
const height = this.data.rect[3] - this.data.rect[1];

switch (rotation) {
case 90:
return [0, 1, -1, 0, width, 0];
case 180:
return [-1, 0, 0, -1, width, height];
case 270:
return [0, -1, 1, 0, 0, height];
default:
throw new Error("Invalid rotation");
}
}

/**
* Set the color for background and border if any.
* The default values are transparent.
Expand All @@ -721,13 +766,22 @@ class Annotation {
}
}

getBorderAndBackgroundAppearances() {
getBorderAndBackgroundAppearances(annotationStorage) {
const storageEntry = annotationStorage
? annotationStorage.get(this.data.id)
: undefined;
let rotation = storageEntry && storageEntry.rotation;
rotation = rotation === undefined ? this.rotation : rotation;

if (!this.backgroundColor && !this.borderColor) {
return "";
}
const width = this.data.rect[2] - this.data.rect[0];
const height = this.data.rect[3] - this.data.rect[1];
const rect = `0 0 ${width} ${height} re`;
const rect =
rotation === 0 || rotation === 180
? `0 0 ${width} ${height} re`
: `0 0 ${height} ${width} re`;

let str = "";
if (this.backgroundColor) {
Expand Down Expand Up @@ -849,7 +903,7 @@ class Annotation {
const data = this.data;
let appearance = this.appearance;
const isUsingOwnCanvas =
this._hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY;
this.data.hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY;
if (!appearance) {
if (!isUsingOwnCanvas) {
return Promise.resolve(new OperatorList());
Expand All @@ -873,7 +927,7 @@ class Annotation {
data.id,
data.rect,
transform,
matrix,
this.getRotationMatrix(annotationStorage),
isUsingOwnCanvas,
]);

Expand Down Expand Up @@ -918,6 +972,7 @@ class Annotation {
type: "",
kidIds: this.data.kidIds,
page: this.data.pageIndex,
rotation: this.rotation,
};
}
return null;
Expand Down Expand Up @@ -1516,7 +1571,7 @@ class WidgetAnnotation extends Annotation {
this.data.id,
this.data.rect,
transform,
matrix,
this.getRotationMatrix(annotationStorage),
]);

const stream = new StringStream(content);
Expand Down Expand Up @@ -1612,6 +1667,12 @@ class WidgetAnnotation extends Annotation {
appearanceDict.set("Resources", this._getSaveFieldResources(xref));
appearanceDict.set("BBox", bbox);

const rotationMatrix = this.getRotationMatrix(annotationStorage);
if (rotationMatrix[0] !== 1) {
// The matrix isn't the identity one.
appearanceDict.set("Matrix", rotationMatrix);
}

const bufferOriginal = [`${this.ref.num} ${this.ref.gen} obj\n`];
writeDict(dict, bufferOriginal, originalTransform);
bufferOriginal.push("\nendobj\n");
Expand Down Expand Up @@ -1639,11 +1700,16 @@ class WidgetAnnotation extends Annotation {
: undefined;
let value =
storageEntry && (storageEntry.formattedValue || storageEntry.value);
if (value === undefined) {
let rotation = storageEntry && storageEntry.rotation;

if (rotation === undefined && value === undefined) {
if (!this._hasValueFromXFA || this.appearance) {
// The annotation hasn't been rendered so use the appearance.
return null;
}
}

if (value === undefined) {
// The annotation has its value in XFA datasets but not in the V field.
value = this.data.fieldValue;
if (!value) {
Expand All @@ -1660,15 +1726,21 @@ class WidgetAnnotation extends Annotation {
return "";
}

rotation = rotation === undefined ? this.rotation : rotation;

let lineCount = -1;
if (this.data.multiLine) {
lineCount = value.split(/\r\n|\r|\n/).length;
}

const defaultPadding = 2;
const hPadding = defaultPadding;
const totalHeight = this.data.rect[3] - this.data.rect[1];
const totalWidth = this.data.rect[2] - this.data.rect[0];
let totalHeight = this.data.rect[3] - this.data.rect[1];
let totalWidth = this.data.rect[2] - this.data.rect[0];

if (rotation === 90 || rotation === 270) {
[totalWidth, totalHeight] = [totalHeight, totalWidth];
}

if (!this._defaultAppearance) {
// The DA is required and must be a string.
Expand Down Expand Up @@ -1719,7 +1791,8 @@ class WidgetAnnotation extends Annotation {
totalHeight,
alignment,
hPadding,
vPadding
vPadding,
annotationStorage
);
}

Expand All @@ -1733,12 +1806,13 @@ class WidgetAnnotation extends Annotation {
encodedString,
totalWidth,
hPadding,
vPadding
vPadding,
annotationStorage
);
}

// Empty or it has a trailing whitespace.
const colors = this.getBorderAndBackgroundAppearances();
const colors = this.getBorderAndBackgroundAppearances(annotationStorage);

if (alignment === 0 || alignment > 2) {
// Left alignment: nothing to do
Expand Down Expand Up @@ -1989,7 +2063,15 @@ class TextWidgetAnnotation extends WidgetAnnotation {
this.data.doNotScroll = this.hasFieldFlag(AnnotationFieldFlag.DONOTSCROLL);
}

_getCombAppearance(defaultAppearance, font, text, width, hPadding, vPadding) {
_getCombAppearance(
defaultAppearance,
font,
text,
width,
hPadding,
vPadding,
annotationStorage
) {
const combWidth = numberToString(width / this.data.maxLen);
const buf = [];
const positions = font.getCharPositions(text);
Expand All @@ -1998,7 +2080,7 @@ class TextWidgetAnnotation extends WidgetAnnotation {
}

// Empty or it has a trailing whitespace.
const colors = this.getBorderAndBackgroundAppearances();
const colors = this.getBorderAndBackgroundAppearances(annotationStorage);
const renderedComb = buf.join(` ${combWidth} 0 Td `);
return (
`/Tx BMC q ${colors}BT ` +
Expand All @@ -2017,7 +2099,8 @@ class TextWidgetAnnotation extends WidgetAnnotation {
height,
alignment,
hPadding,
vPadding
vPadding,
annotationStorage
) {
const lines = text.split(/\r\n?|\n/);
const buf = [];
Expand All @@ -2043,7 +2126,7 @@ class TextWidgetAnnotation extends WidgetAnnotation {
const renderedText = buf.join("\n");

// Empty or it has a trailing whitespace.
const colors = this.getBorderAndBackgroundAppearances();
const colors = this.getBorderAndBackgroundAppearances(annotationStorage);

return (
`/Tx BMC q ${colors}BT ` +
Expand Down Expand Up @@ -2137,6 +2220,7 @@ class TextWidgetAnnotation extends WidgetAnnotation {
page: this.data.pageIndex,
strokeColor: this.data.borderColor,
fillColor: this.data.backgroundColor,
rotation: this.rotation,
type: "text",
};
}
Expand All @@ -2163,7 +2247,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
} else if (this.data.radioButton) {
this._processRadioButton(params);
} else if (this.data.pushButton) {
this._hasOwnCanvas = true;
this.data.hasOwnCanvas = true;
this._processPushButton(params);
} else {
warn("Invalid field flags for button widget annotation");
Expand Down Expand Up @@ -2579,6 +2663,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
page: this.data.pageIndex,
strokeColor: this.data.borderColor,
fillColor: this.data.backgroundColor,
rotation: this.rotation,
type,
};
}
Expand Down Expand Up @@ -2662,6 +2747,7 @@ class ChoiceWidgetAnnotation extends WidgetAnnotation {
page: this.data.pageIndex,
strokeColor: this.data.borderColor,
fillColor: this.data.backgroundColor,
rotation: this.rotation,
type,
};
}
Expand Down
38 changes: 36 additions & 2 deletions src/display/annotation_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,39 @@ class AnnotationElement {

container.style.left = `${(100 * (rect[0] - pageLLx)) / pageWidth}%`;
container.style.top = `${(100 * (rect[1] - pageLLy)) / pageHeight}%`;
container.style.width = `${(100 * width) / pageWidth}%`;
container.style.height = `${(100 * height) / pageHeight}%`;

const { rotation } = data;
if (data.hasOwnCanvas || rotation === 0) {
container.style.width = `${(100 * width) / pageWidth}%`;
container.style.height = `${(100 * height) / pageHeight}%`;
} else {
this.setRotation(rotation, container);
}

return container;
}

setRotation(angle, container = this.container) {
const [pageLLx, pageLLy, pageURx, pageURy] = this.viewport.viewBox;
const pageWidth = pageURx - pageLLx;
const pageHeight = pageURy - pageLLy;
const { width, height } = getRectDims(this.data.rect);

let elementWidth, elementHeight;
if (angle % 180 === 0) {
elementWidth = (100 * width) / pageWidth;
elementHeight = (100 * height) / pageHeight;
} else {
elementWidth = (100 * height) / pageWidth;
elementHeight = (100 * width) / pageHeight;
}

container.style.width = `${elementWidth}%`;
container.style.height = `${elementHeight}%`;

container.setAttribute("data-annotation-rotation", (360 - angle) % 360);
}

get _commonActions() {
const setColor = (jsName, styleName, event) => {
const color = event.detail[jsName];
Expand Down Expand Up @@ -335,6 +362,13 @@ class AnnotationElement {
strokeColor: event => {
setColor("strokeColor", "borderColor", event);
},
rotation: event => {
const angle = event.detail.rotation;
this.setRotation(angle);
this.annotationStorage.setValue(this.data.id, {
rotation: angle,
});
},
});
}

Expand Down
15 changes: 14 additions & 1 deletion src/scripting_api/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ class Field extends PDFObject {
this.required = data.required;
this.richText = data.richText;
this.richValue = data.richValue;
this.rotation = data.rotation;
this.style = data.style;
this.submitName = data.submitName;
this.textFont = data.textFont;
Expand All @@ -84,6 +83,7 @@ class Field extends PDFObject {
this._kidIds = data.kidIds || null;
this._fieldType = getFieldType(this._actions);
this._siblings = data.siblings || null;
this._rotation = data.rotation || 0;

this._globalEval = data.globalEval;
this._appObjects = data.appObjects;
Expand Down Expand Up @@ -188,6 +188,19 @@ class Field extends PDFObject {
throw new Error("field.page is read-only");
}

get rotation() {
return this._rotation;
}

set rotation(angle) {
angle = Math.floor(angle);
if (angle % 90 !== 0) {
throw new Error("Invalid rotation: must be a multiple of 90");
}
angle %= 360;
this._rotation = angle >= 0 ? angle : 360 + angle;
}

get textColor() {
return this._textColor;
}
Expand Down
Loading

0 comments on commit b4e3f79

Please sign in to comment.