Support multiline textfields for printing
This commit is contained in:
parent
8c162f57f7
commit
cd8bb7293b
@ -958,11 +958,10 @@ class WidgetAnnotation extends Annotation {
|
|||||||
if (!annotationStorage || isPassword) {
|
if (!annotationStorage || isPassword) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let value = annotationStorage[this.data.id] || "";
|
const value = annotationStorage[this.data.id];
|
||||||
if (value === "") {
|
if (value === "") {
|
||||||
return null;
|
return "";
|
||||||
}
|
}
|
||||||
value = escapeString(value);
|
|
||||||
|
|
||||||
const defaultPadding = 2;
|
const defaultPadding = 2;
|
||||||
const hPadding = defaultPadding;
|
const hPadding = defaultPadding;
|
||||||
@ -983,12 +982,27 @@ class WidgetAnnotation extends Annotation {
|
|||||||
const vPadding = defaultPadding + Math.abs(descent) * fontSize;
|
const vPadding = defaultPadding + Math.abs(descent) * fontSize;
|
||||||
const defaultAppearance = this.data.defaultAppearance;
|
const defaultAppearance = this.data.defaultAppearance;
|
||||||
const alignment = this.data.textAlignment;
|
const alignment = this.data.textAlignment;
|
||||||
|
|
||||||
|
if (this.data.multiLine) {
|
||||||
|
return this._getMultilineAppearance(
|
||||||
|
defaultAppearance,
|
||||||
|
value,
|
||||||
|
font,
|
||||||
|
fontSize,
|
||||||
|
totalWidth,
|
||||||
|
totalHeight,
|
||||||
|
alignment,
|
||||||
|
hPadding,
|
||||||
|
vPadding
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (alignment === 0 || alignment > 2) {
|
if (alignment === 0 || alignment > 2) {
|
||||||
// Left alignment: nothing to do
|
// Left alignment: nothing to do
|
||||||
return (
|
return (
|
||||||
"/Tx BMC q BT " +
|
"/Tx BMC q BT " +
|
||||||
defaultAppearance +
|
defaultAppearance +
|
||||||
` 1 0 0 1 ${hPadding} ${vPadding} Tm (${value}) Tj` +
|
` 1 0 0 1 ${hPadding} ${vPadding} Tm (${escapeString(value)}) Tj` +
|
||||||
" ET Q EMC"
|
" ET Q EMC"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1076,7 +1090,7 @@ class WidgetAnnotation extends Annotation {
|
|||||||
shift = shift.toFixed(2);
|
shift = shift.toFixed(2);
|
||||||
vPadding = vPadding.toFixed(2);
|
vPadding = vPadding.toFixed(2);
|
||||||
|
|
||||||
return `${shift} ${vPadding} Td (${text}) Tj`;
|
return `${shift} ${vPadding} Td (${escapeString(text)}) Tj`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1114,6 +1128,102 @@ class TextWidgetAnnotation extends WidgetAnnotation {
|
|||||||
!this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) &&
|
!this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) &&
|
||||||
this.data.maxLen !== null;
|
this.data.maxLen !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getMultilineAppearance(
|
||||||
|
defaultAppearance,
|
||||||
|
text,
|
||||||
|
font,
|
||||||
|
fontSize,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
alignment,
|
||||||
|
hPadding,
|
||||||
|
vPadding
|
||||||
|
) {
|
||||||
|
const lines = text.split(/\r\n|\r|\n/);
|
||||||
|
const buf = [];
|
||||||
|
const totalWidth = width - 2 * hPadding;
|
||||||
|
for (const line of lines) {
|
||||||
|
const chunks = this._splitLine(line, font, fontSize, totalWidth);
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
const padding = buf.length === 0 ? hPadding : 0;
|
||||||
|
buf.push(
|
||||||
|
this._renderText(
|
||||||
|
chunk,
|
||||||
|
font,
|
||||||
|
fontSize,
|
||||||
|
width,
|
||||||
|
alignment,
|
||||||
|
padding,
|
||||||
|
-fontSize // <0 because a line is below the previous one
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderedText = buf.join("\n");
|
||||||
|
return (
|
||||||
|
"/Tx BMC q BT " +
|
||||||
|
defaultAppearance +
|
||||||
|
` 1 0 0 1 0 ${height} Tm ${renderedText}` +
|
||||||
|
" ET Q EMC"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_splitLine(line, font, fontSize, width) {
|
||||||
|
if (line.length <= 1) {
|
||||||
|
// Nothing to split
|
||||||
|
return [line];
|
||||||
|
}
|
||||||
|
|
||||||
|
const scale = fontSize / 1000;
|
||||||
|
const whitespace = font.charsToGlyphs(" ", true)[0].width * scale;
|
||||||
|
const chunks = [];
|
||||||
|
|
||||||
|
let lastSpacePos = -1,
|
||||||
|
startChunk = 0,
|
||||||
|
currentWidth = 0;
|
||||||
|
|
||||||
|
for (let i = 0, ii = line.length; i < ii; i++) {
|
||||||
|
const character = line.charAt(i);
|
||||||
|
if (character === " ") {
|
||||||
|
if (currentWidth + whitespace > width) {
|
||||||
|
// We can break here
|
||||||
|
chunks.push(line.substring(startChunk, i));
|
||||||
|
startChunk = i;
|
||||||
|
currentWidth = whitespace;
|
||||||
|
lastSpacePos = -1;
|
||||||
|
} else {
|
||||||
|
currentWidth += whitespace;
|
||||||
|
lastSpacePos = i;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const charWidth = font.charsToGlyphs(character, false)[0].width * scale;
|
||||||
|
if (currentWidth + charWidth > width) {
|
||||||
|
// We must break to the last white position (if available)
|
||||||
|
if (lastSpacePos !== -1) {
|
||||||
|
chunks.push(line.substring(startChunk, lastSpacePos + 1));
|
||||||
|
startChunk = i = lastSpacePos + 1;
|
||||||
|
lastSpacePos = -1;
|
||||||
|
currentWidth = 0;
|
||||||
|
} else {
|
||||||
|
// Just break in the middle of the word
|
||||||
|
chunks.push(line.substring(startChunk, i));
|
||||||
|
startChunk = i;
|
||||||
|
currentWidth = charWidth;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentWidth += charWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startChunk < line.length) {
|
||||||
|
chunks.push(line.substring(startChunk, line.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ButtonWidgetAnnotation extends WidgetAnnotation {
|
class ButtonWidgetAnnotation extends WidgetAnnotation {
|
||||||
|
@ -462,7 +462,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
element.setAttribute("value", textContent);
|
element.setAttribute("value", textContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
element.addEventListener("change", function (event) {
|
element.addEventListener("input", function (event) {
|
||||||
storage.setValue(id, event.target.value);
|
storage.setValue(id, event.target.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -689,7 +689,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
selectElement.appendChild(optionElement);
|
selectElement.appendChild(optionElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
selectElement.addEventListener("change", function (event) {
|
selectElement.addEventListener("input", function (event) {
|
||||||
const options = event.target.options;
|
const options = event.target.options;
|
||||||
const value = options[options.selectedIndex].text;
|
const value = options[options.selectedIndex].text;
|
||||||
storage.setValue(id, value);
|
storage.setValue(id, value);
|
||||||
|
@ -1673,6 +1673,114 @@ describe("annotation", function () {
|
|||||||
done();
|
done();
|
||||||
}, done.fail);
|
}, done.fail);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should render multiline text for printing", function (done) {
|
||||||
|
textWidgetDict.set("Ff", AnnotationFieldFlag.MULTILINE);
|
||||||
|
|
||||||
|
const textWidgetRef = Ref.get(271, 0);
|
||||||
|
const xref = new XRefMock([
|
||||||
|
{ ref: textWidgetRef, data: textWidgetDict },
|
||||||
|
fontRefObj,
|
||||||
|
]);
|
||||||
|
const task = new WorkerTask("test print");
|
||||||
|
partialEvaluator.xref = xref;
|
||||||
|
|
||||||
|
AnnotationFactory.create(
|
||||||
|
xref,
|
||||||
|
textWidgetRef,
|
||||||
|
pdfManagerMock,
|
||||||
|
idFactoryMock
|
||||||
|
)
|
||||||
|
.then(annotation => {
|
||||||
|
const id = annotation.data.id;
|
||||||
|
const annotationStorage = {};
|
||||||
|
annotationStorage[id] =
|
||||||
|
"a aa aaa aaaa aaaaa aaaaaa " +
|
||||||
|
"pneumonoultramicroscopicsilicovolcanoconiosis";
|
||||||
|
return annotation._getAppearance(
|
||||||
|
partialEvaluator,
|
||||||
|
task,
|
||||||
|
annotationStorage
|
||||||
|
);
|
||||||
|
}, done.fail)
|
||||||
|
.then(appearance => {
|
||||||
|
expect(appearance).toEqual(
|
||||||
|
"/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 10 Tm " +
|
||||||
|
"2.00 -5.00 Td (a aa aaa ) Tj\n" +
|
||||||
|
"0.00 -5.00 Td (aaaa aaaaa ) Tj\n" +
|
||||||
|
"0.00 -5.00 Td (aaaaaa ) Tj\n" +
|
||||||
|
"0.00 -5.00 Td (pneumonoultr) Tj\n" +
|
||||||
|
"0.00 -5.00 Td (amicroscopi) Tj\n" +
|
||||||
|
"0.00 -5.00 Td (csilicovolca) Tj\n" +
|
||||||
|
"0.00 -5.00 Td (noconiosis) Tj ET Q EMC"
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
}, done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render multiline text with various EOL for printing", function (done) {
|
||||||
|
textWidgetDict.set("Ff", AnnotationFieldFlag.MULTILINE);
|
||||||
|
textWidgetDict.set("Rect", [0, 0, 128, 10]);
|
||||||
|
|
||||||
|
const textWidgetRef = Ref.get(271, 0);
|
||||||
|
const xref = new XRefMock([
|
||||||
|
{ ref: textWidgetRef, data: textWidgetDict },
|
||||||
|
fontRefObj,
|
||||||
|
]);
|
||||||
|
const task = new WorkerTask("test print");
|
||||||
|
partialEvaluator.xref = xref;
|
||||||
|
const expectedAppearance =
|
||||||
|
"/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 10 Tm " +
|
||||||
|
"2.00 -5.00 Td " +
|
||||||
|
"(Lorem ipsum dolor sit amet, consectetur adipiscing elit.) Tj\n" +
|
||||||
|
"0.00 -5.00 Td " +
|
||||||
|
"(Aliquam vitae felis ac lectus bibendum ultricies quis non) Tj\n" +
|
||||||
|
"0.00 -5.00 Td " +
|
||||||
|
"( diam.) Tj\n" +
|
||||||
|
"0.00 -5.00 Td " +
|
||||||
|
"(Morbi id porttitor quam, a iaculis dui.) Tj\n" +
|
||||||
|
"0.00 -5.00 Td " +
|
||||||
|
"(Pellentesque habitant morbi tristique senectus et netus ) Tj\n" +
|
||||||
|
"0.00 -5.00 Td " +
|
||||||
|
"(et malesuada fames ac turpis egestas.) Tj\n" +
|
||||||
|
"0.00 -5.00 Td () Tj\n" +
|
||||||
|
"0.00 -5.00 Td () Tj\n" +
|
||||||
|
"0.00 -5.00 Td " +
|
||||||
|
"(Nulla consectetur, ligula in tincidunt placerat, velit ) Tj\n" +
|
||||||
|
"0.00 -5.00 Td " +
|
||||||
|
"(augue consectetur orci, sed mattis libero nunc ut massa.) Tj\n" +
|
||||||
|
"0.00 -5.00 Td " +
|
||||||
|
"(Etiam facilisis tempus interdum.) Tj ET Q EMC";
|
||||||
|
|
||||||
|
AnnotationFactory.create(
|
||||||
|
xref,
|
||||||
|
textWidgetRef,
|
||||||
|
pdfManagerMock,
|
||||||
|
idFactoryMock
|
||||||
|
)
|
||||||
|
.then(annotation => {
|
||||||
|
const id = annotation.data.id;
|
||||||
|
const annotationStorage = {};
|
||||||
|
annotationStorage[id] =
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\r" +
|
||||||
|
"Aliquam vitae felis ac lectus bibendum ultricies quis non diam.\n" +
|
||||||
|
"Morbi id porttitor quam, a iaculis dui.\r\n" +
|
||||||
|
"Pellentesque habitant morbi tristique senectus et " +
|
||||||
|
"netus et malesuada fames ac turpis egestas.\n\r\n\r" +
|
||||||
|
"Nulla consectetur, ligula in tincidunt placerat, " +
|
||||||
|
"velit augue consectetur orci, sed mattis libero nunc ut massa.\r" +
|
||||||
|
"Etiam facilisis tempus interdum.";
|
||||||
|
return annotation._getAppearance(
|
||||||
|
partialEvaluator,
|
||||||
|
task,
|
||||||
|
annotationStorage
|
||||||
|
);
|
||||||
|
}, done.fail)
|
||||||
|
.then(appearance => {
|
||||||
|
expect(appearance).toEqual(expectedAppearance);
|
||||||
|
done();
|
||||||
|
}, done.fail);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("ButtonWidgetAnnotation", function () {
|
describe("ButtonWidgetAnnotation", function () {
|
||||||
|
Loading…
Reference in New Issue
Block a user