Skip to content

Commit 0ce33be

Browse files
committed
Support multiline textfields for printing
1 parent 1747d25 commit 0ce33be

File tree

2 files changed

+153
-4
lines changed

2 files changed

+153
-4
lines changed

src/core/annotation.js

+109-4
Original file line numberDiff line numberDiff line change
@@ -958,11 +958,10 @@ class WidgetAnnotation extends Annotation {
958958
if (!annotationStorage || isPassword) {
959959
return null;
960960
}
961-
let value = annotationStorage[this.data.id] || "";
961+
const value = annotationStorage[this.data.id] || "";
962962
if (value === "") {
963963
return null;
964964
}
965-
value = escapeString(value);
966965

967966
const defaultPadding = 2;
968967
const hPadding = defaultPadding;
@@ -982,13 +981,28 @@ class WidgetAnnotation extends Annotation {
982981

983982
const vPadding = defaultPadding + Math.abs(descent) * fontSize;
984983
const defaultAppearance = this.data.defaultAppearance;
984+
985+
if (this.data.multiLine) {
986+
return this._getMultilineAppearance(
987+
defaultAppearance,
988+
value,
989+
font,
990+
fontSize,
991+
totalWidth,
992+
totalHeight,
993+
alignment,
994+
hPadding,
995+
vPadding
996+
);
997+
}
998+
985999
const alignment = this.data.textAlignment;
9861000
if (alignment === 0 || alignment > 2) {
9871001
// Left alignment: nothing to do
9881002
return (
9891003
"/Tx BMC q BT " +
9901004
defaultAppearance +
991-
` 1 0 0 1 ${hPadding} ${vPadding} Tm (${value}) Tj` +
1005+
` 1 0 0 1 ${hPadding} ${vPadding} Tm (${escapeString(value)}) Tj` +
9921006
" ET Q EMC"
9931007
);
9941008
}
@@ -1076,7 +1090,7 @@ class WidgetAnnotation extends Annotation {
10761090
shift = shift.toFixed(2);
10771091
vPadding = vPadding.toFixed(2);
10781092

1079-
return `${shift} ${vPadding} Td (${text}) Tj`;
1093+
return `${shift} ${vPadding} Td (${escapeString(text)}) Tj`;
10801094
}
10811095
}
10821096

@@ -1114,6 +1128,97 @@ class TextWidgetAnnotation extends WidgetAnnotation {
11141128
!this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) &&
11151129
this.data.maxLen !== null;
11161130
}
1131+
1132+
_getMultilineAppearance(
1133+
defaultAppearance,
1134+
text,
1135+
font,
1136+
fontSize,
1137+
width,
1138+
height,
1139+
alignment,
1140+
hPadding,
1141+
vPadding
1142+
) {
1143+
const lines = text.replace("\r\n", "\n").split("\n");
1144+
const buf = [];
1145+
const totalWidth = width - 2 * hPadding;
1146+
for (const line of lines) {
1147+
const chunks = this._splitLine(line, font, fontSize, totalWidth);
1148+
for (const chunk of chunks) {
1149+
const padding = buf.length === 0 ? hPadding : 0;
1150+
buf.push(
1151+
this._renderText(
1152+
chunk,
1153+
font,
1154+
fontSize,
1155+
width,
1156+
alignment,
1157+
padding,
1158+
-fontSize // <0 because a line is below the previous one
1159+
)
1160+
);
1161+
}
1162+
}
1163+
1164+
const renderedText = buf.join("\n");
1165+
return (
1166+
"/Tx BMC q BT " +
1167+
defaultAppearance +
1168+
` 1 0 0 1 0 ${height} Tm ${renderedText}` +
1169+
" ET Q EMC"
1170+
);
1171+
}
1172+
1173+
_splitLine(line, font, fontSize, width) {
1174+
const scale = fontSize / 1000;
1175+
const whitespace = font.charsToGlyphs(" ", true)[0].width * scale;
1176+
const chunks = [];
1177+
1178+
let lastSpacePos = -1,
1179+
startChunk = 0,
1180+
currentWidth = 0;
1181+
1182+
for (let i = 0, ii = line.length; i < ii; i++) {
1183+
const character = line.charAt(i);
1184+
if (character === " ") {
1185+
if (currentWidth + whitespace > width) {
1186+
// We can break here
1187+
chunks.push(line.substring(startChunk, i));
1188+
startChunk = i;
1189+
currentWidth = whitespace;
1190+
lastSpacePos = -1;
1191+
} else {
1192+
currentWidth += whitespace;
1193+
lastSpacePos = i;
1194+
}
1195+
} else {
1196+
const charWidth = font.charsToGlyphs(character, false)[0].width * scale;
1197+
if (currentWidth + charWidth > width) {
1198+
// We must break to the last white position (if available)
1199+
if (lastSpacePos !== -1) {
1200+
chunks.push(line.substring(startChunk, lastSpacePos + 1));
1201+
startChunk = i = lastSpacePos + 1;
1202+
lastSpacePos = -1;
1203+
currentWidth = 0;
1204+
} else {
1205+
// Just break in the middle of the word
1206+
chunks.push(line.substring(startChunk, i));
1207+
startChunk = i;
1208+
currentWidth = charWidth;
1209+
}
1210+
} else {
1211+
currentWidth += charWidth;
1212+
}
1213+
}
1214+
}
1215+
1216+
if (startChunk < line.length) {
1217+
chunks.push(line.substring(startChunk, line.length));
1218+
}
1219+
1220+
return chunks;
1221+
}
11171222
}
11181223

