reamkit 1.7.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +21 -3
  2. package/dist/esm/core/converter/facade.js +2 -0
  3. package/dist/esm/core/converter/ream.d.ts +1 -0
  4. package/dist/esm/core/converter/ream.js +2 -1
  5. package/dist/esm/core/document-model/types.d.ts +3 -1
  6. package/dist/esm/core/drawingml/shape-render.d.ts +3 -1
  7. package/dist/esm/core/drawingml/shape-render.js +27 -1
  8. package/dist/esm/core/vector.d.ts +10 -0
  9. package/dist/esm/html/html-writer.js +4 -2
  10. package/dist/esm/layout/styled-layout.js +5 -2
  11. package/dist/esm/pdf/shading.d.ts +12 -0
  12. package/dist/esm/pdf/shading.js +152 -0
  13. package/dist/esm/pdf/styled-page-emitter.js +29 -4
  14. package/dist/esm/pdf/vector-graphics.d.ts +1 -1
  15. package/dist/esm/pdf/vector-graphics.js +5 -3
  16. package/dist/esm/pdf-reader/ccitt.d.ts +15 -0
  17. package/dist/esm/pdf-reader/ccitt.js +394 -0
  18. package/dist/esm/pdf-reader/content.d.ts +6 -2
  19. package/dist/esm/pdf-reader/content.js +54 -13
  20. package/dist/esm/pdf-reader/decrypt.d.ts +1 -1
  21. package/dist/esm/pdf-reader/decrypt.js +20 -8
  22. package/dist/esm/pdf-reader/document.d.ts +1 -1
  23. package/dist/esm/pdf-reader/document.js +4 -4
  24. package/dist/esm/pdf-reader/flow-build.js +17 -6
  25. package/dist/esm/pdf-reader/image-decode.js +115 -7
  26. package/dist/esm/pdf-reader/layout.js +45 -7
  27. package/dist/esm/pdf-reader/reader.d.ts +1 -1
  28. package/dist/esm/pdf-reader/reader.js +5 -5
  29. package/dist/esm/pdf-reader/shading.d.ts +3 -0
  30. package/dist/esm/pdf-reader/shading.js +147 -0
  31. package/dist/esm/pdf-reader/text.js +47 -13
  32. package/dist/esm/pdf-reader/vector.d.ts +5 -1
  33. package/dist/esm/pdf-reader/vector.js +15 -6
  34. package/dist/esm/pptx/placeholder-cascade.d.ts +9 -0
  35. package/dist/esm/pptx/placeholder-cascade.js +77 -0
  36. package/dist/esm/pptx/pptx-reader.d.ts +4 -0
  37. package/dist/esm/pptx/pptx-reader.js +213 -0
  38. package/dist/esm/pptx/slide-parser.d.ts +18 -0
  39. package/dist/esm/pptx/slide-parser.js +359 -0
  40. package/dist/esm/pptx/sp-helpers.d.ts +17 -0
  41. package/dist/esm/pptx/sp-helpers.js +60 -0
  42. package/dist/esm/svg/svg-writer.js +12 -5
  43. package/dist/esm/word/docx-writer.js +8 -1
  44. package/dist/esm/word/drawing-parser.d.ts +4 -0
  45. package/dist/esm/word/drawing-parser.js +29 -18
  46. package/package.json +4 -2
package/README.md CHANGED
@@ -30,7 +30,7 @@ import { Ream } from 'reamkit';
30
30
  // e.g. from an <input type="file"> or a fetch() — anything that yields bytes.
31
31
  const bytes = new Uint8Array(await file.arrayBuffer());
32
32
 
33
- const doc = Ream.parse(bytes); // docx, xlsx or pdf — sniffed
33
+ const doc = Ream.parse(bytes); // docx, xlsx, pptx or pdf — sniffed
34
34
  const pdf = await doc.convert('pdf'); // async — fetches a font if needed
35
35
  const svg = await doc.convert('svg'); // same parse, different target
36
36
  const html = await doc.convert('html'); // flowed HTML — needs no fonts at all
@@ -130,7 +130,7 @@ rendering — inspect or analyze it without converting:
130
130
 
