Skip to content

Commit

Permalink
Support multiline textfields for printing
Browse files Browse the repository at this point in the history
  • Loading branch information
calixteman committed Aug 9, 2020
1 parent 8c162f5 commit cd8bb72
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 7 deletions.
120 changes: 115 additions & 5 deletions src/core/annotation.js
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) {
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
Expand Up @@ -462,7 +462,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
element.setAttribute("value", textContent);
}

element.addEventListener("change", function (event) {
element.addEventListener("input", function (event) {
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
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

0 comments on commit cd8bb72

Please sign in to comment.