reamkit 1.23.0 → 1.25.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.
- package/README.md +22 -11
- package/dist/esm/core/bmp.d.ts +33 -0
- package/dist/esm/core/bmp.js +276 -0
- package/dist/esm/core/converter/facade.d.ts +3 -3
- package/dist/esm/core/converter/facade.js +12 -0
- package/dist/esm/core/converter/ream.d.ts +48 -4
- package/dist/esm/core/converter/ream.js +25 -4
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +54 -0
- package/dist/esm/core/drawingml/chart-geometry.js +3 -2
- package/dist/esm/core/drawingml/diagram/colors.d.ts +52 -0
- package/dist/esm/core/drawingml/diagram/colors.js +125 -0
- package/dist/esm/core/drawingml/diagram/data-model.d.ts +47 -0
- package/dist/esm/core/drawingml/diagram/data-model.js +119 -0
- package/dist/esm/core/drawingml/diagram/layout-engine.d.ts +46 -0
- package/dist/esm/core/drawingml/diagram/layout-engine.js +948 -0
- package/dist/esm/core/drawingml/diagram/run.d.ts +37 -0
- package/dist/esm/core/drawingml/diagram/run.js +50 -0
- package/dist/esm/core/drawingml/diagram/to-drawing.d.ts +16 -0
- package/dist/esm/core/drawingml/diagram/to-drawing.js +86 -0
- package/dist/esm/core/drawingml/preset-geometry.js +116 -6
- package/dist/esm/core/drawingml/text-warp.d.ts +58 -0
- package/dist/esm/core/drawingml/text-warp.js +355 -0
- package/dist/esm/core/drawingml/theme-parser.js +9 -3
- package/dist/esm/core/font/measure.d.ts +12 -0
- package/dist/esm/core/font/measure.js +36 -0
- package/dist/esm/core/images.d.ts +30 -2
- package/dist/esm/core/images.js +121 -5
- package/dist/esm/core/metafile/emf.js +144 -16
- package/dist/esm/core/metafile/picture.d.ts +49 -0
- package/dist/esm/core/metafile/picture.js +70 -1
- package/dist/esm/core/metafile/wmf.js +53 -4
- package/dist/esm/core/ole/escher-blip.js +11 -1
- package/dist/esm/core/outline.d.ts +17 -0
- package/dist/esm/core/outline.js +30 -0
- package/dist/esm/core/style-cascade/resolver.js +1 -0
- package/dist/esm/core/style-cascade/types.d.ts +3 -1
- package/dist/esm/excel/print-model.js +2 -2
- package/dist/esm/excel/sheet-drawing.js +1 -1
- package/dist/esm/excel/sheet-to-flow.d.ts +10 -0
- package/dist/esm/excel/sheet-to-flow.js +30 -3
- package/dist/esm/html/html-writer.js +5 -3
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/layout/page-doc.d.ts +35 -0
- package/dist/esm/layout/page-doc.js +14 -1
- package/dist/esm/layout/styled-layout.js +217 -30
- package/dist/esm/markdown/markdown-writer.d.ts +41 -0
- package/dist/esm/markdown/markdown-writer.js +733 -0
- package/dist/esm/pdf/shading.js +8 -6
- package/dist/esm/pdf/styled-page-emitter.js +62 -4
- package/dist/esm/pdf/vector-graphics.js +1 -1
- package/dist/esm/pdf-reader/annots.d.ts +24 -0
- package/dist/esm/pdf-reader/annots.js +126 -0
- package/dist/esm/pdf-reader/content.d.ts +131 -5
- package/dist/esm/pdf-reader/content.js +169 -12
- package/dist/esm/pdf-reader/display.d.ts +56 -0
- package/dist/esm/pdf-reader/display.js +162 -0
- package/dist/esm/pdf-reader/document.d.ts +36 -1
- package/dist/esm/pdf-reader/document.js +92 -25
- package/dist/esm/pdf-reader/embedded-fonts.d.ts +31 -0
- package/dist/esm/pdf-reader/embedded-fonts.js +94 -0
- package/dist/esm/pdf-reader/flow-build.d.ts +61 -6
- package/dist/esm/pdf-reader/flow-build.js +128 -22
- package/dist/esm/pdf-reader/font.js +185 -4
- package/dist/esm/pdf-reader/image-decode.js +56 -5
- package/dist/esm/pdf-reader/images.d.ts +6 -0
- package/dist/esm/pdf-reader/images.js +25 -5
- package/dist/esm/pdf-reader/jpeg.d.ts +18 -0
- package/dist/esm/pdf-reader/jpeg.js +419 -0
- package/dist/esm/pdf-reader/layout.d.ts +1 -1
- package/dist/esm/pdf-reader/layout.js +221 -32
- package/dist/esm/pdf-reader/pattern-tint.d.ts +17 -0
- package/dist/esm/pdf-reader/pattern-tint.js +181 -0
- package/dist/esm/pdf-reader/reader.d.ts +9 -1
- package/dist/esm/pdf-reader/reader.js +22 -6
- package/dist/esm/pdf-reader/shading.d.ts +14 -0
- package/dist/esm/pdf-reader/shading.js +27 -1
- package/dist/esm/pdf-reader/tagged.js +156 -17
- package/dist/esm/pdf-reader/text.d.ts +13 -1
- package/dist/esm/pdf-reader/text.js +70 -3
- package/dist/esm/pdf-reader/vector.d.ts +25 -1
- package/dist/esm/pdf-reader/vector.js +168 -12
- package/dist/esm/pptx/ppt/ppt-reader.js +95 -27
- package/dist/esm/pptx/ppt/ppt-text.d.ts +41 -0
- package/dist/esm/pptx/ppt/ppt-text.js +327 -37
- package/dist/esm/pptx/pptx-reader.js +90 -10
- package/dist/esm/pptx/slide-parser.d.ts +4 -1
- package/dist/esm/pptx/slide-parser.js +104 -13
- package/dist/esm/pptx/sp-helpers.d.ts +7 -3
- package/dist/esm/pptx/sp-helpers.js +45 -7
- package/dist/esm/pptx/table-style.d.ts +7 -0
- package/dist/esm/pptx/table-style.js +59 -7
- package/dist/esm/svg/svg-writer.js +1 -0
- package/dist/esm/word/document-parser.d.ts +4 -1
- package/dist/esm/word/document-parser.js +4 -1
- package/dist/esm/word/docx-reader.js +26 -13
- package/dist/esm/word/docx-writer.js +15 -1
- package/dist/esm/word/drawing-parser.d.ts +20 -0
- package/dist/esm/word/drawing-parser.js +44 -5
- package/dist/esm/word/table-parser.js +3 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PathBuilder } from "../vector.js";
|
|
2
|
-
import { applyTransform, cloneDc, colorRef, newDeviceContext } from "./picture.js";
|
|
2
|
+
import { applyTransform, clippedAway, cloneDc, colorRef, intersectClip, newDeviceContext, primBounds } from "./picture.js";
|
|
3
3
|
import { cropDib, fadedDib, readDib } from "./dib.js";
|
|
4
4
|
import { makeBlitter } from "./blit.js";
|
|
5
5
|
import { fromSymbolFont } from "./symbol-fonts.js";
|
|
@@ -71,7 +71,14 @@ function readEmf(bytes) {
|
|
|
71
71
|
};
|
|
72
72
|
let building;
|
|
73
73
|
const open = () => building ?? [];
|
|
74
|
-
const
|
|
74
|
+
const inFigure = () => {
|
|
75
|
+
const tail = building?.[building.length - 1];
|
|
76
|
+
return tail !== void 0 && tail.op !== "close";
|
|
77
|
+
};
|
|
78
|
+
const emit = (prim) => {
|
|
79
|
+
if (!clippedAway(dc, primBounds(prim))) prims.push(prim);
|
|
80
|
+
};
|
|
81
|
+
const blitter = makeBlitter(emit);
|
|
75
82
|
/**
|
|
76
83
|
* §2.3.1 — one blit record: the source bitmap into the destination
|
|
77
84
|
* rectangle, both stated in the record's own fields. The rectangle is mapped
|
|
@@ -101,7 +108,10 @@ function readEmf(bytes) {
|
|
|
101
108
|
};
|
|
102
109
|
const strokeOf = () => {
|
|
103
110
|
if (dc.pen.style === "none") return void 0;
|
|
104
|
-
const
|
|
111
|
+
const m = dc.transform;
|
|
112
|
+
const world = Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2])) || 1;
|
|
113
|
+
const lu = dc.pen.widthLu * world;
|
|
114
|
+
const w = lu <= 1 ? 1 : lu * Math.abs(sx());
|
|
105
115
|
return {
|
|
106
116
|
colorHex: dc.pen.colorHex,
|
|
107
117
|
widthPt: w,
|
|
@@ -117,13 +127,22 @@ function readEmf(bytes) {
|
|
|
117
127
|
const st = stroke ? strokeOf() : void 0;
|
|
118
128
|
const fillHex = fill && !dc.brush.hollow ? dc.brush.colorHex : void 0;
|
|
119
129
|
if (fillHex === void 0 && !st) return;
|
|
120
|
-
|
|
130
|
+
emit({
|
|
121
131
|
kind: "path",
|
|
122
132
|
paths: [path],
|
|
123
133
|
...fillHex !== void 0 ? { fillColorHex: fillHex } : {},
|
|
124
134
|
...st ? { stroke: st } : {}
|
|
125
135
|
});
|
|
126
136
|
};
|
|
137
|
+
/** Two mapped corners → the rectangle they bound, in either order. */
|
|
138
|
+
function rectOf(a, b) {
|
|
139
|
+
return {
|
|
140
|
+
left: Math.min(a.x, b.x),
|
|
141
|
+
top: Math.min(a.y, b.y),
|
|
142
|
+
right: Math.max(a.x, b.x),
|
|
143
|
+
bottom: Math.max(a.y, b.y)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
127
146
|
/** A closed rectangle, as the geometry records draw one. */
|
|
128
147
|
const rectSegments = (l, t, r, b) => {
|
|
129
148
|
const p0 = px(l, t);
|
|
@@ -176,6 +195,32 @@ function readEmf(bytes) {
|
|
|
176
195
|
};
|
|
177
196
|
haveView = true;
|
|
178
197
|
break;
|
|
198
|
+
case 30: {
|
|
199
|
+
const a = px(at(8), at(12));
|
|
200
|
+
const b = px(at(16), at(20));
|
|
201
|
+
dc = {
|
|
202
|
+
...dc,
|
|
203
|
+
clip: intersectClip(dc, rectOf(a, b))
|
|
204
|
+
};
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
case 75: {
|
|
208
|
+
const cbRgnData = u(8);
|
|
209
|
+
const mode = u(12);
|
|
210
|
+
if (cbRgnData === 0 && mode === 5) {
|
|
211
|
+
const { clip: _drop, ...rest } = dc;
|
|
212
|
+
dc = rest;
|
|
213
|
+
} else if (mode === 5 && cbRgnData >= 32 && u(20) === 1) {
|
|
214
|
+
const base = off + 16 + 32;
|
|
215
|
+
const a = px(v.getInt32(base, true), v.getInt32(base + 4, true));
|
|
216
|
+
const b = px(v.getInt32(base + 8, true), v.getInt32(base + 12, true));
|
|
217
|
+
dc = {
|
|
218
|
+
...dc,
|
|
219
|
+
clip: rectOf(a, b)
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
179
224
|
case 33:
|
|
180
225
|
stack.push(cloneDc(dc));
|
|
181
226
|
break;
|
|
@@ -281,7 +326,7 @@ function readEmf(bytes) {
|
|
|
281
326
|
dc.y = at(12);
|
|
282
327
|
const to = px(dc.x, dc.y);
|
|
283
328
|
if (building) {
|
|
284
|
-
if (
|
|
329
|
+
if (!inFigure()) building.push({
|
|
285
330
|
op: "move",
|
|
286
331
|
x: from.x,
|
|
287
332
|
y: from.y
|
|
@@ -327,6 +372,11 @@ function readEmf(bytes) {
|
|
|
327
372
|
case 44:
|
|
328
373
|
paint(rectSegments(at(8), at(12), at(16), at(20)), true, true);
|
|
329
374
|
break;
|
|
375
|
+
case 47:
|
|
376
|
+
case 46:
|
|
377
|
+
case 45:
|
|
378
|
+
paint(arcSegments(px, at(8), at(12), at(16), at(20), at(24), at(28), at(32), at(36), type), type !== 45, true);
|
|
379
|
+
break;
|
|
330
380
|
case 3:
|
|
331
381
|
case 4:
|
|
332
382
|
case 2:
|
|
@@ -345,10 +395,13 @@ function readEmf(bytes) {
|
|
|
345
395
|
const segs = polySegments(px, pts, {
|
|
346
396
|
closed,
|
|
347
397
|
bezier: type === 2 || type === 5 || type === 85 || type === 88,
|
|
348
|
-
...type === 5 || type === 6 || type === 88 || type === 89 ? {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
398
|
+
...type === 5 || type === 6 || type === 88 || type === 89 ? {
|
|
399
|
+
from: {
|
|
400
|
+
x: dc.x,
|
|
401
|
+
y: dc.y
|
|
402
|
+
},
|
|
403
|
+
joined: inFigure()
|
|
404
|
+
} : {}
|
|
352
405
|
});
|
|
353
406
|
if (pts.length > 0) {
|
|
354
407
|
dc.x = pts[pts.length - 1].x;
|
|
@@ -395,7 +448,7 @@ function readEmf(bytes) {
|
|
|
395
448
|
if (text.trim() !== "") {
|
|
396
449
|
const p = px(refX, refY);
|
|
397
450
|
const font = dc.font;
|
|
398
|
-
|
|
451
|
+
emit({
|
|
399
452
|
kind: "text",
|
|
400
453
|
text: fromSymbolFont(text, font?.family),
|
|
401
454
|
x: p.x,
|
|
@@ -422,6 +475,7 @@ function readEmf(bytes) {
|
|
|
422
475
|
const h = at(36);
|
|
423
476
|
if (cbBmiSrc === 0) {
|
|
424
477
|
const hex = rop === 66 ? "000000" : rop === 16711778 ? "FFFFFF" : void 0;
|
|
478
|
+
if (hex === void 0 && rop !== 15728673) break;
|
|
425
479
|
const brush = dc.brush;
|
|
426
480
|
if (hex !== void 0) dc.brush = {
|
|
427
481
|
kind: "brush",
|
|
@@ -671,12 +725,14 @@ function polySegments(map, pts, o) {
|
|
|
671
725
|
if (pts.length === 0) return segs;
|
|
672
726
|
let i = 0;
|
|
673
727
|
if (o.from) {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
728
|
+
if (o.joined !== true) {
|
|
729
|
+
const s = map(o.from.x, o.from.y);
|
|
730
|
+
segs.push({
|
|
731
|
+
op: "move",
|
|
732
|
+
x: s.x,
|
|
733
|
+
y: s.y
|
|
734
|
+
});
|
|
735
|
+
}
|
|
680
736
|
} else {
|
|
681
737
|
const s = map(pts[0].x, pts[0].y);
|
|
682
738
|
segs.push({
|
|
@@ -712,6 +768,78 @@ function polySegments(map, pts, o) {
|
|
|
712
768
|
return segs;
|
|
713
769
|
}
|
|
714
770
|
var KAPPA = .5522847498307936;
|
|
771
|
+
/**
|
|
772
|
+
* §2.3.5.2/.4/.13 — the wedge, chord or bare curve an `EMR_ARC`, `EMR_CHORD` or
|
|
773
|
+
* `EMR_PIE` draws.
|
|
774
|
+
*
|
|
775
|
+
* All three state the same thing: an ellipse inscribed in a box, and two points
|
|
776
|
+
* naming RAYS from its centre. The arc runs from where the first ray crosses
|
|
777
|
+
* the ellipse to where the second does, anticlockwise on the page — GDI's
|
|
778
|
+
* default direction — and the two records differ only in how they close: a pie
|
|
779
|
+
* back through the centre, a chord straight across, an arc not at all.
|
|
780
|
+
*
|
|
781
|
+
* Built in the metafile's own coordinates and mapped point by point, exactly as
|
|
782
|
+
* the ellipse and the polygons are: the mapping is affine, so a Bézier's
|
|
783
|
+
* control points survive it.
|
|
784
|
+
*
|
|
785
|
+
* @param map The metafile's logical → page mapping.
|
|
786
|
+
* @param l,t,r,b The box the ellipse is inscribed in.
|
|
787
|
+
* @param sx,sy The point naming the ray the arc starts at.
|
|
788
|
+
* @param ex,ey The point naming the ray it ends at.
|
|
789
|
+
* @param type The record type, which decides how the figure closes.
|
|
790
|
+
* @returns The path segments, in page coordinates.
|
|
791
|
+
*/
|
|
792
|
+
function arcSegments(map, l, t, r, b, sx, sy, ex, ey, type) {
|
|
793
|
+
const cx = (l + r) / 2;
|
|
794
|
+
const cy = (t + b) / 2;
|
|
795
|
+
const rx = (r - l) / 2;
|
|
796
|
+
const ry = (b - t) / 2;
|
|
797
|
+
if (!(rx > 0) || !(ry > 0)) return [];
|
|
798
|
+
const angleOf = (x, y) => Math.atan2((y - cy) / ry, (x - cx) / rx);
|
|
799
|
+
const start = angleOf(sx, sy);
|
|
800
|
+
let sweep = angleOf(ex, ey) - start;
|
|
801
|
+
if (sweep >= 0) sweep -= 2 * Math.PI;
|
|
802
|
+
const point = (a) => map(cx + rx * Math.cos(a), cy + ry * Math.sin(a));
|
|
803
|
+
const segs = [];
|
|
804
|
+
const from = point(start);
|
|
805
|
+
if (type === 47) {
|
|
806
|
+
const c = map(cx, cy);
|
|
807
|
+
segs.push({
|
|
808
|
+
op: "move",
|
|
809
|
+
x: c.x,
|
|
810
|
+
y: c.y
|
|
811
|
+
}, {
|
|
812
|
+
op: "line",
|
|
813
|
+
x: from.x,
|
|
814
|
+
y: from.y
|
|
815
|
+
});
|
|
816
|
+
} else segs.push({
|
|
817
|
+
op: "move",
|
|
818
|
+
x: from.x,
|
|
819
|
+
y: from.y
|
|
820
|
+
});
|
|
821
|
+
const steps = Math.max(1, Math.ceil(Math.abs(sweep) / (Math.PI / 2)));
|
|
822
|
+
const delta = sweep / steps;
|
|
823
|
+
const alpha = 4 / 3 * Math.tan(delta / 4);
|
|
824
|
+
for (let i = 0; i < steps; i++) {
|
|
825
|
+
const a0 = start + delta * i;
|
|
826
|
+
const a1 = a0 + delta;
|
|
827
|
+
const t0 = map(cx + rx * Math.cos(a0) - alpha * rx * Math.sin(a0), cy + ry * Math.sin(a0) + alpha * ry * Math.cos(a0));
|
|
828
|
+
const t1 = map(cx + rx * Math.cos(a1) + alpha * rx * Math.sin(a1), cy + ry * Math.sin(a1) - alpha * ry * Math.cos(a1));
|
|
829
|
+
const to = point(a1);
|
|
830
|
+
segs.push({
|
|
831
|
+
op: "cubic",
|
|
832
|
+
x1: t0.x,
|
|
833
|
+
y1: t0.y,
|
|
834
|
+
x2: t1.x,
|
|
835
|
+
y2: t1.y,
|
|
836
|
+
x: to.x,
|
|
837
|
+
y: to.y
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
if (type !== 45) segs.push({ op: "close" });
|
|
841
|
+
return segs;
|
|
842
|
+
}
|
|
715
843
|
function ellipseSegments(map, l, t, r, b) {
|
|
716
844
|
const cx = (l + r) / 2;
|
|
717
845
|
const cy = (t + b) / 2;
|
|
@@ -102,12 +102,61 @@ export interface DeviceContext {
|
|
|
102
102
|
y: number;
|
|
103
103
|
/** The world→page transform, as `[a, b, c, d, e, f]`. */
|
|
104
104
|
transform: readonly [number, number, number, number, number, number];
|
|
105
|
+
/**
|
|
106
|
+
* MS-EMF §2.3.2 / MS-WMF §2.3.2 — the region drawing is limited to, in
|
|
107
|
+
* logical units, when the metafile states a RECTANGULAR one. A primitive
|
|
108
|
+
* that falls wholly outside it is not drawn: an embedded workbook's preview
|
|
109
|
+
* clips its sheet to the used range, and unclipped we painted the whole
|
|
110
|
+
* empty grid around it.
|
|
111
|
+
*/
|
|
112
|
+
clip?: {
|
|
113
|
+
readonly left: number;
|
|
114
|
+
readonly top: number;
|
|
115
|
+
readonly right: number;
|
|
116
|
+
readonly bottom: number;
|
|
117
|
+
};
|
|
105
118
|
}
|
|
106
119
|
export declare const DEFAULT_PEN: MetaPen;
|
|
107
120
|
export declare const DEFAULT_BRUSH: MetaBrush;
|
|
108
121
|
/** A device context in its documented initial state (MS-WMF §3.1.5). */
|
|
109
122
|
export declare function newDeviceContext(): DeviceContext;
|
|
110
123
|
export declare function cloneDc(dc: DeviceContext): DeviceContext;
|
|
124
|
+
/**
|
|
125
|
+
* Whether a primitive bounded by `box` is wholly outside the device context's
|
|
126
|
+
* clip. Partial overlaps are kept whole — the reader discards what the clip
|
|
127
|
+
* excludes rather than cutting geometry, which is a missing cut, never wrong
|
|
128
|
+
* content.
|
|
129
|
+
*
|
|
130
|
+
* @param dc The device context, for its clip.
|
|
131
|
+
* @param box The primitive's bounds in logical units.
|
|
132
|
+
* @returns `true` when nothing of it would be drawn.
|
|
133
|
+
*/
|
|
134
|
+
export declare function clippedAway(dc: DeviceContext, box: {
|
|
135
|
+
left: number;
|
|
136
|
+
top: number;
|
|
137
|
+
right: number;
|
|
138
|
+
bottom: number;
|
|
139
|
+
}): boolean;
|
|
140
|
+
/** The intersection of a device context's clip with another rectangle. */
|
|
141
|
+
export declare function intersectClip(dc: DeviceContext, rect: {
|
|
142
|
+
left: number;
|
|
143
|
+
top: number;
|
|
144
|
+
right: number;
|
|
145
|
+
bottom: number;
|
|
146
|
+
}): NonNullable<DeviceContext['clip']>;
|
|
147
|
+
/**
|
|
148
|
+
* A primitive's bounds in the units it is stored in, for the clip test.
|
|
149
|
+
*
|
|
150
|
+
* @param prim The primitive.
|
|
151
|
+
* @returns Its bounding rectangle; a text run's is its reference point, since
|
|
152
|
+
* the box it fills is not known until it is measured.
|
|
153
|
+
*/
|
|
154
|
+
export declare function primBounds(prim: PicturePrim): {
|
|
155
|
+
left: number;
|
|
156
|
+
top: number;
|
|
157
|
+
right: number;
|
|
158
|
+
bottom: number;
|
|
159
|
+
};
|
|
111
160
|
/** A COLORREF (`0x00bbggrr`) as the 6-hex this codebase's colours use. */
|
|
112
161
|
export declare function colorRef(v: number): string;
|
|
113
162
|
/** Apply the world transform to a point. */
|
|
@@ -36,6 +36,75 @@ function newDeviceContext() {
|
|
|
36
36
|
function cloneDc(dc) {
|
|
37
37
|
return { ...dc };
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Whether a primitive bounded by `box` is wholly outside the device context's
|
|
41
|
+
* clip. Partial overlaps are kept whole — the reader discards what the clip
|
|
42
|
+
* excludes rather than cutting geometry, which is a missing cut, never wrong
|
|
43
|
+
* content.
|
|
44
|
+
*
|
|
45
|
+
* @param dc The device context, for its clip.
|
|
46
|
+
* @param box The primitive's bounds in logical units.
|
|
47
|
+
* @returns `true` when nothing of it would be drawn.
|
|
48
|
+
*/
|
|
49
|
+
function clippedAway(dc, box) {
|
|
50
|
+
const c = dc.clip;
|
|
51
|
+
if (!c) return false;
|
|
52
|
+
return box.right < c.left || box.left > c.right || box.bottom < c.top || box.top > c.bottom;
|
|
53
|
+
}
|
|
54
|
+
/** The intersection of a device context's clip with another rectangle. */
|
|
55
|
+
function intersectClip(dc, rect) {
|
|
56
|
+
const c = dc.clip;
|
|
57
|
+
if (!c) return rect;
|
|
58
|
+
return {
|
|
59
|
+
left: Math.max(c.left, rect.left),
|
|
60
|
+
top: Math.max(c.top, rect.top),
|
|
61
|
+
right: Math.min(c.right, rect.right),
|
|
62
|
+
bottom: Math.min(c.bottom, rect.bottom)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A primitive's bounds in the units it is stored in, for the clip test.
|
|
67
|
+
*
|
|
68
|
+
* @param prim The primitive.
|
|
69
|
+
* @returns Its bounding rectangle; a text run's is its reference point, since
|
|
70
|
+
* the box it fills is not known until it is measured.
|
|
71
|
+
*/
|
|
72
|
+
function primBounds(prim) {
|
|
73
|
+
if (prim.kind === "text") return {
|
|
74
|
+
left: prim.x,
|
|
75
|
+
top: prim.y,
|
|
76
|
+
right: prim.x,
|
|
77
|
+
bottom: prim.y
|
|
78
|
+
};
|
|
79
|
+
if (prim.kind === "image") return {
|
|
80
|
+
left: prim.x,
|
|
81
|
+
top: prim.y,
|
|
82
|
+
right: prim.x + prim.width,
|
|
83
|
+
bottom: prim.y + prim.height
|
|
84
|
+
};
|
|
85
|
+
let left = Infinity;
|
|
86
|
+
let top = Infinity;
|
|
87
|
+
let right = -Infinity;
|
|
88
|
+
let bottom = -Infinity;
|
|
89
|
+
for (const path of prim.paths) for (const sg of path.segments) {
|
|
90
|
+
if (!("x" in sg)) continue;
|
|
91
|
+
left = Math.min(left, sg.x);
|
|
92
|
+
right = Math.max(right, sg.x);
|
|
93
|
+
top = Math.min(top, sg.y);
|
|
94
|
+
bottom = Math.max(bottom, sg.y);
|
|
95
|
+
}
|
|
96
|
+
return Number.isFinite(left) ? {
|
|
97
|
+
left,
|
|
98
|
+
top,
|
|
99
|
+
right,
|
|
100
|
+
bottom
|
|
101
|
+
} : {
|
|
102
|
+
left: 0,
|
|
103
|
+
top: 0,
|
|
104
|
+
right: 0,
|
|
105
|
+
bottom: 0
|
|
106
|
+
};
|
|
107
|
+
}
|
|
39
108
|
/** A COLORREF (`0x00bbggrr`) as the 6-hex this codebase's colours use. */
|
|
40
109
|
function colorRef(v) {
|
|
41
110
|
return [
|
|
@@ -52,4 +121,4 @@ function applyTransform(t, x, y) {
|
|
|
52
121
|
};
|
|
53
122
|
}
|
|
54
123
|
//#endregion
|
|
55
|
-
export { applyTransform, cloneDc, colorRef, newDeviceContext };
|
|
124
|
+
export { applyTransform, clippedAway, cloneDc, colorRef, intersectClip, newDeviceContext, primBounds };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PathBuilder } from "../vector.js";
|
|
2
|
-
import { cloneDc, colorRef, newDeviceContext } from "./picture.js";
|
|
2
|
+
import { clippedAway, cloneDc, colorRef, intersectClip, newDeviceContext, primBounds } from "./picture.js";
|
|
3
3
|
import { cropDib, readDib } from "./dib.js";
|
|
4
4
|
import { makeBlitter } from "./blit.js";
|
|
5
5
|
import { fromSymbolFont, symbolGeometryOf, symbolOutline } from "./symbol-fonts.js";
|
|
@@ -62,6 +62,7 @@ function readWmf(bytes) {
|
|
|
62
62
|
};
|
|
63
63
|
let origin;
|
|
64
64
|
const emit = (prim) => {
|
|
65
|
+
if (clippedAway(dc, primBounds(prim))) return;
|
|
65
66
|
origin ??= {
|
|
66
67
|
x: win.x,
|
|
67
68
|
y: win.y
|
|
@@ -145,6 +146,17 @@ function readWmf(bytes) {
|
|
|
145
146
|
cy: p(0)
|
|
146
147
|
};
|
|
147
148
|
break;
|
|
149
|
+
case 1046:
|
|
150
|
+
dc = {
|
|
151
|
+
...dc,
|
|
152
|
+
clip: intersectClip(dc, {
|
|
153
|
+
bottom: p(0),
|
|
154
|
+
right: p(1),
|
|
155
|
+
top: p(2),
|
|
156
|
+
left: p(3)
|
|
157
|
+
})
|
|
158
|
+
};
|
|
159
|
+
break;
|
|
148
160
|
case 30:
|
|
149
161
|
stack.push(cloneDc(dc));
|
|
150
162
|
break;
|
|
@@ -388,12 +400,13 @@ function readWmf(bytes) {
|
|
|
388
400
|
right: win.x + (win.cx || 1e3),
|
|
389
401
|
bottom: win.y + (win.cy || 1e3)
|
|
390
402
|
};
|
|
403
|
+
const flipped = win.cy < 0 ? prims.map((p) => mirrorPrimY(p, box.top + box.bottom)) : prims;
|
|
391
404
|
return {
|
|
392
|
-
left: box.left,
|
|
393
|
-
top: box.top,
|
|
405
|
+
left: Math.min(box.left, box.right),
|
|
406
|
+
top: Math.min(box.top, box.bottom),
|
|
394
407
|
width: Math.max(1, Math.abs(box.right - box.left)),
|
|
395
408
|
height: Math.max(1, Math.abs(box.bottom - box.top)),
|
|
396
|
-
prims,
|
|
409
|
+
prims: flipped,
|
|
397
410
|
skipped: [...skipped]
|
|
398
411
|
};
|
|
399
412
|
}
|
|
@@ -451,6 +464,42 @@ function shiftPrim(prim, dx, dy) {
|
|
|
451
464
|
}))
|
|
452
465
|
};
|
|
453
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* A primitive mirrored about a horizontal axis, for a window whose y grows UP.
|
|
469
|
+
*
|
|
470
|
+
* Only the geometry turns over. A text run's reference point names a corner of
|
|
471
|
+
* the cell IN DEVICE SPACE — GDI never draws the glyphs themselves upside down —
|
|
472
|
+
* so the point moves and the alignment flags stay as they are. An image states
|
|
473
|
+
* the TOP of its destination rectangle, which after the mirror is where its
|
|
474
|
+
* bottom used to be.
|
|
475
|
+
*
|
|
476
|
+
* @param prim The primitive, in the metafile's own logical units.
|
|
477
|
+
* @param twiceAxis Twice the y the mirror runs along, so `y' = twiceAxis - y`.
|
|
478
|
+
* @returns The primitive with its y coordinates turned over.
|
|
479
|
+
*/
|
|
480
|
+
function mirrorPrimY(prim, twiceAxis) {
|
|
481
|
+
const at = (y) => twiceAxis - y;
|
|
482
|
+
if (prim.kind === "text") return {
|
|
483
|
+
...prim,
|
|
484
|
+
y: at(prim.y)
|
|
485
|
+
};
|
|
486
|
+
if (prim.kind === "image") return {
|
|
487
|
+
...prim,
|
|
488
|
+
y: at(prim.y + prim.height)
|
|
489
|
+
};
|
|
490
|
+
return {
|
|
491
|
+
...prim,
|
|
492
|
+
paths: prim.paths.map((path) => ({
|
|
493
|
+
...path,
|
|
494
|
+
segments: path.segments.map((sg) => "x" in sg ? {
|
|
495
|
+
...sg,
|
|
496
|
+
y: at(sg.y),
|
|
497
|
+
..."y1" in sg ? { y1: at(sg.y1) } : {},
|
|
498
|
+
..."y2" in sg ? { y2: at(sg.y2) } : {}
|
|
499
|
+
} : sg)
|
|
500
|
+
}))
|
|
501
|
+
};
|
|
502
|
+
}
|
|
454
503
|
function penStyle(style) {
|
|
455
504
|
switch (style & 15) {
|
|
456
505
|
case 1: return "dash";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dibToBmp } from "../bmp.js";
|
|
1
2
|
import { unzlibSync } from "fflate";
|
|
2
3
|
//#region src/core/ole/escher-blip.ts
|
|
3
4
|
var BLIP_EMF = 61466;
|
|
@@ -54,9 +55,18 @@ var RASTER_MAGICS = [
|
|
|
54
55
|
*/
|
|
55
56
|
function readEscherBlip(type, instance, data) {
|
|
56
57
|
if (type === BLIP_EMF || type === BLIP_WMF || type === BLIP_PICT) return readMetafileBlip(instance, data);
|
|
57
|
-
if (type ===
|
|
58
|
+
if (type === BLIP_DIB) return readDibBlip(instance, data) ?? scanRaster(data);
|
|
59
|
+
if (type === BLIP_JPEG || type === BLIP_PNG || type === BLIP_TIFF || type === BLIP_JPEG_CMYK) return scanRaster(data);
|
|
58
60
|
return scanRaster(data);
|
|
59
61
|
}
|
|
62
|
+
function readDibBlip(instance, data) {
|
|
63
|
+
const uidBytes = TWO_UID_INSTANCES.has(instance) ? 32 : 16;
|
|
64
|
+
for (const at of [uidBytes + 1, uidBytes]) {
|
|
65
|
+
if (data.length <= at) continue;
|
|
66
|
+
const bmp = dibToBmp(data.subarray(at));
|
|
67
|
+
if (bmp) return bmp;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
60
70
|
function readMetafileBlip(instance, data) {
|
|
61
71
|
const uidBytes = TWO_UID_INSTANCES.has(instance) ? 32 : 16;
|
|
62
72
|
const head = uidBytes + METAFILE_HEADER_BYTES;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ResolvedParagraphProperties } from './style-cascade/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The heading level (1–6) a paragraph's resolved properties describe, or
|
|
4
|
+
* `undefined` when it is body text.
|
|
5
|
+
*
|
|
6
|
+
* @param resolved The paragraph's fully-resolved properties.
|
|
7
|
+
* @returns The heading level 1–6, or `undefined` for body text.
|
|
8
|
+
*/
|
|
9
|
+
export declare function headingLevelOf(resolved: ResolvedParagraphProperties): number | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* The heading level a `"Heading N"` / `"Title"` / `"Subtitle"` style id names,
|
|
12
|
+
* for a heading style that declares no outline level of its own.
|
|
13
|
+
*
|
|
14
|
+
* @param styleId The paragraph's style id, if it has one.
|
|
15
|
+
* @returns The heading level 1–6, or `undefined` when the id names no heading.
|
|
16
|
+
*/
|
|
17
|
+
export declare function headingLevelFromStyleId(styleId: string | undefined): number | undefined;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/core/outline.ts
|
|
2
|
+
/** The deepest heading level the consuming media can express. */
|
|
3
|
+
var MAX_HEADING_LEVEL = 6;
|
|
4
|
+
/**
|
|
5
|
+
* The heading level (1–6) a paragraph's resolved properties describe, or
|
|
6
|
+
* `undefined` when it is body text.
|
|
7
|
+
*
|
|
8
|
+
* @param resolved The paragraph's fully-resolved properties.
|
|
9
|
+
* @returns The heading level 1–6, or `undefined` for body text.
|
|
10
|
+
*/
|
|
11
|
+
function headingLevelOf(resolved) {
|
|
12
|
+
const lvl = resolved.outlineLevel;
|
|
13
|
+
if (lvl !== void 0 && lvl >= 0 && lvl <= 8) return Math.min(lvl + 1, MAX_HEADING_LEVEL);
|
|
14
|
+
return headingLevelFromStyleId(resolved.styleId);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The heading level a `"Heading N"` / `"Title"` / `"Subtitle"` style id names,
|
|
18
|
+
* for a heading style that declares no outline level of its own.
|
|
19
|
+
*
|
|
20
|
+
* @param styleId The paragraph's style id, if it has one.
|
|
21
|
+
* @returns The heading level 1–6, or `undefined` when the id names no heading.
|
|
22
|
+
*/
|
|
23
|
+
function headingLevelFromStyleId(styleId) {
|
|
24
|
+
if (styleId === void 0) return void 0;
|
|
25
|
+
const m = /^Heading\s*([1-9])$/i.exec(styleId);
|
|
26
|
+
if (m) return Math.min(Number(m[1]), MAX_HEADING_LEVEL);
|
|
27
|
+
if (/^(Title|Subtitle)$/i.test(styleId)) return 1;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { headingLevelOf };
|
|
@@ -136,6 +136,7 @@ function mergeRun(base, override) {
|
|
|
136
136
|
rtl: override.rtl ?? base.rtl,
|
|
137
137
|
...lang !== void 0 ? { lang } : {},
|
|
138
138
|
...(override.shadingColorHex ?? base.shadingColorHex) !== void 0 ? { shadingColorHex: override.shadingColorHex ?? base.shadingColorHex } : {},
|
|
139
|
+
...(override.textOutline ?? base.textOutline) !== void 0 ? { textOutline: override.textOutline ?? base.textOutline } : {},
|
|
139
140
|
...(override.letterSpacingPt ?? base.letterSpacingPt) !== void 0 ? { letterSpacingPt: override.letterSpacingPt ?? base.letterSpacingPt } : {}
|
|
140
141
|
};
|
|
141
142
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Alignment, CellBorders, CellShading, FontFamilyMap, FrameProperties, NumberingReference, RunProperties, TabStop, UnderlineStyle, VerticalAlign } from '../document-model/index.js';
|
|
1
|
+
import { Alignment, CellBorders, CellShading, FontFamilyMap, FrameProperties, NumberingReference, RunProperties, TabStop, TextOutline, UnderlineStyle, VerticalAlign } from '../document-model/index.js';
|
|
2
2
|
import { Pt } from '../ir/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* A run's fully-resolved properties: every field required because the cascade
|
|
@@ -30,6 +30,8 @@ export interface ResolvedRunProperties {
|
|
|
30
30
|
readonly shadingColorHex?: string;
|
|
31
31
|
/** §17.3.2.35 — extra space between the run's characters, in points. */
|
|
32
32
|
readonly letterSpacingPt?: Pt;
|
|
33
|
+
/** §21.1.2.3.9 — a line drawn round the glyphs themselves. */
|
|
34
|
+
readonly textOutline?: TextOutline;
|
|
33
35
|
}
|
|
34
36
|
/**
|
|
35
37
|
* A paragraph's fully-resolved properties: every field required, the cascade
|
|
@@ -1122,8 +1122,8 @@ function shadingFromXf(xf, styles) {
|
|
|
1122
1122
|
if (!pattern || pattern === "none") return void 0;
|
|
1123
1123
|
if (pattern === "solid") return fill.fgColorHex ? { colorHex: fill.fgColorHex } : void 0;
|
|
1124
1124
|
const density = PATTERN_DENSITY[pattern];
|
|
1125
|
-
if (density === void 0
|
|
1126
|
-
const blended = blendHex(fill.fgColorHex, fill.bgColorHex ?? "FFFFFF", density);
|
|
1125
|
+
if (density === void 0) return void 0;
|
|
1126
|
+
const blended = blendHex(fill.fgColorHex ?? "000000", fill.bgColorHex ?? "FFFFFF", density);
|
|
1127
1127
|
return blended ? { colorHex: blended } : void 0;
|
|
1128
1128
|
}
|
|
1129
1129
|
function blendHex(fg, bg, fraction) {
|
|
@@ -162,7 +162,7 @@ function makeColWidthPt(ws) {
|
|
|
162
162
|
return (col) => {
|
|
163
163
|
for (const c of ws.columns) if (col >= c.min - 1 && col <= c.max - 1) return widthPt(c.widthChars);
|
|
164
164
|
if (ws.defaultColWidthChars !== void 0) return widthPt(ws.defaultColWidthChars);
|
|
165
|
-
if (ws.baseColWidthChars !== void 0) return (columnTwips(ws.baseColWidthChars, 105) +
|
|
165
|
+
if (ws.baseColWidthChars !== void 0) return (columnTwips(ws.baseColWidthChars, 105) + 150) / TWIPS_PER_PT;
|
|
166
166
|
return 960 / TWIPS_PER_PT;
|
|
167
167
|
};
|
|
168
168
|
}
|
|
@@ -42,6 +42,16 @@ export interface ProjectSheetOptions {
|
|
|
42
42
|
* supplies it; absent, the code is dropped exactly as before.
|
|
43
43
|
*/
|
|
44
44
|
readonly fileName?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Open each printed sheet with a heading carrying its NAME.
|
|
47
|
+
*
|
|
48
|
+
* Off by default, and deliberately: a printed page shows no tab name — Excel
|
|
49
|
+
* and Calc both emit it nowhere — so the paginated targets must not see one.
|
|
50
|
+
* A flowed target has no pages to tell one sheet from the next by, and a
|
|
51
|
+
* workbook rendered without them is a pile of tables with nothing to say
|
|
52
|
+
* which is which; markdown asks for this, and gets `# Sheet1`.
|
|
53
|
+
*/
|
|
54
|
+
readonly sheetHeadings?: boolean;
|
|
45
55
|
}
|
|
46
56
|
/**
|
|
47
57
|
* Project a {@link SheetDoc} into a {@link FlowDoc} (E-SHEET SA2): each grid sheet
|
|
@@ -67,7 +67,20 @@ function projectSheetDoc(sheet, options = {}) {
|
|
|
67
67
|
const sheetSection = withHeaderFooter(sectionFromWorksheet(ws.grid), ws, headersFooters, scaleSink.value, sheet.styles.fonts[0]?.sizePt, printed, options.fileName, sheet.themePalette, options.now);
|
|
68
68
|
if (printed === 0) firstSheetSection = sheetSection;
|
|
69
69
|
sheetSections.push(sheetSection);
|
|
70
|
-
if (
|
|
70
|
+
if (options.sheetHeadings === true) body.push({
|
|
71
|
+
kind: "paragraph",
|
|
72
|
+
paragraph: {
|
|
73
|
+
properties: {
|
|
74
|
+
outlineLevel: 0,
|
|
75
|
+
...printed > 0 ? { pageBreakBefore: true } : {}
|
|
76
|
+
},
|
|
77
|
+
runs: [{
|
|
78
|
+
text: ws.name,
|
|
79
|
+
properties: {}
|
|
80
|
+
}]
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
else if (printed > 0) body.push({
|
|
71
84
|
kind: "paragraph",
|
|
72
85
|
paragraph: PAGE_BREAK_PARAGRAPH
|
|
73
86
|
});
|
|
@@ -401,8 +414,16 @@ function withDrawingsByBand(drawings, gridBody, lefts) {
|
|
|
401
414
|
const byBand = lefts.map(() => []);
|
|
402
415
|
for (const el of drawings) {
|
|
403
416
|
const x = floatLeftPt(el);
|
|
404
|
-
|
|
405
|
-
|
|
417
|
+
if (x === void 0) {
|
|
418
|
+
byBand[0].push(el);
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
const start = bandOf(x);
|
|
422
|
+
const right = x + (floatWidthPt(el) ?? 0);
|
|
423
|
+
for (let band = start; band < lefts.length; band++) {
|
|
424
|
+
if (band > start && right <= lefts[band]) break;
|
|
425
|
+
byBand[band].push(shiftFloatLeft(el, lefts[band]));
|
|
426
|
+
}
|
|
406
427
|
}
|
|
407
428
|
const out = [];
|
|
408
429
|
let seen = 0;
|
|
@@ -416,6 +437,12 @@ function withDrawingsByBand(drawings, gridBody, lefts) {
|
|
|
416
437
|
function floatLeftPt(el) {
|
|
417
438
|
return (el.kind === "chart" ? el.chart.float : el.kind === "image" ? el.image.float : el.kind === "shape" ? el.shape.float : void 0)?.posH?.offsetPt;
|
|
418
439
|
}
|
|
440
|
+
/** A float's own width, for the block kinds a sheet anchors. */
|
|
441
|
+
function floatWidthPt(el) {
|
|
442
|
+
if (el.kind === "chart") return el.chart.width;
|
|
443
|
+
if (el.kind === "image") return el.image.width;
|
|
444
|
+
if (el.kind === "shape") return el.shape.width;
|
|
445
|
+
}
|
|
419
446
|
/** The same block with its horizontal anchor measured from `by` points later. */
|
|
420
447
|
function shiftFloatLeft(el, by) {
|
|
421
448
|
if (by === 0) return el;
|