131
131
  ```ts
132
132
  const doc = Ream.parse(bytes);
133
- doc.format; // 'docx' | 'xlsx' | 'pdf'
133
+ doc.format; // 'docx' | 'xlsx' | 'pptx' | 'pdf'
134
134
  doc.flow.body; // paragraphs / tables / images / charts …
135
135
  doc.losses; // read-time losses
136
136
  ```
@@ -177,7 +177,25 @@ encryption, digital signatures (PKCS#7/ECDSA/PAdES/RFC 3161), SVG page
177
177
  preview, flowed HTML export, and **docx + xlsx output** (write WordprocessingML
178
178
  / SpreadsheetML back out, incl. round-trips). Reads OOXML Transitional and Strict.
179
179
 
180
- See `handoff.md` for the full feature matrix and known limitations.
180
+ **Reads PDF, too.** `Ream.parse` accepts a PDF and reconstructs a `FlowDoc` — a
181
+ tagged PDF from its structure tree (headings, tables, lists, reading order), an
182
+ untagged one heuristically from glyph positions (lines, paragraphs, headings,
183
+ and a clean two-column split). It lifts back the text (via each font's
184
+ `/ToUnicode`), raster images (JPEG verbatim; PNG/Flate/LZW/CCITT-fax decoded and
185
+ re-encoded), `/Link` hyperlinks, form-XObject content, and filled / stroked /
186
+ gradient vector shapes. It reads modern compressed files (cross-reference + object
187
+ streams) and encrypted ones (RC4 / AES — the user password is passed to
188
+ `Ream.parse(bytes, { password })`, defaulting to the permissions-only case).
189
+
190
+ **Reads PowerPoint, too.** `Ream.parse` accepts a `.pptx` and turns each slide
191
+ into a page at the deck size — text boxes (with run formatting, alignment,
192
+ bullets and indents), layout/master placeholders, pictures, shapes, DrawingML
193
+ tables, embedded charts, theme colours, slide backgrounds, grouped shapes and
194
+ hyperlinks — then converts onward to PDF, SVG, HTML or DOCX like any source.
195
+
196
+ See [`CHANGELOG.md`](./CHANGELOG.md) for the release history; the docs
197
+ [**Scope**](https://reamkit.dev/guides/scope/) guide has the full feature matrix
198
+ and known limitations.
181
199
 
182
200
  ## License
183
201
 
@@ -13,6 +13,7 @@ import { writeHtml } from "../../html/html-writer.js";
13
13
  import { writeSvg } from "../../svg/svg-writer.js";
14
14
  import { convertDocxToPdf } from "../../word/docx-to-pdf.js";
15
15
  import { convertXlsxToPdf } from "../../excel/xlsx-to-pdf.js";
16
+ import { pptxReader } from "../../pptx/pptx-reader.js";
16
17
  import { pdfReader } from "../../pdf-reader/reader.js";
17
18
  //#region src/core/converter/facade.ts
18
19
  function toFlowDoc(doc) {
@@ -28,6 +29,7 @@ function readToFlow(reader, bytes) {
28
29
  var DEFAULT_READERS = [
29
30
  docxReader,
30
31
  xlsxReader,
32
+ pptxReader,
31
33
  pdfReader
32
34
  ];
33
35
  function createConverter(opts = {}) {
@@ -10,6 +10,7 @@ import { SignatureOptions, StyledRenderOptions } from '../../pdf/index.js';
10
10
  export type ReamTarget = 'pdf' | 'svg' | 'html' | 'docx' | 'xlsx';
11
11
  export interface ReamParseOptions {
12
12
  readonly readers?: ReadonlyArray<DocumentReader<SourceDoc>>;
13
+ readonly password?: string;
13
14
  }
14
15
  export interface ReamConvertOptions extends Omit<StyledRenderOptions, 'registry' | 'styles'> {
15
16
  readonly fonts?: FontBytesByVariant;
@@ -16,6 +16,7 @@ import { DEFAULT_READERS, resolveFontsViaChain, toFlowDoc } from "./facade.js";
16
16
  var SOURCE_MIME = {
17
17
  docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
18
18
  xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
19
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
19
20
  pdf: "application/pdf"
20
21
  };
21
22
  var Ream = class Ream {
@@ -30,7 +31,7 @@ var Ream = class Ream {
30
31
  const readers = options.readers ?? DEFAULT_READERS;
31
32
  const reader = readers.find((r) => r.sniff(bytes));
32
33
  if (!reader) throw new Error(`Unrecognized document format (readers: ${readers.map((r) => r.id).join(", ")})`);
33
- const { doc, losses } = reader.read(bytes);
34
+ const { doc, losses } = reader.read(bytes, { password: options.password });
34
35
  const sheet = doc.kind === "sheet" ? doc : void 0;
35
36
  return new Ream(toFlowDoc(doc), sheet, losses, bytes, reader.id);
36
37
  }
@@ -1,4 +1,5 @@
1
1
  import { NativeBag, Pt, ResourceId } from '../ir/index.js';
2
+ import { ShapeGradient } from '../vector.js';
2
3
  export type Alignment = 'left' | 'right' | 'center' | 'both' | 'distribute';
3
4
  export type UnderlineStyle = 'none' | 'single' | 'double' | 'thick' | 'dotted' | 'dottedHeavy' | 'dash' | 'dashHeavy' | 'wave';
4
5
  export type VerticalAlign = 'baseline' | 'superscript' | 'subscript';
@@ -346,10 +347,11 @@ export interface ShapeGeometry {
346
347
  readonly adjust?: ReadonlyMap<string, number>;
347
348
  readonly custom?: CustomGeometry;
348
349
  }
349
- export type ShapeFillKind = 'none' | 'solid';
350
+ export type ShapeFillKind = 'none' | 'solid' | 'gradient';
350
351
  export interface ShapeFill {
351
352
  readonly kind: ShapeFillKind;
352
353
  readonly colorHex?: string;
354
+ readonly gradient?: ShapeGradient;
353
355
  }
354
356
  export type ShapeDash = 'solid' | 'dot' | 'dash' | 'dashDot' | 'lgDash' | 'lgDashDot' | 'sysDash' | 'sysDot';
355
357
  export interface ShapeLine {
@@ -1,8 +1,10 @@
1
1
  import { ShapeGeometry, ShapeLine } from '../document-model/index.js';
2
- import { StrokeStyle, VectorPath } from '../vector.js';
2
+ import { ShapeGradient, StrokeStyle, VectorPath } from '../vector.js';
3
3
  export declare const EMU_PER_PT = 12700;
4
4
  export declare const DEFAULT_INSET_LR_PT: number;
5
5
  export declare const DEFAULT_INSET_TB_PT: number;
6
6
  export declare function buildShapePaths(geometry: ShapeGeometry, widthPt: number, heightPt: number): Array<VectorPath>;
7
+ export declare function gradientToSolid(gradient: ShapeGradient): string;
8
+ export declare function gradientSvgDef(id: string, g: ShapeGradient): string;
7
9
  export declare function buildStroke(line: ShapeLine | undefined): StrokeStyle | undefined;
8
10
  export declare function buildShapeTransform(pageX: number, pageY: number, widthPt: number, heightPt: number, rotation60k: number, flipH: boolean, flipV: boolean): [number, number, number, number, number, number];
@@ -9,6 +9,32 @@ function buildShapePaths(geometry, widthPt, heightPt) {
9
9
  if (geometry.custom) return customPaths(geometry.custom, widthPt, heightPt);
10
10
  return [rectPath(widthPt, heightPt)];
11
11
  }
12
+ function gradientToSolid(gradient) {
13
+ let r = 0;
14
+ let g = 0;
15
+ let b = 0;
16
+ let n = 0;
17
+ for (const stop of gradient.stops) {
18
+ const num = parseInt(stop.colorHex, 16);
19
+ if (Number.isNaN(num)) continue;
20
+ r += num >> 16 & 255;
21
+ g += num >> 8 & 255;
22
+ b += num & 255;
23
+ n++;
24
+ }
25
+ if (n === 0) return "000000";
26
+ const hx = (x) => Math.round(x / n).toString(16).padStart(2, "0");
27
+ return (hx(r) + hx(g) + hx(b)).toUpperCase();
28
+ }
29
+ function gradientSvgDef(id, g) {
30
+ const n = (x) => String(Math.round(x * 1e4) / 1e4);
31
+ const stops = g.stops.map((s) => `<stop offset="${n(s.offset)}" stop-color="#${s.colorHex}"/>`).join("");
32
+ if (g.kind === "radial") return `<radialGradient id="${id}">${stops}</radialGradient>`;
33
+ const rad = -(g.angle ?? 0) * Math.PI / 180;
34
+ const dx = Math.cos(rad) / 2;
35
+ const dy = Math.sin(rad) / 2;
36
+ return `<linearGradient id="${id}" x1="${n(.5 - dx)}" y1="${n(.5 - dy)}" x2="${n(.5 + dx)}" y2="${n(.5 + dy)}">${stops}</linearGradient>`;
37
+ }
12
38
  function buildStroke(line) {
13
39
  if (!line || line.fill === "none") return void 0;
14
40
  const widthPt = line.width ?? DEFAULT_LINE_WIDTH_EMU / 12700;
@@ -68,4 +94,4 @@ function buildShapeTransform(pageX, pageY, widthPt, heightPt, rotation60k, flipH
68
94
  ];
69
95
  }
70
96
  //#endregion
71
- export { DEFAULT_INSET_LR_PT, DEFAULT_INSET_TB_PT, buildShapePaths, buildShapeTransform, buildStroke };
97
+ export { DEFAULT_INSET_LR_PT, DEFAULT_INSET_TB_PT, buildShapePaths, buildShapeTransform, buildStroke, gradientSvgDef, gradientToSolid };
@@ -28,9 +28,19 @@ export interface StrokeStyle {
28
28
  readonly join?: 'miter' | 'round' | 'bevel';
29
29
  readonly dash?: ReadonlyArray<number>;
30
30
  }
31
+ export interface GradientStop {
32
+ readonly offset: number;
33
+ readonly colorHex: string;
34
+ }
35
+ export interface ShapeGradient {
36
+ readonly kind: 'linear' | 'radial';
37
+ readonly angle?: number;
38
+ readonly stops: ReadonlyArray<GradientStop>;
39
+ }
31
40
  export interface VectorShape {
32
41
  readonly paths: ReadonlyArray<VectorPath>;
33
42
  readonly fillColorHex?: string;
43
+ readonly fillGradient?: ShapeGradient;
34
44
  readonly stroke?: StrokeStyle;
35
45
  readonly transform: readonly [number, number, number, number, number, number];
36
46
  }
@@ -6,7 +6,7 @@ import { PathBuilder, flipTransform, svgPathData } from "../core/vector.js";
6
6
  import { buildSparkline } from "../core/drawingml/sparkline-geometry.js";
7
7
  import { arcPoint, arcToBeziers } from "../core/arc-to-bezier.js";
8
8
  import { buildChartScene } from "../core/drawingml/chart-geometry.js";
9
- import { buildShapePaths, buildShapeTransform, buildStroke } from "../core/drawingml/shape-render.js";
9
+ import { buildShapePaths, buildShapeTransform, buildStroke, gradientSvgDef } from "../core/drawingml/shape-render.js";
10
10
  import { sanitizeHref } from "../core/links.js";
11
11
  import { toBase64 } from "../core/bytes.js";
12
12
  //#region src/html/html-writer.ts
@@ -209,11 +209,13 @@ function emitShapeBlock(out, shape, ctx) {
209
209
  const w = shape.width;
210
210
  const h = shape.height;
211
211
  const paths = buildShapePaths(shape.geometry, w, h);
212
- const fill = shape.fill.kind === "solid" && shape.fill.colorHex ? `#${shape.fill.colorHex}` : "none";
212
+ const gradDef = shape.fill.kind === "gradient" && shape.fill.gradient ? gradientSvgDef("grad0", shape.fill.gradient) : void 0;
213
+ const fill = gradDef ? "url(#grad0)" : shape.fill.kind === "solid" && shape.fill.colorHex ? `#${shape.fill.colorHex}` : "none";
213
214
  const stroke = strokeAttrs(buildStroke(shape.line));
214
215
  const t = shape.transform;
215
216
  const transform = ` transform="matrix(${flipTransform(buildShapeTransform(0, 0, w, h, t?.rotation60k ?? 0, t?.flipH ?? false, t?.flipV ?? false), h).map(fmt).join(" ")})"`;
216
217
  const svg = [svgOpen(w, h, shape.altText)];
218
+ if (gradDef) svg.push(`<defs>${gradDef}</defs>`);
217
219
  for (const path of paths) {
218
220
  const rule = path.fillRule === "evenodd" ? " fill-rule=\"evenodd\"" : "";
219
221
  svg.push(`<path d="${svgPathData(path.segments, fmt)}" fill="${fill}"${rule}${stroke}${transform}/>`);
@@ -17,7 +17,7 @@ import { arcPoint, arcToBeziers } from "../core/arc-to-bezier.js";
17
17
  import { buildChartScene } from "../core/drawingml/chart-geometry.js";
18
18
  import { layoutMath, mathGlyphSegments, variantStyle } from "./math-layout.js";
19
19
  import { rectPath } from "../core/drawingml/preset-geometry.js";
20
- import { buildShapePaths, buildShapeTransform, buildStroke } from "../core/drawingml/shape-render.js";
20
+ import { buildShapePaths, buildShapeTransform, buildStroke, gradientToSolid } from "../core/drawingml/shape-render.js";
21
21
  import { StructTreeBuilder } from "../pdf/struct-tree.js";
22
22
  //#region src/layout/styled-layout.ts
23
23
  function parsePdfAProfile(pdfA) {
@@ -454,7 +454,8 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
454
454
  heightPt = maxHeight;
455
455
  }
456
456
  const paths = buildShapePaths(shape.geometry, widthPt, heightPt);
457
- const fillColorHex = shape.fill.kind === "solid" ? shape.fill.colorHex : void 0;
457
+ const fillGradient = shape.fill.kind === "gradient" ? shape.fill.gradient : void 0;
458
+ const fillColorHex = shape.fill.kind === "solid" ? shape.fill.colorHex : fillGradient ? gradientToSolid(fillGradient) : void 0;
458
459
  const stroke = buildStroke(shape.line);
459
460
  const t = shape.transform;
460
461
  const pp = shape.paragraphProperties;
@@ -483,6 +484,7 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
483
484
  heightPt,
484
485
  paths,
485
486
  ...fillColorHex ? { fillColorHex } : {},
487
+ ...fillGradient ? { fillGradient } : {},
486
488
  ...stroke ? { stroke } : {},
487
489
  rotation60k: t?.rotation60k ?? 0,
488
490
  flipH: t?.flipH ?? false,
@@ -2013,6 +2015,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
2013
2015
  shape: {
2014
2016
  paths: block.paths,
2015
2017
  ...block.fillColorHex ? { fillColorHex: block.fillColorHex } : {},
2018
+ ...block.fillGradient ? { fillGradient: block.fillGradient } : {},
2016
2019
  ...block.stroke ? { stroke: block.stroke } : {},
2017
2020
  transform
2018
2021
  },
@@ -0,0 +1,12 @@
1
+ import { ShapeGradient, VectorShape } from '../core/vector.js';
2
+ import { PdfRef } from './objects.js';
3
+ import { PdfDocument } from './writer.js';
4
+ interface Bbox {
5
+ readonly minX: number;
6
+ readonly minY: number;
7
+ readonly maxX: number;
8
+ readonly maxY: number;
9
+ }
10
+ export declare function shapeBbox(shape: VectorShape): Bbox | undefined;
11
+ export declare function buildGradientPattern(doc: PdfDocument, gradient: ShapeGradient, bbox: Bbox, ctm: readonly [number, number, number, number, number, number]): PdfRef;
12
+ export {};
@@ -0,0 +1,152 @@
1
+ import { dict, name, ref } from "./objects.js";
2
+ //#region src/pdf/shading.ts
3
+ function shapeBbox(shape) {
4
+ let minX = Infinity;
5
+ let minY = Infinity;
6
+ let maxX = -Infinity;
7
+ let maxY = -Infinity;
8
+ const add = (x, y) => {
9
+ minX = Math.min(minX, x);
10
+ minY = Math.min(minY, y);
11
+ maxX = Math.max(maxX, x);
12
+ maxY = Math.max(maxY, y);
13
+ };
14
+ for (const path of shape.paths) for (const seg of path.segments) if (seg.op === "move" || seg.op === "line") add(seg.x, seg.y);
15
+ else if (seg.op === "cubic") {
16
+ add(seg.x1, seg.y1);
17
+ add(seg.x2, seg.y2);
18
+ add(seg.x, seg.y);
19
+ }
20
+ return Number.isFinite(minX) && maxX > minX && maxY > minY ? {
21
+ minX,
22
+ minY,
23
+ maxX,
24
+ maxY
25
+ } : void 0;
26
+ }
27
+ function buildGradientPattern(doc, gradient, bbox, ctm) {
28
+ const fnRef = doc.add(buildRamp(gradient.stops));
29
+ const shading = gradient.kind === "radial" ? radialShading(bbox, fnRef) : axialShading(gradient.angle ?? 0, bbox, fnRef);
30
+ return doc.add(dict({
31
+ Type: name("Pattern"),
32
+ PatternType: 2,
33
+ Shading: shading,
34
+ Matrix: [...ctm]
35
+ }));
36
+ }
37
+ function axialShading(angleDeg, b, fnRef) {
38
+ const rad = angleDeg * Math.PI / 180;
39
+ const dx = Math.cos(rad);
40
+ const dy = -Math.sin(rad);
41
+ const cx = (b.minX + b.maxX) / 2;
42
+ const cy = (b.minY + b.maxY) / 2;
43
+ const cproj = cx * dx + cy * dy;
44
+ let tmin = Infinity;
45
+ let tmax = -Infinity;
46
+ for (const [x, y] of [
47
+ [b.minX, b.minY],
48
+ [b.maxX, b.minY],
49
+ [b.minX, b.maxY],
50
+ [b.maxX, b.maxY]
51
+ ]) {
52
+ const t = x * dx + y * dy;
53
+ tmin = Math.min(tmin, t);
54
+ tmax = Math.max(tmax, t);
55
+ }
56
+ return dict({
57
+ ShadingType: 2,
58
+ ColorSpace: name("DeviceRGB"),
59
+ Coords: [
60
+ cx + (tmin - cproj) * dx,
61
+ cy + (tmin - cproj) * dy,
62
+ cx + (tmax - cproj) * dx,
63
+ cy + (tmax - cproj) * dy
64
+ ],
65
+ Function: ref(fnRef.id),
66
+ Extend: [true, true]
67
+ });
68
+ }
69
+ function radialShading(b, fnRef) {
70
+ const cx = (b.minX + b.maxX) / 2;
71
+ const cy = (b.minY + b.maxY) / 2;
72
+ const r = Math.hypot(b.maxX - b.minX, b.maxY - b.minY) / 2;
73
+ return dict({
74
+ ShadingType: 3,
75
+ ColorSpace: name("DeviceRGB"),
76
+ Coords: [
77
+ cx,
78
+ cy,
79
+ 0,
80
+ cx,
81
+ cy,
82
+ r
83
+ ],
84
+ Function: ref(fnRef.id),
85
+ Extend: [true, true]
86
+ });
87
+ }
88
+ function buildRamp(stopsIn) {
89
+ const stops = normalizeStops(stopsIn);
90
+ if (stops.length <= 2) return dict({
91
+ FunctionType: 2,
92
+ Domain: [0, 1],
93
+ C0: rgb01(stops[0].colorHex),
94
+ C1: rgb01(stops[stops.length - 1].colorHex),
95
+ N: 1
96
+ });
97
+ const functions = [];
98
+ const bounds = [];
99
+ const encode = [];
100
+ for (let i = 0; i < stops.length - 1; i++) {
101
+ functions.push(dict({
102
+ FunctionType: 2,
103
+ Domain: [0, 1],
104
+ C0: rgb01(stops[i].colorHex),
105
+ C1: rgb01(stops[i + 1].colorHex),
106
+ N: 1
107
+ }));
108
+ if (i > 0) bounds.push(stops[i].offset);
109
+ encode.push(0, 1);
110
+ }
111
+ return dict({
112
+ FunctionType: 3,
113
+ Domain: [0, 1],
114
+ Functions: functions,
115
+ Bounds: bounds,
116
+ Encode: encode
117
+ });
118
+ }
119
+ function normalizeStops(stops) {
120
+ const out = stops.map((s) => ({
121
+ offset: Math.max(0, Math.min(1, s.offset)),
122
+ colorHex: s.colorHex
123
+ })).sort((a, b) => a.offset - b.offset);
124
+ if (out.length === 0) return [{
125
+ offset: 0,
126
+ colorHex: "000000"
127
+ }];
128
+ out[0] = {
129
+ offset: 0,
130
+ colorHex: out[0].colorHex
131
+ };
132
+ out[out.length - 1] = {
133
+ offset: 1,
134
+ colorHex: out[out.length - 1].colorHex
135
+ };
136
+ return out;
137
+ }
138
+ function rgb01(hex) {
139
+ const n = parseInt(hex, 16);
140
+ if (Number.isNaN(n)) return [
141
+ 0,
142
+ 0,
143
+ 0
144
+ ];
145
+ return [
146
+ (n >> 16 & 255) / 255,
147
+ (n >> 8 & 255) / 255,
148
+ (n & 255) / 255
149
+ ];
150
+ }
151
+ //#endregion
152
+ export { buildGradientPattern, shapeBbox };
@@ -6,6 +6,7 @@ import { reverseByCodePoint } from "../core/bidi/index.js";
6
6
  import "../layout/styled-layout.js";
7
7
  import { paintPlan } from "../layout/page-doc.js";
8
8
  import { emitVectorShape } from "./vector-graphics.js";
9
+ import { buildGradientPattern, shapeBbox } from "./shading.js";
9
10
  import { sanitizeHref } from "../core/links.js";
10
11
  import { embedAssociatedFile } from "./embedded-file.js";
11
12
  import { buildSrgbIccProfile } from "./icc-profile.js";
@@ -49,9 +50,33 @@ function assembleStyledPdf(laid, options, doc) {
49
50
  let annotParentCount = 0;
50
51
  const fontResourceDict = buildFontResourceDict(laid.fontResources, embeddedFonts);
51
52
  const xobjectResourceDict = buildXObjectResourceDict(laid.imageResources, embeddedImages);
53
+ const gradientNames = /* @__PURE__ */ new Map();
54
+ const patternEntries = {};
55
+ if (!pdfaProfile) for (const page of renderedPages) {
56
+ const ph = page.height;
57
+ for (const item of paintPlan(page.commands).shapes) {
58
+ const gradient = item.shape.fillGradient;
59
+ if (!gradient) continue;
60
+ const bbox = shapeBbox(item.shape);
61
+ if (!bbox) continue;
62
+ const t = item.shape.transform;
63
+ const ctm = [
64
+ t[0],
65
+ -t[1],
66
+ t[2],
67
+ -t[3],
68
+ t[4],
69
+ ph - t[5]
70
+ ];
71
+ const nm = `Sh${gradientNames.size}`;
72
+ patternEntries[nm] = ref(buildGradientPattern(doc, gradient, bbox, ctm).id);
73
+ gradientNames.set(item.shape, nm);
74
+ }
75
+ }
52
76
  const resourcesDict = dict({
53
77
  Font: fontResourceDict,
54
- ...xobjectResourceDict ? { XObject: xobjectResourceDict } : {}
78
+ ...xobjectResourceDict ? { XObject: xobjectResourceDict } : {},
79
+ ...Object.keys(patternEntries).length > 0 ? { Pattern: dict(patternEntries) } : {}
55
80
  });
56
81
  let pdfaIccRef;
57
82
  let transparencyGroup;
@@ -78,7 +103,7 @@ function assembleStyledPdf(laid, options, doc) {
78
103
  tagFor: (structId) => builder.node(structId).type
79
104
  };
80
105
  }
81
- const { content: contentBytes, links } = emitPageContent(page, pageTagging);
106
+ const { content: contentBytes, links } = emitPageContent(page, pageTagging, gradientNames);
82
107
  const contentsRef = doc.add(stream({}, contentBytes));
83
108
  const pageEntries = {
84
109
  Type: name("Page"),
@@ -361,7 +386,7 @@ function emitTaggedRuns(out, cmds, tagging, body) {
361
386
  }
362
387
  close();
363
388
  }
364
- function emitPageContent(page, tagging) {
389
+ function emitPageContent(page, tagging, gradientNames) {
365
390
  const commands = page.commands;
366
391
  const links = [];
367
392
  if (commands.length === 0) return {
@@ -454,7 +479,7 @@ function emitPageContent(page, tagging) {
454
479
  H - t[5]
455
480
  ]
456
481
  };
457
- for (const op of emitVectorShape(shape)) out.push(op);
482
+ for (const op of emitVectorShape(shape, gradientNames?.get(sh.shape))) out.push(op);
458
483
  });
459
484
  const lines = plan.lines;
460
485
  if (lines.length > 0) {
@@ -1,4 +1,4 @@
1
1
  import { VectorShape } from '../core/vector.js';
2
2
  export type { PathSegment, StrokeStyle, VectorPath, VectorShape } from '../core/vector.js';
3
3
  export { PathBuilder } from '../core/vector.js';
4
- export declare function emitVectorShape(shape: VectorShape): Array<string>;
4
+ export declare function emitVectorShape(shape: VectorShape, patternName?: string): Array<string>;
@@ -1,6 +1,6 @@
1
1
  import "../core/vector.js";
2
2
  //#region src/pdf/vector-graphics.ts
3
- function emitVectorShape(shape) {
3
+ function emitVectorShape(shape, patternName) {
4
4
  const out = [];
5
5
  out.push("q");
6
6
  const [a, b, c, d, e, f] = shape.transform;
@@ -14,7 +14,9 @@ function emitVectorShape(shape) {
14
14
  const [r, g, bl] = hexToRgb01(stroke.colorHex);
15
15
  out.push(`${num(r)} ${num(g)} ${num(bl)} RG`);
16
16
  }
17
- if (shape.fillColorHex) {
17
+ const usePattern = patternName !== void 0 && shape.fillGradient !== void 0;
18
+ if (usePattern) out.push(`/Pattern cs /${patternName} scn`);
19
+ else if (shape.fillColorHex) {
18
20
  const [r, g, bl] = hexToRgb01(shape.fillColorHex);
19
21
  out.push(`${num(r)} ${num(g)} ${num(bl)} rg`);
20
22
  }
@@ -23,7 +25,7 @@ function emitVectorShape(shape) {
23
25
  if (path.fillRule === "evenodd") evenodd = true;
24
26
  for (const seg of path.segments) out.push(emitSegment(seg));
25
27
  }
26
- out.push(paintOp(shape.fillColorHex !== void 0, stroke !== void 0, evenodd));
28
+ out.push(paintOp(usePattern || shape.fillColorHex !== void 0, stroke !== void 0, evenodd));
27
29
  out.push("Q");
28
30
  return out;
29
31
  }
@@ -0,0 +1,15 @@
1
+ export interface CcittParams {
2
+ readonly k: number;
3
+ readonly columns: number;
4
+ readonly rows: number;
5
+ readonly byteAlign: boolean;
6
+ }
7
+ export declare function decodeCcitt(data: Uint8Array, params: CcittParams): Uint8Array | undefined;
8
+ export interface Mode {
9
+ readonly kind: 'pass' | 'horizontal' | 'vertical';
10
+ readonly d: number;
11
+ }
12
+ export declare const WHITE_CODES: ReadonlyArray<readonly [number, string]>;
13
+ export declare const BLACK_CODES: ReadonlyArray<readonly [number, string]>;
14
+ export declare const SHARED_MAKEUP: ReadonlyArray<readonly [number, string]>;
15
+ export declare const MODE_CODES: ReadonlyArray<readonly [string, Mode]>;