Re-factor the Jbig2Image class, and related code, to use DataViews when reading data

This commit is contained in:
Jonas Jenwald 2026-04-11 16:44:15 +02:00
parent 81c9a34fd3
commit afbded8da1

View File

@ -14,14 +14,7 @@
*/ */
import { BaseException, shadow } from "../shared/util.js"; import { BaseException, shadow } from "../shared/util.js";
import { import { log2, MAX_INT_32, MIN_INT_32 } from "./core_utils.js";
log2,
MAX_INT_32,
MIN_INT_32,
readInt8,
readUint16,
readUint32,
} from "./core_utils.js";
import { ArithmeticDecoder } from "./arithmetic_decoder.js"; import { ArithmeticDecoder } from "./arithmetic_decoder.js";
import { CCITTFaxDecoder } from "./ccitt.js"; import { CCITTFaxDecoder } from "./ccitt.js";
@ -34,10 +27,7 @@ class Jbig2Error extends BaseException {
// Utility data structures // Utility data structures
class ContextCache { class ContextCache {
getContexts(id) { getContexts(id) {
if (id in this) { return (this[id] ??= new Int8Array(1 << 16));
return this[id];
}
return (this[id] = new Int8Array(1 << 16));
} }
} }
@ -1148,9 +1138,9 @@ function decodeHalftoneRegion(
return regionBitmap; return regionBitmap;
} }
function readSegmentHeader(data, start) { function readSegmentHeader(data, view, start) {
const segmentHeader = {}; const segmentHeader = {};
segmentHeader.number = readUint32(data, start); segmentHeader.number = view.getUint32(start);
const flags = data[start + 4]; const flags = data[start + 4];
const segmentType = flags & 0x3f; const segmentType = flags & 0x3f;
if (!SegmentTypes[segmentType]) { if (!SegmentTypes[segmentType]) {
@ -1166,7 +1156,7 @@ function readSegmentHeader(data, start) {
const retainBits = [referredFlags & 31]; const retainBits = [referredFlags & 31];
let position = start + 6; let position = start + 6;
if (referredToCount === 7) { if (referredToCount === 7) {
referredToCount = readUint32(data, position - 1) & 0x1fffffff; referredToCount = view.getUint32(position - 1) & 0x1fffffff;
position += 3; position += 3;
let bytes = (referredToCount + 8) >> 3; let bytes = (referredToCount + 8) >> 3;
retainBits[0] = data[position++]; retainBits[0] = data[position++];
@ -1192,9 +1182,9 @@ function readSegmentHeader(data, start) {
if (referredToSegmentNumberSize === 1) { if (referredToSegmentNumberSize === 1) {
number = data[position]; number = data[position];
} else if (referredToSegmentNumberSize === 2) { } else if (referredToSegmentNumberSize === 2) {
number = readUint16(data, position); number = view.getUint16(position);
} else { } else {
number = readUint32(data, position); number = view.getUint32(position);
} }
referredTo.push(number); referredTo.push(number);
position += referredToSegmentNumberSize; position += referredToSegmentNumberSize;
@ -1203,17 +1193,21 @@ function readSegmentHeader(data, start) {
if (!pageAssociationFieldSize) { if (!pageAssociationFieldSize) {
segmentHeader.pageAssociation = data[position++]; segmentHeader.pageAssociation = data[position++];
} else { } else {
segmentHeader.pageAssociation = readUint32(data, position); segmentHeader.pageAssociation = view.getUint32(position);
position += 4; position += 4;
} }
segmentHeader.length = readUint32(data, position); segmentHeader.length = view.getUint32(position);
position += 4; position += 4;
if (segmentHeader.length === 0xffffffff) { if (segmentHeader.length === 0xffffffff) {
// 7.2.7 Segment data length, unknown segment length // 7.2.7 Segment data length, unknown segment length
if (segmentType === 38) { if (segmentType === 38) {
// ImmediateGenericRegion // ImmediateGenericRegion
const genericRegionInfo = readRegionSegmentInformation(data, position); const genericRegionInfo = readRegionSegmentInformation(
data,
view,
position
);
const genericRegionSegmentFlags = const genericRegionSegmentFlags =
data[position + RegionSegmentInformationFieldLength]; data[position + RegionSegmentInformationFieldLength];
const genericRegionMmr = !!(genericRegionSegmentFlags & 1); const genericRegionMmr = !!(genericRegionSegmentFlags & 1);
@ -1250,10 +1244,11 @@ function readSegmentHeader(data, start) {
} }
function readSegments(header, data, start, end) { function readSegments(header, data, start, end) {
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
const segments = []; const segments = [];
let position = start; let position = start;
while (position < end) { while (position < end) {
const segmentHeader = readSegmentHeader(data, position); const segmentHeader = readSegmentHeader(data, view, position);
position = segmentHeader.headerEnd; position = segmentHeader.headerEnd;
const segment = { const segment = {
header: segmentHeader, header: segmentHeader,
@ -1280,29 +1275,27 @@ function readSegments(header, data, start, end) {
} }
// 7.4.1 Region segment information field // 7.4.1 Region segment information field
function readRegionSegmentInformation(data, start) { function readRegionSegmentInformation(data, view, start) {
return { return {
width: readUint32(data, start), width: view.getUint32(start),
height: readUint32(data, start + 4), height: view.getUint32(start + 4),
x: readUint32(data, start + 8), x: view.getUint32(start + 8),
y: readUint32(data, start + 12), y: view.getUint32(start + 12),
combinationOperator: data[start + 16] & 7, combinationOperator: data[start + 16] & 7,
}; };
} }
const RegionSegmentInformationFieldLength = 17; const RegionSegmentInformationFieldLength = 17;
function processSegment(segment, visitor) { function processSegment(segment, visitor) {
const header = segment.header; const { header, data, start, end } = segment;
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
const data = segment.data, let position = start;
end = segment.end;
let position = segment.start;
let args, at, i, atLength; let args, at, i, atLength;
switch (header.type) { switch (header.type) {
case 0: // SymbolDictionary case 0: // SymbolDictionary
// 7.4.2 Symbol dictionary segment syntax // 7.4.2 Symbol dictionary segment syntax
const dictionary = {}; const dictionary = {};
const dictionaryFlags = readUint16(data, position); // 7.4.2.1.1 const dictionaryFlags = view.getUint16(position); // 7.4.2.1.1
dictionary.huffman = !!(dictionaryFlags & 1); dictionary.huffman = !!(dictionaryFlags & 1);
dictionary.refinement = !!(dictionaryFlags & 2); dictionary.refinement = !!(dictionaryFlags & 2);
dictionary.huffmanDHSelector = (dictionaryFlags >> 2) & 3; dictionary.huffmanDHSelector = (dictionaryFlags >> 2) & 3;
@ -1319,8 +1312,8 @@ function processSegment(segment, visitor) {
at = []; at = [];
for (i = 0; i < atLength; i++) { for (i = 0; i < atLength; i++) {
at.push({ at.push({
x: readInt8(data, position), x: view.getInt8(position),
y: readInt8(data, position + 1), y: view.getInt8(position + 1),
}); });
position += 2; position += 2;
} }
@ -1330,16 +1323,16 @@ function processSegment(segment, visitor) {
at = []; at = [];
for (i = 0; i < 2; i++) { for (i = 0; i < 2; i++) {
at.push({ at.push({
x: readInt8(data, position), x: view.getInt8(position),
y: readInt8(data, position + 1), y: view.getInt8(position + 1),
}); });
position += 2; position += 2;
} }
dictionary.refinementAt = at; dictionary.refinementAt = at;
} }
dictionary.numberOfExportedSymbols = readUint32(data, position); dictionary.numberOfExportedSymbols = view.getUint32(position);
position += 4; position += 4;
dictionary.numberOfNewSymbols = readUint32(data, position); dictionary.numberOfNewSymbols = view.getUint32(position);
position += 4; position += 4;
args = [ args = [
dictionary, dictionary,
@ -1353,9 +1346,9 @@ function processSegment(segment, visitor) {
case 6: // ImmediateTextRegion case 6: // ImmediateTextRegion
case 7: // ImmediateLosslessTextRegion case 7: // ImmediateLosslessTextRegion
const textRegion = {}; const textRegion = {};
textRegion.info = readRegionSegmentInformation(data, position); textRegion.info = readRegionSegmentInformation(data, view, position);
position += RegionSegmentInformationFieldLength; position += RegionSegmentInformationFieldLength;
const textRegionSegmentFlags = readUint16(data, position); const textRegionSegmentFlags = view.getUint16(position);
position += 2; position += 2;
textRegion.huffman = !!(textRegionSegmentFlags & 1); textRegion.huffman = !!(textRegionSegmentFlags & 1);
textRegion.refinement = !!(textRegionSegmentFlags & 2); textRegion.refinement = !!(textRegionSegmentFlags & 2);
@ -1368,7 +1361,7 @@ function processSegment(segment, visitor) {
textRegion.dsOffset = (textRegionSegmentFlags << 17) >> 27; textRegion.dsOffset = (textRegionSegmentFlags << 17) >> 27;
textRegion.refinementTemplate = (textRegionSegmentFlags >> 15) & 1; textRegion.refinementTemplate = (textRegionSegmentFlags >> 15) & 1;
if (textRegion.huffman) { if (textRegion.huffman) {
const textRegionHuffmanFlags = readUint16(data, position); const textRegionHuffmanFlags = view.getUint16(position);
position += 2; position += 2;
textRegion.huffmanFS = textRegionHuffmanFlags & 3; textRegion.huffmanFS = textRegionHuffmanFlags & 3;
textRegion.huffmanDS = (textRegionHuffmanFlags >> 2) & 3; textRegion.huffmanDS = (textRegionHuffmanFlags >> 2) & 3;
@ -1385,14 +1378,14 @@ function processSegment(segment, visitor) {
at = []; at = [];
for (i = 0; i < 2; i++) { for (i = 0; i < 2; i++) {
at.push({ at.push({
x: readInt8(data, position), x: view.getInt8(position),
y: readInt8(data, position + 1), y: view.getInt8(position + 1),
}); });
position += 2; position += 2;
} }
textRegion.refinementAt = at; textRegion.refinementAt = at;
} }
textRegion.numberOfSymbolInstances = readUint32(data, position); textRegion.numberOfSymbolInstances = view.getUint32(position);
position += 4; position += 4;
args = [textRegion, header.referredTo, data, position, end]; args = [textRegion, header.referredTo, data, position, end];
break; break;
@ -1404,7 +1397,7 @@ function processSegment(segment, visitor) {
patternDictionary.template = (patternDictionaryFlags >> 1) & 3; patternDictionary.template = (patternDictionaryFlags >> 1) & 3;
patternDictionary.patternWidth = data[position++]; patternDictionary.patternWidth = data[position++];
patternDictionary.patternHeight = data[position++]; patternDictionary.patternHeight = data[position++];
patternDictionary.maxPatternIndex = readUint32(data, position); patternDictionary.maxPatternIndex = view.getUint32(position);
position += 4; position += 4;
args = [patternDictionary, header.number, data, position, end]; args = [patternDictionary, header.number, data, position, end];
break; break;
@ -1412,7 +1405,7 @@ function processSegment(segment, visitor) {
case 23: // ImmediateLosslessHalftoneRegion case 23: // ImmediateLosslessHalftoneRegion
// 7.4.5 Halftone region segment syntax // 7.4.5 Halftone region segment syntax
const halftoneRegion = {}; const halftoneRegion = {};
halftoneRegion.info = readRegionSegmentInformation(data, position); halftoneRegion.info = readRegionSegmentInformation(data, view, position);
position += RegionSegmentInformationFieldLength; position += RegionSegmentInformationFieldLength;
const halftoneRegionFlags = data[position++]; const halftoneRegionFlags = data[position++];
halftoneRegion.mmr = !!(halftoneRegionFlags & 1); halftoneRegion.mmr = !!(halftoneRegionFlags & 1);
@ -1420,24 +1413,24 @@ function processSegment(segment, visitor) {
halftoneRegion.enableSkip = !!(halftoneRegionFlags & 8); halftoneRegion.enableSkip = !!(halftoneRegionFlags & 8);
halftoneRegion.combinationOperator = (halftoneRegionFlags >> 4) & 7; halftoneRegion.combinationOperator = (halftoneRegionFlags >> 4) & 7;
halftoneRegion.defaultPixelValue = (halftoneRegionFlags >> 7) & 1; halftoneRegion.defaultPixelValue = (halftoneRegionFlags >> 7) & 1;
halftoneRegion.gridWidth = readUint32(data, position); halftoneRegion.gridWidth = view.getUint32(position);
position += 4; position += 4;
halftoneRegion.gridHeight = readUint32(data, position); halftoneRegion.gridHeight = view.getUint32(position);
position += 4; position += 4;
halftoneRegion.gridOffsetX = readUint32(data, position) & 0xffffffff; halftoneRegion.gridOffsetX = view.getUint32(position) & 0xffffffff;
position += 4; position += 4;
halftoneRegion.gridOffsetY = readUint32(data, position) & 0xffffffff; halftoneRegion.gridOffsetY = view.getUint32(position) & 0xffffffff;
position += 4; position += 4;
halftoneRegion.gridVectorX = readUint16(data, position); halftoneRegion.gridVectorX = view.getUint16(position);
position += 2; position += 2;
halftoneRegion.gridVectorY = readUint16(data, position); halftoneRegion.gridVectorY = view.getUint16(position);
position += 2; position += 2;
args = [halftoneRegion, header.referredTo, data, position, end]; args = [halftoneRegion, header.referredTo, data, position, end];
break; break;
case 38: // ImmediateGenericRegion case 38: // ImmediateGenericRegion
case 39: // ImmediateLosslessGenericRegion case 39: // ImmediateLosslessGenericRegion
const genericRegion = {}; const genericRegion = {};
genericRegion.info = readRegionSegmentInformation(data, position); genericRegion.info = readRegionSegmentInformation(data, view, position);
position += RegionSegmentInformationFieldLength; position += RegionSegmentInformationFieldLength;
const genericRegionSegmentFlags = data[position++]; const genericRegionSegmentFlags = data[position++];
genericRegion.mmr = !!(genericRegionSegmentFlags & 1); genericRegion.mmr = !!(genericRegionSegmentFlags & 1);
@ -1448,8 +1441,8 @@ function processSegment(segment, visitor) {
at = []; at = [];
for (i = 0; i < atLength; i++) { for (i = 0; i < atLength; i++) {
at.push({ at.push({
x: readInt8(data, position), x: view.getInt8(position),
y: readInt8(data, position + 1), y: view.getInt8(position + 1),
}); });
position += 2; position += 2;
} }
@ -1459,16 +1452,16 @@ function processSegment(segment, visitor) {
break; break;
case 48: // PageInformation case 48: // PageInformation
const pageInfo = { const pageInfo = {
width: readUint32(data, position), width: view.getUint32(position),
height: readUint32(data, position + 4), height: view.getUint32(position + 4),
resolutionX: readUint32(data, position + 8), resolutionX: view.getUint32(position + 8),
resolutionY: readUint32(data, position + 12), resolutionY: view.getUint32(position + 12),
}; };
if (pageInfo.height === 0xffffffff) { if (pageInfo.height === 0xffffffff) {
delete pageInfo.height; delete pageInfo.height;
} }
const pageSegmentFlags = data[position + 16]; const pageSegmentFlags = data[position + 16];
readUint16(data, position + 17); // pageStripingInformation view.getUint16(position + 17); // pageStripingInformation
pageInfo.lossless = !!(pageSegmentFlags & 1); pageInfo.lossless = !!(pageSegmentFlags & 1);
pageInfo.refinement = !!(pageSegmentFlags & 2); pageInfo.refinement = !!(pageSegmentFlags & 2);
pageInfo.defaultPixelValue = (pageSegmentFlags >> 2) & 1; pageInfo.defaultPixelValue = (pageSegmentFlags >> 2) & 1;
@ -1484,7 +1477,7 @@ function processSegment(segment, visitor) {
case 51: // EndOfFile case 51: // EndOfFile
break; break;
case 53: // Tables case 53: // Tables
args = [header.number, data, position, end]; args = [header.number, data, view, position, end];
break; break;
case 62: // 7.4.15 defines 2 extension types which case 62: // 7.4.15 defines 2 extension types which
// are comments and can be ignored. // are comments and can be ignored.
@ -1494,11 +1487,8 @@ function processSegment(segment, visitor) {
`segment type ${header.typeName}(${header.type}) is not implemented` `segment type ${header.typeName}(${header.type}) is not implemented`
); );
} }
const callbackName = "on" + header.typeName;
if (callbackName in visitor) {
// eslint-disable-next-line prefer-spread // eslint-disable-next-line prefer-spread
visitor[callbackName].apply(visitor, args); visitor["on" + header.typeName]?.apply(visitor, args);
}
} }
function processSegments(segments, visitor) { function processSegments(segments, visitor) {
@ -1521,6 +1511,7 @@ function parseJbig2(data) {
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("IMAGE_DECODERS")) { if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("IMAGE_DECODERS")) {
throw new Error("Not implemented: parseJbig2"); throw new Error("Not implemented: parseJbig2");
} }
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
const end = data.length; const end = data.length;
let position = 0; let position = 0;
@ -1542,7 +1533,7 @@ function parseJbig2(data) {
const flags = data[position++]; const flags = data[position++];
header.randomAccess = !(flags & 1); header.randomAccess = !(flags & 1);
if (!(flags & 2)) { if (!(flags & 2)) {
header.numberOfPages = readUint32(data, position); header.numberOfPages = view.getUint32(position);
position += 4; position += 4;
} }
@ -1803,9 +1794,9 @@ class SimpleSegmentVisitor {
this.onImmediateHalftoneRegion(...arguments); this.onImmediateHalftoneRegion(...arguments);
} }
onTables(currentSegment, data, start, end) { onTables(currentSegment, data, view, start, end) {
const customTables = (this.customTables ||= {}); const customTables = (this.customTables ||= {});
customTables[currentSegment] = decodeTablesSegment(data, start, end); customTables[currentSegment] = decodeTablesSegment(data, view, start, end);
} }
} }
@ -1931,12 +1922,12 @@ class HuffmanTable {
} }
} }
function decodeTablesSegment(data, start, end) { function decodeTablesSegment(data, view, start, end) {
// Decodes a Tables segment, i.e., a custom Huffman table. // Decodes a Tables segment, i.e., a custom Huffman table.
// Annex B.2 Code table structure. // Annex B.2 Code table structure.
const flags = data[start]; const flags = data[start];
const lowestValue = readUint32(data, start + 1) & 0xffffffff; const lowestValue = view.getUint32(start + 1) & 0xffffffff;
const highestValue = readUint32(data, start + 5) & 0xffffffff; const highestValue = view.getUint32(start + 5) & 0xffffffff;
const reader = new Reader(data, start + 9, end); const reader = new Reader(data, start + 9, end);
const prefixSizeBits = ((flags >> 1) & 7) + 1; const prefixSizeBits = ((flags >> 1) & 7) + 1;