| 246 | } |
| 247 | |
| 248 | function build(): Uint8Array { |
| 249 | const fontRef = addObject({ |
| 250 | Type: '/Font', |
| 251 | Subtype: '/Type1', |
| 252 | BaseFont: '/Helvetica' |
| 253 | }) |
| 254 | |
| 255 | const pagesRef = addObject({ |
| 256 | Type: '/Pages', |
| 257 | Kids: pages, |
| 258 | Count: pages.length |
| 259 | }) |
| 260 | |
| 261 | for (const obj of objects) { |
| 262 | if (obj.dict.Type === '/Page') { |
| 263 | obj.dict.Parent = pagesRef |
| 264 | const resources = obj.dict.Resources as Record<string, PDFValue> | undefined |
| 265 | if (resources?.Font) { |
| 266 | (resources.Font as Record<string, PDFValue>).F1 = fontRef |
| 267 | } |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | const catalogRef = addObject({ |
| 272 | Type: '/Catalog', |
| 273 | Pages: pagesRef |
| 274 | }) |
| 275 | |
| 276 | const parts: (string | Uint8Array)[] = [] |
| 277 | const offsets: number[] = [] |
| 278 | |
| 279 | parts.push('%PDF-1.4\n%\xFF\xFF\xFF\xFF\n') |
| 280 | |
| 281 | for (const obj of objects) { |
| 282 | offsets[obj.id] = parts.reduce((sum, p) => sum + (typeof p === 'string' ? new TextEncoder().encode(p).length : p.length), 0) |
| 283 | |
| 284 | let content = `${obj.id} 0 obj\n${serialize(obj.dict)}\n` |
| 285 | if (obj.stream) { |
| 286 | content += 'stream\n' |
| 287 | parts.push(content) |
| 288 | parts.push(obj.stream) |
| 289 | parts.push('\nendstream\nendobj\n') |
| 290 | } else { |
| 291 | content += 'endobj\n' |
| 292 | parts.push(content) |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | const xrefOffset = parts.reduce((sum, p) => sum + (typeof p === 'string' ? new TextEncoder().encode(p).length : p.length), 0) |
| 297 | |
| 298 | let xref = `xref\n0 ${objects.length + 1}\n` |
| 299 | xref += '0000000000 65535 f \n' |
| 300 | for (let i = 1; i <= objects.length; i++) { |
| 301 | xref += String(offsets[i]).padStart(10, '0') + ' 00000 n \n' |
| 302 | } |
| 303 | parts.push(xref) |
| 304 | |
| 305 | parts.push(`trailer\n${serialize({ Size: objects.length + 1, Root: catalogRef })}\n`) |