Merge pull request #12582 from calixteman/doc

JS -- Implement doc object
This commit is contained in:
calixteman 2020-11-10 16:26:38 +01:00 committed by GitHub
commit 83658c974d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 1172 additions and 15 deletions

View File

@ -0,0 +1,26 @@
/* Copyright 2020 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const ZoomType = Object.freeze({
none: "NoVary",
fitP: "FitPage",
fitW: "FitWidth",
fitH: "FitHeight",
fitV: "FitVisibleWidth",
pref: "Preferred",
refW: "ReflowWidth",
});
export { ZoomType };

View File

@ -14,28 +14,678 @@
*/
import { PDFObject } from "./pdf_object.js";
import { PrintParams } from "./print_params.js";
import { ZoomType } from "./constants.js";
class InfoProxyHandler {
static get(obj, prop) {
return obj[prop.toLowerCase()];
}
static set(obj, prop, value) {
throw new Error(`doc.info.${prop} is read-only`);
}
}
class Doc extends PDFObject {
constructor(data) {
super(data);
this.baseURL = data.baseURL || "";
this.calculate = true;
this.delay = false;
this.dirty = false;
this.disclosed = false;
this.media = undefined;
this.metadata = data.metadata;
this.noautocomplete = undefined;
this.nocache = undefined;
this.spellDictionaryOrder = [];
this.spellLanguageOrder = [];
this._printParams = null;
this._fields = Object.create(null);
this._fields = new Map();
this._fieldNames = [];
this._event = null;
this._author = data.Author || "";
this._creator = data.Creator || "";
this._creationDate = this._getDate(data.CreationDate) || null;
this._docID = data.docID || ["", ""];
this._documentFileName = data.filename || "";
this._filesize = data.filesize || 0;
this._keywords = data.Keywords || "";
this._layout = data.layout || "";
this._modDate = this._getDate(data.ModDate) || null;
this._numFields = 0;
this._numPages = data.numPages || 1;
this._pageNum = data.pageNum || 0;
this._producer = data.Producer || "";
this._subject = data.Subject || "";
this._title = data.Title || "";
this._URL = data.URL || "";
// info has case insensitive properties
// and they're are read-only.
this._info = new Proxy(
{
title: this.title,
author: this.author,
subject: this.subject,
keywords: this.keywords,
creator: this.creator,
producer: this.producer,
creationdate: this._creationDate,
moddate: this._modDate,
trapped: data.Trapped || "Unknown",
},
InfoProxyHandler
);
this._zoomType = ZoomType.none;
this._zoom = data.zoom || 100;
}
_addField(name, field) {
this._fields.set(name, field);
this._fieldNames.push(name);
this._numFields++;
}
_getDate(date) {
// date format is D:YYYYMMDDHHmmSS[OHH'mm']
if (!date || date.length < 15 || !date.startsWith("D:")) {
return date;
}
date = date.substring(2);
const year = date.substring(0, 4);
const month = date.substring(4, 6);
const day = date.substring(6, 8);
const hour = date.substring(8, 10);
const minute = date.substring(10, 12);
const o = date.charAt(12);
let second, offsetPos;
if (o === "Z" || o === "+" || o === "-") {
second = "00";
offsetPos = 12;
} else {
second = date.substring(12, 14);
offsetPos = 14;
}
const offset = date.substring(offsetPos).replaceAll("'", "");
return new Date(
`${year}-${month}-${day}T${hour}:${minute}:${second}${offset}`
);
}
get author() {
return this._author;
}
set author(_) {
throw new Error("doc.author is read-only");
}
get bookmarkRoot() {
return undefined;
}
set bookmarkRoot(_) {
throw new Error("doc.bookmarkRoot is read-only");
}
get creator() {
return this._creator;
}
set creator(_) {
throw new Error("doc.creator is read-only");
}
get dataObjects() {
return [];
}
set dataObjects(_) {
throw new Error("doc.dataObjects is read-only");
}
get docID() {
return this._docID;
}
set docID(_) {
throw new Error("doc.docID is read-only");
}
get documentFileName() {
return this._documentFileName;
}
set documentFileName(_) {
throw new Error("doc.documentFileName is read-only");
}
get dynamicXFAForm() {
return false;
}
set dynamicXFAForm(_) {
throw new Error("doc.dynamicXFAForm is read-only");
}
get external() {
return true;
}
set external(_) {
throw new Error("doc.external is read-only");
}
get filesize() {
return this._filesize;
}
set filesize(_) {
throw new Error("doc.filesize is read-only");
}
get hidden() {
return false;
}
set hidden(_) {
throw new Error("doc.hidden is read-only");
}
get hostContainer() {
return undefined;
}
set hostContainer(_) {
throw new Error("doc.hostContainer is read-only");
}
get icons() {
return undefined;
}
set icons(_) {
throw new Error("doc.icons is read-only");
}
get info() {
return this._info;
}
set info(_) {
throw new Error("doc.info is read-only");
}
get innerAppWindowRect() {
return [0, 0, 0, 0];
}
set innerAppWindowRect(_) {
throw new Error("doc.innerAppWindowRect is read-only");
}
get innerDocWindowRect() {
return [0, 0, 0, 0];
}
set innerDocWindowRect(_) {
throw new Error("doc.innerDocWindowRect is read-only");
}
get isModal() {
return false;
}
set isModal(_) {
throw new Error("doc.isModal is read-only");
}
get keywords() {
return this._keywords;
}
set keywords(_) {
throw new Error("doc.keywords is read-only");
}
get layout() {
return this._layout;
}
set layout(value) {
if (typeof value !== "string") {
return;
}
if (
value !== "SinglePage" &&
value !== "OneColumn" &&
value !== "TwoColumnLeft" &&
value !== "TwoPageLeft" &&
value !== "TwoColumnRight" &&
value !== "TwoPageRight"
) {
value = "SinglePage";
}
this._send({ command: "layout", value });
this._layout = value;
}
get modDate() {
return this._modDate;
}
set modDate(_) {
throw new Error("doc.modDate is read-only");
}
get mouseX() {
return 0;
}
set mouseX(_) {
throw new Error("doc.mouseX is read-only");
}
get mouseY() {
return 0;
}
set mouseY(_) {
throw new Error("doc.mouseY is read-only");
}
get numFields() {
return this._numFields;
}
set numFields(_) {
throw new Error("doc.numFields is read-only");
}
get numPages() {
return this._numPages;
}
set numPages(_) {
throw new Error("doc.numPages is read-only");
}
get numTemplates() {
return 0;
}
set numTemplates(_) {
throw new Error("doc.numTemplates is read-only");
}
get outerAppWindowRect() {
return [0, 0, 0, 0];
}
set outerAppWindowRect(_) {
throw new Error("doc.outerAppWindowRect is read-only");
}
get outerDocWindowRect() {
return [0, 0, 0, 0];
}
set outerDocWindowRect(_) {
throw new Error("doc.outerDocWindowRect is read-only");
}
get pageNum() {
return this._pageNum;
}
set pageNum(value) {
if (typeof value !== "number" || value < 0 || value >= this._numPages) {
return;
}
this._send({ command: "page-num", value });
this._pageNum = value;
}
get pageWindowRect() {
return [0, 0, 0, 0];
}
set pageWindowRect(_) {
throw new Error("doc.pageWindowRect is read-only");
}
get path() {
return "";
}
set path(_) {
throw new Error("doc.path is read-only");
}
get permStatusReady() {
return true;
}
set permStatusReady(_) {
throw new Error("doc.permStatusReady is read-only");
}
get producer() {
return this._producer;
}
set producer(_) {
throw new Error("doc.producer is read-only");
}
get requiresFullSave() {
return false;
}
set requiresFullSave(_) {
throw new Error("doc.requiresFullSave is read-only");
}
get securityHandler() {
return null;
}
set securityHandler(_) {
throw new Error("doc.securityHandler is read-only");
}
get selectedAnnots() {
return [];
}
set selectedAnnots(_) {
throw new Error("doc.selectedAnnots is read-only");
}
get sounds() {
return [];
}
set sounds(_) {
throw new Error("doc.sounds is read-only");
}
get subject() {
return this._subject;
}
set subject(_) {
throw new Error("doc.subject is read-only");
}
get templates() {
return [];
}
set templates(_) {
throw new Error("doc.templates is read-only");
}
get title() {
return this._title;
}
set title(_) {
throw new Error("doc.title is read-only");
}
get URL() {
return this._URL;
}
set URL(_) {
throw new Error("doc.URL is read-only");
}
get viewState() {
return undefined;
}
set viewState(_) {
throw new Error("doc.viewState is read-only");
}
get xfa() {
return this._xfa;
}
set xfa(_) {
throw new Error("doc.xfa is read-only");
}
get XFAForeground() {
return false;
}
set XFAForeground(_) {
throw new Error("doc.XFAForeground is read-only");
}
get zoomType() {
return this._zoomType;
}
set zoomType(type) {
if (typeof type !== "string") {
return;
}
switch (type) {
case ZoomType.none:
this._send({ command: "zoom", value: 1 });
break;
case ZoomType.fitP:
this._send({ command: "zoom", value: "page-fit" });
break;
case ZoomType.fitW:
this._send({ command: "zoom", value: "page-width" });
break;
case ZoomType.fitH:
this._send({ command: "zoom", value: "page-height" });
break;
case ZoomType.fitV:
this._send({ command: "zoom", value: "auto" });
break;
case ZoomType.pref:
case ZoomType.refW:
break;
default:
return;
}
this._zoomType = type;
}
get zoom() {
return this._zoom;
}
set zoom(value) {
if (typeof value !== "number" || value < 8.33 || value > 6400) {
return;
}
this._send({ command: "zoom", value: value / 100 });
}
addAnnot() {
/* Not implemented */
}
addField() {
/* Not implemented */
}
addIcon() {
/* Not implemented */
}
addLink() {
/* Not implemented */
}
addRecipientListCryptFilter() {
/* Not implemented */
}
addRequirement() {
/* Not implemented */
}
addScript() {
/* Not implemented */
}
addThumbnails() {
/* Not implemented */
}
addWatermarkFromFile() {
/* Not implemented */
}
addWatermarkFromText() {
/* Not implemented */
}
addWeblinks() {
/* Not implemented */
}
bringToFront() {
/* Not implemented */
}
calculateNow() {
this._eventDispatcher.calculateNow();
}
closeDoc() {
/* Not implemented */
}
colorConvertPage() {
/* Not implemented */
}
createDataObject() {
/* Not implemented */
}
createTemplate() {
/* Not implemented */
}
deletePages() {
/* Not implemented */
}
deleteSound() {
/* Not implemented */
}
embedDocAsDataObject() {
/* Not implemented */
}
embedOutputIntent() {
/* Not implemented */
}
encryptForRecipients() {
/* Not implemented */
}
encryptUsingPolicy() {
/* Not implemented */
}
exportAsFDF() {
/* Not implemented */
}
exportAsFDFStr() {
/* Not implemented */
}
exportAsText() {
/* Not implemented */
}
exportAsXFDF() {
/* Not implemented */
}
exportAsXFDFStr() {
/* Not implemented */
}
exportDataObject() {
/* Not implemented */
}
exportXFAData() {
/* Not implemented */
}
extractPages() {
/* Not implemented */
}
flattenPages() {
/* Not implemented */
}
getAnnot() {
/* TODO */
}
getAnnots() {
/* TODO */
}
getAnnot3D() {
/* Not implemented */
}
getAnnots3D() {
/* Not implemented */
}
getColorConvertAction() {
/* Not implemented */
}
getDataObject() {
/* Not implemented */
}
getDataObjectContents() {
/* Not implemented */
}
getField(cName) {
if (typeof cName !== "string") {
throw new TypeError("Invalid field name: must be a string");
}
if (cName in this._fields) {
return this._fields[cName];
const searchedField = this._fields.get(cName);
if (searchedField) {
return searchedField;
}
for (const [name, field] of Object.entries(this._fields)) {
for (const [name, field] of this._fields.entries()) {
if (name.includes(cName)) {
return field;
}
@ -43,6 +693,298 @@ class Doc extends PDFObject {
return undefined;
}
getIcon() {
/* Not implemented */
}
getLegalWarnings() {
/* Not implemented */
}
getLinks() {
/* Not implemented */
}
getNthFieldName(nIndex) {
if (typeof nIndex !== "number") {
throw new TypeError("Invalid field index: must be a number");
}
if (0 <= nIndex && nIndex < this.numFields) {
return this._fieldNames[Math.trunc(nIndex)];
}
return null;
}
getNthTemplate() {
return null;
}
getOCGs() {
/* Not implemented */
}
getOCGOrder() {
/* Not implemented */
}
getPageBox() {
/* TODO */
}
getPageLabel() {
/* TODO */
}
getPageNthWord() {
/* TODO or not */
}
getPageNthWordQuads() {
/* TODO or not */
}
getPageNumWords() {
/* TODO or not */
}
getPageRotation() {
/* TODO */
}
getPageTransition() {
/* Not implemented */
}
getPrintParams() {
if (!this._printParams) {
this._printParams = new PrintParams({ lastPage: this._numPages - 1 });
}
return this._printParams;
}
getSound() {
/* Not implemented */
}
getTemplate() {
/* Not implemented */
}
getURL() {
/* Not implemented because unsafe */
}
gotoNamedDest() {
/* TODO */
}
importAnFDF() {
/* Not implemented */
}
importAnXFDF() {
/* Not implemented */
}
importDataObject() {
/* Not implemented */
}
importIcon() {
/* Not implemented */
}
importSound() {
/* Not implemented */
}
importTextData() {
/* Not implemented */
}
importXFAData() {
/* Not implemented */
}
insertPages() {
/* Not implemented */
}
mailDoc() {
/* TODO or not */
}
mailForm() {
/* TODO or not */
}
movePage() {
/* Not implemented */
}
newPage() {
/* Not implemented */
}
openDataObject() {
/* Not implemented */
}
print(
bUI = true,
nStart = 0,
nEnd = -1,
bSilent = false,
bShrinkToFit = false,
bPrintAsImage = false,
bReverse = false,
bAnnotations = true,
printParams = null
) {
// TODO: for now just use nStart and nEnd
// so need to see how to deal with the other params
// (if possible)
if (printParams) {
nStart = printParams.firstPage;
nEnd = printParams.lastPage;
}
if (typeof nStart === "number") {
nStart = Math.max(0, Math.trunc(nStart));
} else {
nStart = 0;
}
if (typeof nEnd === "number") {
nEnd = Math.max(0, Math.trunc(nEnd));
} else {
nEnd = -1;
}
this._send({ id: "print", start: nStart, end: nEnd });
}
removeDataObject() {
/* Not implemented */
}
removeField() {
/* TODO or not */
}
removeIcon() {
/* Not implemented */
}
removeLinks() {
/* Not implemented */
}
removeRequirement() {
/* Not implemented */
}
removeScript() {
/* Not implemented */
}
removeTemplate() {
/* Not implemented */
}
removeThumbnails() {
/* Not implemented */
}
removeWeblinks() {
/* Not implemented */
}
replacePages() {
/* Not implemented */
}
resetForm(aFields = null) {
let mustCalculate = false;
if (aFields) {
for (const fieldName of aFields) {
const field = this.getField(fieldName);
if (field) {
field.value = field.defaultValue;
mustCalculate = true;
}
}
} else {
mustCalculate = this._fields.size !== 0;
for (const field of this._fields.values()) {
field.value = field.defaultValue;
}
}
if (mustCalculate) {
this.calculateNow();
}
}
saveAs() {
/* Not implemented */
}
scroll() {
/* TODO */
}
selectPageNthWord() {
/* TODO */
}
setAction() {
/* TODO */
}
setDataObjectContents() {
/* Not implemented */
}
setOCGOrder() {
/* Not implemented */
}
setPageAction() {
/* TODO */
}
setPageBoxes() {
/* Not implemented */
}
setPageLabels() {
/* Not implemented */
}
setPageRotations() {
/* TODO */
}
setPageTabOrder() {
/* Not implemented */
}
setPageTransitions() {
/* Not implemented */
}
spawnPageFromTemplate() {
/* Not implemented */
}
submitForm() {
/* TODO or not */
}
syncAnnotScan() {
/* Not implemented */
}
}
export { Doc };

View File

@ -20,11 +20,15 @@ import { Doc } from "./doc.js";
import { Field } from "./field.js";
import { ProxyHandler } from "./proxy.js";
import { Util } from "./util.js";
import { ZoomType } from "./constants.js";
function initSandbox(data, extra, out) {
const proxyHandler = new ProxyHandler(data.dispatchEventName);
const { send, crackURL } = extra;
const doc = new Doc({ send });
const doc = new Doc({
send,
...data.docInfo,
});
const _document = { obj: doc, wrapped: new Proxy(doc, proxyHandler) };
const app = new App({
send,
@ -39,7 +43,8 @@ function initSandbox(data, extra, out) {
obj.send = send;
obj.doc = _document.wrapped;
const field = new Field(obj);
const wrapped = (doc._fields[name] = new Proxy(field, proxyHandler));
const wrapped = new Proxy(field, proxyHandler);
doc._addField(name, wrapped);
app._objects[obj.id] = { obj: field, wrapped };
}
@ -47,6 +52,7 @@ function initSandbox(data, extra, out) {
out.app = new Proxy(app, proxyHandler);
out.console = new Proxy(new Console({ send }), proxyHandler);
out.util = new Proxy(util, proxyHandler);
out.zoomtype = ZoomType;
for (const name of Object.getOwnPropertyNames(AForm.prototype)) {
if (name.startsWith("AF")) {
out[name] = aform[name].bind(aform);

View File

@ -0,0 +1,146 @@
/* Copyright 2020 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class PrintParams {
constructor(data) {
this.binaryOk = true;
this.bitmapDPI = 150;
this.booklet = {
binding: 0,
duplexMode: 0,
subsetFrom: 0,
subsetTo: -1,
};
this.colorOverride = 0;
this.colorProfile = "";
this.constants = Object.freeze({
bookletBindings: Object.freeze({
Left: 0,
Right: 1,
LeftTall: 2,
RightTall: 3,
}),
bookletDuplexMode: Object.freeze({
BothSides: 0,
FrontSideOnly: 1,
BasicSideOnly: 2,
}),
colorOverrides: Object.freeze({
auto: 0,
gray: 1,
mono: 2,
}),
fontPolicies: Object.freeze({
everyPage: 0,
jobStart: 1,
pageRange: 2,
}),
handling: Object.freeze({
none: 0,
fit: 1,
shrink: 2,
tileAll: 3,
tileLarge: 4,
nUp: 5,
booklet: 6,
}),
interactionLevel: Object.freeze({
automatic: 0,
full: 1,
silent: 2,
}),
nUpPageOrders: Object.freeze({
Horizontal: 0,
HorizontalReversed: 1,
Vertical: 2,
}),
printContents: Object.freeze({
doc: 0,
docAndComments: 1,
formFieldsOnly: 2,
}),
flagValues: Object.freeze({
applyOverPrint: 1,
applySoftProofSettings: 1 << 1,
applyWorkingColorSpaces: 1 << 2,
emitHalftones: 1 << 3,
emitPostScriptXObjects: 1 << 4,
emitFormsAsPSForms: 1 << 5,
maxJP2KRes: 1 << 6,
setPageSize: 1 << 7,
suppressBG: 1 << 8,
suppressCenter: 1 << 9,
suppressCJKFontSubst: 1 << 10,
suppressCropClip: 1 << 1,
suppressRotate: 1 << 12,
suppressTransfer: 1 << 13,
suppressUCR: 1 << 14,
useTrapAnnots: 1 << 15,
usePrintersMarks: 1 << 16,
}),
rasterFlagValues: Object.freeze({
textToOutline: 1,
strokesToOutline: 1 << 1,
allowComplexClip: 1 << 2,
preserveOverprint: 1 << 3,
}),
subsets: Object.freeze({
all: 0,
even: 1,
odd: 2,
}),
tileMarks: Object.freeze({
none: 0,
west: 1,
east: 2,
}),
usages: Object.freeze({
auto: 0,
use: 1,
noUse: 2,
}),
});
this.downloadFarEastFonts = false;
this.fileName = "";
this.firstPage = 0;
this.flags = 0;
this.fontPolicy = 0;
this.gradientDPI = 150;
this.interactive = 1;
this.lastPage = data.lastPage;
this.npUpAutoRotate = false;
this.npUpNumPagesH = 2;
this.npUpNumPagesV = 2;
this.npUpPageBorder = false;
this.npUpPageOrder = 0;
this.pageHandling = 0;
this.pageSubset = 0;
this.printAsImage = false;
this.printContent = 0;
this.printerName = "";
this.psLevel = 0;
this.rasterFlags = 0;
this.reversePages = false;
this.tileLabel = false;
this.tileMark = 0;
this.tileOverlap = 0;
this.tileScale = 1.0;
this.transparencyLevel = 75;
this.usePrinterCRD = 0;
this.useT1Conversion = 0;
}
}
export { PrintParams };

View File

@ -1350,11 +1350,11 @@ const PDFViewerApplication = {
);
this._idleCallbacks.add(callback);
}
this._initializeJavaScript(pdfDocument);
});
this._initializePageLabels(pdfDocument);
this._initializeMetadata(pdfDocument);
this._initializeJavaScript(pdfDocument);
},
/**
@ -1370,25 +1370,46 @@ const PDFViewerApplication = {
return;
}
const scripting = this.externalServices.scripting;
const {
info,
metadata,
contentDispositionFilename,
} = await pdfDocument.getMetadata();
window.addEventListener("updateFromSandbox", function (event) {
window.addEventListener("updateFromSandbox", event => {
const detail = event.detail;
const id = detail.id;
if (!id) {
switch (detail.command) {
case "println":
console.log(detail.value);
break;
case "clear":
console.clear();
break;
case "alert":
// eslint-disable-next-line no-alert
window.alert(detail.value);
break;
case "clear":
console.clear();
break;
case "error":
console.error(detail.value);
break;
case "layout":
this.pdfViewer.spreadMode = apiPageLayoutToSpreadMode(detail.value);
return;
case "page-num":
this.pdfViewer.currentPageNumber = detail.value + 1;
return;
case "print":
this.triggerPrinting();
return;
case "println":
console.log(detail.value);
break;
case "zoom":
if (typeof detail.value === "string") {
this.pdfViewer.currentScaleValue = detail.value;
} else {
this.pdfViewer.currentScale = detail.value;
}
return;
}
return;
}
@ -1411,7 +1432,23 @@ const PDFViewerApplication = {
const dispatchEventName = generateRandomStringForSandbox(objects);
const calculationOrder = [];
scripting.createSandbox({ objects, dispatchEventName, calculationOrder });
const { length } = await pdfDocument.getDownloadInfo();
const filename =
contentDispositionFilename || getPDFFileNameFromURL(this.url);
scripting.createSandbox({
objects,
dispatchEventName,
calculationOrder,
docInfo: {
...info,
baseURL: this.baseUrl,
filesize: length,
filename,
metadata,
numPages: pdfDocument.numPages,
URL: this.url,
},
});
},
/**