mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 06:38:07 +02:00
[api-minor] Add a jpx decoder based on OpenJPEG 2.5.2
The decoder is compiled in WASM: https://github.com/mozilla/pdf.js.openjpeg Fixes #17289, #17061, #16485, #13051, #6365, #4648, #12213.
This commit is contained in:
parent
2e94511330
commit
2e83cfbbc1
20 changed files with 172 additions and 2347 deletions
|
@ -6,6 +6,7 @@ external/bcmaps/
|
|||
external/builder/fixtures/
|
||||
external/builder/fixtures_esprima/
|
||||
external/quickjs/
|
||||
external/openjpeg/
|
||||
test/tmp/
|
||||
test/pdfs/
|
||||
web/locale/
|
||||
|
|
12
external/openjpeg/README.md
vendored
Normal file
12
external/openjpeg/README.md
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
## Build
|
||||
|
||||
In order to generate the file `openjpeg.js`:
|
||||
* git clone https://github.com/mozilla/pdf.js.openjpeg/
|
||||
* 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/openjpeg/` to compile the decoder
|
||||
|
||||
## Licensing
|
||||
|
||||
[OpenJPEG](https://www.openjpeg.org/) is under [BSD 2-clause "Simplified" License](https://github.com/uclouvain/openjpeg/blob/master/LICENSE)
|
||||
and [pdf.js.openjpeg](https://github.com/mozilla/pdf.js.openjpeg/) is released under [Apache 2](https://github.com/mozilla/pdf.js.openjpeg/blob/main/LICENSE) license so `openjpeg.js` is released under [Apache 2](https://github.com/mozilla/pdf.js.openjpeg/blob/main/LICENSE) license too.
|
15
external/openjpeg/openjpeg.js
vendored
Normal file
15
external/openjpeg/openjpeg.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1603,6 +1603,7 @@ function buildLib(defines, dir) {
|
|||
),
|
||||
gulp.src(["web/*.js", "!web/{pdfjs,viewer}.js"], { base: "." }),
|
||||
gulp.src("test/unit/*.js", { base: "." }),
|
||||
gulp.src("external/openjpeg/*.js", { base: "openjpeg/" }),
|
||||
]);
|
||||
|
||||
return buildLibHelper(bundleDefines, inputStream, dir);
|
||||
|
|
|
@ -16,11 +16,16 @@
|
|||
import { clearPatternCaches } from "./pattern.js";
|
||||
import { clearPrimitiveCaches } from "./primitives.js";
|
||||
import { clearUnicodeCaches } from "./unicode.js";
|
||||
import { JpxImage } from "./jpx.js";
|
||||
|
||||
function clearGlobalCaches() {
|
||||
clearPatternCaches();
|
||||
clearPrimitiveCaches();
|
||||
clearUnicodeCaches();
|
||||
|
||||
// Remove the global `JpxImage` instance, since it may hold a reference to
|
||||
// the WebAssembly module.
|
||||
JpxImage.cleanup();
|
||||
}
|
||||
|
||||
export { clearGlobalCaches };
|
||||
|
|
|
@ -73,7 +73,7 @@ class DecodeStream extends BaseStream {
|
|||
return this.buffer[this.pos++];
|
||||
}
|
||||
|
||||
getBytes(length) {
|
||||
getBytes(length, ignoreColorSpace = false) {
|
||||
const pos = this.pos;
|
||||
let end;
|
||||
|
||||
|
@ -82,7 +82,7 @@ class DecodeStream extends BaseStream {
|
|||
end = pos + length;
|
||||
|
||||
while (!this.eof && this.bufferLength < end) {
|
||||
this.readBlock();
|
||||
this.readBlock(ignoreColorSpace);
|
||||
}
|
||||
const bufEnd = this.bufferLength;
|
||||
if (end > bufEnd) {
|
||||
|
@ -90,7 +90,7 @@ class DecodeStream extends BaseStream {
|
|||
}
|
||||
} else {
|
||||
while (!this.eof) {
|
||||
this.readBlock();
|
||||
this.readBlock(ignoreColorSpace);
|
||||
}
|
||||
end = this.bufferLength;
|
||||
}
|
||||
|
|
|
@ -104,6 +104,7 @@ class PDFImage {
|
|||
localColorSpaceCache,
|
||||
}) {
|
||||
this.image = image;
|
||||
let jpxDecode = false;
|
||||
const dict = image.dict;
|
||||
|
||||
const filter = dict.get("F", "Filter");
|
||||
|
@ -118,14 +119,14 @@ class PDFImage {
|
|||
}
|
||||
switch (filterName) {
|
||||
case "JPXDecode":
|
||||
const jpxImage = new JpxImage();
|
||||
jpxImage.parseImageProperties(image.stream);
|
||||
({
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
componentsCount: image.numComps,
|
||||
bitsPerComponent: image.bitsPerComponent,
|
||||
} = JpxImage.parseImageProperties(image.stream));
|
||||
image.stream.reset();
|
||||
|
||||
image.width = jpxImage.width;
|
||||
image.height = jpxImage.height;
|
||||
image.bitsPerComponent = jpxImage.bitsPerComponent;
|
||||
image.numComps = jpxImage.componentsCount;
|
||||
jpxDecode = true;
|
||||
break;
|
||||
case "JBIG2Decode":
|
||||
image.bitsPerComponent = 1;
|
||||
|
@ -197,6 +198,7 @@ class PDFImage {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.colorSpace = ColorSpace.parse({
|
||||
cs: colorSpace,
|
||||
xref,
|
||||
|
@ -205,6 +207,10 @@ class PDFImage {
|
|||
localColorSpaceCache,
|
||||
});
|
||||
this.numComps = this.colorSpace.numComps;
|
||||
|
||||
// If the jpx image has a color space then it musn't be used in order to
|
||||
// be able to use the color space that comes from the pdf.
|
||||
this.ignoreColorSpace = jpxDecode && this.colorSpace.name === "Indexed";
|
||||
}
|
||||
|
||||
this.decode = dict.getArray("D", "Decode");
|
||||
|
@ -984,7 +990,7 @@ class PDFImage {
|
|||
this.image.drawHeight = drawHeight || this.height;
|
||||
this.image.forceRGBA = !!forceRGBA;
|
||||
this.image.forceRGB = !!forceRGB;
|
||||
const imageBytes = this.image.getBytes(length);
|
||||
const imageBytes = this.image.getBytes(length, this.ignoreColorSpace);
|
||||
|
||||
// If imageBytes came from a DecodeStream, we're safe to transfer it
|
||||
// (and thus detach its underlying buffer) because it will constitute
|
||||
|
|
2326
src/core/jpx.js
2326
src/core/jpx.js
File diff suppressed because it is too large
Load diff
|
@ -41,44 +41,12 @@ class JpxStream extends DecodeStream {
|
|||
// directly insert all of its data into `this.buffer`.
|
||||
}
|
||||
|
||||
readBlock() {
|
||||
readBlock(ignoreColorSpace) {
|
||||
if (this.eof) {
|
||||
return;
|
||||
}
|
||||
const jpxImage = new JpxImage();
|
||||
jpxImage.parse(this.bytes);
|
||||
|
||||
const width = jpxImage.width;
|
||||
const height = jpxImage.height;
|
||||
const componentsCount = jpxImage.componentsCount;
|
||||
const tileCount = jpxImage.tiles.length;
|
||||
if (tileCount === 1) {
|
||||
this.buffer = jpxImage.tiles[0].items;
|
||||
} else {
|
||||
const data = new Uint8ClampedArray(width * height * componentsCount);
|
||||
|
||||
for (let k = 0; k < tileCount; k++) {
|
||||
const tileComponents = jpxImage.tiles[k];
|
||||
const tileWidth = tileComponents.width;
|
||||
const tileHeight = tileComponents.height;
|
||||
const tileLeft = tileComponents.left;
|
||||
const tileTop = tileComponents.top;
|
||||
|
||||
const src = tileComponents.items;
|
||||
let srcPosition = 0;
|
||||
let dataPosition = (width * tileTop + tileLeft) * componentsCount;
|
||||
const imgRowSize = width * componentsCount;
|
||||
const tileRowSize = tileWidth * componentsCount;
|
||||
|
||||
for (let j = 0; j < tileHeight; j++) {
|
||||
const rowBytes = src.subarray(srcPosition, srcPosition + tileRowSize);
|
||||
data.set(rowBytes, dataPosition);
|
||||
srcPosition += tileRowSize;
|
||||
dataPosition += imgRowSize;
|
||||
}
|
||||
}
|
||||
this.buffer = data;
|
||||
}
|
||||
this.buffer = JpxImage.decode(this.bytes, ignoreColorSpace);
|
||||
this.bufferLength = this.buffer.length;
|
||||
this.eof = true;
|
||||
}
|
||||
|
|
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
|
@ -642,3 +642,4 @@
|
|||
!issue17871_top_right.pdf
|
||||
!bug1889122.pdf
|
||||
!issue17929.pdf
|
||||
!issue12213.pdf
|
||||
|
|
2
test/pdfs/bug1659412.pdf.link
Normal file
2
test/pdfs/bug1659412.pdf.link
Normal file
|
@ -0,0 +1,2 @@
|
|||
https://bugzilla.mozilla.org/attachment.cgi?id=9254200
|
||||
|
2
test/pdfs/issue12213.1.pdf.link
Normal file
2
test/pdfs/issue12213.1.pdf.link
Normal file
|
@ -0,0 +1,2 @@
|
|||
https://github.com/mozilla/pdf.js/files/6600376/JPXimageprobem.pdf
|
||||
|
BIN
test/pdfs/issue12213.pdf
Executable file
BIN
test/pdfs/issue12213.pdf
Executable file
Binary file not shown.
2
test/pdfs/issue13051.pdf.link
Normal file
2
test/pdfs/issue13051.pdf.link
Normal file
|
@ -0,0 +1,2 @@
|
|||
https://github.com/mozilla/pdf.js/files/6083044/njp.32101064480005_page_013.2.pdf
|
||||
|
2
test/pdfs/issue16485.pdf.link
Normal file
2
test/pdfs/issue16485.pdf.link
Normal file
|
@ -0,0 +1,2 @@
|
|||
https://web.archive.org/web/20230617061919/https://cdn.dealereprocess.org/cdn/servicemanuals/nissan/2023-ariya.pdf
|
||||
|
2
test/pdfs/issue17061.pdf.link
Normal file
2
test/pdfs/issue17061.pdf.link
Normal file
|
@ -0,0 +1,2 @@
|
|||
https://web.archive.org/web/20240229104747/https://btohq.sgp1.cdn.digitaloceanspaces.com/bto/nov-2019-bto/plantation-village.pdf
|
||||
|
2
test/pdfs/issue17289.pdf.link
Normal file
2
test/pdfs/issue17289.pdf.link
Normal file
|
@ -0,0 +1,2 @@
|
|||
https://github.com/mozilla/pdf.js/files/13391396/example.pdf
|
||||
|
2
test/pdfs/issue4648.pdf.link
Normal file
2
test/pdfs/issue4648.pdf.link
Normal file
|
@ -0,0 +1,2 @@
|
|||
https://web.archive.org/web/20240415131452/http://www.braidense.it/rd/03595.pdf
|
||||
|
1
test/pdfs/issue6365.pdf.link
Normal file
1
test/pdfs/issue6365.pdf.link
Normal file
|
@ -0,0 +1 @@
|
|||
https://web.archive.org/web/20210403114737/http://waltercosand.com/CosandScores/Composers%20A-D/Chopin,%20Frederic/Find_by_Category/Preludes/Preludes-Op28-edDebussy.pdf
|
|
@ -9840,5 +9840,84 @@
|
|||
"md5": "2f5686f77f73fb99037f5c2c7e48ce53",
|
||||
"rounds": 1,
|
||||
"type": "eq"
|
||||
},
|
||||
{
|
||||
"id": "issue17289",
|
||||
"file": "pdfs/issue17289.pdf",
|
||||
"md5": "acf5bb9e4309ba1c4b3568e7c4a7dd73",
|
||||
"rounds": 1,
|
||||
"link": true,
|
||||
"type": "eq"
|
||||
},
|
||||
{
|
||||
"id": "issue17061",
|
||||
"file": "pdfs/issue17061.pdf",
|
||||
"md5": "21a1b887b7e657b01e4205b22d1f4378",
|
||||
"rounds": 1,
|
||||
"link": true,
|
||||
"firstPage": 1,
|
||||
"lastPage": 5,
|
||||
"type": "eq"
|
||||
},
|
||||
{
|
||||
"id": "issue16485",
|
||||
"file": "pdfs/issue16485.pdf",
|
||||
"md5": "8a2c43161e0d89469fc7ed345c7bd3b1",
|
||||
"rounds": 1,
|
||||
"link": true,
|
||||
"firstPage": 266,
|
||||
"lastPage": 266,
|
||||
"type": "eq"
|
||||
},
|
||||
{
|
||||
"id": "issue13051",
|
||||
"file": "pdfs/issue13051.pdf",
|
||||
"md5": "ead6598c79641a0d266a224160e764d8",
|
||||
"rounds": 1,
|
||||
"link": true,
|
||||
"type": "eq"
|
||||
},
|
||||
{
|
||||
"id": "issue6365",
|
||||
"file": "pdfs/issue6365.pdf",
|
||||
"md5": "fd567f3b4f7c896b5b8071ea762af069",
|
||||
"rounds": 1,
|
||||
"link": true,
|
||||
"firstPage": 2,
|
||||
"lastPage": 5,
|
||||
"type": "eq"
|
||||
},
|
||||
{
|
||||
"id": "issue4648",
|
||||
"file": "pdfs/issue4648.pdf",
|
||||
"md5": "9906cb55f190b8a2d340ea34093ed763",
|
||||
"rounds": 1,
|
||||
"link": true,
|
||||
"firstPage": 1,
|
||||
"lastPage": 4,
|
||||
"type": "eq"
|
||||
},
|
||||
{
|
||||
"id": "bug1659412",
|
||||
"file": "pdfs/bug1659412.pdf",
|
||||
"md5": "0a3220e322d7b899e7604cea947d424c",
|
||||
"rounds": 1,
|
||||
"link": true,
|
||||
"type": "eq"
|
||||
},
|
||||
{
|
||||
"id": "issue12213",
|
||||
"file": "pdfs/issue12213.pdf",
|
||||
"md5": "84df1ce3e3d96e793a2e339aaba138e9",
|
||||
"rounds": 1,
|
||||
"type": "eq"
|
||||
},
|
||||
{
|
||||
"id": "issue12213.1",
|
||||
"file": "pdfs/issue12213.1.pdf",
|
||||
"md5": "8ff4af8d7fd391b7f6601406e490d865",
|
||||
"rounds": 1,
|
||||
"link": true,
|
||||
"type": "eq"
|
||||
}
|
||||
]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue