reamkit 1.2.0 → 1.4.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 (48) hide show
  1. package/README.md +10 -6
  2. package/dist/esm/core/converter/facade.d.ts +2 -2
  3. package/dist/esm/core/converter/facade.js +12 -0
  4. package/dist/esm/core/converter/ream.d.ts +1 -1
  5. package/dist/esm/core/converter/ream.js +14 -3
  6. package/dist/esm/core/document-model/types.d.ts +2 -0
  7. package/dist/esm/core/drawingml/chart-geometry.d.ts +1 -1
  8. package/dist/esm/core/drawingml/chart-geometry.js +79 -117
  9. package/dist/esm/core/drawingml/chart-parser.d.ts +3 -0
  10. package/dist/esm/core/drawingml/chart-parser.js +28 -1
  11. package/dist/esm/core/numbering/apply.js +2 -1
  12. package/dist/esm/core/opc/index.d.ts +3 -0
  13. package/dist/esm/core/opc/index.js +1 -0
  14. package/dist/esm/core/opc/opc-writer.d.ts +28 -0
  15. package/dist/esm/core/opc/opc-writer.js +60 -0
  16. package/dist/esm/core/opc/package.js +3 -2
  17. package/dist/esm/core/opc/relationship-types.d.ts +1 -0
  18. package/dist/esm/core/opc/relationship-types.js +5 -2
  19. package/dist/esm/excel/print-model.d.ts +3 -0
  20. package/dist/esm/excel/print-model.js +4 -8
  21. package/dist/esm/excel/sheet-drawing.d.ts +9 -0
  22. package/dist/esm/excel/sheet-drawing.js +113 -0
  23. package/dist/esm/excel/worksheet-parser.d.ts +1 -0
  24. package/dist/esm/excel/worksheet-parser.js +4 -1
  25. package/dist/esm/excel/xlsx-reader.js +41 -0
  26. package/dist/esm/excel/xlsx-to-pdf.js +25 -18
  27. package/dist/esm/index.d.ts +2 -0
  28. package/dist/esm/index.js +2 -1
  29. package/dist/esm/layout/page-doc.d.ts +1 -0
  30. package/dist/esm/layout/styled-layout.d.ts +2 -0
  31. package/dist/esm/layout/styled-layout.js +321 -171
  32. package/dist/esm/pdf/encryption.d.ts +34 -0
  33. package/dist/esm/pdf/encryption.js +143 -0
  34. package/dist/esm/pdf/index.d.ts +2 -1
  35. package/dist/esm/pdf/styled-page-emitter.d.ts +2 -0
  36. package/dist/esm/pdf/styled-page-emitter.js +25 -7
  37. package/dist/esm/pdf/styled-page-renderer.d.ts +1 -0
  38. package/dist/esm/pdf/styled-page-renderer.js +17 -2
  39. package/dist/esm/pdf/writer.d.ts +2 -0
  40. package/dist/esm/pdf/writer.js +6 -1
  41. package/dist/esm/word/document-parser.js +51 -13
  42. package/dist/esm/word/docx-reader.js +7 -11
  43. package/dist/esm/word/docx-to-pdf.js +23 -13
  44. package/dist/esm/word/docx-writer.d.ts +4 -0
  45. package/dist/esm/word/docx-writer.js +602 -0
  46. package/dist/esm/word/drawing-parser.d.ts +1 -0
  47. package/dist/esm/word/drawing-parser.js +37 -2
  48. package/package.json +4 -3
package/README.md CHANGED
@@ -34,6 +34,7 @@ const doc = Ream.parse(bytes); // docx or xlsx — 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
37
+ const docx = await doc.convert('docx'); // write WordprocessingML back out
37
38
 
38
39
  // Hand the bytes to the browser: preview, download, upload, …
39
40
  const url = URL.createObjectURL(new Blob([pdf], { type: 'application/pdf' }));
@@ -150,10 +151,11 @@ automatically from the document's `docProps/core.xml`), `attachments`
150
151
 
151
152
  ### Lower-level APIs
152
153
 
153
- - `docxReader` / `xlsxReader`, `svgWriter`, `htmlWriter` — the `@experimental` reader/writer
154
- interfaces of the interlayer, for building custom pipelines (and keeping
155
- unused formats out of your bundle); `layoutStyledDocument` produces the
156
- frozen page model (`PageItem` pages in a top-left `Pt` frame) they consume.
154
+ - `docxReader` / `xlsxReader`, `svgWriter`, `htmlWriter`, `docxWriter` — the `@experimental`
155
+ reader/writer interfaces of the interlayer, for building custom pipelines (and
156
+ keeping unused formats out of your bundle); `layoutStyledDocument` produces the
157
+ frozen page model (`PageItem` pages in a top-left `Pt` frame) the page-based
158
+ writers consume (`docxWriter` works from the flow model, before layout).
157
159
  - `renderStyledPdf` drives the layout engine directly; the typed document
