diff --git a/eslint.config.mjs b/eslint.config.mjs index 277475fad..07cb66e14 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -35,6 +35,7 @@ export default [ "external/builder/fixtures_babel/", "external/openjpeg/", "external/qcms/", + "external/jbig2/", "external/quickjs/", "test/stats/results/", "test/tmp/", diff --git a/external/jbig2/LICENSE_JBIG2 b/external/jbig2/LICENSE_JBIG2 new file mode 100644 index 000000000..37a329c63 --- /dev/null +++ b/external/jbig2/LICENSE_JBIG2 @@ -0,0 +1,196 @@ +// Copyright 2014 The PDFium Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + 1. Definitions. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + END OF TERMS AND CONDITIONS + APPENDIX: How to apply the Apache License to your work. + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + Copyright [yyyy] [name of copyright owner] + 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 + https://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. diff --git a/external/jbig2/LICENSE_PDFJS_JBIG2 b/external/jbig2/LICENSE_PDFJS_JBIG2 new file mode 100644 index 000000000..f1845baf6 --- /dev/null +++ b/external/jbig2/LICENSE_PDFJS_JBIG2 @@ -0,0 +1,13 @@ +Copyright 2026 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. diff --git a/external/jbig2/README.md b/external/jbig2/README.md new file mode 100644 index 000000000..3736c91ee --- /dev/null +++ b/external/jbig2/README.md @@ -0,0 +1,12 @@ +## Build + +In order to generate the file `jbig2.js`: +* git clone https://github.com/mozilla/pdf.js.jbig2/ +* the build requires to have a [Docker](https://www.docker.com/) setup and then: + * `node build.js -C` to build the Docker image + * `node build.js -co /pdf.js/external/jbig2/` to compile the decoder + +## Licensing + +[PDFium](https://pdfium.googlesource.com/pdfium/) is under [Apache-2.0](https://pdfium.googlesource.com/pdfium/+/main/LICENSE) +and [pdf.js.jbig2](https://github.com/mozilla/pdf.js.jbig2/) is released under [Apache-2.0](https://github.com/mozilla/pdf.js.jbig2/blob/main/LICENSE) license so `jbig2.js` is released under [Apache-2.0](https://github.com/mozilla/pdf.js.jbig2/blob/main/LICENSE) license too. diff --git a/external/jbig2/jbig2.js b/external/jbig2/jbig2.js new file mode 100644 index 000000000..ab95ecb0f --- /dev/null +++ b/external/jbig2/jbig2.js @@ -0,0 +1,3 @@ +/* THIS FILE IS GENERATED - DO NOT EDIT */ +async function JBig2(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=true;var ENVIRONMENT_IS_WORKER=false;var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=import.meta.url;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);HEAPU8=new Uint8Array(b);HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);HEAPU32=new Uint32Array(b);HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["i"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){if(Module["locateFile"]){return locateFile("jbig2.wasm")}return new URL("jbig2.wasm",import.meta.url).href}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var exceptionLast=0;var uncaughtExceptionCount=0;var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw exceptionLast};var __abort_js=()=>abort("");var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{func();maybeExit()}catch(e){handleException(e)}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};function _setImageData(array_ptr,pitch8,pitch32,height){if(pitch32===pitch8){Module.imageData=new Uint8ClampedArray(HEAPU8.subarray(array_ptr,array_ptr+pitch32*height));return}const destSize=pitch8*height;const imageData=Module.imageData=new Uint8ClampedArray(destSize);for(let srcStart=array_ptr,destStart=0;destStart{HEAP8.set(array,buffer)};{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}Module["writeArrayToMemory"]=writeArrayToMemory;var _malloc,_free,_jbig2_decode,__emscripten_timeout,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_malloc=Module["_malloc"]=wasmExports["j"];_free=Module["_free"]=wasmExports["k"];_jbig2_decode=Module["_jbig2_decode"]=wasmExports["l"];__emscripten_timeout=wasmExports["m"];memory=wasmMemory=wasmExports["h"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={a:___cxa_throw,f:__abort_js,c:__emscripten_runtime_keepalive_clear,d:__setitimer_js,e:_emscripten_resize_heap,b:_proc_exit,g:_setImageData};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} +;return moduleRtn}export default JBig2; diff --git a/external/jbig2/jbig2.wasm b/external/jbig2/jbig2.wasm new file mode 100644 index 000000000..ab3c9fcd2 Binary files /dev/null and b/external/jbig2/jbig2.wasm differ diff --git a/gulpfile.mjs b/gulpfile.mjs index 557dae54d..afbf4c2fb 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -696,6 +696,10 @@ function createWasmBundle() { base: "external/qcms", encoding: false, }), + gulp.src(["external/jbig2/*.wasm", "external/jbig2/LICENSE_*"], { + base: "external/jbig2", + encoding: false, + }), ]); } @@ -1696,6 +1700,7 @@ function buildLib(defines, dir) { gulp.src("test/unit/*.js", { base: ".", encoding: false }), gulp.src("external/openjpeg/*.js", { base: "openjpeg/", encoding: false }), gulp.src("external/qcms/*.js", { base: "qcms/", encoding: false }), + gulp.src("external/jbig2/*.js", { base: "jbig2/", encoding: false }), ]); return buildLibHelper(bundleDefines, inputStream, dir); @@ -2207,7 +2212,7 @@ gulp.task( }, function watchWasm() { gulp.watch( - ["external/openjpeg/*", "external/qcms/*"], + ["external/openjpeg/*", "external/qcms/*", "external/jbig2/*"], { ignoreInitial: false }, gulp.series("dev-wasm") ); diff --git a/src/core/image.js b/src/core/image.js index dbeaaa9c3..d4e40fe01 100644 --- a/src/core/image.js +++ b/src/core/image.js @@ -369,7 +369,7 @@ class PDFImage { const inverseDecode = decode?.[0] > 0; const computedLength = ((width + 7) >> 3) * height; - const imgArray = image.getBytes(computedLength); + const imgArray = await image.getImageData(computedLength); const isSingleOpaquePixel = width === 1 && diff --git a/src/core/jbig2_stream.js b/src/core/jbig2_stream.js index 126669c81..0fdda06ae 100644 --- a/src/core/jbig2_stream.js +++ b/src/core/jbig2_stream.js @@ -13,11 +13,12 @@ * limitations under the License. */ +import { shadow, warn } from "../shared/util.js"; import { BaseStream } from "./base_stream.js"; import { DecodeStream } from "./decode_stream.js"; import { Dict } from "./primitives.js"; import { Jbig2Image } from "./jbig2.js"; -import { shadow } from "../shared/util.js"; +import { JBig2WasmImage } from "./jbig2_wasm.js"; /** * For JBIG2's we use a library to decode these images and @@ -47,7 +48,40 @@ class Jbig2Stream extends DecodeStream { this.decodeImage(); } - decodeImage(bytes) { + get isAsyncDecoder() { + return true; + } + + async decodeImage(bytes, _decoderOptions) { + if (this.eof) { + return this.buffer; + } + bytes ||= this.bytes; + try { + let globals = null; + if (this.params instanceof Dict) { + const globalsStream = this.params.get("JBIG2Globals"); + if (globalsStream instanceof BaseStream) { + globals = globalsStream.getBytes(); + } + } + this.buffer = await JBig2WasmImage.decode( + bytes, + this.dict.get("Width"), + this.dict.get("Height"), + globals + ); + } catch { + warn("Jbig2Stream: Falling back to JS JBIG2 decoder."); + return this.decodeImageFallback(bytes); + } + this.bufferLength = this.buffer.length; + this.eof = true; + + return this.buffer; + } + + async decodeImageFallback(bytes) { if (this.eof) { return this.buffer; } diff --git a/src/core/jbig2_wasm.js b/src/core/jbig2_wasm.js new file mode 100644 index 000000000..2e5dca662 --- /dev/null +++ b/src/core/jbig2_wasm.js @@ -0,0 +1,127 @@ +/* Copyright 2026 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BaseException, warn } from "../shared/util.js"; +import { fetchBinaryData } from "./core_utils.js"; +import JBig2 from "../../external/jbig2/jbig2.js"; + +class JBig2Error extends BaseException { + constructor(msg) { + super(msg, "Jbig2Error"); + } +} + +class JBig2WasmImage { + static #buffer = null; + + static #handler = null; + + static #modulePromise = null; + + static #useWasm = true; + + static #useWorkerFetch = true; + + static #wasmUrl = null; + + static setOptions({ handler, useWasm, useWorkerFetch, wasmUrl }) { + this.#useWasm = useWasm; + this.#useWorkerFetch = useWorkerFetch; + this.#wasmUrl = wasmUrl; + + if (!useWorkerFetch) { + this.#handler = handler; + } + } + + static async #instantiateWasm(fallbackCallback, imports, successCallback) { + const filename = "jbig2.wasm"; + try { + if (!this.#buffer) { + if (this.#useWorkerFetch) { + this.#buffer = await fetchBinaryData(`${this.#wasmUrl}${filename}`); + } else { + this.#buffer = await this.#handler.sendWithPromise( + "FetchBinaryData", + { type: "wasmFactory", filename } + ); + } + } + const results = await WebAssembly.instantiate(this.#buffer, imports); + return successCallback(results.instance); + } catch (reason) { + warn(`JBig2Image#instantiateWasm: ${reason}`); + return null; + } finally { + this.#handler = null; + } + } + + static async decode(bytes, width, height, globals) { + if (!this.#modulePromise) { + const { promise, resolve } = Promise.withResolvers(); + const promises = [promise]; + if (this.#useWasm) { + promises.push( + JBig2({ + warn, + instantiateWasm: this.#instantiateWasm.bind(this, resolve), + }) + ); + } else { + resolve(null); + } + this.#modulePromise = Promise.race(promises); + } + const module = await this.#modulePromise; + if (!module) { + throw new JBig2Error("JBig2 failed to initialize"); + } + let ptr, globalsPtr; + + try { + const size = bytes.length; + ptr = module._malloc(size); + module.writeArrayToMemory(bytes, ptr); + const globalsSize = globals ? globals.length : 0; + if (globalsSize > 0) { + globalsPtr = module._malloc(globalsSize); + module.writeArrayToMemory(globals, globalsPtr); + } + + module._jbig2_decode(ptr, size, width, height, globalsPtr, globalsSize); + if (!module.imageData) { + throw new JBig2Error("Unknown error"); + } + const { imageData } = module; + module.imageData = null; + + return imageData; + } finally { + if (ptr) { + module._free(ptr); + } + if (globalsPtr) { + module._free(globalsPtr); + } + } + } + + static cleanup() { + this.#modulePromise = null; + } +} + +export { JBig2Error, JBig2WasmImage }; diff --git a/src/core/pdf_manager.js b/src/core/pdf_manager.js index 9ee866cdb..3c4255efb 100644 --- a/src/core/pdf_manager.js +++ b/src/core/pdf_manager.js @@ -22,6 +22,7 @@ import { } from "../shared/util.js"; import { ChunkedStreamManager } from "./chunked_stream.js"; import { ImageResizer } from "./image_resizer.js"; +import { JBig2WasmImage } from "./jbig2_wasm.js"; import { JpegStream } from "./jpeg_stream.js"; import { JpxImage } from "./jpx.js"; import { MissingDataException } from "./core_utils.js"; @@ -81,6 +82,7 @@ class BasePdfManager { JpxImage.setOptions(options); IccColorSpace.setOptions(options); CmykICCBasedCS.setOptions(options); + JBig2WasmImage.setOptions(options); } get docId() { diff --git a/test/pdfs/JBIG2Globals.pdf.link b/test/pdfs/JBIG2Globals.pdf.link new file mode 100644 index 000000000..a4aa000f5 --- /dev/null +++ b/test/pdfs/JBIG2Globals.pdf.link @@ -0,0 +1 @@ +https://github.com/pdfminer/pdfminer.six/files/8399364/64.pdf diff --git a/test/test_manifest.json b/test/test_manifest.json index 860497c72..22b144a41 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -13131,5 +13131,13 @@ "rounds": 1, "link": true, "type": "eq" + }, + { + "id": "JBIG2Globals", + "file": "pdfs/JBIG2Globals.pdf", + "md5": "78ee015a604678a4efbdcd993e0d77aa", + "rounds": 1, + "link": true, + "type": "eq" } ]