Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiline textfields for printing #12176

Merged
merged 1 commit into from
Aug 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
120 changes: 115 additions & 5 deletions src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -958,11 +958,10 @@ class WidgetAnnotation extends Annotation {
if (!annotationStorage || isPassword) {
return null;
}
let value = annotationStorage[this.data.id] || "";
const value = annotationStorage[this.data.id];
if (value === "") {
return null;
return "";
}
value = escapeString(value);

const defaultPadding = 2;
const hPadding = defaultPadding;
Expand All @@ -983,12 +982,27 @@ class WidgetAnnotation extends Annotation {
const vPadding = defaultPadding + Math.abs(descent) * fontSize;
const defaultAppearance = this.data.defaultAppearance;
const alignment = this.data.textAlignment;

if (this.data.multiLine) {
calixteman marked this conversation as resolved.
Show resolved Hide resolved
return this._getMultilineAppearance(
defaultAppearance,
value,
font,
fontSize,
totalWidth,
totalHeight,
alignment,
hPadding,
vPadding
);
}

if (alignment === 0 || alignment > 2) {
// Left alignment: nothing to do
return (
"/Tx BMC q BT " +
defaultAppearance +
` 1 0 0 1 ${hPadding} ${vPadding} Tm (${value}) Tj` +
` 1 0 0 1 ${hPadding} ${vPadding} Tm (${escapeString(value)}) Tj` +
" ET Q EMC"
);
}
Expand Down Expand Up @@ -1076,7 +1090,7 @@ class WidgetAnnotation extends Annotation {
shift = shift.toFixed(2);
vPadding = vPadding.toFixed(2);

return `${shift} ${vPadding} Td (${text}) Tj`;
return `${shift} ${vPadding} Td (${escapeString(text)}) Tj`;
}
}

Expand Down Expand Up @@ -1114,6 +1128,102 @@ class TextWidgetAnnotation extends WidgetAnnotation {
!this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) &&
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 {
Expand Down
4 changes: 2 additions & 2 deletions src/display/annotation_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
element.setAttribute("value", textContent);
}

element.addEventListener("change", function (event) {
element.addEventListener("input", function (event) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is a problem here, shouldn't it also be changed for checkbox and radio button widgets from previous PRs? If so, please change that in a separate commit.

storage.setValue(id, event.target.value);
});

Expand Down Expand Up @@ -689,7 +689,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
selectElement.appendChild(optionElement);
}

selectElement.addEventListener("change", function (event) {
selectElement.addEventListener("input", function (event) {
const options = event.target.options;
const value = options[options.selectedIndex].text;
storage.setValue(id, value);
Expand Down
108 changes: 108 additions & 0 deletions test/unit/annotation_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1673,6 +1673,114 @@ describe("annotation", function () {
done();
}, 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 () {
Expand Down