/** * @licstart The following is the entire license notice for the * JavaScript code in this page * * Copyright 2022 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @licend The above is the entire license notice for the * JavaScript code in this page */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OperatorList = void 0; var _util = require("../shared/util.js"); function addState(parentState, pattern, checkFn, iterateFn, processFn) { let state = parentState; for (let i = 0, ii = pattern.length - 1; i < ii; i++) { const item = pattern[i]; state = state[item] || (state[item] = []); } state[pattern.at(-1)] = { checkFn, iterateFn, processFn }; } const InitialState = []; addState(InitialState, [_util.OPS.save, _util.OPS.transform, _util.OPS.paintInlineImageXObject, _util.OPS.restore], null, function iterateInlineImageGroup(context, i) { const fnArray = context.fnArray; const iFirstSave = context.iCurr - 3; const pos = (i - iFirstSave) % 4; switch (pos) { case 0: return fnArray[i] === _util.OPS.save; case 1: return fnArray[i] === _util.OPS.transform; case 2: return fnArray[i] === _util.OPS.paintInlineImageXObject; case 3: return fnArray[i] === _util.OPS.restore; } throw new Error(`iterateInlineImageGroup - invalid pos: ${pos}`); }, function foundInlineImageGroup(context, i) { const MIN_IMAGES_IN_INLINE_IMAGES_BLOCK = 10; const MAX_IMAGES_IN_INLINE_IMAGES_BLOCK = 200; const MAX_WIDTH = 1000; const IMAGE_PADDING = 1; const fnArray = context.fnArray, argsArray = context.argsArray; const curr = context.iCurr; const iFirstSave = curr - 3; const iFirstTransform = curr - 2; const iFirstPIIXO = curr - 1; const count = Math.min(Math.floor((i - iFirstSave) / 4), MAX_IMAGES_IN_INLINE_IMAGES_BLOCK); if (count < MIN_IMAGES_IN_INLINE_IMAGES_BLOCK) { return i - (i - iFirstSave) % 4; } let maxX = 0; const map = []; let maxLineHeight = 0; let currentX = IMAGE_PADDING, currentY = IMAGE_PADDING; for (let q = 0; q < count; q++) { const transform = argsArray[iFirstTransform + (q << 2)]; const img = argsArray[iFirstPIIXO + (q << 2)][0]; if (currentX + img.width > MAX_WIDTH) { maxX = Math.max(maxX, currentX); currentY += maxLineHeight + 2 * IMAGE_PADDING; currentX = 0; maxLineHeight = 0; } map.push({ transform, x: currentX, y: currentY, w: img.width, h: img.height }); currentX += img.width + 2 * IMAGE_PADDING; maxLineHeight = Math.max(maxLineHeight, img.height); } const imgWidth = Math.max(maxX, currentX) + IMAGE_PADDING; const imgHeight = currentY + maxLineHeight + IMAGE_PADDING; const imgData = new Uint8Array(imgWidth * imgHeight * 4); const imgRowSize = imgWidth << 2; for (let q = 0; q < count; q++) { const data = argsArray[iFirstPIIXO + (q << 2)][0].data; const rowSize = map[q].w << 2; let dataOffset = 0; let offset = map[q].x + map[q].y * imgWidth << 2; imgData.set(data.subarray(0, rowSize), offset - imgRowSize); for (let k = 0, kk = map[q].h; k < kk; k++) { imgData.set(data.subarray(dataOffset, dataOffset + rowSize), offset); dataOffset += rowSize; offset += imgRowSize; } imgData.set(data.subarray(dataOffset - rowSize, dataOffset), offset); while (offset >= 0) { data[offset - 4] = data[offset]; data[offset - 3] = data[offset + 1]; data[offset - 2] = data[offset + 2]; data[offset - 1] = data[offset + 3]; data[offset + rowSize] = data[offset + rowSize - 4]; data[offset + rowSize + 1] = data[offset + rowSize - 3]; data[offset + rowSize + 2] = data[offset + rowSize - 2]; data[offset + rowSize + 3] = data[offset + rowSize - 1]; offset -= imgRowSize; } } fnArray.splice(iFirstSave, count * 4, _util.OPS.paintInlineImageXObjectGroup); argsArray.splice(iFirstSave, count * 4, [{ width: imgWidth, height: imgHeight, kind: _util.ImageKind.RGBA_32BPP, data: imgData }, map]); return iFirstSave + 1; }); addState(InitialState, [_util.OPS.save, _util.OPS.transform, _util.OPS.paintImageMaskXObject, _util.OPS.restore], null, function iterateImageMaskGroup(context, i) { const fnArray = context.fnArray; const iFirstSave = context.iCurr - 3; const pos = (i - iFirstSave) % 4; switch (pos) { case 0: return fnArray[i] === _util.OPS.save; case 1: return fnArray[i] === _util.OPS.transform; case 2: return fnArray[i] === _util.OPS.paintImageMaskXObject; case 3: return fnArray[i] === _util.OPS.restore; } throw new Error(`iterateImageMaskGroup - invalid pos: ${pos}`); }, function foundImageMaskGroup(context, i) { const MIN_IMAGES_IN_MASKS_BLOCK = 10; const MAX_IMAGES_IN_MASKS_BLOCK = 100; const MAX_SAME_IMAGES_IN_MASKS_BLOCK = 1000; const fnArray = context.fnArray, argsArray = context.argsArray; const curr = context.iCurr; const iFirstSave = curr - 3; const iFirstTransform = curr - 2; const iFirstPIMXO = curr - 1; let count = Math.floor((i - iFirstSave) / 4); if (count < MIN_IMAGES_IN_MASKS_BLOCK) { return i - (i - iFirstSave) % 4; } let isSameImage = false; let iTransform, transformArgs; const firstPIMXOArg0 = argsArray[iFirstPIMXO][0]; const firstTransformArg0 = argsArray[iFirstTransform][0], firstTransformArg1 = argsArray[iFirstTransform][1], firstTransformArg2 = argsArray[iFirstTransform][2], firstTransformArg3 = argsArray[iFirstTransform][3]; if (firstTransformArg1 === firstTransformArg2) { isSameImage = true; iTransform = iFirstTransform + 4; let iPIMXO = iFirstPIMXO + 4; for (let q = 1; q < count; q++, iTransform += 4, iPIMXO += 4) { transformArgs = argsArray[iTransform]; if (argsArray[iPIMXO][0] !== firstPIMXOArg0 || transformArgs[0] !== firstTransformArg0 || transformArgs[1] !== firstTransformArg1 || transformArgs[2] !== firstTransformArg2 || transformArgs[3] !== firstTransformArg3) { if (q < MIN_IMAGES_IN_MASKS_BLOCK) { isSameImage = false; } else { count = q; } break; } } } if (isSameImage) { count = Math.min(count, MAX_SAME_IMAGES_IN_MASKS_BLOCK); const positions = new Float32Array(count * 2); iTransform = iFirstTransform; for (let q = 0; q < count; q++, iTransform += 4) { transformArgs = argsArray[iTransform]; positions[q << 1] = transformArgs[4]; positions[(q << 1) + 1] = transformArgs[5]; } fnArray.splice(iFirstSave, count * 4, _util.OPS.paintImageMaskXObjectRepeat); argsArray.splice(iFirstSave, count * 4, [firstPIMXOArg0, firstTransformArg0, firstTransformArg1, firstTransformArg2, firstTransformArg3, positions]); } else { count = Math.min(count, MAX_IMAGES_IN_MASKS_BLOCK); const images = []; for (let q = 0; q < count; q++) { transformArgs = argsArray[iFirstTransform + (q << 2)]; const maskParams = argsArray[iFirstPIMXO + (q << 2)][0]; images.push({ data: maskParams.data, width: maskParams.width, height: maskParams.height, interpolate: maskParams.interpolate, count: maskParams.count, transform: transformArgs }); } fnArray.splice(iFirstSave, count * 4, _util.OPS.paintImageMaskXObjectGroup); argsArray.splice(iFirstSave, count * 4, [images]); } return iFirstSave + 1; }); addState(InitialState, [_util.OPS.save, _util.OPS.transform, _util.OPS.paintImageXObject, _util.OPS.restore], function (context) { const argsArray = context.argsArray; const iFirstTransform = context.iCurr - 2; return argsArray[iFirstTransform][1] === 0 && argsArray[iFirstTransform][2] === 0; }, function iterateImageGroup(context, i) { const fnArray = context.fnArray, argsArray = context.argsArray; const iFirstSave = context.iCurr - 3; const pos = (i - iFirstSave) % 4; switch (pos) { case 0: return fnArray[i] === _util.OPS.save; case 1: if (fnArray[i] !== _util.OPS.transform) { return false; } const iFirstTransform = context.iCurr - 2; const firstTransformArg0 = argsArray[iFirstTransform][0]; const firstTransformArg3 = argsArray[iFirstTransform][3]; if (argsArray[i][0] !== firstTransformArg0 || argsArray[i][1] !== 0 || argsArray[i][2] !== 0 || argsArray[i][3] !== firstTransformArg3) { return false; } return true; case 2: if (fnArray[i] !== _util.OPS.paintImageXObject) { return false; } const iFirstPIXO = context.iCurr - 1; const firstPIXOArg0 = argsArray[iFirstPIXO][0]; if (argsArray[i][0] !== firstPIXOArg0) { return false; } return true; case 3: return fnArray[i] === _util.OPS.restore; } throw new Error(`iterateImageGroup - invalid pos: ${pos}`); }, function (context, i) { const MIN_IMAGES_IN_BLOCK = 3; const MAX_IMAGES_IN_BLOCK = 1000; const fnArray = context.fnArray, argsArray = context.argsArray; const curr = context.iCurr; const iFirstSave = curr - 3; const iFirstTransform = curr - 2; const iFirstPIXO = curr - 1; const firstPIXOArg0 = argsArray[iFirstPIXO][0]; const firstTransformArg0 = argsArray[iFirstTransform][0]; const firstTransformArg3 = argsArray[iFirstTransform][3]; const count = Math.min(Math.floor((i - iFirstSave) / 4), MAX_IMAGES_IN_BLOCK); if (count < MIN_IMAGES_IN_BLOCK) { return i - (i - iFirstSave) % 4; } const positions = new Float32Array(count * 2); let iTransform = iFirstTransform; for (let q = 0; q < count; q++, iTransform += 4) { const transformArgs = argsArray[iTransform]; positions[q << 1] = transformArgs[4]; positions[(q << 1) + 1] = transformArgs[5]; } const args = [firstPIXOArg0, firstTransformArg0, firstTransformArg3, positions]; fnArray.splice(iFirstSave, count * 4, _util.OPS.paintImageXObjectRepeat); argsArray.splice(iFirstSave, count * 4, args); return iFirstSave + 1; }); addState(InitialState, [_util.OPS.beginText, _util.OPS.setFont, _util.OPS.setTextMatrix, _util.OPS.showText, _util.OPS.endText], null, function iterateShowTextGroup(context, i) { const fnArray = context.fnArray, argsArray = context.argsArray; const iFirstSave = context.iCurr - 4; const pos = (i - iFirstSave) % 5; switch (pos) { case 0: return fnArray[i] === _util.OPS.beginText; case 1: return fnArray[i] === _util.OPS.setFont; case 2: return fnArray[i] === _util.OPS.setTextMatrix; case 3: if (fnArray[i] !== _util.OPS.showText) { return false; } const iFirstSetFont = context.iCurr - 3; const firstSetFontArg0 = argsArray[iFirstSetFont][0]; const firstSetFontArg1 = argsArray[iFirstSetFont][1]; if (argsArray[i][0] !== firstSetFontArg0 || argsArray[i][1] !== firstSetFontArg1) { return false; } return true; case 4: return fnArray[i] === _util.OPS.endText; } throw new Error(`iterateShowTextGroup - invalid pos: ${pos}`); }, function (context, i) { const MIN_CHARS_IN_BLOCK = 3; const MAX_CHARS_IN_BLOCK = 1000; const fnArray = context.fnArray, argsArray = context.argsArray; const curr = context.iCurr; const iFirstBeginText = curr - 4; const iFirstSetFont = curr - 3; const iFirstSetTextMatrix = curr - 2; const iFirstShowText = curr - 1; const iFirstEndText = curr; const firstSetFontArg0 = argsArray[iFirstSetFont][0]; const firstSetFontArg1 = argsArray[iFirstSetFont][1]; let count = Math.min(Math.floor((i - iFirstBeginText) / 5), MAX_CHARS_IN_BLOCK); if (count < MIN_CHARS_IN_BLOCK) { return i - (i - iFirstBeginText) % 5; } let iFirst = iFirstBeginText; if (iFirstBeginText >= 4 && fnArray[iFirstBeginText - 4] === fnArray[iFirstSetFont] && fnArray[iFirstBeginText - 3] === fnArray[iFirstSetTextMatrix] && fnArray[iFirstBeginText - 2] === fnArray[iFirstShowText] && fnArray[iFirstBeginText - 1] === fnArray[iFirstEndText] && argsArray[iFirstBeginText - 4][0] === firstSetFontArg0 && argsArray[iFirstBeginText - 4][1] === firstSetFontArg1) { count++; iFirst -= 5; } let iEndText = iFirst + 4; for (let q = 1; q < count; q++) { fnArray.splice(iEndText, 3); argsArray.splice(iEndText, 3); iEndText += 2; } return iEndText + 1; }); class NullOptimizer { constructor(queue) { this.queue = queue; } _optimize() {} push(fn, args) { this.queue.fnArray.push(fn); this.queue.argsArray.push(args); this._optimize(); } flush() {} reset() {} } class QueueOptimizer extends NullOptimizer { constructor(queue) { super(queue); this.state = null; this.context = { iCurr: 0, fnArray: queue.fnArray, argsArray: queue.argsArray }; this.match = null; this.lastProcessed = 0; } _optimize() { const fnArray = this.queue.fnArray; let i = this.lastProcessed, ii = fnArray.length; let state = this.state; let match = this.match; if (!state && !match && i + 1 === ii && !InitialState[fnArray[i]]) { this.lastProcessed = ii; return; } const context = this.context; while (i < ii) { if (match) { const iterate = (0, match.iterateFn)(context, i); if (iterate) { i++; continue; } i = (0, match.processFn)(context, i + 1); ii = fnArray.length; match = null; state = null; if (i >= ii) { break; } } state = (state || InitialState)[fnArray[i]]; if (!state || Array.isArray(state)) { i++; continue; } context.iCurr = i; i++; if (state.checkFn && !(0, state.checkFn)(context)) { state = null; continue; } match = state; state = null; } this.state = state; this.match = match; this.lastProcessed = i; } flush() { while (this.match) { const length = this.queue.fnArray.length; this.lastProcessed = (0, this.match.processFn)(this.context, length); this.match = null; this.state = null; this._optimize(); } } reset() { this.state = null; this.match = null; this.lastProcessed = 0; } } class OperatorList { static get CHUNK_SIZE() { return (0, _util.shadow)(this, "CHUNK_SIZE", 1000); } static get CHUNK_SIZE_ABOUT() { return (0, _util.shadow)(this, "CHUNK_SIZE_ABOUT", this.CHUNK_SIZE - 5); } constructor(intent = 0, streamSink) { this._streamSink = streamSink; this.fnArray = []; this.argsArray = []; if (streamSink && !(intent & _util.RenderingIntentFlag.OPLIST)) { this.optimizer = new QueueOptimizer(this); } else { this.optimizer = new NullOptimizer(this); } this.dependencies = new Set(); this._totalLength = 0; this.weight = 0; this._resolved = streamSink ? null : Promise.resolve(); } get length() { return this.argsArray.length; } get ready() { return this._resolved || this._streamSink.ready; } get totalLength() { return this._totalLength + this.length; } addOp(fn, args) { this.optimizer.push(fn, args); this.weight++; if (this._streamSink) { if (this.weight >= OperatorList.CHUNK_SIZE) { this.flush(); } else if (this.weight >= OperatorList.CHUNK_SIZE_ABOUT && (fn === _util.OPS.restore || fn === _util.OPS.endText)) { this.flush(); } } } addImageOps(fn, args, optionalContent) { if (optionalContent !== undefined) { this.addOp(_util.OPS.beginMarkedContentProps, ["OC", optionalContent]); } this.addOp(fn, args); if (optionalContent !== undefined) { this.addOp(_util.OPS.endMarkedContent, []); } } addDependency(dependency) { if (this.dependencies.has(dependency)) { return; } this.dependencies.add(dependency); this.addOp(_util.OPS.dependency, [dependency]); } addDependencies(dependencies) { for (const dependency of dependencies) { this.addDependency(dependency); } } addOpList(opList) { if (!(opList instanceof OperatorList)) { (0, _util.warn)('addOpList - ignoring invalid "opList" parameter.'); return; } for (const dependency of opList.dependencies) { this.dependencies.add(dependency); } for (let i = 0, ii = opList.length; i < ii; i++) { this.addOp(opList.fnArray[i], opList.argsArray[i]); } } getIR() { return { fnArray: this.fnArray, argsArray: this.argsArray, length: this.length }; } get _transfers() { const transfers = []; const { fnArray, argsArray, length } = this; for (let i = 0; i < length; i++) { switch (fnArray[i]) { case _util.OPS.paintInlineImageXObject: case _util.OPS.paintInlineImageXObjectGroup: case _util.OPS.paintImageMaskXObject: const arg = argsArray[i][0]; if (!arg.cached && arg.data && arg.data.buffer instanceof ArrayBuffer) { transfers.push(arg.data.buffer); } break; } } return transfers; } flush(lastChunk = false, separateAnnots = null) { this.optimizer.flush(); const length = this.length; this._totalLength += length; this._streamSink.enqueue({ fnArray: this.fnArray, argsArray: this.argsArray, lastChunk, separateAnnots, length }, 1, this._transfers); this.dependencies.clear(); this.fnArray.length = 0; this.argsArray.length = 0; this.weight = 0; this.optimizer.reset(); } } exports.OperatorList = OperatorList;