Skip to content

Commit 90f123f

Browse files
authored
fix(filters): handle sync-flush terminated zlib streams in FlateDecode (#20)
* fix(filters): handle sync-flush terminated zlib streams in FlateDecode pako.inflate() returns undefined (rather than throwing) for zlib streams terminated with a sync-flush marker (00 00 FF FF) instead of a proper final block. Some PDF generators like PDFium produce these streams. The undefined result propagated through FilterPipeline and caused a TypeError in downstream code trying to access .length on undefined chunks. FlateFilter.decode() now detects this case and recovers the decompressed data from pako's internal output buffer. Truly corrupt streams return empty rather than throwing, consistent with the library's lenient approach to malformed PDFs. Closes #16 * chore: gitignore fixtures/private/ * chore: change testing timestamp authority * chore: formatting
1 parent 98df22d commit 90f123f

File tree

8 files changed

+246
-144
lines changed

8 files changed

+246
-144
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ debug/
5050

5151
# Benchmark fixtures (downloaded at runtime)
5252
fixtures/benchmarks/
53+
fixtures/private/
5354

5455
# Temporary files
5556
tmp/

content/docs/advanced/low-level-drawing.mdx

Lines changed: 70 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ page.drawOperators([
2525
```
2626

2727
<Callout type="warn">
28-
The low-level API requires understanding of PDF content stream structure.
29-
Invalid operator sequences may produce corrupted PDFs. Use the high-level
30-
methods when they're sufficient.
28+
The low-level API requires understanding of PDF content stream structure. Invalid operator
29+
sequences may produce corrupted PDFs. Use the high-level methods when they're sufficient.
3130
</Callout>
3231

3332
---
@@ -36,15 +35,15 @@ page.drawOperators([
3635

3736
The high-level methods (`drawRectangle`, `drawText`, etc.) cover most needs. Reach for the low-level API when you need:
3837

39-
| Feature | Low-Level Approach |
40-
| --- | --- |
41-
| Matrix transforms | `ops.concatMatrix()` for arbitrary rotation/scale/skew |
42-
| Gradients | `createAxialShading()` or `createRadialShading()` |
43-
| Repeating patterns | `createTilingPattern()` or `createImagePattern()` |
44-
| Blend modes | `createExtGState({ blendMode: "Multiply" })` |
45-
| Clipping regions | `ops.clip()` with `ops.endPath()` |
46-
| Reusable graphics | `createFormXObject()` for stamps/watermarks |
47-
| Fine-grained control | Direct operator sequences |
38+
| Feature | Low-Level Approach |
39+
| -------------------- | ------------------------------------------------------ |
40+
| Matrix transforms | `ops.concatMatrix()` for arbitrary rotation/scale/skew |
41+
| Gradients | `createAxialShading()` or `createRadialShading()` |
42+
| Repeating patterns | `createTilingPattern()` or `createImagePattern()` |
43+
| Blend modes | `createExtGState({ blendMode: "Multiply" })` |
44+
| Clipping regions | `ops.clip()` with `ops.endPath()` |
45+
| Reusable graphics | `createFormXObject()` for stamps/watermarks |
46+
| Fine-grained control | Direct operator sequences |
4847

4948
---
5049

@@ -59,80 +58,80 @@ import { ops } from "@libpdf/core";
5958
### Graphics State
6059

6160
```typescript
62-
ops.pushGraphicsState() // Save current state (q)
63-
ops.popGraphicsState() // Restore saved state (Q)
64-
ops.setGraphicsState(name) // Apply ExtGState resource (gs)
65-
ops.concatMatrix(a, b, c, d, e, f) // Transform CTM (cm)
61+
ops.pushGraphicsState(); // Save current state (q)
62+
ops.popGraphicsState(); // Restore saved state (Q)
63+
ops.setGraphicsState(name); // Apply ExtGState resource (gs)
64+
ops.concatMatrix(a, b, c, d, e, f); // Transform CTM (cm)
6665
```
6766

6867
### Path Construction
6968

7069
```typescript
71-
ops.moveTo(x, y) // Begin subpath (m)
72-
ops.lineTo(x, y) // Line to point (l)
73-
ops.curveTo(x1, y1, x2, y2, x3, y3) // Cubic bezier (c)
74-
ops.rectangle(x, y, w, h) // Rectangle shorthand (re)
75-
ops.closePath() // Close subpath (h)
70+
ops.moveTo(x, y); // Begin subpath (m)
71+
ops.lineTo(x, y); // Line to point (l)
72+
ops.curveTo(x1, y1, x2, y2, x3, y3); // Cubic bezier (c)
73+
ops.rectangle(x, y, w, h); // Rectangle shorthand (re)
74+
ops.closePath(); // Close subpath (h)
7675
```
7776

7877
### Path Painting
7978

8079
```typescript
81-
ops.stroke() // Stroke path (S)
82-
ops.fill() // Fill path, non-zero winding (f)
83-
ops.fillEvenOdd() // Fill path, even-odd rule (f*)
84-
ops.fillAndStroke() // Fill then stroke (B)
85-
ops.endPath() // Discard path without painting (n)
80+
ops.stroke(); // Stroke path (S)
81+
ops.fill(); // Fill path, non-zero winding (f)
82+
ops.fillEvenOdd(); // Fill path, even-odd rule (f*)
83+
ops.fillAndStroke(); // Fill then stroke (B)
84+
ops.endPath(); // Discard path without painting (n)
8685
```
8786

8887
### Clipping
8988

9089
```typescript
91-
ops.clip() // Set clip region, non-zero (W)
92-
ops.clipEvenOdd() // Set clip region, even-odd (W*)
90+
ops.clip(); // Set clip region, non-zero (W)
91+
ops.clipEvenOdd(); // Set clip region, even-odd (W*)
9392
```
9493

9594
### Color
9695

9796
```typescript
98-
ops.setStrokingGray(g) // Stroke grayscale (G)
99-
ops.setNonStrokingGray(g) // Fill grayscale (g)
100-
ops.setStrokingRGB(r, g, b) // Stroke RGB (RG)
101-
ops.setNonStrokingRGB(r, g, b) // Fill RGB (rg)
102-
ops.setStrokingCMYK(c, m, y, k) // Stroke CMYK (K)
103-
ops.setNonStrokingCMYK(c, m, y, k) // Fill CMYK (k)
104-
ops.setStrokingColorSpace(cs) // Set stroke color space (CS)
105-
ops.setNonStrokingColorSpace(cs) // Set fill color space (cs)
106-
ops.setStrokingColorN(name) // Set stroke pattern (SCN)
107-
ops.setNonStrokingColorN(name) // Set fill pattern (scn)
97+
ops.setStrokingGray(g); // Stroke grayscale (G)
98+
ops.setNonStrokingGray(g); // Fill grayscale (g)
99+
ops.setStrokingRGB(r, g, b); // Stroke RGB (RG)
100+
ops.setNonStrokingRGB(r, g, b); // Fill RGB (rg)
101+
ops.setStrokingCMYK(c, m, y, k); // Stroke CMYK (K)
102+
ops.setNonStrokingCMYK(c, m, y, k); // Fill CMYK (k)
103+
ops.setStrokingColorSpace(cs); // Set stroke color space (CS)
104+
ops.setNonStrokingColorSpace(cs); // Set fill color space (cs)
105+
ops.setStrokingColorN(name); // Set stroke pattern (SCN)
106+
ops.setNonStrokingColorN(name); // Set fill pattern (scn)
108107
```
109108

110109
### Line Style
111110

112111
```typescript
113-
ops.setLineWidth(w) // Line width (w)
114-
ops.setLineCap(cap) // 0=butt, 1=round, 2=square (J)
115-
ops.setLineJoin(join) // 0=miter, 1=round, 2=bevel (j)
116-
ops.setMiterLimit(limit) // Miter limit ratio (M)
117-
ops.setDashPattern(array, phase) // Dash pattern (d)
112+
ops.setLineWidth(w); // Line width (w)
113+
ops.setLineCap(cap); // 0=butt, 1=round, 2=square (J)
114+
ops.setLineJoin(join); // 0=miter, 1=round, 2=bevel (j)
115+
ops.setMiterLimit(limit); // Miter limit ratio (M)
116+
ops.setDashPattern(array, phase); // Dash pattern (d)
118117
```
119118

120119
### Text
121120

122121
```typescript
123-
ops.beginText() // Begin text object (BT)
124-
ops.endText() // End text object (ET)
125-
ops.setFont(name, size) // Set font (Tf)
126-
ops.moveText(tx, ty) // Position text (Td)
127-
ops.setTextMatrix(a, b, c, d, e, f) // Text matrix (Tm)
128-
ops.showText(string) // Show text (Tj)
122+
ops.beginText(); // Begin text object (BT)
123+
ops.endText(); // End text object (ET)
124+
ops.setFont(name, size); // Set font (Tf)
125+
ops.moveText(tx, ty); // Position text (Td)
126+
ops.setTextMatrix(a, b, c, d, e, f); // Text matrix (Tm)
127+
ops.showText(string); // Show text (Tj)
129128
```
130129

131130
### XObjects and Shading
132131

133132
```typescript
134-
ops.paintXObject(name) // Draw XObject (Do)
135-
ops.paintShading(name) // Paint shading (sh)
133+
ops.paintXObject(name); // Draw XObject (Do)
134+
ops.paintShading(name); // Paint shading (sh)
136135
```
137136

138137
---
@@ -146,7 +145,7 @@ import { Matrix, ops } from "@libpdf/core";
146145

147146
const matrix = Matrix.identity()
148147
.translate(200, 300)
149-
.rotate(45) // degrees
148+
.rotate(45) // degrees
150149
.scale(2, 1.5);
151150

152151
page.drawOperators([
@@ -162,14 +161,14 @@ Or use raw matrix components:
162161

163162
```typescript
164163
// Translation: move 100 points right, 200 points up
165-
ops.concatMatrix(1, 0, 0, 1, 100, 200)
164+
ops.concatMatrix(1, 0, 0, 1, 100, 200);
166165

167166
// Scale: 2x horizontal, 0.5x vertical
168-
ops.concatMatrix(2, 0, 0, 0.5, 0, 0)
167+
ops.concatMatrix(2, 0, 0, 0.5, 0, 0);
169168

170169
// Rotation: 45 degrees around origin
171-
const angle = 45 * Math.PI / 180;
172-
ops.concatMatrix(Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), 0, 0)
170+
const angle = (45 * Math.PI) / 180;
171+
ops.concatMatrix(Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), 0, 0);
173172
```
174173

175174
---
@@ -183,7 +182,7 @@ Create linear or radial gradients with color stops:
183182
```typescript
184183
// CSS-style: angle + length
185184
const gradient = pdf.createLinearGradient({
186-
angle: 90, // 0=up, 90=right, 180=down, 270=left
185+
angle: 90, // 0=up, 90=right, 180=down, 270=left
187186
length: 200,
188187
stops: [
189188
{ offset: 0, color: rgb(1, 0, 0) },
@@ -194,7 +193,7 @@ const gradient = pdf.createLinearGradient({
194193

195194
// Or explicit coordinates
196195
const axial = pdf.createAxialShading({
197-
coords: [0, 0, 200, 0], // x0, y0, x1, y1
196+
coords: [0, 0, 200, 0], // x0, y0, x1, y1
198197
stops: [
199198
{ offset: 0, color: rgb(0, 0, 1) },
200199
{ offset: 1, color: rgb(1, 0, 1) },
@@ -206,7 +205,7 @@ const axial = pdf.createAxialShading({
206205

207206
```typescript
208207
const radial = pdf.createRadialShading({
209-
coords: [100, 100, 0, 100, 100, 80], // x0, y0, r0, x1, y1, r1
208+
coords: [100, 100, 0, 100, 100, 80], // x0, y0, r0, x1, y1, r1
210209
stops: [
211210
{ offset: 0, color: rgb(1, 1, 1) },
212211
{ offset: 1, color: rgb(0, 0, 0) },
@@ -234,9 +233,7 @@ page.drawOperators([
234233
// Or wrap in a pattern for PathBuilder
235234
const pattern = pdf.createShadingPattern({ shading: gradient });
236235

237-
page.drawPath()
238-
.rectangle(50, 200, 200, 100)
239-
.fill({ pattern });
236+
page.drawPath().rectangle(50, 200, 200, 100).fill({ pattern });
240237
```
241238

242239
---
@@ -282,9 +279,7 @@ const pattern = pdf.createImagePattern({
282279
height: 50,
283280
});
284281

285-
page.drawPath()
286-
.circle(200, 400, 80)
287-
.fill({ pattern });
282+
page.drawPath().circle(200, 400, 80).fill({ pattern });
288283
```
289284

290285
### Gradient Pattern
@@ -387,23 +382,23 @@ Restrict drawing to a region:
387382
```typescript
388383
page.drawOperators([
389384
ops.pushGraphicsState(),
390-
385+
391386
// Define clip region (circle)
392387
ops.moveTo(200, 300),
393388
// ... circle path using bezier curves
394389
ops.clip(),
395-
ops.endPath(), // Required after clip
396-
390+
ops.endPath(), // Required after clip
391+
397392
// Everything here is clipped to the circle
398393
ops.paintShading(gradientName),
399-
400-
ops.popGraphicsState(), // Clipping is restored
394+
395+
ops.popGraphicsState(), // Clipping is restored
401396
]);
402397
```
403398

404399
<Callout type="info">
405-
Always follow `ops.clip()` with a path-painting operator. Use `ops.endPath()`
406-
to discard the path, or `ops.fill()` to both clip and fill.
400+
Always follow `ops.clip()` with a path-painting operator. Use `ops.endPath()` to discard the path,
401+
or `ops.fill()` to both clip and fill.
407402
</Callout>
408403

409404
---
@@ -444,7 +439,7 @@ const page = pdf.addPage();
444439

445440
// Create button gradient
446441
const gradient = pdf.createLinearGradient({
447-
angle: 180, // top to bottom
442+
angle: 180, // top to bottom
448443
length: 40,
449444
stops: [
450445
{ offset: 0, color: rgb(0.4, 0.6, 1) },
@@ -475,7 +470,8 @@ page.drawOperators([
475470
]);
476471

477472
// Rounded rectangle path
478-
page.drawPath()
473+
page
474+
.drawPath()
479475
.moveTo(110, 700)
480476
.lineTo(250, 700)
481477
.curveTo(255, 700, 260, 705, 260, 710)

content/docs/api/pdf-page.mdx

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -570,9 +570,9 @@ For advanced graphics operations, PDFPage provides methods to emit raw operators
570570

571571
Emit raw PDF operators to the page content stream.
572572

573-
| Param | Type | Description |
574-
| ----------- | ------------ | --------------------------- |
575-
| `operators` | `Operator[]` | Array of operators to emit |
573+
| Param | Type | Description |
574+
| ----------- | ------------ | -------------------------- |
575+
| `operators` | `Operator[]` | Array of operators to emit |
576576

577577
```typescript
578578
import { ops } from "@libpdf/core";
@@ -588,8 +588,8 @@ page.drawOperators([
588588
```
589589

590590
<Callout type="warn">
591-
The caller is responsible for valid operator sequences. Invalid sequences
592-
may produce corrupted PDFs.
591+
The caller is responsible for valid operator sequences. Invalid sequences may produce corrupted
592+
PDFs.
593593
</Callout>
594594

595595
---
@@ -598,8 +598,8 @@ page.drawOperators([
598598

599599
Register a font resource and return its operator name.
600600

601-
| Param | Type | Description |
602-
| ------ | ------------------------------ | ------------------ |
601+
| Param | Type | Description |
602+
| ------ | ------------------------------------ | ---------------- |
603603
| `font` | `EmbeddedFont \| Standard14FontName` | Font to register |
604604

605605
**Returns**: `string` - Resource name (e.g., `"F0"`)
@@ -623,9 +623,9 @@ page.drawOperators([
623623

624624
Register an image resource and return its operator name.
625625

626-
| Param | Type | Description |
627-
| ------- | ---------- | ------------------ |
628-
| `image` | `PDFImage` | Embedded image |
626+
| Param | Type | Description |
627+
| ------- | ---------- | -------------- |
628+
| `image` | `PDFImage` | Embedded image |
629629

630630
**Returns**: `string` - Resource name (e.g., `"Im0"`)
631631

@@ -647,8 +647,8 @@ page.drawOperators([
647647

648648
Register a shading (gradient) resource and return its operator name.
649649

650-
| Param | Type | Description |
651-
| --------- | ------------ | ------------ |
650+
| Param | Type | Description |
651+
| --------- | ------------ | ---------------- |
652652
| `shading` | `PDFShading` | Shading resource |
653653

654654
**Returns**: `string` - Resource name (e.g., `"Sh0"`)
@@ -699,8 +699,8 @@ page.drawOperators([
699699

700700
Register an extended graphics state and return its operator name.
701701

702-
| Param | Type | Description |
703-
| ------- | ------------- | -------------- |
702+
| Param | Type | Description |
703+
| ------- | -------------- | -------------- |
704704
| `state` | `PDFExtGState` | Graphics state |
705705

706706
**Returns**: `string` - Resource name (e.g., `"GS0"`)
@@ -728,8 +728,8 @@ page.drawOperators([
728728

729729
Register a Form XObject or embedded page and return its operator name.
730730

731-
| Param | Type | Description |
732-
| --------- | --------------------------------- | ---------------- |
731+
| Param | Type | Description |
732+
| --------- | ----------------------------------- | ------------------- |
733733
| `xobject` | `PDFFormXObject \| PDFEmbeddedPage` | XObject to register |
734734

735735
**Returns**: `string` - Resource name (e.g., `"Fm0"`)

0 commit comments

Comments
 (0)