Support textfield and choice widgets for printing
This commit is contained in:
parent
63e33a5895
commit
1747d259f9
@ -21,9 +21,9 @@ import {
|
|||||||
AnnotationReplyType,
|
AnnotationReplyType,
|
||||||
AnnotationType,
|
AnnotationType,
|
||||||
assert,
|
assert,
|
||||||
|
escapeString,
|
||||||
isString,
|
isString,
|
||||||
OPS,
|
OPS,
|
||||||
stringToBytes,
|
|
||||||
stringToPDFString,
|
stringToPDFString,
|
||||||
Util,
|
Util,
|
||||||
warn,
|
warn,
|
||||||
@ -33,7 +33,7 @@ import { Dict, isDict, isName, isRef, isStream } from "./primitives.js";
|
|||||||
import { ColorSpace } from "./colorspace.js";
|
import { ColorSpace } from "./colorspace.js";
|
||||||
import { getInheritableProperty } from "./core_utils.js";
|
import { getInheritableProperty } from "./core_utils.js";
|
||||||
import { OperatorList } from "./operator_list.js";
|
import { OperatorList } from "./operator_list.js";
|
||||||
import { Stream } from "./stream.js";
|
import { StringStream } from "./stream.js";
|
||||||
|
|
||||||
class AnnotationFactory {
|
class AnnotationFactory {
|
||||||
/**
|
/**
|
||||||
@ -893,19 +893,199 @@ class WidgetAnnotation extends Annotation {
|
|||||||
if (renderForms) {
|
if (renderForms) {
|
||||||
return Promise.resolve(new OperatorList());
|
return Promise.resolve(new OperatorList());
|
||||||
}
|
}
|
||||||
return super.getOperatorList(
|
|
||||||
evaluator,
|
if (!this._hasText) {
|
||||||
task,
|
return super.getOperatorList(
|
||||||
renderForms,
|
evaluator,
|
||||||
annotationStorage
|
task,
|
||||||
|
renderForms,
|
||||||
|
annotationStorage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._getAppearance(evaluator, task, annotationStorage).then(
|
||||||
|
content => {
|
||||||
|
if (this.appearance && content === null) {
|
||||||
|
return super.getOperatorList(
|
||||||
|
evaluator,
|
||||||
|
task,
|
||||||
|
renderForms,
|
||||||
|
annotationStorage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const operatorList = new OperatorList();
|
||||||
|
|
||||||
|
// Even if there is an appearance stream, ignore it. This is the
|
||||||
|
// behaviour used by Adobe Reader.
|
||||||
|
if (!this.data.defaultAppearance || content === null) {
|
||||||
|
return operatorList;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matrix = [1, 0, 0, 1, 0, 0];
|
||||||
|
const bbox = [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
this.data.rect[2] - this.data.rect[0],
|
||||||
|
this.data.rect[3] - this.data.rect[1],
|
||||||
|
];
|
||||||
|
|
||||||
|
const transform = getTransformMatrix(this.data.rect, bbox, matrix);
|
||||||
|
operatorList.addOp(OPS.beginAnnotation, [
|
||||||
|
this.data.rect,
|
||||||
|
transform,
|
||||||
|
matrix,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const stream = new StringStream(content);
|
||||||
|
return evaluator
|
||||||
|
.getOperatorList({
|
||||||
|
stream,
|
||||||
|
task,
|
||||||
|
resources: this.fieldResources,
|
||||||
|
operatorList,
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
operatorList.addOp(OPS.endAnnotation, []);
|
||||||
|
return operatorList;
|
||||||
|
});
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _getAppearance(evaluator, task, annotationStorage) {
|
||||||
|
const isPassword = this.hasFieldFlag(AnnotationFieldFlag.PASSWORD);
|
||||||
|
if (!annotationStorage || isPassword) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let value = annotationStorage[this.data.id] || "";
|
||||||
|
if (value === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
value = escapeString(value);
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
const fontInfo = await this._getFontData(evaluator, task);
|
||||||
|
const [font, fontName] = fontInfo;
|
||||||
|
let fontSize = fontInfo[2];
|
||||||
|
|
||||||
|
fontSize = this._computeFontSize(font, fontName, fontSize, totalHeight);
|
||||||
|
|
||||||
|
let descent = font.descent;
|
||||||
|
if (isNaN(descent)) {
|
||||||
|
descent = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vPadding = defaultPadding + Math.abs(descent) * fontSize;
|
||||||
|
const defaultAppearance = this.data.defaultAppearance;
|
||||||
|
const alignment = this.data.textAlignment;
|
||||||
|
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` +
|
||||||
|
" ET Q EMC"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderedText = this._renderText(
|
||||||
|
value,
|
||||||
|
font,
|
||||||
|
fontSize,
|
||||||
|
totalWidth,
|
||||||
|
alignment,
|
||||||
|
hPadding,
|
||||||
|
vPadding
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
"/Tx BMC q BT " +
|
||||||
|
defaultAppearance +
|
||||||
|
` 1 0 0 1 0 0 Tm ${renderedText}` +
|
||||||
|
" ET Q EMC"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _getFontData(evaluator, task) {
|
||||||
|
const operatorList = new OperatorList();
|
||||||
|
const initialState = {
|
||||||
|
fontSize: 0,
|
||||||
|
font: null,
|
||||||
|
fontName: null,
|
||||||
|
clone() {
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await evaluator.getOperatorList({
|
||||||
|
stream: new StringStream(this.data.defaultAppearance),
|
||||||
|
task,
|
||||||
|
resources: this.fieldResources,
|
||||||
|
operatorList,
|
||||||
|
initialState,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [initialState.font, initialState.fontName, initialState.fontSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeFontSize(font, fontName, fontSize, height) {
|
||||||
|
if (fontSize === null || fontSize === 0) {
|
||||||
|
const em = font.charsToGlyphs("M", true)[0].width / 1000;
|
||||||
|
// According to https://en.wikipedia.org/wiki/Em_(typography)
|
||||||
|
// an average cap height should be 70% of 1em
|
||||||
|
const capHeight = 0.7 * em;
|
||||||
|
// 1.5 * capHeight * fontSize seems to be a good value for lineHeight
|
||||||
|
fontSize = Math.max(1, Math.floor(height / (1.5 * capHeight)));
|
||||||
|
|
||||||
|
let fontRegex = new RegExp(`/${fontName}\\s+[0-9\.]+\\s+Tf`);
|
||||||
|
if (this.data.defaultAppearance.search(fontRegex) === -1) {
|
||||||
|
// The font size is missing
|
||||||
|
fontRegex = new RegExp(`/${fontName}\\s+Tf`);
|
||||||
|
}
|
||||||
|
this.data.defaultAppearance = this.data.defaultAppearance.replace(
|
||||||
|
fontRegex,
|
||||||
|
`/${fontName} ${fontSize} Tf`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return fontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderText(text, font, fontSize, totalWidth, alignment, hPadding, vPadding) {
|
||||||
|
// We need to get the width of the text in order to align it correctly
|
||||||
|
const glyphs = font.charsToGlyphs(text);
|
||||||
|
const scale = fontSize / 1000;
|
||||||
|
let width = 0;
|
||||||
|
for (const glyph of glyphs) {
|
||||||
|
width += glyph.width * scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
let shift;
|
||||||
|
if (alignment === 1) {
|
||||||
|
// Center
|
||||||
|
shift = (totalWidth - width) / 2;
|
||||||
|
} else if (alignment === 2) {
|
||||||
|
// Right
|
||||||
|
shift = totalWidth - width - hPadding;
|
||||||
|
} else {
|
||||||
|
shift = hPadding;
|
||||||
|
}
|
||||||
|
shift = shift.toFixed(2);
|
||||||
|
vPadding = vPadding.toFixed(2);
|
||||||
|
|
||||||
|
return `${shift} ${vPadding} Td (${text}) Tj`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextWidgetAnnotation extends WidgetAnnotation {
|
class TextWidgetAnnotation extends WidgetAnnotation {
|
||||||
constructor(params) {
|
constructor(params) {
|
||||||
super(params);
|
super(params);
|
||||||
|
|
||||||
|
this._hasText = true;
|
||||||
|
|
||||||
const dict = params.dict;
|
const dict = params.dict;
|
||||||
|
|
||||||
// The field value is always a string.
|
// The field value is always a string.
|
||||||
@ -934,37 +1114,6 @@ class TextWidgetAnnotation extends WidgetAnnotation {
|
|||||||
!this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) &&
|
!this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) &&
|
||||||
this.data.maxLen !== null;
|
this.data.maxLen !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getOperatorList(evaluator, task, renderForms, annotationStorage) {
|
|
||||||
if (renderForms || this.appearance) {
|
|
||||||
return super.getOperatorList(
|
|
||||||
evaluator,
|
|
||||||
task,
|
|
||||||
renderForms,
|
|
||||||
annotationStorage
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const operatorList = new OperatorList();
|
|
||||||
|
|
||||||
// Even if there is an appearance stream, ignore it. This is the
|
|
||||||
// behaviour used by Adobe Reader.
|
|
||||||
if (!this.data.defaultAppearance) {
|
|
||||||
return Promise.resolve(operatorList);
|
|
||||||
}
|
|
||||||
|
|
||||||
const stream = new Stream(stringToBytes(this.data.defaultAppearance));
|
|
||||||
return evaluator
|
|
||||||
.getOperatorList({
|
|
||||||
stream,
|
|
||||||
task,
|
|
||||||
resources: this.fieldResources,
|
|
||||||
operatorList,
|
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
return operatorList;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ButtonWidgetAnnotation extends WidgetAnnotation {
|
class ButtonWidgetAnnotation extends WidgetAnnotation {
|
||||||
@ -1148,6 +1297,7 @@ class ChoiceWidgetAnnotation extends WidgetAnnotation {
|
|||||||
// Process field flags for the display layer.
|
// Process field flags for the display layer.
|
||||||
this.data.combo = this.hasFieldFlag(AnnotationFieldFlag.COMBO);
|
this.data.combo = this.hasFieldFlag(AnnotationFieldFlag.COMBO);
|
||||||
this.data.multiSelect = this.hasFieldFlag(AnnotationFieldFlag.MULTISELECT);
|
this.data.multiSelect = this.hasFieldFlag(AnnotationFieldFlag.MULTISELECT);
|
||||||
|
this._hasText = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -727,10 +727,12 @@ class PartialEvaluator {
|
|||||||
|
|
||||||
handleSetFont(resources, fontArgs, fontRef, operatorList, task, state) {
|
handleSetFont(resources, fontArgs, fontRef, operatorList, task, state) {
|
||||||
// TODO(mack): Not needed?
|
// TODO(mack): Not needed?
|
||||||
var fontName;
|
var fontName,
|
||||||
|
fontSize = 0;
|
||||||
if (fontArgs) {
|
if (fontArgs) {
|
||||||
fontArgs = fontArgs.slice();
|
fontArgs = fontArgs.slice();
|
||||||
fontName = fontArgs[0].name;
|
fontName = fontArgs[0].name;
|
||||||
|
fontSize = fontArgs[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.loadFont(fontName, fontRef, resources)
|
return this.loadFont(fontName, fontRef, resources)
|
||||||
@ -763,6 +765,8 @@ class PartialEvaluator {
|
|||||||
})
|
})
|
||||||
.then(translated => {
|
.then(translated => {
|
||||||
state.font = translated.font;
|
state.font = translated.font;
|
||||||
|
state.fontSize = fontSize;
|
||||||
|
state.fontName = fontName;
|
||||||
translated.send(this.handler);
|
translated.send(this.handler);
|
||||||
return translated.loadedName;
|
return translated.loadedName;
|
||||||
});
|
});
|
||||||
|
@ -441,6 +441,8 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const TEXT_ALIGNMENT = ["left", "center", "right"];
|
const TEXT_ALIGNMENT = ["left", "center", "right"];
|
||||||
|
const storage = this.annotationStorage;
|
||||||
|
const id = this.data.id;
|
||||||
|
|
||||||
this.container.className = "textWidgetAnnotation";
|
this.container.className = "textWidgetAnnotation";
|
||||||
|
|
||||||
@ -449,15 +451,21 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
// NOTE: We cannot set the values using `element.value` below, since it
|
// NOTE: We cannot set the values using `element.value` below, since it
|
||||||
// prevents the AnnotationLayer rasterizer in `test/driver.js`
|
// prevents the AnnotationLayer rasterizer in `test/driver.js`
|
||||||
// from parsing the elements correctly for the reference tests.
|
// from parsing the elements correctly for the reference tests.
|
||||||
|
const textContent = storage.getOrCreateValue(id, this.data.fieldValue);
|
||||||
|
|
||||||
if (this.data.multiLine) {
|
if (this.data.multiLine) {
|
||||||
element = document.createElement("textarea");
|
element = document.createElement("textarea");
|
||||||
element.textContent = this.data.fieldValue;
|
element.textContent = textContent;
|
||||||
} else {
|
} else {
|
||||||
element = document.createElement("input");
|
element = document.createElement("input");
|
||||||
element.type = "text";
|
element.type = "text";
|
||||||
element.setAttribute("value", this.data.fieldValue);
|
element.setAttribute("value", textContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
element.addEventListener("change", function (event) {
|
||||||
|
storage.setValue(id, event.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
element.disabled = this.data.readOnly;
|
element.disabled = this.data.readOnly;
|
||||||
element.name = this.data.fieldName;
|
element.name = this.data.fieldName;
|
||||||
|
|
||||||
@ -654,6 +662,8 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
this.container.className = "choiceWidgetAnnotation";
|
this.container.className = "choiceWidgetAnnotation";
|
||||||
|
const storage = this.annotationStorage;
|
||||||
|
const id = this.data.id;
|
||||||
|
|
||||||
const selectElement = document.createElement("select");
|
const selectElement = document.createElement("select");
|
||||||
selectElement.disabled = this.data.readOnly;
|
selectElement.disabled = this.data.readOnly;
|
||||||
@ -674,10 +684,17 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
optionElement.value = option.exportValue;
|
optionElement.value = option.exportValue;
|
||||||
if (this.data.fieldValue.includes(option.displayValue)) {
|
if (this.data.fieldValue.includes(option.displayValue)) {
|
||||||
optionElement.setAttribute("selected", true);
|
optionElement.setAttribute("selected", true);
|
||||||
|
storage.setValue(id, option.displayValue);
|
||||||
}
|
}
|
||||||
selectElement.appendChild(optionElement);
|
selectElement.appendChild(optionElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectElement.addEventListener("change", function (event) {
|
||||||
|
const options = event.target.options;
|
||||||
|
const value = options[options.selectedIndex].text;
|
||||||
|
storage.setValue(id, value);
|
||||||
|
});
|
||||||
|
|
||||||
this.container.appendChild(selectElement);
|
this.container.appendChild(selectElement);
|
||||||
return this.container;
|
return this.container;
|
||||||
}
|
}
|
||||||
|
@ -793,6 +793,12 @@ function stringToPDFString(str) {
|
|||||||
return strBuf.join("");
|
return strBuf.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeString(str) {
|
||||||
|
// replace "(", ")" and "\" by "\(", "\)" and "\\"
|
||||||
|
// in order to write it in a PDF file.
|
||||||
|
return str.replace(/([\(\)\\])/g, "\\$1");
|
||||||
|
}
|
||||||
|
|
||||||
function stringToUTF8String(str) {
|
function stringToUTF8String(str) {
|
||||||
return decodeURIComponent(escape(str));
|
return decodeURIComponent(escape(str));
|
||||||
}
|
}
|
||||||
@ -927,6 +933,7 @@ export {
|
|||||||
bytesToString,
|
bytesToString,
|
||||||
createPromiseCapability,
|
createPromiseCapability,
|
||||||
createObjectURL,
|
createObjectURL,
|
||||||
|
escapeString,
|
||||||
getVerbosityLevel,
|
getVerbosityLevel,
|
||||||
info,
|
info,
|
||||||
isArrayBuffer,
|
isArrayBuffer,
|
||||||
|
@ -30,7 +30,7 @@ import {
|
|||||||
stringToUTF8String,
|
stringToUTF8String,
|
||||||
} from "../../src/shared/util.js";
|
} from "../../src/shared/util.js";
|
||||||
import { createIdFactory, XRefMock } from "./test_utils.js";
|
import { createIdFactory, XRefMock } from "./test_utils.js";
|
||||||
import { Dict, Name, Ref } from "../../src/core/primitives.js";
|
import { Dict, Name, Ref, RefSetCache } from "../../src/core/primitives.js";
|
||||||
import { Lexer, Parser } from "../../src/core/parser.js";
|
import { Lexer, Parser } from "../../src/core/parser.js";
|
||||||
import { PartialEvaluator } from "../../src/core/evaluator.js";
|
import { PartialEvaluator } from "../../src/core/evaluator.js";
|
||||||
import { StringStream } from "../../src/core/stream.js";
|
import { StringStream } from "../../src/core/stream.js";
|
||||||
@ -82,6 +82,7 @@ describe("annotation", function () {
|
|||||||
handler: new HandlerMock(),
|
handler: new HandlerMock(),
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
idFactory: createIdFactory(/* pageIndex = */ 0),
|
idFactory: createIdFactory(/* pageIndex = */ 0),
|
||||||
|
fontCache: new RefSetCache(),
|
||||||
});
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -1385,18 +1386,35 @@ describe("annotation", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("TextWidgetAnnotation", function () {
|
describe("TextWidgetAnnotation", function () {
|
||||||
let textWidgetDict;
|
let textWidgetDict, fontRefObj;
|
||||||
|
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
textWidgetDict = new Dict();
|
textWidgetDict = new Dict();
|
||||||
textWidgetDict.set("Type", Name.get("Annot"));
|
textWidgetDict.set("Type", Name.get("Annot"));
|
||||||
textWidgetDict.set("Subtype", Name.get("Widget"));
|
textWidgetDict.set("Subtype", Name.get("Widget"));
|
||||||
textWidgetDict.set("FT", Name.get("Tx"));
|
textWidgetDict.set("FT", Name.get("Tx"));
|
||||||
|
|
||||||
|
const helvDict = new Dict();
|
||||||
|
helvDict.set("BaseFont", Name.get("Helvetica"));
|
||||||
|
helvDict.set("Type", Name.get("Font"));
|
||||||
|
helvDict.set("Subtype", Name.get("Type1"));
|
||||||
|
|
||||||
|
const fontRef = Ref.get(314, 0);
|
||||||
|
fontRefObj = { ref: fontRef, data: helvDict };
|
||||||
|
const resourceDict = new Dict();
|
||||||
|
const fontDict = new Dict();
|
||||||
|
fontDict.set("Helv", fontRef);
|
||||||
|
resourceDict.set("Font", fontDict);
|
||||||
|
|
||||||
|
textWidgetDict.set("DA", "/Helv 5 Tf");
|
||||||
|
textWidgetDict.set("DR", resourceDict);
|
||||||
|
textWidgetDict.set("Rect", [0, 0, 32, 10]);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
textWidgetDict = null;
|
textWidgetDict = fontRefObj = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle unknown text alignment, maximum length and flags", function (done) {
|
it("should handle unknown text alignment, maximum length and flags", function (done) {
|
||||||
@ -1552,6 +1570,109 @@ describe("annotation", function () {
|
|||||||
}
|
}
|
||||||
promise.then(done, done.fail);
|
promise.then(done, done.fail);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should render regular text for printing", function (done) {
|
||||||
|
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] = "test\\print";
|
||||||
|
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 0 Tm" +
|
||||||
|
" 2.00 2.00 Td (test\\\\print) Tj ET Q EMC"
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
}, done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render auto-sized text for printing", function (done) {
|
||||||
|
textWidgetDict.set("DA", "/Helv 0 Tf");
|
||||||
|
|
||||||
|
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] = "test (print)";
|
||||||
|
return annotation._getAppearance(
|
||||||
|
partialEvaluator,
|
||||||
|
task,
|
||||||
|
annotationStorage
|
||||||
|
);
|
||||||
|
}, done.fail)
|
||||||
|
.then(appearance => {
|
||||||
|
expect(appearance).toEqual(
|
||||||
|
"/Tx BMC q BT /Helv 11 Tf 1 0 0 1 0 0 Tm" +
|
||||||
|
" 2.00 2.00 Td (test \\(print\\)) Tj ET Q EMC"
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
}, done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not render a password for printing", function (done) {
|
||||||
|
textWidgetDict.set("Ff", AnnotationFieldFlag.PASSWORD);
|
||||||
|
|
||||||
|
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] = "mypassword";
|
||||||
|
return annotation._getAppearance(
|
||||||
|
partialEvaluator,
|
||||||
|
task,
|
||||||
|
annotationStorage
|
||||||
|
);
|
||||||
|
}, done.fail)
|
||||||
|
.then(appearance => {
|
||||||
|
expect(appearance).toEqual(null);
|
||||||
|
done();
|
||||||
|
}, done.fail);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("ButtonWidgetAnnotation", function () {
|
describe("ButtonWidgetAnnotation", function () {
|
||||||
@ -1861,18 +1982,35 @@ describe("annotation", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("ChoiceWidgetAnnotation", function () {
|
describe("ChoiceWidgetAnnotation", function () {
|
||||||
let choiceWidgetDict;
|
let choiceWidgetDict, fontRefObj;
|
||||||
|
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
choiceWidgetDict = new Dict();
|
choiceWidgetDict = new Dict();
|
||||||
choiceWidgetDict.set("Type", Name.get("Annot"));
|
choiceWidgetDict.set("Type", Name.get("Annot"));
|
||||||
choiceWidgetDict.set("Subtype", Name.get("Widget"));
|
choiceWidgetDict.set("Subtype", Name.get("Widget"));
|
||||||
choiceWidgetDict.set("FT", Name.get("Ch"));
|
choiceWidgetDict.set("FT", Name.get("Ch"));
|
||||||
|
|
||||||
|
const helvDict = new Dict();
|
||||||
|
helvDict.set("BaseFont", Name.get("Helvetica"));
|
||||||
|
helvDict.set("Type", Name.get("Font"));
|
||||||
|
helvDict.set("Subtype", Name.get("Type1"));
|
||||||
|
|
||||||
|
const fontRef = Ref.get(314, 0);
|
||||||
|
fontRefObj = { ref: fontRef, data: helvDict };
|
||||||
|
const resourceDict = new Dict();
|
||||||
|
const fontDict = new Dict();
|
||||||
|
fontDict.set("Helv", fontRef);
|
||||||
|
resourceDict.set("Font", fontDict);
|
||||||
|
|
||||||
|
choiceWidgetDict.set("DA", "/Helv 5 Tf");
|
||||||
|
choiceWidgetDict.set("DR", resourceDict);
|
||||||
|
choiceWidgetDict.set("Rect", [0, 0, 32, 10]);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
choiceWidgetDict = null;
|
choiceWidgetDict = fontRefObj = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle missing option arrays", function (done) {
|
it("should handle missing option arrays", function (done) {
|
||||||
@ -2128,6 +2266,40 @@ describe("annotation", function () {
|
|||||||
done();
|
done();
|
||||||
}, done.fail);
|
}, done.fail);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should render choice for printing", function (done) {
|
||||||
|
const choiceWidgetRef = Ref.get(271, 0);
|
||||||
|
const xref = new XRefMock([
|
||||||
|
{ ref: choiceWidgetRef, data: choiceWidgetDict },
|
||||||
|
fontRefObj,
|
||||||
|
]);
|
||||||
|
const task = new WorkerTask("test print");
|
||||||
|
partialEvaluator.xref = xref;
|
||||||
|
|
||||||
|
AnnotationFactory.create(
|
||||||
|
xref,
|
||||||
|
choiceWidgetRef,
|
||||||
|
pdfManagerMock,
|
||||||
|
idFactoryMock
|
||||||
|
)
|
||||||
|
.then(annotation => {
|
||||||
|
const id = annotation.data.id;
|
||||||
|
const annotationStorage = {};
|
||||||
|
annotationStorage[id] = "a value";
|
||||||
|
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 0 Tm" +
|
||||||
|
" 2.00 2.00 Td (a value) Tj ET Q EMC"
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
}, done.fail);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("LineAnnotation", function () {
|
describe("LineAnnotation", function () {
|
||||||
|
@ -66,6 +66,10 @@ function buildGetDocumentParams(filename, options) {
|
|||||||
class XRefMock {
|
class XRefMock {
|
||||||
constructor(array) {
|
constructor(array) {
|
||||||
this._map = Object.create(null);
|
this._map = Object.create(null);
|
||||||
|
this.stats = {
|
||||||
|
streamTypes: Object.create(null),
|
||||||
|
fontTypes: Object.create(null),
|
||||||
|
};
|
||||||
|
|
||||||
for (const key in array) {
|
for (const key in array) {
|
||||||
const obj = array[key];
|
const obj = array[key];
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
bytesToString,
|
bytesToString,
|
||||||
createPromiseCapability,
|
createPromiseCapability,
|
||||||
createValidAbsoluteUrl,
|
createValidAbsoluteUrl,
|
||||||
|
escapeString,
|
||||||
isArrayBuffer,
|
isArrayBuffer,
|
||||||
isBool,
|
isBool,
|
||||||
isNum,
|
isNum,
|
||||||
@ -314,4 +315,12 @@ describe("util", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("escapeString", function () {
|
||||||
|
it("should escape (, ) and \\", function () {
|
||||||
|
expect(escapeString("((a\\a))(b(b\\b)b)")).toEqual(
|
||||||
|
"\\(\\(a\\\\a\\)\\)\\(b\\(b\\\\b\\)b\\)"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user