From 7a7ac3b8f6198d14a201c0ecf8e37044e91ea5f6 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Mon, 16 Nov 2020 14:27:27 +0100 Subject: [PATCH] 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. --- src/scripting_api/app.js | 513 +++++++++++++++++++++++++++- src/scripting_api/color.js | 154 +++++++++ src/scripting_api/constants.js | 113 +++++- src/scripting_api/fullscreen.js | 145 ++++++++ src/scripting_api/initialization.js | 39 ++- src/scripting_api/thermometer.js | 69 ++++ test/unit/scripting_spec.js | 20 +- web/app.js | 4 + 8 files changed, 1049 insertions(+), 8 deletions(-) create mode 100644 src/scripting_api/color.js create mode 100644 src/scripting_api/fullscreen.js create mode 100644 src/scripting_api/thermometer.js diff --git a/src/scripting_api/app.js b/src/scripting_api/app.js index 2a823b168994e7..9bf75a520189f8 100644 --- a/src/scripting_api/app.js +++ b/src/scripting_api/app.js @@ -14,21 +14,48 @@ */ import { EventDispatcher } from "./event.js"; -import { NotSupportedError } from "./error.js"; +import { FullScreen } from "./fullscreen.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 { constructor(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._proxyHandler = data.proxyHandler; this._objects = Object.create(null); this._eventDispatcher = new EventDispatcher( this._document, data.calculationOrder, 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; } @@ -38,12 +65,338 @@ class App extends PDFObject { 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() { return [this._document.wrapped]; } 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) { + 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( @@ -56,6 +409,160 @@ class App extends PDFObject { ) { 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 }; diff --git a/src/scripting_api/color.js b/src/scripting_api/color.js new file mode 100644 index 00000000000000..bca76ecc0df3df --- /dev/null +++ b/src/scripting_api/color.js @@ -0,0 +1,154 @@ +/* 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 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]; + + this._converters = { + CMYK_G(components) { + const [c, y, m, k] = components; + return ["G", 1 - Math.min(1, 0.3 * c + 0.59 * m + 0.11 * y + k)]; + }, + G_CMYK(components) { + const [g] = components; + return ["CMYK", 0, 0, 0, 1 - g]; + }, + G_RGB(components) { + const [g] = components; + return ["RGB", g, g, g]; + }, + RGB_G(components) { + const [r, g, b] = components; + return ["G", 0.3 * r + 0.59 * g + 0.11 * b]; + }, + CMYK_RGB(components) { + const [c, y, m, k] = components; + return [ + "RGB", + 1 - Math.min(1, c + k), + 1 - Math.min(1, m + k), + 1 - Math.min(1, y + k), + ]; + }, + RGB_CMYK(components) { + const [r, g, b] = components; + const c = 1 - r; + const m = 1 - g; + const y = 1 - b; + return ["CMYK", c, m, y, Math.min(c, Math.min(m, y))]; + }, + }; + } + + _isValidSpace(cColorSpace) { + return ( + typeof cColorSpace === "string" && + (cColorSpace === "T" || + cColorSpace === "G" || + cColorSpace === "RGB" || + cColorSpace === "CMYK") + ); + } + + _getCorrectColor(colorArray) { + if (!Array.isArray(colorArray) || colorArray.length === 0) { + return this.black; + } + const space = colorArray[0]; + if (!this._isValidSpace(space)) { + return this.black; + } + + if (space === "T" && colorArray.length !== 1) { + return this.black; + } + + if (space === "G" && colorArray.length !== 2) { + return this.black; + } + + if (space === "RGB" && colorArray.length !== 4) { + return this.black; + } + + if (space === "CMYK" && colorArray.length !== 5) { + return this.black; + } + + if ( + colorArray.slice(1).some(c => typeof c !== "number" || c < 0 || c > 1) + ) { + return this.black; + } + + return colorArray; + } + + convert(colorArray, cColorSpace) { + if (!this._isValidSpace(cColorSpace)) { + return this.black; + } + + if (cColorSpace === "T") { + return ["T"]; + } + + colorArray = this._getCorrectColor(colorArray); + if (colorArray[0] === cColorSpace) { + return colorArray; + } + + if (colorArray[0] === "T") { + return this.convert(this.black, cColorSpace); + } + + return this._converters[`${colorArray[0]}_${cColorSpace}`]( + colorArray.slice(1) + ); + } + + equal(colorArray1, colorArray2) { + colorArray1 = this._getCorrectColor(colorArray1); + colorArray2 = this._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 }; diff --git a/src/scripting_api/constants.js b/src/scripting_api/constants.js index 683e37324a1630..6fb77808d1492a 100644 --- a/src/scripting_api/constants.js +++ b/src/scripting_api/constants.js @@ -13,6 +13,105 @@ * 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({ none: "NoVary", fitP: "FitPage", @@ -23,4 +122,16 @@ const ZoomType = Object.freeze({ refW: "ReflowWidth", }); -export { ZoomType }; +export { + Border, + Cursor, + Display, + Font, + Highlight, + Position, + ScaleHow, + ScaleWhen, + Style, + Trans, + ZoomType, +}; diff --git a/src/scripting_api/fullscreen.js b/src/scripting_api/fullscreen.js new file mode 100644 index 00000000000000..87b7d7f9d3d523 --- /dev/null +++ b/src/scripting_api/fullscreen.js @@ -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 }; diff --git a/src/scripting_api/initialization.js b/src/scripting_api/initialization.js index 9fc65d8e0eb76d..4dcf01fdf0d835 100644 --- a/src/scripting_api/initialization.js +++ b/src/scripting_api/initialization.js @@ -13,6 +13,19 @@ * 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 { App } from "./app.js"; import { Console } from "./console.js"; @@ -20,11 +33,17 @@ 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, testMode = false }) { const proxyHandler = new ProxyHandler(data.dispatchEventName); - const { send, crackURL } = extra; + const { + send, + crackURL, + setTimeout, + clearTimeout, + setInterval, + clearInterval, + } = extra; const doc = new Doc({ send, ...data.docInfo, @@ -32,8 +51,14 @@ function initSandbox({ data, extra, out, testMode = false }) { const _document = { obj: doc, wrapped: new Proxy(doc, proxyHandler) }; const app = new App({ send, + setTimeout, + clearTimeout, + setInterval, + clearInterval, _document, calculationOrder: data.calculationOrder, + proxyHandler, + ...data.appInfo, }); const util = new Util({ crackURL }); const aform = new AForm(doc, app, util); @@ -52,6 +77,16 @@ function initSandbox({ data, extra, out, testMode = false }) { out.app = new Proxy(app, proxyHandler); out.console = new Proxy(new Console({ send }), proxyHandler); out.util = new Proxy(util, proxyHandler); + out.border = Border; + out.cursor = Cursor; + out.display = Display; + out.font = Font; + out.highlight = Highlight; + out.scaleHow = ScaleHow; + out.scaleWhen = ScaleWhen; + out.style = Style; + out.position = Position; + out.trans = Trans; out.zoomtype = ZoomType; for (const name of Object.getOwnPropertyNames(AForm.prototype)) { if (name.startsWith("AF")) { diff --git a/src/scripting_api/thermometer.js b/src/scripting_api/thermometer.js new file mode 100644 index 00000000000000..5e164779439c52 --- /dev/null +++ b/src/scripting_api/thermometer.js @@ -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 }; diff --git a/test/unit/scripting_spec.js b/test/unit/scripting_spec.js index c804d9ecc2ac33..72f1831cf81597 100644 --- a/test/unit/scripting_spec.js +++ b/test/unit/scripting_spec.js @@ -21,8 +21,19 @@ describe("Scripting", function () { beforeAll(function (done) { sandbox = Object.create(null); - const extra = { send: null, crackURL: null }; - const data = { objects: {}, calculationOrder: [] }; + const extra = { + send: null, + crackURL: null, + setTimeout: null, + clearTimeout: null, + setInterval: null, + clearInterval: null, + }; + const data = { + appInfo: { language: "en-US", platform: "Linux x86_64" }, + objects: {}, + calculationOrder: [], + }; initSandbox({ data, extra, out: sandbox }); util = sandbox.util; done(); @@ -109,6 +120,10 @@ describe("Scripting", function () { send_queue.push(data); }, crackURL: null, + setTimeout: null, + clearTimeout: null, + setInterval: null, + clearInterval: null, }; const data = { objects: { @@ -129,6 +144,7 @@ describe("Scripting", function () { }, ], }, + appInfo: { language: "en-US", platform: "Linux x86_64" }, calculationOrder: ["271R"], dispatchEventName: "_dispatchMe", }; diff --git a/web/app.js b/web/app.js index d07853f6cbcc13..eb78ac8c31aabb 100644 --- a/web/app.js +++ b/web/app.js @@ -1486,6 +1486,10 @@ const PDFViewerApplication = { objects, dispatchEventName, calculationOrder, + appInfo: { + platform: navigator.platform, + language: navigator.language, + }, docInfo: { ...info, baseURL: this.baseUrl,