11191224
class ButtonWidgetAnnotation extends WidgetAnnotation {

test/unit/annotation_spec.js

+44
Original file line numberDiff line numberDiff line change
@@ -1673,6 +1673,50 @@ describe("annotation", function () {
16731673
done();
16741674
}, done.fail);
16751675
});
1676+
1677+
it("should render multiline text for printing", function (done) {
1678+
textWidgetDict.set("Ff", AnnotationFieldFlag.MULTILINE);
1679+
1680+
const textWidgetRef = Ref.get(271, 0);
1681+
const xref = new XRefMock([
1682+
{ ref: textWidgetRef, data: textWidgetDict },
1683+
fontRefObj,
1684+
]);
1685+
const task = new WorkerTask("test print");
1686+
partialEvaluator.xref = xref;
1687+
1688+
AnnotationFactory.create(
1689+
xref,
1690+
textWidgetRef,
1691+
pdfManagerMock,
1692+
idFactoryMock
1693+
)
1694+
.then(annotation => {
1695+
const id = annotation.data.id;
1696+
const annotationStorage = {};
1697+
annotationStorage[id] =
1698+
"a aa aaa aaaa aaaaa aaaaaa " +
1699+
"pneumonoultramicroscopicsilicovolcanoconiosis";
1700+
return annotation._getAppearance(
1701+
partialEvaluator,
1702+
task,
1703+
annotationStorage
1704+
);
1705+
}, done.fail)
1706+
.then(appearance => {
1707+
expect(appearance).toEqual(
1708+
"/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 10 Tm " +
1709+
"2.00 -5.00 Td (a aa aaa ) Tj\n" +
1710+
"0.00 -5.00 Td (aaaa aaaaa ) Tj\n" +
1711+
"0.00 -5.00 Td (aaaaaa ) Tj\n" +
1712+
"0.00 -5.00 Td (pneumonoultr) Tj\n" +
1713+
"0.00 -5.00 Td (amicroscopi) Tj\n" +
1714+
"0.00 -5.00 Td (csilicovolca) Tj\n" +
1715+
"0.00 -5.00 Td (noconiosis) Tj ET Q EMC"
1716+
);
1717+
done();
1718+
}, done.fail);
1719+
});
16761720
});
16771721

16781722
describe("ButtonWidgetAnnotation", function () {

0 commit comments

Comments
 (0)