158
160
  model is on the `reamkit/document-model` subpath.
159
161
 
@@ -168,8 +170,10 @@ repeated titles, page breaks), DrawingML shapes and charts, OMML math,
168
170
  Type0+CIDFontType2 embedding with subsetting, Knuth-Plass line breaking,
169
171
  Liang hyphenation, OpenType ligatures/kerning + Arabic cursive joining,
170
172
  BiDi (UAX #9), hyperlinks (PDF link annotations + HTML anchors,
171
- scheme-allowlisted), tagged PDF, PDF/A-1/2/3 (a/b/u), digital signatures
172
- (PKCS#7/ECDSA/PAdES/RFC 3161), SVG page preview, flowed HTML export.
173
+ scheme-allowlisted), tagged PDF, PDF/A-1/2/3 (a/b/u), PDF/UA-1, AES-256
174
+ encryption, digital signatures (PKCS#7/ECDSA/PAdES/RFC 3161), SVG page
175
+ preview, flowed HTML export, and **docx output** (write WordprocessingML back
176
+ out, incl. docx → docx). Reads OOXML Transitional and Strict.
173
177
 
174
178
  See `handoff.md` for the full feature matrix and known limitations.
175
179
 
@@ -5,8 +5,8 @@ import { FontProvider } from '../fonts/provider.js';
5
5
  import { Loss, LossReport } from '../ir/index.js';
6
6
  import { FontBytesByVariant } from '../font/index.js';
7
7
  export interface ConvertOptions extends ConvertDocxOptions {
8
- /** Target: 'pdf' (default), 'svg' (page-stack preview) or 'html' (flowed). */
9
- readonly to?: 'pdf' | 'svg' | 'html';
8
+ /** Target: 'pdf' (default), 'svg' (page-stack preview), 'html' or 'docx' (flowed). */
9
+ readonly to?: 'pdf' | 'svg' | 'html' | 'docx';
10
10
  /**
11
11
  * Strict mode (handoff v1 §5): throw ConversionLossError on the first
12
12
  * recorded loss instead of returning it in the report.
@@ -6,6 +6,7 @@ import { docxReader } from "../../word/docx-reader.js";
6
6
  import { xlsxReader } from "../../excel/xlsx-reader.js";
7
7
  import { chainProviders } from "../fonts/provider.js";
8
8
  import { flowRenderOptions } from "./project.js";
9
+ import { writeDocx } from "../../word/docx-writer.js";
9
10
  import { writeHtml } from "../../html/html-writer.js";
10
11
  import { writeSvg } from "../../svg/svg-writer.js";
11
12
  import { convertDocxToPdf } from "../../word/docx-to-pdf.js";
@@ -20,6 +21,17 @@ function createConverter(opts = {}) {
20
21
  const reader = detect(bytes);
21
22
  if (!reader) throw new Error("Unrecognized input format (no registered reader sniffs these bytes)");
22
23
  const losses = [];
24
+ if (to === "docx") {
25
+ const { doc: flow, losses: readLosses } = reader.read(bytes);
26
+ losses.push(...readLosses);
27
+ const out = writeDocx(flow);
28
+ losses.push(...out.losses);
29
+ if (strict && losses.length > 0) throw new ConversionLossError(losses[0]);
30
+ return {
31
+ bytes: out.bytes,
32
+ losses
33
+ };
34
+ }
23
35
  if (to === "html") {
24
36
  const { doc: flow, losses: readLosses } = reader.read(bytes);
25
37
  losses.push(...readLosses);
@@ -6,7 +6,7 @@ import { Loss } from '../ir/index.js';
6
6
  import { DocumentReader } from '../ir/adapters.js';
7
7
  import { FlowDoc } from '../ir/flow.js';
8
8
  import { SignatureOptions, StyledRenderOptions } from '../../pdf/index.js';
9
- export type ReamTarget = 'pdf' | 'svg' | 'html';
9
+ export type ReamTarget = 'pdf' | 'svg' | 'html' | 'docx';
10
10
  export interface ReamParseOptions {
11
11
  readonly readers?: ReadonlyArray<DocumentReader>;
12
12
  }
@@ -3,9 +3,10 @@ import { FontRegistry } from "../font/font-registry.js";
3
3
  import { ConversionLossError } from "../ir/loss.js";
4
4
  import { layoutStyledDocument } from "../../layout/styled-layout.js";
5
5
  import { signPdf } from "../../pdf/signature.js";
6
- import { renderStyledPdf } from "../../pdf/styled-page-renderer.js";
6
+ import { renderStyledPdf, renderStyledPdfEncrypted } from "../../pdf/styled-page-renderer.js";
7
7
  import "../../pdf/index.js";
8
8
  import { flowRenderOptions } from "./project.js";
9
+ import { writeDocx } from "../../word/docx-writer.js";
9
10
  import { writeHtml } from "../../html/html-writer.js";
10
11
  import { writeSvg } from "../../svg/svg-writer.js";
11
12
  import { resolveDocxAutoFonts } from "../../word/docx-to-pdf.js";
@@ -46,6 +47,15 @@ var Ream = class Ream {
46
47
  losses
47
48
  };
48
49
  }
50
+ if (to === "docx") {
51
+ const docx = writeDocx(this.flow);
52
+ losses.push(...docx.losses);
53
+ this.enforceStrict(options, losses);
54
+ return {
55
+ bytes: docx.bytes,
56
+ losses
57
+ };
58
+ }
49
59
  const { fonts, registriesByFamily } = await this.resolveFonts(options, losses);
50
60
  const registry = FontRegistry.fromBytes(fonts);
51
61
  if (to === "svg") {
@@ -73,7 +83,7 @@ var Ream = class Ream {
73
83
  relationship: "Source",
74
84
  description: "Source document"
75
85
  });
76
- let pdf = renderStyledPdf(this.flow.body, {
86
+ const styled = {
77
87
  registry,
78
88
  ...registriesByFamily ? { registriesByFamily } : {},
79
89
  ...flowRenderOptions(this.flow),
@@ -81,7 +91,8 @@ var Ream = class Ream {
81
91
  ...attachments.length > 0 ? { attachments } : {},
82
92
  ...signature ? { signaturePlaceholder: signature } : {},
83
93
  ...renderOptions
84
- });
94
+ };
95
+ let pdf = styled.encrypt ? await renderStyledPdfEncrypted(this.flow.body, styled) : renderStyledPdf(this.flow.body, styled);
85
96
  if (signature) pdf = await signPdf(pdf, signature);
86
97
  this.enforceStrict(options, losses);
87
98
  return {
@@ -132,6 +132,7 @@ export interface Run {
132
132
  readonly footnoteRef?: string;
133
133
  readonly endnoteRef?: string;
134
134
  readonly noteNumber?: true;
135
+ readonly listMarker?: true;
135
136
  readonly inlineImage?: InlineImage;
136
137
  readonly math?: MathNode;
137
138
  readonly pageBreak?: boolean;
@@ -382,6 +383,7 @@ export interface Chart {
382
383
  readonly showValues?: boolean;
383
384
  readonly catAxisTitle?: string;
384
385
  readonly valAxisTitle?: string;
386
+ readonly seriesColorCycle?: ReadonlyArray<string>;
385
387
  }
386
388
  export interface ChartBlock {
387
389
  readonly float?: FloatAnchor;
@@ -48,7 +48,7 @@ export type MeasureText = (text: string, sizePt: number) => number;
48
48
  export declare const CHART_LABEL_PT = 9;
49
49
  export declare const CHART_TITLE_PT = 13;
50
50
  export declare const SERIES_COLORS: string[];
51
- export declare const seriesColor: (s: ChartSeries, i: number) => string;
51
+ export declare const seriesColor: (s: ChartSeries, i: number, cycle?: ReadonlyArray<string>) => string;
52
52
  export interface Scale {
53
53
  readonly min: number;
54
54
  readonly max: number;
@@ -10,7 +10,7 @@ var SERIES_COLORS = [
10
10
  "5B9BD5",
11
11
  "70AD47"
12
12
  ];
13
- var seriesColor = (s, i) => s.colorHex ?? SERIES_COLORS[i % SERIES_COLORS.length];
13
+ var seriesColor = (s, i, cycle) => s.colorHex ?? (cycle && cycle.length > 0 ? cycle[i % cycle.length] : SERIES_COLORS[i % SERIES_COLORS.length]);
14
14
  function niceNum(range, round) {
15
15
  const exp = Math.floor(Math.log10(range));
16
16
  const f = range / 10 ** exp;
@@ -40,6 +40,68 @@ var ticks = (s) => {
40
40
  for (let v = s.min; v <= s.max + 1e-9; v += s.step) out.push(Math.abs(v) < 1e-9 ? 0 : v);
41
41
  return out;
42
42
  };
43
+ function pushChartTitle(labels, chart, wPt, hPt) {
44
+ if (!chart.title) return;
45
+ labels.push({
46
+ text: chart.title,
47
+ x: wPt / 2,
48
+ y: hPt - 4 - 13,
49
+ sizePt: 13,
50
+ colorHex: TITLE_COLOR,
51
+ align: "center"
52
+ });
53
+ }
54
+ function buildLegendBlock(chart, wPt, hPt, measure) {
55
+ return layoutLegend(chart.series.map((s, i) => ({
56
+ name: s.name ?? "",
57
+ colorHex: seriesColor(s, i, chart.seriesColorCycle)
58
+ })).filter((e) => e.name !== ""), chart.hasLegend, chart.legendPos ?? "b", wPt, hPt, measure);
59
+ }
60
+ function pushGridTicks(polylines, labels, tickVals, fmt, axis, at, x0, y0, plotW, plotH) {
61
+ for (const v of tickVals) if (axis === "x") {
62
+ const gx = at(v);
63
+ polylines.push({
64
+ points: [[gx, y0], [gx, y0 + plotH]],
65
+ strokeHex: GRID_COLOR,
66
+ widthPt: .75
67
+ });
68
+ labels.push({
69
+ text: fmt(v),
70
+ x: gx,
71
+ y: y0 - 9,
72
+ sizePt: 9,
73
+ colorHex: LABEL_COLOR,
74
+ align: "center"
75
+ });
76
+ } else {
77
+ const gy = at(v);
78
+ polylines.push({
79
+ points: [[x0, gy], [x0 + plotW, gy]],
80
+ strokeHex: GRID_COLOR,
81
+ widthPt: .75
82
+ });
83
+ labels.push({
84
+ text: fmt(v),
85
+ x: x0 - 3,
86
+ y: gy - 9 / 3,
87
+ sizePt: 9,
88
+ colorHex: LABEL_COLOR,
89
+ align: "right"
90
+ });
91
+ }
92
+ }
93
+ function pushAxisLines(polylines, x0, y0, plotW, plotH) {
94
+ polylines.push({
95
+ points: [[x0, y0], [x0, y0 + plotH]],
96
+ strokeHex: AXIS_COLOR,
97
+ widthPt: 1
98
+ });
99
+ polylines.push({
100
+ points: [[x0, y0], [x0 + plotW, y0]],
101
+ strokeHex: AXIS_COLOR,
102
+ widthPt: 1
103
+ });
104
+ }
43
105
  function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
44
106
  const rects = [];
45
107
  const polylines = [];
@@ -53,10 +115,7 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
53
115
  let top = 4;
54
116
  if (chart.title) top += 13 * 1.6;
55
117
  if (chart.valAxisTitle) top += 9 * 1.4;
56
- const legend = layoutLegend(chart.series.map((s, i) => ({
57
- name: s.name ?? "",
58
- colorHex: seriesColor(s, i)
59
- })).filter((e) => e.name !== ""), chart.hasLegend, chart.legendPos ?? "b", wPt, hPt, measure);
118
+ const legend = buildLegendBlock(chart, wPt, hPt, measure);
60
119
  const plotRight = wPt - 4 - legend.rightWidth;
61
120
  const tickLabelW = Math.max(0, ...tickVals.map((v) => measure(fmtVal(v), 9))) + 4;
62
121
  const catLabelH = 9 * 1.6;
@@ -67,14 +126,7 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
67
126
  const plotH = Math.max(1, hPt - top - y0);
68
127
  const valueOffset = (v) => (v - scale.min) / (scale.max - scale.min) * (horizontal ? plotW : plotH);
69
128
  const zeroOffset = valueOffset(0);
70
- if (chart.title) labels.push({
71
- text: chart.title,
72
- x: wPt / 2,
73
- y: hPt - 4 - 13,
74
- sizePt: 13,
75
- colorHex: TITLE_COLOR,
76
- align: "center"
77
- });
129
+ pushChartTitle(labels, chart, wPt, hPt);
78
130
  if (chart.valAxisTitle) labels.push({
79
131
  text: chart.valAxisTitle,
80
132
  x: x0,
@@ -91,40 +143,8 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
91
143
  colorHex: LABEL_COLOR,
92
144
  align: "center"
93
145
  });
94
- for (const v of tickVals) {
95
- const off = valueOffset(v);
96
- if (horizontal) {
97
- const gx = x0 + off;
98
- polylines.push({
99
- points: [[gx, y0], [gx, y0 + plotH]],
100
- strokeHex: GRID_COLOR,
101
- widthPt: .75
102
- });
103
- labels.push({
104
- text: fmtVal(v),
105
- x: gx,
106
- y: y0 - 9,
107
- sizePt: 9,
108
- colorHex: LABEL_COLOR,
109
- align: "center"
110
- });
111
- } else {
112
- const gy = y0 + off;
113
- polylines.push({
114
- points: [[x0, gy], [x0 + plotW, gy]],
115
- strokeHex: GRID_COLOR,
116
- widthPt: .75
117
- });
118
- labels.push({
119
- text: fmtVal(v),
120
- x: x0 - 3,
121
- y: gy - 9 / 3,
122
- sizePt: 9,
123
- colorHex: LABEL_COLOR,
124
- align: "right"
125
- });
126
- }
127
- }
146
+ if (horizontal) pushGridTicks(polylines, labels, tickVals, fmtVal, "x", (v) => x0 + valueOffset(v), x0, y0, plotW, plotH);
147
+ else pushGridTicks(polylines, labels, tickVals, fmtVal, "y", (v) => y0 + valueOffset(v), x0, y0, plotW, plotH);
128
148
  const slot = (horizontal ? plotH : plotW) / nCats;
129
149
  for (let c = 0; c < nCats; c++) {
130
150
  const cat = chart.categories[c] ?? "";
@@ -147,16 +167,7 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
147
167
  align: "center"
148
168
  });
149
169
  }
150
- polylines.push({
151
- points: [[x0, y0], [x0, y0 + plotH]],
152
- strokeHex: AXIS_COLOR,
153
- widthPt: 1
154
- });
155
- polylines.push({
156
- points: [[x0, y0], [x0 + plotW, y0]],
157
- strokeHex: AXIS_COLOR,
158
- widthPt: 1
159
- });
170
+ pushAxisLines(polylines, x0, y0, plotW, plotH);
160
171
  legend.emit(rects, labels);
161
172
  return {
162
173
  x0,
@@ -232,7 +243,7 @@ function buildBarScene(chart, wPt, hPt, measure) {
232
243
  const o1 = f.valueOffset(top);
233
244
  const lo = Math.min(o0, o1);
234
245
  const span = Math.abs(o1 - o0);
235
- const color = pointColor(series, c) ?? seriesColor(series, s);
246
+ const color = pointColor(series, c) ?? seriesColor(series, s, chart.seriesColorCycle);
236
247
  if (horizontal) f.rects.push({
237
248
  x: f.x0 + lo,
238
249
  y: along,
@@ -271,7 +282,7 @@ function buildBarScene(chart, wPt, hPt, measure) {
271
282
  for (let s = 0; s < chart.series.length; s++) {
272
283
  const series = chart.series[s];
273
284
  const len = f.valueOffset(series.values[c] ?? 0) - f.zeroOffset;
274
- const color = pointColor(series, c) ?? seriesColor(series, s);
285
+ const color = pointColor(series, c) ?? seriesColor(series, s, chart.seriesColorCycle);
275
286
  const along = slotStart + s * barW;
276
287
  if (horizontal) {
277
288
  const bx = f.x0 + f.zeroOffset + Math.min(0, len);
@@ -356,7 +367,7 @@ function buildAreaScene(chart, wPt, hPt, measure) {
356
367
  const denom = percent ? chart.series.reduce((a, ss) => a + Math.abs(ss.values[c] ?? 0), 0) || 1 : 1;
357
368
  return b + (series.values[c] ?? 0) / denom;
358
369
  });
359
- polygons.push(areaBand(top, base, f, xAt, seriesColor(series, s)));
370
+ polygons.push(areaBand(top, base, f, xAt, seriesColor(series, s, chart.seriesColorCycle)));
360
371
  for (let c = 0; c < nCats; c++) cum[c] = top[c];
361
372
  }
362
373
  } else {
@@ -364,7 +375,7 @@ function buildAreaScene(chart, wPt, hPt, measure) {
364
375
  for (let s = chart.series.length - 1; s >= 0; s--) {
365
376
  const series = chart.series[s];
366
377
  const top = Array.from({ length: nCats }, (_, c) => series.values[c] ?? 0);
367
- polygons.push(areaBand(top, base, f, xAt, seriesColor(series, s)));
378
+ polygons.push(areaBand(top, base, f, xAt, seriesColor(series, s, chart.seriesColorCycle)));
368
379
  }
369
380
  }
370
381
  return {
@@ -397,69 +408,20 @@ function buildScatterScene(chart, wPt, hPt, measure) {
397
408
  const yTicks = ticks(yScale);
398
409
  let top = 4;
399
410
  if (chart.title) top += 13 * 1.6;
400
- const legend = layoutLegend(chart.series.map((s, i) => ({
401
- name: s.name ?? "",
402
- colorHex: seriesColor(s, i)
403
- })).filter((e) => e.name !== ""), chart.hasLegend, chart.legendPos ?? "b", wPt, hPt, measure);
411
+ const legend = buildLegendBlock(chart, wPt, hPt, measure);
404
412
  const x0 = 4 + (Math.max(0, ...yTicks.map((v) => measure(formatTick(v, yScale.step), 9))) + 4);
405
413
  const y0 = 4 + legend.bottomHeight + 9 * 1.6;
406
414
  const plotW = Math.max(1, wPt - 4 - legend.rightWidth - x0);
407
415
  const plotH = Math.max(1, hPt - top - y0);
408
416
  const xAt = (v) => x0 + (v - xScale.min) / (xScale.max - xScale.min) * plotW;
409
417
  const yAt = (v) => y0 + (v - yScale.min) / (yScale.max - yScale.min) * plotH;
410
- if (chart.title) labels.push({
411
- text: chart.title,
412
- x: wPt / 2,
413
- y: hPt - 4 - 13,
414
- sizePt: 13,
415
- colorHex: TITLE_COLOR,
416
- align: "center"
417
- });
418
- for (const v of yTicks) {
419
- const gy = yAt(v);
420
- polylines.push({
421
- points: [[x0, gy], [x0 + plotW, gy]],
422
- strokeHex: GRID_COLOR,
423
- widthPt: .75
424
- });
425
- labels.push({
426
- text: formatTick(v, yScale.step),
427
- x: x0 - 3,
428
- y: gy - 9 / 3,
429
- sizePt: 9,
430
- colorHex: LABEL_COLOR,
431
- align: "right"
432
- });
433
- }
434
- for (const v of xTicks) {
435
- const gx = xAt(v);
436
- polylines.push({
437
- points: [[gx, y0], [gx, y0 + plotH]],
438
- strokeHex: GRID_COLOR,
439
- widthPt: .75
440
- });
441
- labels.push({
442
- text: formatTick(v, xScale.step),
443
- x: gx,
444
- y: y0 - 9,
445
- sizePt: 9,
446
- colorHex: LABEL_COLOR,
447
- align: "center"
448
- });
449
- }
450
- polylines.push({
451
- points: [[x0, y0], [x0, y0 + plotH]],
452
- strokeHex: AXIS_COLOR,
453
- widthPt: 1
454
- });
455
- polylines.push({
456
- points: [[x0, y0], [x0 + plotW, y0]],
457
- strokeHex: AXIS_COLOR,
458
- widthPt: 1
459
- });
418
+ pushChartTitle(labels, chart, wPt, hPt);
419
+ pushGridTicks(polylines, labels, yTicks, (v) => formatTick(v, yScale.step), "y", yAt, x0, y0, plotW, plotH);
420
+ pushGridTicks(polylines, labels, xTicks, (v) => formatTick(v, xScale.step), "x", xAt, x0, y0, plotW, plotH);
421
+ pushAxisLines(polylines, x0, y0, plotW, plotH);
460
422
  for (let s = 0; s < chart.series.length; s++) {
461
423
  const series = chart.series[s];
462
- const color = seriesColor(series, s);
424
+ const color = seriesColor(series, s, chart.seriesColorCycle);
463
425
  for (let i = 0; i < series.values.length; i++) {
464
426
  const px = xAt(series.xValues?.[i] ?? i);
465
427
  const py = yAt(series.values[i] ?? 0);
@@ -485,7 +447,7 @@ function buildLineScene(chart, wPt, hPt, measure) {
485
447
  const f = buildFrame(chart, wPt, hPt, measure, false, { dataRange: allVals.length > 0 ? [Math.min(...allVals), Math.max(...allVals)] : [0, 1] });
486
448
  for (let s = 0; s < chart.series.length; s++) {
487
449
  const series = chart.series[s];
488
- const color = seriesColor(series, s);
450
+ const color = seriesColor(series, s, chart.seriesColorCycle);
489
451
  const pts = [];
490
452
  for (let c = 0; c < f.nCats; c++) {
491
453
  const x = f.x0 + c * f.slot + f.slot / 2;
@@ -1,3 +1,6 @@
1
1
  import { Chart } from '../document-model/index.js';
2
+ import { OpcPackage } from '../opc/index.js';
2
3
  import { ColorResolver } from './colors.js';
3
4
  export declare function parseChart(chartXml: Uint8Array, resolveColor: ColorResolver): Chart | null;
5
+ export declare function parseChartColorStyle(colorsXml: Uint8Array, resolveColor: ColorResolver): Array<string>;
6
+ export declare function withChartColorStyle(chart: Chart, pkg: OpcPackage, chartPartPath: string, resolveColor: ColorResolver): Chart;
@@ -191,5 +191,32 @@ function isLegendPos(v) {
191
191
  function isGrouping(v) {
192
192
  return v === "clustered" || v === "stacked" || v === "percentStacked" || v === "standard";
193
193
  }
194
+ function parseChartColorStyle(colorsXml, resolveColor) {
195
+ const root = parser.parse(new TextDecoder().decode(colorsXml)).find((n) => {
196
+ const tag = Object.keys(n).find((k) => k !== ":@" && k !== "#text");
197
+ return tag !== void 0 && tag.endsWith("colorStyle");
198
+ });
199
+ if (!root) return [];
200
+ const out = [];
201
+ for (const child of poChildren(root)) {
202
+ const hex = resolveColorNode(child, resolveColor);
203
+ if (hex !== void 0) out.push(hex);
204
+ }
205
+ return out;
206
+ }
207
+ function withChartColorStyle(chart, pkg, chartPartPath, resolveColor) {
208
+ for (const rel of pkg.getPartRelationships(chartPartPath)) {
209
+ if (rel.type !== REL_CHART_COLOR_STYLE) continue;
210
+ const resolved = pkg.resolveRelatedPart(chartPartPath, rel);
211
+ if (!resolved) continue;
212
+ const cycle = parseChartColorStyle(resolved.data, resolveColor);
213
+ if (cycle.length > 0) return {
214
+ ...chart,
215
+ seriesColorCycle: cycle
216
+ };
217
+ }
218
+ return chart;
219
+ }
220
+ var REL_CHART_COLOR_STYLE = "http://schemas.microsoft.com/office/2011/relationships/chartColorStyle";
194
221
  //#endregion
195
- export { parseChart };
222
+ export { parseChart, withChartColorStyle };
@@ -12,7 +12,8 @@ function applyNumbering(body, numbering) {
12
12
  const level = (instance ? numbering.abstractNums.get(instance.abstractNumId) : void 0)?.levels.get(ref.ilvl);
13
13
  const markerRun = {
14
14
  text: `${marker}\t`,
15
- properties: level?.runProperties ?? {}
15
+ properties: level?.runProperties ?? {},
16
+ listMarker: true
16
17
  };
17
18
  const newProps = mergeIndentFromLevel(p.properties, level?.paragraphProperties);
18
19
  return {
@@ -3,3 +3,6 @@ export type { Relationship } from './relationships.js';
3
3
  export { REL_OFFICE_DOCUMENT, REL_CORE_PROPERTIES, REL_EXTENDED_PROPERTIES, } from './relationship-types.js';
4
4
  export { parseCoreProperties } from './core-properties.js';
5
5
  export type { CoreProperties } from './core-properties.js';
6
+ export { isOoxmlRel } from './relationship-types.js';
7
+ export { buildOpcPackage, relsPathFor } from './opc-writer.js';
8
+ export type { OpcPart, OpcPartRelationships, OpcWriteOptions } from './opc-writer.js';
@@ -1,3 +1,4 @@
1
1
  import "./relationship-types.js";
2
2
  import "./package.js";
3
3
  import "./core-properties.js";
4
+ import "./opc-writer.js";
@@ -0,0 +1,28 @@
1
+ import { Relationship } from './relationships.js';
2
+ export interface OpcPart {
3
+ /** Package path, e.g. 'word/document.xml'. No leading slash. */
4
+ readonly path: string;
5
+ readonly data: Uint8Array;
6
+ /** Content type for the [Content_Types].xml Override (XML parts). */
7
+ readonly contentType: string;
8
+ }
9
+ export interface OpcPartRelationships {
10
+ /** The owning part, e.g. 'word/document.xml' (or '' for the package root). */
11
+ readonly sourcePart: string;
12
+ readonly relationships: ReadonlyArray<Relationship>;
13
+ }
14
+ export interface OpcWriteOptions {
15
+ readonly parts: ReadonlyArray<OpcPart>;
16
+ /** Package-level relationships (_rels/.rels). */
17
+ readonly rootRelationships: ReadonlyArray<Relationship>;
18
+ /** Per-part relationships (word/_rels/<part>.rels). */
19
+ readonly partRelationships?: ReadonlyArray<OpcPartRelationships>;
20
+ /**
21
+ * Default content types by lowercase extension, for parts not given an
22
+ * Override (typically binaries: png, jpeg…). `rels` and `xml` are always
23
+ * present and need not be repeated.
24
+ */
25
+ readonly defaultsByExtension?: Readonly<Record<string, string>>;
26
+ }
27
+ export declare function buildOpcPackage(options: OpcWriteOptions): Uint8Array;
28
+ export declare function relsPathFor(partPath: string): string;
@@ -0,0 +1,60 @@
1
+ import { zipSync } from "fflate";
2
+ //#region src/core/opc/opc-writer.ts
3
+ var encoder = new TextEncoder();
4
+ var RELS_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
5
+ var CT_NS = "http://schemas.openxmlformats.org/package/2006/content-types";
6
+ var FIXED_MTIME = Date.UTC(1980, 0, 1, 0, 0, 0);
7
+ function buildOpcPackage(options) {
8
+ const entries = {};
9
+ const add = (path, data) => {
10
+ entries[path] = [data, { mtime: FIXED_MTIME }];
11
+ };
12
+ add("[Content_Types].xml", encoder.encode(contentTypesXml(options)));
13
+ add("_rels/.rels", encoder.encode(relationshipsXml(options.rootRelationships)));
14
+ for (const part of options.parts) add(part.path, part.data);
15
+ for (const pr of options.partRelationships ?? []) {
16
+ if (pr.relationships.length === 0) continue;
17
+ add(relsPathFor(pr.sourcePart), encoder.encode(relationshipsXml(pr.relationships)));
18
+ }
19
+ return zipSync(entries);
20
+ }
21
+ function relsPathFor(partPath) {
22
+ const slash = partPath.lastIndexOf("/");
23
+ const dir = slash >= 0 ? partPath.slice(0, slash) : "";
24
+ const name = slash >= 0 ? partPath.slice(slash + 1) : partPath;
25
+ return dir ? `${dir}/_rels/${name}.rels` : `_rels/${name}.rels`;
26
+ }
27
+ function contentTypesXml(options) {
28
+ const defaults = {
29
+ rels: "application/vnd.openxmlformats-package.relationships+xml",
30
+ xml: "application/xml",
31
+ ...options.defaultsByExtension ?? {}
32
+ };
33
+ const lines = ["<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>", `<Types xmlns="${CT_NS}">`];
34
+ for (const ext of Object.keys(defaults).sort()) lines.push(`<Default Extension="${ext}" ContentType="${escapeAttr(defaults[ext])}"/>`);
35
+ for (const part of options.parts) {
36
+ const ext = extensionOf(part.path);
37
+ if (ext !== void 0 && defaults[ext] === part.contentType) continue;
38
+ lines.push(`<Override PartName="/${escapeAttr(part.path)}" ContentType="${escapeAttr(part.contentType)}"/>`);
39
+ }
40
+ lines.push("</Types>");
41
+ return lines.join("");
42
+ }
43
+ function relationshipsXml(rels) {
44
+ const lines = ["<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>", `<Relationships xmlns="${RELS_NS}">`];
45
+ for (const r of rels) {
46
+ const mode = r.targetMode === "External" ? " TargetMode=\"External\"" : "";
47
+ lines.push(`<Relationship Id="${escapeAttr(r.id)}" Type="${escapeAttr(r.type)}" Target="${escapeAttr(r.target)}"${mode}/>`);
48
+ }
49
+ lines.push("</Relationships>");
50
+ return lines.join("");
51
+ }
52
+ function extensionOf(path) {
53
+ const dot = path.lastIndexOf(".");
54
+ return dot >= 0 ? path.slice(dot + 1).toLowerCase() : void 0;
55
+ }
56
+ function escapeAttr(s) {
57
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
58
+ }
59
+ //#endregion
60
+ export { buildOpcPackage, relsPathFor };
@@ -1,5 +1,5 @@
1
1
  import { parseRelationships } from "./relationships.js";
2
- import "./relationship-types.js";
2
+ import { isOoxmlRel } from "./relationship-types.js";
3
3
  import { unzipSync } from "fflate";
4
4
  //#region src/core/opc/package.ts
5
5
  var ROOT_RELS_PATH = "_rels/.rels";
@@ -11,6 +11,7 @@ var OpcPackage = class OpcPackage {
11
11
  }
12
12
  relsCache = /* @__PURE__ */ new Map();
13
13
  static open(buffer, options = {}) {
14
+ if (buffer.length >= 4 && buffer[0] === 208 && buffer[1] === 207 && buffer[2] === 17 && buffer[3] === 224) throw new Error("OLE compound file: a password-protected (encrypted) OOXML document or a legacy binary .doc/.xls — re-save as an unencrypted .docx/.xlsx");
14
15
  const maxArchive = options.maxArchiveBytes ?? 128 * MIB;
15
16
  const maxEntry = options.maxEntryBytes ?? 256 * MIB;
16
17
  const maxTotal = options.maxTotalBytes ?? 512 * MIB;
@@ -77,7 +78,7 @@ var OpcPackage = class OpcPackage {
77
78
  };
78
79
  }
79
80
  getMainDocumentPath() {
80
- const candidates = this.rootRelationships.filter((r) => r.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" && r.targetMode === "Internal");
81
+ const candidates = this.rootRelationships.filter((r) => isOoxmlRel(r.type, "officeDocument") && r.targetMode === "Internal");
81
82
  if (candidates.length === 0) throw new Error("OPC package has no officeDocument relationship");
82
83
  if (candidates.length > 1) throw new Error(`OPC package has multiple officeDocument relationships (${candidates.length})`);
83
84
  return resolveTarget("/", candidates[0].target);
@@ -1,3 +1,4 @@
1
1
  export declare const REL_OFFICE_DOCUMENT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
2
2
  export declare const REL_CORE_PROPERTIES = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
3
3
  export declare const REL_EXTENDED_PROPERTIES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";
4
+ export declare function isOoxmlRel(type: string, name: string): boolean;
@@ -1,4 +1,7 @@
1
1
  //#region src/core/opc/relationship-types.ts
2
- var REL_OFFICE_DOCUMENT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
2
+ var OOXML_REL_BASES = ["http://schemas.openxmlformats.org/officeDocument/2006/relationships/", "http://purl.oclc.org/ooxml/officeDocument/relationships/"];
3
+ function isOoxmlRel(type, name) {
4
+ return OOXML_REL_BASES.some((base) => type === base + name);
5
+ }
3
6
  //#endregion
4
- export { REL_OFFICE_DOCUMENT };
7
+ export { isOoxmlRel };
@@ -1,5 +1,8 @@
1
1
  import { BodyElement, SectionProperties } from '../core/document-model/index.js';
2
2
  import { CellRange, DefinedName, ParsedWorksheet, XlsxStyles } from './index.js';
3
+ export declare const TWIPS_PER_EXCEL_CHAR = 105;
4
+ export declare const DEFAULT_COL_TWIPS = 960;
5
+ export declare const DEFAULT_ROW_TWIPS = 300;
3
6
  export declare function sectionFromWorksheet(worksheet: ParsedWorksheet): SectionProperties | undefined;
4
7
  export declare function resolvePrintArea(definedNames: ReadonlyArray<DefinedName>, sheetIdx: number): CellRange | undefined;
5
8
  export declare function resolvePrintTitleRows(definedNames: ReadonlyArray<DefinedName>, sheetIdx: number): {