* Parse xref stream format (PDF 1.5+). * Scanner must be positioned at stream object start ("N M obj"). * * XRef streams encode cross-reference data as binary in a stream object. * The stream dictionary contains: * - /Type /XRef * - /Size - total number of objects * - /W [w1 w2
()
| 193 | * - /Index [first count ...] - object number ranges (optional, defaults to [0 Size]) |
| 194 | */ |
| 195 | parseStream(): XRefData { |
| 196 | // Parse the indirect object containing the xref stream |
| 197 | const parser = new IndirectObjectParser(this.scanner); |
| 198 | const indirectObj = parser.parseObject(); |
| 199 | |
| 200 | if (!(indirectObj.value instanceof PdfStream)) { |
| 201 | throw new XRefParseError("Expected XRef stream object"); |
| 202 | } |
| 203 | |
| 204 | const stream = indirectObj.value; |
| 205 | |
| 206 | // Validate /Type is /XRef (optional per spec, but good to check) |
| 207 | const type = stream.getName("Type"); |
| 208 | if (type !== undefined && type.value !== "XRef") { |
| 209 | throw new XRefParseError(`Expected /Type /XRef, got /Type /${type.value}`); |
| 210 | } |
| 211 | |
| 212 | // Get required /W array (field widths) |
| 213 | const wArray = stream.getArray("W"); |
| 214 | if (wArray === undefined || wArray.length < 3) { |
| 215 | throw new XRefParseError("XRef stream missing or invalid /W array"); |
| 216 | } |
| 217 | |
| 218 | const w0 = wArray.at(0); |
| 219 | const w1Val = wArray.at(1); |
| 220 | const w2Val = wArray.at(2); |
| 221 | |
| 222 | const w1 = w0 instanceof PdfNumber ? w0.value : 0; // Type field width |
| 223 | const w2 = w1Val instanceof PdfNumber ? w1Val.value : 0; // Offset field width |
| 224 | const w3 = w2Val instanceof PdfNumber ? w2Val.value : 0; // Generation field width |
| 225 | |
| 226 | // Validate field widths to prevent integer overflow during parsing. |
| 227 | // JavaScript safely handles integers up to 2^53-1, but bitwise operations |
| 228 | // are limited to 32 bits. Fields wider than 6 bytes (48 bits) could cause |
| 229 | // issues with shift operations. Most PDFs use 1-4 byte widths. |
| 230 | const MAX_FIELD_WIDTH = 6; |
| 231 | |
| 232 | if (w1 < 0 || w1 > MAX_FIELD_WIDTH) { |
| 233 | throw new XRefParseError(`Invalid XRef stream field width w1=${w1} (max ${MAX_FIELD_WIDTH})`); |
| 234 | } |
| 235 | |
| 236 | if (w2 < 0 || w2 > MAX_FIELD_WIDTH) { |
| 237 | throw new XRefParseError(`Invalid XRef stream field width w2=${w2} (max ${MAX_FIELD_WIDTH})`); |
| 238 | } |
| 239 | |
| 240 | if (w3 < 0 || w3 > MAX_FIELD_WIDTH) { |
| 241 | throw new XRefParseError(`Invalid XRef stream field width w3=${w3} (max ${MAX_FIELD_WIDTH})`); |
| 242 | } |
| 243 | |
| 244 | const entrySize = w1 + w2 + w3; |
| 245 | |
| 246 | // Get /Size (total object count) |
| 247 | const size = stream.getNumber("Size")?.value; |
| 248 | if (size === undefined) { |
| 249 | throw new XRefParseError("XRef stream missing /Size"); |
| 250 | } |
| 251 | |
| 252 | // Get /Index array or default to [0 Size] |
no test coverage detected