tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit c4e294aaf9be3773f7b435174b035cc0715c8325
parent 6cebd143bf4d5cb9d727f52e6f1dbaccb3d90a28
Author: Calixte Denizet <calixte.denizet@gmail.com>
Date:   Thu, 23 Oct 2025 10:35:05 +0000

Bug 1995924 - Update PDF.js to new version f6317ddbbb847b9d6e70aa843f651a93e470257b r=pdfjs-reviewers,fluent-reviewers,flod,marco

Differential Revision: https://phabricator.services.mozilla.com/D269707

Diffstat:
Mtoolkit/components/pdfjs/content/build/pdf.mjs | 8639+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mtoolkit/components/pdfjs/content/build/pdf.scripting.mjs | 4++--
Mtoolkit/components/pdfjs/content/build/pdf.worker.mjs | 1202++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mtoolkit/components/pdfjs/content/web/viewer-geckoview.mjs | 15+++++++++++----
Mtoolkit/components/pdfjs/content/web/viewer.css | 31+++++++++++++++++++++++++------
Mtoolkit/components/pdfjs/content/web/viewer.mjs | 15+++++++++++----
Mtoolkit/components/pdfjs/moz.yaml | 4++--
Mtoolkit/locales/en-US/toolkit/pdfviewer/viewer.ftl | 4++--
8 files changed, 5199 insertions(+), 4715 deletions(-)

diff --git a/toolkit/components/pdfjs/content/build/pdf.mjs b/toolkit/components/pdfjs/content/build/pdf.mjs @@ -21,8 +21,8 @@ */ /** - * pdfjsVersion = 5.4.329 - * pdfjsBuild = 3eca60735 + * pdfjsVersion = 5.4.370 + * pdfjsBuild = f6317ddbb */ /******/ // The require scope /******/ var __webpack_require__ = {}; @@ -331,7 +331,8 @@ const DrawOPS = { moveTo: 0, lineTo: 1, curveTo: 2, - closePath: 3 + quadraticCurveTo: 3, + closePath: 4 }; const PasswordResponses = { NEED_PASSWORD: 1, @@ -520,6 +521,9 @@ class util_FeatureTest { static get isImageDecoderSupported() { return shadow(this, "isImageDecoderSupported", typeof ImageDecoder !== "undefined"); } + static get isFloat16ArraySupported() { + return shadow(this, "isFloat16ArraySupported", typeof Float16Array !== "undefined"); + } static get platform() { const { platform, @@ -1812,6 +1816,35 @@ function renderRichText({ fragment.firstChild.classList.add("richText", className); container.append(fragment); } +function makePathFromDrawOPS(data) { + const path = new Path2D(); + if (!data) { + return path; + } + for (let i = 0, ii = data.length; i < ii;) { + switch (data[i++]) { + case DrawOPS.moveTo: + path.moveTo(data[i++], data[i++]); + break; + case DrawOPS.lineTo: + path.lineTo(data[i++], data[i++]); + break; + case DrawOPS.curveTo: + path.bezierCurveTo(data[i++], data[i++], data[i++], data[i++], data[i++], data[i++]); + break; + case DrawOPS.quadraticCurveTo: + path.quadraticCurveTo(data[i++], data[i++], data[i++], data[i++]); + break; + case DrawOPS.closePath: + path.closePath(); + break; + default: + warn(`Unrecognized drawing path operator: ${data[i - 1]}`); + break; + } + } + return path; +} ;// ./src/display/editor/toolbar.js @@ -1978,23 +2011,34 @@ class EditorToolbar { async addButton(name, tool) { switch (name) { case "colorPicker": - this.addColorPicker(tool); + if (tool) { + this.addColorPicker(tool); + } break; case "altText": - await this.addAltText(tool); + if (tool) { + await this.addAltText(tool); + } break; case "editSignature": - await this.addEditSignatureButton(tool); + if (tool) { + await this.addEditSignatureButton(tool); + } break; case "delete": this.addDeleteButton(); break; case "comment": - this.addComment(tool); + if (tool) { + this.addComment(tool); + } break; } } async addButtonBefore(name, tool, beforeSelector) { + if (!tool && name === "comment") { + return; + } const beforeElement = this.#buttons.querySelector(beforeSelector); if (!beforeElement) { return; @@ -3195,11 +3239,11 @@ class AnnotationEditorUIManager { } addEditListeners() { this.#addKeyboardManager(); - this.#addCopyPasteListeners(); + this.setEditingState(true); } removeEditListeners() { this.#removeKeyboardManager(); - this.#removeCopyPasteListeners(); + this.setEditingState(false); } dragOver(event) { for (const { @@ -4482,7 +4526,7 @@ class Comment { comment.setAttribute("data-l10n-id", "pdfjs-show-comment-button"); } else { comment.ariaControlsElements = [this.#editor._uiManager.getCommentDialogElement()]; - comment.setAttribute("data-l10n-id", "pdfjs-editor-edit-comment-button"); + comment.setAttribute("data-l10n-id", "pdfjs-editor-add-comment-button"); } const signal = this.#editor._uiManager._signal; if (!(signal instanceof AbortSignal) || signal.aborted) { @@ -4895,6 +4939,7 @@ class AnnotationEditor { this.annotationElementId = parameters.annotationElementId || null; this.creationDate = parameters.creationDate || new Date(); this.modificationDate = parameters.modificationDate || null; + this.canAddComment = true; const { rotation, rawDims: { @@ -5574,7 +5619,7 @@ class AnnotationEditor { this.#comment?.focusButton(); } addCommentButton() { - return this.#comment ||= new Comment(this); + return this.canAddComment ? this.#comment ||= new Comment(this) : null; } addStandaloneCommentButton() { if (!this._uiManager.hasCommentManager()) { @@ -6150,7 +6195,7 @@ class AnnotationEditor { return this.div; } setCommentButtonStates(options) { - this.#comment.setCommentButtonStates(options); + this.#comment?.setCommentButtonStates(options); } keydown(event) { if (!this.isResizable || event.target !== this.div || event.key !== "Enter") { @@ -6814,6 +6859,7 @@ class PrintAnnotationStorage extends AnnotationStorage { ;// ./src/display/font_loader.js + class FontLoader { #systemFonts = new Set(); constructor({ @@ -6990,7 +7036,7 @@ class FontFaceObject { } catch (ex) { warn(`getPathGenerator - ignoring character: "${ex}".`); } - const path = new Path2D(cmds || ""); + const path = makePathFromDrawOPS(cmds); if (!this.fontExtraProperties) { objs.delete(objId); } @@ -7070,4646 +7116,4830 @@ class FontFaceObject { } } -;// ./src/display/api_utils.js +;// ./src/shared/obj-bin-transform.js -function getUrlProp(val) { - return null; -} -function getDataProp(val) { - if (val instanceof Uint8Array && val.byteLength === val.buffer.byteLength) { - return val; +class CssFontInfo { + #buffer; + #view; + #decoder; + static strings = ["fontFamily", "fontWeight", "italicAngle"]; + static write(info) { + const encoder = new TextEncoder(); + const encodedStrings = {}; + let stringsLength = 0; + for (const prop of CssFontInfo.strings) { + const encoded = encoder.encode(info[prop]); + encodedStrings[prop] = encoded; + stringsLength += 4 + encoded.length; + } + const buffer = new ArrayBuffer(stringsLength); + const data = new Uint8Array(buffer); + const view = new DataView(buffer); + let offset = 0; + for (const prop of CssFontInfo.strings) { + const encoded = encodedStrings[prop]; + const length = encoded.length; + view.setUint32(offset, length); + data.set(encoded, offset + 4); + offset += 4 + length; + } + assert(offset === buffer.byteLength, "CssFontInfo.write: Buffer overflow"); + return buffer; } - if (typeof val === "string") { - return stringToBytes(val); + constructor(buffer) { + this.#buffer = buffer; + this.#view = new DataView(this.#buffer); + this.#decoder = new TextDecoder(); } - if (val instanceof ArrayBuffer || ArrayBuffer.isView(val) || typeof val === "object" && !isNaN(val?.length)) { - return new Uint8Array(val); + #readString(index) { + assert(index < CssFontInfo.strings.length, "Invalid string index"); + let offset = 0; + for (let i = 0; i < index; i++) { + offset += this.#view.getUint32(offset) + 4; + } + const length = this.#view.getUint32(offset); + return this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); } - throw new Error("Invalid PDF binary data: either TypedArray, " + "string, or array-like object is expected in the data property."); -} -function getFactoryUrlProp(val) { - if (typeof val !== "string") { - return null; + get fontFamily() { + return this.#readString(0); } - if (val.endsWith("/")) { - return val; + get fontWeight() { + return this.#readString(1); } - throw new Error(`Invalid factory url: "${val}" must include trailing slash.`); -} -const isRefProxy = v => typeof v === "object" && Number.isInteger(v?.num) && v.num >= 0 && Number.isInteger(v?.gen) && v.gen >= 0; -const isNameProxy = v => typeof v === "object" && typeof v?.name === "string"; -const isValidExplicitDest = _isValidExplicitDest.bind(null, isRefProxy, isNameProxy); -class LoopbackPort { - #listeners = new Map(); - #deferred = Promise.resolve(); - postMessage(obj, transfer) { - const event = { - data: structuredClone(obj, transfer ? { - transfer - } : null) - }; - this.#deferred.then(() => { - for (const [listener] of this.#listeners) { - listener.call(this, event); - } - }); + get italicAngle() { + return this.#readString(2); } - addEventListener(name, listener, options = null) { - let rmAbort = null; - if (options?.signal instanceof AbortSignal) { - const { - signal - } = options; - if (signal.aborted) { - warn("LoopbackPort - cannot use an `aborted` signal."); - return; - } - const onAbort = () => this.removeEventListener(name, listener); - rmAbort = () => signal.removeEventListener("abort", onAbort); - signal.addEventListener("abort", onAbort); +} +class SystemFontInfo { + #buffer; + #view; + #decoder; + static strings = ["css", "loadedName", "baseFontName", "src"]; + static write(info) { + const encoder = new TextEncoder(); + const encodedStrings = {}; + let stringsLength = 0; + for (const prop of SystemFontInfo.strings) { + const encoded = encoder.encode(info[prop]); + encodedStrings[prop] = encoded; + stringsLength += 4 + encoded.length; } - this.#listeners.set(listener, rmAbort); + stringsLength += 4; + let encodedStyleStyle, + encodedStyleWeight, + lengthEstimate = 1 + stringsLength; + if (info.style) { + encodedStyleStyle = encoder.encode(info.style.style); + encodedStyleWeight = encoder.encode(info.style.weight); + lengthEstimate += 4 + encodedStyleStyle.length + 4 + encodedStyleWeight.length; + } + const buffer = new ArrayBuffer(lengthEstimate); + const data = new Uint8Array(buffer); + const view = new DataView(buffer); + let offset = 0; + view.setUint8(offset++, info.guessFallback ? 1 : 0); + view.setUint32(offset, 0); + offset += 4; + stringsLength = 0; + for (const prop of SystemFontInfo.strings) { + const encoded = encodedStrings[prop]; + const length = encoded.length; + stringsLength += 4 + length; + view.setUint32(offset, length); + data.set(encoded, offset + 4); + offset += 4 + length; + } + view.setUint32(offset - stringsLength - 4, stringsLength); + if (info.style) { + view.setUint32(offset, encodedStyleStyle.length); + data.set(encodedStyleStyle, offset + 4); + offset += 4 + encodedStyleStyle.length; + view.setUint32(offset, encodedStyleWeight.length); + data.set(encodedStyleWeight, offset + 4); + offset += 4 + encodedStyleWeight.length; + } + assert(offset <= buffer.byteLength, "SubstitionInfo.write: Buffer overflow"); + return buffer.transferToFixedLength(offset); } - removeEventListener(name, listener) { - const rmAbort = this.#listeners.get(listener); - rmAbort?.(); - this.#listeners.delete(listener); + constructor(buffer) { + this.#buffer = buffer; + this.#view = new DataView(this.#buffer); + this.#decoder = new TextDecoder(); } - terminate() { - for (const [, rmAbort] of this.#listeners) { - rmAbort?.(); + get guessFallback() { + return this.#view.getUint8(0) !== 0; + } + #readString(index) { + assert(index < SystemFontInfo.strings.length, "Invalid string index"); + let offset = 5; + for (let i = 0; i < index; i++) { + offset += this.#view.getUint32(offset) + 4; } - this.#listeners.clear(); + const length = this.#view.getUint32(offset); + return this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); } -} - -;// ./src/shared/message_handler.js - -const CallbackKind = { - DATA: 1, - ERROR: 2 -}; -const StreamKind = { - CANCEL: 1, - CANCEL_COMPLETE: 2, - CLOSE: 3, - ENQUEUE: 4, - ERROR: 5, - PULL: 6, - PULL_COMPLETE: 7, - START_COMPLETE: 8 -}; -function onFn() {} -function wrapReason(ex) { - if (ex instanceof AbortException || ex instanceof InvalidPDFException || ex instanceof PasswordException || ex instanceof ResponseException || ex instanceof UnknownErrorException) { - return ex; + get css() { + return this.#readString(0); } - if (!(ex instanceof Error || typeof ex === "object" && ex !== null)) { - unreachable('wrapReason: Expected "reason" to be a (possibly cloned) Error.'); + get loadedName() { + return this.#readString(1); } - switch (ex.name) { - case "AbortException": - return new AbortException(ex.message); - case "InvalidPDFException": - return new InvalidPDFException(ex.message); - case "PasswordException": - return new PasswordException(ex.message, ex.code); - case "ResponseException": - return new ResponseException(ex.message, ex.status, ex.missing); - case "UnknownErrorException": - return new UnknownErrorException(ex.message, ex.details); + get baseFontName() { + return this.#readString(2); } - return new UnknownErrorException(ex.message, ex.toString()); -} -class MessageHandler { - #messageAC = new AbortController(); - constructor(sourceName, targetName, comObj) { - this.sourceName = sourceName; - this.targetName = targetName; - this.comObj = comObj; - this.callbackId = 1; - this.streamId = 1; - this.streamSinks = Object.create(null); - this.streamControllers = Object.create(null); - this.callbackCapabilities = Object.create(null); - this.actionHandler = Object.create(null); - comObj.addEventListener("message", this.#onMessage.bind(this), { - signal: this.#messageAC.signal - }); + get src() { + return this.#readString(3); } - #onMessage({ - data - }) { - if (data.targetName !== this.sourceName) { - return; - } - if (data.stream) { - this.#processStreamMessage(data); - return; + get style() { + let offset = 1; + offset += 4 + this.#view.getUint32(offset); + const styleLength = this.#view.getUint32(offset); + const style = this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, styleLength)); + offset += 4 + styleLength; + const weightLength = this.#view.getUint32(offset); + const weight = this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, weightLength)); + return { + style, + weight + }; + } +} +class FontInfo { + static bools = ["black", "bold", "disableFontFace", "fontExtraProperties", "isInvalidPDFjsFont", "isType3Font", "italic", "missingFile", "remeasure", "vertical"]; + static numbers = ["ascent", "defaultWidth", "descent"]; + static strings = ["fallbackName", "loadedName", "mimetype", "name"]; + static #OFFSET_NUMBERS = Math.ceil(this.bools.length * 2 / 8); + static #OFFSET_BBOX = this.#OFFSET_NUMBERS + this.numbers.length * 8; + static #OFFSET_FONT_MATRIX = this.#OFFSET_BBOX + 1 + 2 * 4; + static #OFFSET_DEFAULT_VMETRICS = this.#OFFSET_FONT_MATRIX + 1 + 8 * 6; + static #OFFSET_STRINGS = this.#OFFSET_DEFAULT_VMETRICS + 1 + 2 * 3; + #buffer; + #decoder; + #view; + constructor({ + data, + extra + }) { + this.#buffer = data; + this.#decoder = new TextDecoder(); + this.#view = new DataView(this.#buffer); + if (extra) { + Object.assign(this, extra); } - if (data.callback) { - const callbackId = data.callbackId; - const capability = this.callbackCapabilities[callbackId]; - if (!capability) { - throw new Error(`Cannot resolve callback ${callbackId}`); - } - delete this.callbackCapabilities[callbackId]; - if (data.callback === CallbackKind.DATA) { - capability.resolve(data.data); - } else if (data.callback === CallbackKind.ERROR) { - capability.reject(wrapReason(data.reason)); - } else { - throw new Error("Unexpected callback case"); - } - return; + } + #readBoolean(index) { + assert(index < FontInfo.bools.length, "Invalid boolean index"); + const byteOffset = Math.floor(index / 4); + const bitOffset = index * 2 % 8; + const value = this.#view.getUint8(byteOffset) >> bitOffset & 0x03; + return value === 0x00 ? undefined : value === 0x02; + } + get black() { + return this.#readBoolean(0); + } + get bold() { + return this.#readBoolean(1); + } + get disableFontFace() { + return this.#readBoolean(2); + } + get fontExtraProperties() { + return this.#readBoolean(3); + } + get isInvalidPDFjsFont() { + return this.#readBoolean(4); + } + get isType3Font() { + return this.#readBoolean(5); + } + get italic() { + return this.#readBoolean(6); + } + get missingFile() { + return this.#readBoolean(7); + } + get remeasure() { + return this.#readBoolean(8); + } + get vertical() { + return this.#readBoolean(9); + } + #readNumber(index) { + assert(index < FontInfo.numbers.length, "Invalid number index"); + return this.#view.getFloat64(FontInfo.#OFFSET_NUMBERS + index * 8); + } + get ascent() { + return this.#readNumber(0); + } + get defaultWidth() { + return this.#readNumber(1); + } + get descent() { + return this.#readNumber(2); + } + get bbox() { + let offset = FontInfo.#OFFSET_BBOX; + const numCoords = this.#view.getUint8(offset); + if (numCoords === 0) { + return undefined; } - const action = this.actionHandler[data.action]; - if (!action) { - throw new Error(`Unknown action from worker: ${data.action}`); + offset += 1; + const bbox = []; + for (let i = 0; i < 4; i++) { + bbox.push(this.#view.getInt16(offset, true)); + offset += 2; } - if (data.callbackId) { - const sourceName = this.sourceName, - targetName = data.sourceName, - comObj = this.comObj; - Promise.try(action, data.data).then(function (result) { - comObj.postMessage({ - sourceName, - targetName, - callback: CallbackKind.DATA, - callbackId: data.callbackId, - data: result - }); - }, function (reason) { - comObj.postMessage({ - sourceName, - targetName, - callback: CallbackKind.ERROR, - callbackId: data.callbackId, - reason: wrapReason(reason) - }); - }); - return; + return bbox; + } + get fontMatrix() { + let offset = FontInfo.#OFFSET_FONT_MATRIX; + const numPoints = this.#view.getUint8(offset); + if (numPoints === 0) { + return undefined; } - if (data.streamId) { - this.#createStreamSink(data); - return; + offset += 1; + const fontMatrix = []; + for (let i = 0; i < 6; i++) { + fontMatrix.push(this.#view.getFloat64(offset, true)); + offset += 8; } - action(data.data); + return fontMatrix; } - on(actionName, handler) { - const ah = this.actionHandler; - if (ah[actionName]) { - throw new Error(`There is already an actionName called "${actionName}"`); + get defaultVMetrics() { + let offset = FontInfo.#OFFSET_DEFAULT_VMETRICS; + const numMetrics = this.#view.getUint8(offset); + if (numMetrics === 0) { + return undefined; } - ah[actionName] = handler; + offset += 1; + const defaultVMetrics = []; + for (let i = 0; i < 3; i++) { + defaultVMetrics.push(this.#view.getInt16(offset, true)); + offset += 2; + } + return defaultVMetrics; } - send(actionName, data, transfers) { - this.comObj.postMessage({ - sourceName: this.sourceName, - targetName: this.targetName, - action: actionName, - data - }, transfers); + #readString(index) { + assert(index < FontInfo.strings.length, "Invalid string index"); + let offset = FontInfo.#OFFSET_STRINGS + 4; + for (let i = 0; i < index; i++) { + offset += this.#view.getUint32(offset) + 4; + } + const length = this.#view.getUint32(offset); + const stringData = new Uint8Array(length); + stringData.set(new Uint8Array(this.#buffer, offset + 4, length)); + return this.#decoder.decode(stringData); } - sendWithPromise(actionName, data, transfers) { - const callbackId = this.callbackId++; - const capability = Promise.withResolvers(); - this.callbackCapabilities[callbackId] = capability; - try { - this.comObj.postMessage({ - sourceName: this.sourceName, - targetName: this.targetName, - action: actionName, - callbackId, - data - }, transfers); - } catch (ex) { - capability.reject(ex); + get fallbackName() { + return this.#readString(0); + } + get loadedName() { + return this.#readString(1); + } + get mimetype() { + return this.#readString(2); + } + get name() { + return this.#readString(3); + } + get data() { + let offset = FontInfo.#OFFSET_STRINGS; + const stringsLength = this.#view.getUint32(offset); + offset += 4 + stringsLength; + const systemFontInfoLength = this.#view.getUint32(offset); + offset += 4 + systemFontInfoLength; + const cssFontInfoLength = this.#view.getUint32(offset); + offset += 4 + cssFontInfoLength; + const length = this.#view.getUint32(offset); + if (length === 0) { + return undefined; } - return capability.promise; + return new Uint8Array(this.#buffer, offset + 4, length); } - sendWithStream(actionName, data, queueingStrategy, transfers) { - const streamId = this.streamId++, - sourceName = this.sourceName, - targetName = this.targetName, - comObj = this.comObj; - return new ReadableStream({ - start: controller => { - const startCapability = Promise.withResolvers(); - this.streamControllers[streamId] = { - controller, - startCall: startCapability, - pullCall: null, - cancelCall: null, - isClosed: false - }; - comObj.postMessage({ - sourceName, - targetName, - action: actionName, - streamId, - data, - desiredSize: controller.desiredSize - }, transfers); - return startCapability.promise; - }, - pull: controller => { - const pullCapability = Promise.withResolvers(); - this.streamControllers[streamId].pullCall = pullCapability; - comObj.postMessage({ - sourceName, - targetName, - stream: StreamKind.PULL, - streamId, - desiredSize: controller.desiredSize - }); - return pullCapability.promise; - }, - cancel: reason => { - assert(reason instanceof Error, "cancel must have a valid reason"); - const cancelCapability = Promise.withResolvers(); - this.streamControllers[streamId].cancelCall = cancelCapability; - this.streamControllers[streamId].isClosed = true; - comObj.postMessage({ - sourceName, - targetName, - stream: StreamKind.CANCEL, - streamId, - reason: wrapReason(reason) - }); - return cancelCapability.promise; - } - }, queueingStrategy); - } - #createStreamSink(data) { - const streamId = data.streamId, - sourceName = this.sourceName, - targetName = data.sourceName, - comObj = this.comObj; - const self = this, - action = this.actionHandler[data.action]; - const streamSink = { - enqueue(chunk, size = 1, transfers) { - if (this.isCancelled) { - return; - } - const lastDesiredSize = this.desiredSize; - this.desiredSize -= size; - if (lastDesiredSize > 0 && this.desiredSize <= 0) { - this.sinkCapability = Promise.withResolvers(); - this.ready = this.sinkCapability.promise; - } - comObj.postMessage({ - sourceName, - targetName, - stream: StreamKind.ENQUEUE, - streamId, - chunk - }, transfers); - }, - close() { - if (this.isCancelled) { - return; - } - this.isCancelled = true; - comObj.postMessage({ - sourceName, - targetName, - stream: StreamKind.CLOSE, - streamId - }); - delete self.streamSinks[streamId]; - }, - error(reason) { - assert(reason instanceof Error, "error must have a valid reason"); - if (this.isCancelled) { - return; - } - this.isCancelled = true; - comObj.postMessage({ - sourceName, - targetName, - stream: StreamKind.ERROR, - streamId, - reason: wrapReason(reason) - }); - }, - sinkCapability: Promise.withResolvers(), - onPull: null, - onCancel: null, - isCancelled: false, - desiredSize: data.desiredSize, - ready: null - }; - streamSink.sinkCapability.resolve(); - streamSink.ready = streamSink.sinkCapability.promise; - this.streamSinks[streamId] = streamSink; - Promise.try(action, data.data, streamSink).then(function () { - comObj.postMessage({ - sourceName, - targetName, - stream: StreamKind.START_COMPLETE, - streamId, - success: true - }); - }, function (reason) { - comObj.postMessage({ - sourceName, - targetName, - stream: StreamKind.START_COMPLETE, - streamId, - reason: wrapReason(reason) - }); - }); - } - #processStreamMessage(data) { - const streamId = data.streamId, - sourceName = this.sourceName, - targetName = data.sourceName, - comObj = this.comObj; - const streamController = this.streamControllers[streamId], - streamSink = this.streamSinks[streamId]; - switch (data.stream) { - case StreamKind.START_COMPLETE: - if (data.success) { - streamController.startCall.resolve(); - } else { - streamController.startCall.reject(wrapReason(data.reason)); - } - break; - case StreamKind.PULL_COMPLETE: - if (data.success) { - streamController.pullCall.resolve(); - } else { - streamController.pullCall.reject(wrapReason(data.reason)); - } - break; - case StreamKind.PULL: - if (!streamSink) { - comObj.postMessage({ - sourceName, - targetName, - stream: StreamKind.PULL_COMPLETE, - streamId, - success: true - }); - break; - } - if (streamSink.desiredSize <= 0 && data.desiredSize > 0) { - streamSink.sinkCapability.resolve(); - } - streamSink.desiredSize = data.desiredSize; - Promise.try(streamSink.onPull || onFn).then(function () { - comObj.postMessage({ - sourceName, - targetName, - stream: StreamKind.PULL_COMPLETE, - streamId, - success: true - }); - }, function (reason) { - comObj.postMessage({ - sourceName, - targetName, - stream: StreamKind.PULL_COMPLETE, - streamId, - reason: wrapReason(reason) - }); - }); - break; - case StreamKind.ENQUEUE: - assert(streamController, "enqueue should have stream controller"); - if (streamController.isClosed) { - break; - } - streamController.controller.enqueue(data.chunk); - break; - case StreamKind.CLOSE: - assert(streamController, "close should have stream controller"); - if (streamController.isClosed) { - break; - } - streamController.isClosed = true; - streamController.controller.close(); - this.#deleteStreamController(streamController, streamId); - break; - case StreamKind.ERROR: - assert(streamController, "error should have stream controller"); - streamController.controller.error(wrapReason(data.reason)); - this.#deleteStreamController(streamController, streamId); - break; - case StreamKind.CANCEL_COMPLETE: - if (data.success) { - streamController.cancelCall.resolve(); - } else { - streamController.cancelCall.reject(wrapReason(data.reason)); - } - this.#deleteStreamController(streamController, streamId); - break; - case StreamKind.CANCEL: - if (!streamSink) { - break; - } - const dataReason = wrapReason(data.reason); - Promise.try(streamSink.onCancel || onFn, dataReason).then(function () { - comObj.postMessage({ - sourceName, - targetName, - stream: StreamKind.CANCEL_COMPLETE, - streamId, - success: true - }); - }, function (reason) { - comObj.postMessage({ - sourceName, - targetName, - stream: StreamKind.CANCEL_COMPLETE, - streamId, - reason: wrapReason(reason) - }); - }); - streamSink.sinkCapability.reject(dataReason); - streamSink.isCancelled = true; - delete this.streamSinks[streamId]; - break; - default: - throw new Error("Unexpected stream case"); - } - } - async #deleteStreamController(streamController, streamId) { - await Promise.allSettled([streamController.startCall?.promise, streamController.pullCall?.promise, streamController.cancelCall?.promise]); - delete this.streamControllers[streamId]; - } - destroy() { - this.#messageAC?.abort(); - this.#messageAC = null; - } -} - -;// ./src/display/canvas_dependency_tracker.js - -const FORCED_DEPENDENCY_LABEL = "__forcedDependency"; -const { - floor, - ceil -} = Math; -function expandBBox(array, index, minX, minY, maxX, maxY) { - array[index * 4 + 0] = Math.min(array[index * 4 + 0], minX); - array[index * 4 + 1] = Math.min(array[index * 4 + 1], minY); - array[index * 4 + 2] = Math.max(array[index * 4 + 2], maxX); - array[index * 4 + 3] = Math.max(array[index * 4 + 3], maxY); -} -const EMPTY_BBOX = new Uint32Array(new Uint8Array([255, 255, 0, 0]).buffer)[0]; -class BBoxReader { - #bboxes; - #coords; - constructor(bboxes, coords) { - this.#bboxes = bboxes; - this.#coords = coords; - } - get length() { - return this.#bboxes.length; - } - isEmpty(i) { - return this.#bboxes[i] === EMPTY_BBOX; - } - minX(i) { - return this.#coords[i * 4 + 0] / 256; - } - minY(i) { - return this.#coords[i * 4 + 1] / 256; - } - maxX(i) { - return (this.#coords[i * 4 + 2] + 1) / 256; - } - maxY(i) { - return (this.#coords[i * 4 + 3] + 1) / 256; - } -} -const ensureDebugMetadata = (map, key) => { - if (!map) { - return undefined; - } - let value = map.get(key); - if (!value) { - value = { - dependencies: new Set(), - isRenderingOperation: false - }; - map.set(key, value); - } - return value; -}; -class CanvasDependencyTracker { - #simple = { - __proto__: null - }; - #incremental = { - __proto__: null, - transform: [], - moveText: [], - sameLineText: [], - [FORCED_DEPENDENCY_LABEL]: [] - }; - #namedDependencies = new Map(); - #savesStack = []; - #markedContentStack = []; - #baseTransformStack = [[1, 0, 0, 1, 0, 0]]; - #clipBox = [-Infinity, -Infinity, Infinity, Infinity]; - #pendingBBox = new Float64Array([Infinity, Infinity, -Infinity, -Infinity]); - #pendingBBoxIdx = -1; - #pendingDependencies = new Set(); - #operations = new Map(); - #fontBBoxTrustworthy = new Map(); - #canvasWidth; - #canvasHeight; - #bboxesCoords; - #bboxes; - #debugMetadata; - constructor(canvas, operationsCount, recordDebugMetadata = false) { - this.#canvasWidth = canvas.width; - this.#canvasHeight = canvas.height; - this.#initializeBBoxes(operationsCount); - if (recordDebugMetadata) { - this.#debugMetadata = new Map(); - } - } - growOperationsCount(operationsCount) { - if (operationsCount >= this.#bboxes.length) { - this.#initializeBBoxes(operationsCount, this.#bboxes); - } - } - #initializeBBoxes(operationsCount, oldBBoxes) { - const buffer = new ArrayBuffer(operationsCount * 4); - this.#bboxesCoords = new Uint8ClampedArray(buffer); - this.#bboxes = new Uint32Array(buffer); - if (oldBBoxes && oldBBoxes.length > 0) { - this.#bboxes.set(oldBBoxes); - this.#bboxes.fill(EMPTY_BBOX, oldBBoxes.length); - } else { - this.#bboxes.fill(EMPTY_BBOX); - } - } - save(opIdx) { - this.#simple = { - __proto__: this.#simple - }; - this.#incremental = { - __proto__: this.#incremental, - transform: { - __proto__: this.#incremental.transform - }, - moveText: { - __proto__: this.#incremental.moveText - }, - sameLineText: { - __proto__: this.#incremental.sameLineText - }, - [FORCED_DEPENDENCY_LABEL]: { - __proto__: this.#incremental[FORCED_DEPENDENCY_LABEL] - } - }; - this.#clipBox = { - __proto__: this.#clipBox - }; - this.#savesStack.push(opIdx); - return this; - } - restore(opIdx) { - const previous = Object.getPrototypeOf(this.#simple); - if (previous === null) { - return this; - } - this.#simple = previous; - this.#incremental = Object.getPrototypeOf(this.#incremental); - this.#clipBox = Object.getPrototypeOf(this.#clipBox); - const lastSave = this.#savesStack.pop(); - if (lastSave !== undefined) { - ensureDebugMetadata(this.#debugMetadata, opIdx)?.dependencies.add(lastSave); - this.#bboxes[opIdx] = this.#bboxes[lastSave]; - } - return this; - } - recordOpenMarker(idx) { - this.#savesStack.push(idx); - return this; - } - getOpenMarker() { - if (this.#savesStack.length === 0) { - return null; - } - return this.#savesStack.at(-1); - } - recordCloseMarker(opIdx) { - const lastSave = this.#savesStack.pop(); - if (lastSave !== undefined) { - ensureDebugMetadata(this.#debugMetadata, opIdx)?.dependencies.add(lastSave); - this.#bboxes[opIdx] = this.#bboxes[lastSave]; - } - return this; - } - beginMarkedContent(opIdx) { - this.#markedContentStack.push(opIdx); - return this; - } - endMarkedContent(opIdx) { - const lastSave = this.#markedContentStack.pop(); - if (lastSave !== undefined) { - ensureDebugMetadata(this.#debugMetadata, opIdx)?.dependencies.add(lastSave); - this.#bboxes[opIdx] = this.#bboxes[lastSave]; - } - return this; - } - pushBaseTransform(ctx) { - this.#baseTransformStack.push(Util.multiplyByDOMMatrix(this.#baseTransformStack.at(-1), ctx.getTransform())); - return this; - } - popBaseTransform() { - if (this.#baseTransformStack.length > 1) { - this.#baseTransformStack.pop(); - } - return this; - } - recordSimpleData(name, idx) { - this.#simple[name] = idx; - return this; - } - recordIncrementalData(name, idx) { - this.#incremental[name].push(idx); - return this; - } - resetIncrementalData(name, idx) { - this.#incremental[name].length = 0; - return this; - } - recordNamedData(name, idx) { - this.#namedDependencies.set(name, idx); - return this; - } - recordSimpleDataFromNamed(name, depName, fallbackIdx) { - this.#simple[name] = this.#namedDependencies.get(depName) ?? fallbackIdx; - } - recordFutureForcedDependency(name, idx) { - this.recordIncrementalData(FORCED_DEPENDENCY_LABEL, idx); - return this; - } - inheritSimpleDataAsFutureForcedDependencies(names) { - for (const name of names) { - if (name in this.#simple) { - this.recordFutureForcedDependency(name, this.#simple[name]); - } - } - return this; + clearData() { + let offset = FontInfo.#OFFSET_STRINGS; + const stringsLength = this.#view.getUint32(offset); + offset += 4 + stringsLength; + const systemFontInfoLength = this.#view.getUint32(offset); + offset += 4 + systemFontInfoLength; + const cssFontInfoLength = this.#view.getUint32(offset); + offset += 4 + cssFontInfoLength; + const length = this.#view.getUint32(offset); + const data = new Uint8Array(this.#buffer, offset + 4, length); + data.fill(0); + this.#view.setUint32(offset, 0); } - inheritPendingDependenciesAsFutureForcedDependencies() { - for (const dep of this.#pendingDependencies) { - this.recordFutureForcedDependency(FORCED_DEPENDENCY_LABEL, dep); + get cssFontInfo() { + let offset = FontInfo.#OFFSET_STRINGS; + const stringsLength = this.#view.getUint32(offset); + offset += 4 + stringsLength; + const systemFontInfoLength = this.#view.getUint32(offset); + offset += 4 + systemFontInfoLength; + const cssFontInfoLength = this.#view.getUint32(offset); + if (cssFontInfoLength === 0) { + return null; } - return this; + const cssFontInfoData = new Uint8Array(cssFontInfoLength); + cssFontInfoData.set(new Uint8Array(this.#buffer, offset + 4, cssFontInfoLength)); + return new CssFontInfo(cssFontInfoData.buffer); } - resetBBox(idx) { - if (this.#pendingBBoxIdx !== idx) { - this.#pendingBBoxIdx = idx; - this.#pendingBBox[0] = Infinity; - this.#pendingBBox[1] = Infinity; - this.#pendingBBox[2] = -Infinity; - this.#pendingBBox[3] = -Infinity; + get systemFontInfo() { + let offset = FontInfo.#OFFSET_STRINGS; + const stringsLength = this.#view.getUint32(offset); + offset += 4 + stringsLength; + const systemFontInfoLength = this.#view.getUint32(offset); + if (systemFontInfoLength === 0) { + return null; } - return this; + const systemFontInfoData = new Uint8Array(systemFontInfoLength); + systemFontInfoData.set(new Uint8Array(this.#buffer, offset + 4, systemFontInfoLength)); + return new SystemFontInfo(systemFontInfoData.buffer); } - recordClipBox(idx, ctx, minX, maxX, minY, maxY) { - const transform = Util.multiplyByDOMMatrix(this.#baseTransformStack.at(-1), ctx.getTransform()); - const clipBox = [Infinity, Infinity, -Infinity, -Infinity]; - Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, clipBox); - const intersection = Util.intersect(this.#clipBox, clipBox); - if (intersection) { - this.#clipBox[0] = intersection[0]; - this.#clipBox[1] = intersection[1]; - this.#clipBox[2] = intersection[2]; - this.#clipBox[3] = intersection[3]; - } else { - this.#clipBox[0] = this.#clipBox[1] = Infinity; - this.#clipBox[2] = this.#clipBox[3] = -Infinity; + static write(font) { + const systemFontInfoBuffer = font.systemFontInfo ? SystemFontInfo.write(font.systemFontInfo) : null; + const cssFontInfoBuffer = font.cssFontInfo ? CssFontInfo.write(font.cssFontInfo) : null; + const encoder = new TextEncoder(); + const encodedStrings = {}; + let stringsLength = 0; + for (const prop of FontInfo.strings) { + encodedStrings[prop] = encoder.encode(font[prop]); + stringsLength += 4 + encodedStrings[prop].length; } - return this; - } - recordBBox(idx, ctx, minX, maxX, minY, maxY) { - const clipBox = this.#clipBox; - if (clipBox[0] === Infinity) { - return this; + const lengthEstimate = FontInfo.#OFFSET_STRINGS + 4 + stringsLength + 4 + (systemFontInfoBuffer ? systemFontInfoBuffer.byteLength : 0) + 4 + (cssFontInfoBuffer ? cssFontInfoBuffer.byteLength : 0) + 4 + (font.data ? font.data.length : 0); + const buffer = new ArrayBuffer(lengthEstimate); + const data = new Uint8Array(buffer); + const view = new DataView(buffer); + let offset = 0; + const numBools = FontInfo.bools.length; + let boolByte = 0, + boolBit = 0; + for (let i = 0; i < numBools; i++) { + const value = font[FontInfo.bools[i]]; + const bits = value === undefined ? 0x00 : value ? 0x02 : 0x01; + boolByte |= bits << boolBit; + boolBit += 2; + if (boolBit === 8 || i === numBools - 1) { + view.setUint8(offset++, boolByte); + boolByte = 0; + boolBit = 0; + } } - const transform = Util.multiplyByDOMMatrix(this.#baseTransformStack.at(-1), ctx.getTransform()); - if (clipBox[0] === -Infinity) { - Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, this.#pendingBBox); - return this; + assert(offset === FontInfo.#OFFSET_NUMBERS, "FontInfo.write: Boolean properties offset mismatch"); + for (const prop of FontInfo.numbers) { + view.setFloat64(offset, font[prop]); + offset += 8; } - const bbox = [Infinity, Infinity, -Infinity, -Infinity]; - Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, bbox); - this.#pendingBBox[0] = Math.min(this.#pendingBBox[0], Math.max(bbox[0], clipBox[0])); - this.#pendingBBox[1] = Math.min(this.#pendingBBox[1], Math.max(bbox[1], clipBox[1])); - this.#pendingBBox[2] = Math.max(this.#pendingBBox[2], Math.min(bbox[2], clipBox[2])); - this.#pendingBBox[3] = Math.max(this.#pendingBBox[3], Math.min(bbox[3], clipBox[3])); - return this; - } - recordCharacterBBox(idx, ctx, font, scale = 1, x = 0, y = 0, getMeasure) { - const fontBBox = font.bbox; - let isBBoxTrustworthy; - let computedBBox; - if (fontBBox) { - isBBoxTrustworthy = fontBBox[2] !== fontBBox[0] && fontBBox[3] !== fontBBox[1] && this.#fontBBoxTrustworthy.get(font); - if (isBBoxTrustworthy !== false) { - computedBBox = [0, 0, 0, 0]; - Util.axialAlignedBoundingBox(fontBBox, font.fontMatrix, computedBBox); - if (scale !== 1 || x !== 0 || y !== 0) { - Util.scaleMinMax([scale, 0, 0, -scale, x, y], computedBBox); - } - if (isBBoxTrustworthy) { - return this.recordBBox(idx, ctx, computedBBox[0], computedBBox[2], computedBBox[1], computedBBox[3]); - } + assert(offset === FontInfo.#OFFSET_BBOX, "FontInfo.write: Number properties offset mismatch"); + if (font.bbox) { + view.setUint8(offset++, 4); + for (const coord of font.bbox) { + view.setInt16(offset, coord, true); + offset += 2; } + } else { + view.setUint8(offset++, 0); + offset += 2 * 4; } - if (!getMeasure) { - return this.recordFullPageBBox(idx); - } - const measure = getMeasure(); - if (fontBBox && computedBBox && isBBoxTrustworthy === undefined) { - isBBoxTrustworthy = computedBBox[0] <= x - measure.actualBoundingBoxLeft && computedBBox[2] >= x + measure.actualBoundingBoxRight && computedBBox[1] <= y - measure.actualBoundingBoxAscent && computedBBox[3] >= y + measure.actualBoundingBoxDescent; - this.#fontBBoxTrustworthy.set(font, isBBoxTrustworthy); - if (isBBoxTrustworthy) { - return this.recordBBox(idx, ctx, computedBBox[0], computedBBox[2], computedBBox[1], computedBBox[3]); + assert(offset === FontInfo.#OFFSET_FONT_MATRIX, "FontInfo.write: BBox properties offset mismatch"); + if (font.fontMatrix) { + view.setUint8(offset++, 6); + for (const point of font.fontMatrix) { + view.setFloat64(offset, point, true); + offset += 8; } + } else { + view.setUint8(offset++, 0); + offset += 8 * 6; } - return this.recordBBox(idx, ctx, x - measure.actualBoundingBoxLeft, x + measure.actualBoundingBoxRight, y - measure.actualBoundingBoxAscent, y + measure.actualBoundingBoxDescent); - } - recordFullPageBBox(idx) { - this.#pendingBBox[0] = Math.max(0, this.#clipBox[0]); - this.#pendingBBox[1] = Math.max(0, this.#clipBox[1]); - this.#pendingBBox[2] = Math.min(this.#canvasWidth, this.#clipBox[2]); - this.#pendingBBox[3] = Math.min(this.#canvasHeight, this.#clipBox[3]); - return this; - } - getSimpleIndex(dependencyName) { - return this.#simple[dependencyName]; - } - recordDependencies(idx, dependencyNames) { - const pendingDependencies = this.#pendingDependencies; - const simple = this.#simple; - const incremental = this.#incremental; - for (const name of dependencyNames) { - if (name in this.#simple) { - pendingDependencies.add(simple[name]); - } else if (name in incremental) { - incremental[name].forEach(pendingDependencies.add, pendingDependencies); + assert(offset === FontInfo.#OFFSET_DEFAULT_VMETRICS, "FontInfo.write: FontMatrix properties offset mismatch"); + if (font.defaultVMetrics) { + view.setUint8(offset++, 1); + for (const metric of font.defaultVMetrics) { + view.setInt16(offset, metric, true); + offset += 2; } + } else { + view.setUint8(offset++, 0); + offset += 3 * 2; } - return this; - } - recordNamedDependency(idx, name) { - if (this.#namedDependencies.has(name)) { - this.#pendingDependencies.add(this.#namedDependencies.get(name)); + assert(offset === FontInfo.#OFFSET_STRINGS, "FontInfo.write: DefaultVMetrics properties offset mismatch"); + view.setUint32(FontInfo.#OFFSET_STRINGS, 0); + offset += 4; + for (const prop of FontInfo.strings) { + const encoded = encodedStrings[prop]; + const length = encoded.length; + view.setUint32(offset, length); + data.set(encoded, offset + 4); + offset += 4 + length; } - return this; - } - recordOperation(idx, preserve = false) { - this.recordDependencies(idx, [FORCED_DEPENDENCY_LABEL]); - if (this.#debugMetadata) { - const metadata = ensureDebugMetadata(this.#debugMetadata, idx); - const { - dependencies - } = metadata; - this.#pendingDependencies.forEach(dependencies.add, dependencies); - this.#savesStack.forEach(dependencies.add, dependencies); - this.#markedContentStack.forEach(dependencies.add, dependencies); - dependencies.delete(idx); - metadata.isRenderingOperation = true; + view.setUint32(FontInfo.#OFFSET_STRINGS, offset - FontInfo.#OFFSET_STRINGS - 4); + if (!systemFontInfoBuffer) { + view.setUint32(offset, 0); + offset += 4; + } else { + const length = systemFontInfoBuffer.byteLength; + view.setUint32(offset, length); + assert(offset + 4 + length <= buffer.byteLength, "FontInfo.write: Buffer overflow at systemFontInfo"); + data.set(new Uint8Array(systemFontInfoBuffer), offset + 4); + offset += 4 + length; } - if (this.#pendingBBoxIdx === idx) { - const minX = floor(this.#pendingBBox[0] * 256 / this.#canvasWidth); - const minY = floor(this.#pendingBBox[1] * 256 / this.#canvasHeight); - const maxX = ceil(this.#pendingBBox[2] * 256 / this.#canvasWidth); - const maxY = ceil(this.#pendingBBox[3] * 256 / this.#canvasHeight); - expandBBox(this.#bboxesCoords, idx, minX, minY, maxX, maxY); - for (const depIdx of this.#pendingDependencies) { - if (depIdx !== idx) { - expandBBox(this.#bboxesCoords, depIdx, minX, minY, maxX, maxY); - } - } - for (const saveIdx of this.#savesStack) { - if (saveIdx !== idx) { - expandBBox(this.#bboxesCoords, saveIdx, minX, minY, maxX, maxY); - } - } - for (const saveIdx of this.#markedContentStack) { - if (saveIdx !== idx) { - expandBBox(this.#bboxesCoords, saveIdx, minX, minY, maxX, maxY); - } - } - if (!preserve) { - this.#pendingDependencies.clear(); - this.#pendingBBoxIdx = -1; - } + if (!cssFontInfoBuffer) { + view.setUint32(offset, 0); + offset += 4; + } else { + const length = cssFontInfoBuffer.byteLength; + view.setUint32(offset, length); + assert(offset + 4 + length <= buffer.byteLength, "FontInfo.write: Buffer overflow at cssFontInfo"); + data.set(new Uint8Array(cssFontInfoBuffer), offset + 4); + offset += 4 + length; + } + if (font.data === undefined) { + view.setUint32(offset, 0); + offset += 4; + } else { + view.setUint32(offset, font.data.length); + data.set(font.data, offset + 4); + offset += 4 + font.data.length; } - return this; + assert(offset <= buffer.byteLength, "FontInfo.write: Buffer overflow"); + return buffer.transferToFixedLength(offset); } - recordShowTextOperation(idx, preserve = false) { - const deps = Array.from(this.#pendingDependencies); - this.recordOperation(idx, preserve); - this.recordIncrementalData("sameLineText", idx); - for (const dep of deps) { - this.recordIncrementalData("sameLineText", dep); +} +class PatternInfo { + static #KIND = 0; + static #HAS_BBOX = 1; + static #HAS_BACKGROUND = 2; + static #SHADING_TYPE = 3; + static #N_COORD = 4; + static #N_COLOR = 8; + static #N_STOP = 12; + static #N_FIGURES = 16; + constructor(buffer) { + this.buffer = buffer; + this.view = new DataView(buffer); + this.data = new Uint8Array(buffer); + } + static write(ir) { + let kind, + bbox = null, + coords = [], + colors = [], + colorStops = [], + figures = [], + shadingType = null, + background = null; + switch (ir[0]) { + case "RadialAxial": + kind = ir[1] === "axial" ? 1 : 2; + bbox = ir[2]; + colorStops = ir[3]; + if (kind === 1) { + coords.push(...ir[4], ...ir[5]); + } else { + coords.push(ir[4][0], ir[4][1], ir[6], ir[5][0], ir[5][1], ir[7]); + } + break; + case "Mesh": + kind = 3; + shadingType = ir[1]; + coords = ir[2]; + colors = ir[3]; + figures = ir[4] || []; + bbox = ir[6]; + background = ir[7]; + break; + default: + throw new Error(`Unsupported pattern type: ${ir[0]}`); + } + const nCoord = Math.floor(coords.length / 2); + const nColor = Math.floor(colors.length / 3); + const nStop = colorStops.length; + const nFigures = figures.length; + let figuresSize = 0; + for (const figure of figures) { + figuresSize += 1; + figuresSize = Math.ceil(figuresSize / 4) * 4; + figuresSize += 4 + figure.coords.length * 4; + figuresSize += 4 + figure.colors.length * 4; + if (figure.verticesPerRow !== undefined) { + figuresSize += 4; + } + } + const byteLen = 20 + nCoord * 8 + nColor * 3 + nStop * 8 + (bbox ? 16 : 0) + (background ? 3 : 0) + figuresSize; + const buffer = new ArrayBuffer(byteLen); + const dataView = new DataView(buffer); + const u8data = new Uint8Array(buffer); + dataView.setUint8(PatternInfo.#KIND, kind); + dataView.setUint8(PatternInfo.#HAS_BBOX, bbox ? 1 : 0); + dataView.setUint8(PatternInfo.#HAS_BACKGROUND, background ? 1 : 0); + dataView.setUint8(PatternInfo.#SHADING_TYPE, shadingType); + dataView.setUint32(PatternInfo.#N_COORD, nCoord, true); + dataView.setUint32(PatternInfo.#N_COLOR, nColor, true); + dataView.setUint32(PatternInfo.#N_STOP, nStop, true); + dataView.setUint32(PatternInfo.#N_FIGURES, nFigures, true); + let offset = 20; + const coordsView = new Float32Array(buffer, offset, nCoord * 2); + coordsView.set(coords); + offset += nCoord * 8; + u8data.set(colors, offset); + offset += nColor * 3; + for (const [pos, hex] of colorStops) { + dataView.setFloat32(offset, pos, true); + offset += 4; + dataView.setUint32(offset, parseInt(hex.slice(1), 16), true); + offset += 4; } - return this; - } - bboxToClipBoxDropOperation(idx, preserve = false) { - if (this.#pendingBBoxIdx === idx) { - this.#pendingBBoxIdx = -1; - this.#clipBox[0] = Math.max(this.#clipBox[0], this.#pendingBBox[0]); - this.#clipBox[1] = Math.max(this.#clipBox[1], this.#pendingBBox[1]); - this.#clipBox[2] = Math.min(this.#clipBox[2], this.#pendingBBox[2]); - this.#clipBox[3] = Math.min(this.#clipBox[3], this.#pendingBBox[3]); - if (!preserve) { - this.#pendingDependencies.clear(); + if (bbox) { + for (const v of bbox) { + dataView.setFloat32(offset, v, true); + offset += 4; } } - return this; - } - _takePendingDependencies() { - const pendingDependencies = this.#pendingDependencies; - this.#pendingDependencies = new Set(); - return pendingDependencies; - } - _extractOperation(idx) { - const operation = this.#operations.get(idx); - this.#operations.delete(idx); - return operation; - } - _pushPendingDependencies(dependencies) { - for (const dep of dependencies) { - this.#pendingDependencies.add(dep); + if (background) { + u8data.set(background, offset); + offset += 3; } - } - take() { - this.#fontBBoxTrustworthy.clear(); - return new BBoxReader(this.#bboxes, this.#bboxesCoords); - } - takeDebugMetadata() { - return this.#debugMetadata; - } -} -class CanvasNestedDependencyTracker { - #dependencyTracker; - #opIdx; - #ignoreBBoxes; - #nestingLevel = 0; - #savesLevel = 0; - constructor(dependencyTracker, opIdx, ignoreBBoxes) { - if (dependencyTracker instanceof CanvasNestedDependencyTracker && dependencyTracker.#ignoreBBoxes === !!ignoreBBoxes) { - return dependencyTracker; + for (let i = 0; i < figures.length; i++) { + const figure = figures[i]; + dataView.setUint8(offset, figure.type); + offset += 1; + offset = Math.ceil(offset / 4) * 4; + dataView.setUint32(offset, figure.coords.length, true); + offset += 4; + const figureCoordsView = new Int32Array(buffer, offset, figure.coords.length); + figureCoordsView.set(figure.coords); + offset += figure.coords.length * 4; + dataView.setUint32(offset, figure.colors.length, true); + offset += 4; + const colorsView = new Int32Array(buffer, offset, figure.colors.length); + colorsView.set(figure.colors); + offset += figure.colors.length * 4; + if (figure.verticesPerRow !== undefined) { + dataView.setUint32(offset, figure.verticesPerRow, true); + offset += 4; + } } - this.#dependencyTracker = dependencyTracker; - this.#opIdx = opIdx; - this.#ignoreBBoxes = !!ignoreBBoxes; - } - growOperationsCount() { - throw new Error("Unreachable"); - } - save(opIdx) { - this.#savesLevel++; - this.#dependencyTracker.save(this.#opIdx); - return this; + return buffer; } - restore(opIdx) { - if (this.#savesLevel > 0) { - this.#dependencyTracker.restore(this.#opIdx); - this.#savesLevel--; + getIR() { + const dataView = this.view; + const kind = this.data[PatternInfo.#KIND]; + const hasBBox = !!this.data[PatternInfo.#HAS_BBOX]; + const hasBackground = !!this.data[PatternInfo.#HAS_BACKGROUND]; + const nCoord = dataView.getUint32(PatternInfo.#N_COORD, true); + const nColor = dataView.getUint32(PatternInfo.#N_COLOR, true); + const nStop = dataView.getUint32(PatternInfo.#N_STOP, true); + const nFigures = dataView.getUint32(PatternInfo.#N_FIGURES, true); + let offset = 20; + const coords = new Float32Array(this.buffer, offset, nCoord * 2); + offset += nCoord * 8; + const colors = new Uint8Array(this.buffer, offset, nColor * 3); + offset += nColor * 3; + const stops = []; + for (let i = 0; i < nStop; ++i) { + const p = dataView.getFloat32(offset, true); + offset += 4; + const rgb = dataView.getUint32(offset, true); + offset += 4; + stops.push([p, `#${rgb.toString(16).padStart(6, "0")}`]); + } + let bbox = null; + if (hasBBox) { + bbox = []; + for (let i = 0; i < 4; ++i) { + bbox.push(dataView.getFloat32(offset, true)); + offset += 4; + } + } + let background = null; + if (hasBackground) { + background = new Uint8Array(this.buffer, offset, 3); + offset += 3; + } + const figures = []; + for (let i = 0; i < nFigures; ++i) { + const type = dataView.getUint8(offset); + offset += 1; + offset = Math.ceil(offset / 4) * 4; + const coordsLength = dataView.getUint32(offset, true); + offset += 4; + const figureCoords = new Int32Array(this.buffer, offset, coordsLength); + offset += coordsLength * 4; + const colorsLength = dataView.getUint32(offset, true); + offset += 4; + const figureColors = new Int32Array(this.buffer, offset, colorsLength); + offset += colorsLength * 4; + const figure = { + type, + coords: figureCoords, + colors: figureColors + }; + if (type === MeshFigureType.LATTICE) { + figure.verticesPerRow = dataView.getUint32(offset, true); + offset += 4; + } + figures.push(figure); + } + if (kind === 1) { + return ["RadialAxial", "axial", bbox, stops, Array.from(coords.slice(0, 2)), Array.from(coords.slice(2, 4)), null, null]; + } + if (kind === 2) { + return ["RadialAxial", "radial", bbox, stops, [coords[0], coords[1]], [coords[3], coords[4]], coords[2], coords[5]]; + } + if (kind === 3) { + const shadingType = this.data[PatternInfo.#SHADING_TYPE]; + let bounds = null; + if (coords.length > 0) { + let minX = coords[0], + maxX = coords[0]; + let minY = coords[1], + maxY = coords[1]; + for (let i = 0; i < coords.length; i += 2) { + const x = coords[i], + y = coords[i + 1]; + minX = minX > x ? x : minX; + minY = minY > y ? y : minY; + maxX = maxX < x ? x : maxX; + maxY = maxY < y ? y : maxY; + } + bounds = [minX, minY, maxX, maxY]; + } + return ["Mesh", shadingType, coords, colors, figures, bounds, bbox, background]; } - return this; - } - recordOpenMarker(idx) { - this.#nestingLevel++; - return this; - } - getOpenMarker() { - return this.#nestingLevel > 0 ? this.#opIdx : this.#dependencyTracker.getOpenMarker(); + throw new Error(`Unsupported pattern kind: ${kind}`); } - recordCloseMarker(idx) { - this.#nestingLevel--; - return this; +} + +;// ./src/display/api_utils.js + +function getUrlProp(val) { + return null; +} +function getDataProp(val) { + if (val instanceof Uint8Array && val.byteLength === val.buffer.byteLength) { + return val; } - beginMarkedContent(opIdx) { - return this; + if (typeof val === "string") { + return stringToBytes(val); } - endMarkedContent(opIdx) { - return this; + if (val instanceof ArrayBuffer || ArrayBuffer.isView(val) || typeof val === "object" && !isNaN(val?.length)) { + return new Uint8Array(val); } - pushBaseTransform(ctx) { - this.#dependencyTracker.pushBaseTransform(ctx); - return this; + throw new Error("Invalid PDF binary data: either TypedArray, " + "string, or array-like object is expected in the data property."); +} +function getFactoryUrlProp(val) { + if (typeof val !== "string") { + return null; } - popBaseTransform() { - this.#dependencyTracker.popBaseTransform(); - return this; + if (val.endsWith("/")) { + return val; } - recordSimpleData(name, idx) { - this.#dependencyTracker.recordSimpleData(name, this.#opIdx); - return this; + throw new Error(`Invalid factory url: "${val}" must include trailing slash.`); +} +const isRefProxy = v => typeof v === "object" && Number.isInteger(v?.num) && v.num >= 0 && Number.isInteger(v?.gen) && v.gen >= 0; +const isNameProxy = v => typeof v === "object" && typeof v?.name === "string"; +const isValidExplicitDest = _isValidExplicitDest.bind(null, isRefProxy, isNameProxy); +class LoopbackPort { + #listeners = new Map(); + #deferred = Promise.resolve(); + postMessage(obj, transfer) { + const event = { + data: structuredClone(obj, transfer ? { + transfer + } : null) + }; + this.#deferred.then(() => { + for (const [listener] of this.#listeners) { + listener.call(this, event); + } + }); } - recordIncrementalData(name, idx) { - this.#dependencyTracker.recordIncrementalData(name, this.#opIdx); - return this; + addEventListener(name, listener, options = null) { + let rmAbort = null; + if (options?.signal instanceof AbortSignal) { + const { + signal + } = options; + if (signal.aborted) { + warn("LoopbackPort - cannot use an `aborted` signal."); + return; + } + const onAbort = () => this.removeEventListener(name, listener); + rmAbort = () => signal.removeEventListener("abort", onAbort); + signal.addEventListener("abort", onAbort); + } + this.#listeners.set(listener, rmAbort); } - resetIncrementalData(name, idx) { - this.#dependencyTracker.resetIncrementalData(name, this.#opIdx); - return this; + removeEventListener(name, listener) { + const rmAbort = this.#listeners.get(listener); + rmAbort?.(); + this.#listeners.delete(listener); } - recordNamedData(name, idx) { - return this; + terminate() { + for (const [, rmAbort] of this.#listeners) { + rmAbort?.(); + } + this.#listeners.clear(); } - recordSimpleDataFromNamed(name, depName, fallbackIdx) { - this.#dependencyTracker.recordSimpleDataFromNamed(name, depName, this.#opIdx); - return this; +} + +;// ./src/shared/message_handler.js + +const CallbackKind = { + DATA: 1, + ERROR: 2 +}; +const StreamKind = { + CANCEL: 1, + CANCEL_COMPLETE: 2, + CLOSE: 3, + ENQUEUE: 4, + ERROR: 5, + PULL: 6, + PULL_COMPLETE: 7, + START_COMPLETE: 8 +}; +function onFn() {} +function wrapReason(ex) { + if (ex instanceof AbortException || ex instanceof InvalidPDFException || ex instanceof PasswordException || ex instanceof ResponseException || ex instanceof UnknownErrorException) { + return ex; } - recordFutureForcedDependency(name, idx) { - this.#dependencyTracker.recordFutureForcedDependency(name, this.#opIdx); - return this; + if (!(ex instanceof Error || typeof ex === "object" && ex !== null)) { + unreachable('wrapReason: Expected "reason" to be a (possibly cloned) Error.'); } - inheritSimpleDataAsFutureForcedDependencies(names) { - this.#dependencyTracker.inheritSimpleDataAsFutureForcedDependencies(names); - return this; + switch (ex.name) { + case "AbortException": + return new AbortException(ex.message); + case "InvalidPDFException": + return new InvalidPDFException(ex.message); + case "PasswordException": + return new PasswordException(ex.message, ex.code); + case "ResponseException": + return new ResponseException(ex.message, ex.status, ex.missing); + case "UnknownErrorException": + return new UnknownErrorException(ex.message, ex.details); } - inheritPendingDependenciesAsFutureForcedDependencies() { - this.#dependencyTracker.inheritPendingDependenciesAsFutureForcedDependencies(); - return this; + return new UnknownErrorException(ex.message, ex.toString()); +} +class MessageHandler { + #messageAC = new AbortController(); + constructor(sourceName, targetName, comObj) { + this.sourceName = sourceName; + this.targetName = targetName; + this.comObj = comObj; + this.callbackId = 1; + this.streamId = 1; + this.streamSinks = Object.create(null); + this.streamControllers = Object.create(null); + this.callbackCapabilities = Object.create(null); + this.actionHandler = Object.create(null); + comObj.addEventListener("message", this.#onMessage.bind(this), { + signal: this.#messageAC.signal + }); } - resetBBox(idx) { - if (!this.#ignoreBBoxes) { - this.#dependencyTracker.resetBBox(this.#opIdx); + #onMessage({ + data + }) { + if (data.targetName !== this.sourceName) { + return; } - return this; - } - recordClipBox(idx, ctx, minX, maxX, minY, maxY) { - if (!this.#ignoreBBoxes) { - this.#dependencyTracker.recordClipBox(this.#opIdx, ctx, minX, maxX, minY, maxY); + if (data.stream) { + this.#processStreamMessage(data); + return; } - return this; - } - recordBBox(idx, ctx, minX, maxX, minY, maxY) { - if (!this.#ignoreBBoxes) { - this.#dependencyTracker.recordBBox(this.#opIdx, ctx, minX, maxX, minY, maxY); + if (data.callback) { + const callbackId = data.callbackId; + const capability = this.callbackCapabilities[callbackId]; + if (!capability) { + throw new Error(`Cannot resolve callback ${callbackId}`); + } + delete this.callbackCapabilities[callbackId]; + if (data.callback === CallbackKind.DATA) { + capability.resolve(data.data); + } else if (data.callback === CallbackKind.ERROR) { + capability.reject(wrapReason(data.reason)); + } else { + throw new Error("Unexpected callback case"); + } + return; } - return this; - } - recordCharacterBBox(idx, ctx, font, scale, x, y, getMeasure) { - if (!this.#ignoreBBoxes) { - this.#dependencyTracker.recordCharacterBBox(this.#opIdx, ctx, font, scale, x, y, getMeasure); + const action = this.actionHandler[data.action]; + if (!action) { + throw new Error(`Unknown action from worker: ${data.action}`); } - return this; - } - recordFullPageBBox(idx) { - if (!this.#ignoreBBoxes) { - this.#dependencyTracker.recordFullPageBBox(this.#opIdx); + if (data.callbackId) { + const sourceName = this.sourceName, + targetName = data.sourceName, + comObj = this.comObj; + Promise.try(action, data.data).then(function (result) { + comObj.postMessage({ + sourceName, + targetName, + callback: CallbackKind.DATA, + callbackId: data.callbackId, + data: result + }); + }, function (reason) { + comObj.postMessage({ + sourceName, + targetName, + callback: CallbackKind.ERROR, + callbackId: data.callbackId, + reason: wrapReason(reason) + }); + }); + return; } - return this; + if (data.streamId) { + this.#createStreamSink(data); + return; + } + action(data.data); } - getSimpleIndex(dependencyName) { - return this.#dependencyTracker.getSimpleIndex(dependencyName); + on(actionName, handler) { + const ah = this.actionHandler; + if (ah[actionName]) { + throw new Error(`There is already an actionName called "${actionName}"`); + } + ah[actionName] = handler; } - recordDependencies(idx, dependencyNames) { - this.#dependencyTracker.recordDependencies(this.#opIdx, dependencyNames); - return this; + send(actionName, data, transfers) { + this.comObj.postMessage({ + sourceName: this.sourceName, + targetName: this.targetName, + action: actionName, + data + }, transfers); } - recordNamedDependency(idx, name) { - this.#dependencyTracker.recordNamedDependency(this.#opIdx, name); - return this; + sendWithPromise(actionName, data, transfers) { + const callbackId = this.callbackId++; + const capability = Promise.withResolvers(); + this.callbackCapabilities[callbackId] = capability; + try { + this.comObj.postMessage({ + sourceName: this.sourceName, + targetName: this.targetName, + action: actionName, + callbackId, + data + }, transfers); + } catch (ex) { + capability.reject(ex); + } + return capability.promise; } - recordOperation(idx) { - this.#dependencyTracker.recordOperation(this.#opIdx, true); - return this; + sendWithStream(actionName, data, queueingStrategy, transfers) { + const streamId = this.streamId++, + sourceName = this.sourceName, + targetName = this.targetName, + comObj = this.comObj; + return new ReadableStream({ + start: controller => { + const startCapability = Promise.withResolvers(); + this.streamControllers[streamId] = { + controller, + startCall: startCapability, + pullCall: null, + cancelCall: null, + isClosed: false + }; + comObj.postMessage({ + sourceName, + targetName, + action: actionName, + streamId, + data, + desiredSize: controller.desiredSize + }, transfers); + return startCapability.promise; + }, + pull: controller => { + const pullCapability = Promise.withResolvers(); + this.streamControllers[streamId].pullCall = pullCapability; + comObj.postMessage({ + sourceName, + targetName, + stream: StreamKind.PULL, + streamId, + desiredSize: controller.desiredSize + }); + return pullCapability.promise; + }, + cancel: reason => { + assert(reason instanceof Error, "cancel must have a valid reason"); + const cancelCapability = Promise.withResolvers(); + this.streamControllers[streamId].cancelCall = cancelCapability; + this.streamControllers[streamId].isClosed = true; + comObj.postMessage({ + sourceName, + targetName, + stream: StreamKind.CANCEL, + streamId, + reason: wrapReason(reason) + }); + return cancelCapability.promise; + } + }, queueingStrategy); } - recordShowTextOperation(idx) { - this.#dependencyTracker.recordShowTextOperation(this.#opIdx, true); - return this; + #createStreamSink(data) { + const streamId = data.streamId, + sourceName = this.sourceName, + targetName = data.sourceName, + comObj = this.comObj; + const self = this, + action = this.actionHandler[data.action]; + const streamSink = { + enqueue(chunk, size = 1, transfers) { + if (this.isCancelled) { + return; + } + const lastDesiredSize = this.desiredSize; + this.desiredSize -= size; + if (lastDesiredSize > 0 && this.desiredSize <= 0) { + this.sinkCapability = Promise.withResolvers(); + this.ready = this.sinkCapability.promise; + } + comObj.postMessage({ + sourceName, + targetName, + stream: StreamKind.ENQUEUE, + streamId, + chunk + }, transfers); + }, + close() { + if (this.isCancelled) { + return; + } + this.isCancelled = true; + comObj.postMessage({ + sourceName, + targetName, + stream: StreamKind.CLOSE, + streamId + }); + delete self.streamSinks[streamId]; + }, + error(reason) { + assert(reason instanceof Error, "error must have a valid reason"); + if (this.isCancelled) { + return; + } + this.isCancelled = true; + comObj.postMessage({ + sourceName, + targetName, + stream: StreamKind.ERROR, + streamId, + reason: wrapReason(reason) + }); + }, + sinkCapability: Promise.withResolvers(), + onPull: null, + onCancel: null, + isCancelled: false, + desiredSize: data.desiredSize, + ready: null + }; + streamSink.sinkCapability.resolve(); + streamSink.ready = streamSink.sinkCapability.promise; + this.streamSinks[streamId] = streamSink; + Promise.try(action, data.data, streamSink).then(function () { + comObj.postMessage({ + sourceName, + targetName, + stream: StreamKind.START_COMPLETE, + streamId, + success: true + }); + }, function (reason) { + comObj.postMessage({ + sourceName, + targetName, + stream: StreamKind.START_COMPLETE, + streamId, + reason: wrapReason(reason) + }); + }); } - bboxToClipBoxDropOperation(idx) { - if (!this.#ignoreBBoxes) { - this.#dependencyTracker.bboxToClipBoxDropOperation(this.#opIdx, true); + #processStreamMessage(data) { + const streamId = data.streamId, + sourceName = this.sourceName, + targetName = data.sourceName, + comObj = this.comObj; + const streamController = this.streamControllers[streamId], + streamSink = this.streamSinks[streamId]; + switch (data.stream) { + case StreamKind.START_COMPLETE: + if (data.success) { + streamController.startCall.resolve(); + } else { + streamController.startCall.reject(wrapReason(data.reason)); + } + break; + case StreamKind.PULL_COMPLETE: + if (data.success) { + streamController.pullCall.resolve(); + } else { + streamController.pullCall.reject(wrapReason(data.reason)); + } + break; + case StreamKind.PULL: + if (!streamSink) { + comObj.postMessage({ + sourceName, + targetName, + stream: StreamKind.PULL_COMPLETE, + streamId, + success: true + }); + break; + } + if (streamSink.desiredSize <= 0 && data.desiredSize > 0) { + streamSink.sinkCapability.resolve(); + } + streamSink.desiredSize = data.desiredSize; + Promise.try(streamSink.onPull || onFn).then(function () { + comObj.postMessage({ + sourceName, + targetName, + stream: StreamKind.PULL_COMPLETE, + streamId, + success: true + }); + }, function (reason) { + comObj.postMessage({ + sourceName, + targetName, + stream: StreamKind.PULL_COMPLETE, + streamId, + reason: wrapReason(reason) + }); + }); + break; + case StreamKind.ENQUEUE: + assert(streamController, "enqueue should have stream controller"); + if (streamController.isClosed) { + break; + } + streamController.controller.enqueue(data.chunk); + break; + case StreamKind.CLOSE: + assert(streamController, "close should have stream controller"); + if (streamController.isClosed) { + break; + } + streamController.isClosed = true; + streamController.controller.close(); + this.#deleteStreamController(streamController, streamId); + break; + case StreamKind.ERROR: + assert(streamController, "error should have stream controller"); + streamController.controller.error(wrapReason(data.reason)); + this.#deleteStreamController(streamController, streamId); + break; + case StreamKind.CANCEL_COMPLETE: + if (data.success) { + streamController.cancelCall.resolve(); + } else { + streamController.cancelCall.reject(wrapReason(data.reason)); + } + this.#deleteStreamController(streamController, streamId); + break; + case StreamKind.CANCEL: + if (!streamSink) { + break; + } + const dataReason = wrapReason(data.reason); + Promise.try(streamSink.onCancel || onFn, dataReason).then(function () { + comObj.postMessage({ + sourceName, + targetName, + stream: StreamKind.CANCEL_COMPLETE, + streamId, + success: true + }); + }, function (reason) { + comObj.postMessage({ + sourceName, + targetName, + stream: StreamKind.CANCEL_COMPLETE, + streamId, + reason: wrapReason(reason) + }); + }); + streamSink.sinkCapability.reject(dataReason); + streamSink.isCancelled = true; + delete this.streamSinks[streamId]; + break; + default: + throw new Error("Unexpected stream case"); } - return this; } - take() { - throw new Error("Unreachable"); + async #deleteStreamController(streamController, streamId) { + await Promise.allSettled([streamController.startCall?.promise, streamController.pullCall?.promise, streamController.cancelCall?.promise]); + delete this.streamControllers[streamId]; } - takeDebugMetadata() { - throw new Error("Unreachable"); + destroy() { + this.#messageAC?.abort(); + this.#messageAC = null; } } -const Dependencies = { - stroke: ["path", "transform", "filter", "strokeColor", "strokeAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "dash"], - fill: ["path", "transform", "filter", "fillColor", "fillAlpha", "globalCompositeOperation", "SMask"], - imageXObject: ["transform", "SMask", "filter", "fillAlpha", "strokeAlpha", "globalCompositeOperation"], - rawFillPath: ["filter", "fillColor", "fillAlpha"], - showText: ["transform", "leading", "charSpacing", "wordSpacing", "hScale", "textRise", "moveText", "textMatrix", "font", "fontObj", "filter", "fillColor", "textRenderingMode", "SMask", "fillAlpha", "strokeAlpha", "globalCompositeOperation", "sameLineText"], - transform: ["transform"], - transformAndFill: ["transform", "fillColor"] -}; - -;// ./src/display/pattern_helper.js +;// ./src/display/canvas_dependency_tracker.js -const PathType = { - FILL: "Fill", - STROKE: "Stroke", - SHADING: "Shading" -}; -function applyBoundingBox(ctx, bbox) { - if (!bbox) { - return; - } - const width = bbox[2] - bbox[0]; - const height = bbox[3] - bbox[1]; - const region = new Path2D(); - region.rect(bbox[0], bbox[1], width, height); - ctx.clip(region); +const FORCED_DEPENDENCY_LABEL = "__forcedDependency"; +const { + floor, + ceil +} = Math; +function expandBBox(array, index, minX, minY, maxX, maxY) { + array[index * 4 + 0] = Math.min(array[index * 4 + 0], minX); + array[index * 4 + 1] = Math.min(array[index * 4 + 1], minY); + array[index * 4 + 2] = Math.max(array[index * 4 + 2], maxX); + array[index * 4 + 3] = Math.max(array[index * 4 + 3], maxY); } -class BaseShadingPattern { - isModifyingCurrentTransform() { - return false; - } - getPattern() { - unreachable("Abstract method `getPattern` called."); +const EMPTY_BBOX = new Uint32Array(new Uint8Array([255, 255, 0, 0]).buffer)[0]; +class BBoxReader { + #bboxes; + #coords; + constructor(bboxes, coords) { + this.#bboxes = bboxes; + this.#coords = coords; } -} -class RadialAxialShadingPattern extends BaseShadingPattern { - constructor(IR) { - super(); - this._type = IR[1]; - this._bbox = IR[2]; - this._colorStops = IR[3]; - this._p0 = IR[4]; - this._p1 = IR[5]; - this._r0 = IR[6]; - this._r1 = IR[7]; - this.matrix = null; + get length() { + return this.#bboxes.length; } - _createGradient(ctx) { - let grad; - if (this._type === "axial") { - grad = ctx.createLinearGradient(this._p0[0], this._p0[1], this._p1[0], this._p1[1]); - } else if (this._type === "radial") { - grad = ctx.createRadialGradient(this._p0[0], this._p0[1], this._r0, this._p1[0], this._p1[1], this._r1); - } - for (const colorStop of this._colorStops) { - grad.addColorStop(colorStop[0], colorStop[1]); - } - return grad; + isEmpty(i) { + return this.#bboxes[i] === EMPTY_BBOX; } - getPattern(ctx, owner, inverse, pathType) { - let pattern; - if (pathType === PathType.STROKE || pathType === PathType.FILL) { - const ownerBBox = owner.current.getClippedPathBoundingBox(pathType, getCurrentTransform(ctx)) || [0, 0, 0, 0]; - const width = Math.ceil(ownerBBox[2] - ownerBBox[0]) || 1; - const height = Math.ceil(ownerBBox[3] - ownerBBox[1]) || 1; - const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", width, height); - const tmpCtx = tmpCanvas.context; - tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height); - tmpCtx.beginPath(); - tmpCtx.rect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height); - tmpCtx.translate(-ownerBBox[0], -ownerBBox[1]); - inverse = Util.transform(inverse, [1, 0, 0, 1, ownerBBox[0], ownerBBox[1]]); - tmpCtx.transform(...owner.baseTransform); - if (this.matrix) { - tmpCtx.transform(...this.matrix); - } - applyBoundingBox(tmpCtx, this._bbox); - tmpCtx.fillStyle = this._createGradient(tmpCtx); - tmpCtx.fill(); - pattern = ctx.createPattern(tmpCanvas.canvas, "no-repeat"); - const domMatrix = new DOMMatrix(inverse); - pattern.setTransform(domMatrix); - } else { - applyBoundingBox(ctx, this._bbox); - pattern = this._createGradient(ctx); - } - return pattern; + minX(i) { + return this.#coords[i * 4 + 0] / 256; } -} -function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) { - const coords = context.coords, - colors = context.colors; - const bytes = data.data, - rowSize = data.width * 4; - let tmp; - if (coords[p1 + 1] > coords[p2 + 1]) { - tmp = p1; - p1 = p2; - p2 = tmp; - tmp = c1; - c1 = c2; - c2 = tmp; + minY(i) { + return this.#coords[i * 4 + 1] / 256; } - if (coords[p2 + 1] > coords[p3 + 1]) { - tmp = p2; - p2 = p3; - p3 = tmp; - tmp = c2; - c2 = c3; - c3 = tmp; + maxX(i) { + return (this.#coords[i * 4 + 2] + 1) / 256; } - if (coords[p1 + 1] > coords[p2 + 1]) { - tmp = p1; - p1 = p2; - p2 = tmp; - tmp = c1; - c1 = c2; - c2 = tmp; + maxY(i) { + return (this.#coords[i * 4 + 3] + 1) / 256; } - const x1 = (coords[p1] + context.offsetX) * context.scaleX; - const y1 = (coords[p1 + 1] + context.offsetY) * context.scaleY; - const x2 = (coords[p2] + context.offsetX) * context.scaleX; - const y2 = (coords[p2 + 1] + context.offsetY) * context.scaleY; - const x3 = (coords[p3] + context.offsetX) * context.scaleX; - const y3 = (coords[p3 + 1] + context.offsetY) * context.scaleY; - if (y1 >= y3) { - return; +} +const ensureDebugMetadata = (map, key) => { + if (!map) { + return undefined; } - const c1r = colors[c1], - c1g = colors[c1 + 1], - c1b = colors[c1 + 2]; - const c2r = colors[c2], - c2g = colors[c2 + 1], - c2b = colors[c2 + 2]; - const c3r = colors[c3], - c3g = colors[c3 + 1], - c3b = colors[c3 + 2]; - const minY = Math.round(y1), - maxY = Math.round(y3); - let xa, car, cag, cab; - let xb, cbr, cbg, cbb; - for (let y = minY; y <= maxY; y++) { - if (y < y2) { - const k = y < y1 ? 0 : (y1 - y) / (y1 - y2); - xa = x1 - (x1 - x2) * k; - car = c1r - (c1r - c2r) * k; - cag = c1g - (c1g - c2g) * k; - cab = c1b - (c1b - c2b) * k; - } else { - let k; - if (y > y3) { - k = 1; - } else if (y2 === y3) { - k = 0; - } else { - k = (y2 - y) / (y2 - y3); - } - xa = x2 - (x2 - x3) * k; - car = c2r - (c2r - c3r) * k; - cag = c2g - (c2g - c3g) * k; - cab = c2b - (c2b - c3b) * k; + let value = map.get(key); + if (!value) { + value = { + dependencies: new Set(), + isRenderingOperation: false + }; + map.set(key, value); + } + return value; +}; +class CanvasDependencyTracker { + #simple = { + __proto__: null + }; + #incremental = { + __proto__: null, + transform: [], + moveText: [], + sameLineText: [], + [FORCED_DEPENDENCY_LABEL]: [] + }; + #namedDependencies = new Map(); + #savesStack = []; + #markedContentStack = []; + #baseTransformStack = [[1, 0, 0, 1, 0, 0]]; + #clipBox = [-Infinity, -Infinity, Infinity, Infinity]; + #pendingBBox = new Float64Array([Infinity, Infinity, -Infinity, -Infinity]); + #pendingBBoxIdx = -1; + #pendingDependencies = new Set(); + #operations = new Map(); + #fontBBoxTrustworthy = new Map(); + #canvasWidth; + #canvasHeight; + #bboxesCoords; + #bboxes; + #debugMetadata; + constructor(canvas, operationsCount, recordDebugMetadata = false) { + this.#canvasWidth = canvas.width; + this.#canvasHeight = canvas.height; + this.#initializeBBoxes(operationsCount); + if (recordDebugMetadata) { + this.#debugMetadata = new Map(); } - let k; - if (y < y1) { - k = 0; - } else if (y > y3) { - k = 1; - } else { - k = (y1 - y) / (y1 - y3); + } + growOperationsCount(operationsCount) { + if (operationsCount >= this.#bboxes.length) { + this.#initializeBBoxes(operationsCount, this.#bboxes); } - xb = x1 - (x1 - x3) * k; - cbr = c1r - (c1r - c3r) * k; - cbg = c1g - (c1g - c3g) * k; - cbb = c1b - (c1b - c3b) * k; - const x1_ = Math.round(Math.min(xa, xb)); - const x2_ = Math.round(Math.max(xa, xb)); - let j = rowSize * y + x1_ * 4; - for (let x = x1_; x <= x2_; x++) { - k = (xa - x) / (xa - xb); - if (k < 0) { - k = 0; - } else if (k > 1) { - k = 1; - } - bytes[j++] = car - (car - cbr) * k | 0; - bytes[j++] = cag - (cag - cbg) * k | 0; - bytes[j++] = cab - (cab - cbb) * k | 0; - bytes[j++] = 255; + } + #initializeBBoxes(operationsCount, oldBBoxes) { + const buffer = new ArrayBuffer(operationsCount * 4); + this.#bboxesCoords = new Uint8ClampedArray(buffer); + this.#bboxes = new Uint32Array(buffer); + if (oldBBoxes && oldBBoxes.length > 0) { + this.#bboxes.set(oldBBoxes); + this.#bboxes.fill(EMPTY_BBOX, oldBBoxes.length); + } else { + this.#bboxes.fill(EMPTY_BBOX); } } -} -function drawFigure(data, figure, context) { - const ps = figure.coords; - const cs = figure.colors; - let i, ii; - switch (figure.type) { - case MeshFigureType.LATTICE: - const verticesPerRow = figure.verticesPerRow; - const rows = Math.floor(ps.length / verticesPerRow) - 1; - const cols = verticesPerRow - 1; - for (i = 0; i < rows; i++) { - let q = i * verticesPerRow; - for (let j = 0; j < cols; j++, q++) { - drawTriangle(data, context, ps[q], ps[q + 1], ps[q + verticesPerRow], cs[q], cs[q + 1], cs[q + verticesPerRow]); - drawTriangle(data, context, ps[q + verticesPerRow + 1], ps[q + 1], ps[q + verticesPerRow], cs[q + verticesPerRow + 1], cs[q + 1], cs[q + verticesPerRow]); - } - } - break; - case MeshFigureType.TRIANGLES: - for (i = 0, ii = ps.length; i < ii; i += 3) { - drawTriangle(data, context, ps[i], ps[i + 1], ps[i + 2], cs[i], cs[i + 1], cs[i + 2]); + save(opIdx) { + this.#simple = { + __proto__: this.#simple + }; + this.#incremental = { + __proto__: this.#incremental, + transform: { + __proto__: this.#incremental.transform + }, + moveText: { + __proto__: this.#incremental.moveText + }, + sameLineText: { + __proto__: this.#incremental.sameLineText + }, + [FORCED_DEPENDENCY_LABEL]: { + __proto__: this.#incremental[FORCED_DEPENDENCY_LABEL] } - break; - default: - throw new Error("illegal figure"); + }; + this.#clipBox = { + __proto__: this.#clipBox + }; + this.#savesStack.push(opIdx); + return this; } -} -class MeshShadingPattern extends BaseShadingPattern { - constructor(IR) { - super(); - this._coords = IR[2]; - this._colors = IR[3]; - this._figures = IR[4]; - this._bounds = IR[5]; - this._bbox = IR[6]; - this._background = IR[7]; - this.matrix = null; + restore(opIdx) { + const previous = Object.getPrototypeOf(this.#simple); + if (previous === null) { + return this; + } + this.#simple = previous; + this.#incremental = Object.getPrototypeOf(this.#incremental); + this.#clipBox = Object.getPrototypeOf(this.#clipBox); + const lastSave = this.#savesStack.pop(); + if (lastSave !== undefined) { + ensureDebugMetadata(this.#debugMetadata, opIdx)?.dependencies.add(lastSave); + this.#bboxes[opIdx] = this.#bboxes[lastSave]; + } + return this; } - _createMeshCanvas(combinedScale, backgroundColor, cachedCanvases) { - const EXPECTED_SCALE = 1.1; - const MAX_PATTERN_SIZE = 3000; - const BORDER_SIZE = 2; - const offsetX = Math.floor(this._bounds[0]); - const offsetY = Math.floor(this._bounds[1]); - const boundsWidth = Math.ceil(this._bounds[2]) - offsetX; - const boundsHeight = Math.ceil(this._bounds[3]) - offsetY; - const width = Math.min(Math.ceil(Math.abs(boundsWidth * combinedScale[0] * EXPECTED_SCALE)), MAX_PATTERN_SIZE); - const height = Math.min(Math.ceil(Math.abs(boundsHeight * combinedScale[1] * EXPECTED_SCALE)), MAX_PATTERN_SIZE); - const scaleX = boundsWidth / width; - const scaleY = boundsHeight / height; - const context = { - coords: this._coords, - colors: this._colors, - offsetX: -offsetX, - offsetY: -offsetY, - scaleX: 1 / scaleX, - scaleY: 1 / scaleY - }; - const paddedWidth = width + BORDER_SIZE * 2; - const paddedHeight = height + BORDER_SIZE * 2; - const tmpCanvas = cachedCanvases.getCanvas("mesh", paddedWidth, paddedHeight); - const tmpCtx = tmpCanvas.context; - const data = tmpCtx.createImageData(width, height); - if (backgroundColor) { - const bytes = data.data; - for (let i = 0, ii = bytes.length; i < ii; i += 4) { - bytes[i] = backgroundColor[0]; - bytes[i + 1] = backgroundColor[1]; - bytes[i + 2] = backgroundColor[2]; - bytes[i + 3] = 255; - } + recordOpenMarker(idx) { + this.#savesStack.push(idx); + return this; + } + getOpenMarker() { + if (this.#savesStack.length === 0) { + return null; } - for (const figure of this._figures) { - drawFigure(data, figure, context); + return this.#savesStack.at(-1); + } + recordCloseMarker(opIdx) { + const lastSave = this.#savesStack.pop(); + if (lastSave !== undefined) { + ensureDebugMetadata(this.#debugMetadata, opIdx)?.dependencies.add(lastSave); + this.#bboxes[opIdx] = this.#bboxes[lastSave]; } - tmpCtx.putImageData(data, BORDER_SIZE, BORDER_SIZE); - const canvas = tmpCanvas.canvas; - return { - canvas, - offsetX: offsetX - BORDER_SIZE * scaleX, - offsetY: offsetY - BORDER_SIZE * scaleY, - scaleX, - scaleY - }; + return this; } - isModifyingCurrentTransform() { - return true; + beginMarkedContent(opIdx) { + this.#markedContentStack.push(opIdx); + return this; } - getPattern(ctx, owner, inverse, pathType) { - applyBoundingBox(ctx, this._bbox); - const scale = new Float32Array(2); - if (pathType === PathType.SHADING) { - Util.singularValueDecompose2dScale(getCurrentTransform(ctx), scale); - } else if (this.matrix) { - Util.singularValueDecompose2dScale(this.matrix, scale); - const [matrixScaleX, matrixScaleY] = scale; - Util.singularValueDecompose2dScale(owner.baseTransform, scale); - scale[0] *= matrixScaleX; - scale[1] *= matrixScaleY; - } else { - Util.singularValueDecompose2dScale(owner.baseTransform, scale); + endMarkedContent(opIdx) { + const lastSave = this.#markedContentStack.pop(); + if (lastSave !== undefined) { + ensureDebugMetadata(this.#debugMetadata, opIdx)?.dependencies.add(lastSave); + this.#bboxes[opIdx] = this.#bboxes[lastSave]; } - const temporaryPatternCanvas = this._createMeshCanvas(scale, pathType === PathType.SHADING ? null : this._background, owner.cachedCanvases); - if (pathType !== PathType.SHADING) { - ctx.setTransform(...owner.baseTransform); - if (this.matrix) { - ctx.transform(...this.matrix); - } + return this; + } + pushBaseTransform(ctx) { + this.#baseTransformStack.push(Util.multiplyByDOMMatrix(this.#baseTransformStack.at(-1), ctx.getTransform())); + return this; + } + popBaseTransform() { + if (this.#baseTransformStack.length > 1) { + this.#baseTransformStack.pop(); } - ctx.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY); - ctx.scale(temporaryPatternCanvas.scaleX, temporaryPatternCanvas.scaleY); - return ctx.createPattern(temporaryPatternCanvas.canvas, "no-repeat"); + return this; } -} -class DummyShadingPattern extends BaseShadingPattern { - getPattern() { - return "hotpink"; + recordSimpleData(name, idx) { + this.#simple[name] = idx; + return this; } -} -function getShadingPattern(IR) { - switch (IR[0]) { - case "RadialAxial": - return new RadialAxialShadingPattern(IR); - case "Mesh": - return new MeshShadingPattern(IR); - case "Dummy": - return new DummyShadingPattern(); + recordIncrementalData(name, idx) { + this.#incremental[name].push(idx); + return this; } - throw new Error(`Unknown IR type: ${IR[0]}`); -} -const PaintType = { - COLORED: 1, - UNCOLORED: 2 -}; -class TilingPattern { - static MAX_PATTERN_SIZE = 3000; - constructor(IR, ctx, canvasGraphicsFactory, baseTransform) { - this.color = IR[1]; - this.operatorList = IR[2]; - this.matrix = IR[3]; - this.bbox = IR[4]; - this.xstep = IR[5]; - this.ystep = IR[6]; - this.paintType = IR[7]; - this.tilingType = IR[8]; - this.ctx = ctx; - this.canvasGraphicsFactory = canvasGraphicsFactory; - this.baseTransform = baseTransform; + resetIncrementalData(name, idx) { + this.#incremental[name].length = 0; + return this; } - createPatternCanvas(owner, opIdx) { - const { - bbox, - operatorList, - paintType, - tilingType, - color, - canvasGraphicsFactory - } = this; - let { - xstep, - ystep - } = this; - xstep = Math.abs(xstep); - ystep = Math.abs(ystep); - info("TilingType: " + tilingType); - const x0 = bbox[0], - y0 = bbox[1], - x1 = bbox[2], - y1 = bbox[3]; - const width = x1 - x0; - const height = y1 - y0; - const scale = new Float32Array(2); - Util.singularValueDecompose2dScale(this.matrix, scale); - const [matrixScaleX, matrixScaleY] = scale; - Util.singularValueDecompose2dScale(this.baseTransform, scale); - const combinedScaleX = matrixScaleX * scale[0]; - const combinedScaleY = matrixScaleY * scale[1]; - let canvasWidth = width, - canvasHeight = height, - redrawHorizontally = false, - redrawVertically = false; - const xScaledStep = Math.ceil(xstep * combinedScaleX); - const yScaledStep = Math.ceil(ystep * combinedScaleY); - const xScaledWidth = Math.ceil(width * combinedScaleX); - const yScaledHeight = Math.ceil(height * combinedScaleY); - if (xScaledStep >= xScaledWidth) { - canvasWidth = xstep; - } else { - redrawHorizontally = true; - } - if (yScaledStep >= yScaledHeight) { - canvasHeight = ystep; - } else { - redrawVertically = true; - } - const dimx = this.getSizeAndScale(canvasWidth, this.ctx.canvas.width, combinedScaleX); - const dimy = this.getSizeAndScale(canvasHeight, this.ctx.canvas.height, combinedScaleY); - const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", dimx.size, dimy.size); - const tmpCtx = tmpCanvas.context; - const graphics = canvasGraphicsFactory.createCanvasGraphics(tmpCtx, opIdx); - graphics.groupLevel = owner.groupLevel; - this.setFillAndStrokeStyleToContext(graphics, paintType, color); - tmpCtx.translate(-dimx.scale * x0, -dimy.scale * y0); - graphics.transform(0, dimx.scale, 0, 0, dimy.scale, 0, 0); - tmpCtx.save(); - graphics.dependencyTracker?.save(); - this.clipBbox(graphics, x0, y0, x1, y1); - graphics.baseTransform = getCurrentTransform(graphics.ctx); - graphics.executeOperatorList(operatorList); - graphics.endDrawing(); - graphics.dependencyTracker?.restore(); - tmpCtx.restore(); - if (redrawHorizontally || redrawVertically) { - const image = tmpCanvas.canvas; - if (redrawHorizontally) { - canvasWidth = xstep; - } - if (redrawVertically) { - canvasHeight = ystep; - } - const dimx2 = this.getSizeAndScale(canvasWidth, this.ctx.canvas.width, combinedScaleX); - const dimy2 = this.getSizeAndScale(canvasHeight, this.ctx.canvas.height, combinedScaleY); - const xSize = dimx2.size; - const ySize = dimy2.size; - const tmpCanvas2 = owner.cachedCanvases.getCanvas("pattern-workaround", xSize, ySize); - const tmpCtx2 = tmpCanvas2.context; - const ii = redrawHorizontally ? Math.floor(width / xstep) : 0; - const jj = redrawVertically ? Math.floor(height / ystep) : 0; - for (let i = 0; i <= ii; i++) { - for (let j = 0; j <= jj; j++) { - tmpCtx2.drawImage(image, xSize * i, ySize * j, xSize, ySize, 0, 0, xSize, ySize); - } + recordNamedData(name, idx) { + this.#namedDependencies.set(name, idx); + return this; + } + recordSimpleDataFromNamed(name, depName, fallbackIdx) { + this.#simple[name] = this.#namedDependencies.get(depName) ?? fallbackIdx; + } + recordFutureForcedDependency(name, idx) { + this.recordIncrementalData(FORCED_DEPENDENCY_LABEL, idx); + return this; + } + inheritSimpleDataAsFutureForcedDependencies(names) { + for (const name of names) { + if (name in this.#simple) { + this.recordFutureForcedDependency(name, this.#simple[name]); } - return { - canvas: tmpCanvas2.canvas, - scaleX: dimx2.scale, - scaleY: dimy2.scale, - offsetX: x0, - offsetY: y0 - }; } - return { - canvas: tmpCanvas.canvas, - scaleX: dimx.scale, - scaleY: dimy.scale, - offsetX: x0, - offsetY: y0 - }; + return this; } - getSizeAndScale(step, realOutputSize, scale) { - const maxSize = Math.max(TilingPattern.MAX_PATTERN_SIZE, realOutputSize); - let size = Math.ceil(step * scale); - if (size >= maxSize) { - size = maxSize; - } else { - scale = size / step; + inheritPendingDependenciesAsFutureForcedDependencies() { + for (const dep of this.#pendingDependencies) { + this.recordFutureForcedDependency(FORCED_DEPENDENCY_LABEL, dep); } - return { - scale, - size - }; + return this; } - clipBbox(graphics, x0, y0, x1, y1) { - const bboxWidth = x1 - x0; - const bboxHeight = y1 - y0; - graphics.ctx.rect(x0, y0, bboxWidth, bboxHeight); - Util.axialAlignedBoundingBox([x0, y0, x1, y1], getCurrentTransform(graphics.ctx), graphics.current.minMax); - graphics.clip(); - graphics.endPath(); + resetBBox(idx) { + if (this.#pendingBBoxIdx !== idx) { + this.#pendingBBoxIdx = idx; + this.#pendingBBox[0] = Infinity; + this.#pendingBBox[1] = Infinity; + this.#pendingBBox[2] = -Infinity; + this.#pendingBBox[3] = -Infinity; + } + return this; } - setFillAndStrokeStyleToContext(graphics, paintType, color) { - const context = graphics.ctx, - current = graphics.current; - switch (paintType) { - case PaintType.COLORED: - const { - fillStyle, - strokeStyle - } = this.ctx; - context.fillStyle = current.fillColor = fillStyle; - context.strokeStyle = current.strokeColor = strokeStyle; - break; - case PaintType.UNCOLORED: - context.fillStyle = context.strokeStyle = color; - current.fillColor = current.strokeColor = color; - break; - default: - throw new FormatError(`Unsupported paint type: ${paintType}`); + recordClipBox(idx, ctx, minX, maxX, minY, maxY) { + const transform = Util.multiplyByDOMMatrix(this.#baseTransformStack.at(-1), ctx.getTransform()); + const clipBox = [Infinity, Infinity, -Infinity, -Infinity]; + Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, clipBox); + const intersection = Util.intersect(this.#clipBox, clipBox); + if (intersection) { + this.#clipBox[0] = intersection[0]; + this.#clipBox[1] = intersection[1]; + this.#clipBox[2] = intersection[2]; + this.#clipBox[3] = intersection[3]; + } else { + this.#clipBox[0] = this.#clipBox[1] = Infinity; + this.#clipBox[2] = this.#clipBox[3] = -Infinity; } + return this; } - isModifyingCurrentTransform() { - return false; + recordBBox(idx, ctx, minX, maxX, minY, maxY) { + const clipBox = this.#clipBox; + if (clipBox[0] === Infinity) { + return this; + } + const transform = Util.multiplyByDOMMatrix(this.#baseTransformStack.at(-1), ctx.getTransform()); + if (clipBox[0] === -Infinity) { + Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, this.#pendingBBox); + return this; + } + const bbox = [Infinity, Infinity, -Infinity, -Infinity]; + Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, bbox); + this.#pendingBBox[0] = Math.min(this.#pendingBBox[0], Math.max(bbox[0], clipBox[0])); + this.#pendingBBox[1] = Math.min(this.#pendingBBox[1], Math.max(bbox[1], clipBox[1])); + this.#pendingBBox[2] = Math.max(this.#pendingBBox[2], Math.min(bbox[2], clipBox[2])); + this.#pendingBBox[3] = Math.max(this.#pendingBBox[3], Math.min(bbox[3], clipBox[3])); + return this; } - getPattern(ctx, owner, inverse, pathType, opIdx) { - let matrix = inverse; - if (pathType !== PathType.SHADING) { - matrix = Util.transform(matrix, owner.baseTransform); - if (this.matrix) { - matrix = Util.transform(matrix, this.matrix); + recordCharacterBBox(idx, ctx, font, scale = 1, x = 0, y = 0, getMeasure) { + const fontBBox = font.bbox; + let isBBoxTrustworthy; + let computedBBox; + if (fontBBox) { + isBBoxTrustworthy = fontBBox[2] !== fontBBox[0] && fontBBox[3] !== fontBBox[1] && this.#fontBBoxTrustworthy.get(font); + if (isBBoxTrustworthy !== false) { + computedBBox = [0, 0, 0, 0]; + Util.axialAlignedBoundingBox(fontBBox, font.fontMatrix, computedBBox); + if (scale !== 1 || x !== 0 || y !== 0) { + Util.scaleMinMax([scale, 0, 0, -scale, x, y], computedBBox); + } + if (isBBoxTrustworthy) { + return this.recordBBox(idx, ctx, computedBBox[0], computedBBox[2], computedBBox[1], computedBBox[3]); + } } } - const temporaryPatternCanvas = this.createPatternCanvas(owner, opIdx); - let domMatrix = new DOMMatrix(matrix); - domMatrix = domMatrix.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY); - domMatrix = domMatrix.scale(1 / temporaryPatternCanvas.scaleX, 1 / temporaryPatternCanvas.scaleY); - const pattern = ctx.createPattern(temporaryPatternCanvas.canvas, "repeat"); - pattern.setTransform(domMatrix); - return pattern; - } -} - -;// ./src/shared/image_utils.js - -function convertToRGBA(params) { - switch (params.kind) { - case ImageKind.GRAYSCALE_1BPP: - return convertBlackAndWhiteToRGBA(params); - case ImageKind.RGB_24BPP: - return convertRGBToRGBA(params); - } - return null; -} -function convertBlackAndWhiteToRGBA({ - src, - srcPos = 0, - dest, - width, - height, - nonBlackColor = 0xffffffff, - inverseDecode = false -}) { - const black = util_FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff; - const [zeroMapping, oneMapping] = inverseDecode ? [nonBlackColor, black] : [black, nonBlackColor]; - const widthInSource = width >> 3; - const widthRemainder = width & 7; - const srcLength = src.length; - dest = new Uint32Array(dest.buffer); - let destPos = 0; - for (let i = 0; i < height; i++) { - for (const max = srcPos + widthInSource; srcPos < max; srcPos++) { - const elem = srcPos < srcLength ? src[srcPos] : 255; - dest[destPos++] = elem & 0b10000000 ? oneMapping : zeroMapping; - dest[destPos++] = elem & 0b1000000 ? oneMapping : zeroMapping; - dest[destPos++] = elem & 0b100000 ? oneMapping : zeroMapping; - dest[destPos++] = elem & 0b10000 ? oneMapping : zeroMapping; - dest[destPos++] = elem & 0b1000 ? oneMapping : zeroMapping; - dest[destPos++] = elem & 0b100 ? oneMapping : zeroMapping; - dest[destPos++] = elem & 0b10 ? oneMapping : zeroMapping; - dest[destPos++] = elem & 0b1 ? oneMapping : zeroMapping; + if (!getMeasure) { + return this.recordFullPageBBox(idx); } - if (widthRemainder === 0) { - continue; + const measure = getMeasure(); + if (fontBBox && computedBBox && isBBoxTrustworthy === undefined) { + isBBoxTrustworthy = computedBBox[0] <= x - measure.actualBoundingBoxLeft && computedBBox[2] >= x + measure.actualBoundingBoxRight && computedBBox[1] <= y - measure.actualBoundingBoxAscent && computedBBox[3] >= y + measure.actualBoundingBoxDescent; + this.#fontBBoxTrustworthy.set(font, isBBoxTrustworthy); + if (isBBoxTrustworthy) { + return this.recordBBox(idx, ctx, computedBBox[0], computedBBox[2], computedBBox[1], computedBBox[3]); + } } - const elem = srcPos < srcLength ? src[srcPos++] : 255; - for (let j = 0; j < widthRemainder; j++) { - dest[destPos++] = elem & 1 << 7 - j ? oneMapping : zeroMapping; + return this.recordBBox(idx, ctx, x - measure.actualBoundingBoxLeft, x + measure.actualBoundingBoxRight, y - measure.actualBoundingBoxAscent, y + measure.actualBoundingBoxDescent); + } + recordFullPageBBox(idx) { + this.#pendingBBox[0] = Math.max(0, this.#clipBox[0]); + this.#pendingBBox[1] = Math.max(0, this.#clipBox[1]); + this.#pendingBBox[2] = Math.min(this.#canvasWidth, this.#clipBox[2]); + this.#pendingBBox[3] = Math.min(this.#canvasHeight, this.#clipBox[3]); + return this; + } + getSimpleIndex(dependencyName) { + return this.#simple[dependencyName]; + } + recordDependencies(idx, dependencyNames) { + const pendingDependencies = this.#pendingDependencies; + const simple = this.#simple; + const incremental = this.#incremental; + for (const name of dependencyNames) { + if (name in this.#simple) { + pendingDependencies.add(simple[name]); + } else if (name in incremental) { + incremental[name].forEach(pendingDependencies.add, pendingDependencies); + } } + return this; } - return { - srcPos, - destPos - }; -} -function convertRGBToRGBA({ - src, - srcPos = 0, - dest, - destPos = 0, - width, - height -}) { - let i = 0; - const len = width * height * 3; - const len32 = len >> 2; - const src32 = new Uint32Array(src.buffer, srcPos, len32); - if (FeatureTest.isLittleEndian) { - for (; i < len32 - 2; i += 3, destPos += 4) { - const s1 = src32[i]; - const s2 = src32[i + 1]; - const s3 = src32[i + 2]; - dest[destPos] = s1 | 0xff000000; - dest[destPos + 1] = s1 >>> 24 | s2 << 8 | 0xff000000; - dest[destPos + 2] = s2 >>> 16 | s3 << 16 | 0xff000000; - dest[destPos + 3] = s3 >>> 8 | 0xff000000; + recordNamedDependency(idx, name) { + if (this.#namedDependencies.has(name)) { + this.#pendingDependencies.add(this.#namedDependencies.get(name)); } - for (let j = i * 4, jj = srcPos + len; j < jj; j += 3) { - dest[destPos++] = src[j] | src[j + 1] << 8 | src[j + 2] << 16 | 0xff000000; + return this; + } + recordOperation(idx, preserve = false) { + this.recordDependencies(idx, [FORCED_DEPENDENCY_LABEL]); + if (this.#debugMetadata) { + const metadata = ensureDebugMetadata(this.#debugMetadata, idx); + const { + dependencies + } = metadata; + this.#pendingDependencies.forEach(dependencies.add, dependencies); + this.#savesStack.forEach(dependencies.add, dependencies); + this.#markedContentStack.forEach(dependencies.add, dependencies); + dependencies.delete(idx); + metadata.isRenderingOperation = true; } - } else { - for (; i < len32 - 2; i += 3, destPos += 4) { - const s1 = src32[i]; - const s2 = src32[i + 1]; - const s3 = src32[i + 2]; - dest[destPos] = s1 | 0xff; - dest[destPos + 1] = s1 << 24 | s2 >>> 8 | 0xff; - dest[destPos + 2] = s2 << 16 | s3 >>> 16 | 0xff; - dest[destPos + 3] = s3 << 8 | 0xff; + if (this.#pendingBBoxIdx === idx) { + const minX = floor(this.#pendingBBox[0] * 256 / this.#canvasWidth); + const minY = floor(this.#pendingBBox[1] * 256 / this.#canvasHeight); + const maxX = ceil(this.#pendingBBox[2] * 256 / this.#canvasWidth); + const maxY = ceil(this.#pendingBBox[3] * 256 / this.#canvasHeight); + expandBBox(this.#bboxesCoords, idx, minX, minY, maxX, maxY); + for (const depIdx of this.#pendingDependencies) { + if (depIdx !== idx) { + expandBBox(this.#bboxesCoords, depIdx, minX, minY, maxX, maxY); + } + } + for (const saveIdx of this.#savesStack) { + if (saveIdx !== idx) { + expandBBox(this.#bboxesCoords, saveIdx, minX, minY, maxX, maxY); + } + } + for (const saveIdx of this.#markedContentStack) { + if (saveIdx !== idx) { + expandBBox(this.#bboxesCoords, saveIdx, minX, minY, maxX, maxY); + } + } + if (!preserve) { + this.#pendingDependencies.clear(); + this.#pendingBBoxIdx = -1; + } } - for (let j = i * 4, jj = srcPos + len; j < jj; j += 3) { - dest[destPos++] = src[j] << 24 | src[j + 1] << 16 | src[j + 2] << 8 | 0xff; + return this; + } + recordShowTextOperation(idx, preserve = false) { + const deps = Array.from(this.#pendingDependencies); + this.recordOperation(idx, preserve); + this.recordIncrementalData("sameLineText", idx); + for (const dep of deps) { + this.recordIncrementalData("sameLineText", dep); } + return this; } - return { - srcPos: srcPos + len, - destPos - }; -} -function grayToRGBA(src, dest) { - if (FeatureTest.isLittleEndian) { - for (let i = 0, ii = src.length; i < ii; i++) { - dest[i] = src[i] * 0x10101 | 0xff000000; + bboxToClipBoxDropOperation(idx, preserve = false) { + if (this.#pendingBBoxIdx === idx) { + this.#pendingBBoxIdx = -1; + this.#clipBox[0] = Math.max(this.#clipBox[0], this.#pendingBBox[0]); + this.#clipBox[1] = Math.max(this.#clipBox[1], this.#pendingBBox[1]); + this.#clipBox[2] = Math.min(this.#clipBox[2], this.#pendingBBox[2]); + this.#clipBox[3] = Math.min(this.#clipBox[3], this.#pendingBBox[3]); + if (!preserve) { + this.#pendingDependencies.clear(); + } } - } else { - for (let i = 0, ii = src.length; i < ii; i++) { - dest[i] = src[i] * 0x1010100 | 0x000000ff; + return this; + } + _takePendingDependencies() { + const pendingDependencies = this.#pendingDependencies; + this.#pendingDependencies = new Set(); + return pendingDependencies; + } + _extractOperation(idx) { + const operation = this.#operations.get(idx); + this.#operations.delete(idx); + return operation; + } + _pushPendingDependencies(dependencies) { + for (const dep of dependencies) { + this.#pendingDependencies.add(dep); } } -} - -;// ./src/display/canvas.js - - - - - -const MIN_FONT_SIZE = 16; -const MAX_FONT_SIZE = 100; -const EXECUTION_TIME = 15; -const EXECUTION_STEPS = 10; -const FULL_CHUNK_HEIGHT = 16; -const SCALE_MATRIX = new DOMMatrix(); -const XY = new Float32Array(2); -const MIN_MAX_INIT = new Float32Array([Infinity, Infinity, -Infinity, -Infinity]); -function mirrorContextOperations(ctx, destCtx) { - if (ctx._removeMirroring) { - throw new Error("Context is already forwarding operations."); + take() { + this.#fontBBoxTrustworthy.clear(); + return new BBoxReader(this.#bboxes, this.#bboxesCoords); } - ctx.__originalSave = ctx.save; - ctx.__originalRestore = ctx.restore; - ctx.__originalRotate = ctx.rotate; - ctx.__originalScale = ctx.scale; - ctx.__originalTranslate = ctx.translate; - ctx.__originalTransform = ctx.transform; - ctx.__originalSetTransform = ctx.setTransform; - ctx.__originalResetTransform = ctx.resetTransform; - ctx.__originalClip = ctx.clip; - ctx.__originalMoveTo = ctx.moveTo; - ctx.__originalLineTo = ctx.lineTo; - ctx.__originalBezierCurveTo = ctx.bezierCurveTo; - ctx.__originalRect = ctx.rect; - ctx.__originalClosePath = ctx.closePath; - ctx.__originalBeginPath = ctx.beginPath; - ctx._removeMirroring = () => { - ctx.save = ctx.__originalSave; - ctx.restore = ctx.__originalRestore; - ctx.rotate = ctx.__originalRotate; - ctx.scale = ctx.__originalScale; - ctx.translate = ctx.__originalTranslate; - ctx.transform = ctx.__originalTransform; - ctx.setTransform = ctx.__originalSetTransform; - ctx.resetTransform = ctx.__originalResetTransform; - ctx.clip = ctx.__originalClip; - ctx.moveTo = ctx.__originalMoveTo; - ctx.lineTo = ctx.__originalLineTo; - ctx.bezierCurveTo = ctx.__originalBezierCurveTo; - ctx.rect = ctx.__originalRect; - ctx.closePath = ctx.__originalClosePath; - ctx.beginPath = ctx.__originalBeginPath; - delete ctx._removeMirroring; - }; - ctx.save = function () { - destCtx.save(); - this.__originalSave(); - }; - ctx.restore = function () { - destCtx.restore(); - this.__originalRestore(); - }; - ctx.translate = function (x, y) { - destCtx.translate(x, y); - this.__originalTranslate(x, y); - }; - ctx.scale = function (x, y) { - destCtx.scale(x, y); - this.__originalScale(x, y); - }; - ctx.transform = function (a, b, c, d, e, f) { - destCtx.transform(a, b, c, d, e, f); - this.__originalTransform(a, b, c, d, e, f); - }; - ctx.setTransform = function (a, b, c, d, e, f) { - destCtx.setTransform(a, b, c, d, e, f); - this.__originalSetTransform(a, b, c, d, e, f); - }; - ctx.resetTransform = function () { - destCtx.resetTransform(); - this.__originalResetTransform(); - }; - ctx.rotate = function (angle) { - destCtx.rotate(angle); - this.__originalRotate(angle); - }; - ctx.clip = function (rule) { - destCtx.clip(rule); - this.__originalClip(rule); - }; - ctx.moveTo = function (x, y) { - destCtx.moveTo(x, y); - this.__originalMoveTo(x, y); - }; - ctx.lineTo = function (x, y) { - destCtx.lineTo(x, y); - this.__originalLineTo(x, y); - }; - ctx.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { - destCtx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); - this.__originalBezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); - }; - ctx.rect = function (x, y, width, height) { - destCtx.rect(x, y, width, height); - this.__originalRect(x, y, width, height); - }; - ctx.closePath = function () { - destCtx.closePath(); - this.__originalClosePath(); - }; - ctx.beginPath = function () { - destCtx.beginPath(); - this.__originalBeginPath(); - }; -} -class CachedCanvases { - constructor(canvasFactory) { - this.canvasFactory = canvasFactory; - this.cache = Object.create(null); + takeDebugMetadata() { + return this.#debugMetadata; } - getCanvas(id, width, height) { - let canvasEntry; - if (this.cache[id] !== undefined) { - canvasEntry = this.cache[id]; - this.canvasFactory.reset(canvasEntry, width, height); - } else { - canvasEntry = this.canvasFactory.create(width, height); - this.cache[id] = canvasEntry; +} +class CanvasNestedDependencyTracker { + #dependencyTracker; + #opIdx; + #ignoreBBoxes; + #nestingLevel = 0; + #savesLevel = 0; + constructor(dependencyTracker, opIdx, ignoreBBoxes) { + if (dependencyTracker instanceof CanvasNestedDependencyTracker && dependencyTracker.#ignoreBBoxes === !!ignoreBBoxes) { + return dependencyTracker; } - return canvasEntry; + this.#dependencyTracker = dependencyTracker; + this.#opIdx = opIdx; + this.#ignoreBBoxes = !!ignoreBBoxes; } - delete(id) { - delete this.cache[id]; + growOperationsCount() { + throw new Error("Unreachable"); } - clear() { - for (const id in this.cache) { - const canvasEntry = this.cache[id]; - this.canvasFactory.destroy(canvasEntry); - delete this.cache[id]; + save(opIdx) { + this.#savesLevel++; + this.#dependencyTracker.save(this.#opIdx); + return this; + } + restore(opIdx) { + if (this.#savesLevel > 0) { + this.#dependencyTracker.restore(this.#opIdx); + this.#savesLevel--; } + return this; } -} -function drawImageAtIntegerCoords(ctx, srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH) { - const [a, b, c, d, tx, ty] = getCurrentTransform(ctx); - if (b === 0 && c === 0) { - const tlX = destX * a + tx; - const rTlX = Math.round(tlX); - const tlY = destY * d + ty; - const rTlY = Math.round(tlY); - const brX = (destX + destW) * a + tx; - const rWidth = Math.abs(Math.round(brX) - rTlX) || 1; - const brY = (destY + destH) * d + ty; - const rHeight = Math.abs(Math.round(brY) - rTlY) || 1; - ctx.setTransform(Math.sign(a), 0, 0, Math.sign(d), rTlX, rTlY); - ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rWidth, rHeight); - ctx.setTransform(a, b, c, d, tx, ty); - return [rWidth, rHeight]; + recordOpenMarker(idx) { + this.#nestingLevel++; + return this; } - if (a === 0 && d === 0) { - const tlX = destY * c + tx; - const rTlX = Math.round(tlX); - const tlY = destX * b + ty; - const rTlY = Math.round(tlY); - const brX = (destY + destH) * c + tx; - const rWidth = Math.abs(Math.round(brX) - rTlX) || 1; - const brY = (destX + destW) * b + ty; - const rHeight = Math.abs(Math.round(brY) - rTlY) || 1; - ctx.setTransform(0, Math.sign(b), Math.sign(c), 0, rTlX, rTlY); - ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rHeight, rWidth); - ctx.setTransform(a, b, c, d, tx, ty); - return [rHeight, rWidth]; + getOpenMarker() { + return this.#nestingLevel > 0 ? this.#opIdx : this.#dependencyTracker.getOpenMarker(); } - ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH); - const scaleX = Math.hypot(a, b); - const scaleY = Math.hypot(c, d); - return [scaleX * destW, scaleY * destH]; -} -class CanvasExtraState { - alphaIsShape = false; - fontSize = 0; - fontSizeScale = 1; - textMatrix = null; - textMatrixScale = 1; - fontMatrix = FONT_IDENTITY_MATRIX; - leading = 0; - x = 0; - y = 0; - lineX = 0; - lineY = 0; - charSpacing = 0; - wordSpacing = 0; - textHScale = 1; - textRenderingMode = TextRenderingMode.FILL; - textRise = 0; - fillColor = "#000000"; - strokeColor = "#000000"; - patternFill = false; - patternStroke = false; - fillAlpha = 1; - strokeAlpha = 1; - lineWidth = 1; - activeSMask = null; - transferMaps = "none"; - constructor(width, height, preInit) { - preInit?.(this); - this.clipBox = new Float32Array([0, 0, width, height]); - this.minMax = MIN_MAX_INIT.slice(); + recordCloseMarker(idx) { + this.#nestingLevel--; + return this; + } + beginMarkedContent(opIdx) { + return this; + } + endMarkedContent(opIdx) { + return this; + } + pushBaseTransform(ctx) { + this.#dependencyTracker.pushBaseTransform(ctx); + return this; + } + popBaseTransform() { + this.#dependencyTracker.popBaseTransform(); + return this; + } + recordSimpleData(name, idx) { + this.#dependencyTracker.recordSimpleData(name, this.#opIdx); + return this; + } + recordIncrementalData(name, idx) { + this.#dependencyTracker.recordIncrementalData(name, this.#opIdx); + return this; } - clone() { - const clone = Object.create(this); - clone.clipBox = this.clipBox.slice(); - clone.minMax = this.minMax.slice(); - return clone; + resetIncrementalData(name, idx) { + this.#dependencyTracker.resetIncrementalData(name, this.#opIdx); + return this; } - getPathBoundingBox(pathType = PathType.FILL, transform = null) { - const box = this.minMax.slice(); - if (pathType === PathType.STROKE) { - if (!transform) { - unreachable("Stroke bounding box must include transform."); - } - Util.singularValueDecompose2dScale(transform, XY); - const xStrokePad = XY[0] * this.lineWidth / 2; - const yStrokePad = XY[1] * this.lineWidth / 2; - box[0] -= xStrokePad; - box[1] -= yStrokePad; - box[2] += xStrokePad; - box[3] += yStrokePad; - } - return box; + recordNamedData(name, idx) { + return this; } - updateClipFromPath() { - const intersect = Util.intersect(this.clipBox, this.getPathBoundingBox()); - this.startNewPathAndClipBox(intersect || [0, 0, 0, 0]); + recordSimpleDataFromNamed(name, depName, fallbackIdx) { + this.#dependencyTracker.recordSimpleDataFromNamed(name, depName, this.#opIdx); + return this; } - isEmptyClip() { - return this.minMax[0] === Infinity; + recordFutureForcedDependency(name, idx) { + this.#dependencyTracker.recordFutureForcedDependency(name, this.#opIdx); + return this; } - startNewPathAndClipBox(box) { - this.clipBox.set(box, 0); - this.minMax.set(MIN_MAX_INIT, 0); + inheritSimpleDataAsFutureForcedDependencies(names) { + this.#dependencyTracker.inheritSimpleDataAsFutureForcedDependencies(names); + return this; } - getClippedPathBoundingBox(pathType = PathType.FILL, transform = null) { - return Util.intersect(this.clipBox, this.getPathBoundingBox(pathType, transform)); + inheritPendingDependenciesAsFutureForcedDependencies() { + this.#dependencyTracker.inheritPendingDependenciesAsFutureForcedDependencies(); + return this; } -} -function putBinaryImageData(ctx, imgData) { - if (imgData instanceof ImageData) { - ctx.putImageData(imgData, 0, 0); - return; + resetBBox(idx) { + if (!this.#ignoreBBoxes) { + this.#dependencyTracker.resetBBox(this.#opIdx); + } + return this; } - const height = imgData.height, - width = imgData.width; - const partialChunkHeight = height % FULL_CHUNK_HEIGHT; - const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT; - const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1; - const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT); - let srcPos = 0, - destPos; - const src = imgData.data; - const dest = chunkImgData.data; - let i, j, thisChunkHeight, elemsInThisChunk; - if (imgData.kind === util_ImageKind.GRAYSCALE_1BPP) { - const srcLength = src.byteLength; - const dest32 = new Uint32Array(dest.buffer, 0, dest.byteLength >> 2); - const dest32DataLength = dest32.length; - const fullSrcDiff = width + 7 >> 3; - const white = 0xffffffff; - const black = util_FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff; - for (i = 0; i < totalChunks; i++) { - thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight; - destPos = 0; - for (j = 0; j < thisChunkHeight; j++) { - const srcDiff = srcLength - srcPos; - let k = 0; - const kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7; - const kEndUnrolled = kEnd & ~7; - let mask = 0; - let srcByte = 0; - for (; k < kEndUnrolled; k += 8) { - srcByte = src[srcPos++]; - dest32[destPos++] = srcByte & 128 ? white : black; - dest32[destPos++] = srcByte & 64 ? white : black; - dest32[destPos++] = srcByte & 32 ? white : black; - dest32[destPos++] = srcByte & 16 ? white : black; - dest32[destPos++] = srcByte & 8 ? white : black; - dest32[destPos++] = srcByte & 4 ? white : black; - dest32[destPos++] = srcByte & 2 ? white : black; - dest32[destPos++] = srcByte & 1 ? white : black; - } - for (; k < kEnd; k++) { - if (mask === 0) { - srcByte = src[srcPos++]; - mask = 128; - } - dest32[destPos++] = srcByte & mask ? white : black; - mask >>= 1; - } - } - while (destPos < dest32DataLength) { - dest32[destPos++] = 0; - } - ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); + recordClipBox(idx, ctx, minX, maxX, minY, maxY) { + if (!this.#ignoreBBoxes) { + this.#dependencyTracker.recordClipBox(this.#opIdx, ctx, minX, maxX, minY, maxY); } - } else if (imgData.kind === util_ImageKind.RGBA_32BPP) { - j = 0; - elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4; - for (i = 0; i < fullChunks; i++) { - dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); - srcPos += elemsInThisChunk; - ctx.putImageData(chunkImgData, 0, j); - j += FULL_CHUNK_HEIGHT; + return this; + } + recordBBox(idx, ctx, minX, maxX, minY, maxY) { + if (!this.#ignoreBBoxes) { + this.#dependencyTracker.recordBBox(this.#opIdx, ctx, minX, maxX, minY, maxY); } - if (i < totalChunks) { - elemsInThisChunk = width * partialChunkHeight * 4; - dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); - ctx.putImageData(chunkImgData, 0, j); + return this; + } + recordCharacterBBox(idx, ctx, font, scale, x, y, getMeasure) { + if (!this.#ignoreBBoxes) { + this.#dependencyTracker.recordCharacterBBox(this.#opIdx, ctx, font, scale, x, y, getMeasure); } - } else if (imgData.kind === util_ImageKind.RGB_24BPP) { - thisChunkHeight = FULL_CHUNK_HEIGHT; - elemsInThisChunk = width * thisChunkHeight; - for (i = 0; i < totalChunks; i++) { - if (i >= fullChunks) { - thisChunkHeight = partialChunkHeight; - elemsInThisChunk = width * thisChunkHeight; - } - destPos = 0; - for (j = elemsInThisChunk; j--;) { - dest[destPos++] = src[srcPos++]; - dest[destPos++] = src[srcPos++]; - dest[destPos++] = src[srcPos++]; - dest[destPos++] = 255; - } - ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); + return this; + } + recordFullPageBBox(idx) { + if (!this.#ignoreBBoxes) { + this.#dependencyTracker.recordFullPageBBox(this.#opIdx); } - } else { - throw new Error(`bad image kind: ${imgData.kind}`); + return this; } -} -function putBinaryImageMask(ctx, imgData) { - if (imgData.bitmap) { - ctx.drawImage(imgData.bitmap, 0, 0); - return; + getSimpleIndex(dependencyName) { + return this.#dependencyTracker.getSimpleIndex(dependencyName); } - const height = imgData.height, - width = imgData.width; - const partialChunkHeight = height % FULL_CHUNK_HEIGHT; - const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT; - const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1; - const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT); - let srcPos = 0; - const src = imgData.data; - const dest = chunkImgData.data; - for (let i = 0; i < totalChunks; i++) { - const thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight; - ({ - srcPos - } = convertBlackAndWhiteToRGBA({ - src, - srcPos, - dest, - width, - height: thisChunkHeight, - nonBlackColor: 0 - })); - ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); + recordDependencies(idx, dependencyNames) { + this.#dependencyTracker.recordDependencies(this.#opIdx, dependencyNames); + return this; } -} -function copyCtxState(sourceCtx, destCtx) { - const properties = ["strokeStyle", "fillStyle", "fillRule", "globalAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "globalCompositeOperation", "font", "filter"]; - for (const property of properties) { - if (sourceCtx[property] !== undefined) { - destCtx[property] = sourceCtx[property]; + recordNamedDependency(idx, name) { + this.#dependencyTracker.recordNamedDependency(this.#opIdx, name); + return this; + } + recordOperation(idx) { + this.#dependencyTracker.recordOperation(this.#opIdx, true); + return this; + } + recordShowTextOperation(idx) { + this.#dependencyTracker.recordShowTextOperation(this.#opIdx, true); + return this; + } + bboxToClipBoxDropOperation(idx) { + if (!this.#ignoreBBoxes) { + this.#dependencyTracker.bboxToClipBoxDropOperation(this.#opIdx, true); } + return this; } - if (sourceCtx.setLineDash !== undefined) { - destCtx.setLineDash(sourceCtx.getLineDash()); - destCtx.lineDashOffset = sourceCtx.lineDashOffset; + take() { + throw new Error("Unreachable"); } -} -function resetCtxToDefault(ctx) { - ctx.strokeStyle = ctx.fillStyle = "#000000"; - ctx.fillRule = "nonzero"; - ctx.globalAlpha = 1; - ctx.lineWidth = 1; - ctx.lineCap = "butt"; - ctx.lineJoin = "miter"; - ctx.miterLimit = 10; - ctx.globalCompositeOperation = "source-over"; - ctx.font = "10px sans-serif"; - if (ctx.setLineDash !== undefined) { - ctx.setLineDash([]); - ctx.lineDashOffset = 0; + takeDebugMetadata() { + throw new Error("Unreachable"); } - const { - filter - } = ctx; - if (filter !== "none" && filter !== "") { - ctx.filter = "none"; +} +const Dependencies = { + stroke: ["path", "transform", "filter", "strokeColor", "strokeAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "dash"], + fill: ["path", "transform", "filter", "fillColor", "fillAlpha", "globalCompositeOperation", "SMask"], + imageXObject: ["transform", "SMask", "filter", "fillAlpha", "strokeAlpha", "globalCompositeOperation"], + rawFillPath: ["filter", "fillColor", "fillAlpha"], + showText: ["transform", "leading", "charSpacing", "wordSpacing", "hScale", "textRise", "moveText", "textMatrix", "font", "fontObj", "filter", "fillColor", "textRenderingMode", "SMask", "fillAlpha", "strokeAlpha", "globalCompositeOperation", "sameLineText"], + transform: ["transform"], + transformAndFill: ["transform", "fillColor"] +}; + +;// ./src/display/pattern_helper.js + + +const PathType = { + FILL: "Fill", + STROKE: "Stroke", + SHADING: "Shading" +}; +function applyBoundingBox(ctx, bbox) { + if (!bbox) { + return; } + const width = bbox[2] - bbox[0]; + const height = bbox[3] - bbox[1]; + const region = new Path2D(); + region.rect(bbox[0], bbox[1], width, height); + ctx.clip(region); } -function getImageSmoothingEnabled(transform, interpolate) { - if (interpolate) { - return true; +class BaseShadingPattern { + isModifyingCurrentTransform() { + return false; } - Util.singularValueDecompose2dScale(transform, XY); - const actualScale = Math.fround(OutputScale.pixelRatio * PixelsPerInch.PDF_TO_CSS_UNITS); - return XY[0] <= actualScale && XY[1] <= actualScale; -} -const LINE_CAP_STYLES = ["butt", "round", "square"]; -const LINE_JOIN_STYLES = ["miter", "round", "bevel"]; -const NORMAL_CLIP = {}; -const EO_CLIP = {}; -class CanvasGraphics { - constructor(canvasCtx, commonObjs, objs, canvasFactory, filterFactory, { - optionalContentConfig, - markedContentStack = null - }, annotationCanvasMap, pageColors, dependencyTracker) { - this.ctx = canvasCtx; - this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height); - this.stateStack = []; - this.pendingClip = null; - this.pendingEOFill = false; - this.res = null; - this.xobjs = null; - this.commonObjs = commonObjs; - this.objs = objs; - this.canvasFactory = canvasFactory; - this.filterFactory = filterFactory; - this.groupStack = []; - this.baseTransform = null; - this.baseTransformStack = []; - this.groupLevel = 0; - this.smaskStack = []; - this.smaskCounter = 0; - this.tempSMask = null; - this.suspendedCtx = null; - this.contentVisible = true; - this.markedContentStack = markedContentStack || []; - this.optionalContentConfig = optionalContentConfig; - this.cachedCanvases = new CachedCanvases(this.canvasFactory); - this.cachedPatterns = new Map(); - this.annotationCanvasMap = annotationCanvasMap; - this.viewportScale = 1; - this.outputScaleX = 1; - this.outputScaleY = 1; - this.pageColors = pageColors; - this._cachedScaleForStroking = [-1, 0]; - this._cachedGetSinglePixelWidth = null; - this._cachedBitmapsMap = new Map(); - this.dependencyTracker = dependencyTracker ?? null; + getPattern() { + unreachable("Abstract method `getPattern` called."); } - getObject(opIdx, data, fallback = null) { - if (typeof data === "string") { - this.dependencyTracker?.recordNamedDependency(opIdx, data); - return data.startsWith("g_") ? this.commonObjs.get(data) : this.objs.get(data); - } - return fallback; +} +class RadialAxialShadingPattern extends BaseShadingPattern { + constructor(IR) { + super(); + this._type = IR[1]; + this._bbox = IR[2]; + this._colorStops = IR[3]; + this._p0 = IR[4]; + this._p1 = IR[5]; + this._r0 = IR[6]; + this._r1 = IR[7]; + this.matrix = null; } - beginDrawing({ - transform, - viewport, - transparency = false, - background = null - }) { - const width = this.ctx.canvas.width; - const height = this.ctx.canvas.height; - const savedFillStyle = this.ctx.fillStyle; - this.ctx.fillStyle = background || "#ffffff"; - this.ctx.fillRect(0, 0, width, height); - this.ctx.fillStyle = savedFillStyle; - if (transparency) { - const transparentCanvas = this.cachedCanvases.getCanvas("transparent", width, height); - this.compositeCtx = this.ctx; - this.transparentCanvas = transparentCanvas.canvas; - this.ctx = transparentCanvas.context; - this.ctx.save(); - this.ctx.transform(...getCurrentTransform(this.compositeCtx)); + _createGradient(ctx) { + let grad; + if (this._type === "axial") { + grad = ctx.createLinearGradient(this._p0[0], this._p0[1], this._p1[0], this._p1[1]); + } else if (this._type === "radial") { + grad = ctx.createRadialGradient(this._p0[0], this._p0[1], this._r0, this._p1[0], this._p1[1], this._r1); } - this.ctx.save(); - resetCtxToDefault(this.ctx); - if (transform) { - this.ctx.transform(...transform); - this.outputScaleX = transform[0]; - this.outputScaleY = transform[0]; + for (const colorStop of this._colorStops) { + grad.addColorStop(colorStop[0], colorStop[1]); } - this.ctx.transform(...viewport.transform); - this.viewportScale = viewport.scale; - this.baseTransform = getCurrentTransform(this.ctx); + return grad; } - executeOperatorList(operatorList, executionStartIdx, continueCallback, stepper, operationsFilter) { - const argsArray = operatorList.argsArray; - const fnArray = operatorList.fnArray; - let i = executionStartIdx || 0; - const argsArrayLen = argsArray.length; - if (argsArrayLen === i) { - return i; - } - const chunkOperations = argsArrayLen - i > EXECUTION_STEPS && typeof continueCallback === "function"; - const endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0; - let steps = 0; - const commonObjs = this.commonObjs; - const objs = this.objs; - let fnId, fnArgs; - while (true) { - if (stepper !== undefined && i === stepper.nextBreakPoint) { - stepper.breakIt(i, continueCallback); - return i; - } - if (!operationsFilter || operationsFilter(i)) { - fnId = fnArray[i]; - fnArgs = argsArray[i] ?? null; - if (fnId !== OPS.dependency) { - if (fnArgs === null) { - this[fnId](i); - } else { - this[fnId](i, ...fnArgs); - } - } else { - for (const depObjId of fnArgs) { - this.dependencyTracker?.recordNamedData(depObjId, i); - const objsPool = depObjId.startsWith("g_") ? commonObjs : objs; - if (!objsPool.has(depObjId)) { - objsPool.get(depObjId, continueCallback); - return i; - } - } - } - } - i++; - if (i === argsArrayLen) { - return i; + getPattern(ctx, owner, inverse, pathType) { + let pattern; + if (pathType === PathType.STROKE || pathType === PathType.FILL) { + const ownerBBox = owner.current.getClippedPathBoundingBox(pathType, getCurrentTransform(ctx)) || [0, 0, 0, 0]; + const width = Math.ceil(ownerBBox[2] - ownerBBox[0]) || 1; + const height = Math.ceil(ownerBBox[3] - ownerBBox[1]) || 1; + const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", width, height); + const tmpCtx = tmpCanvas.context; + tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height); + tmpCtx.beginPath(); + tmpCtx.rect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height); + tmpCtx.translate(-ownerBBox[0], -ownerBBox[1]); + inverse = Util.transform(inverse, [1, 0, 0, 1, ownerBBox[0], ownerBBox[1]]); + tmpCtx.transform(...owner.baseTransform); + if (this.matrix) { + tmpCtx.transform(...this.matrix); } - if (chunkOperations && ++steps > EXECUTION_STEPS) { - if (Date.now() > endTime) { - continueCallback(); - return i; - } - steps = 0; + applyBoundingBox(tmpCtx, this._bbox); + tmpCtx.fillStyle = this._createGradient(tmpCtx); + tmpCtx.fill(); + pattern = ctx.createPattern(tmpCanvas.canvas, "no-repeat"); + const domMatrix = new DOMMatrix(inverse); + pattern.setTransform(domMatrix); + } else { + applyBoundingBox(ctx, this._bbox); + pattern = this._createGradient(ctx); + } + return pattern; + } +} +function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) { + const coords = context.coords, + colors = context.colors; + const bytes = data.data, + rowSize = data.width * 4; + let tmp; + if (coords[p1 + 1] > coords[p2 + 1]) { + tmp = p1; + p1 = p2; + p2 = tmp; + tmp = c1; + c1 = c2; + c2 = tmp; + } + if (coords[p2 + 1] > coords[p3 + 1]) { + tmp = p2; + p2 = p3; + p3 = tmp; + tmp = c2; + c2 = c3; + c3 = tmp; + } + if (coords[p1 + 1] > coords[p2 + 1]) { + tmp = p1; + p1 = p2; + p2 = tmp; + tmp = c1; + c1 = c2; + c2 = tmp; + } + const x1 = (coords[p1] + context.offsetX) * context.scaleX; + const y1 = (coords[p1 + 1] + context.offsetY) * context.scaleY; + const x2 = (coords[p2] + context.offsetX) * context.scaleX; + const y2 = (coords[p2 + 1] + context.offsetY) * context.scaleY; + const x3 = (coords[p3] + context.offsetX) * context.scaleX; + const y3 = (coords[p3 + 1] + context.offsetY) * context.scaleY; + if (y1 >= y3) { + return; + } + const c1r = colors[c1], + c1g = colors[c1 + 1], + c1b = colors[c1 + 2]; + const c2r = colors[c2], + c2g = colors[c2 + 1], + c2b = colors[c2 + 2]; + const c3r = colors[c3], + c3g = colors[c3 + 1], + c3b = colors[c3 + 2]; + const minY = Math.round(y1), + maxY = Math.round(y3); + let xa, car, cag, cab; + let xb, cbr, cbg, cbb; + for (let y = minY; y <= maxY; y++) { + if (y < y2) { + const k = y < y1 ? 0 : (y1 - y) / (y1 - y2); + xa = x1 - (x1 - x2) * k; + car = c1r - (c1r - c2r) * k; + cag = c1g - (c1g - c2g) * k; + cab = c1b - (c1b - c2b) * k; + } else { + let k; + if (y > y3) { + k = 1; + } else if (y2 === y3) { + k = 0; + } else { + k = (y2 - y) / (y2 - y3); } + xa = x2 - (x2 - x3) * k; + car = c2r - (c2r - c3r) * k; + cag = c2g - (c2g - c3g) * k; + cab = c2b - (c2b - c3b) * k; } - } - #restoreInitialState() { - while (this.stateStack.length || this.inSMaskMode) { - this.restore(); + let k; + if (y < y1) { + k = 0; + } else if (y > y3) { + k = 1; + } else { + k = (y1 - y) / (y1 - y3); } - this.current.activeSMask = null; - this.ctx.restore(); - if (this.transparentCanvas) { - this.ctx = this.compositeCtx; - this.ctx.save(); - this.ctx.setTransform(1, 0, 0, 1, 0, 0); - this.ctx.drawImage(this.transparentCanvas, 0, 0); - this.ctx.restore(); - this.transparentCanvas = null; + xb = x1 - (x1 - x3) * k; + cbr = c1r - (c1r - c3r) * k; + cbg = c1g - (c1g - c3g) * k; + cbb = c1b - (c1b - c3b) * k; + const x1_ = Math.round(Math.min(xa, xb)); + const x2_ = Math.round(Math.max(xa, xb)); + let j = rowSize * y + x1_ * 4; + for (let x = x1_; x <= x2_; x++) { + k = (xa - x) / (xa - xb); + if (k < 0) { + k = 0; + } else if (k > 1) { + k = 1; + } + bytes[j++] = car - (car - cbr) * k | 0; + bytes[j++] = cag - (cag - cbg) * k | 0; + bytes[j++] = cab - (cab - cbb) * k | 0; + bytes[j++] = 255; } } - endDrawing() { - this.#restoreInitialState(); - this.cachedCanvases.clear(); - this.cachedPatterns.clear(); - for (const cache of this._cachedBitmapsMap.values()) { - for (const canvas of cache.values()) { - if (typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement) { - canvas.width = canvas.height = 0; +} +function drawFigure(data, figure, context) { + const ps = figure.coords; + const cs = figure.colors; + let i, ii; + switch (figure.type) { + case MeshFigureType.LATTICE: + const verticesPerRow = figure.verticesPerRow; + const rows = Math.floor(ps.length / verticesPerRow) - 1; + const cols = verticesPerRow - 1; + for (i = 0; i < rows; i++) { + let q = i * verticesPerRow; + for (let j = 0; j < cols; j++, q++) { + drawTriangle(data, context, ps[q], ps[q + 1], ps[q + verticesPerRow], cs[q], cs[q + 1], cs[q + verticesPerRow]); + drawTriangle(data, context, ps[q + verticesPerRow + 1], ps[q + 1], ps[q + verticesPerRow], cs[q + verticesPerRow + 1], cs[q + 1], cs[q + verticesPerRow]); } } - cache.clear(); - } - this._cachedBitmapsMap.clear(); - this.#drawFilter(); - } - #drawFilter() { - if (this.pageColors) { - const hcmFilterId = this.filterFactory.addHCMFilter(this.pageColors.foreground, this.pageColors.background); - if (hcmFilterId !== "none") { - const savedFilter = this.ctx.filter; - this.ctx.filter = hcmFilterId; - this.ctx.drawImage(this.ctx.canvas, 0, 0); - this.ctx.filter = savedFilter; + break; + case MeshFigureType.TRIANGLES: + for (i = 0, ii = ps.length; i < ii; i += 3) { + drawTriangle(data, context, ps[i], ps[i + 1], ps[i + 2], cs[i], cs[i + 1], cs[i + 2]); } - } + break; + default: + throw new Error("illegal figure"); } - _scaleImage(img, inverseTransform) { - const width = img.width ?? img.displayWidth; - const height = img.height ?? img.displayHeight; - let widthScale = Math.max(Math.hypot(inverseTransform[0], inverseTransform[1]), 1); - let heightScale = Math.max(Math.hypot(inverseTransform[2], inverseTransform[3]), 1); - let paintWidth = width, - paintHeight = height; - let tmpCanvasId = "prescale1"; - let tmpCanvas, tmpCtx; - while (widthScale > 2 && paintWidth > 1 || heightScale > 2 && paintHeight > 1) { - let newWidth = paintWidth, - newHeight = paintHeight; - if (widthScale > 2 && paintWidth > 1) { - newWidth = paintWidth >= 16384 ? Math.floor(paintWidth / 2) - 1 || 1 : Math.ceil(paintWidth / 2); - widthScale /= paintWidth / newWidth; - } - if (heightScale > 2 && paintHeight > 1) { - newHeight = paintHeight >= 16384 ? Math.floor(paintHeight / 2) - 1 || 1 : Math.ceil(paintHeight) / 2; - heightScale /= paintHeight / newHeight; - } - tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight); - tmpCtx = tmpCanvas.context; - tmpCtx.clearRect(0, 0, newWidth, newHeight); - tmpCtx.drawImage(img, 0, 0, paintWidth, paintHeight, 0, 0, newWidth, newHeight); - img = tmpCanvas.canvas; - paintWidth = newWidth; - paintHeight = newHeight; - tmpCanvasId = tmpCanvasId === "prescale1" ? "prescale2" : "prescale1"; - } - return { - img, - paintWidth, - paintHeight - }; +} +class MeshShadingPattern extends BaseShadingPattern { + constructor(IR) { + super(); + this._coords = IR[2]; + this._colors = IR[3]; + this._figures = IR[4]; + this._bounds = IR[5]; + this._bbox = IR[6]; + this._background = IR[7]; + this.matrix = null; } - _createMaskCanvas(opIdx, img) { - const ctx = this.ctx; - const { - width, - height - } = img; - const fillColor = this.current.fillColor; - const isPatternFill = this.current.patternFill; - const currentTransform = getCurrentTransform(ctx); - let cache, cacheKey, scaled, maskCanvas; - if ((img.bitmap || img.data) && img.count > 1) { - const mainKey = img.bitmap || img.data.buffer; - cacheKey = JSON.stringify(isPatternFill ? currentTransform : [currentTransform.slice(0, 4), fillColor]); - cache = this._cachedBitmapsMap.get(mainKey); - if (!cache) { - cache = new Map(); - this._cachedBitmapsMap.set(mainKey, cache); - } - const cachedImage = cache.get(cacheKey); - if (cachedImage && !isPatternFill) { - const offsetX = Math.round(Math.min(currentTransform[0], currentTransform[2]) + currentTransform[4]); - const offsetY = Math.round(Math.min(currentTransform[1], currentTransform[3]) + currentTransform[5]); - this.dependencyTracker?.recordDependencies(opIdx, Dependencies.transformAndFill); - return { - canvas: cachedImage, - offsetX, - offsetY - }; - } - scaled = cachedImage; - } - if (!scaled) { - maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height); - putBinaryImageMask(maskCanvas.context, img); - } - let maskToCanvas = Util.transform(currentTransform, [1 / width, 0, 0, -1 / height, 0, 0]); - maskToCanvas = Util.transform(maskToCanvas, [1, 0, 0, 1, 0, -height]); - const minMax = MIN_MAX_INIT.slice(); - Util.axialAlignedBoundingBox([0, 0, width, height], maskToCanvas, minMax); - const [minX, minY, maxX, maxY] = minMax; - const drawnWidth = Math.round(maxX - minX) || 1; - const drawnHeight = Math.round(maxY - minY) || 1; - const fillCanvas = this.cachedCanvases.getCanvas("fillCanvas", drawnWidth, drawnHeight); - const fillCtx = fillCanvas.context; - const offsetX = minX; - const offsetY = minY; - fillCtx.translate(-offsetX, -offsetY); - fillCtx.transform(...maskToCanvas); - if (!scaled) { - scaled = this._scaleImage(maskCanvas.canvas, getCurrentTransformInverse(fillCtx)); - scaled = scaled.img; - if (cache && isPatternFill) { - cache.set(cacheKey, scaled); - } - } - fillCtx.imageSmoothingEnabled = getImageSmoothingEnabled(getCurrentTransform(fillCtx), img.interpolate); - drawImageAtIntegerCoords(fillCtx, scaled, 0, 0, scaled.width, scaled.height, 0, 0, width, height); - fillCtx.globalCompositeOperation = "source-in"; - const inverse = Util.transform(getCurrentTransformInverse(fillCtx), [1, 0, 0, 1, -offsetX, -offsetY]); - fillCtx.fillStyle = isPatternFill ? fillColor.getPattern(ctx, this, inverse, PathType.FILL, opIdx) : fillColor; - fillCtx.fillRect(0, 0, width, height); - if (cache && !isPatternFill) { - this.cachedCanvases.delete("fillCanvas"); - cache.set(cacheKey, fillCanvas.canvas); - } - this.dependencyTracker?.recordDependencies(opIdx, Dependencies.transformAndFill); - return { - canvas: fillCanvas.canvas, - offsetX: Math.round(offsetX), - offsetY: Math.round(offsetY) + _createMeshCanvas(combinedScale, backgroundColor, cachedCanvases) { + const EXPECTED_SCALE = 1.1; + const MAX_PATTERN_SIZE = 3000; + const BORDER_SIZE = 2; + const offsetX = Math.floor(this._bounds[0]); + const offsetY = Math.floor(this._bounds[1]); + const boundsWidth = Math.ceil(this._bounds[2]) - offsetX; + const boundsHeight = Math.ceil(this._bounds[3]) - offsetY; + const width = Math.min(Math.ceil(Math.abs(boundsWidth * combinedScale[0] * EXPECTED_SCALE)), MAX_PATTERN_SIZE); + const height = Math.min(Math.ceil(Math.abs(boundsHeight * combinedScale[1] * EXPECTED_SCALE)), MAX_PATTERN_SIZE); + const scaleX = boundsWidth / width; + const scaleY = boundsHeight / height; + const context = { + coords: this._coords, + colors: this._colors, + offsetX: -offsetX, + offsetY: -offsetY, + scaleX: 1 / scaleX, + scaleY: 1 / scaleY }; - } - setLineWidth(opIdx, width) { - this.dependencyTracker?.recordSimpleData("lineWidth", opIdx); - if (width !== this.current.lineWidth) { - this._cachedScaleForStroking[0] = -1; + const paddedWidth = width + BORDER_SIZE * 2; + const paddedHeight = height + BORDER_SIZE * 2; + const tmpCanvas = cachedCanvases.getCanvas("mesh", paddedWidth, paddedHeight); + const tmpCtx = tmpCanvas.context; + const data = tmpCtx.createImageData(width, height); + if (backgroundColor) { + const bytes = data.data; + for (let i = 0, ii = bytes.length; i < ii; i += 4) { + bytes[i] = backgroundColor[0]; + bytes[i + 1] = backgroundColor[1]; + bytes[i + 2] = backgroundColor[2]; + bytes[i + 3] = 255; + } } - this.current.lineWidth = width; - this.ctx.lineWidth = width; - } - setLineCap(opIdx, style) { - this.dependencyTracker?.recordSimpleData("lineCap", opIdx); - this.ctx.lineCap = LINE_CAP_STYLES[style]; - } - setLineJoin(opIdx, style) { - this.dependencyTracker?.recordSimpleData("lineJoin", opIdx); - this.ctx.lineJoin = LINE_JOIN_STYLES[style]; + for (const figure of this._figures) { + drawFigure(data, figure, context); + } + tmpCtx.putImageData(data, BORDER_SIZE, BORDER_SIZE); + const canvas = tmpCanvas.canvas; + return { + canvas, + offsetX: offsetX - BORDER_SIZE * scaleX, + offsetY: offsetY - BORDER_SIZE * scaleY, + scaleX, + scaleY + }; } - setMiterLimit(opIdx, limit) { - this.dependencyTracker?.recordSimpleData("miterLimit", opIdx); - this.ctx.miterLimit = limit; + isModifyingCurrentTransform() { + return true; } - setDash(opIdx, dashArray, dashPhase) { - this.dependencyTracker?.recordSimpleData("dash", opIdx); - const ctx = this.ctx; - if (ctx.setLineDash !== undefined) { - ctx.setLineDash(dashArray); - ctx.lineDashOffset = dashPhase; + getPattern(ctx, owner, inverse, pathType) { + applyBoundingBox(ctx, this._bbox); + const scale = new Float32Array(2); + if (pathType === PathType.SHADING) { + Util.singularValueDecompose2dScale(getCurrentTransform(ctx), scale); + } else if (this.matrix) { + Util.singularValueDecompose2dScale(this.matrix, scale); + const [matrixScaleX, matrixScaleY] = scale; + Util.singularValueDecompose2dScale(owner.baseTransform, scale); + scale[0] *= matrixScaleX; + scale[1] *= matrixScaleY; + } else { + Util.singularValueDecompose2dScale(owner.baseTransform, scale); } - } - setRenderingIntent(opIdx, intent) {} - setFlatness(opIdx, flatness) {} - setGState(opIdx, states) { - for (const [key, value] of states) { - switch (key) { - case "LW": - this.setLineWidth(opIdx, value); - break; - case "LC": - this.setLineCap(opIdx, value); - break; - case "LJ": - this.setLineJoin(opIdx, value); - break; - case "ML": - this.setMiterLimit(opIdx, value); - break; - case "D": - this.setDash(opIdx, value[0], value[1]); - break; - case "RI": - this.setRenderingIntent(opIdx, value); - break; - case "FL": - this.setFlatness(opIdx, value); - break; - case "Font": - this.setFont(opIdx, value[0], value[1]); - break; - case "CA": - this.dependencyTracker?.recordSimpleData("strokeAlpha", opIdx); - this.current.strokeAlpha = value; - break; - case "ca": - this.dependencyTracker?.recordSimpleData("fillAlpha", opIdx); - this.ctx.globalAlpha = this.current.fillAlpha = value; - break; - case "BM": - this.dependencyTracker?.recordSimpleData("globalCompositeOperation", opIdx); - this.ctx.globalCompositeOperation = value; - break; - case "SMask": - this.dependencyTracker?.recordSimpleData("SMask", opIdx); - this.current.activeSMask = value ? this.tempSMask : null; - this.tempSMask = null; - this.checkSMaskState(); - break; - case "TR": - this.dependencyTracker?.recordSimpleData("filter", opIdx); - this.ctx.filter = this.current.transferMaps = this.filterFactory.addFilter(value); - break; + const temporaryPatternCanvas = this._createMeshCanvas(scale, pathType === PathType.SHADING ? null : this._background, owner.cachedCanvases); + if (pathType !== PathType.SHADING) { + ctx.setTransform(...owner.baseTransform); + if (this.matrix) { + ctx.transform(...this.matrix); } } + ctx.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY); + ctx.scale(temporaryPatternCanvas.scaleX, temporaryPatternCanvas.scaleY); + return ctx.createPattern(temporaryPatternCanvas.canvas, "no-repeat"); } - get inSMaskMode() { - return !!this.suspendedCtx; +} +class DummyShadingPattern extends BaseShadingPattern { + getPattern() { + return "hotpink"; } - checkSMaskState() { - const inSMaskMode = this.inSMaskMode; - if (this.current.activeSMask && !inSMaskMode) { - this.beginSMaskMode(); - } else if (!this.current.activeSMask && inSMaskMode) { - this.endSMaskMode(); - } +} +function getShadingPattern(IR) { + switch (IR[0]) { + case "RadialAxial": + return new RadialAxialShadingPattern(IR); + case "Mesh": + return new MeshShadingPattern(IR); + case "Dummy": + return new DummyShadingPattern(); } - beginSMaskMode(opIdx) { - if (this.inSMaskMode) { - throw new Error("beginSMaskMode called while already in smask mode"); - } - const drawnWidth = this.ctx.canvas.width; - const drawnHeight = this.ctx.canvas.height; - const cacheId = "smaskGroupAt" + this.groupLevel; - const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight); - this.suspendedCtx = this.ctx; - const ctx = this.ctx = scratchCanvas.context; - ctx.setTransform(this.suspendedCtx.getTransform()); - copyCtxState(this.suspendedCtx, ctx); - mirrorContextOperations(ctx, this.suspendedCtx); - this.setGState(opIdx, [["BM", "source-over"]]); + throw new Error(`Unknown IR type: ${IR[0]}`); +} +const PaintType = { + COLORED: 1, + UNCOLORED: 2 +}; +class TilingPattern { + static MAX_PATTERN_SIZE = 3000; + constructor(IR, ctx, canvasGraphicsFactory, baseTransform) { + this.color = IR[1]; + this.operatorList = IR[2]; + this.matrix = IR[3]; + this.bbox = IR[4]; + this.xstep = IR[5]; + this.ystep = IR[6]; + this.paintType = IR[7]; + this.tilingType = IR[8]; + this.ctx = ctx; + this.canvasGraphicsFactory = canvasGraphicsFactory; + this.baseTransform = baseTransform; } - endSMaskMode() { - if (!this.inSMaskMode) { - throw new Error("endSMaskMode called while not in smask mode"); + createPatternCanvas(owner, opIdx) { + const { + bbox, + operatorList, + paintType, + tilingType, + color, + canvasGraphicsFactory + } = this; + let { + xstep, + ystep + } = this; + xstep = Math.abs(xstep); + ystep = Math.abs(ystep); + info("TilingType: " + tilingType); + const x0 = bbox[0], + y0 = bbox[1], + x1 = bbox[2], + y1 = bbox[3]; + const width = x1 - x0; + const height = y1 - y0; + const scale = new Float32Array(2); + Util.singularValueDecompose2dScale(this.matrix, scale); + const [matrixScaleX, matrixScaleY] = scale; + Util.singularValueDecompose2dScale(this.baseTransform, scale); + const combinedScaleX = matrixScaleX * scale[0]; + const combinedScaleY = matrixScaleY * scale[1]; + let canvasWidth = width, + canvasHeight = height, + redrawHorizontally = false, + redrawVertically = false; + const xScaledStep = Math.ceil(xstep * combinedScaleX); + const yScaledStep = Math.ceil(ystep * combinedScaleY); + const xScaledWidth = Math.ceil(width * combinedScaleX); + const yScaledHeight = Math.ceil(height * combinedScaleY); + if (xScaledStep >= xScaledWidth) { + canvasWidth = xstep; + } else { + redrawHorizontally = true; } - this.ctx._removeMirroring(); - copyCtxState(this.ctx, this.suspendedCtx); - this.ctx = this.suspendedCtx; - this.suspendedCtx = null; + if (yScaledStep >= yScaledHeight) { + canvasHeight = ystep; + } else { + redrawVertically = true; + } + const dimx = this.getSizeAndScale(canvasWidth, this.ctx.canvas.width, combinedScaleX); + const dimy = this.getSizeAndScale(canvasHeight, this.ctx.canvas.height, combinedScaleY); + const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", dimx.size, dimy.size); + const tmpCtx = tmpCanvas.context; + const graphics = canvasGraphicsFactory.createCanvasGraphics(tmpCtx, opIdx); + graphics.groupLevel = owner.groupLevel; + this.setFillAndStrokeStyleToContext(graphics, paintType, color); + tmpCtx.translate(-dimx.scale * x0, -dimy.scale * y0); + graphics.transform(0, dimx.scale, 0, 0, dimy.scale, 0, 0); + tmpCtx.save(); + graphics.dependencyTracker?.save(); + this.clipBbox(graphics, x0, y0, x1, y1); + graphics.baseTransform = getCurrentTransform(graphics.ctx); + graphics.executeOperatorList(operatorList); + graphics.endDrawing(); + graphics.dependencyTracker?.restore(); + tmpCtx.restore(); + if (redrawHorizontally || redrawVertically) { + const image = tmpCanvas.canvas; + if (redrawHorizontally) { + canvasWidth = xstep; + } + if (redrawVertically) { + canvasHeight = ystep; + } + const dimx2 = this.getSizeAndScale(canvasWidth, this.ctx.canvas.width, combinedScaleX); + const dimy2 = this.getSizeAndScale(canvasHeight, this.ctx.canvas.height, combinedScaleY); + const xSize = dimx2.size; + const ySize = dimy2.size; + const tmpCanvas2 = owner.cachedCanvases.getCanvas("pattern-workaround", xSize, ySize); + const tmpCtx2 = tmpCanvas2.context; + const ii = redrawHorizontally ? Math.floor(width / xstep) : 0; + const jj = redrawVertically ? Math.floor(height / ystep) : 0; + for (let i = 0; i <= ii; i++) { + for (let j = 0; j <= jj; j++) { + tmpCtx2.drawImage(image, xSize * i, ySize * j, xSize, ySize, 0, 0, xSize, ySize); + } + } + return { + canvas: tmpCanvas2.canvas, + scaleX: dimx2.scale, + scaleY: dimy2.scale, + offsetX: x0, + offsetY: y0 + }; + } + return { + canvas: tmpCanvas.canvas, + scaleX: dimx.scale, + scaleY: dimy.scale, + offsetX: x0, + offsetY: y0 + }; } - compose(dirtyBox) { - if (!this.current.activeSMask) { - return; - } - if (!dirtyBox) { - dirtyBox = [0, 0, this.ctx.canvas.width, this.ctx.canvas.height]; + getSizeAndScale(step, realOutputSize, scale) { + const maxSize = Math.max(TilingPattern.MAX_PATTERN_SIZE, realOutputSize); + let size = Math.ceil(step * scale); + if (size >= maxSize) { + size = maxSize; } else { - dirtyBox[0] = Math.floor(dirtyBox[0]); - dirtyBox[1] = Math.floor(dirtyBox[1]); - dirtyBox[2] = Math.ceil(dirtyBox[2]); - dirtyBox[3] = Math.ceil(dirtyBox[3]); + scale = size / step; } - const smask = this.current.activeSMask; - const suspendedCtx = this.suspendedCtx; - this.composeSMask(suspendedCtx, smask, this.ctx, dirtyBox); - this.ctx.save(); - this.ctx.setTransform(1, 0, 0, 1, 0, 0); - this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); - this.ctx.restore(); + return { + scale, + size + }; } - composeSMask(ctx, smask, layerCtx, layerBox) { - const layerOffsetX = layerBox[0]; - const layerOffsetY = layerBox[1]; - const layerWidth = layerBox[2] - layerOffsetX; - const layerHeight = layerBox[3] - layerOffsetY; - if (layerWidth === 0 || layerHeight === 0) { - return; - } - this.genericComposeSMask(smask.context, layerCtx, layerWidth, layerHeight, smask.subtype, smask.backdrop, smask.transferMap, layerOffsetX, layerOffsetY, smask.offsetX, smask.offsetY); - ctx.save(); - ctx.globalAlpha = 1; - ctx.globalCompositeOperation = "source-over"; - ctx.setTransform(1, 0, 0, 1, 0, 0); - ctx.drawImage(layerCtx.canvas, 0, 0); - ctx.restore(); + clipBbox(graphics, x0, y0, x1, y1) { + const bboxWidth = x1 - x0; + const bboxHeight = y1 - y0; + graphics.ctx.rect(x0, y0, bboxWidth, bboxHeight); + Util.axialAlignedBoundingBox([x0, y0, x1, y1], getCurrentTransform(graphics.ctx), graphics.current.minMax); + graphics.clip(); + graphics.endPath(); } - genericComposeSMask(maskCtx, layerCtx, width, height, subtype, backdrop, transferMap, layerOffsetX, layerOffsetY, maskOffsetX, maskOffsetY) { - let maskCanvas = maskCtx.canvas; - let maskX = layerOffsetX - maskOffsetX; - let maskY = layerOffsetY - maskOffsetY; - if (backdrop) { - if (maskX < 0 || maskY < 0 || maskX + width > maskCanvas.width || maskY + height > maskCanvas.height) { - const canvas = this.cachedCanvases.getCanvas("maskExtension", width, height); - const ctx = canvas.context; - ctx.drawImage(maskCanvas, -maskX, -maskY); - ctx.globalCompositeOperation = "destination-atop"; - ctx.fillStyle = backdrop; - ctx.fillRect(0, 0, width, height); - ctx.globalCompositeOperation = "source-over"; - maskCanvas = canvas.canvas; - maskX = maskY = 0; - } else { - maskCtx.save(); - maskCtx.globalAlpha = 1; - maskCtx.setTransform(1, 0, 0, 1, 0, 0); - const clip = new Path2D(); - clip.rect(maskX, maskY, width, height); - maskCtx.clip(clip); - maskCtx.globalCompositeOperation = "destination-atop"; - maskCtx.fillStyle = backdrop; - maskCtx.fillRect(maskX, maskY, width, height); - maskCtx.restore(); - } - } - layerCtx.save(); - layerCtx.globalAlpha = 1; - layerCtx.setTransform(1, 0, 0, 1, 0, 0); - if (subtype === "Alpha" && transferMap) { - layerCtx.filter = this.filterFactory.addAlphaFilter(transferMap); - } else if (subtype === "Luminosity") { - layerCtx.filter = this.filterFactory.addLuminosityFilter(transferMap); + setFillAndStrokeStyleToContext(graphics, paintType, color) { + const context = graphics.ctx, + current = graphics.current; + switch (paintType) { + case PaintType.COLORED: + const { + fillStyle, + strokeStyle + } = this.ctx; + context.fillStyle = current.fillColor = fillStyle; + context.strokeStyle = current.strokeColor = strokeStyle; + break; + case PaintType.UNCOLORED: + context.fillStyle = context.strokeStyle = color; + current.fillColor = current.strokeColor = color; + break; + default: + throw new FormatError(`Unsupported paint type: ${paintType}`); } - const clip = new Path2D(); - clip.rect(layerOffsetX, layerOffsetY, width, height); - layerCtx.clip(clip); - layerCtx.globalCompositeOperation = "destination-in"; - layerCtx.drawImage(maskCanvas, maskX, maskY, width, height, layerOffsetX, layerOffsetY, width, height); - layerCtx.restore(); } - save(opIdx) { - if (this.inSMaskMode) { - copyCtxState(this.ctx, this.suspendedCtx); - } - this.ctx.save(); - const old = this.current; - this.stateStack.push(old); - this.current = old.clone(); - this.dependencyTracker?.save(opIdx); + isModifyingCurrentTransform() { + return false; } - restore(opIdx) { - this.dependencyTracker?.restore(opIdx); - if (this.stateStack.length === 0) { - if (this.inSMaskMode) { - this.endSMaskMode(); + getPattern(ctx, owner, inverse, pathType, opIdx) { + let matrix = inverse; + if (pathType !== PathType.SHADING) { + matrix = Util.transform(matrix, owner.baseTransform); + if (this.matrix) { + matrix = Util.transform(matrix, this.matrix); } - return; - } - this.current = this.stateStack.pop(); - this.ctx.restore(); - if (this.inSMaskMode) { - copyCtxState(this.suspendedCtx, this.ctx); } - this.checkSMaskState(); - this.pendingClip = null; - this._cachedScaleForStroking[0] = -1; - this._cachedGetSinglePixelWidth = null; + const temporaryPatternCanvas = this.createPatternCanvas(owner, opIdx); + let domMatrix = new DOMMatrix(matrix); + domMatrix = domMatrix.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY); + domMatrix = domMatrix.scale(1 / temporaryPatternCanvas.scaleX, 1 / temporaryPatternCanvas.scaleY); + const pattern = ctx.createPattern(temporaryPatternCanvas.canvas, "repeat"); + pattern.setTransform(domMatrix); + return pattern; } - transform(opIdx, a, b, c, d, e, f) { - this.dependencyTracker?.recordIncrementalData("transform", opIdx); - this.ctx.transform(a, b, c, d, e, f); - this._cachedScaleForStroking[0] = -1; - this._cachedGetSinglePixelWidth = null; +} + +;// ./src/shared/image_utils.js + +function convertToRGBA(params) { + switch (params.kind) { + case ImageKind.GRAYSCALE_1BPP: + return convertBlackAndWhiteToRGBA(params); + case ImageKind.RGB_24BPP: + return convertRGBToRGBA(params); } - constructPath(opIdx, op, data, minMax) { - let [path] = data; - if (!minMax) { - path ||= data[0] = new Path2D(); - this[op](opIdx, path); - return; + return null; +} +function convertBlackAndWhiteToRGBA({ + src, + srcPos = 0, + dest, + width, + height, + nonBlackColor = 0xffffffff, + inverseDecode = false +}) { + const black = util_FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff; + const [zeroMapping, oneMapping] = inverseDecode ? [nonBlackColor, black] : [black, nonBlackColor]; + const widthInSource = width >> 3; + const widthRemainder = width & 7; + const srcLength = src.length; + dest = new Uint32Array(dest.buffer); + let destPos = 0; + for (let i = 0; i < height; i++) { + for (const max = srcPos + widthInSource; srcPos < max; srcPos++) { + const elem = srcPos < srcLength ? src[srcPos] : 255; + dest[destPos++] = elem & 0b10000000 ? oneMapping : zeroMapping; + dest[destPos++] = elem & 0b1000000 ? oneMapping : zeroMapping; + dest[destPos++] = elem & 0b100000 ? oneMapping : zeroMapping; + dest[destPos++] = elem & 0b10000 ? oneMapping : zeroMapping; + dest[destPos++] = elem & 0b1000 ? oneMapping : zeroMapping; + dest[destPos++] = elem & 0b100 ? oneMapping : zeroMapping; + dest[destPos++] = elem & 0b10 ? oneMapping : zeroMapping; + dest[destPos++] = elem & 0b1 ? oneMapping : zeroMapping; } - if (this.dependencyTracker !== null) { - const outerExtraSize = op === OPS.stroke ? this.current.lineWidth / 2 : 0; - this.dependencyTracker.resetBBox(opIdx).recordBBox(opIdx, this.ctx, minMax[0] - outerExtraSize, minMax[2] + outerExtraSize, minMax[1] - outerExtraSize, minMax[3] + outerExtraSize).recordDependencies(opIdx, ["transform"]); + if (widthRemainder === 0) { + continue; } - if (!(path instanceof Path2D)) { - const path2d = data[0] = new Path2D(); - for (let i = 0, ii = path.length; i < ii;) { - switch (path[i++]) { - case DrawOPS.moveTo: - path2d.moveTo(path[i++], path[i++]); - break; - case DrawOPS.lineTo: - path2d.lineTo(path[i++], path[i++]); - break; - case DrawOPS.curveTo: - path2d.bezierCurveTo(path[i++], path[i++], path[i++], path[i++], path[i++], path[i++]); - break; - case DrawOPS.closePath: - path2d.closePath(); - break; - default: - warn(`Unrecognized drawing path operator: ${path[i - 1]}`); - break; - } - } - path = path2d; + const elem = srcPos < srcLength ? src[srcPos++] : 255; + for (let j = 0; j < widthRemainder; j++) { + dest[destPos++] = elem & 1 << 7 - j ? oneMapping : zeroMapping; } - Util.axialAlignedBoundingBox(minMax, getCurrentTransform(this.ctx), this.current.minMax); - this[op](opIdx, path); - this._pathStartIdx = opIdx; - } - closePath(opIdx) { - this.ctx.closePath(); } - stroke(opIdx, path, consumePath = true) { - const ctx = this.ctx; - const strokeColor = this.current.strokeColor; - ctx.globalAlpha = this.current.strokeAlpha; - if (this.contentVisible) { - if (typeof strokeColor === "object" && strokeColor?.getPattern) { - const baseTransform = strokeColor.isModifyingCurrentTransform() ? ctx.getTransform() : null; - ctx.save(); - ctx.strokeStyle = strokeColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.STROKE, opIdx); - if (baseTransform) { - const newPath = new Path2D(); - newPath.addPath(path, ctx.getTransform().invertSelf().multiplySelf(baseTransform)); - path = newPath; - } - this.rescaleAndStroke(path, false); - ctx.restore(); - } else { - this.rescaleAndStroke(path, true); - } + return { + srcPos, + destPos + }; +} +function convertRGBToRGBA({ + src, + srcPos = 0, + dest, + destPos = 0, + width, + height +}) { + let i = 0; + const len = width * height * 3; + const len32 = len >> 2; + const src32 = new Uint32Array(src.buffer, srcPos, len32); + if (FeatureTest.isLittleEndian) { + for (; i < len32 - 2; i += 3, destPos += 4) { + const s1 = src32[i]; + const s2 = src32[i + 1]; + const s3 = src32[i + 2]; + dest[destPos] = s1 | 0xff000000; + dest[destPos + 1] = s1 >>> 24 | s2 << 8 | 0xff000000; + dest[destPos + 2] = s2 >>> 16 | s3 << 16 | 0xff000000; + dest[destPos + 3] = s3 >>> 8 | 0xff000000; } - this.dependencyTracker?.recordDependencies(opIdx, Dependencies.stroke); - if (consumePath) { - this.consumePath(opIdx, path, this.current.getClippedPathBoundingBox(PathType.STROKE, getCurrentTransform(this.ctx))); + for (let j = i * 4, jj = srcPos + len; j < jj; j += 3) { + dest[destPos++] = src[j] | src[j + 1] << 8 | src[j + 2] << 16 | 0xff000000; } - ctx.globalAlpha = this.current.fillAlpha; - } - closeStroke(opIdx, path) { - this.stroke(opIdx, path); - } - fill(opIdx, path, consumePath = true) { - const ctx = this.ctx; - const fillColor = this.current.fillColor; - const isPatternFill = this.current.patternFill; - let needRestore = false; - if (isPatternFill) { - const baseTransform = fillColor.isModifyingCurrentTransform() ? ctx.getTransform() : null; - this.dependencyTracker?.save(opIdx); - ctx.save(); - ctx.fillStyle = fillColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.FILL, opIdx); - if (baseTransform) { - const newPath = new Path2D(); - newPath.addPath(path, ctx.getTransform().invertSelf().multiplySelf(baseTransform)); - path = newPath; - } - needRestore = true; + } else { + for (; i < len32 - 2; i += 3, destPos += 4) { + const s1 = src32[i]; + const s2 = src32[i + 1]; + const s3 = src32[i + 2]; + dest[destPos] = s1 | 0xff; + dest[destPos + 1] = s1 << 24 | s2 >>> 8 | 0xff; + dest[destPos + 2] = s2 << 16 | s3 >>> 16 | 0xff; + dest[destPos + 3] = s3 << 8 | 0xff; } - const intersect = this.current.getClippedPathBoundingBox(); - if (this.contentVisible && intersect !== null) { - if (this.pendingEOFill) { - ctx.fill(path, "evenodd"); - this.pendingEOFill = false; - } else { - ctx.fill(path); - } + for (let j = i * 4, jj = srcPos + len; j < jj; j += 3) { + dest[destPos++] = src[j] << 24 | src[j + 1] << 16 | src[j + 2] << 8 | 0xff; } - this.dependencyTracker?.recordDependencies(opIdx, Dependencies.fill); - if (needRestore) { - ctx.restore(); - this.dependencyTracker?.restore(opIdx); + } + return { + srcPos: srcPos + len, + destPos + }; +} +function grayToRGBA(src, dest) { + if (FeatureTest.isLittleEndian) { + for (let i = 0, ii = src.length; i < ii; i++) { + dest[i] = src[i] * 0x10101 | 0xff000000; } - if (consumePath) { - this.consumePath(opIdx, path, intersect); + } else { + for (let i = 0, ii = src.length; i < ii; i++) { + dest[i] = src[i] * 0x1010100 | 0x000000ff; } } - eoFill(opIdx, path) { - this.pendingEOFill = true; - this.fill(opIdx, path); - } - fillStroke(opIdx, path) { - this.fill(opIdx, path, false); - this.stroke(opIdx, path, false); - this.consumePath(opIdx, path); - } - eoFillStroke(opIdx, path) { - this.pendingEOFill = true; - this.fillStroke(opIdx, path); - } - closeFillStroke(opIdx, path) { - this.fillStroke(opIdx, path); - } - closeEOFillStroke(opIdx, path) { - this.pendingEOFill = true; - this.fillStroke(opIdx, path); - } - endPath(opIdx, path) { - this.consumePath(opIdx, path); - } - rawFillPath(opIdx, path) { - this.ctx.fill(path); - this.dependencyTracker?.recordDependencies(opIdx, Dependencies.rawFillPath).recordOperation(opIdx); - } - clip(opIdx) { - this.dependencyTracker?.recordFutureForcedDependency("clipMode", opIdx); - this.pendingClip = NORMAL_CLIP; - } - eoClip(opIdx) { - this.dependencyTracker?.recordFutureForcedDependency("clipMode", opIdx); - this.pendingClip = EO_CLIP; - } - beginText(opIdx) { - this.current.textMatrix = null; - this.current.textMatrixScale = 1; - this.current.x = this.current.lineX = 0; - this.current.y = this.current.lineY = 0; - this.dependencyTracker?.recordOpenMarker(opIdx).resetIncrementalData("sameLineText").resetIncrementalData("moveText", opIdx); +} + +;// ./src/display/canvas.js + + + + + +const MIN_FONT_SIZE = 16; +const MAX_FONT_SIZE = 100; +const EXECUTION_TIME = 15; +const EXECUTION_STEPS = 10; +const FULL_CHUNK_HEIGHT = 16; +const SCALE_MATRIX = new DOMMatrix(); +const XY = new Float32Array(2); +const MIN_MAX_INIT = new Float32Array([Infinity, Infinity, -Infinity, -Infinity]); +function mirrorContextOperations(ctx, destCtx) { + if (ctx._removeMirroring) { + throw new Error("Context is already forwarding operations."); } - endText(opIdx) { - const paths = this.pendingTextPaths; - const ctx = this.ctx; - if (this.dependencyTracker) { - const { - dependencyTracker - } = this; - if (paths !== undefined) { - dependencyTracker.recordFutureForcedDependency("textClip", dependencyTracker.getOpenMarker()).recordFutureForcedDependency("textClip", opIdx); - } - dependencyTracker.recordCloseMarker(opIdx); - } - if (paths !== undefined) { - const newPath = new Path2D(); - const invTransf = ctx.getTransform().invertSelf(); - for (const { - transform, - x, - y, - fontSize, - path - } of paths) { - if (!path) { - continue; - } - newPath.addPath(path, new DOMMatrix(transform).preMultiplySelf(invTransf).translate(x, y).scale(fontSize, -fontSize)); - } - ctx.clip(newPath); + ctx.__originalSave = ctx.save; + ctx.__originalRestore = ctx.restore; + ctx.__originalRotate = ctx.rotate; + ctx.__originalScale = ctx.scale; + ctx.__originalTranslate = ctx.translate; + ctx.__originalTransform = ctx.transform; + ctx.__originalSetTransform = ctx.setTransform; + ctx.__originalResetTransform = ctx.resetTransform; + ctx.__originalClip = ctx.clip; + ctx.__originalMoveTo = ctx.moveTo; + ctx.__originalLineTo = ctx.lineTo; + ctx.__originalBezierCurveTo = ctx.bezierCurveTo; + ctx.__originalRect = ctx.rect; + ctx.__originalClosePath = ctx.closePath; + ctx.__originalBeginPath = ctx.beginPath; + ctx._removeMirroring = () => { + ctx.save = ctx.__originalSave; + ctx.restore = ctx.__originalRestore; + ctx.rotate = ctx.__originalRotate; + ctx.scale = ctx.__originalScale; + ctx.translate = ctx.__originalTranslate; + ctx.transform = ctx.__originalTransform; + ctx.setTransform = ctx.__originalSetTransform; + ctx.resetTransform = ctx.__originalResetTransform; + ctx.clip = ctx.__originalClip; + ctx.moveTo = ctx.__originalMoveTo; + ctx.lineTo = ctx.__originalLineTo; + ctx.bezierCurveTo = ctx.__originalBezierCurveTo; + ctx.rect = ctx.__originalRect; + ctx.closePath = ctx.__originalClosePath; + ctx.beginPath = ctx.__originalBeginPath; + delete ctx._removeMirroring; + }; + ctx.save = function () { + destCtx.save(); + this.__originalSave(); + }; + ctx.restore = function () { + destCtx.restore(); + this.__originalRestore(); + }; + ctx.translate = function (x, y) { + destCtx.translate(x, y); + this.__originalTranslate(x, y); + }; + ctx.scale = function (x, y) { + destCtx.scale(x, y); + this.__originalScale(x, y); + }; + ctx.transform = function (a, b, c, d, e, f) { + destCtx.transform(a, b, c, d, e, f); + this.__originalTransform(a, b, c, d, e, f); + }; + ctx.setTransform = function (a, b, c, d, e, f) { + destCtx.setTransform(a, b, c, d, e, f); + this.__originalSetTransform(a, b, c, d, e, f); + }; + ctx.resetTransform = function () { + destCtx.resetTransform(); + this.__originalResetTransform(); + }; + ctx.rotate = function (angle) { + destCtx.rotate(angle); + this.__originalRotate(angle); + }; + ctx.clip = function (rule) { + destCtx.clip(rule); + this.__originalClip(rule); + }; + ctx.moveTo = function (x, y) { + destCtx.moveTo(x, y); + this.__originalMoveTo(x, y); + }; + ctx.lineTo = function (x, y) { + destCtx.lineTo(x, y); + this.__originalLineTo(x, y); + }; + ctx.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { + destCtx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); + this.__originalBezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); + }; + ctx.rect = function (x, y, width, height) { + destCtx.rect(x, y, width, height); + this.__originalRect(x, y, width, height); + }; + ctx.closePath = function () { + destCtx.closePath(); + this.__originalClosePath(); + }; + ctx.beginPath = function () { + destCtx.beginPath(); + this.__originalBeginPath(); + }; +} +class CachedCanvases { + constructor(canvasFactory) { + this.canvasFactory = canvasFactory; + this.cache = Object.create(null); + } + getCanvas(id, width, height) { + let canvasEntry; + if (this.cache[id] !== undefined) { + canvasEntry = this.cache[id]; + this.canvasFactory.reset(canvasEntry, width, height); + } else { + canvasEntry = this.canvasFactory.create(width, height); + this.cache[id] = canvasEntry; } - delete this.pendingTextPaths; + return canvasEntry; } - setCharSpacing(opIdx, spacing) { - this.dependencyTracker?.recordSimpleData("charSpacing", opIdx); - this.current.charSpacing = spacing; + delete(id) { + delete this.cache[id]; } - setWordSpacing(opIdx, spacing) { - this.dependencyTracker?.recordSimpleData("wordSpacing", opIdx); - this.current.wordSpacing = spacing; + clear() { + for (const id in this.cache) { + const canvasEntry = this.cache[id]; + this.canvasFactory.destroy(canvasEntry); + delete this.cache[id]; + } } - setHScale(opIdx, scale) { - this.dependencyTracker?.recordSimpleData("hScale", opIdx); - this.current.textHScale = scale / 100; +} +function drawImageAtIntegerCoords(ctx, srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH) { + const [a, b, c, d, tx, ty] = getCurrentTransform(ctx); + if (b === 0 && c === 0) { + const tlX = destX * a + tx; + const rTlX = Math.round(tlX); + const tlY = destY * d + ty; + const rTlY = Math.round(tlY); + const brX = (destX + destW) * a + tx; + const rWidth = Math.abs(Math.round(brX) - rTlX) || 1; + const brY = (destY + destH) * d + ty; + const rHeight = Math.abs(Math.round(brY) - rTlY) || 1; + ctx.setTransform(Math.sign(a), 0, 0, Math.sign(d), rTlX, rTlY); + ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rWidth, rHeight); + ctx.setTransform(a, b, c, d, tx, ty); + return [rWidth, rHeight]; } - setLeading(opIdx, leading) { - this.dependencyTracker?.recordSimpleData("leading", opIdx); - this.current.leading = -leading; + if (a === 0 && d === 0) { + const tlX = destY * c + tx; + const rTlX = Math.round(tlX); + const tlY = destX * b + ty; + const rTlY = Math.round(tlY); + const brX = (destY + destH) * c + tx; + const rWidth = Math.abs(Math.round(brX) - rTlX) || 1; + const brY = (destX + destW) * b + ty; + const rHeight = Math.abs(Math.round(brY) - rTlY) || 1; + ctx.setTransform(0, Math.sign(b), Math.sign(c), 0, rTlX, rTlY); + ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rHeight, rWidth); + ctx.setTransform(a, b, c, d, tx, ty); + return [rHeight, rWidth]; } - setFont(opIdx, fontRefName, size) { - this.dependencyTracker?.recordSimpleData("font", opIdx).recordSimpleDataFromNamed("fontObj", fontRefName, opIdx); - const fontObj = this.commonObjs.get(fontRefName); - const current = this.current; - if (!fontObj) { - throw new Error(`Can't find font for ${fontRefName}`); - } - current.fontMatrix = fontObj.fontMatrix || FONT_IDENTITY_MATRIX; - if (current.fontMatrix[0] === 0 || current.fontMatrix[3] === 0) { - warn("Invalid font matrix for font " + fontRefName); - } - if (size < 0) { - size = -size; - current.fontDirection = -1; - } else { - current.fontDirection = 1; - } - this.current.font = fontObj; - this.current.fontSize = size; - if (fontObj.isType3Font) { - return; - } - const name = fontObj.loadedName || "sans-serif"; - const typeface = fontObj.systemFontInfo?.css || `"${name}", ${fontObj.fallbackName}`; - let bold = "normal"; - if (fontObj.black) { - bold = "900"; - } else if (fontObj.bold) { - bold = "bold"; - } - const italic = fontObj.italic ? "italic" : "normal"; - let browserFontSize = size; - if (size < MIN_FONT_SIZE) { - browserFontSize = MIN_FONT_SIZE; - } else if (size > MAX_FONT_SIZE) { - browserFontSize = MAX_FONT_SIZE; - } - this.current.fontSizeScale = size / browserFontSize; - this.ctx.font = `${italic} ${bold} ${browserFontSize}px ${typeface}`; + ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH); + const scaleX = Math.hypot(a, b); + const scaleY = Math.hypot(c, d); + return [scaleX * destW, scaleY * destH]; +} +class CanvasExtraState { + alphaIsShape = false; + fontSize = 0; + fontSizeScale = 1; + textMatrix = null; + textMatrixScale = 1; + fontMatrix = FONT_IDENTITY_MATRIX; + leading = 0; + x = 0; + y = 0; + lineX = 0; + lineY = 0; + charSpacing = 0; + wordSpacing = 0; + textHScale = 1; + textRenderingMode = TextRenderingMode.FILL; + textRise = 0; + fillColor = "#000000"; + strokeColor = "#000000"; + patternFill = false; + patternStroke = false; + fillAlpha = 1; + strokeAlpha = 1; + lineWidth = 1; + activeSMask = null; + transferMaps = "none"; + constructor(width, height, preInit) { + preInit?.(this); + this.clipBox = new Float32Array([0, 0, width, height]); + this.minMax = MIN_MAX_INIT.slice(); } - setTextRenderingMode(opIdx, mode) { - this.dependencyTracker?.recordSimpleData("textRenderingMode", opIdx); - this.current.textRenderingMode = mode; + clone() { + const clone = Object.create(this); + clone.clipBox = this.clipBox.slice(); + clone.minMax = this.minMax.slice(); + return clone; } - setTextRise(opIdx, rise) { - this.dependencyTracker?.recordSimpleData("textRise", opIdx); - this.current.textRise = rise; + getPathBoundingBox(pathType = PathType.FILL, transform = null) { + const box = this.minMax.slice(); + if (pathType === PathType.STROKE) { + if (!transform) { + unreachable("Stroke bounding box must include transform."); + } + Util.singularValueDecompose2dScale(transform, XY); + const xStrokePad = XY[0] * this.lineWidth / 2; + const yStrokePad = XY[1] * this.lineWidth / 2; + box[0] -= xStrokePad; + box[1] -= yStrokePad; + box[2] += xStrokePad; + box[3] += yStrokePad; + } + return box; } - moveText(opIdx, x, y) { - this.dependencyTracker?.resetIncrementalData("sameLineText").recordIncrementalData("moveText", opIdx); - this.current.x = this.current.lineX += x; - this.current.y = this.current.lineY += y; + updateClipFromPath() { + const intersect = Util.intersect(this.clipBox, this.getPathBoundingBox()); + this.startNewPathAndClipBox(intersect || [0, 0, 0, 0]); } - setLeadingMoveText(opIdx, x, y) { - this.setLeading(opIdx, -y); - this.moveText(opIdx, x, y); + isEmptyClip() { + return this.minMax[0] === Infinity; } - setTextMatrix(opIdx, matrix) { - this.dependencyTracker?.recordSimpleData("textMatrix", opIdx); - const { - current - } = this; - current.textMatrix = matrix; - current.textMatrixScale = Math.hypot(matrix[0], matrix[1]); - current.x = current.lineX = 0; - current.y = current.lineY = 0; + startNewPathAndClipBox(box) { + this.clipBox.set(box, 0); + this.minMax.set(MIN_MAX_INIT, 0); } - nextLine(opIdx) { - this.moveText(opIdx, 0, this.current.leading); - this.dependencyTracker?.recordIncrementalData("moveText", this.dependencyTracker.getSimpleIndex("leading") ?? opIdx); + getClippedPathBoundingBox(pathType = PathType.FILL, transform = null) { + return Util.intersect(this.clipBox, this.getPathBoundingBox(pathType, transform)); } - #getScaledPath(path, currentTransform, transform) { - const newPath = new Path2D(); - newPath.addPath(path, new DOMMatrix(transform).invertSelf().multiplySelf(currentTransform)); - return newPath; +} +function putBinaryImageData(ctx, imgData) { + if (imgData instanceof ImageData) { + ctx.putImageData(imgData, 0, 0); + return; } - paintChar(opIdx, character, x, y, patternFillTransform, patternStrokeTransform) { - const ctx = this.ctx; - const current = this.current; - const font = current.font; - const textRenderingMode = current.textRenderingMode; - const fontSize = current.fontSize / current.fontSizeScale; - const fillStrokeMode = textRenderingMode & TextRenderingMode.FILL_STROKE_MASK; - const isAddToPathSet = !!(textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG); - const patternFill = current.patternFill && !font.missingFile; - const patternStroke = current.patternStroke && !font.missingFile; - let path; - if ((font.disableFontFace || isAddToPathSet || patternFill || patternStroke) && !font.missingFile) { - path = font.getPathGenerator(this.commonObjs, character); - } - if (path && (font.disableFontFace || patternFill || patternStroke)) { - ctx.save(); - ctx.translate(x, y); - ctx.scale(fontSize, -fontSize); - this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, font); - let currentTransform; - if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) { - if (patternFillTransform) { - currentTransform = ctx.getTransform(); - ctx.setTransform(...patternFillTransform); - const scaledPath = this.#getScaledPath(path, currentTransform, patternFillTransform); - ctx.fill(scaledPath); - } else { - ctx.fill(path); + const height = imgData.height, + width = imgData.width; + const partialChunkHeight = height % FULL_CHUNK_HEIGHT; + const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT; + const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1; + const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT); + let srcPos = 0, + destPos; + const src = imgData.data; + const dest = chunkImgData.data; + let i, j, thisChunkHeight, elemsInThisChunk; + if (imgData.kind === util_ImageKind.GRAYSCALE_1BPP) { + const srcLength = src.byteLength; + const dest32 = new Uint32Array(dest.buffer, 0, dest.byteLength >> 2); + const dest32DataLength = dest32.length; + const fullSrcDiff = width + 7 >> 3; + const white = 0xffffffff; + const black = util_FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff; + for (i = 0; i < totalChunks; i++) { + thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight; + destPos = 0; + for (j = 0; j < thisChunkHeight; j++) { + const srcDiff = srcLength - srcPos; + let k = 0; + const kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7; + const kEndUnrolled = kEnd & ~7; + let mask = 0; + let srcByte = 0; + for (; k < kEndUnrolled; k += 8) { + srcByte = src[srcPos++]; + dest32[destPos++] = srcByte & 128 ? white : black; + dest32[destPos++] = srcByte & 64 ? white : black; + dest32[destPos++] = srcByte & 32 ? white : black; + dest32[destPos++] = srcByte & 16 ? white : black; + dest32[destPos++] = srcByte & 8 ? white : black; + dest32[destPos++] = srcByte & 4 ? white : black; + dest32[destPos++] = srcByte & 2 ? white : black; + dest32[destPos++] = srcByte & 1 ? white : black; } - } - if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) { - if (patternStrokeTransform) { - currentTransform ||= ctx.getTransform(); - ctx.setTransform(...patternStrokeTransform); - const { - a, - b, - c, - d - } = currentTransform; - const invPatternTransform = Util.inverseTransform(patternStrokeTransform); - const transf = Util.transform([a, b, c, d, 0, 0], invPatternTransform); - Util.singularValueDecompose2dScale(transf, XY); - ctx.lineWidth *= Math.max(XY[0], XY[1]) / fontSize; - ctx.stroke(this.#getScaledPath(path, currentTransform, patternStrokeTransform)); - } else { - ctx.lineWidth /= fontSize; - ctx.stroke(path); + for (; k < kEnd; k++) { + if (mask === 0) { + srcByte = src[srcPos++]; + mask = 128; + } + dest32[destPos++] = srcByte & mask ? white : black; + mask >>= 1; } } - ctx.restore(); - } else { - if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) { - ctx.fillText(character, x, y); - this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, font, fontSize, x, y, () => ctx.measureText(character)); - } - if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) { - if (this.dependencyTracker) { - this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, font, fontSize, x, y, () => ctx.measureText(character)).recordDependencies(opIdx, Dependencies.stroke); - } - ctx.strokeText(character, x, y); + while (destPos < dest32DataLength) { + dest32[destPos++] = 0; } + ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } - if (isAddToPathSet) { - const paths = this.pendingTextPaths ||= []; - paths.push({ - transform: getCurrentTransform(ctx), - x, - y, - fontSize, - path - }); - this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, font, fontSize, x, y); + } else if (imgData.kind === util_ImageKind.RGBA_32BPP) { + j = 0; + elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4; + for (i = 0; i < fullChunks; i++) { + dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); + srcPos += elemsInThisChunk; + ctx.putImageData(chunkImgData, 0, j); + j += FULL_CHUNK_HEIGHT; } - } - get isFontSubpixelAAEnabled() { - const { - context: ctx - } = this.cachedCanvases.getCanvas("isFontSubpixelAAEnabled", 10, 10); - ctx.scale(1.5, 1); - ctx.fillText("I", 0, 10); - const data = ctx.getImageData(0, 0, 10, 10).data; - let enabled = false; - for (let i = 3; i < data.length; i += 4) { - if (data[i] > 0 && data[i] < 255) { - enabled = true; - break; - } + if (i < totalChunks) { + elemsInThisChunk = width * partialChunkHeight * 4; + dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); + ctx.putImageData(chunkImgData, 0, j); } - return shadow(this, "isFontSubpixelAAEnabled", enabled); - } - showText(opIdx, glyphs) { - if (this.dependencyTracker) { - this.dependencyTracker.recordDependencies(opIdx, Dependencies.showText).resetBBox(opIdx); - if (this.current.textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG) { - this.dependencyTracker.recordFutureForcedDependency("textClip", opIdx).inheritPendingDependenciesAsFutureForcedDependencies(); + } else if (imgData.kind === util_ImageKind.RGB_24BPP) { + thisChunkHeight = FULL_CHUNK_HEIGHT; + elemsInThisChunk = width * thisChunkHeight; + for (i = 0; i < totalChunks; i++) { + if (i >= fullChunks) { + thisChunkHeight = partialChunkHeight; + elemsInThisChunk = width * thisChunkHeight; } + destPos = 0; + for (j = elemsInThisChunk; j--;) { + dest[destPos++] = src[srcPos++]; + dest[destPos++] = src[srcPos++]; + dest[destPos++] = src[srcPos++]; + dest[destPos++] = 255; + } + ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } - const current = this.current; - const font = current.font; - if (font.isType3Font) { - this.showType3Text(opIdx, glyphs); - this.dependencyTracker?.recordShowTextOperation(opIdx); - return undefined; - } - const fontSize = current.fontSize; - if (fontSize === 0) { - this.dependencyTracker?.recordOperation(opIdx); - return undefined; - } - const ctx = this.ctx; - const fontSizeScale = current.fontSizeScale; - const charSpacing = current.charSpacing; - const wordSpacing = current.wordSpacing; - const fontDirection = current.fontDirection; - const textHScale = current.textHScale * fontDirection; - const glyphsLength = glyphs.length; - const vertical = font.vertical; - const spacingDir = vertical ? 1 : -1; - const defaultVMetrics = font.defaultVMetrics; - const widthAdvanceScale = fontSize * current.fontMatrix[0]; - const simpleFillText = current.textRenderingMode === TextRenderingMode.FILL && !font.disableFontFace && !current.patternFill; - ctx.save(); - if (current.textMatrix) { - ctx.transform(...current.textMatrix); - } - ctx.translate(current.x, current.y + current.textRise); - if (fontDirection > 0) { - ctx.scale(textHScale, -1); - } else { - ctx.scale(textHScale, 1); + } else { + throw new Error(`bad image kind: ${imgData.kind}`); + } +} +function putBinaryImageMask(ctx, imgData) { + if (imgData.bitmap) { + ctx.drawImage(imgData.bitmap, 0, 0); + return; + } + const height = imgData.height, + width = imgData.width; + const partialChunkHeight = height % FULL_CHUNK_HEIGHT; + const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT; + const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1; + const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT); + let srcPos = 0; + const src = imgData.data; + const dest = chunkImgData.data; + for (let i = 0; i < totalChunks; i++) { + const thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight; + ({ + srcPos + } = convertBlackAndWhiteToRGBA({ + src, + srcPos, + dest, + width, + height: thisChunkHeight, + nonBlackColor: 0 + })); + ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); + } +} +function copyCtxState(sourceCtx, destCtx) { + const properties = ["strokeStyle", "fillStyle", "fillRule", "globalAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "globalCompositeOperation", "font", "filter"]; + for (const property of properties) { + if (sourceCtx[property] !== undefined) { + destCtx[property] = sourceCtx[property]; } - let patternFillTransform, patternStrokeTransform; - if (current.patternFill) { - ctx.save(); - const pattern = current.fillColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.FILL, opIdx); - patternFillTransform = getCurrentTransform(ctx); - ctx.restore(); - ctx.fillStyle = pattern; + } + if (sourceCtx.setLineDash !== undefined) { + destCtx.setLineDash(sourceCtx.getLineDash()); + destCtx.lineDashOffset = sourceCtx.lineDashOffset; + } +} +function resetCtxToDefault(ctx) { + ctx.strokeStyle = ctx.fillStyle = "#000000"; + ctx.fillRule = "nonzero"; + ctx.globalAlpha = 1; + ctx.lineWidth = 1; + ctx.lineCap = "butt"; + ctx.lineJoin = "miter"; + ctx.miterLimit = 10; + ctx.globalCompositeOperation = "source-over"; + ctx.font = "10px sans-serif"; + if (ctx.setLineDash !== undefined) { + ctx.setLineDash([]); + ctx.lineDashOffset = 0; + } + const { + filter + } = ctx; + if (filter !== "none" && filter !== "") { + ctx.filter = "none"; + } +} +function getImageSmoothingEnabled(transform, interpolate) { + if (interpolate) { + return true; + } + Util.singularValueDecompose2dScale(transform, XY); + const actualScale = Math.fround(OutputScale.pixelRatio * PixelsPerInch.PDF_TO_CSS_UNITS); + return XY[0] <= actualScale && XY[1] <= actualScale; +} +const LINE_CAP_STYLES = ["butt", "round", "square"]; +const LINE_JOIN_STYLES = ["miter", "round", "bevel"]; +const NORMAL_CLIP = {}; +const EO_CLIP = {}; +class CanvasGraphics { + constructor(canvasCtx, commonObjs, objs, canvasFactory, filterFactory, { + optionalContentConfig, + markedContentStack = null + }, annotationCanvasMap, pageColors, dependencyTracker) { + this.ctx = canvasCtx; + this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height); + this.stateStack = []; + this.pendingClip = null; + this.pendingEOFill = false; + this.res = null; + this.xobjs = null; + this.commonObjs = commonObjs; + this.objs = objs; + this.canvasFactory = canvasFactory; + this.filterFactory = filterFactory; + this.groupStack = []; + this.baseTransform = null; + this.baseTransformStack = []; + this.groupLevel = 0; + this.smaskStack = []; + this.smaskCounter = 0; + this.tempSMask = null; + this.suspendedCtx = null; + this.contentVisible = true; + this.markedContentStack = markedContentStack || []; + this.optionalContentConfig = optionalContentConfig; + this.cachedCanvases = new CachedCanvases(this.canvasFactory); + this.cachedPatterns = new Map(); + this.annotationCanvasMap = annotationCanvasMap; + this.viewportScale = 1; + this.outputScaleX = 1; + this.outputScaleY = 1; + this.pageColors = pageColors; + this._cachedScaleForStroking = [-1, 0]; + this._cachedGetSinglePixelWidth = null; + this._cachedBitmapsMap = new Map(); + this.dependencyTracker = dependencyTracker ?? null; + } + getObject(opIdx, data, fallback = null) { + if (typeof data === "string") { + this.dependencyTracker?.recordNamedDependency(opIdx, data); + return data.startsWith("g_") ? this.commonObjs.get(data) : this.objs.get(data); } - if (current.patternStroke) { - ctx.save(); - const pattern = current.strokeColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.STROKE, opIdx); - patternStrokeTransform = getCurrentTransform(ctx); - ctx.restore(); - ctx.strokeStyle = pattern; + return fallback; + } + beginDrawing({ + transform, + viewport, + transparency = false, + background = null + }) { + const width = this.ctx.canvas.width; + const height = this.ctx.canvas.height; + const savedFillStyle = this.ctx.fillStyle; + this.ctx.fillStyle = background || "#ffffff"; + this.ctx.fillRect(0, 0, width, height); + this.ctx.fillStyle = savedFillStyle; + if (transparency) { + const transparentCanvas = this.cachedCanvases.getCanvas("transparent", width, height); + this.compositeCtx = this.ctx; + this.transparentCanvas = transparentCanvas.canvas; + this.ctx = transparentCanvas.context; + this.ctx.save(); + this.ctx.transform(...getCurrentTransform(this.compositeCtx)); } - let lineWidth = current.lineWidth; - const scale = current.textMatrixScale; - if (scale === 0 || lineWidth === 0) { - const fillStrokeMode = current.textRenderingMode & TextRenderingMode.FILL_STROKE_MASK; - if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) { - lineWidth = this.getSinglePixelWidth(); - } - } else { - lineWidth /= scale; + this.ctx.save(); + resetCtxToDefault(this.ctx); + if (transform) { + this.ctx.transform(...transform); + this.outputScaleX = transform[0]; + this.outputScaleY = transform[0]; } - if (fontSizeScale !== 1.0) { - ctx.scale(fontSizeScale, fontSizeScale); - lineWidth /= fontSizeScale; + this.ctx.transform(...viewport.transform); + this.viewportScale = viewport.scale; + this.baseTransform = getCurrentTransform(this.ctx); + } + executeOperatorList(operatorList, executionStartIdx, continueCallback, stepper, operationsFilter) { + const argsArray = operatorList.argsArray; + const fnArray = operatorList.fnArray; + let i = executionStartIdx || 0; + const argsArrayLen = argsArray.length; + if (argsArrayLen === i) { + return i; } - ctx.lineWidth = lineWidth; - if (font.isInvalidPDFjsFont) { - const chars = []; - let width = 0; - for (const glyph of glyphs) { - chars.push(glyph.unicode); - width += glyph.width; - } - const joinedChars = chars.join(""); - ctx.fillText(joinedChars, 0, 0); - if (this.dependencyTracker !== null) { - const measure = ctx.measureText(joinedChars); - this.dependencyTracker.recordBBox(opIdx, this.ctx, -measure.actualBoundingBoxLeft, measure.actualBoundingBoxRight, -measure.actualBoundingBoxAscent, measure.actualBoundingBoxDescent).recordShowTextOperation(opIdx); + const chunkOperations = argsArrayLen - i > EXECUTION_STEPS && typeof continueCallback === "function"; + const endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0; + let steps = 0; + const commonObjs = this.commonObjs; + const objs = this.objs; + let fnId, fnArgs; + while (true) { + if (stepper !== undefined && i === stepper.nextBreakPoint) { + stepper.breakIt(i, continueCallback); + return i; } - current.x += width * widthAdvanceScale * textHScale; - ctx.restore(); - this.compose(); - return undefined; - } - let x = 0, - i; - for (i = 0; i < glyphsLength; ++i) { - const glyph = glyphs[i]; - if (typeof glyph === "number") { - x += spacingDir * glyph * fontSize / 1000; - continue; + if (!operationsFilter || operationsFilter(i)) { + fnId = fnArray[i]; + fnArgs = argsArray[i] ?? null; + if (fnId !== OPS.dependency) { + if (fnArgs === null) { + this[fnId](i); + } else { + this[fnId](i, ...fnArgs); + } + } else { + for (const depObjId of fnArgs) { + this.dependencyTracker?.recordNamedData(depObjId, i); + const objsPool = depObjId.startsWith("g_") ? commonObjs : objs; + if (!objsPool.has(depObjId)) { + objsPool.get(depObjId, continueCallback); + return i; + } + } + } } - let restoreNeeded = false; - const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing; - const character = glyph.fontChar; - const accent = glyph.accent; - let scaledX, scaledY; - let width = glyph.width; - if (vertical) { - const vmetric = glyph.vmetric || defaultVMetrics; - const vx = -(glyph.vmetric ? vmetric[1] : width * 0.5) * widthAdvanceScale; - const vy = vmetric[2] * widthAdvanceScale; - width = vmetric ? -vmetric[0] : width; - scaledX = vx / fontSizeScale; - scaledY = (x + vy) / fontSizeScale; - } else { - scaledX = x / fontSizeScale; - scaledY = 0; + i++; + if (i === argsArrayLen) { + return i; } - let measure; - if (font.remeasure && width > 0) { - measure = ctx.measureText(character); - const measuredWidth = measure.width * 1000 / fontSize * fontSizeScale; - if (width < measuredWidth && this.isFontSubpixelAAEnabled) { - const characterScaleX = width / measuredWidth; - restoreNeeded = true; - ctx.save(); - ctx.scale(characterScaleX, 1); - scaledX /= characterScaleX; - } else if (width !== measuredWidth) { - scaledX += (width - measuredWidth) / 2000 * fontSize / fontSizeScale; + if (chunkOperations && ++steps > EXECUTION_STEPS) { + if (Date.now() > endTime) { + continueCallback(); + return i; } + steps = 0; } - if (this.contentVisible && (glyph.isInFont || font.missingFile)) { - if (simpleFillText && !accent) { - ctx.fillText(character, scaledX, scaledY); - this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, measure ? { - bbox: null - } : font, fontSize / fontSizeScale, scaledX, scaledY, () => measure ?? ctx.measureText(character)); - } else { - this.paintChar(opIdx, character, scaledX, scaledY, patternFillTransform, patternStrokeTransform); - if (accent) { - const scaledAccentX = scaledX + fontSize * accent.offset.x / fontSizeScale; - const scaledAccentY = scaledY - fontSize * accent.offset.y / fontSizeScale; - this.paintChar(opIdx, accent.fontChar, scaledAccentX, scaledAccentY, patternFillTransform, patternStrokeTransform); - } + } + } + #restoreInitialState() { + while (this.stateStack.length || this.inSMaskMode) { + this.restore(); + } + this.current.activeSMask = null; + this.ctx.restore(); + if (this.transparentCanvas) { + this.ctx = this.compositeCtx; + this.ctx.save(); + this.ctx.setTransform(1, 0, 0, 1, 0, 0); + this.ctx.drawImage(this.transparentCanvas, 0, 0); + this.ctx.restore(); + this.transparentCanvas = null; + } + } + endDrawing() { + this.#restoreInitialState(); + this.cachedCanvases.clear(); + this.cachedPatterns.clear(); + for (const cache of this._cachedBitmapsMap.values()) { + for (const canvas of cache.values()) { + if (typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement) { + canvas.width = canvas.height = 0; } } - const charWidth = vertical ? width * widthAdvanceScale - spacing * fontDirection : width * widthAdvanceScale + spacing * fontDirection; - x += charWidth; - if (restoreNeeded) { - ctx.restore(); + cache.clear(); + } + this._cachedBitmapsMap.clear(); + this.#drawFilter(); + } + #drawFilter() { + if (this.pageColors) { + const hcmFilterId = this.filterFactory.addHCMFilter(this.pageColors.foreground, this.pageColors.background); + if (hcmFilterId !== "none") { + const savedFilter = this.ctx.filter; + this.ctx.filter = hcmFilterId; + this.ctx.drawImage(this.ctx.canvas, 0, 0); + this.ctx.filter = savedFilter; } } - if (vertical) { - current.y -= x; - } else { - current.x += x * textHScale; + } + _scaleImage(img, inverseTransform) { + const width = img.width ?? img.displayWidth; + const height = img.height ?? img.displayHeight; + let widthScale = Math.max(Math.hypot(inverseTransform[0], inverseTransform[1]), 1); + let heightScale = Math.max(Math.hypot(inverseTransform[2], inverseTransform[3]), 1); + let paintWidth = width, + paintHeight = height; + let tmpCanvasId = "prescale1"; + let tmpCanvas, tmpCtx; + while (widthScale > 2 && paintWidth > 1 || heightScale > 2 && paintHeight > 1) { + let newWidth = paintWidth, + newHeight = paintHeight; + if (widthScale > 2 && paintWidth > 1) { + newWidth = paintWidth >= 16384 ? Math.floor(paintWidth / 2) - 1 || 1 : Math.ceil(paintWidth / 2); + widthScale /= paintWidth / newWidth; + } + if (heightScale > 2 && paintHeight > 1) { + newHeight = paintHeight >= 16384 ? Math.floor(paintHeight / 2) - 1 || 1 : Math.ceil(paintHeight) / 2; + heightScale /= paintHeight / newHeight; + } + tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight); + tmpCtx = tmpCanvas.context; + tmpCtx.clearRect(0, 0, newWidth, newHeight); + tmpCtx.drawImage(img, 0, 0, paintWidth, paintHeight, 0, 0, newWidth, newHeight); + img = tmpCanvas.canvas; + paintWidth = newWidth; + paintHeight = newHeight; + tmpCanvasId = tmpCanvasId === "prescale1" ? "prescale2" : "prescale1"; } - ctx.restore(); - this.compose(); - this.dependencyTracker?.recordShowTextOperation(opIdx); - return undefined; + return { + img, + paintWidth, + paintHeight + }; } - showType3Text(opIdx, glyphs) { + _createMaskCanvas(opIdx, img) { const ctx = this.ctx; - const current = this.current; - const font = current.font; - const fontSize = current.fontSize; - const fontDirection = current.fontDirection; - const spacingDir = font.vertical ? 1 : -1; - const charSpacing = current.charSpacing; - const wordSpacing = current.wordSpacing; - const textHScale = current.textHScale * fontDirection; - const fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX; - const glyphsLength = glyphs.length; - const isTextInvisible = current.textRenderingMode === TextRenderingMode.INVISIBLE; - let i, glyph, width, spacingLength; - if (isTextInvisible || fontSize === 0) { - return; + const { + width, + height + } = img; + const fillColor = this.current.fillColor; + const isPatternFill = this.current.patternFill; + const currentTransform = getCurrentTransform(ctx); + let cache, cacheKey, scaled, maskCanvas; + if ((img.bitmap || img.data) && img.count > 1) { + const mainKey = img.bitmap || img.data.buffer; + cacheKey = JSON.stringify(isPatternFill ? currentTransform : [currentTransform.slice(0, 4), fillColor]); + cache = this._cachedBitmapsMap.get(mainKey); + if (!cache) { + cache = new Map(); + this._cachedBitmapsMap.set(mainKey, cache); + } + const cachedImage = cache.get(cacheKey); + if (cachedImage && !isPatternFill) { + const offsetX = Math.round(Math.min(currentTransform[0], currentTransform[2]) + currentTransform[4]); + const offsetY = Math.round(Math.min(currentTransform[1], currentTransform[3]) + currentTransform[5]); + this.dependencyTracker?.recordDependencies(opIdx, Dependencies.transformAndFill); + return { + canvas: cachedImage, + offsetX, + offsetY + }; + } + scaled = cachedImage; } - this._cachedScaleForStroking[0] = -1; - this._cachedGetSinglePixelWidth = null; - ctx.save(); - if (current.textMatrix) { - ctx.transform(...current.textMatrix); + if (!scaled) { + maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height); + putBinaryImageMask(maskCanvas.context, img); } - ctx.translate(current.x, current.y + current.textRise); - ctx.scale(textHScale, fontDirection); - const dependencyTracker = this.dependencyTracker; - this.dependencyTracker = dependencyTracker ? new CanvasNestedDependencyTracker(dependencyTracker, opIdx) : null; - for (i = 0; i < glyphsLength; ++i) { - glyph = glyphs[i]; - if (typeof glyph === "number") { - spacingLength = spacingDir * glyph * fontSize / 1000; - this.ctx.translate(spacingLength, 0); - current.x += spacingLength * textHScale; - continue; - } - const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing; - const operatorList = font.charProcOperatorList[glyph.operatorListId]; - if (!operatorList) { - warn(`Type3 character "${glyph.operatorListId}" is not available.`); - } else if (this.contentVisible) { - this.save(); - ctx.scale(fontSize, fontSize); - ctx.transform(...fontMatrix); - this.executeOperatorList(operatorList); - this.restore(); + let maskToCanvas = Util.transform(currentTransform, [1 / width, 0, 0, -1 / height, 0, 0]); + maskToCanvas = Util.transform(maskToCanvas, [1, 0, 0, 1, 0, -height]); + const minMax = MIN_MAX_INIT.slice(); + Util.axialAlignedBoundingBox([0, 0, width, height], maskToCanvas, minMax); + const [minX, minY, maxX, maxY] = minMax; + const drawnWidth = Math.round(maxX - minX) || 1; + const drawnHeight = Math.round(maxY - minY) || 1; + const fillCanvas = this.cachedCanvases.getCanvas("fillCanvas", drawnWidth, drawnHeight); + const fillCtx = fillCanvas.context; + const offsetX = minX; + const offsetY = minY; + fillCtx.translate(-offsetX, -offsetY); + fillCtx.transform(...maskToCanvas); + if (!scaled) { + scaled = this._scaleImage(maskCanvas.canvas, getCurrentTransformInverse(fillCtx)); + scaled = scaled.img; + if (cache && isPatternFill) { + cache.set(cacheKey, scaled); } - const p = [glyph.width, 0]; - Util.applyTransform(p, fontMatrix); - width = p[0] * fontSize + spacing; - ctx.translate(width, 0); - current.x += width * textHScale; } - ctx.restore(); - if (dependencyTracker) { - this.dependencyTracker = dependencyTracker; + fillCtx.imageSmoothingEnabled = getImageSmoothingEnabled(getCurrentTransform(fillCtx), img.interpolate); + drawImageAtIntegerCoords(fillCtx, scaled, 0, 0, scaled.width, scaled.height, 0, 0, width, height); + fillCtx.globalCompositeOperation = "source-in"; + const inverse = Util.transform(getCurrentTransformInverse(fillCtx), [1, 0, 0, 1, -offsetX, -offsetY]); + fillCtx.fillStyle = isPatternFill ? fillColor.getPattern(ctx, this, inverse, PathType.FILL, opIdx) : fillColor; + fillCtx.fillRect(0, 0, width, height); + if (cache && !isPatternFill) { + this.cachedCanvases.delete("fillCanvas"); + cache.set(cacheKey, fillCanvas.canvas); } + this.dependencyTracker?.recordDependencies(opIdx, Dependencies.transformAndFill); + return { + canvas: fillCanvas.canvas, + offsetX: Math.round(offsetX), + offsetY: Math.round(offsetY) + }; } - setCharWidth(opIdx, xWidth, yWidth) {} - setCharWidthAndBounds(opIdx, xWidth, yWidth, llx, lly, urx, ury) { - const clip = new Path2D(); - clip.rect(llx, lly, urx - llx, ury - lly); - this.ctx.clip(clip); - this.dependencyTracker?.recordBBox(opIdx, this.ctx, llx, urx, lly, ury).recordClipBox(opIdx, this.ctx, llx, urx, lly, ury); - this.endPath(opIdx); - } - getColorN_Pattern(opIdx, IR) { - let pattern; - if (IR[0] === "TilingPattern") { - const baseTransform = this.baseTransform || getCurrentTransform(this.ctx); - const canvasGraphicsFactory = { - createCanvasGraphics: (ctx, renderingOpIdx) => new CanvasGraphics(ctx, this.commonObjs, this.objs, this.canvasFactory, this.filterFactory, { - optionalContentConfig: this.optionalContentConfig, - markedContentStack: this.markedContentStack - }, undefined, undefined, this.dependencyTracker ? new CanvasNestedDependencyTracker(this.dependencyTracker, renderingOpIdx, true) : null) - }; - pattern = new TilingPattern(IR, this.ctx, canvasGraphicsFactory, baseTransform); - } else { - pattern = this._getPattern(opIdx, IR[1], IR[2]); + setLineWidth(opIdx, width) { + this.dependencyTracker?.recordSimpleData("lineWidth", opIdx); + if (width !== this.current.lineWidth) { + this._cachedScaleForStroking[0] = -1; } - return pattern; - } - setStrokeColorN(opIdx, ...args) { - this.dependencyTracker?.recordSimpleData("strokeColor", opIdx); - this.current.strokeColor = this.getColorN_Pattern(opIdx, args); - this.current.patternStroke = true; - } - setFillColorN(opIdx, ...args) { - this.dependencyTracker?.recordSimpleData("fillColor", opIdx); - this.current.fillColor = this.getColorN_Pattern(opIdx, args); - this.current.patternFill = true; - } - setStrokeRGBColor(opIdx, color) { - this.dependencyTracker?.recordSimpleData("strokeColor", opIdx); - this.ctx.strokeStyle = this.current.strokeColor = color; - this.current.patternStroke = false; + this.current.lineWidth = width; + this.ctx.lineWidth = width; } - setStrokeTransparent(opIdx) { - this.dependencyTracker?.recordSimpleData("strokeColor", opIdx); - this.ctx.strokeStyle = this.current.strokeColor = "transparent"; - this.current.patternStroke = false; + setLineCap(opIdx, style) { + this.dependencyTracker?.recordSimpleData("lineCap", opIdx); + this.ctx.lineCap = LINE_CAP_STYLES[style]; } - setFillRGBColor(opIdx, color) { - this.dependencyTracker?.recordSimpleData("fillColor", opIdx); - this.ctx.fillStyle = this.current.fillColor = color; - this.current.patternFill = false; + setLineJoin(opIdx, style) { + this.dependencyTracker?.recordSimpleData("lineJoin", opIdx); + this.ctx.lineJoin = LINE_JOIN_STYLES[style]; } - setFillTransparent(opIdx) { - this.dependencyTracker?.recordSimpleData("fillColor", opIdx); - this.ctx.fillStyle = this.current.fillColor = "transparent"; - this.current.patternFill = false; + setMiterLimit(opIdx, limit) { + this.dependencyTracker?.recordSimpleData("miterLimit", opIdx); + this.ctx.miterLimit = limit; } - _getPattern(opIdx, objId, matrix = null) { - let pattern; - if (this.cachedPatterns.has(objId)) { - pattern = this.cachedPatterns.get(objId); - } else { - pattern = getShadingPattern(this.getObject(opIdx, objId)); - this.cachedPatterns.set(objId, pattern); - } - if (matrix) { - pattern.matrix = matrix; + setDash(opIdx, dashArray, dashPhase) { + this.dependencyTracker?.recordSimpleData("dash", opIdx); + const ctx = this.ctx; + if (ctx.setLineDash !== undefined) { + ctx.setLineDash(dashArray); + ctx.lineDashOffset = dashPhase; } - return pattern; } - shadingFill(opIdx, objId) { - if (!this.contentVisible) { - return; - } - const ctx = this.ctx; - this.save(opIdx); - const pattern = this._getPattern(opIdx, objId); - ctx.fillStyle = pattern.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.SHADING, opIdx); - const inv = getCurrentTransformInverse(ctx); - if (inv) { - const { - width, - height - } = ctx.canvas; - const minMax = MIN_MAX_INIT.slice(); - Util.axialAlignedBoundingBox([0, 0, width, height], inv, minMax); - const [x0, y0, x1, y1] = minMax; - this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0); - } else { - this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); + setRenderingIntent(opIdx, intent) {} + setFlatness(opIdx, flatness) {} + setGState(opIdx, states) { + for (const [key, value] of states) { + switch (key) { + case "LW": + this.setLineWidth(opIdx, value); + break; + case "LC": + this.setLineCap(opIdx, value); + break; + case "LJ": + this.setLineJoin(opIdx, value); + break; + case "ML": + this.setMiterLimit(opIdx, value); + break; + case "D": + this.setDash(opIdx, value[0], value[1]); + break; + case "RI": + this.setRenderingIntent(opIdx, value); + break; + case "FL": + this.setFlatness(opIdx, value); + break; + case "Font": + this.setFont(opIdx, value[0], value[1]); + break; + case "CA": + this.dependencyTracker?.recordSimpleData("strokeAlpha", opIdx); + this.current.strokeAlpha = value; + break; + case "ca": + this.dependencyTracker?.recordSimpleData("fillAlpha", opIdx); + this.ctx.globalAlpha = this.current.fillAlpha = value; + break; + case "BM": + this.dependencyTracker?.recordSimpleData("globalCompositeOperation", opIdx); + this.ctx.globalCompositeOperation = value; + break; + case "SMask": + this.dependencyTracker?.recordSimpleData("SMask", opIdx); + this.current.activeSMask = value ? this.tempSMask : null; + this.tempSMask = null; + this.checkSMaskState(); + break; + case "TR": + this.dependencyTracker?.recordSimpleData("filter", opIdx); + this.ctx.filter = this.current.transferMaps = this.filterFactory.addFilter(value); + break; + } } - this.dependencyTracker?.resetBBox(opIdx).recordFullPageBBox(opIdx).recordDependencies(opIdx, Dependencies.transform).recordDependencies(opIdx, Dependencies.fill).recordOperation(opIdx); - this.compose(this.current.getClippedPathBoundingBox()); - this.restore(opIdx); - } - beginInlineImage() { - unreachable("Should not call beginInlineImage"); - } - beginImageData() { - unreachable("Should not call beginImageData"); } - paintFormXObjectBegin(opIdx, matrix, bbox) { - if (!this.contentVisible) { - return; - } - this.save(opIdx); - this.baseTransformStack.push(this.baseTransform); - if (matrix) { - this.transform(opIdx, ...matrix); - } - this.baseTransform = getCurrentTransform(this.ctx); - if (bbox) { - Util.axialAlignedBoundingBox(bbox, this.baseTransform, this.current.minMax); - const [x0, y0, x1, y1] = bbox; - const clip = new Path2D(); - clip.rect(x0, y0, x1 - x0, y1 - y0); - this.ctx.clip(clip); - this.dependencyTracker?.recordClipBox(opIdx, this.ctx, x0, x1, y0, y1); - this.endPath(opIdx); - } + get inSMaskMode() { + return !!this.suspendedCtx; } - paintFormXObjectEnd(opIdx) { - if (!this.contentVisible) { - return; + checkSMaskState() { + const inSMaskMode = this.inSMaskMode; + if (this.current.activeSMask && !inSMaskMode) { + this.beginSMaskMode(); + } else if (!this.current.activeSMask && inSMaskMode) { + this.endSMaskMode(); } - this.restore(opIdx); - this.baseTransform = this.baseTransformStack.pop(); } - beginGroup(opIdx, group) { - if (!this.contentVisible) { - return; - } - this.save(opIdx); + beginSMaskMode(opIdx) { if (this.inSMaskMode) { - this.endSMaskMode(); - this.current.activeSMask = null; - } - const currentCtx = this.ctx; - if (!group.isolated) { - info("TODO: Support non-isolated groups."); - } - if (group.knockout) { - warn("Knockout groups not supported."); - } - const currentTransform = getCurrentTransform(currentCtx); - if (group.matrix) { - currentCtx.transform(...group.matrix); - } - if (!group.bbox) { - throw new Error("Bounding box is required."); - } - let bounds = MIN_MAX_INIT.slice(); - Util.axialAlignedBoundingBox(group.bbox, getCurrentTransform(currentCtx), bounds); - const canvasBounds = [0, 0, currentCtx.canvas.width, currentCtx.canvas.height]; - bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0]; - const offsetX = Math.floor(bounds[0]); - const offsetY = Math.floor(bounds[1]); - const drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1); - const drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1); - this.current.startNewPathAndClipBox([0, 0, drawnWidth, drawnHeight]); - let cacheId = "groupAt" + this.groupLevel; - if (group.smask) { - cacheId += "_smask_" + this.smaskCounter++ % 2; + throw new Error("beginSMaskMode called while already in smask mode"); } + const drawnWidth = this.ctx.canvas.width; + const drawnHeight = this.ctx.canvas.height; + const cacheId = "smaskGroupAt" + this.groupLevel; const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight); - const groupCtx = scratchCanvas.context; - groupCtx.translate(-offsetX, -offsetY); - groupCtx.transform(...currentTransform); - let clip = new Path2D(); - const [x0, y0, x1, y1] = group.bbox; - clip.rect(x0, y0, x1 - x0, y1 - y0); - if (group.matrix) { - const path = new Path2D(); - path.addPath(clip, new DOMMatrix(group.matrix)); - clip = path; - } - groupCtx.clip(clip); - if (group.smask) { - this.smaskStack.push({ - canvas: scratchCanvas.canvas, - context: groupCtx, - offsetX, - offsetY, - subtype: group.smask.subtype, - backdrop: group.smask.backdrop, - transferMap: group.smask.transferMap || null, - startTransformInverse: null - }); - } - if (!group.smask || this.dependencyTracker) { - currentCtx.setTransform(1, 0, 0, 1, 0, 0); - currentCtx.translate(offsetX, offsetY); - currentCtx.save(); + this.suspendedCtx = this.ctx; + const ctx = this.ctx = scratchCanvas.context; + ctx.setTransform(this.suspendedCtx.getTransform()); + copyCtxState(this.suspendedCtx, ctx); + mirrorContextOperations(ctx, this.suspendedCtx); + this.setGState(opIdx, [["BM", "source-over"]]); + } + endSMaskMode() { + if (!this.inSMaskMode) { + throw new Error("endSMaskMode called while not in smask mode"); } - copyCtxState(currentCtx, groupCtx); - this.ctx = groupCtx; - this.dependencyTracker?.inheritSimpleDataAsFutureForcedDependencies(["fillAlpha", "strokeAlpha", "globalCompositeOperation"]).pushBaseTransform(currentCtx); - this.setGState(opIdx, [["BM", "source-over"], ["ca", 1], ["CA", 1]]); - this.groupStack.push(currentCtx); - this.groupLevel++; + this.ctx._removeMirroring(); + copyCtxState(this.ctx, this.suspendedCtx); + this.ctx = this.suspendedCtx; + this.suspendedCtx = null; } - endGroup(opIdx, group) { - if (!this.contentVisible) { + compose(dirtyBox) { + if (!this.current.activeSMask) { return; } - this.groupLevel--; - const groupCtx = this.ctx; - const ctx = this.groupStack.pop(); - this.ctx = ctx; - this.ctx.imageSmoothingEnabled = false; - this.dependencyTracker?.popBaseTransform(); - if (group.smask) { - this.tempSMask = this.smaskStack.pop(); - this.restore(opIdx); - if (this.dependencyTracker) { - this.ctx.restore(); - } + if (!dirtyBox) { + dirtyBox = [0, 0, this.ctx.canvas.width, this.ctx.canvas.height]; } else { - this.ctx.restore(); - const currentMtx = getCurrentTransform(this.ctx); - this.restore(opIdx); - this.ctx.save(); - this.ctx.setTransform(...currentMtx); - const dirtyBox = MIN_MAX_INIT.slice(); - Util.axialAlignedBoundingBox([0, 0, groupCtx.canvas.width, groupCtx.canvas.height], currentMtx, dirtyBox); - this.ctx.drawImage(groupCtx.canvas, 0, 0); - this.ctx.restore(); - this.compose(dirtyBox); + dirtyBox[0] = Math.floor(dirtyBox[0]); + dirtyBox[1] = Math.floor(dirtyBox[1]); + dirtyBox[2] = Math.ceil(dirtyBox[2]); + dirtyBox[3] = Math.ceil(dirtyBox[3]); } - } - beginAnnotation(opIdx, id, rect, transform, matrix, hasOwnCanvas) { - this.#restoreInitialState(); - resetCtxToDefault(this.ctx); + const smask = this.current.activeSMask; + const suspendedCtx = this.suspendedCtx; + this.composeSMask(suspendedCtx, smask, this.ctx, dirtyBox); this.ctx.save(); - this.save(opIdx); - if (this.baseTransform) { - this.ctx.setTransform(...this.baseTransform); + this.ctx.setTransform(1, 0, 0, 1, 0, 0); + this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + this.ctx.restore(); + } + composeSMask(ctx, smask, layerCtx, layerBox) { + const layerOffsetX = layerBox[0]; + const layerOffsetY = layerBox[1]; + const layerWidth = layerBox[2] - layerOffsetX; + const layerHeight = layerBox[3] - layerOffsetY; + if (layerWidth === 0 || layerHeight === 0) { + return; } - if (rect) { - const width = rect[2] - rect[0]; - const height = rect[3] - rect[1]; - if (hasOwnCanvas && this.annotationCanvasMap) { - transform = transform.slice(); - transform[4] -= rect[0]; - transform[5] -= rect[1]; - rect = rect.slice(); - rect[0] = rect[1] = 0; - rect[2] = width; - rect[3] = height; - Util.singularValueDecompose2dScale(getCurrentTransform(this.ctx), XY); - const { - viewportScale - } = this; - const canvasWidth = Math.ceil(width * this.outputScaleX * viewportScale); - const canvasHeight = Math.ceil(height * this.outputScaleY * viewportScale); - this.annotationCanvas = this.canvasFactory.create(canvasWidth, canvasHeight); - const { - canvas, - context - } = this.annotationCanvas; - this.annotationCanvasMap.set(id, canvas); - this.annotationCanvas.savedCtx = this.ctx; - this.ctx = context; - this.ctx.save(); - this.ctx.setTransform(XY[0], 0, 0, -XY[1], 0, height * XY[1]); - resetCtxToDefault(this.ctx); + this.genericComposeSMask(smask.context, layerCtx, layerWidth, layerHeight, smask.subtype, smask.backdrop, smask.transferMap, layerOffsetX, layerOffsetY, smask.offsetX, smask.offsetY); + ctx.save(); + ctx.globalAlpha = 1; + ctx.globalCompositeOperation = "source-over"; + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.drawImage(layerCtx.canvas, 0, 0); + ctx.restore(); + } + genericComposeSMask(maskCtx, layerCtx, width, height, subtype, backdrop, transferMap, layerOffsetX, layerOffsetY, maskOffsetX, maskOffsetY) { + let maskCanvas = maskCtx.canvas; + let maskX = layerOffsetX - maskOffsetX; + let maskY = layerOffsetY - maskOffsetY; + if (backdrop) { + if (maskX < 0 || maskY < 0 || maskX + width > maskCanvas.width || maskY + height > maskCanvas.height) { + const canvas = this.cachedCanvases.getCanvas("maskExtension", width, height); + const ctx = canvas.context; + ctx.drawImage(maskCanvas, -maskX, -maskY); + ctx.globalCompositeOperation = "destination-atop"; + ctx.fillStyle = backdrop; + ctx.fillRect(0, 0, width, height); + ctx.globalCompositeOperation = "source-over"; + maskCanvas = canvas.canvas; + maskX = maskY = 0; } else { - resetCtxToDefault(this.ctx); - this.endPath(opIdx); + maskCtx.save(); + maskCtx.globalAlpha = 1; + maskCtx.setTransform(1, 0, 0, 1, 0, 0); const clip = new Path2D(); - clip.rect(rect[0], rect[1], width, height); - this.ctx.clip(clip); + clip.rect(maskX, maskY, width, height); + maskCtx.clip(clip); + maskCtx.globalCompositeOperation = "destination-atop"; + maskCtx.fillStyle = backdrop; + maskCtx.fillRect(maskX, maskY, width, height); + maskCtx.restore(); } } - this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height); - this.transform(opIdx, ...transform); - this.transform(opIdx, ...matrix); + layerCtx.save(); + layerCtx.globalAlpha = 1; + layerCtx.setTransform(1, 0, 0, 1, 0, 0); + if (subtype === "Alpha" && transferMap) { + layerCtx.filter = this.filterFactory.addAlphaFilter(transferMap); + } else if (subtype === "Luminosity") { + layerCtx.filter = this.filterFactory.addLuminosityFilter(transferMap); + } + const clip = new Path2D(); + clip.rect(layerOffsetX, layerOffsetY, width, height); + layerCtx.clip(clip); + layerCtx.globalCompositeOperation = "destination-in"; + layerCtx.drawImage(maskCanvas, maskX, maskY, width, height, layerOffsetX, layerOffsetY, width, height); + layerCtx.restore(); } - endAnnotation(opIdx) { - if (this.annotationCanvas) { - this.ctx.restore(); - this.#drawFilter(); - this.ctx = this.annotationCanvas.savedCtx; - delete this.annotationCanvas.savedCtx; - delete this.annotationCanvas; + save(opIdx) { + if (this.inSMaskMode) { + copyCtxState(this.ctx, this.suspendedCtx); } + this.ctx.save(); + const old = this.current; + this.stateStack.push(old); + this.current = old.clone(); + this.dependencyTracker?.save(opIdx); } - paintImageMaskXObject(opIdx, img) { - if (!this.contentVisible) { + restore(opIdx) { + this.dependencyTracker?.restore(opIdx); + if (this.stateStack.length === 0) { + if (this.inSMaskMode) { + this.endSMaskMode(); + } return; } - const count = img.count; - img = this.getObject(opIdx, img.data, img); - img.count = count; - const ctx = this.ctx; - const mask = this._createMaskCanvas(opIdx, img); - const maskCanvas = mask.canvas; - ctx.save(); - ctx.setTransform(1, 0, 0, 1, 0, 0); - ctx.drawImage(maskCanvas, mask.offsetX, mask.offsetY); - this.dependencyTracker?.resetBBox(opIdx).recordBBox(opIdx, this.ctx, mask.offsetX, mask.offsetX + maskCanvas.width, mask.offsetY, mask.offsetY + maskCanvas.height).recordOperation(opIdx); - ctx.restore(); - this.compose(); + this.current = this.stateStack.pop(); + this.ctx.restore(); + if (this.inSMaskMode) { + copyCtxState(this.suspendedCtx, this.ctx); + } + this.checkSMaskState(); + this.pendingClip = null; + this._cachedScaleForStroking[0] = -1; + this._cachedGetSinglePixelWidth = null; } - paintImageMaskXObjectRepeat(opIdx, img, scaleX, skewX = 0, skewY = 0, scaleY, positions) { - if (!this.contentVisible) { + transform(opIdx, a, b, c, d, e, f) { + this.dependencyTracker?.recordIncrementalData("transform", opIdx); + this.ctx.transform(a, b, c, d, e, f); + this._cachedScaleForStroking[0] = -1; + this._cachedGetSinglePixelWidth = null; + } + constructPath(opIdx, op, data, minMax) { + let [path] = data; + if (!minMax) { + path ||= data[0] = new Path2D(); + this[op](opIdx, path); return; } - img = this.getObject(opIdx, img.data, img); - const ctx = this.ctx; - ctx.save(); - const currentTransform = getCurrentTransform(ctx); - ctx.transform(scaleX, skewX, skewY, scaleY, 0, 0); - const mask = this._createMaskCanvas(opIdx, img); - ctx.setTransform(1, 0, 0, 1, mask.offsetX - currentTransform[4], mask.offsetY - currentTransform[5]); - this.dependencyTracker?.resetBBox(opIdx); - for (let i = 0, ii = positions.length; i < ii; i += 2) { - const trans = Util.transform(currentTransform, [scaleX, skewX, skewY, scaleY, positions[i], positions[i + 1]]); - ctx.drawImage(mask.canvas, trans[4], trans[5]); - this.dependencyTracker?.recordBBox(opIdx, this.ctx, trans[4], trans[4] + mask.canvas.width, trans[5], trans[5] + mask.canvas.height); + if (this.dependencyTracker !== null) { + const outerExtraSize = op === OPS.stroke ? this.current.lineWidth / 2 : 0; + this.dependencyTracker.resetBBox(opIdx).recordBBox(opIdx, this.ctx, minMax[0] - outerExtraSize, minMax[2] + outerExtraSize, minMax[1] - outerExtraSize, minMax[3] + outerExtraSize).recordDependencies(opIdx, ["transform"]); } - ctx.restore(); - this.compose(); - this.dependencyTracker?.recordOperation(opIdx); + if (!(path instanceof Path2D)) { + path = data[0] = makePathFromDrawOPS(path); + } + Util.axialAlignedBoundingBox(minMax, getCurrentTransform(this.ctx), this.current.minMax); + this[op](opIdx, path); + this._pathStartIdx = opIdx; } - paintImageMaskXObjectGroup(opIdx, images) { - if (!this.contentVisible) { - return; + closePath(opIdx) { + this.ctx.closePath(); + } + stroke(opIdx, path, consumePath = true) { + const ctx = this.ctx; + const strokeColor = this.current.strokeColor; + ctx.globalAlpha = this.current.strokeAlpha; + if (this.contentVisible) { + if (typeof strokeColor === "object" && strokeColor?.getPattern) { + const baseTransform = strokeColor.isModifyingCurrentTransform() ? ctx.getTransform() : null; + ctx.save(); + ctx.strokeStyle = strokeColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.STROKE, opIdx); + if (baseTransform) { + const newPath = new Path2D(); + newPath.addPath(path, ctx.getTransform().invertSelf().multiplySelf(baseTransform)); + path = newPath; + } + this.rescaleAndStroke(path, false); + ctx.restore(); + } else { + this.rescaleAndStroke(path, true); + } + } + this.dependencyTracker?.recordDependencies(opIdx, Dependencies.stroke); + if (consumePath) { + this.consumePath(opIdx, path, this.current.getClippedPathBoundingBox(PathType.STROKE, getCurrentTransform(this.ctx))); } + ctx.globalAlpha = this.current.fillAlpha; + } + closeStroke(opIdx, path) { + this.stroke(opIdx, path); + } + fill(opIdx, path, consumePath = true) { const ctx = this.ctx; const fillColor = this.current.fillColor; const isPatternFill = this.current.patternFill; - this.dependencyTracker?.resetBBox(opIdx).recordDependencies(opIdx, Dependencies.transformAndFill); - for (const image of images) { - const { - data, - width, - height, - transform - } = image; - const maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height); - const maskCtx = maskCanvas.context; - maskCtx.save(); - const img = this.getObject(opIdx, data, image); - putBinaryImageMask(maskCtx, img); - maskCtx.globalCompositeOperation = "source-in"; - maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this, getCurrentTransformInverse(ctx), PathType.FILL, opIdx) : fillColor; - maskCtx.fillRect(0, 0, width, height); - maskCtx.restore(); + let needRestore = false; + if (isPatternFill) { + const baseTransform = fillColor.isModifyingCurrentTransform() ? ctx.getTransform() : null; + this.dependencyTracker?.save(opIdx); ctx.save(); - ctx.transform(...transform); - ctx.scale(1, -1); - drawImageAtIntegerCoords(ctx, maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1); - this.dependencyTracker?.recordBBox(opIdx, ctx, 0, width, 0, height); + ctx.fillStyle = fillColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.FILL, opIdx); + if (baseTransform) { + const newPath = new Path2D(); + newPath.addPath(path, ctx.getTransform().invertSelf().multiplySelf(baseTransform)); + path = newPath; + } + needRestore = true; + } + const intersect = this.current.getClippedPathBoundingBox(); + if (this.contentVisible && intersect !== null) { + if (this.pendingEOFill) { + ctx.fill(path, "evenodd"); + this.pendingEOFill = false; + } else { + ctx.fill(path); + } + } + this.dependencyTracker?.recordDependencies(opIdx, Dependencies.fill); + if (needRestore) { ctx.restore(); + this.dependencyTracker?.restore(opIdx); } - this.compose(); - this.dependencyTracker?.recordOperation(opIdx); + if (consumePath) { + this.consumePath(opIdx, path, intersect); + } + } + eoFill(opIdx, path) { + this.pendingEOFill = true; + this.fill(opIdx, path); + } + fillStroke(opIdx, path) { + this.fill(opIdx, path, false); + this.stroke(opIdx, path, false); + this.consumePath(opIdx, path); + } + eoFillStroke(opIdx, path) { + this.pendingEOFill = true; + this.fillStroke(opIdx, path); + } + closeFillStroke(opIdx, path) { + this.fillStroke(opIdx, path); + } + closeEOFillStroke(opIdx, path) { + this.pendingEOFill = true; + this.fillStroke(opIdx, path); + } + endPath(opIdx, path) { + this.consumePath(opIdx, path); + } + rawFillPath(opIdx, path) { + this.ctx.fill(path); + this.dependencyTracker?.recordDependencies(opIdx, Dependencies.rawFillPath).recordOperation(opIdx); + } + clip(opIdx) { + this.dependencyTracker?.recordFutureForcedDependency("clipMode", opIdx); + this.pendingClip = NORMAL_CLIP; + } + eoClip(opIdx) { + this.dependencyTracker?.recordFutureForcedDependency("clipMode", opIdx); + this.pendingClip = EO_CLIP; + } + beginText(opIdx) { + this.current.textMatrix = null; + this.current.textMatrixScale = 1; + this.current.x = this.current.lineX = 0; + this.current.y = this.current.lineY = 0; + this.dependencyTracker?.recordOpenMarker(opIdx).resetIncrementalData("sameLineText").resetIncrementalData("moveText", opIdx); + } + endText(opIdx) { + const paths = this.pendingTextPaths; + const ctx = this.ctx; + if (this.dependencyTracker) { + const { + dependencyTracker + } = this; + if (paths !== undefined) { + dependencyTracker.recordFutureForcedDependency("textClip", dependencyTracker.getOpenMarker()).recordFutureForcedDependency("textClip", opIdx); + } + dependencyTracker.recordCloseMarker(opIdx); + } + if (paths !== undefined) { + const newPath = new Path2D(); + const invTransf = ctx.getTransform().invertSelf(); + for (const { + transform, + x, + y, + fontSize, + path + } of paths) { + if (!path) { + continue; + } + newPath.addPath(path, new DOMMatrix(transform).preMultiplySelf(invTransf).translate(x, y).scale(fontSize, -fontSize)); + } + ctx.clip(newPath); + } + delete this.pendingTextPaths; } - paintImageXObject(opIdx, objId) { - if (!this.contentVisible) { - return; + setCharSpacing(opIdx, spacing) { + this.dependencyTracker?.recordSimpleData("charSpacing", opIdx); + this.current.charSpacing = spacing; + } + setWordSpacing(opIdx, spacing) { + this.dependencyTracker?.recordSimpleData("wordSpacing", opIdx); + this.current.wordSpacing = spacing; + } + setHScale(opIdx, scale) { + this.dependencyTracker?.recordSimpleData("hScale", opIdx); + this.current.textHScale = scale / 100; + } + setLeading(opIdx, leading) { + this.dependencyTracker?.recordSimpleData("leading", opIdx); + this.current.leading = -leading; + } + setFont(opIdx, fontRefName, size) { + this.dependencyTracker?.recordSimpleData("font", opIdx).recordSimpleDataFromNamed("fontObj", fontRefName, opIdx); + const fontObj = this.commonObjs.get(fontRefName); + const current = this.current; + if (!fontObj) { + throw new Error(`Can't find font for ${fontRefName}`); } - const imgData = this.getObject(opIdx, objId); - if (!imgData) { - warn("Dependent image isn't ready yet"); - return; + current.fontMatrix = fontObj.fontMatrix || FONT_IDENTITY_MATRIX; + if (current.fontMatrix[0] === 0 || current.fontMatrix[3] === 0) { + warn("Invalid font matrix for font " + fontRefName); } - this.paintInlineImageXObject(opIdx, imgData); - } - paintImageXObjectRepeat(opIdx, objId, scaleX, scaleY, positions) { - if (!this.contentVisible) { - return; + if (size < 0) { + size = -size; + current.fontDirection = -1; + } else { + current.fontDirection = 1; } - const imgData = this.getObject(opIdx, objId); - if (!imgData) { - warn("Dependent image isn't ready yet"); + this.current.font = fontObj; + this.current.fontSize = size; + if (fontObj.isType3Font) { return; } - const width = imgData.width; - const height = imgData.height; - const map = []; - for (let i = 0, ii = positions.length; i < ii; i += 2) { - map.push({ - transform: [scaleX, 0, 0, scaleY, positions[i], positions[i + 1]], - x: 0, - y: 0, - w: width, - h: height - }); + const name = fontObj.loadedName || "sans-serif"; + const typeface = fontObj.systemFontInfo?.css || `"${name}", ${fontObj.fallbackName}`; + let bold = "normal"; + if (fontObj.black) { + bold = "900"; + } else if (fontObj.bold) { + bold = "bold"; } - this.paintInlineImageXObjectGroup(opIdx, imgData, map); - } - applyTransferMapsToCanvas(ctx) { - if (this.current.transferMaps !== "none") { - ctx.filter = this.current.transferMaps; - ctx.drawImage(ctx.canvas, 0, 0); - ctx.filter = "none"; + const italic = fontObj.italic ? "italic" : "normal"; + let browserFontSize = size; + if (size < MIN_FONT_SIZE) { + browserFontSize = MIN_FONT_SIZE; + } else if (size > MAX_FONT_SIZE) { + browserFontSize = MAX_FONT_SIZE; } - return ctx.canvas; + this.current.fontSizeScale = size / browserFontSize; + this.ctx.font = `${italic} ${bold} ${browserFontSize}px ${typeface}`; } - applyTransferMapsToBitmap(imgData) { - if (this.current.transferMaps === "none") { - return imgData.bitmap; - } + setTextRenderingMode(opIdx, mode) { + this.dependencyTracker?.recordSimpleData("textRenderingMode", opIdx); + this.current.textRenderingMode = mode; + } + setTextRise(opIdx, rise) { + this.dependencyTracker?.recordSimpleData("textRise", opIdx); + this.current.textRise = rise; + } + moveText(opIdx, x, y) { + this.dependencyTracker?.resetIncrementalData("sameLineText").recordIncrementalData("moveText", opIdx); + this.current.x = this.current.lineX += x; + this.current.y = this.current.lineY += y; + } + setLeadingMoveText(opIdx, x, y) { + this.setLeading(opIdx, -y); + this.moveText(opIdx, x, y); + } + setTextMatrix(opIdx, matrix) { + this.dependencyTracker?.resetIncrementalData("sameLineText").recordSimpleData("textMatrix", opIdx); const { - bitmap, - width, - height - } = imgData; - const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height); - const tmpCtx = tmpCanvas.context; - tmpCtx.filter = this.current.transferMaps; - tmpCtx.drawImage(bitmap, 0, 0); - tmpCtx.filter = "none"; - return tmpCanvas.canvas; + current + } = this; + current.textMatrix = matrix; + current.textMatrixScale = Math.hypot(matrix[0], matrix[1]); + current.x = current.lineX = 0; + current.y = current.lineY = 0; } - paintInlineImageXObject(opIdx, imgData) { - if (!this.contentVisible) { - return; - } - const width = imgData.width; - const height = imgData.height; + nextLine(opIdx) { + this.moveText(opIdx, 0, this.current.leading); + this.dependencyTracker?.recordIncrementalData("moveText", this.dependencyTracker.getSimpleIndex("leading") ?? opIdx); + } + #getScaledPath(path, currentTransform, transform) { + const newPath = new Path2D(); + newPath.addPath(path, new DOMMatrix(transform).invertSelf().multiplySelf(currentTransform)); + return newPath; + } + paintChar(opIdx, character, x, y, patternFillTransform, patternStrokeTransform) { const ctx = this.ctx; - this.save(opIdx); - const { - filter - } = ctx; - if (filter !== "none" && filter !== "") { - ctx.filter = "none"; + const current = this.current; + const font = current.font; + const textRenderingMode = current.textRenderingMode; + const fontSize = current.fontSize / current.fontSizeScale; + const fillStrokeMode = textRenderingMode & TextRenderingMode.FILL_STROKE_MASK; + const isAddToPathSet = !!(textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG); + const patternFill = current.patternFill && !font.missingFile; + const patternStroke = current.patternStroke && !font.missingFile; + let path; + if ((font.disableFontFace || isAddToPathSet || patternFill || patternStroke) && !font.missingFile) { + path = font.getPathGenerator(this.commonObjs, character); } - ctx.scale(1 / width, -1 / height); - let imgToPaint; - if (imgData.bitmap) { - imgToPaint = this.applyTransferMapsToBitmap(imgData); - } else if (typeof HTMLElement === "function" && imgData instanceof HTMLElement || !imgData.data) { - imgToPaint = imgData; + if (path && (font.disableFontFace || patternFill || patternStroke)) { + ctx.save(); + ctx.translate(x, y); + ctx.scale(fontSize, -fontSize); + this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, font); + let currentTransform; + if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) { + if (patternFillTransform) { + currentTransform = ctx.getTransform(); + ctx.setTransform(...patternFillTransform); + const scaledPath = this.#getScaledPath(path, currentTransform, patternFillTransform); + ctx.fill(scaledPath); + } else { + ctx.fill(path); + } + } + if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) { + if (patternStrokeTransform) { + currentTransform ||= ctx.getTransform(); + ctx.setTransform(...patternStrokeTransform); + const { + a, + b, + c, + d + } = currentTransform; + const invPatternTransform = Util.inverseTransform(patternStrokeTransform); + const transf = Util.transform([a, b, c, d, 0, 0], invPatternTransform); + Util.singularValueDecompose2dScale(transf, XY); + ctx.lineWidth *= Math.max(XY[0], XY[1]) / fontSize; + ctx.stroke(this.#getScaledPath(path, currentTransform, patternStrokeTransform)); + } else { + ctx.lineWidth /= fontSize; + ctx.stroke(path); + } + } + ctx.restore(); } else { - const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height); - const tmpCtx = tmpCanvas.context; - putBinaryImageData(tmpCtx, imgData); - imgToPaint = this.applyTransferMapsToCanvas(tmpCtx); + if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) { + ctx.fillText(character, x, y); + this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, font, fontSize, x, y, () => ctx.measureText(character)); + } + if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) { + if (this.dependencyTracker) { + this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, font, fontSize, x, y, () => ctx.measureText(character)).recordDependencies(opIdx, Dependencies.stroke); + } + ctx.strokeText(character, x, y); + } + } + if (isAddToPathSet) { + const paths = this.pendingTextPaths ||= []; + paths.push({ + transform: getCurrentTransform(ctx), + x, + y, + fontSize, + path + }); + this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, font, fontSize, x, y); } - const scaled = this._scaleImage(imgToPaint, getCurrentTransformInverse(ctx)); - ctx.imageSmoothingEnabled = getImageSmoothingEnabled(getCurrentTransform(ctx), imgData.interpolate); - this.dependencyTracker?.resetBBox(opIdx).recordBBox(opIdx, ctx, 0, width, -height, 0).recordDependencies(opIdx, Dependencies.imageXObject).recordOperation(opIdx); - drawImageAtIntegerCoords(ctx, scaled.img, 0, 0, scaled.paintWidth, scaled.paintHeight, 0, -height, width, height); - this.compose(); - this.restore(opIdx); } - paintInlineImageXObjectGroup(opIdx, imgData, map) { - if (!this.contentVisible) { - return; + get isFontSubpixelAAEnabled() { + const { + context: ctx + } = this.cachedCanvases.getCanvas("isFontSubpixelAAEnabled", 10, 10); + ctx.scale(1.5, 1); + ctx.fillText("I", 0, 10); + const data = ctx.getImageData(0, 0, 10, 10).data; + let enabled = false; + for (let i = 3; i < data.length; i += 4) { + if (data[i] > 0 && data[i] < 255) { + enabled = true; + break; + } + } + return shadow(this, "isFontSubpixelAAEnabled", enabled); + } + showText(opIdx, glyphs) { + if (this.dependencyTracker) { + this.dependencyTracker.recordDependencies(opIdx, Dependencies.showText).resetBBox(opIdx); + if (this.current.textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG) { + this.dependencyTracker.recordFutureForcedDependency("textClip", opIdx).inheritPendingDependenciesAsFutureForcedDependencies(); + } + } + const current = this.current; + const font = current.font; + if (font.isType3Font) { + this.showType3Text(opIdx, glyphs); + this.dependencyTracker?.recordShowTextOperation(opIdx); + return undefined; + } + const fontSize = current.fontSize; + if (fontSize === 0) { + this.dependencyTracker?.recordOperation(opIdx); + return undefined; } const ctx = this.ctx; - let imgToPaint; - if (imgData.bitmap) { - imgToPaint = imgData.bitmap; + const fontSizeScale = current.fontSizeScale; + const charSpacing = current.charSpacing; + const wordSpacing = current.wordSpacing; + const fontDirection = current.fontDirection; + const textHScale = current.textHScale * fontDirection; + const glyphsLength = glyphs.length; + const vertical = font.vertical; + const spacingDir = vertical ? 1 : -1; + const defaultVMetrics = font.defaultVMetrics; + const widthAdvanceScale = fontSize * current.fontMatrix[0]; + const simpleFillText = current.textRenderingMode === TextRenderingMode.FILL && !font.disableFontFace && !current.patternFill; + ctx.save(); + if (current.textMatrix) { + ctx.transform(...current.textMatrix); + } + ctx.translate(current.x, current.y + current.textRise); + if (fontDirection > 0) { + ctx.scale(textHScale, -1); } else { - const w = imgData.width; - const h = imgData.height; - const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h); - const tmpCtx = tmpCanvas.context; - putBinaryImageData(tmpCtx, imgData); - imgToPaint = this.applyTransferMapsToCanvas(tmpCtx); + ctx.scale(textHScale, 1); } - this.dependencyTracker?.resetBBox(opIdx); - for (const entry of map) { + let patternFillTransform, patternStrokeTransform; + if (current.patternFill) { ctx.save(); - ctx.transform(...entry.transform); - ctx.scale(1, -1); - drawImageAtIntegerCoords(ctx, imgToPaint, entry.x, entry.y, entry.w, entry.h, 0, -1, 1, 1); - this.dependencyTracker?.recordBBox(opIdx, ctx, 0, 1, -1, 0); + const pattern = current.fillColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.FILL, opIdx); + patternFillTransform = getCurrentTransform(ctx); ctx.restore(); + ctx.fillStyle = pattern; } - this.dependencyTracker?.recordOperation(opIdx); - this.compose(); - } - paintSolidColorImageMask(opIdx) { - if (!this.contentVisible) { - return; + if (current.patternStroke) { + ctx.save(); + const pattern = current.strokeColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.STROKE, opIdx); + patternStrokeTransform = getCurrentTransform(ctx); + ctx.restore(); + ctx.strokeStyle = pattern; } - this.dependencyTracker?.resetBBox(opIdx).recordBBox(opIdx, this.ctx, 0, 1, 0, 1).recordDependencies(opIdx, Dependencies.fill).recordOperation(opIdx); - this.ctx.fillRect(0, 0, 1, 1); - this.compose(); - } - markPoint(opIdx, tag) {} - markPointProps(opIdx, tag, properties) {} - beginMarkedContent(opIdx, tag) { - this.dependencyTracker?.beginMarkedContent(opIdx); - this.markedContentStack.push({ - visible: true - }); - } - beginMarkedContentProps(opIdx, tag, properties) { - this.dependencyTracker?.beginMarkedContent(opIdx); - if (tag === "OC") { - this.markedContentStack.push({ - visible: this.optionalContentConfig.isVisible(properties) - }); + let lineWidth = current.lineWidth; + const scale = current.textMatrixScale; + if (scale === 0 || lineWidth === 0) { + const fillStrokeMode = current.textRenderingMode & TextRenderingMode.FILL_STROKE_MASK; + if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) { + lineWidth = this.getSinglePixelWidth(); + } } else { - this.markedContentStack.push({ - visible: true - }); - } - this.contentVisible = this.isContentVisible(); - } - endMarkedContent(opIdx) { - this.dependencyTracker?.endMarkedContent(opIdx); - this.markedContentStack.pop(); - this.contentVisible = this.isContentVisible(); - } - beginCompat(opIdx) {} - endCompat(opIdx) {} - consumePath(opIdx, path, clipBox) { - const isEmpty = this.current.isEmptyClip(); - if (this.pendingClip) { - this.current.updateClipFromPath(); + lineWidth /= scale; } - if (!this.pendingClip) { - this.compose(clipBox); + if (fontSizeScale !== 1.0) { + ctx.scale(fontSizeScale, fontSizeScale); + lineWidth /= fontSizeScale; } - const ctx = this.ctx; - if (this.pendingClip) { - if (!isEmpty) { - if (this.pendingClip === EO_CLIP) { - ctx.clip(path, "evenodd"); - } else { - ctx.clip(path); - } + ctx.lineWidth = lineWidth; + if (font.isInvalidPDFjsFont) { + const chars = []; + let width = 0; + for (const glyph of glyphs) { + chars.push(glyph.unicode); + width += glyph.width; } - this.pendingClip = null; - this.dependencyTracker?.bboxToClipBoxDropOperation(opIdx).recordFutureForcedDependency("clipPath", opIdx); - } else { - this.dependencyTracker?.recordOperation(opIdx); + const joinedChars = chars.join(""); + ctx.fillText(joinedChars, 0, 0); + if (this.dependencyTracker !== null) { + const measure = ctx.measureText(joinedChars); + this.dependencyTracker.recordBBox(opIdx, this.ctx, -measure.actualBoundingBoxLeft, measure.actualBoundingBoxRight, -measure.actualBoundingBoxAscent, measure.actualBoundingBoxDescent).recordShowTextOperation(opIdx); + } + current.x += width * widthAdvanceScale * textHScale; + ctx.restore(); + this.compose(); + return undefined; } - this.current.startNewPathAndClipBox(this.current.clipBox); - } - getSinglePixelWidth() { - if (!this._cachedGetSinglePixelWidth) { - const m = getCurrentTransform(this.ctx); - if (m[1] === 0 && m[2] === 0) { - this._cachedGetSinglePixelWidth = 1 / Math.min(Math.abs(m[0]), Math.abs(m[3])); + let x = 0, + i; + for (i = 0; i < glyphsLength; ++i) { + const glyph = glyphs[i]; + if (typeof glyph === "number") { + x += spacingDir * glyph * fontSize / 1000; + continue; + } + let restoreNeeded = false; + const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing; + const character = glyph.fontChar; + const accent = glyph.accent; + let scaledX, scaledY; + let width = glyph.width; + if (vertical) { + const vmetric = glyph.vmetric || defaultVMetrics; + const vx = -(glyph.vmetric ? vmetric[1] : width * 0.5) * widthAdvanceScale; + const vy = vmetric[2] * widthAdvanceScale; + width = vmetric ? -vmetric[0] : width; + scaledX = vx / fontSizeScale; + scaledY = (x + vy) / fontSizeScale; } else { - const absDet = Math.abs(m[0] * m[3] - m[2] * m[1]); - const normX = Math.hypot(m[0], m[2]); - const normY = Math.hypot(m[1], m[3]); - this._cachedGetSinglePixelWidth = Math.max(normX, normY) / absDet; + scaledX = x / fontSizeScale; + scaledY = 0; } - } - return this._cachedGetSinglePixelWidth; - } - getScaleForStroking() { - if (this._cachedScaleForStroking[0] === -1) { - const { - lineWidth - } = this.current; - const { - a, - b, - c, - d - } = this.ctx.getTransform(); - let scaleX, scaleY; - if (b === 0 && c === 0) { - const normX = Math.abs(a); - const normY = Math.abs(d); - if (normX === normY) { - if (lineWidth === 0) { - scaleX = scaleY = 1 / normX; - } else { - const scaledLineWidth = normX * lineWidth; - scaleX = scaleY = scaledLineWidth < 1 ? 1 / scaledLineWidth : 1; - } - } else if (lineWidth === 0) { - scaleX = 1 / normX; - scaleY = 1 / normY; - } else { - const scaledXLineWidth = normX * lineWidth; - const scaledYLineWidth = normY * lineWidth; - scaleX = scaledXLineWidth < 1 ? 1 / scaledXLineWidth : 1; - scaleY = scaledYLineWidth < 1 ? 1 / scaledYLineWidth : 1; + let measure; + if (font.remeasure && width > 0) { + measure = ctx.measureText(character); + const measuredWidth = measure.width * 1000 / fontSize * fontSizeScale; + if (width < measuredWidth && this.isFontSubpixelAAEnabled) { + const characterScaleX = width / measuredWidth; + restoreNeeded = true; + ctx.save(); + ctx.scale(characterScaleX, 1); + scaledX /= characterScaleX; + } else if (width !== measuredWidth) { + scaledX += (width - measuredWidth) / 2000 * fontSize / fontSizeScale; } - } else { - const absDet = Math.abs(a * d - b * c); - const normX = Math.hypot(a, b); - const normY = Math.hypot(c, d); - if (lineWidth === 0) { - scaleX = normY / absDet; - scaleY = normX / absDet; + } + if (this.contentVisible && (glyph.isInFont || font.missingFile)) { + if (simpleFillText && !accent) { + ctx.fillText(character, scaledX, scaledY); + this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, measure ? { + bbox: null + } : font, fontSize / fontSizeScale, scaledX, scaledY, () => measure ?? ctx.measureText(character)); } else { - const baseArea = lineWidth * absDet; - scaleX = normY > baseArea ? normY / baseArea : 1; - scaleY = normX > baseArea ? normX / baseArea : 1; + this.paintChar(opIdx, character, scaledX, scaledY, patternFillTransform, patternStrokeTransform); + if (accent) { + const scaledAccentX = scaledX + fontSize * accent.offset.x / fontSizeScale; + const scaledAccentY = scaledY - fontSize * accent.offset.y / fontSizeScale; + this.paintChar(opIdx, accent.fontChar, scaledAccentX, scaledAccentY, patternFillTransform, patternStrokeTransform); + } } } - this._cachedScaleForStroking[0] = scaleX; - this._cachedScaleForStroking[1] = scaleY; - } - return this._cachedScaleForStroking; - } - rescaleAndStroke(path, saveRestore) { - const { - ctx, - current: { - lineWidth - } - } = this; - const [scaleX, scaleY] = this.getScaleForStroking(); - if (scaleX === scaleY) { - ctx.lineWidth = (lineWidth || 1) * scaleX; - ctx.stroke(path); - return; + const charWidth = vertical ? width * widthAdvanceScale - spacing * fontDirection : width * widthAdvanceScale + spacing * fontDirection; + x += charWidth; + if (restoreNeeded) { + ctx.restore(); + } } - const dashes = ctx.getLineDash(); - if (saveRestore) { - ctx.save(); + if (vertical) { + current.y -= x; + } else { + current.x += x * textHScale; } - ctx.scale(scaleX, scaleY); - SCALE_MATRIX.a = 1 / scaleX; - SCALE_MATRIX.d = 1 / scaleY; - const newPath = new Path2D(); - newPath.addPath(path, SCALE_MATRIX); - if (dashes.length > 0) { - const scale = Math.max(scaleX, scaleY); - ctx.setLineDash(dashes.map(x => x / scale)); - ctx.lineDashOffset /= scale; + ctx.restore(); + this.compose(); + this.dependencyTracker?.recordShowTextOperation(opIdx); + return undefined; + } + showType3Text(opIdx, glyphs) { + const ctx = this.ctx; + const current = this.current; + const font = current.font; + const fontSize = current.fontSize; + const fontDirection = current.fontDirection; + const spacingDir = font.vertical ? 1 : -1; + const charSpacing = current.charSpacing; + const wordSpacing = current.wordSpacing; + const textHScale = current.textHScale * fontDirection; + const fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX; + const glyphsLength = glyphs.length; + const isTextInvisible = current.textRenderingMode === TextRenderingMode.INVISIBLE; + let i, glyph, width, spacingLength; + if (isTextInvisible || fontSize === 0) { + return; } - ctx.lineWidth = lineWidth || 1; - ctx.stroke(newPath); - if (saveRestore) { - ctx.restore(); + this._cachedScaleForStroking[0] = -1; + this._cachedGetSinglePixelWidth = null; + ctx.save(); + if (current.textMatrix) { + ctx.transform(...current.textMatrix); } - } - isContentVisible() { - for (let i = this.markedContentStack.length - 1; i >= 0; i--) { - if (!this.markedContentStack[i].visible) { - return false; + ctx.translate(current.x, current.y + current.textRise); + ctx.scale(textHScale, fontDirection); + const dependencyTracker = this.dependencyTracker; + this.dependencyTracker = dependencyTracker ? new CanvasNestedDependencyTracker(dependencyTracker, opIdx) : null; + for (i = 0; i < glyphsLength; ++i) { + glyph = glyphs[i]; + if (typeof glyph === "number") { + spacingLength = spacingDir * glyph * fontSize / 1000; + this.ctx.translate(spacingLength, 0); + current.x += spacingLength * textHScale; + continue; + } + const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing; + const operatorList = font.charProcOperatorList[glyph.operatorListId]; + if (!operatorList) { + warn(`Type3 character "${glyph.operatorListId}" is not available.`); + } else if (this.contentVisible) { + this.save(); + ctx.scale(fontSize, fontSize); + ctx.transform(...fontMatrix); + this.executeOperatorList(operatorList); + this.restore(); } + const p = [glyph.width, 0]; + Util.applyTransform(p, fontMatrix); + width = p[0] * fontSize + spacing; + ctx.translate(width, 0); + current.x += width * textHScale; } - return true; - } -} -for (const op in OPS) { - if (CanvasGraphics.prototype[op] !== undefined) { - CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op]; - } -} - -;// ./src/display/canvas_factory.js - -class BaseCanvasFactory { - #enableHWA = false; - constructor({ - enableHWA = false - }) { - this.#enableHWA = enableHWA; - } - create(width, height) { - if (width <= 0 || height <= 0) { - throw new Error("Invalid canvas size"); + ctx.restore(); + if (dependencyTracker) { + this.dependencyTracker = dependencyTracker; } - const canvas = this._createCanvas(width, height); - return { - canvas, - context: canvas.getContext("2d", { - willReadFrequently: !this.#enableHWA - }) - }; } - reset(canvasAndContext, width, height) { - if (!canvasAndContext.canvas) { - throw new Error("Canvas is not specified"); - } - if (width <= 0 || height <= 0) { - throw new Error("Invalid canvas size"); - } - canvasAndContext.canvas.width = width; - canvasAndContext.canvas.height = height; + setCharWidth(opIdx, xWidth, yWidth) {} + setCharWidthAndBounds(opIdx, xWidth, yWidth, llx, lly, urx, ury) { + const clip = new Path2D(); + clip.rect(llx, lly, urx - llx, ury - lly); + this.ctx.clip(clip); + this.dependencyTracker?.recordBBox(opIdx, this.ctx, llx, urx, lly, ury).recordClipBox(opIdx, this.ctx, llx, urx, lly, ury); + this.endPath(opIdx); } - destroy(canvasAndContext) { - if (!canvasAndContext.canvas) { - throw new Error("Canvas is not specified"); + getColorN_Pattern(opIdx, IR) { + let pattern; + if (IR[0] === "TilingPattern") { + const baseTransform = this.baseTransform || getCurrentTransform(this.ctx); + const canvasGraphicsFactory = { + createCanvasGraphics: (ctx, renderingOpIdx) => new CanvasGraphics(ctx, this.commonObjs, this.objs, this.canvasFactory, this.filterFactory, { + optionalContentConfig: this.optionalContentConfig, + markedContentStack: this.markedContentStack + }, undefined, undefined, this.dependencyTracker ? new CanvasNestedDependencyTracker(this.dependencyTracker, renderingOpIdx, true) : null) + }; + pattern = new TilingPattern(IR, this.ctx, canvasGraphicsFactory, baseTransform); + } else { + pattern = this._getPattern(opIdx, IR[1], IR[2]); } - canvasAndContext.canvas.width = 0; - canvasAndContext.canvas.height = 0; - canvasAndContext.canvas = null; - canvasAndContext.context = null; + return pattern; } - _createCanvas(width, height) { - unreachable("Abstract method `_createCanvas` called."); + setStrokeColorN(opIdx, ...args) { + this.dependencyTracker?.recordSimpleData("strokeColor", opIdx); + this.current.strokeColor = this.getColorN_Pattern(opIdx, args); + this.current.patternStroke = true; } -} -class DOMCanvasFactory extends BaseCanvasFactory { - constructor({ - ownerDocument = globalThis.document, - enableHWA = false - }) { - super({ - enableHWA - }); - this._document = ownerDocument; + setFillColorN(opIdx, ...args) { + this.dependencyTracker?.recordSimpleData("fillColor", opIdx); + this.current.fillColor = this.getColorN_Pattern(opIdx, args); + this.current.patternFill = true; } - _createCanvas(width, height) { - const canvas = this._document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - return canvas; + setStrokeRGBColor(opIdx, color) { + this.dependencyTracker?.recordSimpleData("strokeColor", opIdx); + this.ctx.strokeStyle = this.current.strokeColor = color; + this.current.patternStroke = false; } -} - -;// ./src/display/stubs.js -const DOMCMapReaderFactory = null; -const DOMWasmFactory = null; -const DOMStandardFontDataFactory = null; -const NodeCanvasFactory = null; -const NodeCMapReaderFactory = null; -const NodeFilterFactory = null; -const NodeWasmFactory = null; -const NodeStandardFontDataFactory = null; -const PDFFetchStream = null; -const PDFNetworkStream = null; -const PDFNodeStream = null; - -;// ./src/display/filter_factory.js - - -class BaseFilterFactory { - addFilter(maps) { - return "none"; + setStrokeTransparent(opIdx) { + this.dependencyTracker?.recordSimpleData("strokeColor", opIdx); + this.ctx.strokeStyle = this.current.strokeColor = "transparent"; + this.current.patternStroke = false; } - addHCMFilter(fgColor, bgColor) { - return "none"; + setFillRGBColor(opIdx, color) { + this.dependencyTracker?.recordSimpleData("fillColor", opIdx); + this.ctx.fillStyle = this.current.fillColor = color; + this.current.patternFill = false; } - addAlphaFilter(map) { - return "none"; + setFillTransparent(opIdx) { + this.dependencyTracker?.recordSimpleData("fillColor", opIdx); + this.ctx.fillStyle = this.current.fillColor = "transparent"; + this.current.patternFill = false; } - addLuminosityFilter(map) { - return "none"; + _getPattern(opIdx, objId, matrix = null) { + let pattern; + if (this.cachedPatterns.has(objId)) { + pattern = this.cachedPatterns.get(objId); + } else { + pattern = getShadingPattern(this.getObject(opIdx, objId)); + this.cachedPatterns.set(objId, pattern); + } + if (matrix) { + pattern.matrix = matrix; + } + return pattern; } - addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) { - return "none"; + shadingFill(opIdx, objId) { + if (!this.contentVisible) { + return; + } + const ctx = this.ctx; + this.save(opIdx); + const pattern = this._getPattern(opIdx, objId); + ctx.fillStyle = pattern.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.SHADING, opIdx); + const inv = getCurrentTransformInverse(ctx); + if (inv) { + const { + width, + height + } = ctx.canvas; + const minMax = MIN_MAX_INIT.slice(); + Util.axialAlignedBoundingBox([0, 0, width, height], inv, minMax); + const [x0, y0, x1, y1] = minMax; + this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0); + } else { + this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); + } + this.dependencyTracker?.resetBBox(opIdx).recordFullPageBBox(opIdx).recordDependencies(opIdx, Dependencies.transform).recordDependencies(opIdx, Dependencies.fill).recordOperation(opIdx); + this.compose(this.current.getClippedPathBoundingBox()); + this.restore(opIdx); } - destroy(keepHCM = false) {} -} -class DOMFilterFactory extends BaseFilterFactory { - #baseUrl; - #_cache; - #_defs; - #docId; - #document; - #_hcmCache; - #id = 0; - constructor({ - docId, - ownerDocument = globalThis.document - }) { - super(); - this.#docId = docId; - this.#document = ownerDocument; + beginInlineImage() { + unreachable("Should not call beginInlineImage"); } - get #cache() { - return this.#_cache ||= new Map(); + beginImageData() { + unreachable("Should not call beginImageData"); } - get #hcmCache() { - return this.#_hcmCache ||= new Map(); + paintFormXObjectBegin(opIdx, matrix, bbox) { + if (!this.contentVisible) { + return; + } + this.save(opIdx); + this.baseTransformStack.push(this.baseTransform); + if (matrix) { + this.transform(opIdx, ...matrix); + } + this.baseTransform = getCurrentTransform(this.ctx); + if (bbox) { + Util.axialAlignedBoundingBox(bbox, this.baseTransform, this.current.minMax); + const [x0, y0, x1, y1] = bbox; + const clip = new Path2D(); + clip.rect(x0, y0, x1 - x0, y1 - y0); + this.ctx.clip(clip); + this.dependencyTracker?.recordClipBox(opIdx, this.ctx, x0, x1, y0, y1); + this.endPath(opIdx); + } } - get #defs() { - if (!this.#_defs) { - const div = this.#document.createElement("div"); - const { - style - } = div; - style.visibility = "hidden"; - style.contain = "strict"; - style.width = style.height = 0; - style.position = "absolute"; - style.top = style.left = 0; - style.zIndex = -1; - const svg = this.#document.createElementNS(SVG_NS, "svg"); - svg.setAttribute("width", 0); - svg.setAttribute("height", 0); - this.#_defs = this.#document.createElementNS(SVG_NS, "defs"); - div.append(svg); - svg.append(this.#_defs); - this.#document.body.append(div); + paintFormXObjectEnd(opIdx) { + if (!this.contentVisible) { + return; } - return this.#_defs; + this.restore(opIdx); + this.baseTransform = this.baseTransformStack.pop(); } - #createTables(maps) { - if (maps.length === 1) { - const mapR = maps[0]; - const buffer = new Array(256); - for (let i = 0; i < 256; i++) { - buffer[i] = mapR[i] / 255; - } - const table = buffer.join(","); - return [table, table, table]; + beginGroup(opIdx, group) { + if (!this.contentVisible) { + return; } - const [mapR, mapG, mapB] = maps; - const bufferR = new Array(256); - const bufferG = new Array(256); - const bufferB = new Array(256); - for (let i = 0; i < 256; i++) { - bufferR[i] = mapR[i] / 255; - bufferG[i] = mapG[i] / 255; - bufferB[i] = mapB[i] / 255; + this.save(opIdx); + if (this.inSMaskMode) { + this.endSMaskMode(); + this.current.activeSMask = null; } - return [bufferR.join(","), bufferG.join(","), bufferB.join(",")]; + const currentCtx = this.ctx; + if (!group.isolated) { + info("TODO: Support non-isolated groups."); + } + if (group.knockout) { + warn("Knockout groups not supported."); + } + const currentTransform = getCurrentTransform(currentCtx); + if (group.matrix) { + currentCtx.transform(...group.matrix); + } + if (!group.bbox) { + throw new Error("Bounding box is required."); + } + let bounds = MIN_MAX_INIT.slice(); + Util.axialAlignedBoundingBox(group.bbox, getCurrentTransform(currentCtx), bounds); + const canvasBounds = [0, 0, currentCtx.canvas.width, currentCtx.canvas.height]; + bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0]; + const offsetX = Math.floor(bounds[0]); + const offsetY = Math.floor(bounds[1]); + const drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1); + const drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1); + this.current.startNewPathAndClipBox([0, 0, drawnWidth, drawnHeight]); + let cacheId = "groupAt" + this.groupLevel; + if (group.smask) { + cacheId += "_smask_" + this.smaskCounter++ % 2; + } + const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight); + const groupCtx = scratchCanvas.context; + groupCtx.translate(-offsetX, -offsetY); + groupCtx.transform(...currentTransform); + let clip = new Path2D(); + const [x0, y0, x1, y1] = group.bbox; + clip.rect(x0, y0, x1 - x0, y1 - y0); + if (group.matrix) { + const path = new Path2D(); + path.addPath(clip, new DOMMatrix(group.matrix)); + clip = path; + } + groupCtx.clip(clip); + if (group.smask) { + this.smaskStack.push({ + canvas: scratchCanvas.canvas, + context: groupCtx, + offsetX, + offsetY, + subtype: group.smask.subtype, + backdrop: group.smask.backdrop, + transferMap: group.smask.transferMap || null, + startTransformInverse: null + }); + } + if (!group.smask || this.dependencyTracker) { + currentCtx.setTransform(1, 0, 0, 1, 0, 0); + currentCtx.translate(offsetX, offsetY); + currentCtx.save(); + } + copyCtxState(currentCtx, groupCtx); + this.ctx = groupCtx; + this.dependencyTracker?.inheritSimpleDataAsFutureForcedDependencies(["fillAlpha", "strokeAlpha", "globalCompositeOperation"]).pushBaseTransform(currentCtx); + this.setGState(opIdx, [["BM", "source-over"], ["ca", 1], ["CA", 1]]); + this.groupStack.push(currentCtx); + this.groupLevel++; } - #createUrl(id) { - if (this.#baseUrl === undefined) { - this.#baseUrl = ""; - const url = this.#document.URL; - if (url !== this.#document.baseURI) { - if (isDataScheme(url)) { - warn('#createUrl: ignore "data:"-URL for performance reasons.'); - } else { - this.#baseUrl = updateUrlHash(url, ""); - } + endGroup(opIdx, group) { + if (!this.contentVisible) { + return; + } + this.groupLevel--; + const groupCtx = this.ctx; + const ctx = this.groupStack.pop(); + this.ctx = ctx; + this.ctx.imageSmoothingEnabled = false; + this.dependencyTracker?.popBaseTransform(); + if (group.smask) { + this.tempSMask = this.smaskStack.pop(); + this.restore(opIdx); + if (this.dependencyTracker) { + this.ctx.restore(); } + } else { + this.ctx.restore(); + const currentMtx = getCurrentTransform(this.ctx); + this.restore(opIdx); + this.ctx.save(); + this.ctx.setTransform(...currentMtx); + const dirtyBox = MIN_MAX_INIT.slice(); + Util.axialAlignedBoundingBox([0, 0, groupCtx.canvas.width, groupCtx.canvas.height], currentMtx, dirtyBox); + this.ctx.drawImage(groupCtx.canvas, 0, 0); + this.ctx.restore(); + this.compose(dirtyBox); } - return `url(${this.#baseUrl}#${id})`; } - addFilter(maps) { - if (!maps) { - return "none"; + beginAnnotation(opIdx, id, rect, transform, matrix, hasOwnCanvas) { + this.#restoreInitialState(); + resetCtxToDefault(this.ctx); + this.ctx.save(); + this.save(opIdx); + if (this.baseTransform) { + this.ctx.setTransform(...this.baseTransform); } - let value = this.#cache.get(maps); - if (value) { - return value; + if (rect) { + const width = rect[2] - rect[0]; + const height = rect[3] - rect[1]; + if (hasOwnCanvas && this.annotationCanvasMap) { + transform = transform.slice(); + transform[4] -= rect[0]; + transform[5] -= rect[1]; + rect = rect.slice(); + rect[0] = rect[1] = 0; + rect[2] = width; + rect[3] = height; + Util.singularValueDecompose2dScale(getCurrentTransform(this.ctx), XY); + const { + viewportScale + } = this; + const canvasWidth = Math.ceil(width * this.outputScaleX * viewportScale); + const canvasHeight = Math.ceil(height * this.outputScaleY * viewportScale); + this.annotationCanvas = this.canvasFactory.create(canvasWidth, canvasHeight); + const { + canvas, + context + } = this.annotationCanvas; + this.annotationCanvasMap.set(id, canvas); + this.annotationCanvas.savedCtx = this.ctx; + this.ctx = context; + this.ctx.save(); + this.ctx.setTransform(XY[0], 0, 0, -XY[1], 0, height * XY[1]); + resetCtxToDefault(this.ctx); + } else { + resetCtxToDefault(this.ctx); + this.endPath(opIdx); + const clip = new Path2D(); + clip.rect(rect[0], rect[1], width, height); + this.ctx.clip(clip); + } } - const [tableR, tableG, tableB] = this.#createTables(maps); - const key = maps.length === 1 ? tableR : `${tableR}${tableG}${tableB}`; - value = this.#cache.get(key); - if (value) { - this.#cache.set(maps, value); - return value; + this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height); + this.transform(opIdx, ...transform); + this.transform(opIdx, ...matrix); + } + endAnnotation(opIdx) { + if (this.annotationCanvas) { + this.ctx.restore(); + this.#drawFilter(); + this.ctx = this.annotationCanvas.savedCtx; + delete this.annotationCanvas.savedCtx; + delete this.annotationCanvas; } - const id = `g_${this.#docId}_transfer_map_${this.#id++}`; - const url = this.#createUrl(id); - this.#cache.set(maps, url); - this.#cache.set(key, url); - const filter = this.#createFilter(id); - this.#addTransferMapConversion(tableR, tableG, tableB, filter); - return url; } - addHCMFilter(fgColor, bgColor) { - const key = `${fgColor}-${bgColor}`; - const filterName = "base"; - let info = this.#hcmCache.get(filterName); - if (info?.key === key) { - return info.url; + paintImageMaskXObject(opIdx, img) { + if (!this.contentVisible) { + return; } - if (info) { - info.filter?.remove(); - info.key = key; - info.url = "none"; - info.filter = null; - } else { - info = { - key, - url: "none", - filter: null - }; - this.#hcmCache.set(filterName, info); + const count = img.count; + img = this.getObject(opIdx, img.data, img); + img.count = count; + const ctx = this.ctx; + const mask = this._createMaskCanvas(opIdx, img); + const maskCanvas = mask.canvas; + ctx.save(); + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.drawImage(maskCanvas, mask.offsetX, mask.offsetY); + this.dependencyTracker?.resetBBox(opIdx).recordBBox(opIdx, this.ctx, mask.offsetX, mask.offsetX + maskCanvas.width, mask.offsetY, mask.offsetY + maskCanvas.height).recordOperation(opIdx); + ctx.restore(); + this.compose(); + } + paintImageMaskXObjectRepeat(opIdx, img, scaleX, skewX = 0, skewY = 0, scaleY, positions) { + if (!this.contentVisible) { + return; } - if (!fgColor || !bgColor) { - return info.url; + img = this.getObject(opIdx, img.data, img); + const ctx = this.ctx; + ctx.save(); + const currentTransform = getCurrentTransform(ctx); + ctx.transform(scaleX, skewX, skewY, scaleY, 0, 0); + const mask = this._createMaskCanvas(opIdx, img); + ctx.setTransform(1, 0, 0, 1, mask.offsetX - currentTransform[4], mask.offsetY - currentTransform[5]); + this.dependencyTracker?.resetBBox(opIdx); + for (let i = 0, ii = positions.length; i < ii; i += 2) { + const trans = Util.transform(currentTransform, [scaleX, skewX, skewY, scaleY, positions[i], positions[i + 1]]); + ctx.drawImage(mask.canvas, trans[4], trans[5]); + this.dependencyTracker?.recordBBox(opIdx, this.ctx, trans[4], trans[4] + mask.canvas.width, trans[5], trans[5] + mask.canvas.height); } - const fgRGB = this.#getRGB(fgColor); - fgColor = Util.makeHexColor(...fgRGB); - const bgRGB = this.#getRGB(bgColor); - bgColor = Util.makeHexColor(...bgRGB); - this.#defs.style.color = ""; - if (fgColor === "#000000" && bgColor === "#ffffff" || fgColor === bgColor) { - return info.url; + ctx.restore(); + this.compose(); + this.dependencyTracker?.recordOperation(opIdx); + } + paintImageMaskXObjectGroup(opIdx, images) { + if (!this.contentVisible) { + return; } - const map = new Array(256); - for (let i = 0; i <= 255; i++) { - const x = i / 255; - map[i] = x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4; + const ctx = this.ctx; + const fillColor = this.current.fillColor; + const isPatternFill = this.current.patternFill; + this.dependencyTracker?.resetBBox(opIdx).recordDependencies(opIdx, Dependencies.transformAndFill); + for (const image of images) { + const { + data, + width, + height, + transform + } = image; + const maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height); + const maskCtx = maskCanvas.context; + maskCtx.save(); + const img = this.getObject(opIdx, data, image); + putBinaryImageMask(maskCtx, img); + maskCtx.globalCompositeOperation = "source-in"; + maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this, getCurrentTransformInverse(ctx), PathType.FILL, opIdx) : fillColor; + maskCtx.fillRect(0, 0, width, height); + maskCtx.restore(); + ctx.save(); + ctx.transform(...transform); + ctx.scale(1, -1); + drawImageAtIntegerCoords(ctx, maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1); + this.dependencyTracker?.recordBBox(opIdx, ctx, 0, width, 0, height); + ctx.restore(); } - const table = map.join(","); - const id = `g_${this.#docId}_hcm_filter`; - const filter = info.filter = this.#createFilter(id); - this.#addTransferMapConversion(table, table, table, filter); - this.#addGrayConversion(filter); - const getSteps = (c, n) => { - const start = fgRGB[c] / 255; - const end = bgRGB[c] / 255; - const arr = new Array(n + 1); - for (let i = 0; i <= n; i++) { - arr[i] = start + i / n * (end - start); - } - return arr.join(","); - }; - this.#addTransferMapConversion(getSteps(0, 5), getSteps(1, 5), getSteps(2, 5), filter); - info.url = this.#createUrl(id); - return info.url; + this.compose(); + this.dependencyTracker?.recordOperation(opIdx); } - addAlphaFilter(map) { - let value = this.#cache.get(map); - if (value) { - return value; + paintImageXObject(opIdx, objId) { + if (!this.contentVisible) { + return; } - const [tableA] = this.#createTables([map]); - const key = `alpha_${tableA}`; - value = this.#cache.get(key); - if (value) { - this.#cache.set(map, value); - return value; + const imgData = this.getObject(opIdx, objId); + if (!imgData) { + warn("Dependent image isn't ready yet"); + return; } - const id = `g_${this.#docId}_alpha_map_${this.#id++}`; - const url = this.#createUrl(id); - this.#cache.set(map, url); - this.#cache.set(key, url); - const filter = this.#createFilter(id); - this.#addTransferMapAlphaConversion(tableA, filter); - return url; + this.paintInlineImageXObject(opIdx, imgData); } - addLuminosityFilter(map) { - let value = this.#cache.get(map || "luminosity"); - if (value) { - return value; + paintImageXObjectRepeat(opIdx, objId, scaleX, scaleY, positions) { + if (!this.contentVisible) { + return; } - let tableA, key; - if (map) { - [tableA] = this.#createTables([map]); - key = `luminosity_${tableA}`; - } else { - key = "luminosity"; + const imgData = this.getObject(opIdx, objId); + if (!imgData) { + warn("Dependent image isn't ready yet"); + return; } - value = this.#cache.get(key); - if (value) { - this.#cache.set(map, value); - return value; + const width = imgData.width; + const height = imgData.height; + const map = []; + for (let i = 0, ii = positions.length; i < ii; i += 2) { + map.push({ + transform: [scaleX, 0, 0, scaleY, positions[i], positions[i + 1]], + x: 0, + y: 0, + w: width, + h: height + }); } - const id = `g_${this.#docId}_luminosity_map_${this.#id++}`; - const url = this.#createUrl(id); - this.#cache.set(map, url); - this.#cache.set(key, url); - const filter = this.#createFilter(id); - this.#addLuminosityConversion(filter); - if (map) { - this.#addTransferMapAlphaConversion(tableA, filter); + this.paintInlineImageXObjectGroup(opIdx, imgData, map); + } + applyTransferMapsToCanvas(ctx) { + if (this.current.transferMaps !== "none") { + ctx.filter = this.current.transferMaps; + ctx.drawImage(ctx.canvas, 0, 0); + ctx.filter = "none"; } - return url; + return ctx.canvas; } - addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) { - const key = `${fgColor}-${bgColor}-${newFgColor}-${newBgColor}`; - let info = this.#hcmCache.get(filterName); - if (info?.key === key) { - return info.url; + applyTransferMapsToBitmap(imgData) { + if (this.current.transferMaps === "none") { + return imgData.bitmap; } - if (info) { - info.filter?.remove(); - info.key = key; - info.url = "none"; - info.filter = null; + const { + bitmap, + width, + height + } = imgData; + const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height); + const tmpCtx = tmpCanvas.context; + tmpCtx.filter = this.current.transferMaps; + tmpCtx.drawImage(bitmap, 0, 0); + tmpCtx.filter = "none"; + return tmpCanvas.canvas; + } + paintInlineImageXObject(opIdx, imgData) { + if (!this.contentVisible) { + return; + } + const width = imgData.width; + const height = imgData.height; + const ctx = this.ctx; + this.save(opIdx); + const { + filter + } = ctx; + if (filter !== "none" && filter !== "") { + ctx.filter = "none"; + } + ctx.scale(1 / width, -1 / height); + let imgToPaint; + if (imgData.bitmap) { + imgToPaint = this.applyTransferMapsToBitmap(imgData); + } else if (typeof HTMLElement === "function" && imgData instanceof HTMLElement || !imgData.data) { + imgToPaint = imgData; } else { - info = { - key, - url: "none", - filter: null - }; - this.#hcmCache.set(filterName, info); + const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height); + const tmpCtx = tmpCanvas.context; + putBinaryImageData(tmpCtx, imgData); + imgToPaint = this.applyTransferMapsToCanvas(tmpCtx); + } + const scaled = this._scaleImage(imgToPaint, getCurrentTransformInverse(ctx)); + ctx.imageSmoothingEnabled = getImageSmoothingEnabled(getCurrentTransform(ctx), imgData.interpolate); + this.dependencyTracker?.resetBBox(opIdx).recordBBox(opIdx, ctx, 0, width, -height, 0).recordDependencies(opIdx, Dependencies.imageXObject).recordOperation(opIdx); + drawImageAtIntegerCoords(ctx, scaled.img, 0, 0, scaled.paintWidth, scaled.paintHeight, 0, -height, width, height); + this.compose(); + this.restore(opIdx); + } + paintInlineImageXObjectGroup(opIdx, imgData, map) { + if (!this.contentVisible) { + return; } - if (!fgColor || !bgColor) { - return info.url; + const ctx = this.ctx; + let imgToPaint; + if (imgData.bitmap) { + imgToPaint = imgData.bitmap; + } else { + const w = imgData.width; + const h = imgData.height; + const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h); + const tmpCtx = tmpCanvas.context; + putBinaryImageData(tmpCtx, imgData); + imgToPaint = this.applyTransferMapsToCanvas(tmpCtx); } - const [fgRGB, bgRGB] = [fgColor, bgColor].map(this.#getRGB.bind(this)); - let fgGray = Math.round(0.2126 * fgRGB[0] + 0.7152 * fgRGB[1] + 0.0722 * fgRGB[2]); - let bgGray = Math.round(0.2126 * bgRGB[0] + 0.7152 * bgRGB[1] + 0.0722 * bgRGB[2]); - let [newFgRGB, newBgRGB] = [newFgColor, newBgColor].map(this.#getRGB.bind(this)); - if (bgGray < fgGray) { - [fgGray, bgGray, newFgRGB, newBgRGB] = [bgGray, fgGray, newBgRGB, newFgRGB]; + this.dependencyTracker?.resetBBox(opIdx); + for (const entry of map) { + ctx.save(); + ctx.transform(...entry.transform); + ctx.scale(1, -1); + drawImageAtIntegerCoords(ctx, imgToPaint, entry.x, entry.y, entry.w, entry.h, 0, -1, 1, 1); + this.dependencyTracker?.recordBBox(opIdx, ctx, 0, 1, -1, 0); + ctx.restore(); } - this.#defs.style.color = ""; - const getSteps = (fg, bg, n) => { - const arr = new Array(256); - const step = (bgGray - fgGray) / n; - const newStart = fg / 255; - const newStep = (bg - fg) / (255 * n); - let prev = 0; - for (let i = 0; i <= n; i++) { - const k = Math.round(fgGray + i * step); - const value = newStart + i * newStep; - for (let j = prev; j <= k; j++) { - arr[j] = value; - } - prev = k + 1; - } - for (let i = prev; i < 256; i++) { - arr[i] = arr[prev - 1]; - } - return arr.join(","); - }; - const id = `g_${this.#docId}_hcm_${filterName}_filter`; - const filter = info.filter = this.#createFilter(id); - this.#addGrayConversion(filter); - this.#addTransferMapConversion(getSteps(newFgRGB[0], newBgRGB[0], 5), getSteps(newFgRGB[1], newBgRGB[1], 5), getSteps(newFgRGB[2], newBgRGB[2], 5), filter); - info.url = this.#createUrl(id); - return info.url; + this.dependencyTracker?.recordOperation(opIdx); + this.compose(); } - destroy(keepHCM = false) { - if (keepHCM && this.#_hcmCache?.size) { + paintSolidColorImageMask(opIdx) { + if (!this.contentVisible) { return; } - this.#_defs?.parentNode.parentNode.remove(); - this.#_defs = null; - this.#_cache?.clear(); - this.#_cache = null; - this.#_hcmCache?.clear(); - this.#_hcmCache = null; - this.#id = 0; - } - #addLuminosityConversion(filter) { - const feColorMatrix = this.#document.createElementNS(SVG_NS, "feColorMatrix"); - feColorMatrix.setAttribute("type", "matrix"); - feColorMatrix.setAttribute("values", "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.59 0.11 0 0"); - filter.append(feColorMatrix); - } - #addGrayConversion(filter) { - const feColorMatrix = this.#document.createElementNS(SVG_NS, "feColorMatrix"); - feColorMatrix.setAttribute("type", "matrix"); - feColorMatrix.setAttribute("values", "0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"); - filter.append(feColorMatrix); - } - #createFilter(id) { - const filter = this.#document.createElementNS(SVG_NS, "filter"); - filter.setAttribute("color-interpolation-filters", "sRGB"); - filter.setAttribute("id", id); - this.#defs.append(filter); - return filter; - } - #appendFeFunc(feComponentTransfer, func, table) { - const feFunc = this.#document.createElementNS(SVG_NS, func); - feFunc.setAttribute("type", "discrete"); - feFunc.setAttribute("tableValues", table); - feComponentTransfer.append(feFunc); + this.dependencyTracker?.resetBBox(opIdx).recordBBox(opIdx, this.ctx, 0, 1, 0, 1).recordDependencies(opIdx, Dependencies.fill).recordOperation(opIdx); + this.ctx.fillRect(0, 0, 1, 1); + this.compose(); } - #addTransferMapConversion(rTable, gTable, bTable, filter) { - const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer"); - filter.append(feComponentTransfer); - this.#appendFeFunc(feComponentTransfer, "feFuncR", rTable); - this.#appendFeFunc(feComponentTransfer, "feFuncG", gTable); - this.#appendFeFunc(feComponentTransfer, "feFuncB", bTable); + markPoint(opIdx, tag) {} + markPointProps(opIdx, tag, properties) {} + beginMarkedContent(opIdx, tag) { + this.dependencyTracker?.beginMarkedContent(opIdx); + this.markedContentStack.push({ + visible: true + }); } - #addTransferMapAlphaConversion(aTable, filter) { - const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer"); - filter.append(feComponentTransfer); - this.#appendFeFunc(feComponentTransfer, "feFuncA", aTable); + beginMarkedContentProps(opIdx, tag, properties) { + this.dependencyTracker?.beginMarkedContent(opIdx); + if (tag === "OC") { + this.markedContentStack.push({ + visible: this.optionalContentConfig.isVisible(properties) + }); + } else { + this.markedContentStack.push({ + visible: true + }); + } + this.contentVisible = this.isContentVisible(); } - #getRGB(color) { - this.#defs.style.color = color; - return getRGB(getComputedStyle(this.#defs).getPropertyValue("color")); + endMarkedContent(opIdx) { + this.dependencyTracker?.endMarkedContent(opIdx); + this.markedContentStack.pop(); + this.contentVisible = this.isContentVisible(); } -} - -;// ./src/shared/obj-bin-transform.js - -class CssFontInfo { - #buffer; - #view; - #decoder; - static strings = ["fontFamily", "fontWeight", "italicAngle"]; - static write(info) { - const encoder = new TextEncoder(); - const encodedStrings = {}; - let stringsLength = 0; - for (const prop of CssFontInfo.strings) { - const encoded = encoder.encode(info[prop]); - encodedStrings[prop] = encoded; - stringsLength += 4 + encoded.length; + beginCompat(opIdx) {} + endCompat(opIdx) {} + consumePath(opIdx, path, clipBox) { + const isEmpty = this.current.isEmptyClip(); + if (this.pendingClip) { + this.current.updateClipFromPath(); } - const buffer = new ArrayBuffer(stringsLength); - const data = new Uint8Array(buffer); - const view = new DataView(buffer); - let offset = 0; - for (const prop of CssFontInfo.strings) { - const encoded = encodedStrings[prop]; - const length = encoded.length; - view.setUint32(offset, length); - data.set(encoded, offset + 4); - offset += 4 + length; + if (!this.pendingClip) { + this.compose(clipBox); } - assert(offset === buffer.byteLength, "CssFontInfo.write: Buffer overflow"); - return buffer; + const ctx = this.ctx; + if (this.pendingClip) { + if (!isEmpty) { + if (this.pendingClip === EO_CLIP) { + ctx.clip(path, "evenodd"); + } else { + ctx.clip(path); + } + } + this.pendingClip = null; + this.dependencyTracker?.bboxToClipBoxDropOperation(opIdx).recordFutureForcedDependency("clipPath", opIdx); + } else { + this.dependencyTracker?.recordOperation(opIdx); + } + this.current.startNewPathAndClipBox(this.current.clipBox); } - constructor(buffer) { - this.#buffer = buffer; - this.#view = new DataView(this.#buffer); - this.#decoder = new TextDecoder(); + getSinglePixelWidth() { + if (!this._cachedGetSinglePixelWidth) { + const m = getCurrentTransform(this.ctx); + if (m[1] === 0 && m[2] === 0) { + this._cachedGetSinglePixelWidth = 1 / Math.min(Math.abs(m[0]), Math.abs(m[3])); + } else { + const absDet = Math.abs(m[0] * m[3] - m[2] * m[1]); + const normX = Math.hypot(m[0], m[2]); + const normY = Math.hypot(m[1], m[3]); + this._cachedGetSinglePixelWidth = Math.max(normX, normY) / absDet; + } + } + return this._cachedGetSinglePixelWidth; } - #readString(index) { - assert(index < CssFontInfo.strings.length, "Invalid string index"); - let offset = 0; - for (let i = 0; i < index; i++) { - offset += this.#view.getUint32(offset) + 4; + getScaleForStroking() { + if (this._cachedScaleForStroking[0] === -1) { + const { + lineWidth + } = this.current; + const { + a, + b, + c, + d + } = this.ctx.getTransform(); + let scaleX, scaleY; + if (b === 0 && c === 0) { + const normX = Math.abs(a); + const normY = Math.abs(d); + if (normX === normY) { + if (lineWidth === 0) { + scaleX = scaleY = 1 / normX; + } else { + const scaledLineWidth = normX * lineWidth; + scaleX = scaleY = scaledLineWidth < 1 ? 1 / scaledLineWidth : 1; + } + } else if (lineWidth === 0) { + scaleX = 1 / normX; + scaleY = 1 / normY; + } else { + const scaledXLineWidth = normX * lineWidth; + const scaledYLineWidth = normY * lineWidth; + scaleX = scaledXLineWidth < 1 ? 1 / scaledXLineWidth : 1; + scaleY = scaledYLineWidth < 1 ? 1 / scaledYLineWidth : 1; + } + } else { + const absDet = Math.abs(a * d - b * c); + const normX = Math.hypot(a, b); + const normY = Math.hypot(c, d); + if (lineWidth === 0) { + scaleX = normY / absDet; + scaleY = normX / absDet; + } else { + const baseArea = lineWidth * absDet; + scaleX = normY > baseArea ? normY / baseArea : 1; + scaleY = normX > baseArea ? normX / baseArea : 1; + } + } + this._cachedScaleForStroking[0] = scaleX; + this._cachedScaleForStroking[1] = scaleY; } - const length = this.#view.getUint32(offset); - return this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); - } - get fontFamily() { - return this.#readString(0); - } - get fontWeight() { - return this.#readString(1); - } - get italicAngle() { - return this.#readString(2); + return this._cachedScaleForStroking; } -} -class SystemFontInfo { - #buffer; - #view; - #decoder; - static strings = ["css", "loadedName", "baseFontName", "src"]; - static write(info) { - const encoder = new TextEncoder(); - const encodedStrings = {}; - let stringsLength = 0; - for (const prop of SystemFontInfo.strings) { - const encoded = encoder.encode(info[prop]); - encodedStrings[prop] = encoded; - stringsLength += 4 + encoded.length; + rescaleAndStroke(path, saveRestore) { + const { + ctx, + current: { + lineWidth + } + } = this; + const [scaleX, scaleY] = this.getScaleForStroking(); + if (scaleX === scaleY) { + ctx.lineWidth = (lineWidth || 1) * scaleX; + ctx.stroke(path); + return; } - stringsLength += 4; - let encodedStyleStyle, - encodedStyleWeight, - lengthEstimate = 1 + stringsLength; - if (info.style) { - encodedStyleStyle = encoder.encode(info.style.style); - encodedStyleWeight = encoder.encode(info.style.weight); - lengthEstimate += 4 + encodedStyleStyle.length + 4 + encodedStyleWeight.length; + const dashes = ctx.getLineDash(); + if (saveRestore) { + ctx.save(); } - const buffer = new ArrayBuffer(lengthEstimate); - const data = new Uint8Array(buffer); - const view = new DataView(buffer); - let offset = 0; - view.setUint8(offset++, info.guessFallback ? 1 : 0); - view.setUint32(offset, 0); - offset += 4; - stringsLength = 0; - for (const prop of SystemFontInfo.strings) { - const encoded = encodedStrings[prop]; - const length = encoded.length; - stringsLength += 4 + length; - view.setUint32(offset, length); - data.set(encoded, offset + 4); - offset += 4 + length; + ctx.scale(scaleX, scaleY); + SCALE_MATRIX.a = 1 / scaleX; + SCALE_MATRIX.d = 1 / scaleY; + const newPath = new Path2D(); + newPath.addPath(path, SCALE_MATRIX); + if (dashes.length > 0) { + const scale = Math.max(scaleX, scaleY); + ctx.setLineDash(dashes.map(x => x / scale)); + ctx.lineDashOffset /= scale; } - view.setUint32(offset - stringsLength - 4, stringsLength); - if (info.style) { - view.setUint32(offset, encodedStyleStyle.length); - data.set(encodedStyleStyle, offset + 4); - offset += 4 + encodedStyleStyle.length; - view.setUint32(offset, encodedStyleWeight.length); - data.set(encodedStyleWeight, offset + 4); - offset += 4 + encodedStyleWeight.length; + ctx.lineWidth = lineWidth || 1; + ctx.stroke(newPath); + if (saveRestore) { + ctx.restore(); } - assert(offset <= buffer.byteLength, "SubstitionInfo.write: Buffer overflow"); - return buffer.transferToFixedLength(offset); - } - constructor(buffer) { - this.#buffer = buffer; - this.#view = new DataView(this.#buffer); - this.#decoder = new TextDecoder(); - } - get guessFallback() { - return this.#view.getUint8(0) !== 0; } - #readString(index) { - assert(index < SystemFontInfo.strings.length, "Invalid string index"); - let offset = 5; - for (let i = 0; i < index; i++) { - offset += this.#view.getUint32(offset) + 4; + isContentVisible() { + for (let i = this.markedContentStack.length - 1; i >= 0; i--) { + if (!this.markedContentStack[i].visible) { + return false; + } } - const length = this.#view.getUint32(offset); - return this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); - } - get css() { - return this.#readString(0); - } - get loadedName() { - return this.#readString(1); - } - get baseFontName() { - return this.#readString(2); - } - get src() { - return this.#readString(3); + return true; } - get style() { - let offset = 1; - offset += 4 + this.#view.getUint32(offset); - const styleLength = this.#view.getUint32(offset); - const style = this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, styleLength)); - offset += 4 + styleLength; - const weightLength = this.#view.getUint32(offset); - const weight = this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, weightLength)); - return { - style, - weight - }; +} +for (const op in OPS) { + if (CanvasGraphics.prototype[op] !== undefined) { + CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op]; } } -class FontInfo { - static bools = ["black", "bold", "disableFontFace", "fontExtraProperties", "isInvalidPDFjsFont", "isType3Font", "italic", "missingFile", "remeasure", "vertical"]; - static numbers = ["ascent", "defaultWidth", "descent"]; - static strings = ["fallbackName", "loadedName", "mimetype", "name"]; - static #OFFSET_NUMBERS = Math.ceil(this.bools.length * 2 / 8); - static #OFFSET_BBOX = this.#OFFSET_NUMBERS + this.numbers.length * 8; - static #OFFSET_FONT_MATRIX = this.#OFFSET_BBOX + 1 + 2 * 4; - static #OFFSET_DEFAULT_VMETRICS = this.#OFFSET_FONT_MATRIX + 1 + 8 * 6; - static #OFFSET_STRINGS = this.#OFFSET_DEFAULT_VMETRICS + 1 + 2 * 3; - #buffer; - #decoder; - #view; + +;// ./src/display/canvas_factory.js + +class BaseCanvasFactory { + #enableHWA = false; constructor({ - data, - extra + enableHWA = false }) { - this.#buffer = data; - this.#decoder = new TextDecoder(); - this.#view = new DataView(this.#buffer); - if (extra) { - Object.assign(this, extra); - } - } - #readBoolean(index) { - assert(index < FontInfo.bools.length, "Invalid boolean index"); - const byteOffset = Math.floor(index / 4); - const bitOffset = index * 2 % 8; - const value = this.#view.getUint8(byteOffset) >> bitOffset & 0x03; - return value === 0x00 ? undefined : value === 0x02; + this.#enableHWA = enableHWA; } - get black() { - return this.#readBoolean(0); + create(width, height) { + if (width <= 0 || height <= 0) { + throw new Error("Invalid canvas size"); + } + const canvas = this._createCanvas(width, height); + return { + canvas, + context: canvas.getContext("2d", { + willReadFrequently: !this.#enableHWA + }) + }; } - get bold() { - return this.#readBoolean(1); + reset(canvasAndContext, width, height) { + if (!canvasAndContext.canvas) { + throw new Error("Canvas is not specified"); + } + if (width <= 0 || height <= 0) { + throw new Error("Invalid canvas size"); + } + canvasAndContext.canvas.width = width; + canvasAndContext.canvas.height = height; } - get disableFontFace() { - return this.#readBoolean(2); + destroy(canvasAndContext) { + if (!canvasAndContext.canvas) { + throw new Error("Canvas is not specified"); + } + canvasAndContext.canvas.width = 0; + canvasAndContext.canvas.height = 0; + canvasAndContext.canvas = null; + canvasAndContext.context = null; } - get fontExtraProperties() { - return this.#readBoolean(3); + _createCanvas(width, height) { + unreachable("Abstract method `_createCanvas` called."); } - get isInvalidPDFjsFont() { - return this.#readBoolean(4); +} +class DOMCanvasFactory extends BaseCanvasFactory { + constructor({ + ownerDocument = globalThis.document, + enableHWA = false + }) { + super({ + enableHWA + }); + this._document = ownerDocument; } - get isType3Font() { - return this.#readBoolean(5); + _createCanvas(width, height) { + const canvas = this._document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + return canvas; } - get italic() { - return this.#readBoolean(6); +} + +;// ./src/display/stubs.js +const DOMCMapReaderFactory = null; +const DOMWasmFactory = null; +const DOMStandardFontDataFactory = null; +const NodeCanvasFactory = null; +const NodeCMapReaderFactory = null; +const NodeFilterFactory = null; +const NodeWasmFactory = null; +const NodeStandardFontDataFactory = null; +const PDFFetchStream = null; +const PDFNetworkStream = null; +const PDFNodeStream = null; + +;// ./src/display/filter_factory.js + + +class BaseFilterFactory { + addFilter(maps) { + return "none"; } - get missingFile() { - return this.#readBoolean(7); + addHCMFilter(fgColor, bgColor) { + return "none"; } - get remeasure() { - return this.#readBoolean(8); + addAlphaFilter(map) { + return "none"; } - get vertical() { - return this.#readBoolean(9); + addLuminosityFilter(map) { + return "none"; } - #readNumber(index) { - assert(index < FontInfo.numbers.length, "Invalid number index"); - return this.#view.getFloat64(FontInfo.#OFFSET_NUMBERS + index * 8); + addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) { + return "none"; } - get ascent() { - return this.#readNumber(0); + destroy(keepHCM = false) {} +} +class DOMFilterFactory extends BaseFilterFactory { + #baseUrl; + #_cache; + #_defs; + #docId; + #document; + #_hcmCache; + #id = 0; + constructor({ + docId, + ownerDocument = globalThis.document + }) { + super(); + this.#docId = docId; + this.#document = ownerDocument; } - get defaultWidth() { - return this.#readNumber(1); + get #cache() { + return this.#_cache ||= new Map(); } - get descent() { - return this.#readNumber(2); + get #hcmCache() { + return this.#_hcmCache ||= new Map(); } - get bbox() { - let offset = FontInfo.#OFFSET_BBOX; - const numCoords = this.#view.getUint8(offset); - if (numCoords === 0) { - return undefined; - } - offset += 1; - const bbox = []; - for (let i = 0; i < 4; i++) { - bbox.push(this.#view.getInt16(offset, true)); - offset += 2; + get #defs() { + if (!this.#_defs) { + const div = this.#document.createElement("div"); + const { + style + } = div; + style.visibility = "hidden"; + style.contain = "strict"; + style.width = style.height = 0; + style.position = "absolute"; + style.top = style.left = 0; + style.zIndex = -1; + const svg = this.#document.createElementNS(SVG_NS, "svg"); + svg.setAttribute("width", 0); + svg.setAttribute("height", 0); + this.#_defs = this.#document.createElementNS(SVG_NS, "defs"); + div.append(svg); + svg.append(this.#_defs); + this.#document.body.append(div); } - return bbox; + return this.#_defs; } - get fontMatrix() { - let offset = FontInfo.#OFFSET_FONT_MATRIX; - const numPoints = this.#view.getUint8(offset); - if (numPoints === 0) { - return undefined; + #createTables(maps) { + if (maps.length === 1) { + const mapR = maps[0]; + const buffer = new Array(256); + for (let i = 0; i < 256; i++) { + buffer[i] = mapR[i] / 255; + } + const table = buffer.join(","); + return [table, table, table]; } - offset += 1; - const fontMatrix = []; - for (let i = 0; i < 6; i++) { - fontMatrix.push(this.#view.getFloat64(offset, true)); - offset += 8; + const [mapR, mapG, mapB] = maps; + const bufferR = new Array(256); + const bufferG = new Array(256); + const bufferB = new Array(256); + for (let i = 0; i < 256; i++) { + bufferR[i] = mapR[i] / 255; + bufferG[i] = mapG[i] / 255; + bufferB[i] = mapB[i] / 255; } - return fontMatrix; + return [bufferR.join(","), bufferG.join(","), bufferB.join(",")]; } - get defaultVMetrics() { - let offset = FontInfo.#OFFSET_DEFAULT_VMETRICS; - const numMetrics = this.#view.getUint8(offset); - if (numMetrics === 0) { - return undefined; - } - offset += 1; - const defaultVMetrics = []; - for (let i = 0; i < 3; i++) { - defaultVMetrics.push(this.#view.getInt16(offset, true)); - offset += 2; + #createUrl(id) { + if (this.#baseUrl === undefined) { + this.#baseUrl = ""; + const url = this.#document.URL; + if (url !== this.#document.baseURI) { + if (isDataScheme(url)) { + warn('#createUrl: ignore "data:"-URL for performance reasons.'); + } else { + this.#baseUrl = updateUrlHash(url, ""); + } + } } - return defaultVMetrics; + return `url(${this.#baseUrl}#${id})`; } - #readString(index) { - assert(index < FontInfo.strings.length, "Invalid string index"); - let offset = FontInfo.#OFFSET_STRINGS + 4; - for (let i = 0; i < index; i++) { - offset += this.#view.getUint32(offset) + 4; + addFilter(maps) { + if (!maps) { + return "none"; } - const length = this.#view.getUint32(offset); - const stringData = new Uint8Array(length); - stringData.set(new Uint8Array(this.#buffer, offset + 4, length)); - return this.#decoder.decode(stringData); - } - get fallbackName() { - return this.#readString(0); - } - get loadedName() { - return this.#readString(1); - } - get mimetype() { - return this.#readString(2); - } - get name() { - return this.#readString(3); - } - get data() { - let offset = FontInfo.#OFFSET_STRINGS; - const stringsLength = this.#view.getUint32(offset); - offset += 4 + stringsLength; - const systemFontInfoLength = this.#view.getUint32(offset); - offset += 4 + systemFontInfoLength; - const cssFontInfoLength = this.#view.getUint32(offset); - offset += 4 + cssFontInfoLength; - const length = this.#view.getUint32(offset); - if (length === 0) { - return undefined; + let value = this.#cache.get(maps); + if (value) { + return value; } - return new Uint8Array(this.#buffer, offset + 4, length); - } - clearData() { - let offset = FontInfo.#OFFSET_STRINGS; - const stringsLength = this.#view.getUint32(offset); - offset += 4 + stringsLength; - const systemFontInfoLength = this.#view.getUint32(offset); - offset += 4 + systemFontInfoLength; - const cssFontInfoLength = this.#view.getUint32(offset); - offset += 4 + cssFontInfoLength; - const length = this.#view.getUint32(offset); - const data = new Uint8Array(this.#buffer, offset + 4, length); - data.fill(0); - this.#view.setUint32(offset, 0); - } - get cssFontInfo() { - let offset = FontInfo.#OFFSET_STRINGS; - const stringsLength = this.#view.getUint32(offset); - offset += 4 + stringsLength; - const systemFontInfoLength = this.#view.getUint32(offset); - offset += 4 + systemFontInfoLength; - const cssFontInfoLength = this.#view.getUint32(offset); - if (cssFontInfoLength === 0) { - return null; + const [tableR, tableG, tableB] = this.#createTables(maps); + const key = maps.length === 1 ? tableR : `${tableR}${tableG}${tableB}`; + value = this.#cache.get(key); + if (value) { + this.#cache.set(maps, value); + return value; } - const cssFontInfoData = new Uint8Array(cssFontInfoLength); - cssFontInfoData.set(new Uint8Array(this.#buffer, offset + 4, cssFontInfoLength)); - return new CssFontInfo(cssFontInfoData.buffer); + const id = `g_${this.#docId}_transfer_map_${this.#id++}`; + const url = this.#createUrl(id); + this.#cache.set(maps, url); + this.#cache.set(key, url); + const filter = this.#createFilter(id); + this.#addTransferMapConversion(tableR, tableG, tableB, filter); + return url; } - get systemFontInfo() { - let offset = FontInfo.#OFFSET_STRINGS; - const stringsLength = this.#view.getUint32(offset); - offset += 4 + stringsLength; - const systemFontInfoLength = this.#view.getUint32(offset); - if (systemFontInfoLength === 0) { - return null; + addHCMFilter(fgColor, bgColor) { + const key = `${fgColor}-${bgColor}`; + const filterName = "base"; + let info = this.#hcmCache.get(filterName); + if (info?.key === key) { + return info.url; } - const systemFontInfoData = new Uint8Array(systemFontInfoLength); - systemFontInfoData.set(new Uint8Array(this.#buffer, offset + 4, systemFontInfoLength)); - return new SystemFontInfo(systemFontInfoData.buffer); - } - static write(font) { - const systemFontInfoBuffer = font.systemFontInfo ? SystemFontInfo.write(font.systemFontInfo) : null; - const cssFontInfoBuffer = font.cssFontInfo ? CssFontInfo.write(font.cssFontInfo) : null; - const encoder = new TextEncoder(); - const encodedStrings = {}; - let stringsLength = 0; - for (const prop of FontInfo.strings) { - encodedStrings[prop] = encoder.encode(font[prop]); - stringsLength += 4 + encodedStrings[prop].length; + if (info) { + info.filter?.remove(); + info.key = key; + info.url = "none"; + info.filter = null; + } else { + info = { + key, + url: "none", + filter: null + }; + this.#hcmCache.set(filterName, info); + } + if (!fgColor || !bgColor) { + return info.url; + } + const fgRGB = this.#getRGB(fgColor); + fgColor = Util.makeHexColor(...fgRGB); + const bgRGB = this.#getRGB(bgColor); + bgColor = Util.makeHexColor(...bgRGB); + this.#defs.style.color = ""; + if (fgColor === "#000000" && bgColor === "#ffffff" || fgColor === bgColor) { + return info.url; + } + const map = new Array(256); + for (let i = 0; i <= 255; i++) { + const x = i / 255; + map[i] = x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4; } - const lengthEstimate = FontInfo.#OFFSET_STRINGS + 4 + stringsLength + 4 + (systemFontInfoBuffer ? systemFontInfoBuffer.byteLength : 0) + 4 + (cssFontInfoBuffer ? cssFontInfoBuffer.byteLength : 0) + 4 + (font.data ? font.data.length : 0); - const buffer = new ArrayBuffer(lengthEstimate); - const data = new Uint8Array(buffer); - const view = new DataView(buffer); - let offset = 0; - const numBools = FontInfo.bools.length; - let boolByte = 0, - boolBit = 0; - for (let i = 0; i < numBools; i++) { - const value = font[FontInfo.bools[i]]; - const bits = value === undefined ? 0x00 : value ? 0x02 : 0x01; - boolByte |= bits << boolBit; - boolBit += 2; - if (boolBit === 8 || i === numBools - 1) { - view.setUint8(offset++, boolByte); - boolByte = 0; - boolBit = 0; + const table = map.join(","); + const id = `g_${this.#docId}_hcm_filter`; + const filter = info.filter = this.#createFilter(id); + this.#addTransferMapConversion(table, table, table, filter); + this.#addGrayConversion(filter); + const getSteps = (c, n) => { + const start = fgRGB[c] / 255; + const end = bgRGB[c] / 255; + const arr = new Array(n + 1); + for (let i = 0; i <= n; i++) { + arr[i] = start + i / n * (end - start); } + return arr.join(","); + }; + this.#addTransferMapConversion(getSteps(0, 5), getSteps(1, 5), getSteps(2, 5), filter); + info.url = this.#createUrl(id); + return info.url; + } + addAlphaFilter(map) { + let value = this.#cache.get(map); + if (value) { + return value; } - assert(offset === FontInfo.#OFFSET_NUMBERS, "FontInfo.write: Boolean properties offset mismatch"); - for (const prop of FontInfo.numbers) { - view.setFloat64(offset, font[prop]); - offset += 8; + const [tableA] = this.#createTables([map]); + const key = `alpha_${tableA}`; + value = this.#cache.get(key); + if (value) { + this.#cache.set(map, value); + return value; } - assert(offset === FontInfo.#OFFSET_BBOX, "FontInfo.write: Number properties offset mismatch"); - if (font.bbox) { - view.setUint8(offset++, 4); - for (const coord of font.bbox) { - view.setInt16(offset, coord, true); - offset += 2; - } - } else { - view.setUint8(offset++, 0); - offset += 2 * 4; + const id = `g_${this.#docId}_alpha_map_${this.#id++}`; + const url = this.#createUrl(id); + this.#cache.set(map, url); + this.#cache.set(key, url); + const filter = this.#createFilter(id); + this.#addTransferMapAlphaConversion(tableA, filter); + return url; + } + addLuminosityFilter(map) { + let value = this.#cache.get(map || "luminosity"); + if (value) { + return value; } - assert(offset === FontInfo.#OFFSET_FONT_MATRIX, "FontInfo.write: BBox properties offset mismatch"); - if (font.fontMatrix) { - view.setUint8(offset++, 6); - for (const point of font.fontMatrix) { - view.setFloat64(offset, point, true); - offset += 8; - } + let tableA, key; + if (map) { + [tableA] = this.#createTables([map]); + key = `luminosity_${tableA}`; } else { - view.setUint8(offset++, 0); - offset += 8 * 6; + key = "luminosity"; } - assert(offset === FontInfo.#OFFSET_DEFAULT_VMETRICS, "FontInfo.write: FontMatrix properties offset mismatch"); - if (font.defaultVMetrics) { - view.setUint8(offset++, 1); - for (const metric of font.defaultVMetrics) { - view.setInt16(offset, metric, true); - offset += 2; - } - } else { - view.setUint8(offset++, 0); - offset += 3 * 2; + value = this.#cache.get(key); + if (value) { + this.#cache.set(map, value); + return value; } - assert(offset === FontInfo.#OFFSET_STRINGS, "FontInfo.write: DefaultVMetrics properties offset mismatch"); - view.setUint32(FontInfo.#OFFSET_STRINGS, 0); - offset += 4; - for (const prop of FontInfo.strings) { - const encoded = encodedStrings[prop]; - const length = encoded.length; - view.setUint32(offset, length); - data.set(encoded, offset + 4); - offset += 4 + length; + const id = `g_${this.#docId}_luminosity_map_${this.#id++}`; + const url = this.#createUrl(id); + this.#cache.set(map, url); + this.#cache.set(key, url); + const filter = this.#createFilter(id); + this.#addLuminosityConversion(filter); + if (map) { + this.#addTransferMapAlphaConversion(tableA, filter); } - view.setUint32(FontInfo.#OFFSET_STRINGS, offset - FontInfo.#OFFSET_STRINGS - 4); - if (!systemFontInfoBuffer) { - view.setUint32(offset, 0); - offset += 4; - } else { - const length = systemFontInfoBuffer.byteLength; - view.setUint32(offset, length); - assert(offset + 4 + length <= buffer.byteLength, "FontInfo.write: Buffer overflow at systemFontInfo"); - data.set(new Uint8Array(systemFontInfoBuffer), offset + 4); - offset += 4 + length; + return url; + } + addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) { + const key = `${fgColor}-${bgColor}-${newFgColor}-${newBgColor}`; + let info = this.#hcmCache.get(filterName); + if (info?.key === key) { + return info.url; } - if (!cssFontInfoBuffer) { - view.setUint32(offset, 0); - offset += 4; + if (info) { + info.filter?.remove(); + info.key = key; + info.url = "none"; + info.filter = null; } else { - const length = cssFontInfoBuffer.byteLength; - view.setUint32(offset, length); - assert(offset + 4 + length <= buffer.byteLength, "FontInfo.write: Buffer overflow at cssFontInfo"); - data.set(new Uint8Array(cssFontInfoBuffer), offset + 4); - offset += 4 + length; + info = { + key, + url: "none", + filter: null + }; + this.#hcmCache.set(filterName, info); } - if (font.data === undefined) { - view.setUint32(offset, 0); - offset += 4; - } else { - view.setUint32(offset, font.data.length); - data.set(font.data, offset + 4); - offset += 4 + font.data.length; + if (!fgColor || !bgColor) { + return info.url; } - assert(offset <= buffer.byteLength, "FontInfo.write: Buffer overflow"); - return buffer.transferToFixedLength(offset); + const [fgRGB, bgRGB] = [fgColor, bgColor].map(this.#getRGB.bind(this)); + let fgGray = Math.round(0.2126 * fgRGB[0] + 0.7152 * fgRGB[1] + 0.0722 * fgRGB[2]); + let bgGray = Math.round(0.2126 * bgRGB[0] + 0.7152 * bgRGB[1] + 0.0722 * bgRGB[2]); + let [newFgRGB, newBgRGB] = [newFgColor, newBgColor].map(this.#getRGB.bind(this)); + if (bgGray < fgGray) { + [fgGray, bgGray, newFgRGB, newBgRGB] = [bgGray, fgGray, newBgRGB, newFgRGB]; + } + this.#defs.style.color = ""; + const getSteps = (fg, bg, n) => { + const arr = new Array(256); + const step = (bgGray - fgGray) / n; + const newStart = fg / 255; + const newStep = (bg - fg) / (255 * n); + let prev = 0; + for (let i = 0; i <= n; i++) { + const k = Math.round(fgGray + i * step); + const value = newStart + i * newStep; + for (let j = prev; j <= k; j++) { + arr[j] = value; + } + prev = k + 1; + } + for (let i = prev; i < 256; i++) { + arr[i] = arr[prev - 1]; + } + return arr.join(","); + }; + const id = `g_${this.#docId}_hcm_${filterName}_filter`; + const filter = info.filter = this.#createFilter(id); + this.#addGrayConversion(filter); + this.#addTransferMapConversion(getSteps(newFgRGB[0], newBgRGB[0], 5), getSteps(newFgRGB[1], newBgRGB[1], 5), getSteps(newFgRGB[2], newBgRGB[2], 5), filter); + info.url = this.#createUrl(id); + return info.url; + } + destroy(keepHCM = false) { + if (keepHCM && this.#_hcmCache?.size) { + return; + } + this.#_defs?.parentNode.parentNode.remove(); + this.#_defs = null; + this.#_cache?.clear(); + this.#_cache = null; + this.#_hcmCache?.clear(); + this.#_hcmCache = null; + this.#id = 0; + } + #addLuminosityConversion(filter) { + const feColorMatrix = this.#document.createElementNS(SVG_NS, "feColorMatrix"); + feColorMatrix.setAttribute("type", "matrix"); + feColorMatrix.setAttribute("values", "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.59 0.11 0 0"); + filter.append(feColorMatrix); + } + #addGrayConversion(filter) { + const feColorMatrix = this.#document.createElementNS(SVG_NS, "feColorMatrix"); + feColorMatrix.setAttribute("type", "matrix"); + feColorMatrix.setAttribute("values", "0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"); + filter.append(feColorMatrix); + } + #createFilter(id) { + const filter = this.#document.createElementNS(SVG_NS, "filter"); + filter.setAttribute("color-interpolation-filters", "sRGB"); + filter.setAttribute("id", id); + this.#defs.append(filter); + return filter; + } + #appendFeFunc(feComponentTransfer, func, table) { + const feFunc = this.#document.createElementNS(SVG_NS, func); + feFunc.setAttribute("type", "discrete"); + feFunc.setAttribute("tableValues", table); + feComponentTransfer.append(feFunc); + } + #addTransferMapConversion(rTable, gTable, bTable, filter) { + const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer"); + filter.append(feComponentTransfer); + this.#appendFeFunc(feComponentTransfer, "feFuncR", rTable); + this.#appendFeFunc(feComponentTransfer, "feFuncG", gTable); + this.#appendFeFunc(feComponentTransfer, "feFuncB", bTable); + } + #addTransferMapAlphaConversion(aTable, filter) { + const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer"); + filter.append(feComponentTransfer); + this.#appendFeFunc(feComponentTransfer, "feFuncA", aTable); + } + #getRGB(color) { + this.#defs.style.color = color; + return getRGB(getComputedStyle(this.#defs).getPropertyValue("color")); } } @@ -12802,7 +13032,7 @@ function getDocument(src = {}) { } const docParams = { docId, - apiVersion: "5.4.329", + apiVersion: "5.4.370", data, password, disableAutoFetch, @@ -13987,9 +14217,12 @@ class WorkerTransport { break; case "FontPath": case "Image": - case "Pattern": this.commonObjs.resolve(id, exportedData); break; + case "Pattern": + const pattern = new PatternInfo(exportedData); + this.commonObjs.resolve(id, pattern.getIR()); + break; default: throw new Error(`Got unknown common object type ${type}`); } @@ -14379,8 +14612,8 @@ class InternalRenderTask { } } } -const version = "5.4.329"; -const build = "3eca60735"; +const version = "5.4.370"; +const build = "f6317ddbb"; ;// ./src/display/editor/color_picker.js @@ -14636,11 +14869,11 @@ class BasicColorPicker { const { editorType, colorType, - colorValue + color } = this.#editor; const input = this.#input = document.createElement("input"); input.type = "color"; - input.value = colorValue || "#000000"; + input.value = color || "#000000"; input.className = "basicColorPicker"; input.tabIndex = 0; input.setAttribute("data-l10n-id", BasicColorPicker.#l10nColor[editorType]); @@ -16962,6 +17195,9 @@ class PopupElement { } this.#firstElement.commentText = this.#commentText = text; } + focus() { + this.#firstElement.container?.focus(); + } get parentBoundingClientRect() { return this.#firstElement.layer.getBoundingClientRect(); } @@ -18213,6 +18449,7 @@ class FreeTextEditor extends AnnotationEditor { if (!this.annotationElementId) { this._uiManager.a11yAlert("pdfjs-editor-freetext-added-alert"); } + this.canAddComment = false; } static initialize(l10n, uiManager) { AnnotationEditor.initialize(l10n, uiManager); diff --git a/toolkit/components/pdfjs/content/build/pdf.scripting.mjs b/toolkit/components/pdfjs/content/build/pdf.scripting.mjs @@ -21,8 +21,8 @@ */ /** - * pdfjsVersion = 5.4.329 - * pdfjsBuild = 3eca60735 + * pdfjsVersion = 5.4.370 + * pdfjsBuild = f6317ddbb */ var __webpack_exports__ = {}; diff --git a/toolkit/components/pdfjs/content/build/pdf.worker.mjs b/toolkit/components/pdfjs/content/build/pdf.worker.mjs @@ -21,8 +21,8 @@ */ /** - * pdfjsVersion = 5.4.329 - * pdfjsBuild = 3eca60735 + * pdfjsVersion = 5.4.370 + * pdfjsBuild = f6317ddbb */ /******/ // The require scope /******/ var __webpack_require__ = {}; @@ -331,7 +331,8 @@ const DrawOPS = { moveTo: 0, lineTo: 1, curveTo: 2, - closePath: 3 + quadraticCurveTo: 3, + closePath: 4 }; const PasswordResponses = { NEED_PASSWORD: 1, @@ -520,6 +521,9 @@ class FeatureTest { static get isImageDecoderSupported() { return shadow(this, "isImageDecoderSupported", typeof ImageDecoder !== "undefined"); } + static get isFloat16ArraySupported() { + return shadow(this, "isFloat16ArraySupported", typeof Float16Array !== "undefined"); + } static get platform() { const { platform, @@ -1293,6 +1297,9 @@ class BaseStream { getBaseStreams() { return null; } + getOriginalStream() { + return this.stream?.getOriginalStream() || this; + } } ;// ./src/core/core_utils.js @@ -3898,7 +3905,7 @@ class DecodeStream extends BaseStream { return new Stream(this.buffer, start, length, dict); } getBaseStreams() { - return this.str ? this.str.getBaseStreams() : null; + return this.stream ? this.stream.getBaseStreams() : null; } } class StreamsSequenceStream extends DecodeStream { @@ -6979,7 +6986,7 @@ class Ascii85Stream extends DecodeStream { maybeLength *= 0.8; } super(maybeLength); - this.str = str; + this.stream = str; this.dict = str.dict; this.input = new Uint8Array(5); } @@ -6987,7 +6994,7 @@ class Ascii85Stream extends DecodeStream { const TILDA_CHAR = 0x7e; const Z_LOWER_CHAR = 0x7a; const EOF = -1; - const str = this.str; + const str = this.stream; let c = str.getByte(); while (isWhiteSpace(c)) { c = str.getByte(); @@ -7045,13 +7052,13 @@ class AsciiHexStream extends DecodeStream { maybeLength *= 0.5; } super(maybeLength); - this.str = str; + this.stream = str; this.dict = str.dict; this.firstDigit = -1; } readBlock() { const UPSTREAM_BLOCK_SIZE = 8000; - const bytes = this.str.getBytes(UPSTREAM_BLOCK_SIZE); + const bytes = this.stream.getBytes(UPSTREAM_BLOCK_SIZE); if (!bytes.length) { this.eof = true; return; @@ -7605,7 +7612,7 @@ class CCITTFaxDecoder { class CCITTFaxStream extends DecodeStream { constructor(str, maybeLength, params) { super(maybeLength); - this.str = str; + this.stream = str; this.dict = str.dict; if (!(params instanceof Dict)) { params = Dict.empty; @@ -7650,7 +7657,7 @@ const fixedDistCodeTab = [new Int32Array([0x50000, 0x50010, 0x50008, 0x50018, 0x class FlateStream extends DecodeStream { constructor(str, maybeLength) { super(maybeLength); - this.str = str; + this.stream = str; this.dict = str.dict; const cmf = str.getByte(); const flg = str.getByte(); @@ -7680,8 +7687,8 @@ class FlateStream extends DecodeStream { return data.subarray(0, length); } async asyncGetBytes() { - this.str.reset(); - const bytes = this.str.getBytes(); + this.stream.reset(); + const bytes = this.stream.getBytes(); try { const { readable, @@ -7707,7 +7714,7 @@ class FlateStream extends DecodeStream { } return data; } catch { - this.str = new Stream(bytes, 2, bytes.length, this.str.dict); + this.stream = new Stream(bytes, 2, bytes.length, this.stream.dict); this.reset(); return null; } @@ -7716,7 +7723,7 @@ class FlateStream extends DecodeStream { return true; } getBits(bits) { - const str = this.str; + const str = this.stream; let codeSize = this.codeSize; let codeBuf = this.codeBuf; let b; @@ -7733,7 +7740,7 @@ class FlateStream extends DecodeStream { return b; } getCode(table) { - const str = this.str; + const str = this.stream; const codes = table[0]; const maxLen = table[1]; let codeSize = this.codeSize; @@ -7791,7 +7798,7 @@ class FlateStream extends DecodeStream { } readBlock() { let buffer, hdr, len; - const str = this.str; + const str = this.stream; try { hdr = this.getBits(3); } catch (ex) { @@ -10103,7 +10110,7 @@ class JpxStream extends DecodeStream { class LZWStream extends DecodeStream { constructor(str, maybeLength, earlyChange) { super(maybeLength); - this.str = str; + this.stream = str; this.dict = str.dict; this.cachedData = 0; this.bitsCached = 0; @@ -10128,7 +10135,7 @@ class LZWStream extends DecodeStream { let bitsCached = this.bitsCached; let cachedData = this.cachedData; while (bitsCached < n) { - const c = this.str.getByte(); + const c = this.stream.getByte(); if (c === -1) { this.eof = true; return null; @@ -10233,7 +10240,7 @@ class PredictorStream extends DecodeStream { throw new FormatError(`Unsupported predictor: ${predictor}`); } this.readBlock = predictor === 2 ? this.readBlockTiff : this.readBlockPng; - this.str = str; + this.stream = str; this.dict = str.dict; const colors = this.colors = params.get("Colors") || 1; const bits = this.bits = params.get("BPC", "BitsPerComponent") || 8; @@ -10248,7 +10255,7 @@ class PredictorStream extends DecodeStream { const buffer = this.ensureBuffer(bufferLength + rowBytes); const bits = this.bits; const colors = this.colors; - const rawBytes = this.str.getBytes(rowBytes); + const rawBytes = this.stream.getBytes(rowBytes); this.eof = !rawBytes.length; if (this.eof) { return; @@ -10317,8 +10324,8 @@ class PredictorStream extends DecodeStream { readBlockPng() { const rowBytes = this.rowBytes; const pixBytes = this.pixBytes; - const predictor = this.str.getByte(); - const rawBytes = this.str.getBytes(rowBytes); + const predictor = this.stream.getByte(); + const rawBytes = this.stream.getBytes(rowBytes); this.eof = !rawBytes.length; if (this.eof) { return; @@ -10407,11 +10414,11 @@ class PredictorStream extends DecodeStream { class RunLengthStream extends DecodeStream { constructor(str, maybeLength) { super(maybeLength); - this.str = str; + this.stream = str; this.dict = str.dict; } readBlock() { - const repeatHeader = this.str.getBytes(2); + const repeatHeader = this.stream.getBytes(2); if (!repeatHeader || repeatHeader.length < 2 || repeatHeader[0] === 128) { this.eof = true; return; @@ -10423,7 +10430,7 @@ class RunLengthStream extends DecodeStream { buffer = this.ensureBuffer(bufferLength + n + 1); buffer[bufferLength++] = repeatHeader[1]; if (n > 0) { - const source = this.str.getBytes(n); + const source = this.stream.getBytes(n); buffer.set(source, bufferLength); bufferLength += n; } @@ -19534,16 +19541,16 @@ function lookupCmap(ranges, unicode) { function compileGlyf(code, cmds, font) { function moveTo(x, y) { if (firstPoint) { - cmds.add("L", firstPoint); + cmds.add(DrawOPS.lineTo, firstPoint); } firstPoint = [x, y]; - cmds.add("M", [x, y]); + cmds.add(DrawOPS.moveTo, [x, y]); } function lineTo(x, y) { - cmds.add("L", [x, y]); + cmds.add(DrawOPS.lineTo, [x, y]); } function quadraticCurveTo(xa, ya, x, y) { - cmds.add("Q", [xa, ya, x, y]); + cmds.add(DrawOPS.quadraticCurveTo, [xa, ya, x, y]); } let i = 0; const numberOfContours = readInt16(code, i); @@ -19696,16 +19703,16 @@ function compileGlyf(code, cmds, font) { function compileCharString(charStringCode, cmds, font, glyphId) { function moveTo(x, y) { if (firstPoint) { - cmds.add("L", firstPoint); + cmds.add(DrawOPS.lineTo, firstPoint); } firstPoint = [x, y]; - cmds.add("M", [x, y]); + cmds.add(DrawOPS.moveTo, [x, y]); } function lineTo(x, y) { - cmds.add("L", [x, y]); + cmds.add(DrawOPS.lineTo, [x, y]); } function bezierCurveTo(x1, y1, x2, y2, x, y) { - cmds.add("C", [x1, y1, x2, y2, x, y]); + cmds.add(DrawOPS.curveTo, [x1, y1, x2, y2, x, y]); } const stack = []; let x = 0, @@ -20059,7 +20066,7 @@ class Commands { for (let i = 0, ii = args.length; i < ii; i += 2) { Util.applyTransform(args, currentTransform, i); } - this.cmds.push(`${cmd}${args.join(" ")}`); + this.cmds.push(cmd, ...args); } else { this.cmds.push(cmd); } @@ -20076,8 +20083,8 @@ class Commands { restore() { this.currentTransform = this.transformStack.pop() || [1, 0, 0, 1, 0, 0]; } - getSVG() { - return this.cmds.join(""); + getPath() { + return new Float16Array(this.cmds); } } class CompiledFont { @@ -20126,8 +20133,8 @@ class CompiledFont { const cmds = new Commands(); cmds.transform(fontMatrix.slice()); this.compileGlyphImpl(code, cmds, glyphId); - cmds.add("Z"); - return cmds.getSVG(); + cmds.add(DrawOPS.closePath); + return cmds.getPath(); } compileGlyphImpl() { unreachable("Children classes should implement this."); @@ -27183,6 +27190,660 @@ class ErrorFont { } } +;// ./src/shared/obj-bin-transform.js + +class CssFontInfo { + #buffer; + #view; + #decoder; + static strings = ["fontFamily", "fontWeight", "italicAngle"]; + static write(info) { + const encoder = new TextEncoder(); + const encodedStrings = {}; + let stringsLength = 0; + for (const prop of CssFontInfo.strings) { + const encoded = encoder.encode(info[prop]); + encodedStrings[prop] = encoded; + stringsLength += 4 + encoded.length; + } + const buffer = new ArrayBuffer(stringsLength); + const data = new Uint8Array(buffer); + const view = new DataView(buffer); + let offset = 0; + for (const prop of CssFontInfo.strings) { + const encoded = encodedStrings[prop]; + const length = encoded.length; + view.setUint32(offset, length); + data.set(encoded, offset + 4); + offset += 4 + length; + } + assert(offset === buffer.byteLength, "CssFontInfo.write: Buffer overflow"); + return buffer; + } + constructor(buffer) { + this.#buffer = buffer; + this.#view = new DataView(this.#buffer); + this.#decoder = new TextDecoder(); + } + #readString(index) { + assert(index < CssFontInfo.strings.length, "Invalid string index"); + let offset = 0; + for (let i = 0; i < index; i++) { + offset += this.#view.getUint32(offset) + 4; + } + const length = this.#view.getUint32(offset); + return this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); + } + get fontFamily() { + return this.#readString(0); + } + get fontWeight() { + return this.#readString(1); + } + get italicAngle() { + return this.#readString(2); + } +} +class SystemFontInfo { + #buffer; + #view; + #decoder; + static strings = ["css", "loadedName", "baseFontName", "src"]; + static write(info) { + const encoder = new TextEncoder(); + const encodedStrings = {}; + let stringsLength = 0; + for (const prop of SystemFontInfo.strings) { + const encoded = encoder.encode(info[prop]); + encodedStrings[prop] = encoded; + stringsLength += 4 + encoded.length; + } + stringsLength += 4; + let encodedStyleStyle, + encodedStyleWeight, + lengthEstimate = 1 + stringsLength; + if (info.style) { + encodedStyleStyle = encoder.encode(info.style.style); + encodedStyleWeight = encoder.encode(info.style.weight); + lengthEstimate += 4 + encodedStyleStyle.length + 4 + encodedStyleWeight.length; + } + const buffer = new ArrayBuffer(lengthEstimate); + const data = new Uint8Array(buffer); + const view = new DataView(buffer); + let offset = 0; + view.setUint8(offset++, info.guessFallback ? 1 : 0); + view.setUint32(offset, 0); + offset += 4; + stringsLength = 0; + for (const prop of SystemFontInfo.strings) { + const encoded = encodedStrings[prop]; + const length = encoded.length; + stringsLength += 4 + length; + view.setUint32(offset, length); + data.set(encoded, offset + 4); + offset += 4 + length; + } + view.setUint32(offset - stringsLength - 4, stringsLength); + if (info.style) { + view.setUint32(offset, encodedStyleStyle.length); + data.set(encodedStyleStyle, offset + 4); + offset += 4 + encodedStyleStyle.length; + view.setUint32(offset, encodedStyleWeight.length); + data.set(encodedStyleWeight, offset + 4); + offset += 4 + encodedStyleWeight.length; + } + assert(offset <= buffer.byteLength, "SubstitionInfo.write: Buffer overflow"); + return buffer.transferToFixedLength(offset); + } + constructor(buffer) { + this.#buffer = buffer; + this.#view = new DataView(this.#buffer); + this.#decoder = new TextDecoder(); + } + get guessFallback() { + return this.#view.getUint8(0) !== 0; + } + #readString(index) { + assert(index < SystemFontInfo.strings.length, "Invalid string index"); + let offset = 5; + for (let i = 0; i < index; i++) { + offset += this.#view.getUint32(offset) + 4; + } + const length = this.#view.getUint32(offset); + return this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); + } + get css() { + return this.#readString(0); + } + get loadedName() { + return this.#readString(1); + } + get baseFontName() { + return this.#readString(2); + } + get src() { + return this.#readString(3); + } + get style() { + let offset = 1; + offset += 4 + this.#view.getUint32(offset); + const styleLength = this.#view.getUint32(offset); + const style = this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, styleLength)); + offset += 4 + styleLength; + const weightLength = this.#view.getUint32(offset); + const weight = this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, weightLength)); + return { + style, + weight + }; + } +} +class FontInfo { + static bools = ["black", "bold", "disableFontFace", "fontExtraProperties", "isInvalidPDFjsFont", "isType3Font", "italic", "missingFile", "remeasure", "vertical"]; + static numbers = ["ascent", "defaultWidth", "descent"]; + static strings = ["fallbackName", "loadedName", "mimetype", "name"]; + static #OFFSET_NUMBERS = Math.ceil(this.bools.length * 2 / 8); + static #OFFSET_BBOX = this.#OFFSET_NUMBERS + this.numbers.length * 8; + static #OFFSET_FONT_MATRIX = this.#OFFSET_BBOX + 1 + 2 * 4; + static #OFFSET_DEFAULT_VMETRICS = this.#OFFSET_FONT_MATRIX + 1 + 8 * 6; + static #OFFSET_STRINGS = this.#OFFSET_DEFAULT_VMETRICS + 1 + 2 * 3; + #buffer; + #decoder; + #view; + constructor({ + data, + extra + }) { + this.#buffer = data; + this.#decoder = new TextDecoder(); + this.#view = new DataView(this.#buffer); + if (extra) { + Object.assign(this, extra); + } + } + #readBoolean(index) { + assert(index < FontInfo.bools.length, "Invalid boolean index"); + const byteOffset = Math.floor(index / 4); + const bitOffset = index * 2 % 8; + const value = this.#view.getUint8(byteOffset) >> bitOffset & 0x03; + return value === 0x00 ? undefined : value === 0x02; + } + get black() { + return this.#readBoolean(0); + } + get bold() { + return this.#readBoolean(1); + } + get disableFontFace() { + return this.#readBoolean(2); + } + get fontExtraProperties() { + return this.#readBoolean(3); + } + get isInvalidPDFjsFont() { + return this.#readBoolean(4); + } + get isType3Font() { + return this.#readBoolean(5); + } + get italic() { + return this.#readBoolean(6); + } + get missingFile() { + return this.#readBoolean(7); + } + get remeasure() { + return this.#readBoolean(8); + } + get vertical() { + return this.#readBoolean(9); + } + #readNumber(index) { + assert(index < FontInfo.numbers.length, "Invalid number index"); + return this.#view.getFloat64(FontInfo.#OFFSET_NUMBERS + index * 8); + } + get ascent() { + return this.#readNumber(0); + } + get defaultWidth() { + return this.#readNumber(1); + } + get descent() { + return this.#readNumber(2); + } + get bbox() { + let offset = FontInfo.#OFFSET_BBOX; + const numCoords = this.#view.getUint8(offset); + if (numCoords === 0) { + return undefined; + } + offset += 1; + const bbox = []; + for (let i = 0; i < 4; i++) { + bbox.push(this.#view.getInt16(offset, true)); + offset += 2; + } + return bbox; + } + get fontMatrix() { + let offset = FontInfo.#OFFSET_FONT_MATRIX; + const numPoints = this.#view.getUint8(offset); + if (numPoints === 0) { + return undefined; + } + offset += 1; + const fontMatrix = []; + for (let i = 0; i < 6; i++) { + fontMatrix.push(this.#view.getFloat64(offset, true)); + offset += 8; + } + return fontMatrix; + } + get defaultVMetrics() { + let offset = FontInfo.#OFFSET_DEFAULT_VMETRICS; + const numMetrics = this.#view.getUint8(offset); + if (numMetrics === 0) { + return undefined; + } + offset += 1; + const defaultVMetrics = []; + for (let i = 0; i < 3; i++) { + defaultVMetrics.push(this.#view.getInt16(offset, true)); + offset += 2; + } + return defaultVMetrics; + } + #readString(index) { + assert(index < FontInfo.strings.length, "Invalid string index"); + let offset = FontInfo.#OFFSET_STRINGS + 4; + for (let i = 0; i < index; i++) { + offset += this.#view.getUint32(offset) + 4; + } + const length = this.#view.getUint32(offset); + const stringData = new Uint8Array(length); + stringData.set(new Uint8Array(this.#buffer, offset + 4, length)); + return this.#decoder.decode(stringData); + } + get fallbackName() { + return this.#readString(0); + } + get loadedName() { + return this.#readString(1); + } + get mimetype() { + return this.#readString(2); + } + get name() { + return this.#readString(3); + } + get data() { + let offset = FontInfo.#OFFSET_STRINGS; + const stringsLength = this.#view.getUint32(offset); + offset += 4 + stringsLength; + const systemFontInfoLength = this.#view.getUint32(offset); + offset += 4 + systemFontInfoLength; + const cssFontInfoLength = this.#view.getUint32(offset); + offset += 4 + cssFontInfoLength; + const length = this.#view.getUint32(offset); + if (length === 0) { + return undefined; + } + return new Uint8Array(this.#buffer, offset + 4, length); + } + clearData() { + let offset = FontInfo.#OFFSET_STRINGS; + const stringsLength = this.#view.getUint32(offset); + offset += 4 + stringsLength; + const systemFontInfoLength = this.#view.getUint32(offset); + offset += 4 + systemFontInfoLength; + const cssFontInfoLength = this.#view.getUint32(offset); + offset += 4 + cssFontInfoLength; + const length = this.#view.getUint32(offset); + const data = new Uint8Array(this.#buffer, offset + 4, length); + data.fill(0); + this.#view.setUint32(offset, 0); + } + get cssFontInfo() { + let offset = FontInfo.#OFFSET_STRINGS; + const stringsLength = this.#view.getUint32(offset); + offset += 4 + stringsLength; + const systemFontInfoLength = this.#view.getUint32(offset); + offset += 4 + systemFontInfoLength; + const cssFontInfoLength = this.#view.getUint32(offset); + if (cssFontInfoLength === 0) { + return null; + } + const cssFontInfoData = new Uint8Array(cssFontInfoLength); + cssFontInfoData.set(new Uint8Array(this.#buffer, offset + 4, cssFontInfoLength)); + return new CssFontInfo(cssFontInfoData.buffer); + } + get systemFontInfo() { + let offset = FontInfo.#OFFSET_STRINGS; + const stringsLength = this.#view.getUint32(offset); + offset += 4 + stringsLength; + const systemFontInfoLength = this.#view.getUint32(offset); + if (systemFontInfoLength === 0) { + return null; + } + const systemFontInfoData = new Uint8Array(systemFontInfoLength); + systemFontInfoData.set(new Uint8Array(this.#buffer, offset + 4, systemFontInfoLength)); + return new SystemFontInfo(systemFontInfoData.buffer); + } + static write(font) { + const systemFontInfoBuffer = font.systemFontInfo ? SystemFontInfo.write(font.systemFontInfo) : null; + const cssFontInfoBuffer = font.cssFontInfo ? CssFontInfo.write(font.cssFontInfo) : null; + const encoder = new TextEncoder(); + const encodedStrings = {}; + let stringsLength = 0; + for (const prop of FontInfo.strings) { + encodedStrings[prop] = encoder.encode(font[prop]); + stringsLength += 4 + encodedStrings[prop].length; + } + const lengthEstimate = FontInfo.#OFFSET_STRINGS + 4 + stringsLength + 4 + (systemFontInfoBuffer ? systemFontInfoBuffer.byteLength : 0) + 4 + (cssFontInfoBuffer ? cssFontInfoBuffer.byteLength : 0) + 4 + (font.data ? font.data.length : 0); + const buffer = new ArrayBuffer(lengthEstimate); + const data = new Uint8Array(buffer); + const view = new DataView(buffer); + let offset = 0; + const numBools = FontInfo.bools.length; + let boolByte = 0, + boolBit = 0; + for (let i = 0; i < numBools; i++) { + const value = font[FontInfo.bools[i]]; + const bits = value === undefined ? 0x00 : value ? 0x02 : 0x01; + boolByte |= bits << boolBit; + boolBit += 2; + if (boolBit === 8 || i === numBools - 1) { + view.setUint8(offset++, boolByte); + boolByte = 0; + boolBit = 0; + } + } + assert(offset === FontInfo.#OFFSET_NUMBERS, "FontInfo.write: Boolean properties offset mismatch"); + for (const prop of FontInfo.numbers) { + view.setFloat64(offset, font[prop]); + offset += 8; + } + assert(offset === FontInfo.#OFFSET_BBOX, "FontInfo.write: Number properties offset mismatch"); + if (font.bbox) { + view.setUint8(offset++, 4); + for (const coord of font.bbox) { + view.setInt16(offset, coord, true); + offset += 2; + } + } else { + view.setUint8(offset++, 0); + offset += 2 * 4; + } + assert(offset === FontInfo.#OFFSET_FONT_MATRIX, "FontInfo.write: BBox properties offset mismatch"); + if (font.fontMatrix) { + view.setUint8(offset++, 6); + for (const point of font.fontMatrix) { + view.setFloat64(offset, point, true); + offset += 8; + } + } else { + view.setUint8(offset++, 0); + offset += 8 * 6; + } + assert(offset === FontInfo.#OFFSET_DEFAULT_VMETRICS, "FontInfo.write: FontMatrix properties offset mismatch"); + if (font.defaultVMetrics) { + view.setUint8(offset++, 1); + for (const metric of font.defaultVMetrics) { + view.setInt16(offset, metric, true); + offset += 2; + } + } else { + view.setUint8(offset++, 0); + offset += 3 * 2; + } + assert(offset === FontInfo.#OFFSET_STRINGS, "FontInfo.write: DefaultVMetrics properties offset mismatch"); + view.setUint32(FontInfo.#OFFSET_STRINGS, 0); + offset += 4; + for (const prop of FontInfo.strings) { + const encoded = encodedStrings[prop]; + const length = encoded.length; + view.setUint32(offset, length); + data.set(encoded, offset + 4); + offset += 4 + length; + } + view.setUint32(FontInfo.#OFFSET_STRINGS, offset - FontInfo.#OFFSET_STRINGS - 4); + if (!systemFontInfoBuffer) { + view.setUint32(offset, 0); + offset += 4; + } else { + const length = systemFontInfoBuffer.byteLength; + view.setUint32(offset, length); + assert(offset + 4 + length <= buffer.byteLength, "FontInfo.write: Buffer overflow at systemFontInfo"); + data.set(new Uint8Array(systemFontInfoBuffer), offset + 4); + offset += 4 + length; + } + if (!cssFontInfoBuffer) { + view.setUint32(offset, 0); + offset += 4; + } else { + const length = cssFontInfoBuffer.byteLength; + view.setUint32(offset, length); + assert(offset + 4 + length <= buffer.byteLength, "FontInfo.write: Buffer overflow at cssFontInfo"); + data.set(new Uint8Array(cssFontInfoBuffer), offset + 4); + offset += 4 + length; + } + if (font.data === undefined) { + view.setUint32(offset, 0); + offset += 4; + } else { + view.setUint32(offset, font.data.length); + data.set(font.data, offset + 4); + offset += 4 + font.data.length; + } + assert(offset <= buffer.byteLength, "FontInfo.write: Buffer overflow"); + return buffer.transferToFixedLength(offset); + } +} +class PatternInfo { + static #KIND = 0; + static #HAS_BBOX = 1; + static #HAS_BACKGROUND = 2; + static #SHADING_TYPE = 3; + static #N_COORD = 4; + static #N_COLOR = 8; + static #N_STOP = 12; + static #N_FIGURES = 16; + constructor(buffer) { + this.buffer = buffer; + this.view = new DataView(buffer); + this.data = new Uint8Array(buffer); + } + static write(ir) { + let kind, + bbox = null, + coords = [], + colors = [], + colorStops = [], + figures = [], + shadingType = null, + background = null; + switch (ir[0]) { + case "RadialAxial": + kind = ir[1] === "axial" ? 1 : 2; + bbox = ir[2]; + colorStops = ir[3]; + if (kind === 1) { + coords.push(...ir[4], ...ir[5]); + } else { + coords.push(ir[4][0], ir[4][1], ir[6], ir[5][0], ir[5][1], ir[7]); + } + break; + case "Mesh": + kind = 3; + shadingType = ir[1]; + coords = ir[2]; + colors = ir[3]; + figures = ir[4] || []; + bbox = ir[6]; + background = ir[7]; + break; + default: + throw new Error(`Unsupported pattern type: ${ir[0]}`); + } + const nCoord = Math.floor(coords.length / 2); + const nColor = Math.floor(colors.length / 3); + const nStop = colorStops.length; + const nFigures = figures.length; + let figuresSize = 0; + for (const figure of figures) { + figuresSize += 1; + figuresSize = Math.ceil(figuresSize / 4) * 4; + figuresSize += 4 + figure.coords.length * 4; + figuresSize += 4 + figure.colors.length * 4; + if (figure.verticesPerRow !== undefined) { + figuresSize += 4; + } + } + const byteLen = 20 + nCoord * 8 + nColor * 3 + nStop * 8 + (bbox ? 16 : 0) + (background ? 3 : 0) + figuresSize; + const buffer = new ArrayBuffer(byteLen); + const dataView = new DataView(buffer); + const u8data = new Uint8Array(buffer); + dataView.setUint8(PatternInfo.#KIND, kind); + dataView.setUint8(PatternInfo.#HAS_BBOX, bbox ? 1 : 0); + dataView.setUint8(PatternInfo.#HAS_BACKGROUND, background ? 1 : 0); + dataView.setUint8(PatternInfo.#SHADING_TYPE, shadingType); + dataView.setUint32(PatternInfo.#N_COORD, nCoord, true); + dataView.setUint32(PatternInfo.#N_COLOR, nColor, true); + dataView.setUint32(PatternInfo.#N_STOP, nStop, true); + dataView.setUint32(PatternInfo.#N_FIGURES, nFigures, true); + let offset = 20; + const coordsView = new Float32Array(buffer, offset, nCoord * 2); + coordsView.set(coords); + offset += nCoord * 8; + u8data.set(colors, offset); + offset += nColor * 3; + for (const [pos, hex] of colorStops) { + dataView.setFloat32(offset, pos, true); + offset += 4; + dataView.setUint32(offset, parseInt(hex.slice(1), 16), true); + offset += 4; + } + if (bbox) { + for (const v of bbox) { + dataView.setFloat32(offset, v, true); + offset += 4; + } + } + if (background) { + u8data.set(background, offset); + offset += 3; + } + for (let i = 0; i < figures.length; i++) { + const figure = figures[i]; + dataView.setUint8(offset, figure.type); + offset += 1; + offset = Math.ceil(offset / 4) * 4; + dataView.setUint32(offset, figure.coords.length, true); + offset += 4; + const figureCoordsView = new Int32Array(buffer, offset, figure.coords.length); + figureCoordsView.set(figure.coords); + offset += figure.coords.length * 4; + dataView.setUint32(offset, figure.colors.length, true); + offset += 4; + const colorsView = new Int32Array(buffer, offset, figure.colors.length); + colorsView.set(figure.colors); + offset += figure.colors.length * 4; + if (figure.verticesPerRow !== undefined) { + dataView.setUint32(offset, figure.verticesPerRow, true); + offset += 4; + } + } + return buffer; + } + getIR() { + const dataView = this.view; + const kind = this.data[PatternInfo.#KIND]; + const hasBBox = !!this.data[PatternInfo.#HAS_BBOX]; + const hasBackground = !!this.data[PatternInfo.#HAS_BACKGROUND]; + const nCoord = dataView.getUint32(PatternInfo.#N_COORD, true); + const nColor = dataView.getUint32(PatternInfo.#N_COLOR, true); + const nStop = dataView.getUint32(PatternInfo.#N_STOP, true); + const nFigures = dataView.getUint32(PatternInfo.#N_FIGURES, true); + let offset = 20; + const coords = new Float32Array(this.buffer, offset, nCoord * 2); + offset += nCoord * 8; + const colors = new Uint8Array(this.buffer, offset, nColor * 3); + offset += nColor * 3; + const stops = []; + for (let i = 0; i < nStop; ++i) { + const p = dataView.getFloat32(offset, true); + offset += 4; + const rgb = dataView.getUint32(offset, true); + offset += 4; + stops.push([p, `#${rgb.toString(16).padStart(6, "0")}`]); + } + let bbox = null; + if (hasBBox) { + bbox = []; + for (let i = 0; i < 4; ++i) { + bbox.push(dataView.getFloat32(offset, true)); + offset += 4; + } + } + let background = null; + if (hasBackground) { + background = new Uint8Array(this.buffer, offset, 3); + offset += 3; + } + const figures = []; + for (let i = 0; i < nFigures; ++i) { + const type = dataView.getUint8(offset); + offset += 1; + offset = Math.ceil(offset / 4) * 4; + const coordsLength = dataView.getUint32(offset, true); + offset += 4; + const figureCoords = new Int32Array(this.buffer, offset, coordsLength); + offset += coordsLength * 4; + const colorsLength = dataView.getUint32(offset, true); + offset += 4; + const figureColors = new Int32Array(this.buffer, offset, colorsLength); + offset += colorsLength * 4; + const figure = { + type, + coords: figureCoords, + colors: figureColors + }; + if (type === MeshFigureType.LATTICE) { + figure.verticesPerRow = dataView.getUint32(offset, true); + offset += 4; + } + figures.push(figure); + } + if (kind === 1) { + return ["RadialAxial", "axial", bbox, stops, Array.from(coords.slice(0, 2)), Array.from(coords.slice(2, 4)), null, null]; + } + if (kind === 2) { + return ["RadialAxial", "radial", bbox, stops, [coords[0], coords[1]], [coords[3], coords[4]], coords[2], coords[5]]; + } + if (kind === 3) { + const shadingType = this.data[PatternInfo.#SHADING_TYPE]; + let bounds = null; + if (coords.length > 0) { + let minX = coords[0], + maxX = coords[0]; + let minY = coords[1], + maxY = coords[1]; + for (let i = 0; i < coords.length; i += 2) { + const x = coords[i], + y = coords[i + 1]; + minX = minX > x ? x : minX; + minY = minY > y ? y : minY; + maxX = maxX < x ? x : maxX; + maxY = maxY < y ? y : maxY; + } + bounds = [minX, minY, maxX, maxY]; + } + return ["Mesh", shadingType, coords, colors, figures, bounds, bbox, background]; + } + throw new Error(`Unsupported pattern kind: ${kind}`); + } +} + ;// ./src/core/pattern.js @@ -29911,456 +30572,6 @@ function bidi(str, startLevel = -1, vertical = false) { return createBidiText(chars.join(""), isLTR); } -;// ./src/shared/obj-bin-transform.js - -class CssFontInfo { - #buffer; - #view; - #decoder; - static strings = ["fontFamily", "fontWeight", "italicAngle"]; - static write(info) { - const encoder = new TextEncoder(); - const encodedStrings = {}; - let stringsLength = 0; - for (const prop of CssFontInfo.strings) { - const encoded = encoder.encode(info[prop]); - encodedStrings[prop] = encoded; - stringsLength += 4 + encoded.length; - } - const buffer = new ArrayBuffer(stringsLength); - const data = new Uint8Array(buffer); - const view = new DataView(buffer); - let offset = 0; - for (const prop of CssFontInfo.strings) { - const encoded = encodedStrings[prop]; - const length = encoded.length; - view.setUint32(offset, length); - data.set(encoded, offset + 4); - offset += 4 + length; - } - assert(offset === buffer.byteLength, "CssFontInfo.write: Buffer overflow"); - return buffer; - } - constructor(buffer) { - this.#buffer = buffer; - this.#view = new DataView(this.#buffer); - this.#decoder = new TextDecoder(); - } - #readString(index) { - assert(index < CssFontInfo.strings.length, "Invalid string index"); - let offset = 0; - for (let i = 0; i < index; i++) { - offset += this.#view.getUint32(offset) + 4; - } - const length = this.#view.getUint32(offset); - return this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); - } - get fontFamily() { - return this.#readString(0); - } - get fontWeight() { - return this.#readString(1); - } - get italicAngle() { - return this.#readString(2); - } -} -class SystemFontInfo { - #buffer; - #view; - #decoder; - static strings = ["css", "loadedName", "baseFontName", "src"]; - static write(info) { - const encoder = new TextEncoder(); - const encodedStrings = {}; - let stringsLength = 0; - for (const prop of SystemFontInfo.strings) { - const encoded = encoder.encode(info[prop]); - encodedStrings[prop] = encoded; - stringsLength += 4 + encoded.length; - } - stringsLength += 4; - let encodedStyleStyle, - encodedStyleWeight, - lengthEstimate = 1 + stringsLength; - if (info.style) { - encodedStyleStyle = encoder.encode(info.style.style); - encodedStyleWeight = encoder.encode(info.style.weight); - lengthEstimate += 4 + encodedStyleStyle.length + 4 + encodedStyleWeight.length; - } - const buffer = new ArrayBuffer(lengthEstimate); - const data = new Uint8Array(buffer); - const view = new DataView(buffer); - let offset = 0; - view.setUint8(offset++, info.guessFallback ? 1 : 0); - view.setUint32(offset, 0); - offset += 4; - stringsLength = 0; - for (const prop of SystemFontInfo.strings) { - const encoded = encodedStrings[prop]; - const length = encoded.length; - stringsLength += 4 + length; - view.setUint32(offset, length); - data.set(encoded, offset + 4); - offset += 4 + length; - } - view.setUint32(offset - stringsLength - 4, stringsLength); - if (info.style) { - view.setUint32(offset, encodedStyleStyle.length); - data.set(encodedStyleStyle, offset + 4); - offset += 4 + encodedStyleStyle.length; - view.setUint32(offset, encodedStyleWeight.length); - data.set(encodedStyleWeight, offset + 4); - offset += 4 + encodedStyleWeight.length; - } - assert(offset <= buffer.byteLength, "SubstitionInfo.write: Buffer overflow"); - return buffer.transferToFixedLength(offset); - } - constructor(buffer) { - this.#buffer = buffer; - this.#view = new DataView(this.#buffer); - this.#decoder = new TextDecoder(); - } - get guessFallback() { - return this.#view.getUint8(0) !== 0; - } - #readString(index) { - assert(index < SystemFontInfo.strings.length, "Invalid string index"); - let offset = 5; - for (let i = 0; i < index; i++) { - offset += this.#view.getUint32(offset) + 4; - } - const length = this.#view.getUint32(offset); - return this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); - } - get css() { - return this.#readString(0); - } - get loadedName() { - return this.#readString(1); - } - get baseFontName() { - return this.#readString(2); - } - get src() { - return this.#readString(3); - } - get style() { - let offset = 1; - offset += 4 + this.#view.getUint32(offset); - const styleLength = this.#view.getUint32(offset); - const style = this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, styleLength)); - offset += 4 + styleLength; - const weightLength = this.#view.getUint32(offset); - const weight = this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, weightLength)); - return { - style, - weight - }; - } -} -class FontInfo { - static bools = ["black", "bold", "disableFontFace", "fontExtraProperties", "isInvalidPDFjsFont", "isType3Font", "italic", "missingFile", "remeasure", "vertical"]; - static numbers = ["ascent", "defaultWidth", "descent"]; - static strings = ["fallbackName", "loadedName", "mimetype", "name"]; - static #OFFSET_NUMBERS = Math.ceil(this.bools.length * 2 / 8); - static #OFFSET_BBOX = this.#OFFSET_NUMBERS + this.numbers.length * 8; - static #OFFSET_FONT_MATRIX = this.#OFFSET_BBOX + 1 + 2 * 4; - static #OFFSET_DEFAULT_VMETRICS = this.#OFFSET_FONT_MATRIX + 1 + 8 * 6; - static #OFFSET_STRINGS = this.#OFFSET_DEFAULT_VMETRICS + 1 + 2 * 3; - #buffer; - #decoder; - #view; - constructor({ - data, - extra - }) { - this.#buffer = data; - this.#decoder = new TextDecoder(); - this.#view = new DataView(this.#buffer); - if (extra) { - Object.assign(this, extra); - } - } - #readBoolean(index) { - assert(index < FontInfo.bools.length, "Invalid boolean index"); - const byteOffset = Math.floor(index / 4); - const bitOffset = index * 2 % 8; - const value = this.#view.getUint8(byteOffset) >> bitOffset & 0x03; - return value === 0x00 ? undefined : value === 0x02; - } - get black() { - return this.#readBoolean(0); - } - get bold() { - return this.#readBoolean(1); - } - get disableFontFace() { - return this.#readBoolean(2); - } - get fontExtraProperties() { - return this.#readBoolean(3); - } - get isInvalidPDFjsFont() { - return this.#readBoolean(4); - } - get isType3Font() { - return this.#readBoolean(5); - } - get italic() { - return this.#readBoolean(6); - } - get missingFile() { - return this.#readBoolean(7); - } - get remeasure() { - return this.#readBoolean(8); - } - get vertical() { - return this.#readBoolean(9); - } - #readNumber(index) { - assert(index < FontInfo.numbers.length, "Invalid number index"); - return this.#view.getFloat64(FontInfo.#OFFSET_NUMBERS + index * 8); - } - get ascent() { - return this.#readNumber(0); - } - get defaultWidth() { - return this.#readNumber(1); - } - get descent() { - return this.#readNumber(2); - } - get bbox() { - let offset = FontInfo.#OFFSET_BBOX; - const numCoords = this.#view.getUint8(offset); - if (numCoords === 0) { - return undefined; - } - offset += 1; - const bbox = []; - for (let i = 0; i < 4; i++) { - bbox.push(this.#view.getInt16(offset, true)); - offset += 2; - } - return bbox; - } - get fontMatrix() { - let offset = FontInfo.#OFFSET_FONT_MATRIX; - const numPoints = this.#view.getUint8(offset); - if (numPoints === 0) { - return undefined; - } - offset += 1; - const fontMatrix = []; - for (let i = 0; i < 6; i++) { - fontMatrix.push(this.#view.getFloat64(offset, true)); - offset += 8; - } - return fontMatrix; - } - get defaultVMetrics() { - let offset = FontInfo.#OFFSET_DEFAULT_VMETRICS; - const numMetrics = this.#view.getUint8(offset); - if (numMetrics === 0) { - return undefined; - } - offset += 1; - const defaultVMetrics = []; - for (let i = 0; i < 3; i++) { - defaultVMetrics.push(this.#view.getInt16(offset, true)); - offset += 2; - } - return defaultVMetrics; - } - #readString(index) { - assert(index < FontInfo.strings.length, "Invalid string index"); - let offset = FontInfo.#OFFSET_STRINGS + 4; - for (let i = 0; i < index; i++) { - offset += this.#view.getUint32(offset) + 4; - } - const length = this.#view.getUint32(offset); - const stringData = new Uint8Array(length); - stringData.set(new Uint8Array(this.#buffer, offset + 4, length)); - return this.#decoder.decode(stringData); - } - get fallbackName() { - return this.#readString(0); - } - get loadedName() { - return this.#readString(1); - } - get mimetype() { - return this.#readString(2); - } - get name() { - return this.#readString(3); - } - get data() { - let offset = FontInfo.#OFFSET_STRINGS; - const stringsLength = this.#view.getUint32(offset); - offset += 4 + stringsLength; - const systemFontInfoLength = this.#view.getUint32(offset); - offset += 4 + systemFontInfoLength; - const cssFontInfoLength = this.#view.getUint32(offset); - offset += 4 + cssFontInfoLength; - const length = this.#view.getUint32(offset); - if (length === 0) { - return undefined; - } - return new Uint8Array(this.#buffer, offset + 4, length); - } - clearData() { - let offset = FontInfo.#OFFSET_STRINGS; - const stringsLength = this.#view.getUint32(offset); - offset += 4 + stringsLength; - const systemFontInfoLength = this.#view.getUint32(offset); - offset += 4 + systemFontInfoLength; - const cssFontInfoLength = this.#view.getUint32(offset); - offset += 4 + cssFontInfoLength; - const length = this.#view.getUint32(offset); - const data = new Uint8Array(this.#buffer, offset + 4, length); - data.fill(0); - this.#view.setUint32(offset, 0); - } - get cssFontInfo() { - let offset = FontInfo.#OFFSET_STRINGS; - const stringsLength = this.#view.getUint32(offset); - offset += 4 + stringsLength; - const systemFontInfoLength = this.#view.getUint32(offset); - offset += 4 + systemFontInfoLength; - const cssFontInfoLength = this.#view.getUint32(offset); - if (cssFontInfoLength === 0) { - return null; - } - const cssFontInfoData = new Uint8Array(cssFontInfoLength); - cssFontInfoData.set(new Uint8Array(this.#buffer, offset + 4, cssFontInfoLength)); - return new CssFontInfo(cssFontInfoData.buffer); - } - get systemFontInfo() { - let offset = FontInfo.#OFFSET_STRINGS; - const stringsLength = this.#view.getUint32(offset); - offset += 4 + stringsLength; - const systemFontInfoLength = this.#view.getUint32(offset); - if (systemFontInfoLength === 0) { - return null; - } - const systemFontInfoData = new Uint8Array(systemFontInfoLength); - systemFontInfoData.set(new Uint8Array(this.#buffer, offset + 4, systemFontInfoLength)); - return new SystemFontInfo(systemFontInfoData.buffer); - } - static write(font) { - const systemFontInfoBuffer = font.systemFontInfo ? SystemFontInfo.write(font.systemFontInfo) : null; - const cssFontInfoBuffer = font.cssFontInfo ? CssFontInfo.write(font.cssFontInfo) : null; - const encoder = new TextEncoder(); - const encodedStrings = {}; - let stringsLength = 0; - for (const prop of FontInfo.strings) { - encodedStrings[prop] = encoder.encode(font[prop]); - stringsLength += 4 + encodedStrings[prop].length; - } - const lengthEstimate = FontInfo.#OFFSET_STRINGS + 4 + stringsLength + 4 + (systemFontInfoBuffer ? systemFontInfoBuffer.byteLength : 0) + 4 + (cssFontInfoBuffer ? cssFontInfoBuffer.byteLength : 0) + 4 + (font.data ? font.data.length : 0); - const buffer = new ArrayBuffer(lengthEstimate); - const data = new Uint8Array(buffer); - const view = new DataView(buffer); - let offset = 0; - const numBools = FontInfo.bools.length; - let boolByte = 0, - boolBit = 0; - for (let i = 0; i < numBools; i++) { - const value = font[FontInfo.bools[i]]; - const bits = value === undefined ? 0x00 : value ? 0x02 : 0x01; - boolByte |= bits << boolBit; - boolBit += 2; - if (boolBit === 8 || i === numBools - 1) { - view.setUint8(offset++, boolByte); - boolByte = 0; - boolBit = 0; - } - } - assert(offset === FontInfo.#OFFSET_NUMBERS, "FontInfo.write: Boolean properties offset mismatch"); - for (const prop of FontInfo.numbers) { - view.setFloat64(offset, font[prop]); - offset += 8; - } - assert(offset === FontInfo.#OFFSET_BBOX, "FontInfo.write: Number properties offset mismatch"); - if (font.bbox) { - view.setUint8(offset++, 4); - for (const coord of font.bbox) { - view.setInt16(offset, coord, true); - offset += 2; - } - } else { - view.setUint8(offset++, 0); - offset += 2 * 4; - } - assert(offset === FontInfo.#OFFSET_FONT_MATRIX, "FontInfo.write: BBox properties offset mismatch"); - if (font.fontMatrix) { - view.setUint8(offset++, 6); - for (const point of font.fontMatrix) { - view.setFloat64(offset, point, true); - offset += 8; - } - } else { - view.setUint8(offset++, 0); - offset += 8 * 6; - } - assert(offset === FontInfo.#OFFSET_DEFAULT_VMETRICS, "FontInfo.write: FontMatrix properties offset mismatch"); - if (font.defaultVMetrics) { - view.setUint8(offset++, 1); - for (const metric of font.defaultVMetrics) { - view.setInt16(offset, metric, true); - offset += 2; - } - } else { - view.setUint8(offset++, 0); - offset += 3 * 2; - } - assert(offset === FontInfo.#OFFSET_STRINGS, "FontInfo.write: DefaultVMetrics properties offset mismatch"); - view.setUint32(FontInfo.#OFFSET_STRINGS, 0); - offset += 4; - for (const prop of FontInfo.strings) { - const encoded = encodedStrings[prop]; - const length = encoded.length; - view.setUint32(offset, length); - data.set(encoded, offset + 4); - offset += 4 + length; - } - view.setUint32(FontInfo.#OFFSET_STRINGS, offset - FontInfo.#OFFSET_STRINGS - 4); - if (!systemFontInfoBuffer) { - view.setUint32(offset, 0); - offset += 4; - } else { - const length = systemFontInfoBuffer.byteLength; - view.setUint32(offset, length); - assert(offset + 4 + length <= buffer.byteLength, "FontInfo.write: Buffer overflow at systemFontInfo"); - data.set(new Uint8Array(systemFontInfoBuffer), offset + 4); - offset += 4 + length; - } - if (!cssFontInfoBuffer) { - view.setUint32(offset, 0); - offset += 4; - } else { - const length = cssFontInfoBuffer.byteLength; - view.setUint32(offset, length); - assert(offset + 4 + length <= buffer.byteLength, "FontInfo.write: Buffer overflow at cssFontInfo"); - data.set(new Uint8Array(cssFontInfoBuffer), offset + 4); - offset += 4 + length; - } - if (font.data === undefined) { - view.setUint32(offset, 0); - offset += 4; - } else { - view.setUint32(offset, font.data.length); - data.set(font.data, offset + 4); - offset += 4 + font.data.length; - } - assert(offset <= buffer.byteLength, "FontInfo.write: Buffer overflow"); - return buffer.transferToFixedLength(offset); - } -} - ;// ./src/core/font_substitutions.js @@ -32572,7 +32783,10 @@ class PartialEvaluator { } localShadingPatternCache.set(shading, id); if (this.parsingType3Font) { - this.handler.send("commonobj", [id, "Pattern", patternIR]); + const transfers = []; + const patternBuffer = PatternInfo.write(patternIR); + transfers.push(patternBuffer); + this.handler.send("commonobj", [id, "Pattern", patternBuffer], transfers); } else { this.handler.send("obj", [id, this.pageIndex, "Pattern", patternIR]); } @@ -34579,7 +34793,7 @@ class PartialEvaluator { } hash.update(`${firstChar}-${lastChar}`); if (toUnicode instanceof BaseStream) { - const stream = toUnicode.str || toUnicode; + const stream = toUnicode.stream || toUnicode; const uint8array = stream.buffer ? new Uint8Array(stream.buffer.buffer, 0, stream.bufferLength) : new Uint8Array(stream.bytes.buffer, stream.start, stream.end - stream.start); hash.update(uint8array); } else if (toUnicode instanceof Name) { @@ -54020,7 +54234,7 @@ const chunkSize = 512; class DecryptStream extends DecodeStream { constructor(str, maybeLength, decrypt) { super(maybeLength); - this.str = str; + this.stream = str; this.dict = str.dict; this.decrypt = decrypt; this.nextChunk = null; @@ -54031,14 +54245,14 @@ class DecryptStream extends DecodeStream { if (this.initialized) { chunk = this.nextChunk; } else { - chunk = this.str.getBytes(chunkSize); + chunk = this.stream.getBytes(chunkSize); this.initialized = true; } if (!chunk?.length) { this.eof = true; return; } - this.nextChunk = this.str.getBytes(chunkSize); + this.nextChunk = this.stream.getBytes(chunkSize); const hasMoreData = this.nextChunk?.length > 0; const decrypt = this.decrypt; chunk = decrypt(chunk, !hasMoreData); @@ -58164,7 +58378,7 @@ class WorkerMessageHandler { docId, apiVersion } = docParams; - const workerVersion = "5.4.329"; + const workerVersion = "5.4.370"; if (apiVersion !== workerVersion) { throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`); } diff --git a/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs b/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs @@ -21,8 +21,8 @@ */ /** - * pdfjsVersion = 5.4.329 - * pdfjsBuild = 3eca60735 + * pdfjsVersion = 5.4.370 + * pdfjsBuild = f6317ddbb */ /******/ // The require scope /******/ var __webpack_require__ = {}; @@ -3454,11 +3454,18 @@ class CommentPopup { if (!correctPosition) { this.#editor.commentPopupPosition = [x, y]; } else { - const widthRatio = this._popupWidth / this.#editor.parentBoundingClientRect.width; + const parentRect = this.#editor.parentBoundingClientRect; + const widthRatio = this._popupWidth / parentRect.width; if (this.#isLTR && x + widthRatio > 1 || !this.#isLTR && x - widthRatio >= 0) { const buttonWidth = this.#editor.commentButtonWidth; x -= widthRatio - buttonWidth; } + const margin = 0.01; + if (this.#isLTR) { + x = Math.max(x, -parentRect.x / parentRect.width + margin); + } else { + x = Math.min(x, (window.innerWidth - parentRect.x) / parentRect.width - widthRatio - margin); + } } this.#posX = x; this.#posY = y; @@ -8213,7 +8220,7 @@ class PDFViewer { #textLayerMode = TextLayerMode.ENABLE; #viewerAlert = null; constructor(options) { - const viewerVersion = "5.4.329"; + const viewerVersion = "5.4.370"; if (version !== viewerVersion) { throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`); } diff --git a/toolkit/components/pdfjs/content/web/viewer.css b/toolkit/components/pdfjs/content/web/viewer.css @@ -2717,6 +2717,9 @@ --button-comment-hover-bg:light-dark(#e0e0e6, #52525e); --button-comment-hover-color:var(--button-comment-color); + --link-fg-color:light-dark(#0060df, #0df); + --link-hover-fg-color:light-dark(#0250bb, #80ebff); + @media screen and (forced-colors: active){ --comment-date-fg-color:CanvasText; --comment-bg-color:Canvas; @@ -2733,11 +2736,13 @@ --comment-indicator-selected-fg-color:SelectedItem; --button-comment-bg:ButtonFace; --button-comment-color:ButtonText; - --button-comment-active-bg:ButtonText; + --button-comment-active-bg:Highlight; --button-comment-active-color:HighlightText; --button-comment-border:1px solid ButtonText; --button-comment-hover-bg:Highlight; --button-comment-hover-color:HighlightText; + --link-fg-color:LinkText; + --link-hover-fg-color:LinkText; } } @@ -2792,8 +2797,8 @@ height:32px; padding:8px; border-radius:4px; - border:none; - background:none; + border:var(--button-comment-border); + background-color:var(--button-comment-bg); cursor:pointer; &::before{ @@ -2804,15 +2809,24 @@ mask-repeat:no-repeat; mask-position:center; mask-image:var(--comment-close-button-icon); - background-color:var(--comment-fg-color); + background-color:var(--button-comment-color); } &:hover{ - background-color:var(--comment-hover-bg-color); + background-color:var(--button-comment-hover-bg); + + &::before{ + background-color:var(--button-comment-hover-color); + } } &:active{ - background-color:var(--comment-active-bg-color); + border:var(--button-comment-active-border); + background-color:var(--button-comment-active-bg); + + &::before{ + background-color:var(--button-comment-active-color); + } } &:focus-visible{ @@ -2941,10 +2955,15 @@ height:auto; overflow-wrap:break-word; margin-block-start:15px; + color:var(--link-fg-color); &:focus-visible{ outline:var(--focus-ring-outline); } + + &:hover{ + color:var(--link-hover-fg-color); + } } } diff --git a/toolkit/components/pdfjs/content/web/viewer.mjs b/toolkit/components/pdfjs/content/web/viewer.mjs @@ -21,8 +21,8 @@ */ /** - * pdfjsVersion = 5.4.329 - * pdfjsBuild = 3eca60735 + * pdfjsVersion = 5.4.370 + * pdfjsBuild = f6317ddbb */ /******/ // The require scope /******/ var __webpack_require__ = {}; @@ -4301,11 +4301,18 @@ class CommentPopup { if (!correctPosition) { this.#editor.commentPopupPosition = [x, y]; } else { - const widthRatio = this._popupWidth / this.#editor.parentBoundingClientRect.width; + const parentRect = this.#editor.parentBoundingClientRect; + const widthRatio = this._popupWidth / parentRect.width; if (this.#isLTR && x + widthRatio > 1 || !this.#isLTR && x - widthRatio >= 0) { const buttonWidth = this.#editor.commentButtonWidth; x -= widthRatio - buttonWidth; } + const margin = 0.01; + if (this.#isLTR) { + x = Math.max(x, -parentRect.x / parentRect.width + margin); + } else { + x = Math.min(x, (window.innerWidth - parentRect.x) / parentRect.width - widthRatio - margin); + } } this.#posX = x; this.#posY = y; @@ -11398,7 +11405,7 @@ class PDFViewer { #textLayerMode = TextLayerMode.ENABLE; #viewerAlert = null; constructor(options) { - const viewerVersion = "5.4.329"; + const viewerVersion = "5.4.370"; if (version !== viewerVersion) { throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`); } diff --git a/toolkit/components/pdfjs/moz.yaml b/toolkit/components/pdfjs/moz.yaml @@ -20,8 +20,8 @@ origin: # Human-readable identifier for this version/release # Generally "version NNN", "tag SSS", "bookmark SSS" - release: 3eca60735ba27d0c6ec16b60b88dd6f1f5297e1c (2025-10-14T18:25:40Z). - revision: 3eca60735ba27d0c6ec16b60b88dd6f1f5297e1c + release: f6317ddbbb847b9d6e70aa843f651a93e470257b (2025-10-23T07:01:50Z). + revision: f6317ddbbb847b9d6e70aa843f651a93e470257b # The package's license, where possible using the mnemonic from # https://spdx.org/licenses/ diff --git a/toolkit/locales/en-US/toolkit/pdfviewer/viewer.ftl b/toolkit/locales/en-US/toolkit/pdfviewer/viewer.ftl @@ -700,5 +700,5 @@ pdfjs-editor-edit-comment-dialog-cancel-button = Cancel ## Edit a comment button in the editor toolbar -pdfjs-editor-edit-comment-button = - .title = Edit comment +pdfjs-editor-add-comment-button = + .title = Add comment