JS -- Implement app object

* https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/AcrobatDC_js_api_reference.pdf
 * Add color, fullscreen objects + few constants.
This commit is contained in:
Calixte Denizet 2020-11-16 14:27:27 +01:00
parent c88e805870
commit 283aac4c53
12 changed files with 1259 additions and 19 deletions

View File

@ -15,6 +15,7 @@
import { import {
addLinkAttributes, addLinkAttributes,
ColorConverters,
DOMSVGFactory, DOMSVGFactory,
getFilenameFromUrl, getFilenameFromUrl,
LinkTarget, LinkTarget,
@ -567,6 +568,12 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
event.target.setSelectionRange(selStart, selEnd); event.target.setSelectionRange(selStart, selEnd);
} }
}, },
strokeColor() {
const color = detail.strokeColor;
event.target.style.color = ColorConverters[`${color[0]}_HTML`](
color.slice(1)
);
},
}; };
for (const name of Object.keys(detail)) { for (const name of Object.keys(detail)) {
if (name in actions) { if (name in actions) {

View File

@ -635,6 +635,59 @@ class PDFDateString {
} }
} }
function makeColorComp(n) {
return Math.floor(Math.max(0, Math.min(1, n)) * 255)
.toString(16)
.padStart(2, "0");
}
const ColorConverters = {
// PDF specifications section 10.3
CMYK_G([c, y, m, k]) {
return ["G", 1 - Math.min(1, 0.3 * c + 0.59 * m + 0.11 * y + k)];
},
G_CMYK([g]) {
return ["CMYK", 0, 0, 0, 1 - g];
},
G_RGB([g]) {
return ["RGB", g, g, g];
},
G_HTML([g]) {
const G = makeColorComp(g);
return `#${G}${G}${G}`;
},
RGB_G([r, g, b]) {
return ["G", 0.3 * r + 0.59 * g + 0.11 * b];
},
RGB_HTML([r, g, b]) {
const R = makeColorComp(r);
const G = makeColorComp(g);
const B = makeColorComp(b);
return `#${R}${G}${B}`;
},
T_HTML() {
return "#00000000";
},
CMYK_RGB([c, y, m, k]) {
return [
"RGB",
1 - Math.min(1, c + k),
1 - Math.min(1, m + k),
1 - Math.min(1, y + k),
];
},
CMYK_HTML(components) {
return ColorConverters.RGB_HTML(ColorConverters.CMYK_RGB(components));
},
RGB_CMYK([r, g, b]) {
const c = 1 - r;
const m = 1 - g;
const y = 1 - b;
const k = Math.min(c, m, y);
return ["CMYK", c, m, y, k];
},
};
export { export {
PageViewport, PageViewport,
RenderingCancelledException, RenderingCancelledException,
@ -645,6 +698,7 @@ export {
BaseCanvasFactory, BaseCanvasFactory,
DOMCanvasFactory, DOMCanvasFactory,
BaseCMapReaderFactory, BaseCMapReaderFactory,
ColorConverters,
DOMCMapReaderFactory, DOMCMapReaderFactory,
DOMSVGFactory, DOMSVGFactory,
StatTimer, StatTimer,

View File

@ -13,22 +13,50 @@
* limitations under the License. * limitations under the License.
*/ */
import { Color } from "./color.js";
import { EventDispatcher } from "./event.js"; import { EventDispatcher } from "./event.js";
import { NotSupportedError } from "./error.js"; import { FullScreen } from "./fullscreen.js";
import { PDFObject } from "./pdf_object.js"; import { PDFObject } from "./pdf_object.js";
import { Thermometer } from "./thermometer.js";
const VIEWER_TYPE = "PDF.js";
const VIEWER_VARIATION = "Full";
const VIEWER_VERSION = "10.0";
const FORMS_VERSION = undefined;
class App extends PDFObject { class App extends PDFObject {
constructor(data) { constructor(data) {
super(data); super(data);
this.calculate = true;
this._constants = null;
this._focusRect = true;
this._fs = null;
this._language = App._getLanguage(data.language);
this._openInPlace = false;
this._platform = App._getPlatform(data.platform);
this._runtimeHighlight = false;
this._runtimeHighlightColor = ["T"];
this._thermometer = null;
this._toolbar = false;
this._document = data._document; this._document = data._document;
this._proxyHandler = data.proxyHandler;
this._objects = Object.create(null); this._objects = Object.create(null);
this._eventDispatcher = new EventDispatcher( this._eventDispatcher = new EventDispatcher(
this._document, this._document,
data.calculationOrder, data.calculationOrder,
this._objects this._objects
); );
this._setTimeout = data.setTimeout;
this._clearTimeout = data.clearTimeout;
this._setInterval = data.setInterval;
this._clearInterval = data.clearInterval;
this._timeoutIds = null;
this._timeoutIdsRegistry = null;
// used in proxy.js to check that this the object with the backdoor // used in proxy.js to check that this is the object with the backdoor
this._isApp = true; this._isApp = true;
} }
@ -38,12 +66,339 @@ class App extends PDFObject {
this._eventDispatcher.dispatch(pdfEvent); this._eventDispatcher.dispatch(pdfEvent);
} }
_registerTimeout(timeout, id, interval) {
if (!this._timeoutIds) {
this._timeoutIds = new WeakMap();
// FinalizationRegistry isn't implemented in QuickJS
// eslint-disable-next-line no-undef
if (typeof FinalizationRegistry !== "undefined") {
// About setTimeOut/setInterval return values (specs):
// The return value of this method must be held in a
// JavaScript variable.
// Otherwise, the timeout object is subject to garbage-collection,
// which would cause the clock to stop.
// eslint-disable-next-line no-undef
this._timeoutIdsRegistry = new FinalizationRegistry(
([timeoutId, isInterval]) => {
if (isInterval) {
this._clearInterval(timeoutId);
} else {
this._clearTimeout(timeoutId);
}
}
);
}
}
this._timeoutIds.set(timeout, [id, interval]);
if (this._timeoutIdsRegistry) {
this._timeoutIdsRegistry.register(timeout, [id, interval]);
}
}
_unregisterTimeout(timeout) {
if (!this._timeoutIds || !this._timeoutIds.has(timeout)) {
return;
}
const [id, interval] = this._timeoutIds.get(timeout);
if (this._timeoutIdsRegistry) {
this._timeoutIdsRegistry.unregister(timeout);
}
this._timeoutIds.delete(timeout);
if (interval) {
this._clearInterval(id);
} else {
this._clearTimeout(id);
}
}
static _getPlatform(platform) {
if (typeof platform === "string") {
platform = platform.toLowerCase();
if (platform.includes("win")) {
return "WIN";
} else if (platform.includes("mac")) {
return "MAC";
}
}
return "UNIX";
}
static _getLanguage(language) {
const [main, sub] = language.toLowerCase().split(/[-_]/);
switch (main) {
case "zh":
if (sub === "cn" || sub === "sg") {
return "CHS";
}
return "CHT";
case "da":
return "DAN";
case "de":
return "DEU";
case "es":
return "ESP";
case "fr":
return "FRA";
case "it":
return "ITA";
case "ko":
return "KOR";
case "ja":
return "JPN";
case "nl":
return "NLD";
case "no":
return "NOR";
case "pt":
if (sub === "br") {
return "PTB";
}
return "ENU";
case "fi":
return "SUO";
case "SV":
return "SVE";
default:
return "ENU";
}
}
get activeDocs() { get activeDocs() {
return [this._document.wrapped]; return [this._document.wrapped];
} }
set activeDocs(_) { set activeDocs(_) {
throw new NotSupportedError("app.activeDocs"); throw new Error("app.activeDocs is read-only");
}
get constants() {
if (!this._constants) {
this._constants = Object.freeze({
align: Object.freeze({
left: 0,
center: 1,
right: 2,
top: 3,
bottom: 4,
}),
});
}
return this._constants;
}
set constants(_) {
throw new Error("app.constants is read-only");
}
get focusRect() {
return this._focusRect;
}
set focusRect(val) {
/* TODO or not */
this._focusRect = val;
}
get formsVersion() {
return FORMS_VERSION;
}
set formsVersion(_) {
throw new Error("app.formsVersion is read-only");
}
get fromPDFConverters() {
return [];
}
set fromPDFConverters(_) {
throw new Error("app.fromPDFConverters is read-only");
}
get fs() {
if (this._fs === null) {
this._fs = new Proxy(
new FullScreen({ send: this._send }),
this._proxyHandler
);
}
return this._fs;
}
set fs(_) {
throw new Error("app.fs is read-only");
}
get language() {
return this._language;
}
set language(_) {
throw new Error("app.language is read-only");
}
get media() {
return undefined;
}
set media(_) {
throw new Error("app.media is read-only");
}
get monitors() {
return [];
}
set monitors(_) {
throw new Error("app.monitors is read-only");
}
get numPlugins() {
return 0;
}
set numPlugins(_) {
throw new Error("app.numPlugins is read-only");
}
get openInPlace() {
return this._openInPlace;
}
set openInPlace(val) {
this._openInPlace = val;
/* TODO */
}
get platform() {
return this._platform;
}
set platform(_) {
throw new Error("app.platform is read-only");
}
get plugins() {
return [];
}
set plugins(_) {
throw new Error("app.plugins is read-only");
}
get printColorProfiles() {
return [];
}
set printColorProfiles(_) {
throw new Error("app.printColorProfiles is read-only");
}
get printerNames() {
return [];
}
set printerNames(_) {
throw new Error("app.printerNames is read-only");
}
get runtimeHighlight() {
return this._runtimeHighlight;
}
set runtimeHighlight(val) {
this._runtimeHighlight = val;
/* TODO */
}
get runtimeHighlightColor() {
return this._runtimeHighlightColor;
}
set runtimeHighlightColor(val) {
if (Color._isValidColor(val)) {
this._runtimeHighlightColor = val;
/* TODO */
}
}
get thermometer() {
if (this._thermometer === null) {
this._thermometer = new Proxy(
new Thermometer({ send: this._send }),
this._proxyHandler
);
}
return this._thermometer;
}
set thermometer(_) {
throw new Error("app.thermometer is read-only");
}
get toolbar() {
return this._toolbar;
}
set toolbar(val) {
this._toolbar = val;
/* TODO */
}
get toolbarHorizontal() {
return this.toolbar;
}
set toolbarHorizontal(value) {
/* has been deprecated and it's now equivalent to toolbar */
this.toolbar = value;
}
get toolbarVertical() {
return this.toolbar;
}
set toolbarVertical(value) {
/* has been deprecated and it's now equivalent to toolbar */
this.toolbar = value;
}
get viewerType() {
return VIEWER_TYPE;
}
set viewerType(_) {
throw new Error("app.viewerType is read-only");
}
get viewerVariation() {
return VIEWER_VARIATION;
}
set viewerVariation(_) {
throw new Error("app.viewerVariation is read-only");
}
get viewerVersion() {
return VIEWER_VERSION;
}
set viewerVersion(_) {
throw new Error("app.viewerVersion is read-only");
}
addMenuItem() {
/* Not implemented */
}
addSubMenu() {
/* Not implemented */
}
addToolButton() {
/* Not implemented */
} }
alert( alert(
@ -56,6 +411,160 @@ class App extends PDFObject {
) { ) {
this._send({ command: "alert", value: cMsg }); this._send({ command: "alert", value: cMsg });
} }
beep() {
/* Not implemented */
}
beginPriv() {
/* Not implemented */
}
browseForDoc() {
/* Not implemented */
}
clearInterval(oInterval) {
this.unregisterTimeout(oInterval);
}
clearTimeOut(oTime) {
this.unregisterTimeout(oTime);
}
endPriv() {
/* Not implemented */
}
execDialog() {
/* Not implemented */
}
execMenuItem() {
/* Not implemented */
}
getNthPlugInName() {
/* Not implemented */
}
getPath() {
/* Not implemented */
}
goBack() {
/* TODO */
}
goForward() {
/* TODO */
}
hideMenuItem() {
/* Not implemented */
}
hideToolbarButton() {
/* Not implemented */
}
launchURL() {
/* Unsafe */
}
listMenuItems() {
/* Not implemented */
}
listToolbarButtons() {
/* Not implemented */
}
loadPolicyFile() {
/* Not implemented */
}
mailGetAddrs() {
/* Not implemented */
}
mailMsg() {
/* TODO or not ? */
}
newDoc() {
/* Not implemented */
}
newCollection() {
/* Not implemented */
}
newFDF() {
/* Not implemented */
}
openDoc() {
/* Not implemented */
}
openFDF() {
/* Not implemented */
}
popUpMenu() {
/* Not implemented */
}
popUpMenuEx() {
/* Not implemented */
}
removeToolButton() {
/* Not implemented */
}
response() {
/* TODO or not */
}
setInterval(cExpr, nMilliseconds) {
if (typeof cExpr !== "string") {
throw new TypeError("First argument of app.setInterval must be a string");
}
if (typeof nMilliseconds !== "number") {
throw new TypeError(
"Second argument of app.setInterval must be a number"
);
}
const id = this._setInterval(cExpr, nMilliseconds);
const timeout = Object.create(null);
this._registerTimeout(timeout, id, true);
return timeout;
}
setTimeOut(cExpr, nMilliseconds) {
if (typeof cExpr !== "string") {
throw new TypeError("First argument of app.setTimeOut must be a string");
}
if (typeof nMilliseconds !== "number") {
throw new TypeError("Second argument of app.setTimeOut must be a number");
}
const id = this._setTimeout(cExpr, nMilliseconds);
const timeout = Object.create(null);
this._registerTimeout(timeout, id, false);
return timeout;
}
trustedFunction() {
/* Not implemented */
}
trustPropagatorFunction() {
/* Not implemented */
}
} }
export { App }; export { App };

129
src/scripting_api/color.js Normal file
View File

@ -0,0 +1,129 @@
/* 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.
*/
import { ColorConverters } from "../display/display_utils.js";
import { PDFObject } from "./pdf_object.js";
class Color extends PDFObject {
constructor() {
super({});
this.transparent = ["T"];
this.black = ["G", 0];
this.white = ["G", 1];
this.red = ["RGB", 1, 0, 0];
this.green = ["RGB", 0, 1, 0];
this.blue = ["RGB", 0, 0, 1];
this.cyan = ["CMYK", 1, 0, 0, 0];
this.magenta = ["CMYK", 0, 1, 0, 0];
this.yellow = ["CMYK", 0, 0, 1, 0];
this.dkGray = ["G", 0.25];
this.gray = ["G", 0.5];
this.ltGray = ["G", 0.75];
}
static _isValidSpace(cColorSpace) {
return (
typeof cColorSpace === "string" &&
(cColorSpace === "T" ||
cColorSpace === "G" ||
cColorSpace === "RGB" ||
cColorSpace === "CMYK")
);
}
static _isValidColor(colorArray) {
if (!Array.isArray(colorArray) || colorArray.length === 0) {
return false;
}
const space = colorArray[0];
if (!Color._isValidSpace(space)) {
return false;
}
switch (space) {
case "T":
if (colorArray.length !== 1) {
return false;
}
break;
case "G":
if (colorArray.length !== 2) {
return false;
}
break;
case "RGB":
if (colorArray.length !== 4) {
return false;
}
break;
case "CMYK":
if (colorArray.length !== 5) {
return false;
}
break;
default:
return false;
}
return colorArray
.slice(1)
.every(c => typeof c === "number" && c >= 0 && c <= 1);
}
static _getCorrectColor(colorArray) {
return Color._isValidColor(colorArray) ? colorArray : ["G", 0];
}
convert(colorArray, cColorSpace) {
if (!Color._isValidSpace(cColorSpace)) {
return this.black;
}
if (cColorSpace === "T") {
return ["T"];
}
colorArray = Color._getCorrectColor(colorArray);
if (colorArray[0] === cColorSpace) {
return colorArray;
}
if (colorArray[0] === "T") {
return this.convert(this.black, cColorSpace);
}
return ColorConverters[`${colorArray[0]}_${cColorSpace}`](
colorArray.slice(1)
);
}
equal(colorArray1, colorArray2) {
colorArray1 = Color._getCorrectColor(colorArray1);
colorArray2 = Color._getCorrectColor(colorArray2);
if (colorArray1[0] === "T" || colorArray2[0] === "T") {
return colorArray1[0] === "T" && colorArray2[0] === "T";
}
if (colorArray1[0] !== colorArray2[0]) {
colorArray2 = this.convert(colorArray2, colorArray1[0]);
}
return colorArray1.slice(1).every((c, i) => c === colorArray2[i + 1]);
}
}
export { Color };

View File

@ -13,6 +13,105 @@
* limitations under the License. * limitations under the License.
*/ */
const Border = Object.freeze({
s: "solid",
d: "dashed",
b: "beveled",
i: "inset",
u: "underline",
});
const Cursor = Object.freeze({
visible: 0,
hidden: 1,
delay: 2,
});
const Display = Object.freeze({
visible: 0,
hidden: 1,
noPrint: 2,
noView: 3,
});
const Font = Object.freeze({
Times: "Times-Roman",
TimesB: "Times-Bold",
TimesI: "Times-Italic",
TimesBI: "Times-BoldItalic",
Helv: "Helvetica",
HelvB: "Helvetica-Bold",
HelvI: "Helvetica-Oblique",
HelvBI: "Helvetica-BoldOblique",
Cour: "Courier",
CourB: "Courier-Bold",
CourI: "Courier-Oblique",
CourBI: "Courier-BoldOblique",
Symbol: "Symbol",
ZapfD: "ZapfDingbats",
KaGo: "HeiseiKakuGo-W5-UniJIS-UCS2-H",
KaMi: "HeiseiMin-W3-UniJIS-UCS2-H",
});
const Highlight = Object.freeze({
n: "none",
i: "invert",
p: "push",
o: "outline",
});
const Position = Object.freeze({
textOnly: 0,
iconOnly: 1,
iconTextV: 2,
textIconV: 3,
iconTextH: 4,
textIconH: 5,
overlay: 6,
});
const ScaleHow = Object.freeze({
proportional: 0,
anamorphic: 1,
});
const ScaleWhen = Object.freeze({
always: 0,
never: 1,
tooBig: 2,
tooSmall: 3,
});
const Style = Object.freeze({
ch: "check",
cr: "cross",
di: "diamond",
ci: "circle",
st: "star",
sq: "square",
});
const Trans = Object.freeze({
blindsH: "BlindsHorizontal",
blindsV: "BlindsVertical",
boxI: "BoxIn",
boxO: "BoxOut",
dissolve: "Dissolve",
glitterD: "GlitterDown",
glitterR: "GlitterRight",
glitterRD: "GlitterRightDown",
random: "Random",
replace: "Replace",
splitHI: "SplitHorizontalIn",
splitHO: "SplitHorizontalOut",
splitVI: "SplitVerticalIn",
splitVO: "SplitVerticalOut",
wipeD: "WipeDown",
wipeL: "WipeLeft",
wipeR: "WipeRight",
wipeU: "WipeUp",
});
const ZoomType = Object.freeze({ const ZoomType = Object.freeze({
none: "NoVary", none: "NoVary",
fitP: "FitPage", fitP: "FitPage",
@ -23,4 +122,16 @@ const ZoomType = Object.freeze({
refW: "ReflowWidth", refW: "ReflowWidth",
}); });
export { ZoomType }; export {
Border,
Cursor,
Display,
Font,
Highlight,
Position,
ScaleHow,
ScaleWhen,
Style,
Trans,
ZoomType,
};

View File

@ -13,6 +13,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Color } from "./color.js";
import { PDFObject } from "./pdf_object.js"; import { PDFObject } from "./pdf_object.js";
class Field extends PDFObject { class Field extends PDFObject {
@ -41,7 +42,6 @@ class Field extends PDFObject {
this.editable = data.editable; this.editable = data.editable;
this.exportValues = data.exportValues; this.exportValues = data.exportValues;
this.fileSelect = data.fileSelect; this.fileSelect = data.fileSelect;
this.fillColor = data.fillColor;
this.hidden = data.hidden; this.hidden = data.hidden;
this.highlight = data.highlight; this.highlight = data.highlight;
this.lineWidth = data.lineWidth; this.lineWidth = data.lineWidth;
@ -59,10 +59,8 @@ class Field extends PDFObject {
this.richText = data.richText; this.richText = data.richText;
this.richValue = data.richValue; this.richValue = data.richValue;
this.rotation = data.rotation; this.rotation = data.rotation;
this.strokeColor = data.strokeColor;
this.style = data.style; this.style = data.style;
this.submitName = data.submitName; this.submitName = data.submitName;
this.textColor = data.textColor;
this.textFont = data.textFont; this.textFont = data.textFont;
this.textSize = data.textSize; this.textSize = data.textSize;
this.type = data.type; this.type = data.type;
@ -73,6 +71,40 @@ class Field extends PDFObject {
// Private // Private
this._document = data.doc; this._document = data.doc;
this._actions = this._createActionsMap(data.actions); this._actions = this._createActionsMap(data.actions);
this._fillColor = data.fillColor | ["T"];
this._strokeColor = data.strokeColor | ["G", 0];
this._textColor = data.textColor | ["G", 0];
}
get fillColor() {
return this._fillColor;
}
set fillColor(color) {
if (Color._isValidColor(color)) {
this._fillColor = color;
}
}
get strokeColor() {
return this._strokeColor;
}
set strokeColor(color) {
if (Color._isValidColor(color)) {
this._strokeColor = color;
}
}
get textColor() {
return this._textColor;
}
set textColor(color) {
if (Color._isValidColor(color)) {
this._textColor = color;
}
} }
setAction(cTrigger, cScript) { setAction(cTrigger, cScript) {
@ -131,7 +163,7 @@ class Field extends PDFObject {
`"${error.toString()}" for event ` + `"${error.toString()}" for event ` +
`"${eventName}" in object ${this._id}.` + `"${eventName}" in object ${this._id}.` +
`\n${error.stack}`; `\n${error.stack}`;
this._send({ id: "error", value }); this._send({ command: "error", value });
} }
return true; return true;

View File

@ -0,0 +1,145 @@
/* 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.
*/
import { Cursor } from "./constants.js";
import { PDFObject } from "./pdf_object.js";
class FullScreen extends PDFObject {
constructor(data) {
super(data);
this._backgroundColor = [];
this._clickAdvances = true;
this._cursor = Cursor.hidden;
this._defaultTransition = "";
this._escapeExits = true;
this._isFullScreen = true;
this._loop = false;
this._timeDelay = 3600;
this._usePageTiming = false;
this._useTimer = false;
}
get backgroundColor() {
return this._backgroundColor;
}
set backgroundColor(_) {
/* TODO or not */
}
get clickAdvances() {
return this._clickAdvances;
}
set clickAdvances(_) {
/* TODO or not */
}
get cursor() {
return this._cursor;
}
set cursor(_) {
/* TODO or not */
}
get defaultTransition() {
return this._defaultTransition;
}
set defaultTransition(_) {
/* TODO or not */
}
get escapeExits() {
return this._escapeExits;
}
set escapeExits(_) {
/* TODO or not */
}
get isFullScreen() {
return this._isFullScreen;
}
set isFullScreen(_) {
/* TODO or not */
}
get loop() {
return this._loop;
}
set loop(_) {
/* TODO or not */
}
get timeDelay() {
return this._timeDelay;
}
set timeDelay(_) {
/* TODO or not */
}
get transitions() {
// This list of possible value for transition has been found:
// https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/5186AcroJS.pdf#page=198
return [
"Replace",
"WipeRight",
"WipeLeft",
"WipeDown",
"WipeUp",
"SplitHorizontalIn",
"SplitHorizontalOut",
"SplitVerticalIn",
"SplitVerticalOut",
"BlindsHorizontal",
"BlindsVertical",
"BoxIn",
"BoxOut",
"GlitterRight",
"GlitterDown",
"GlitterRightDown",
"Dissolve",
"Random",
];
}
set transitions(_) {
throw new Error("fullscreen.transitions is read-only");
}
get usePageTiming() {
return this._usePageTiming;
}
set usePageTiming(_) {
/* TODO or not */
}
get useTimer() {
return this._useTimer;
}
set useTimer(_) {
/* TODO or not */
}
}
export { FullScreen };

View File

@ -13,18 +13,38 @@
* limitations under the License. * limitations under the License.
*/ */
import {
Border,
Cursor,
Display,
Font,
Highlight,
Position,
ScaleHow,
ScaleWhen,
Style,
Trans,
ZoomType,
} from "./constants.js";
import { AForm } from "./aform.js"; import { AForm } from "./aform.js";
import { App } from "./app.js"; import { App } from "./app.js";
import { Color } from "./color.js";
import { Console } from "./console.js"; import { Console } from "./console.js";
import { Doc } from "./doc.js"; import { Doc } from "./doc.js";
import { Field } from "./field.js"; import { Field } from "./field.js";
import { ProxyHandler } from "./proxy.js"; import { ProxyHandler } from "./proxy.js";
import { Util } from "./util.js"; import { Util } from "./util.js";
import { ZoomType } from "./constants.js";
function initSandbox({ data, extra, out }) { function initSandbox({ data, extra, out }) {
const proxyHandler = new ProxyHandler(data.dispatchEventName); const proxyHandler = new ProxyHandler(data.dispatchEventName);
const { send, crackURL } = extra; const {
send,
crackURL,
setTimeout,
clearTimeout,
setInterval,
clearInterval,
} = extra;
const doc = new Doc({ const doc = new Doc({
send, send,
...data.docInfo, ...data.docInfo,
@ -32,8 +52,14 @@ function initSandbox({ data, extra, out }) {
const _document = { obj: doc, wrapped: new Proxy(doc, proxyHandler) }; const _document = { obj: doc, wrapped: new Proxy(doc, proxyHandler) };
const app = new App({ const app = new App({
send, send,
setTimeout,
clearTimeout,
setInterval,
clearInterval,
_document, _document,
calculationOrder: data.calculationOrder, calculationOrder: data.calculationOrder,
proxyHandler,
...data.appInfo,
}); });
const util = new Util({ crackURL }); const util = new Util({ crackURL });
const aform = new AForm(doc, app, util); const aform = new AForm(doc, app, util);
@ -50,9 +76,21 @@ function initSandbox({ data, extra, out }) {
out.global = Object.create(null); out.global = Object.create(null);
out.app = new Proxy(app, proxyHandler); out.app = new Proxy(app, proxyHandler);
out.color = new Proxy(new Color(), proxyHandler);
out.console = new Proxy(new Console({ send }), proxyHandler); out.console = new Proxy(new Console({ send }), proxyHandler);
out.util = new Proxy(util, proxyHandler); out.util = new Proxy(util, proxyHandler);
out.border = Border;
out.cursor = Cursor;
out.display = Display;
out.font = Font;
out.highlight = Highlight;
out.position = Position;
out.scaleHow = ScaleHow;
out.scaleWhen = ScaleWhen;
out.style = Style;
out.trans = Trans;
out.zoomtype = ZoomType; out.zoomtype = ZoomType;
for (const name of Object.getOwnPropertyNames(AForm.prototype)) { for (const name of Object.getOwnPropertyNames(AForm.prototype)) {
if (name.startsWith("AF")) { if (name.startsWith("AF")) {
out[name] = aform[name].bind(aform); out[name] = aform[name].bind(aform);

View File

@ -83,7 +83,11 @@ class Sandbox {
evalForTesting(code, key) { evalForTesting(code, key) {
if (this._testMode) { if (this._testMode) {
this._evalInSandbox( this._evalInSandbox(
`send({ id: "${key}", result: ${code} });`, `try {
send({ id: "${key}", result: ${code} });
} catch (error) {
send({ id: "${key}", result: error.message });
}`,
this._alertOnError this._alertOnError
); );
} }

View File

@ -0,0 +1,69 @@
/* 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.
*/
import { PDFObject } from "./pdf_object.js";
class Thermometer extends PDFObject {
constructor(data) {
super(data);
this._cancelled = false;
this._duration = 100;
this._text = "";
this._value = 0;
}
get cancelled() {
return this._cancelled;
}
set cancelled(_) {
throw new Error("thermometer.cancelled is read-only");
}
get duration() {
return this._duration;
}
set duration(val) {
this._duration = val;
}
get text() {
return this._text;
}
set text(val) {
this._text = val;
}
get value() {
return this._value;
}
set value(val) {
this._value = val;
}
begin() {
/* TODO */
}
end() {
/* TODO */
}
}
export { Thermometer };

View File

@ -23,6 +23,15 @@ describe("Scripting", function () {
return id; return id;
} }
function myeval(code) {
const key = (test_id++).toString();
return sandbox.eval(code, key).then(() => {
const result = send_queue.get(key).result;
send_queue.delete(key);
return result;
});
}
beforeAll(function (done) { beforeAll(function (done) {
test_id = 0; test_id = 0;
ref = 1; ref = 1;
@ -92,6 +101,7 @@ describe("Scripting", function () {
], ],
}, },
calculationOrder: [], calculationOrder: [],
appInfo: { language: "en-US", platform: "Linux x86_64" },
dispatchEventName: "_dispatchMe", dispatchEventName: "_dispatchMe",
}; };
sandbox.createSandbox(data); sandbox.createSandbox(data);
@ -115,15 +125,9 @@ describe("Scripting", function () {
}); });
describe("Util", function () { describe("Util", function () {
function myeval(code) {
const key = (test_id++).toString();
return sandbox.eval(code, key).then(() => {
return send_queue.get(key).result;
});
}
beforeAll(function (done) { beforeAll(function (done) {
sandbox.createSandbox({ sandbox.createSandbox({
appInfo: { language: "en-US", platform: "Linux x86_64" },
objects: {}, objects: {},
calculationOrder: [], calculationOrder: [],
dispatchEventName: "_dispatchMe", dispatchEventName: "_dispatchMe",
@ -214,7 +218,7 @@ describe("Scripting", function () {
.then(() => done()); .then(() => done());
}); });
it(" print a string with a percent", function (done) { it("print a string with a percent", function (done) {
myeval(`util.printf("%%s")`) myeval(`util.printf("%%s")`)
.then(value => { .then(value => {
expect(value).toEqual("%%s"); expect(value).toEqual("%%s");
@ -250,6 +254,7 @@ describe("Scripting", function () {
}, },
], ],
}, },
appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [], calculationOrder: [],
dispatchEventName: "_dispatchMe", dispatchEventName: "_dispatchMe",
}; };
@ -287,6 +292,7 @@ describe("Scripting", function () {
}, },
], ],
}, },
appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [], calculationOrder: [],
dispatchEventName: "_dispatchMe", dispatchEventName: "_dispatchMe",
}; };
@ -328,6 +334,7 @@ describe("Scripting", function () {
}, },
], ],
}, },
appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [], calculationOrder: [],
dispatchEventName: "_dispatchMe", dispatchEventName: "_dispatchMe",
}; };
@ -368,6 +375,7 @@ describe("Scripting", function () {
}, },
], ],
}, },
appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [], calculationOrder: [],
dispatchEventName: "_dispatchMe", dispatchEventName: "_dispatchMe",
}; };
@ -412,6 +420,7 @@ describe("Scripting", function () {
}, },
], ],
}, },
appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [refId2], calculationOrder: [refId2],
dispatchEventName: "_dispatchMe", dispatchEventName: "_dispatchMe",
}; };
@ -435,4 +444,133 @@ describe("Scripting", function () {
.catch(done.fail); .catch(done.fail);
}); });
}); });
describe("Color", function () {
beforeAll(function (done) {
sandbox.createSandbox({
appInfo: { language: "en-US", platform: "Linux x86_64" },
objects: {},
calculationOrder: [],
dispatchEventName: "_dispatchMe",
});
done();
});
function round(color) {
return [
color[0],
...color.slice(1).map(x => Math.round(x * 1000) / 1000),
];
}
it("should convert RGB color for different color spaces", function (done) {
Promise.all([
myeval(`color.convert(["RGB", 0.1, 0.2, 0.3], "T")`).then(value => {
expect(round(value)).toEqual(["T"]);
}),
myeval(`color.convert(["RGB", 0.1, 0.2, 0.3], "G")`).then(value => {
expect(round(value)).toEqual(["G", 0.181]);
}),
myeval(`color.convert(["RGB", 0.1, 0.2, 0.3], "RGB")`).then(value => {
expect(round(value)).toEqual(["RGB", 0.1, 0.2, 0.3]);
}),
myeval(`color.convert(["RGB", 0.1, 0.2, 0.3], "CMYK")`).then(value => {
expect(round(value)).toEqual(["CMYK", 0.9, 0.8, 0.7, 0.7]);
}),
]).then(() => done());
});
it("should convert CMYK color for different color spaces", function (done) {
Promise.all([
myeval(`color.convert(["CMYK", 0.1, 0.2, 0.3, 0.4], "T")`).then(
value => {
expect(round(value)).toEqual(["T"]);
}
),
myeval(`color.convert(["CMYK", 0.1, 0.2, 0.3, 0.4], "G")`).then(
value => {
expect(round(value)).toEqual(["G", 0.371]);
}
),
myeval(`color.convert(["CMYK", 0.1, 0.2, 0.3, 0.4], "RGB")`).then(
value => {
expect(round(value)).toEqual(["RGB", 0.5, 0.3, 0.4]);
}
),
myeval(`color.convert(["CMYK", 0.1, 0.2, 0.3, 0.4], "CMYK")`).then(
value => {
expect(round(value)).toEqual(["CMYK", 0.1, 0.2, 0.3, 0.4]);
}
),
]).then(() => done());
});
it("should convert Gray color for different color spaces", function (done) {
Promise.all([
myeval(`color.convert(["G", 0.1], "T")`).then(value => {
expect(round(value)).toEqual(["T"]);
}),
myeval(`color.convert(["G", 0.1], "G")`).then(value => {
expect(round(value)).toEqual(["G", 0.1]);
}),
myeval(`color.convert(["G", 0.1], "RGB")`).then(value => {
expect(round(value)).toEqual(["RGB", 0.1, 0.1, 0.1]);
}),
myeval(`color.convert(["G", 0.1], "CMYK")`).then(value => {
expect(round(value)).toEqual(["CMYK", 0, 0, 0, 0.9]);
}),
]).then(() => done());
});
it("should convert Transparent color for different color spaces", function (done) {
Promise.all([
myeval(`color.convert(["T"], "T")`).then(value => {
expect(round(value)).toEqual(["T"]);
}),
myeval(`color.convert(["T"], "G")`).then(value => {
expect(round(value)).toEqual(["G", 0]);
}),
myeval(`color.convert(["T"], "RGB")`).then(value => {
expect(round(value)).toEqual(["RGB", 0, 0, 0]);
}),
myeval(`color.convert(["T"], "CMYK")`).then(value => {
expect(round(value)).toEqual(["CMYK", 0, 0, 0, 1]);
}),
]).then(() => done());
});
});
describe("App", function () {
beforeAll(function (done) {
sandbox.createSandbox({
appInfo: { language: "en-US", platform: "Linux x86_64" },
objects: {},
calculationOrder: [],
dispatchEventName: "_dispatchMe",
});
done();
});
it("should test language", function (done) {
Promise.all([
myeval(`app.language`).then(value => {
expect(value).toEqual("ENU");
}),
myeval(`app.language = "hello"`).then(value => {
expect(value).toEqual("app.language is read-only");
}),
]).then(() => done());
});
it("should test platform", function (done) {
Promise.all([
myeval(`app.platform`).then(value => {
expect(value).toEqual("UNIX");
}),
myeval(`app.platform = "hello"`).then(value => {
expect(value).toEqual("app.platform is read-only");
}),
]).then(() => done());
});
});
}); });

View File

@ -1486,6 +1486,10 @@ const PDFViewerApplication = {
objects, objects,
dispatchEventName, dispatchEventName,
calculationOrder, calculationOrder,
appInfo: {
platform: navigator.platform,
language: navigator.language,
},
docInfo: { docInfo: {
...info, ...info,
baseURL: this.baseUrl, baseURL: this.baseUrl,