@json-to-office/core-docx 1.0.0 → 1.3.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/dist/components/visual.d.ts +6 -6
- package/dist/components/visual.d.ts.map +1 -1
- package/dist/core/desugarExternals.d.ts.map +1 -1
- package/dist/core/flattenVisuals.d.ts.map +1 -1
- package/dist/core/imageResources.d.ts.map +1 -1
- package/dist/core/prerasterizeVisuals.d.ts +10 -4
- package/dist/core/prerasterizeVisuals.d.ts.map +1 -1
- package/dist/index.js +1222 -98
- package/dist/index.js.map +1 -1
- package/dist/ir/compiler.d.ts.map +1 -1
- package/dist/ir/features.d.ts +1 -1
- package/dist/ir/features.d.ts.map +1 -1
- package/dist/ir/nativeVisual.d.ts +79 -0
- package/dist/ir/nativeVisual.d.ts.map +1 -0
- package/dist/ir/types.d.ts +155 -1
- package/dist/ir/types.d.ts.map +1 -1
- package/dist/ir/units.d.ts +12 -0
- package/dist/ir/units.d.ts.map +1 -1
- package/dist/ir/validation.d.ts.map +1 -1
- package/dist/plugin/example/index.js +1215 -96
- package/dist/plugin/example/index.js.map +1 -1
- package/dist/renderers/docxjs/emit.d.ts.map +1 -1
- package/dist/renderers/docxjs/index.d.ts.map +1 -1
- package/dist/renderers/office-open/chartParts.d.ts +28 -0
- package/dist/renderers/office-open/chartParts.d.ts.map +1 -0
- package/dist/renderers/office-open/emit.d.ts +47 -6
- package/dist/renderers/office-open/emit.d.ts.map +1 -1
- package/dist/renderers/office-open/index.d.ts +2 -2
- package/dist/renderers/office-open/index.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/chartWorkbook.d.ts +19 -0
- package/dist/utils/chartWorkbook.d.ts.map +1 -0
- package/dist/utils/packageDocument.d.ts +2 -0
- package/dist/utils/packageDocument.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -2005,6 +2005,12 @@ function pointsToEighthPoints(points) {
|
|
|
2005
2005
|
function inchesToTwips(inches) {
|
|
2006
2006
|
return Math.round(inches * TWIPS_PER_INCH);
|
|
2007
2007
|
}
|
|
2008
|
+
function inchesToEmu(inches) {
|
|
2009
|
+
return Math.round(inches * EMU_PER_INCH);
|
|
2010
|
+
}
|
|
2011
|
+
function pointsToEmu(points) {
|
|
2012
|
+
return Math.round(points * EMU_PER_INCH / 72);
|
|
2013
|
+
}
|
|
2008
2014
|
function twipsToEmu(twips) {
|
|
2009
2015
|
return Math.round(twips * EMU_PER_TWIP);
|
|
2010
2016
|
}
|
|
@@ -2207,6 +2213,14 @@ function inlineChildren(children, resources = /* @__PURE__ */ new Map()) {
|
|
|
2207
2213
|
case "shape":
|
|
2208
2214
|
out.push(emitShape(child, resources));
|
|
2209
2215
|
break;
|
|
2216
|
+
case "drawingGroup":
|
|
2217
|
+
throw new Error(
|
|
2218
|
+
"the docxjs renderer has no emitter for a drawing group; this document should have been refused by the capability check"
|
|
2219
|
+
);
|
|
2220
|
+
case "chart":
|
|
2221
|
+
throw new Error(
|
|
2222
|
+
"the docxjs renderer has no emitter for a chart; this document should have been refused by the capability check"
|
|
2223
|
+
);
|
|
2210
2224
|
case "noteReference":
|
|
2211
2225
|
out.push(
|
|
2212
2226
|
child.noteKind === "endnote" ? new EndnoteReferenceRun(child.id) : new FootnoteReferenceRun(child.id)
|
|
@@ -2308,7 +2322,7 @@ function paragraphOptions(formatting) {
|
|
|
2308
2322
|
const options = {};
|
|
2309
2323
|
if (!formatting) return options;
|
|
2310
2324
|
if (formatting.alignment) {
|
|
2311
|
-
options.alignment =
|
|
2325
|
+
options.alignment = ALIGNMENT2[formatting.alignment];
|
|
2312
2326
|
}
|
|
2313
2327
|
if (formatting.spacing) {
|
|
2314
2328
|
const spacing = {};
|
|
@@ -2542,11 +2556,11 @@ function emitBorder(border3) {
|
|
|
2542
2556
|
color: border3.color?.hex ?? "000000"
|
|
2543
2557
|
};
|
|
2544
2558
|
}
|
|
2545
|
-
var
|
|
2559
|
+
var ALIGNMENT2, PAGE_FIELD, WRAP_TYPE, VERTICAL_ALIGN, TABLE_ANCHOR, HORIZONTAL_POSITION, VERTICAL_POSITION, BORDER_STYLE;
|
|
2546
2560
|
var init_emit = __esm({
|
|
2547
2561
|
"src/renderers/docxjs/emit.ts"() {
|
|
2548
2562
|
"use strict";
|
|
2549
|
-
|
|
2563
|
+
ALIGNMENT2 = {
|
|
2550
2564
|
left: AlignmentType.LEFT,
|
|
2551
2565
|
center: AlignmentType.CENTER,
|
|
2552
2566
|
right: AlignmentType.RIGHT,
|
|
@@ -2669,7 +2683,7 @@ function paragraphProperties(formatting) {
|
|
|
2669
2683
|
out.spacing = spacing;
|
|
2670
2684
|
}
|
|
2671
2685
|
if (formatting.alignment !== void 0) {
|
|
2672
|
-
out.alignment =
|
|
2686
|
+
out.alignment = ALIGNMENT2[formatting.alignment] ?? formatting.alignment;
|
|
2673
2687
|
}
|
|
2674
2688
|
if (formatting.keepNext !== void 0) out.keepNext = formatting.keepNext;
|
|
2675
2689
|
if (formatting.keepLines !== void 0) out.keepLines = formatting.keepLines;
|
|
@@ -2798,6 +2812,26 @@ var init_features = __esm({
|
|
|
2798
2812
|
"text-frames",
|
|
2799
2813
|
/** Native shape text boxes. */
|
|
2800
2814
|
"text-boxes",
|
|
2815
|
+
/**
|
|
2816
|
+
* A DrawingML group: shapes, text boxes and pictures sharing one child
|
|
2817
|
+
* coordinate space, drawn as a single anchored or inline object.
|
|
2818
|
+
*
|
|
2819
|
+
* Separate from `text-boxes` because a lone `wps:wsp` run is a far smaller
|
|
2820
|
+
* ask than `wpg:wgp` with child transforms, preset geometry and grouped
|
|
2821
|
+
* pictures — a backend can plausibly have the first and not the second.
|
|
2822
|
+
*/
|
|
2823
|
+
"drawing-groups",
|
|
2824
|
+
/**
|
|
2825
|
+
* A native chart part: a `c:chartSpace` with its own embedded workbook.
|
|
2826
|
+
*
|
|
2827
|
+
* Separate from `images` because a chart is not a picture with extra data —
|
|
2828
|
+
* it is its own part, its own relationship and its own workbook, and a
|
|
2829
|
+
* backend either writes all three or writes none. docx.js has no chart
|
|
2830
|
+
* primitive at all, which is what turns a `chart` component sent to that
|
|
2831
|
+
* backend into a named capability error rather than a document missing a
|
|
2832
|
+
* figure.
|
|
2833
|
+
*/
|
|
2834
|
+
"charts",
|
|
2801
2835
|
/** A table-of-contents field. */
|
|
2802
2836
|
"toc",
|
|
2803
2837
|
/** TOC entries baked in so an unrefreshed reader still shows them. */
|
|
@@ -3044,7 +3078,7 @@ function numberingConfig(numbering) {
|
|
|
3044
3078
|
level: level.level,
|
|
3045
3079
|
format: level.format,
|
|
3046
3080
|
text: level.text,
|
|
3047
|
-
alignment: level.alignment ?
|
|
3081
|
+
alignment: level.alignment ? ALIGNMENT2[level.alignment] : void 0,
|
|
3048
3082
|
...level.suffix ? { suffix: level.suffix } : {},
|
|
3049
3083
|
style: {
|
|
3050
3084
|
...level.indent ? {
|
|
@@ -3262,7 +3296,16 @@ var init_docxjs = __esm({
|
|
|
3262
3296
|
"cached-fields",
|
|
3263
3297
|
"shading",
|
|
3264
3298
|
"borders",
|
|
3265
|
-
"rtl"
|
|
3299
|
+
"rtl",
|
|
3300
|
+
// `drawing-groups` is the one exclusion that is a real backend gap rather
|
|
3301
|
+
// than a slice boundary: docx.js has no `wpg:wgp`. Leaving it out is what
|
|
3302
|
+
// turns a native `visual` sent to this backend into a named capability
|
|
3303
|
+
// error instead of a document with the graphic missing.
|
|
3304
|
+
"drawing-groups",
|
|
3305
|
+
// `charts` is the second real backend gap: docx.js has no chart primitive at
|
|
3306
|
+
// all, so a `chart` component sent here becomes a named capability error
|
|
3307
|
+
// rather than a document missing a figure.
|
|
3308
|
+
"charts"
|
|
3266
3309
|
]);
|
|
3267
3310
|
DOCXJS_CAPABILITIES = new Set(
|
|
3268
3311
|
[...ALL_DOCX_FEATURES].filter((feature) => !NOT_YET_EMITTED.has(feature))
|
|
@@ -3270,11 +3313,105 @@ var init_docxjs = __esm({
|
|
|
3270
3313
|
}
|
|
3271
3314
|
});
|
|
3272
3315
|
|
|
3316
|
+
// src/utils/chartWorkbook.ts
|
|
3317
|
+
import AdmZip3 from "adm-zip";
|
|
3318
|
+
import { chartWorkbookParts } from "@json-to-office/shared/rendering";
|
|
3319
|
+
import {
|
|
3320
|
+
CHART_WORKBOOK_SHEET_NAME,
|
|
3321
|
+
categoryReference,
|
|
3322
|
+
columnLetter,
|
|
3323
|
+
seriesNameReference,
|
|
3324
|
+
seriesValueReference
|
|
3325
|
+
} from "@json-to-office/shared/rendering";
|
|
3326
|
+
function buildChartWorkbook(series) {
|
|
3327
|
+
const zip = new AdmZip3();
|
|
3328
|
+
for (const [name, content] of chartWorkbookParts(series)) {
|
|
3329
|
+
zip.addFile(name, Buffer.from(content, "utf8"));
|
|
3330
|
+
}
|
|
3331
|
+
for (const entry of zip.getEntries()) {
|
|
3332
|
+
entry.header.timeval = WORKBOOK_TIMESTAMP;
|
|
3333
|
+
}
|
|
3334
|
+
return new Uint8Array(zip.toBuffer());
|
|
3335
|
+
}
|
|
3336
|
+
var WORKBOOK_TIMESTAMP;
|
|
3337
|
+
var init_chartWorkbook = __esm({
|
|
3338
|
+
"src/utils/chartWorkbook.ts"() {
|
|
3339
|
+
"use strict";
|
|
3340
|
+
init_packageDocument();
|
|
3341
|
+
WORKBOOK_TIMESTAMP = toDosTime(DEFAULT_GENERATION_DATE);
|
|
3342
|
+
}
|
|
3343
|
+
});
|
|
3344
|
+
|
|
3345
|
+
// src/renderers/office-open/chartParts.ts
|
|
3346
|
+
import {
|
|
3347
|
+
CHART_WORKBOOK_CONTENT_TYPE,
|
|
3348
|
+
chartWorkbookRelsXml,
|
|
3349
|
+
matchChartParts,
|
|
3350
|
+
spliceChartXml
|
|
3351
|
+
} from "@json-to-office/shared/rendering";
|
|
3352
|
+
function spliceInput(chart) {
|
|
3353
|
+
return {
|
|
3354
|
+
chartType: chart.chartType,
|
|
3355
|
+
series: chart.series,
|
|
3356
|
+
colors: chart.colors,
|
|
3357
|
+
...chart.legendPosition ? { legendPosition: chart.legendPosition } : {},
|
|
3358
|
+
...chart.categoryAxisTitle ? { categoryAxis: { title: chart.categoryAxisTitle } } : {},
|
|
3359
|
+
...chart.valueAxisTitle ? { valueAxis: { title: chart.valueAxisTitle } } : {}
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
3362
|
+
function declareWorkbookContentType(zip) {
|
|
3363
|
+
const entry = zip.getEntry("[Content_Types].xml");
|
|
3364
|
+
if (!entry) return;
|
|
3365
|
+
const xml = entry.getData().toString("utf8");
|
|
3366
|
+
if (xml.includes(`Extension="xlsx"`)) return;
|
|
3367
|
+
zip.updateFile(
|
|
3368
|
+
entry,
|
|
3369
|
+
Buffer.from(
|
|
3370
|
+
xml.replace(
|
|
3371
|
+
'<Default Extension="xml"',
|
|
3372
|
+
`<Default Extension="xlsx" ContentType="${CHART_WORKBOOK_CONTENT_TYPE}"/><Default Extension="xml"`
|
|
3373
|
+
),
|
|
3374
|
+
"utf8"
|
|
3375
|
+
)
|
|
3376
|
+
);
|
|
3377
|
+
}
|
|
3378
|
+
function spliceChartParts(zip, charts) {
|
|
3379
|
+
if (charts.length === 0) return;
|
|
3380
|
+
const parts = zip.getEntries().map((entry) => entry.entryName).map((name) => name.match(/^word\/charts\/chart(\d+)\.xml$/)).filter((match) => match !== null).map(
|
|
3381
|
+
(match) => [
|
|
3382
|
+
Number(match[1]),
|
|
3383
|
+
zip.getEntry(match[0]).getData().toString("utf8")
|
|
3384
|
+
]
|
|
3385
|
+
);
|
|
3386
|
+
for (const { ordinal, xml, chart } of matchChartParts(parts, charts)) {
|
|
3387
|
+
const workbookName = `chart${ordinal}.xlsx`;
|
|
3388
|
+
zip.updateFile(
|
|
3389
|
+
zip.getEntry(`word/charts/chart${ordinal}.xml`),
|
|
3390
|
+
Buffer.from(spliceChartXml(xml, spliceInput(chart)), "utf8")
|
|
3391
|
+
);
|
|
3392
|
+
zip.addFile(
|
|
3393
|
+
`word/embeddings/${workbookName}`,
|
|
3394
|
+
Buffer.from(buildChartWorkbook(chart.series))
|
|
3395
|
+
);
|
|
3396
|
+
zip.addFile(
|
|
3397
|
+
`word/charts/_rels/chart${ordinal}.xml.rels`,
|
|
3398
|
+
Buffer.from(chartWorkbookRelsXml(workbookName), "utf8")
|
|
3399
|
+
);
|
|
3400
|
+
}
|
|
3401
|
+
declareWorkbookContentType(zip);
|
|
3402
|
+
}
|
|
3403
|
+
var init_chartParts = __esm({
|
|
3404
|
+
"src/renderers/office-open/chartParts.ts"() {
|
|
3405
|
+
"use strict";
|
|
3406
|
+
init_chartWorkbook();
|
|
3407
|
+
}
|
|
3408
|
+
});
|
|
3409
|
+
|
|
3273
3410
|
// src/renderers/office-open/emit.ts
|
|
3274
3411
|
import { assertNever as assertNever2 } from "@json-to-office/shared/rendering";
|
|
3275
3412
|
function emptyContext() {
|
|
3276
3413
|
let next = 1;
|
|
3277
|
-
return { pictures: /* @__PURE__ */ new Map(), nextDrawingId: () => next
|
|
3414
|
+
return { pictures: /* @__PURE__ */ new Map(), nextDrawingId: () => next++, charts: [] };
|
|
3278
3415
|
}
|
|
3279
3416
|
function simpleField(instruction, cachedText) {
|
|
3280
3417
|
return {
|
|
@@ -3373,15 +3510,15 @@ function inlineChildren2(children, ctx = emptyContext()) {
|
|
|
3373
3510
|
out.push({ bookmarkEnd: { id: child.id } });
|
|
3374
3511
|
break;
|
|
3375
3512
|
case "image": {
|
|
3376
|
-
const build = ctx.pictures.get(child.resourceId);
|
|
3377
|
-
if (!build) {
|
|
3378
|
-
throw new Error(
|
|
3379
|
-
`no image was prepared for resource "${child.resourceId}"`
|
|
3380
|
-
);
|
|
3381
|
-
}
|
|
3382
3513
|
const pending = breakOption();
|
|
3383
3514
|
if (pending.break) out.push(pending);
|
|
3384
|
-
out.push({ picture:
|
|
3515
|
+
out.push({ picture: pictureOptions(child, ctx) });
|
|
3516
|
+
break;
|
|
3517
|
+
}
|
|
3518
|
+
case "drawingGroup": {
|
|
3519
|
+
const pending = breakOption();
|
|
3520
|
+
if (pending.break) out.push(pending);
|
|
3521
|
+
out.push({ wpgGroup: drawingGroupOptions(child, ctx) });
|
|
3385
3522
|
break;
|
|
3386
3523
|
}
|
|
3387
3524
|
case "hyperlink":
|
|
@@ -3410,6 +3547,10 @@ function inlineChildren2(children, ctx = emptyContext()) {
|
|
|
3410
3547
|
case "shape":
|
|
3411
3548
|
out.push({ wpsShape: shapeOptions(child, ctx) });
|
|
3412
3549
|
break;
|
|
3550
|
+
case "chart":
|
|
3551
|
+
ctx.charts?.push(child);
|
|
3552
|
+
out.push({ chart: chartOptions(child, ctx) });
|
|
3553
|
+
break;
|
|
3413
3554
|
case "noteReference":
|
|
3414
3555
|
out.push(
|
|
3415
3556
|
child.noteKind === "endnote" ? { endnoteReference: child.id } : { footnoteReference: child.id }
|
|
@@ -3476,6 +3617,185 @@ function floatingOptions2(floating) {
|
|
|
3476
3617
|
zIndex: floating.zIndex
|
|
3477
3618
|
};
|
|
3478
3619
|
}
|
|
3620
|
+
function imageMedia(ctx, resourceId, placement) {
|
|
3621
|
+
const build = ctx.pictures.get(resourceId);
|
|
3622
|
+
if (!build) {
|
|
3623
|
+
throw new Error(`no image was prepared for resource "${resourceId}"`);
|
|
3624
|
+
}
|
|
3625
|
+
return build(placement);
|
|
3626
|
+
}
|
|
3627
|
+
function pictureOptions(image, ctx) {
|
|
3628
|
+
const media = imageMedia(ctx, image.resourceId, image);
|
|
3629
|
+
return {
|
|
3630
|
+
type: media.type,
|
|
3631
|
+
data: media.data,
|
|
3632
|
+
// No `fileName`: at run level the backend allocates one, and stating our
|
|
3633
|
+
// own would only fight it.
|
|
3634
|
+
...media.fallback ? { fallback: media.fallback } : {},
|
|
3635
|
+
// Raw numbers are EMUs in @office-open/docx. Passing pixels here makes a
|
|
3636
|
+
// normal image only a few hundred EMUs wide, effectively a dot.
|
|
3637
|
+
transformation: { width: image.widthEmu, height: image.heightEmu },
|
|
3638
|
+
// The id is stated rather than left to the backend's process-global
|
|
3639
|
+
// counter. `name` stays empty, which is what it was before and what the
|
|
3640
|
+
// backend falls back to.
|
|
3641
|
+
altText: { id: String(ctx.nextDrawingId()) },
|
|
3642
|
+
...image.floating ? { floating: floatingOptions2(image.floating) } : {}
|
|
3643
|
+
// No `description` or `title`: no DOCX this pipeline has produced carries
|
|
3644
|
+
// `wp:docPr` alt text, and the compiler warns so the gap is visible rather
|
|
3645
|
+
// than silent.
|
|
3646
|
+
};
|
|
3647
|
+
}
|
|
3648
|
+
function drawingGroupOptions(group, ctx) {
|
|
3649
|
+
const id = ctx.nextDrawingId();
|
|
3650
|
+
return {
|
|
3651
|
+
altText: {
|
|
3652
|
+
id: String(id),
|
|
3653
|
+
...group.altText ? { description: group.altText } : {}
|
|
3654
|
+
},
|
|
3655
|
+
transformation: { width: group.widthEmu, height: group.heightEmu },
|
|
3656
|
+
childOffset: { x: 0, y: 0 },
|
|
3657
|
+
childExtent: { cx: group.canvasWidthEmu, cy: group.canvasHeightEmu },
|
|
3658
|
+
children: group.children.map((child) => groupChild(child, ctx)),
|
|
3659
|
+
...group.floating ? { floating: floatingOptions2(group.floating) } : {}
|
|
3660
|
+
};
|
|
3661
|
+
}
|
|
3662
|
+
function chartOptions(chart, ctx) {
|
|
3663
|
+
const id = ctx.nextDrawingId();
|
|
3664
|
+
return {
|
|
3665
|
+
type: chart.chartType,
|
|
3666
|
+
categories: chart.series[0]?.labels ?? [],
|
|
3667
|
+
series: chart.series.map((entry, index) => ({
|
|
3668
|
+
name: entry.name ?? `Series ${index + 1}`,
|
|
3669
|
+
values: entry.values
|
|
3670
|
+
})),
|
|
3671
|
+
...chart.title && chart.showTitle !== false ? { title: chart.title } : {},
|
|
3672
|
+
...chart.showLegend !== void 0 ? { showLegend: chart.showLegend } : {},
|
|
3673
|
+
transformation: { width: chart.widthEmu, height: chart.heightEmu },
|
|
3674
|
+
altText: {
|
|
3675
|
+
id: String(id),
|
|
3676
|
+
...chart.altText ? { description: chart.altText } : {}
|
|
3677
|
+
},
|
|
3678
|
+
...chart.floating ? { floating: floatingOptions2(chart.floating) } : {}
|
|
3679
|
+
};
|
|
3680
|
+
}
|
|
3681
|
+
function groupChild(child, ctx) {
|
|
3682
|
+
return child.kind === "shape" ? groupShape(child, ctx) : groupPicture(child, ctx);
|
|
3683
|
+
}
|
|
3684
|
+
function groupShape(shape, ctx) {
|
|
3685
|
+
const id = ctx.nextDrawingId();
|
|
3686
|
+
return {
|
|
3687
|
+
type: "wps",
|
|
3688
|
+
transformation: childTransformation(shape.frame),
|
|
3689
|
+
data: {
|
|
3690
|
+
nonVisualProperties: {
|
|
3691
|
+
id,
|
|
3692
|
+
...shape.name ? { name: shape.name } : {},
|
|
3693
|
+
// `txBox="1"` is how Word tells a text box from a shape that happens
|
|
3694
|
+
// to hold text, and it changes how the object behaves on selection.
|
|
3695
|
+
...shape.isTextBox ? { textBox: "1" } : {}
|
|
3696
|
+
},
|
|
3697
|
+
presetGeometry: { preset: shape.geometry },
|
|
3698
|
+
...shape.fill ? { fill: drawingFill(shape.fill) } : {},
|
|
3699
|
+
...shape.outline ? { outline: drawingOutline(shape.outline) } : {},
|
|
3700
|
+
children: (shape.text?.paragraphs ?? []).map(
|
|
3701
|
+
(child) => paragraph(child, ctx)
|
|
3702
|
+
),
|
|
3703
|
+
...shape.text ? { bodyProperties: bodyProperties(shape.text) } : {}
|
|
3704
|
+
}
|
|
3705
|
+
};
|
|
3706
|
+
}
|
|
3707
|
+
function groupPicture(picture2, ctx) {
|
|
3708
|
+
const id = ctx.nextDrawingId();
|
|
3709
|
+
const media = imageMedia(ctx, picture2.resourceId, {
|
|
3710
|
+
widthEmu: picture2.frame.widthEmu,
|
|
3711
|
+
heightEmu: picture2.frame.heightEmu
|
|
3712
|
+
});
|
|
3713
|
+
return {
|
|
3714
|
+
type: media.type,
|
|
3715
|
+
data: media.data,
|
|
3716
|
+
fileName: media.fileName,
|
|
3717
|
+
...media.fallback ? {
|
|
3718
|
+
fallback: {
|
|
3719
|
+
...media.fallback,
|
|
3720
|
+
fileName: media.fallbackFileName
|
|
3721
|
+
}
|
|
3722
|
+
} : {},
|
|
3723
|
+
transformation: childTransformation(picture2.frame),
|
|
3724
|
+
nonVisualProperties: {
|
|
3725
|
+
id,
|
|
3726
|
+
...picture2.name ? { name: picture2.name } : {},
|
|
3727
|
+
...picture2.altText ? { description: picture2.altText } : {}
|
|
3728
|
+
},
|
|
3729
|
+
...picture2.crop ? { sourceRectangle: sourceRectangle(picture2.crop) } : {}
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
function childTransformation(frame) {
|
|
3733
|
+
const flip = {
|
|
3734
|
+
...frame.flipHorizontal ? { horizontal: true } : {},
|
|
3735
|
+
...frame.flipVertical ? { vertical: true } : {}
|
|
3736
|
+
};
|
|
3737
|
+
return {
|
|
3738
|
+
offset: {
|
|
3739
|
+
emus: { x: frame.xEmu, y: frame.yEmu },
|
|
3740
|
+
pixels: {
|
|
3741
|
+
x: Math.round(emuToPixels(frame.xEmu)),
|
|
3742
|
+
y: Math.round(emuToPixels(frame.yEmu))
|
|
3743
|
+
}
|
|
3744
|
+
},
|
|
3745
|
+
emus: { x: frame.widthEmu, y: frame.heightEmu },
|
|
3746
|
+
pixels: {
|
|
3747
|
+
x: Math.round(emuToPixels(frame.widthEmu)),
|
|
3748
|
+
y: Math.round(emuToPixels(frame.heightEmu))
|
|
3749
|
+
},
|
|
3750
|
+
// Degrees: the backend multiplies by 60000 on the way into `@rot`.
|
|
3751
|
+
...frame.rotationDegrees !== void 0 ? { rotation: frame.rotationDegrees } : {},
|
|
3752
|
+
...Object.keys(flip).length > 0 ? { flip } : {}
|
|
3753
|
+
};
|
|
3754
|
+
}
|
|
3755
|
+
function drawingFill(fill) {
|
|
3756
|
+
if (fill.kind === "none") return { type: "none" };
|
|
3757
|
+
return {
|
|
3758
|
+
type: "solid",
|
|
3759
|
+
color: {
|
|
3760
|
+
type: "rgb",
|
|
3761
|
+
value: fill.color.hex,
|
|
3762
|
+
// Transparency becomes an alpha transform on the colour. DrawingML
|
|
3763
|
+
// states *opacity*, so the value is the complement of what the author
|
|
3764
|
+
// wrote; the backend scales the percentage into `a:alpha`'s thousandths
|
|
3765
|
+
// itself, so it must be handed a plain percentage here.
|
|
3766
|
+
...fill.transparencyPercent !== void 0 ? { transforms: { alpha: 100 - fill.transparencyPercent } } : {}
|
|
3767
|
+
}
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
3770
|
+
function drawingOutline(outline) {
|
|
3771
|
+
return {
|
|
3772
|
+
...outline.widthEmu !== void 0 ? { width: outline.widthEmu } : {},
|
|
3773
|
+
...outline.dash ? { dash: outline.dash } : {},
|
|
3774
|
+
...outline.color ? { type: "solidFill", color: { value: outline.color.hex } } : {}
|
|
3775
|
+
};
|
|
3776
|
+
}
|
|
3777
|
+
function bodyProperties(text) {
|
|
3778
|
+
const insets = text.insetsEmu;
|
|
3779
|
+
return {
|
|
3780
|
+
...text.anchor ? { anchor: BODY_ANCHOR[text.anchor] } : {},
|
|
3781
|
+
// `lIns`/`tIns`/`rIns`/`bIns` is the vocabulary `a:bodyPr` actually has;
|
|
3782
|
+
// the backend also accepts a `margins` object and folds it into the same
|
|
3783
|
+
// attributes.
|
|
3784
|
+
...insets?.left !== void 0 ? { lIns: insets.left } : {},
|
|
3785
|
+
...insets?.top !== void 0 ? { tIns: insets.top } : {},
|
|
3786
|
+
...insets?.right !== void 0 ? { rIns: insets.right } : {},
|
|
3787
|
+
...insets?.bottom !== void 0 ? { bIns: insets.bottom } : {}
|
|
3788
|
+
};
|
|
3789
|
+
}
|
|
3790
|
+
function sourceRectangle(crop) {
|
|
3791
|
+
const thousandths = (fraction) => Math.round(fraction * 1e5);
|
|
3792
|
+
return {
|
|
3793
|
+
...crop.left !== void 0 ? { left: thousandths(crop.left) } : {},
|
|
3794
|
+
...crop.top !== void 0 ? { top: thousandths(crop.top) } : {},
|
|
3795
|
+
...crop.right !== void 0 ? { right: thousandths(crop.right) } : {},
|
|
3796
|
+
...crop.bottom !== void 0 ? { bottom: thousandths(crop.bottom) } : {}
|
|
3797
|
+
};
|
|
3798
|
+
}
|
|
3479
3799
|
function shapeOptions(shape, ctx) {
|
|
3480
3800
|
const id = String(ctx.nextDrawingId());
|
|
3481
3801
|
return {
|
|
@@ -3493,23 +3813,8 @@ function shapeOptions(shape, ctx) {
|
|
|
3493
3813
|
color: { type: "rgb", value: shape.fill.hex }
|
|
3494
3814
|
}
|
|
3495
3815
|
} : {},
|
|
3496
|
-
...shape.outline ? {
|
|
3497
|
-
|
|
3498
|
-
...shape.outline.widthEmu !== void 0 ? { width: shape.outline.widthEmu } : {},
|
|
3499
|
-
fill: {
|
|
3500
|
-
type: "solid",
|
|
3501
|
-
color: { type: "rgb", value: shape.outline.color.hex }
|
|
3502
|
-
}
|
|
3503
|
-
}
|
|
3504
|
-
} : {},
|
|
3505
|
-
...shape.insetsEmu ? {
|
|
3506
|
-
bodyProperties: {
|
|
3507
|
-
...shape.insetsEmu.top !== void 0 ? { topInset: shape.insetsEmu.top } : {},
|
|
3508
|
-
...shape.insetsEmu.bottom !== void 0 ? { bottomInset: shape.insetsEmu.bottom } : {},
|
|
3509
|
-
...shape.insetsEmu.left !== void 0 ? { leftInset: shape.insetsEmu.left } : {},
|
|
3510
|
-
...shape.insetsEmu.right !== void 0 ? { rightInset: shape.insetsEmu.right } : {}
|
|
3511
|
-
}
|
|
3512
|
-
} : {},
|
|
3816
|
+
...shape.outline ? { outline: drawingOutline(shape.outline) } : {},
|
|
3817
|
+
...shape.insetsEmu ? { bodyProperties: bodyProperties({ insetsEmu: shape.insetsEmu }) } : {},
|
|
3513
3818
|
...shape.floating ? { floating: floatingOptions2(shape.floating) } : {}
|
|
3514
3819
|
};
|
|
3515
3820
|
}
|
|
@@ -3866,7 +4171,7 @@ function numberingConfig2(numbering) {
|
|
|
3866
4171
|
}))
|
|
3867
4172
|
};
|
|
3868
4173
|
}
|
|
3869
|
-
var WRAP_TYPE2, TOC_PAGE_TAB_TWIPS;
|
|
4174
|
+
var WRAP_TYPE2, BODY_ANCHOR, TOC_PAGE_TAB_TWIPS;
|
|
3870
4175
|
var init_emit2 = __esm({
|
|
3871
4176
|
"src/renderers/office-open/emit.ts"() {
|
|
3872
4177
|
"use strict";
|
|
@@ -3877,6 +4182,11 @@ var init_emit2 = __esm({
|
|
|
3877
4182
|
tight: 2,
|
|
3878
4183
|
topAndBottom: 3
|
|
3879
4184
|
};
|
|
4185
|
+
BODY_ANCHOR = {
|
|
4186
|
+
top: "t",
|
|
4187
|
+
middle: "ctr",
|
|
4188
|
+
bottom: "b"
|
|
4189
|
+
};
|
|
3880
4190
|
TOC_PAGE_TAB_TWIPS = 9025;
|
|
3881
4191
|
}
|
|
3882
4192
|
});
|
|
@@ -3940,6 +4250,7 @@ __export(office_open_exports, {
|
|
|
3940
4250
|
buildDocumentOptions: () => buildDocumentOptions,
|
|
3941
4251
|
createOfficeOpenDocxRenderer: () => createOfficeOpenDocxRenderer
|
|
3942
4252
|
});
|
|
4253
|
+
import AdmZip4 from "adm-zip";
|
|
3943
4254
|
async function createOfficeOpenDocxRenderer() {
|
|
3944
4255
|
const backend = await import(
|
|
3945
4256
|
/* @vite-ignore */
|
|
@@ -3955,13 +4266,19 @@ async function createOfficeOpenDocxRenderer() {
|
|
|
3955
4266
|
format: "docx",
|
|
3956
4267
|
capabilities: OFFICE_OPEN_CAPABILITIES,
|
|
3957
4268
|
async render(ir, options) {
|
|
3958
|
-
const
|
|
4269
|
+
const charts = [];
|
|
4270
|
+
const document = await buildDocumentOptions(ir, charts);
|
|
3959
4271
|
const bytes = await backend.generateDocument(document, {
|
|
3960
4272
|
type: "uint8array"
|
|
3961
4273
|
});
|
|
3962
|
-
|
|
4274
|
+
let raw = Buffer.from(
|
|
3963
4275
|
bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)
|
|
3964
4276
|
);
|
|
4277
|
+
if (charts.length > 0) {
|
|
4278
|
+
const zip = new AdmZip4(raw);
|
|
4279
|
+
spliceChartParts(zip, charts);
|
|
4280
|
+
raw = zip.toBuffer();
|
|
4281
|
+
}
|
|
3965
4282
|
if (options?.deterministic === false) return new Uint8Array(raw);
|
|
3966
4283
|
return new Uint8Array(
|
|
3967
4284
|
canonicalizeDocxBuffer(
|
|
@@ -3975,11 +4292,12 @@ async function createOfficeOpenDocxRenderer() {
|
|
|
3975
4292
|
}
|
|
3976
4293
|
};
|
|
3977
4294
|
}
|
|
3978
|
-
async function buildDocumentOptions(ir) {
|
|
4295
|
+
async function buildDocumentOptions(ir, charts = []) {
|
|
3979
4296
|
let nextDrawingId = 1;
|
|
3980
4297
|
const ctx = {
|
|
3981
4298
|
pictures: await prepareImages2(ir),
|
|
3982
|
-
nextDrawingId: () => nextDrawingId
|
|
4299
|
+
nextDrawingId: () => nextDrawingId++,
|
|
4300
|
+
charts
|
|
3983
4301
|
};
|
|
3984
4302
|
return {
|
|
3985
4303
|
styles: emitStyles2(ir.styles),
|
|
@@ -4054,18 +4372,9 @@ async function prepareImages2(ir) {
|
|
|
4054
4372
|
index === 0 ? data : distinguishImageBytes(data, type, size)
|
|
4055
4373
|
);
|
|
4056
4374
|
});
|
|
4057
|
-
resources.set(resource.id, (
|
|
4058
|
-
const
|
|
4059
|
-
|
|
4060
|
-
height: emuToPixels(image.heightEmu)
|
|
4061
|
-
};
|
|
4062
|
-
const sizeKey = `${rasterSize.width}x${rasterSize.height}`;
|
|
4063
|
-
const transformation = {
|
|
4064
|
-
// Raw numbers are EMUs in @office-open/docx. Passing pixels here makes
|
|
4065
|
-
// a normal image only a few hundred EMUs wide, effectively a dot.
|
|
4066
|
-
width: image.widthEmu,
|
|
4067
|
-
height: image.heightEmu
|
|
4068
|
-
};
|
|
4375
|
+
resources.set(resource.id, (placement) => {
|
|
4376
|
+
const sizeKey = placementKey(placement);
|
|
4377
|
+
const stem = `${resource.id}-${sizeKey}`;
|
|
4069
4378
|
return {
|
|
4070
4379
|
type,
|
|
4071
4380
|
// @office-open/docx stores a drawing transformation on its deduplicated
|
|
@@ -4073,29 +4382,28 @@ async function prepareImages2(ir) {
|
|
|
4073
4382
|
// reuse the first size, so each distinct placement size gets equivalent
|
|
4074
4383
|
// image bytes carrying a harmless format-native marker.
|
|
4075
4384
|
data: placementData.get(sizeKey) ?? data,
|
|
4076
|
-
|
|
4077
|
-
//
|
|
4078
|
-
//
|
|
4079
|
-
|
|
4080
|
-
altText: { id: String(drawingId) },
|
|
4385
|
+
// Named after the resource and the size it is drawn at, so two
|
|
4386
|
+
// placements of one image at one size share a single part and a third
|
|
4387
|
+
// at another size gets its own.
|
|
4388
|
+
fileName: `${stem}.${type}`,
|
|
4081
4389
|
...type === "svg" ? {
|
|
4082
4390
|
// Word before 2016 draws the fallback rather than the vector. A
|
|
4083
4391
|
// raster that could not be produced falls back to the SVG bytes,
|
|
4084
4392
|
// which is what this pipeline has always shipped.
|
|
4085
4393
|
fallback: {
|
|
4086
4394
|
type: "png",
|
|
4087
|
-
data: rasters.get(`${
|
|
4088
|
-
}
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
// No `description` or `title`: no DOCX this pipeline has produced
|
|
4092
|
-
// carries `wp:docPr` alt text, and the compiler warns so the gap is
|
|
4093
|
-
// visible rather than silent.
|
|
4395
|
+
data: rasters.get(`${resource.id}:${sizeKey}`) ?? data
|
|
4396
|
+
},
|
|
4397
|
+
fallbackFileName: `${stem}-fallback.png`
|
|
4398
|
+
} : {}
|
|
4094
4399
|
};
|
|
4095
4400
|
});
|
|
4096
4401
|
}
|
|
4097
4402
|
return resources;
|
|
4098
4403
|
}
|
|
4404
|
+
function placementKey(placement) {
|
|
4405
|
+
return `${emuToPixels(placement.widthEmu)}x${emuToPixels(placement.heightEmu)}`;
|
|
4406
|
+
}
|
|
4099
4407
|
function distinguishImageBytes(data, mediaType, placement) {
|
|
4100
4408
|
const marker = Buffer.from(`json-to-office:${placement}`, "utf8");
|
|
4101
4409
|
switch (mediaType) {
|
|
@@ -4194,12 +4502,27 @@ function crc32(data) {
|
|
|
4194
4502
|
}
|
|
4195
4503
|
function collectImagePlacements2(ir) {
|
|
4196
4504
|
const placements = /* @__PURE__ */ new Map();
|
|
4505
|
+
const record = (resourceId, placement) => {
|
|
4506
|
+
const sizes = placements.get(resourceId) ?? /* @__PURE__ */ new Set();
|
|
4507
|
+
sizes.add(placementKey(placement));
|
|
4508
|
+
placements.set(resourceId, sizes);
|
|
4509
|
+
};
|
|
4197
4510
|
const visitInline = (inline) => {
|
|
4198
4511
|
if (inline.kind === "image") {
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4512
|
+
record(inline.resourceId, inline);
|
|
4513
|
+
return;
|
|
4514
|
+
}
|
|
4515
|
+
if (inline.kind === "drawingGroup") {
|
|
4516
|
+
for (const child of inline.children) {
|
|
4517
|
+
if (child.kind === "picture") {
|
|
4518
|
+
record(child.resourceId, {
|
|
4519
|
+
widthEmu: child.frame.widthEmu,
|
|
4520
|
+
heightEmu: child.frame.heightEmu
|
|
4521
|
+
});
|
|
4522
|
+
continue;
|
|
4523
|
+
}
|
|
4524
|
+
child.text?.paragraphs.forEach(visitBlock);
|
|
4525
|
+
}
|
|
4203
4526
|
return;
|
|
4204
4527
|
}
|
|
4205
4528
|
if (inline.kind === "hyperlink" || inline.kind === "revision") {
|
|
@@ -4249,6 +4572,7 @@ var init_office_open = __esm({
|
|
|
4249
4572
|
"src/renderers/office-open/index.ts"() {
|
|
4250
4573
|
"use strict";
|
|
4251
4574
|
init_features();
|
|
4575
|
+
init_chartParts();
|
|
4252
4576
|
init_imageUtils();
|
|
4253
4577
|
init_packageDocument();
|
|
4254
4578
|
init_emit2();
|
|
@@ -4372,6 +4696,9 @@ import {
|
|
|
4372
4696
|
DEFAULT_VISUAL_DPI,
|
|
4373
4697
|
MAX_RASTERIZE_BATCH_SLIDES
|
|
4374
4698
|
} from "@json-to-office/shared";
|
|
4699
|
+
import {
|
|
4700
|
+
isNativeVisualProps
|
|
4701
|
+
} from "@json-to-office/shared-docx";
|
|
4375
4702
|
|
|
4376
4703
|
// src/components/visual.ts
|
|
4377
4704
|
import { createHash } from "crypto";
|
|
@@ -4584,7 +4911,9 @@ function collectVisualProps(root) {
|
|
|
4584
4911
|
const obj = node;
|
|
4585
4912
|
if (typeof obj.name === "string" && obj.enabled === false) return;
|
|
4586
4913
|
if (obj.name === "visual" && obj.props && typeof obj.props === "object") {
|
|
4587
|
-
|
|
4914
|
+
if (!isNativeVisualProps(obj.props)) {
|
|
4915
|
+
found.push(obj.props);
|
|
4916
|
+
}
|
|
4588
4917
|
return;
|
|
4589
4918
|
}
|
|
4590
4919
|
for (const value of Object.values(obj)) visit(value);
|
|
@@ -4864,6 +5193,9 @@ import {
|
|
|
4864
5193
|
clampVisualDpi as clampVisualDpi2,
|
|
4865
5194
|
DEFAULT_VISUAL_DPI as DEFAULT_VISUAL_DPI2
|
|
4866
5195
|
} from "@json-to-office/shared";
|
|
5196
|
+
import {
|
|
5197
|
+
isNativeVisualProps as isNativeVisualProps2
|
|
5198
|
+
} from "@json-to-office/shared-docx";
|
|
4867
5199
|
|
|
4868
5200
|
// src/components/highcharts.ts
|
|
4869
5201
|
init_colorUtils();
|
|
@@ -5025,6 +5357,7 @@ async function desugarExternals(document, options) {
|
|
|
5025
5357
|
return transformComponents(document, async (node) => {
|
|
5026
5358
|
if (node.enabled === false) return void 0;
|
|
5027
5359
|
if (node.name === "visual") {
|
|
5360
|
+
if (isNativeVisualProps2(node.props)) return void 0;
|
|
5028
5361
|
return withNodeIdentity(node, {
|
|
5029
5362
|
name: "image",
|
|
5030
5363
|
props: await visualImageProps(
|
|
@@ -5077,6 +5410,9 @@ async function visualImageProps(props, prerastered, options) {
|
|
|
5077
5410
|
|
|
5078
5411
|
// src/core/imageResources.ts
|
|
5079
5412
|
init_imageUtils();
|
|
5413
|
+
import {
|
|
5414
|
+
isNativeVisualProps as isNativeVisualProps3
|
|
5415
|
+
} from "@json-to-office/shared-docx";
|
|
5080
5416
|
async function loadImageResources(components) {
|
|
5081
5417
|
const sources = /* @__PURE__ */ new Set();
|
|
5082
5418
|
for (const component of components) collectSources(component, sources);
|
|
@@ -5115,6 +5451,9 @@ function collectSources(component, out) {
|
|
|
5115
5451
|
const children = component.children;
|
|
5116
5452
|
for (const child of children ?? []) collectSources(child, out);
|
|
5117
5453
|
const props = component.props ?? {};
|
|
5454
|
+
if (component.name === "visual" && isNativeVisualProps3(props)) {
|
|
5455
|
+
collectNativeVisualSources(props, out);
|
|
5456
|
+
}
|
|
5118
5457
|
for (const key of ["header", "footer"]) {
|
|
5119
5458
|
const part = props[key];
|
|
5120
5459
|
if (Array.isArray(part)) {
|
|
@@ -5136,6 +5475,23 @@ function collectSources(component, out) {
|
|
|
5136
5475
|
}
|
|
5137
5476
|
}
|
|
5138
5477
|
}
|
|
5478
|
+
function collectNativeVisualSources(props, out) {
|
|
5479
|
+
const canvas = props.canvas;
|
|
5480
|
+
const background = canvas?.background?.image;
|
|
5481
|
+
if (background) {
|
|
5482
|
+
const source = resolveImageSource(background);
|
|
5483
|
+
if (source) out.add(source);
|
|
5484
|
+
}
|
|
5485
|
+
const elements = props.elements;
|
|
5486
|
+
if (!Array.isArray(elements)) return;
|
|
5487
|
+
for (const element of elements) {
|
|
5488
|
+
if (!element || element.name !== "image") continue;
|
|
5489
|
+
const source = resolveImageSource(
|
|
5490
|
+
element.props ?? {}
|
|
5491
|
+
);
|
|
5492
|
+
if (source) out.add(source);
|
|
5493
|
+
}
|
|
5494
|
+
}
|
|
5139
5495
|
function collectCellSource(content, out) {
|
|
5140
5496
|
if (content && typeof content === "object") {
|
|
5141
5497
|
collectSources(content, out);
|
|
@@ -5147,7 +5503,520 @@ init_colorUtils();
|
|
|
5147
5503
|
import {
|
|
5148
5504
|
FeatureRequirementCollector
|
|
5149
5505
|
} from "@json-to-office/shared/rendering";
|
|
5150
|
-
import {
|
|
5506
|
+
import {
|
|
5507
|
+
synthesizeFamilyName,
|
|
5508
|
+
DEFAULT_CHART_THEME_COLORS as DEFAULT_CHART_THEME_COLORS2
|
|
5509
|
+
} from "@json-to-office/shared";
|
|
5510
|
+
import {
|
|
5511
|
+
isNativeVisualProps as isNativeVisualProps4
|
|
5512
|
+
} from "@json-to-office/shared-docx";
|
|
5513
|
+
|
|
5514
|
+
// src/ir/nativeVisual.ts
|
|
5515
|
+
init_units();
|
|
5516
|
+
init_imageUtils();
|
|
5517
|
+
var PIXELS_PER_INCH2 = 96;
|
|
5518
|
+
var DEFAULT_WIDTH_FRACTION = 0.75;
|
|
5519
|
+
var ASSUMED_FONT_SIZE_POINTS = 18;
|
|
5520
|
+
var MIN_DERIVED_TEXT_HEIGHT_INCHES = 0.5;
|
|
5521
|
+
var DERIVED_LINE_HEIGHT_RATIO = 1.6;
|
|
5522
|
+
var GEOMETRY_ALIASES = {
|
|
5523
|
+
arrow: "rightArrow",
|
|
5524
|
+
lightning: "lightningBolt"
|
|
5525
|
+
};
|
|
5526
|
+
var DASH_VALUES = /* @__PURE__ */ new Set([
|
|
5527
|
+
"solid",
|
|
5528
|
+
"dash",
|
|
5529
|
+
"dot",
|
|
5530
|
+
"dashDot"
|
|
5531
|
+
]);
|
|
5532
|
+
var ANCHOR = {
|
|
5533
|
+
top: "top",
|
|
5534
|
+
middle: "middle",
|
|
5535
|
+
bottom: "bottom"
|
|
5536
|
+
};
|
|
5537
|
+
var ALIGNMENT = {
|
|
5538
|
+
left: "left",
|
|
5539
|
+
center: "center",
|
|
5540
|
+
right: "right",
|
|
5541
|
+
justify: "justified"
|
|
5542
|
+
};
|
|
5543
|
+
function compileNativeVisualGroup(props, outer) {
|
|
5544
|
+
const canvas = {
|
|
5545
|
+
widthEmu: inchesToEmu(props.canvas.width),
|
|
5546
|
+
heightEmu: inchesToEmu(props.canvas.height)
|
|
5547
|
+
};
|
|
5548
|
+
const children = [];
|
|
5549
|
+
let refused = false;
|
|
5550
|
+
const deps = {
|
|
5551
|
+
...outer,
|
|
5552
|
+
reject: (detail) => {
|
|
5553
|
+
refused = true;
|
|
5554
|
+
outer.reject(detail);
|
|
5555
|
+
}
|
|
5556
|
+
};
|
|
5557
|
+
const background = props.canvas.background;
|
|
5558
|
+
if (background?.color) {
|
|
5559
|
+
const color = deps.color(background.color);
|
|
5560
|
+
if (!color) {
|
|
5561
|
+
deps.reject(`canvas.background.color "${background.color}"`);
|
|
5562
|
+
} else {
|
|
5563
|
+
children.push({
|
|
5564
|
+
kind: "shape",
|
|
5565
|
+
frame: fullBleed(canvas),
|
|
5566
|
+
geometry: "rect",
|
|
5567
|
+
fill: { kind: "solid", color },
|
|
5568
|
+
name: "Canvas background"
|
|
5569
|
+
});
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
if (background?.image) {
|
|
5573
|
+
const source = resolveImageSource(background.image);
|
|
5574
|
+
const resource = source ? deps.picture(source) : void 0;
|
|
5575
|
+
if (!resource) {
|
|
5576
|
+
deps.reject("canvas.background.image could not be loaded");
|
|
5577
|
+
} else {
|
|
5578
|
+
children.push({
|
|
5579
|
+
kind: "picture",
|
|
5580
|
+
frame: fullBleed(canvas),
|
|
5581
|
+
resourceId: resource.resourceId,
|
|
5582
|
+
name: "Canvas background"
|
|
5583
|
+
});
|
|
5584
|
+
}
|
|
5585
|
+
}
|
|
5586
|
+
for (const [index, element] of (props.elements ?? []).entries()) {
|
|
5587
|
+
if (element.enabled === false) continue;
|
|
5588
|
+
const child = compileElement(element, index, canvas, deps);
|
|
5589
|
+
if (child) children.push(child);
|
|
5590
|
+
}
|
|
5591
|
+
return refused ? void 0 : { canvas, children };
|
|
5592
|
+
}
|
|
5593
|
+
function compileElement(element, index, canvas, deps) {
|
|
5594
|
+
const at = `elements[${index}]`;
|
|
5595
|
+
switch (element.name) {
|
|
5596
|
+
case "text":
|
|
5597
|
+
return compileText(element.props, at, canvas, deps);
|
|
5598
|
+
case "shape":
|
|
5599
|
+
return compileShape(element.props, at, canvas, deps);
|
|
5600
|
+
case "image":
|
|
5601
|
+
return compileImage(element.props, at, canvas, deps);
|
|
5602
|
+
default:
|
|
5603
|
+
deps.reject(`${at} is a "${element.name}"`);
|
|
5604
|
+
return void 0;
|
|
5605
|
+
}
|
|
5606
|
+
}
|
|
5607
|
+
function compileText(props, at, canvas, deps) {
|
|
5608
|
+
const runs = textRuns(props);
|
|
5609
|
+
if (!runs) {
|
|
5610
|
+
deps.warn(
|
|
5611
|
+
`[core-docx] native visual ${at} has neither "text" nor "runs"; nothing was drawn for it.`
|
|
5612
|
+
);
|
|
5613
|
+
return void 0;
|
|
5614
|
+
}
|
|
5615
|
+
const colors = resolveColors(
|
|
5616
|
+
[
|
|
5617
|
+
["color", props.color],
|
|
5618
|
+
["fill.color", props.fill?.color],
|
|
5619
|
+
...runs.map((run, i) => [`runs[${i}].color`, run.color]),
|
|
5620
|
+
...runs.map(
|
|
5621
|
+
(run, i) => [
|
|
5622
|
+
`runs[${i}].underline.color`,
|
|
5623
|
+
underlineColor(run.underline)
|
|
5624
|
+
]
|
|
5625
|
+
),
|
|
5626
|
+
["underline.color", underlineColor(props.underline)]
|
|
5627
|
+
],
|
|
5628
|
+
at,
|
|
5629
|
+
deps
|
|
5630
|
+
);
|
|
5631
|
+
if (!colors) return void 0;
|
|
5632
|
+
const frame = resolveFrame(props, canvas, {
|
|
5633
|
+
defaultHeightInches: derivedTextHeightInches(props, runs)
|
|
5634
|
+
});
|
|
5635
|
+
const paragraphs = textParagraphs(runs, props, colors, at);
|
|
5636
|
+
return {
|
|
5637
|
+
kind: "shape",
|
|
5638
|
+
frame,
|
|
5639
|
+
geometry: "rect",
|
|
5640
|
+
isTextBox: true,
|
|
5641
|
+
// A text box with no fill is transparent, which is what the rasterized
|
|
5642
|
+
// path draws; saying so explicitly stops Word applying its own default.
|
|
5643
|
+
fill: props.fill?.color ? solidFill(colors.get("fill.color"), props.fill.transparency) : { kind: "none" },
|
|
5644
|
+
text: {
|
|
5645
|
+
paragraphs,
|
|
5646
|
+
// pptx anchors a text box at the top unless told otherwise, and a
|
|
5647
|
+
// drawing that changes anchor between render modes moves its text.
|
|
5648
|
+
anchor: ANCHOR[props.valign ?? "top"] ?? "top",
|
|
5649
|
+
// A text box's insets default to nothing, again matching pptx. A shape
|
|
5650
|
+
// deliberately leaves them unstated so OOXML's own defaults apply.
|
|
5651
|
+
insetsEmu: marginInsets(props.margin) ?? ZERO_INSETS
|
|
5652
|
+
},
|
|
5653
|
+
name: `Text ${at}`
|
|
5654
|
+
};
|
|
5655
|
+
}
|
|
5656
|
+
var ZERO_INSETS = { top: 0, bottom: 0, left: 0, right: 0 };
|
|
5657
|
+
function textRuns(props) {
|
|
5658
|
+
if (props.runs?.length) return props.runs;
|
|
5659
|
+
if (props.text === void 0) return void 0;
|
|
5660
|
+
return [{ text: props.text }];
|
|
5661
|
+
}
|
|
5662
|
+
function textParagraphs(runs, props, colors, at) {
|
|
5663
|
+
const paragraphs = [];
|
|
5664
|
+
let current = [];
|
|
5665
|
+
const flush = () => {
|
|
5666
|
+
paragraphs.push({
|
|
5667
|
+
kind: "paragraph",
|
|
5668
|
+
id: `${at}.p${paragraphs.length}`,
|
|
5669
|
+
path: `${at}.paragraphs[${paragraphs.length}]`,
|
|
5670
|
+
children: current,
|
|
5671
|
+
formatting: {
|
|
5672
|
+
...props.align ? { alignment: ALIGNMENT[props.align] } : {},
|
|
5673
|
+
// A drawing's text is set flush against the shape; the document's
|
|
5674
|
+
// paragraph spacing would push it away from the top edge, which is not
|
|
5675
|
+
// what an absolutely-placed box means.
|
|
5676
|
+
spacing: { beforeTwips: 0, afterTwips: 0 }
|
|
5677
|
+
}
|
|
5678
|
+
});
|
|
5679
|
+
current = [];
|
|
5680
|
+
};
|
|
5681
|
+
runs.forEach((run, index) => {
|
|
5682
|
+
const formatting = runFormatting(run, props, colors, index);
|
|
5683
|
+
const lines = run.text.split("\n");
|
|
5684
|
+
lines.forEach((line, lineIndex) => {
|
|
5685
|
+
if (lineIndex > 0) current.push({ kind: "lineBreak" });
|
|
5686
|
+
if (line) {
|
|
5687
|
+
current.push({ kind: "text", text: line, ...formatting });
|
|
5688
|
+
}
|
|
5689
|
+
});
|
|
5690
|
+
if (run.breakLine && index < runs.length - 1) flush();
|
|
5691
|
+
});
|
|
5692
|
+
flush();
|
|
5693
|
+
return paragraphs;
|
|
5694
|
+
}
|
|
5695
|
+
function runFormatting(run, props, colors, index) {
|
|
5696
|
+
const fontFamily = run.fontFace ?? props.fontFace;
|
|
5697
|
+
const sizePoints = run.fontSize ?? props.fontSize;
|
|
5698
|
+
const color = colors.get(`runs[${index}].color`) ?? (run.color === void 0 ? colors.get("color") : void 0);
|
|
5699
|
+
const bold = run.bold ?? props.bold;
|
|
5700
|
+
const italic = run.italic ?? props.italic;
|
|
5701
|
+
const strike = run.strike ?? props.strike;
|
|
5702
|
+
const underline = compileUnderline(
|
|
5703
|
+
run.underline ?? props.underline,
|
|
5704
|
+
colors.get(
|
|
5705
|
+
run.underline !== void 0 ? `runs[${index}].underline.color` : "underline.color"
|
|
5706
|
+
)
|
|
5707
|
+
);
|
|
5708
|
+
const formatting = {
|
|
5709
|
+
...fontFamily ? { fontFamily } : {},
|
|
5710
|
+
...sizePoints !== void 0 ? { sizeHalfPoints: pointsToHalfPoints(sizePoints) } : {},
|
|
5711
|
+
...color ? { color } : {},
|
|
5712
|
+
...bold !== void 0 ? { bold } : {},
|
|
5713
|
+
...italic !== void 0 ? { italic } : {},
|
|
5714
|
+
...strike !== void 0 ? { strike } : {},
|
|
5715
|
+
...underline ? { underline } : {}
|
|
5716
|
+
};
|
|
5717
|
+
return Object.keys(formatting).length > 0 ? { formatting } : {};
|
|
5718
|
+
}
|
|
5719
|
+
var UNDERLINE_TYPES = {
|
|
5720
|
+
sng: "single",
|
|
5721
|
+
dbl: "double",
|
|
5722
|
+
dash: "dash",
|
|
5723
|
+
dotted: "dotted"
|
|
5724
|
+
};
|
|
5725
|
+
function compileUnderline(value, color) {
|
|
5726
|
+
if (value === void 0 || value === false) return void 0;
|
|
5727
|
+
const style = value === true ? "sng" : value.style ?? "sng";
|
|
5728
|
+
return {
|
|
5729
|
+
type: UNDERLINE_TYPES[style] ?? "single",
|
|
5730
|
+
...color ? { color } : {}
|
|
5731
|
+
};
|
|
5732
|
+
}
|
|
5733
|
+
function underlineColor(value) {
|
|
5734
|
+
return typeof value === "object" ? value.color : void 0;
|
|
5735
|
+
}
|
|
5736
|
+
function derivedTextHeightInches(props, runs) {
|
|
5737
|
+
const fontSize = props.fontSize ?? ASSUMED_FONT_SIZE_POINTS;
|
|
5738
|
+
const lineCount = runs.reduce(
|
|
5739
|
+
(total, run) => total + (run.breakLine ? 1 : 0) + (run.text.match(/\n/g)?.length ?? 0),
|
|
5740
|
+
1
|
|
5741
|
+
);
|
|
5742
|
+
return Math.max(
|
|
5743
|
+
MIN_DERIVED_TEXT_HEIGHT_INCHES,
|
|
5744
|
+
fontSize / 72 * DERIVED_LINE_HEIGHT_RATIO * lineCount
|
|
5745
|
+
);
|
|
5746
|
+
}
|
|
5747
|
+
function compileShape(props, at, canvas, deps) {
|
|
5748
|
+
const segments = shapeSegments(props);
|
|
5749
|
+
const colors = resolveColors(
|
|
5750
|
+
[
|
|
5751
|
+
["fill.color", props.fill?.color],
|
|
5752
|
+
["line.color", props.line?.color],
|
|
5753
|
+
["fontColor", props.fontColor],
|
|
5754
|
+
...segments.map(
|
|
5755
|
+
(segment, i) => [`text[${i}].color`, segment.color]
|
|
5756
|
+
)
|
|
5757
|
+
],
|
|
5758
|
+
at,
|
|
5759
|
+
deps
|
|
5760
|
+
);
|
|
5761
|
+
if (!colors) return void 0;
|
|
5762
|
+
const frame = resolveFrame(props, canvas, { defaultHeightInches: 0 });
|
|
5763
|
+
if (props.flipH) frame.flipHorizontal = true;
|
|
5764
|
+
if (props.flipV) frame.flipVertical = true;
|
|
5765
|
+
const dash = props.line?.dashType;
|
|
5766
|
+
if (dash !== void 0 && !DASH_VALUES.has(dash)) {
|
|
5767
|
+
deps.reject(`${at}.line.dashType "${dash}"`);
|
|
5768
|
+
return void 0;
|
|
5769
|
+
}
|
|
5770
|
+
const outline = props.line ? {
|
|
5771
|
+
...colors.get("line.color") ? { color: colors.get("line.color") } : {},
|
|
5772
|
+
...props.line.width !== void 0 ? { widthEmu: pointsToEmu(props.line.width) } : {},
|
|
5773
|
+
...dash && dash !== "solid" ? { dash } : {}
|
|
5774
|
+
} : void 0;
|
|
5775
|
+
const text = segments.length ? {
|
|
5776
|
+
paragraphs: shapeParagraphs(segments, props, colors, at),
|
|
5777
|
+
anchor: ANCHOR[props.valign ?? "middle"] ?? "middle",
|
|
5778
|
+
// Unstated insets are left unstated so OOXML's own shape defaults
|
|
5779
|
+
// apply, which is what the raster path draws.
|
|
5780
|
+
...marginInsets(props.margin) ? { insetsEmu: marginInsets(props.margin) } : {}
|
|
5781
|
+
} : void 0;
|
|
5782
|
+
return {
|
|
5783
|
+
kind: "shape",
|
|
5784
|
+
frame,
|
|
5785
|
+
geometry: GEOMETRY_ALIASES[props.type] ?? props.type,
|
|
5786
|
+
...props.fill ? {
|
|
5787
|
+
fill: props.fill.color ? solidFill(colors.get("fill.color"), props.fill.transparency) : { kind: "none" }
|
|
5788
|
+
} : {},
|
|
5789
|
+
...outline && Object.keys(outline).length > 0 ? { outline } : {},
|
|
5790
|
+
...text ? { text } : {},
|
|
5791
|
+
name: `Shape ${at}`
|
|
5792
|
+
};
|
|
5793
|
+
}
|
|
5794
|
+
function shapeSegments(props) {
|
|
5795
|
+
if (props.text === void 0) return [];
|
|
5796
|
+
if (typeof props.text === "string") {
|
|
5797
|
+
return props.text ? [{ text: props.text }] : [];
|
|
5798
|
+
}
|
|
5799
|
+
return props.text;
|
|
5800
|
+
}
|
|
5801
|
+
function shapeParagraphs(segments, props, colors, at) {
|
|
5802
|
+
const paragraphs = [];
|
|
5803
|
+
let current = [];
|
|
5804
|
+
const flush = () => {
|
|
5805
|
+
paragraphs.push({
|
|
5806
|
+
kind: "paragraph",
|
|
5807
|
+
id: `${at}.p${paragraphs.length}`,
|
|
5808
|
+
path: `${at}.paragraphs[${paragraphs.length}]`,
|
|
5809
|
+
children: current,
|
|
5810
|
+
formatting: {
|
|
5811
|
+
// A shape centres its text unless told otherwise, which is what the
|
|
5812
|
+
// rasterized shape draws.
|
|
5813
|
+
alignment: props.align ? ALIGNMENT[props.align] : "center",
|
|
5814
|
+
spacing: { beforeTwips: 0, afterTwips: 0 }
|
|
5815
|
+
}
|
|
5816
|
+
});
|
|
5817
|
+
current = [];
|
|
5818
|
+
};
|
|
5819
|
+
segments.forEach((segment, index) => {
|
|
5820
|
+
const fontFamily = segment.fontFace ?? props.fontFace;
|
|
5821
|
+
const sizePoints = segment.fontSize ?? props.fontSize;
|
|
5822
|
+
const color = colors.get(`text[${index}].color`) ?? (segment.color === void 0 ? colors.get("fontColor") : void 0);
|
|
5823
|
+
const bold = segment.bold ?? props.bold;
|
|
5824
|
+
const italic = segment.italic ?? props.italic;
|
|
5825
|
+
const formatting = {
|
|
5826
|
+
...fontFamily ? { fontFamily } : {},
|
|
5827
|
+
...sizePoints !== void 0 ? { sizeHalfPoints: pointsToHalfPoints(sizePoints) } : {},
|
|
5828
|
+
...color ? { color } : {},
|
|
5829
|
+
...bold !== void 0 ? { bold } : {},
|
|
5830
|
+
...italic !== void 0 ? { italic } : {}
|
|
5831
|
+
};
|
|
5832
|
+
const wrapped = Object.keys(formatting).length > 0 ? { formatting } : void 0;
|
|
5833
|
+
segment.text.split("\n").forEach((line, lineIndex) => {
|
|
5834
|
+
if (lineIndex > 0) current.push({ kind: "lineBreak" });
|
|
5835
|
+
if (line) current.push({ kind: "text", text: line, ...wrapped });
|
|
5836
|
+
});
|
|
5837
|
+
if (segment.breakLine && index < segments.length - 1) flush();
|
|
5838
|
+
});
|
|
5839
|
+
flush();
|
|
5840
|
+
return paragraphs;
|
|
5841
|
+
}
|
|
5842
|
+
function compileImage(props, at, canvas, deps) {
|
|
5843
|
+
const source = resolveImageSource(props);
|
|
5844
|
+
if (!source) {
|
|
5845
|
+
deps.reject(`${at} has none of "path", "base64" or "svg"`);
|
|
5846
|
+
return void 0;
|
|
5847
|
+
}
|
|
5848
|
+
const resource = deps.picture(source);
|
|
5849
|
+
if (!resource) {
|
|
5850
|
+
deps.reject(`${at} image could not be loaded`);
|
|
5851
|
+
return void 0;
|
|
5852
|
+
}
|
|
5853
|
+
const aspect = resource.intrinsic ? resource.intrinsic.width / resource.intrinsic.height : void 0;
|
|
5854
|
+
const intrinsicInches = resource.intrinsic ? {
|
|
5855
|
+
width: resource.intrinsic.width / PIXELS_PER_INCH2,
|
|
5856
|
+
height: resource.intrinsic.height / PIXELS_PER_INCH2
|
|
5857
|
+
} : void 0;
|
|
5858
|
+
const box = resolveImageBox(props, canvas, aspect, intrinsicInches);
|
|
5859
|
+
const sizing = props.sizing;
|
|
5860
|
+
if (!sizing || !aspect) {
|
|
5861
|
+
if (sizing && !aspect) {
|
|
5862
|
+
deps.warn(
|
|
5863
|
+
`[core-docx] native visual ${at} asks for "${sizing.type}" sizing, but the image's stored size could not be read; it was stretched to its box instead.`
|
|
5864
|
+
);
|
|
5865
|
+
}
|
|
5866
|
+
return picture(box, props, resource, at);
|
|
5867
|
+
}
|
|
5868
|
+
const boxAspect = box.widthEmu / box.heightEmu;
|
|
5869
|
+
if (sizing.type === "contain") {
|
|
5870
|
+
const fitted = boxAspect > aspect ? {
|
|
5871
|
+
widthEmu: Math.round(box.heightEmu * aspect),
|
|
5872
|
+
heightEmu: box.heightEmu
|
|
5873
|
+
} : {
|
|
5874
|
+
widthEmu: box.widthEmu,
|
|
5875
|
+
heightEmu: Math.round(box.widthEmu / aspect)
|
|
5876
|
+
};
|
|
5877
|
+
return picture(
|
|
5878
|
+
{
|
|
5879
|
+
...box,
|
|
5880
|
+
xEmu: box.xEmu + Math.round((box.widthEmu - fitted.widthEmu) / 2),
|
|
5881
|
+
yEmu: box.yEmu + Math.round((box.heightEmu - fitted.heightEmu) / 2),
|
|
5882
|
+
...fitted
|
|
5883
|
+
},
|
|
5884
|
+
props,
|
|
5885
|
+
resource,
|
|
5886
|
+
at
|
|
5887
|
+
);
|
|
5888
|
+
}
|
|
5889
|
+
const visible = boxAspect > aspect ? { widthFraction: 1, heightFraction: aspect / boxAspect } : { widthFraction: boxAspect / aspect, heightFraction: 1 };
|
|
5890
|
+
const crop = sizing.type === "cover" ? {
|
|
5891
|
+
...visible.widthFraction < 1 ? {
|
|
5892
|
+
left: (1 - visible.widthFraction) / 2,
|
|
5893
|
+
right: (1 - visible.widthFraction) / 2
|
|
5894
|
+
} : {},
|
|
5895
|
+
...visible.heightFraction < 1 ? {
|
|
5896
|
+
top: (1 - visible.heightFraction) / 2,
|
|
5897
|
+
bottom: (1 - visible.heightFraction) / 2
|
|
5898
|
+
} : {}
|
|
5899
|
+
} : {
|
|
5900
|
+
...visible.widthFraction < 1 ? { right: 1 - visible.widthFraction } : {},
|
|
5901
|
+
...visible.heightFraction < 1 ? { bottom: 1 - visible.heightFraction } : {}
|
|
5902
|
+
};
|
|
5903
|
+
return picture(box, props, resource, at, crop);
|
|
5904
|
+
}
|
|
5905
|
+
function picture(frame, props, resource, at, crop) {
|
|
5906
|
+
return {
|
|
5907
|
+
kind: "picture",
|
|
5908
|
+
frame,
|
|
5909
|
+
resourceId: resource.resourceId,
|
|
5910
|
+
...props.alt ? { altText: props.alt } : {},
|
|
5911
|
+
...crop && Object.keys(crop).length > 0 ? { crop } : {},
|
|
5912
|
+
name: `Image ${at}`
|
|
5913
|
+
};
|
|
5914
|
+
}
|
|
5915
|
+
function resolveImageBox(props, canvas, aspect, intrinsicInches) {
|
|
5916
|
+
const width = props.sizing?.w ?? props.w;
|
|
5917
|
+
const height = props.sizing?.h ?? props.h;
|
|
5918
|
+
const widthEmu = width !== void 0 ? resolveExtent(width, canvas.widthEmu) : void 0;
|
|
5919
|
+
const heightEmu = height !== void 0 ? resolveExtent(height, canvas.heightEmu) : void 0;
|
|
5920
|
+
const resolved = (() => {
|
|
5921
|
+
if (widthEmu !== void 0 && heightEmu !== void 0) {
|
|
5922
|
+
return { widthEmu, heightEmu };
|
|
5923
|
+
}
|
|
5924
|
+
if (widthEmu !== void 0) {
|
|
5925
|
+
return {
|
|
5926
|
+
widthEmu,
|
|
5927
|
+
heightEmu: aspect ? Math.round(widthEmu / aspect) : Math.round(widthEmu * (3 / 4))
|
|
5928
|
+
};
|
|
5929
|
+
}
|
|
5930
|
+
if (heightEmu !== void 0) {
|
|
5931
|
+
return {
|
|
5932
|
+
widthEmu: aspect ? Math.round(heightEmu * aspect) : Math.round(heightEmu * (4 / 3)),
|
|
5933
|
+
heightEmu
|
|
5934
|
+
};
|
|
5935
|
+
}
|
|
5936
|
+
if (intrinsicInches) {
|
|
5937
|
+
return {
|
|
5938
|
+
widthEmu: inchesToEmu(intrinsicInches.width),
|
|
5939
|
+
heightEmu: inchesToEmu(intrinsicInches.height)
|
|
5940
|
+
};
|
|
5941
|
+
}
|
|
5942
|
+
const fallbackWidth = Math.round(canvas.widthEmu * DEFAULT_WIDTH_FRACTION);
|
|
5943
|
+
return {
|
|
5944
|
+
widthEmu: fallbackWidth,
|
|
5945
|
+
heightEmu: Math.round(fallbackWidth * (3 / 4))
|
|
5946
|
+
};
|
|
5947
|
+
})();
|
|
5948
|
+
return {
|
|
5949
|
+
xEmu: props.x !== void 0 ? resolveOffset(props.x, canvas.widthEmu) : 0,
|
|
5950
|
+
yEmu: props.y !== void 0 ? resolveOffset(props.y, canvas.heightEmu) : 0,
|
|
5951
|
+
...resolved,
|
|
5952
|
+
...rotation(props.rotate)
|
|
5953
|
+
};
|
|
5954
|
+
}
|
|
5955
|
+
function fullBleed(canvas) {
|
|
5956
|
+
return {
|
|
5957
|
+
xEmu: 0,
|
|
5958
|
+
yEmu: 0,
|
|
5959
|
+
widthEmu: canvas.widthEmu,
|
|
5960
|
+
heightEmu: canvas.heightEmu
|
|
5961
|
+
};
|
|
5962
|
+
}
|
|
5963
|
+
function resolveFrame(props, canvas, options) {
|
|
5964
|
+
return {
|
|
5965
|
+
xEmu: props.x !== void 0 ? resolveOffset(props.x, canvas.widthEmu) : 0,
|
|
5966
|
+
yEmu: props.y !== void 0 ? resolveOffset(props.y, canvas.heightEmu) : 0,
|
|
5967
|
+
widthEmu: props.w !== void 0 ? resolveExtent(props.w, canvas.widthEmu) : Math.round(canvas.widthEmu * DEFAULT_WIDTH_FRACTION),
|
|
5968
|
+
heightEmu: props.h !== void 0 ? resolveExtent(props.h, canvas.heightEmu) : inchesToEmu(options.defaultHeightInches),
|
|
5969
|
+
...rotation(props.rotate)
|
|
5970
|
+
};
|
|
5971
|
+
}
|
|
5972
|
+
function rotation(degrees) {
|
|
5973
|
+
if (degrees === void 0 || degrees % 360 === 0) return {};
|
|
5974
|
+
return { rotationDegrees: degrees };
|
|
5975
|
+
}
|
|
5976
|
+
function resolveOffset(value, axisEmu) {
|
|
5977
|
+
return resolveLength(value, axisEmu);
|
|
5978
|
+
}
|
|
5979
|
+
function resolveExtent(value, axisEmu) {
|
|
5980
|
+
return Math.max(0, resolveLength(value, axisEmu));
|
|
5981
|
+
}
|
|
5982
|
+
function resolveLength(value, axisEmu) {
|
|
5983
|
+
if (typeof value === "number") return inchesToEmu(value);
|
|
5984
|
+
const percent = Number.parseFloat(value);
|
|
5985
|
+
if (!Number.isFinite(percent)) return 0;
|
|
5986
|
+
return Math.round(percent / 100 * axisEmu);
|
|
5987
|
+
}
|
|
5988
|
+
function marginInsets(margin) {
|
|
5989
|
+
if (margin === void 0) return void 0;
|
|
5990
|
+
const [top, right, bottom, left] = Array.isArray(margin) ? margin : [margin, margin, margin, margin];
|
|
5991
|
+
return {
|
|
5992
|
+
top: pointsToEmu(top ?? 0),
|
|
5993
|
+
right: pointsToEmu(right ?? 0),
|
|
5994
|
+
bottom: pointsToEmu(bottom ?? 0),
|
|
5995
|
+
left: pointsToEmu(left ?? 0)
|
|
5996
|
+
};
|
|
5997
|
+
}
|
|
5998
|
+
function solidFill(color, transparency) {
|
|
5999
|
+
return {
|
|
6000
|
+
kind: "solid",
|
|
6001
|
+
color,
|
|
6002
|
+
...transparency !== void 0 && transparency > 0 ? { transparencyPercent: transparency } : {}
|
|
6003
|
+
};
|
|
6004
|
+
}
|
|
6005
|
+
function resolveColors(entries, at, deps) {
|
|
6006
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
6007
|
+
let failed = false;
|
|
6008
|
+
for (const [key, value] of entries) {
|
|
6009
|
+
if (value === void 0) continue;
|
|
6010
|
+
const color = deps.color(value);
|
|
6011
|
+
if (!color) {
|
|
6012
|
+
deps.reject(`${at}.${key} "${value}"`);
|
|
6013
|
+
failed = true;
|
|
6014
|
+
continue;
|
|
6015
|
+
}
|
|
6016
|
+
resolved.set(key, color);
|
|
6017
|
+
}
|
|
6018
|
+
return failed ? void 0 : resolved;
|
|
6019
|
+
}
|
|
5151
6020
|
|
|
5152
6021
|
// src/styles/themeToStyles.ts
|
|
5153
6022
|
init_themes();
|
|
@@ -6990,7 +7859,7 @@ function decoratedStyle(match, base) {
|
|
|
6990
7859
|
function pushSegment(out, text, options, override = {}) {
|
|
6991
7860
|
const bold = override.bold ?? options.base.bold;
|
|
6992
7861
|
const italic = override.italic ?? options.base.italic;
|
|
6993
|
-
const formatting =
|
|
7862
|
+
const formatting = runFormatting2(options, { bold, italic });
|
|
6994
7863
|
const lines = text.split("\n");
|
|
6995
7864
|
for (const [lineIndex, line] of lines.entries()) {
|
|
6996
7865
|
if (!line && lineIndex === 0) continue;
|
|
@@ -7068,7 +7937,7 @@ function pushNoProofWords(out, text, formatting, options) {
|
|
|
7068
7937
|
out.push({ kind: "text", text: text.slice(lastIndex), formatting });
|
|
7069
7938
|
}
|
|
7070
7939
|
}
|
|
7071
|
-
function
|
|
7940
|
+
function runFormatting2(options, state) {
|
|
7072
7941
|
const formatting = { ...options.base };
|
|
7073
7942
|
if (state.bold !== void 0) formatting.bold = state.bold;
|
|
7074
7943
|
if (state.italic !== void 0) formatting.italic = state.italic;
|
|
@@ -7440,7 +8309,11 @@ function compileComponent(component, scope) {
|
|
|
7440
8309
|
case "columns":
|
|
7441
8310
|
return compileColumns(component, scope);
|
|
7442
8311
|
case "image":
|
|
7443
|
-
return
|
|
8312
|
+
return compileImage2(component, scope);
|
|
8313
|
+
case "visual":
|
|
8314
|
+
return compileVisual(component, scope);
|
|
8315
|
+
case "chart":
|
|
8316
|
+
return compileChart(component, scope);
|
|
7444
8317
|
case "table":
|
|
7445
8318
|
return compileTable(component, scope);
|
|
7446
8319
|
default:
|
|
@@ -7684,7 +8557,7 @@ function compileTextBox(component, scope) {
|
|
|
7684
8557
|
const style = props.style;
|
|
7685
8558
|
const padding = style?.padding;
|
|
7686
8559
|
if (props.renderAs === "shape") {
|
|
7687
|
-
const shape =
|
|
8560
|
+
const shape = compileShape2(props, children, scope);
|
|
7688
8561
|
if (shape) return shape;
|
|
7689
8562
|
}
|
|
7690
8563
|
ctx.features.require("tables", path4);
|
|
@@ -7738,7 +8611,7 @@ function compileTextBox(component, scope) {
|
|
|
7738
8611
|
];
|
|
7739
8612
|
}
|
|
7740
8613
|
var EMU_PER_PIXEL = 9525;
|
|
7741
|
-
function
|
|
8614
|
+
function compileShape2(props, children, scope) {
|
|
7742
8615
|
const { ctx, path: path4 } = scope;
|
|
7743
8616
|
const width = shapeSize(props.width, "width", ctx);
|
|
7744
8617
|
const height = shapeSize(props.height, "height", ctx);
|
|
@@ -8232,7 +9105,7 @@ function reportUnlowered(component, props, text, scope) {
|
|
|
8232
9105
|
}
|
|
8233
9106
|
function compileRuns(props, text, ctx, path4, mode = "inline") {
|
|
8234
9107
|
const font = props.font ?? {};
|
|
8235
|
-
const base =
|
|
9108
|
+
const base = runFormatting3(font, props, ctx);
|
|
8236
9109
|
if (base.language) ctx.features.require("proofing-language", path4);
|
|
8237
9110
|
if (base.noProof) ctx.features.require("proofing-language", path4);
|
|
8238
9111
|
const words = mergeNoProofWords(noProofWords(ctx.theme), props.noProofWords);
|
|
@@ -8502,7 +9375,7 @@ function isoDate(date) {
|
|
|
8502
9375
|
function isoDateTime(date) {
|
|
8503
9376
|
return date.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "Z");
|
|
8504
9377
|
}
|
|
8505
|
-
function
|
|
9378
|
+
function runFormatting3(font, props, ctx) {
|
|
8506
9379
|
const hasWeightRequest = font.fontWeight != null || font.bold === true;
|
|
8507
9380
|
const effectiveFamily = font.family ?? (hasWeightRequest ? resolveBodyFamily(ctx.theme) : void 0);
|
|
8508
9381
|
const weighted = applyFontWeightAlias({
|
|
@@ -8665,8 +9538,107 @@ function headingLevel(level) {
|
|
|
8665
9538
|
const value = typeof level === "number" ? level : 1;
|
|
8666
9539
|
return value >= 1 && value <= 6 ? value : 1;
|
|
8667
9540
|
}
|
|
9541
|
+
function compileVisual(component, scope) {
|
|
9542
|
+
const { ctx, path: path4 } = scope;
|
|
9543
|
+
const props = component.props ?? {};
|
|
9544
|
+
if (!isNativeVisualProps4(props)) {
|
|
9545
|
+
ctx.unsupported.push({
|
|
9546
|
+
name: "visual",
|
|
9547
|
+
path: path4,
|
|
9548
|
+
detail: "a raster visual reached the compiler; it should have been rasterized during desugaring"
|
|
9549
|
+
});
|
|
9550
|
+
return [];
|
|
9551
|
+
}
|
|
9552
|
+
const group = compileNativeVisualGroup(
|
|
9553
|
+
props,
|
|
9554
|
+
nativeVisualDeps(ctx, path4)
|
|
9555
|
+
);
|
|
9556
|
+
if (!group) return [];
|
|
9557
|
+
const size = visualPlacementPixels(props, group.canvas, {
|
|
9558
|
+
widthPx: Math.round(twipsToPixels(getAvailableWidthTwips(ctx.theme))),
|
|
9559
|
+
heightPx: Math.round(twipsToPixels(getAvailableHeightTwips(ctx.theme)))
|
|
9560
|
+
});
|
|
9561
|
+
const drawing = {
|
|
9562
|
+
kind: "drawingGroup",
|
|
9563
|
+
widthEmu: pixelsToEmu(size.width),
|
|
9564
|
+
heightEmu: pixelsToEmu(size.height),
|
|
9565
|
+
canvasWidthEmu: group.canvas.widthEmu,
|
|
9566
|
+
canvasHeightEmu: group.canvas.heightEmu,
|
|
9567
|
+
children: group.children,
|
|
9568
|
+
...typeof props.alt === "string" && props.alt ? { altText: props.alt } : {},
|
|
9569
|
+
...props.floating ? { floating: compileFloating(props.floating, ctx, path4) } : {}
|
|
9570
|
+
};
|
|
9571
|
+
ctx.features.require("drawing-groups", path4);
|
|
9572
|
+
if (props.floating) ctx.features.require("floating-images", path4);
|
|
9573
|
+
const spacing = {};
|
|
9574
|
+
if (props.spacing?.before !== void 0) {
|
|
9575
|
+
spacing.beforeTwips = pointsToTwips2(props.spacing.before);
|
|
9576
|
+
}
|
|
9577
|
+
if (props.spacing?.after !== void 0) {
|
|
9578
|
+
spacing.afterTwips = pointsToTwips2(props.spacing.after);
|
|
9579
|
+
}
|
|
9580
|
+
const blocks = [
|
|
9581
|
+
{
|
|
9582
|
+
kind: "paragraph",
|
|
9583
|
+
id: scope.id,
|
|
9584
|
+
path: path4,
|
|
9585
|
+
children: [drawing],
|
|
9586
|
+
formatting: {
|
|
9587
|
+
// A floating drawing is anchored, so aligning its paragraph would move
|
|
9588
|
+
// the anchor rather than the graphic — the same rule an image follows.
|
|
9589
|
+
...props.floating ? {} : { alignment: compileAlignment(props.alignment) ?? "center" },
|
|
9590
|
+
...Object.keys(spacing).length > 0 ? { spacing } : {},
|
|
9591
|
+
...props.keepNext !== void 0 ? { keepNext: props.keepNext } : {},
|
|
9592
|
+
...props.keepLines !== void 0 ? { keepLines: props.keepLines } : {}
|
|
9593
|
+
}
|
|
9594
|
+
}
|
|
9595
|
+
];
|
|
9596
|
+
const caption = captionBlock(props.caption, scope, "visual");
|
|
9597
|
+
if (caption === void 0) return [];
|
|
9598
|
+
if (caption) blocks.push(caption);
|
|
9599
|
+
return blocks;
|
|
9600
|
+
}
|
|
9601
|
+
function nativeVisualDeps(ctx, path4) {
|
|
9602
|
+
return {
|
|
9603
|
+
color: (value) => {
|
|
9604
|
+
try {
|
|
9605
|
+
return irColor(resolveColor(value, ctx.theme));
|
|
9606
|
+
} catch {
|
|
9607
|
+
return void 0;
|
|
9608
|
+
}
|
|
9609
|
+
},
|
|
9610
|
+
picture: (source) => {
|
|
9611
|
+
const loaded = ctx.images.get(source);
|
|
9612
|
+
if (!loaded) return void 0;
|
|
9613
|
+
const mediaType = detectImageType(source, loaded.contentType);
|
|
9614
|
+
if (mediaType === "svg") ctx.features.require("svg-images", path4);
|
|
9615
|
+
return {
|
|
9616
|
+
resourceId: declareResource(loaded, mediaType, ctx),
|
|
9617
|
+
mediaType,
|
|
9618
|
+
...loaded.intrinsic ? { intrinsic: loaded.intrinsic } : {}
|
|
9619
|
+
};
|
|
9620
|
+
},
|
|
9621
|
+
reject: (detail) => ctx.unsupported.push({ name: "visual", path: path4, detail }),
|
|
9622
|
+
warn: (message) => warnOnce(ctx, `visual:${message}`, message)
|
|
9623
|
+
};
|
|
9624
|
+
}
|
|
9625
|
+
function visualPlacementPixels(props, canvas, reference) {
|
|
9626
|
+
const canvasWidthPx = Math.round(emuToPixels(canvas.widthEmu));
|
|
9627
|
+
const canvasHeightPx = Math.round(emuToPixels(canvas.heightEmu));
|
|
9628
|
+
const targetWidth = props.width !== void 0 ? parseWidthValue(props.width, reference.widthPx) : void 0;
|
|
9629
|
+
const targetHeight = props.height !== void 0 ? parseDimensionValue(props.height, reference.heightPx) : void 0;
|
|
9630
|
+
if (targetWidth === void 0 && targetHeight === void 0) {
|
|
9631
|
+
return { width: canvasWidthPx, height: canvasHeightPx };
|
|
9632
|
+
}
|
|
9633
|
+
return calculateMissingDimension(
|
|
9634
|
+
canvasWidthPx,
|
|
9635
|
+
canvasHeightPx,
|
|
9636
|
+
targetWidth,
|
|
9637
|
+
targetHeight
|
|
9638
|
+
);
|
|
9639
|
+
}
|
|
8668
9640
|
var UNLOWERED_IMAGE_PROPS = ["comment"];
|
|
8669
|
-
function
|
|
9641
|
+
function compileImage2(component, scope) {
|
|
8670
9642
|
const { ctx, path: path4 } = scope;
|
|
8671
9643
|
const props = component.props ?? {};
|
|
8672
9644
|
for (const prop of UNLOWERED_IMAGE_PROPS) {
|
|
@@ -8727,32 +9699,142 @@ function compileImage(component, scope) {
|
|
|
8727
9699
|
}
|
|
8728
9700
|
}
|
|
8729
9701
|
];
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
|
|
9702
|
+
const caption = captionBlock(props.caption, scope, "image");
|
|
9703
|
+
if (caption === void 0) return [];
|
|
9704
|
+
if (caption) blocks.push(caption);
|
|
9705
|
+
return blocks;
|
|
9706
|
+
}
|
|
9707
|
+
var DEFAULT_CHART_HEIGHT_INCHES = 3;
|
|
9708
|
+
function compileChart(component, scope) {
|
|
9709
|
+
const { ctx, path: path4 } = scope;
|
|
9710
|
+
const props = component.props ?? {};
|
|
9711
|
+
if (props.type === "bubble") {
|
|
9712
|
+
throw new Error(
|
|
9713
|
+
`Chart at ${path4} is a bubble chart, which no docx renderer draws; use the pptx \`chart\` component on the pptxgenjs renderer for one.`
|
|
9714
|
+
);
|
|
9715
|
+
}
|
|
9716
|
+
const rawSeries = Array.isArray(props.data) ? props.data : [];
|
|
9717
|
+
if (rawSeries.length === 0) {
|
|
9718
|
+
throw new Error(`Chart at ${path4} has no data series.`);
|
|
9719
|
+
}
|
|
9720
|
+
const series = rawSeries.map((entry, index) => {
|
|
9721
|
+
const raw = entry ?? {};
|
|
9722
|
+
const label = typeof raw.name === "string" ? raw.name : `series ${index}`;
|
|
9723
|
+
const labels = raw.labels;
|
|
9724
|
+
const values = raw.values;
|
|
9725
|
+
if (!Array.isArray(labels) || !Array.isArray(values)) {
|
|
9726
|
+
throw new Error(
|
|
9727
|
+
`Chart series "${label}" at ${path4} needs both "labels" and "values"; every series carries its own, not just the first.`
|
|
9728
|
+
);
|
|
9729
|
+
}
|
|
9730
|
+
if (labels.length !== values.length) {
|
|
9731
|
+
throw new Error(
|
|
9732
|
+
`Chart series "${label}" at ${path4} has ${labels.length} labels and ${values.length} values; they must be the same length.`
|
|
9733
|
+
);
|
|
9734
|
+
}
|
|
9735
|
+
return {
|
|
9736
|
+
...typeof raw.name === "string" ? { name: raw.name } : {},
|
|
9737
|
+
labels: labels.map((value) => String(value)),
|
|
9738
|
+
values: values.map((value) => Number(value))
|
|
9739
|
+
};
|
|
9740
|
+
});
|
|
9741
|
+
const categories = series[0].labels;
|
|
9742
|
+
for (const [index, entry] of series.entries()) {
|
|
9743
|
+
if (index === 0) continue;
|
|
9744
|
+
if (entry.labels.length !== categories.length || entry.labels.some((label, i) => label !== categories[i])) {
|
|
9745
|
+
throw new Error(
|
|
9746
|
+
`Chart series "${entry.name ?? `series ${index}`}" at ${path4} has different labels from the first series. A chart has one category axis, so every series must name the same categories in the same order.`
|
|
9747
|
+
);
|
|
8736
9748
|
}
|
|
8737
|
-
blocks.push({
|
|
8738
|
-
kind: "paragraph",
|
|
8739
|
-
id: `${scope.id}:caption`,
|
|
8740
|
-
path: `${path4}.caption`,
|
|
8741
|
-
styleId: "Normal",
|
|
8742
|
-
// Captions default to left alignment, whatever the figure did.
|
|
8743
|
-
formatting: { alignment: "left" },
|
|
8744
|
-
// A caption reaches the parser only when it carries a decorator, which
|
|
8745
|
-
// is why a link in an otherwise plain caption stays literal.
|
|
8746
|
-
children: DECORATED.test(caption) ? parseInline(caption, {
|
|
8747
|
-
base: {},
|
|
8748
|
-
hyperlinks: true,
|
|
8749
|
-
resolvePlaceholder: placeholderResolver(ctx),
|
|
8750
|
-
resolveCrossReference: crossReferenceResolver(ctx, path4)
|
|
8751
|
-
}) : parseLiteral(caption, { base: {} })
|
|
8752
|
-
});
|
|
8753
9749
|
}
|
|
9750
|
+
const colors = Array.isArray(props.chartColors) ? props.chartColors.map(
|
|
9751
|
+
(color) => resolveColor(String(color), ctx.theme)
|
|
9752
|
+
) : DEFAULT_CHART_THEME_COLORS2.map((token) => {
|
|
9753
|
+
const value = ctx.theme?.colors?.[token];
|
|
9754
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
9755
|
+
try {
|
|
9756
|
+
return resolveColor(token, ctx.theme);
|
|
9757
|
+
} catch {
|
|
9758
|
+
return void 0;
|
|
9759
|
+
}
|
|
9760
|
+
}).filter((color) => color !== void 0);
|
|
9761
|
+
const page = getPageSetup(ctx.theme);
|
|
9762
|
+
const contentWidthInches = (page.size.width - page.margin.left - page.margin.right) / 1440;
|
|
9763
|
+
const widthInches = typeof props.width === "number" ? props.width : contentWidthInches;
|
|
9764
|
+
const heightInches = typeof props.height === "number" ? props.height : DEFAULT_CHART_HEIGHT_INCHES;
|
|
9765
|
+
const chart = {
|
|
9766
|
+
kind: "chart",
|
|
9767
|
+
chartType: props.type,
|
|
9768
|
+
series,
|
|
9769
|
+
colors,
|
|
9770
|
+
widthEmu: inchesToEmu(widthInches),
|
|
9771
|
+
heightEmu: inchesToEmu(heightInches),
|
|
9772
|
+
...typeof props.title === "string" ? { title: props.title } : {},
|
|
9773
|
+
...props.showTitle !== void 0 ? { showTitle: props.showTitle } : {},
|
|
9774
|
+
...props.showLegend !== void 0 ? { showLegend: props.showLegend } : {},
|
|
9775
|
+
...props.legendPos ? { legendPosition: props.legendPos } : {},
|
|
9776
|
+
...typeof props.catAxisTitle === "string" ? { categoryAxisTitle: props.catAxisTitle } : {},
|
|
9777
|
+
...typeof props.valAxisTitle === "string" ? { valueAxisTitle: props.valAxisTitle } : {},
|
|
9778
|
+
...typeof props.alt === "string" && props.alt ? { altText: props.alt } : {},
|
|
9779
|
+
...props.floating ? { floating: compileFloating(props.floating, ctx, path4) } : {}
|
|
9780
|
+
};
|
|
9781
|
+
ctx.features.require("charts", path4);
|
|
9782
|
+
if (props.floating) ctx.features.require("floating-images", path4);
|
|
9783
|
+
const spacing = {};
|
|
9784
|
+
if (props.spacing?.before !== void 0) {
|
|
9785
|
+
spacing.beforeTwips = pointsToTwips2(props.spacing.before);
|
|
9786
|
+
}
|
|
9787
|
+
if (props.spacing?.after !== void 0) {
|
|
9788
|
+
spacing.afterTwips = pointsToTwips2(props.spacing.after);
|
|
9789
|
+
}
|
|
9790
|
+
const blocks = [
|
|
9791
|
+
{
|
|
9792
|
+
kind: "paragraph",
|
|
9793
|
+
id: scope.id,
|
|
9794
|
+
path: path4,
|
|
9795
|
+
children: [chart],
|
|
9796
|
+
formatting: {
|
|
9797
|
+
// An anchored chart moves with its anchor, so aligning the paragraph
|
|
9798
|
+
// would move the anchor rather than the chart — the same reasoning as
|
|
9799
|
+
// a floating image.
|
|
9800
|
+
...props.floating ? {} : { alignment: compileAlignment(props.alignment) ?? "center" },
|
|
9801
|
+
...Object.keys(spacing).length > 0 ? { spacing } : {},
|
|
9802
|
+
...props.keepNext !== void 0 ? { keepNext: props.keepNext } : {},
|
|
9803
|
+
...props.keepLines !== void 0 ? { keepLines: props.keepLines } : {}
|
|
9804
|
+
}
|
|
9805
|
+
}
|
|
9806
|
+
];
|
|
9807
|
+
const caption = captionBlock(props.caption, scope, "chart");
|
|
9808
|
+
if (caption === void 0) return [];
|
|
9809
|
+
if (caption) blocks.push(caption);
|
|
8754
9810
|
return blocks;
|
|
8755
9811
|
}
|
|
9812
|
+
function captionBlock(value, scope, componentName) {
|
|
9813
|
+
if (!value) return null;
|
|
9814
|
+
const { ctx, path: path4 } = scope;
|
|
9815
|
+
const caption = String(value);
|
|
9816
|
+
const syntax = containsUnsupportedSyntax(caption);
|
|
9817
|
+
if (syntax) {
|
|
9818
|
+
ctx.unsupported.push({ name: componentName, path: path4, detail: syntax });
|
|
9819
|
+
return void 0;
|
|
9820
|
+
}
|
|
9821
|
+
return {
|
|
9822
|
+
kind: "paragraph",
|
|
9823
|
+
id: `${scope.id}:caption`,
|
|
9824
|
+
path: `${path4}.caption`,
|
|
9825
|
+
styleId: "Normal",
|
|
9826
|
+
// Captions default to left alignment, whatever the figure did.
|
|
9827
|
+
formatting: { alignment: "left" },
|
|
9828
|
+
// A caption reaches the parser only when it carries a decorator, which
|
|
9829
|
+
// is why a link in an otherwise plain caption stays literal.
|
|
9830
|
+
children: DECORATED.test(caption) ? parseInline(caption, {
|
|
9831
|
+
base: {},
|
|
9832
|
+
hyperlinks: true,
|
|
9833
|
+
resolvePlaceholder: placeholderResolver(ctx),
|
|
9834
|
+
resolveCrossReference: crossReferenceResolver(ctx, path4)
|
|
9835
|
+
}) : parseLiteral(caption, { base: {} })
|
|
9836
|
+
};
|
|
9837
|
+
}
|
|
8756
9838
|
function relativeFrom(value, axis) {
|
|
8757
9839
|
const allowed = axis === "horizontal" ? ["character", "column", "margin", "page"] : ["margin", "page", "paragraph", "line"];
|
|
8758
9840
|
return typeof value === "string" && allowed.includes(value) ? value : void 0;
|
|
@@ -9176,6 +10258,9 @@ function cellChildren(cell, baseStyle, scope, rowRevision) {
|
|
|
9176
10258
|
if (content.name === "image") {
|
|
9177
10259
|
return wrap(cellImage(content, base, ctx));
|
|
9178
10260
|
}
|
|
10261
|
+
if (content.name === "visual") {
|
|
10262
|
+
return wrap(cellVisual(content, scope));
|
|
10263
|
+
}
|
|
9179
10264
|
if (content.name !== "paragraph") {
|
|
9180
10265
|
return wrap([
|
|
9181
10266
|
{
|
|
@@ -9215,6 +10300,40 @@ function cellChildren(cell, baseStyle, scope, rowRevision) {
|
|
|
9215
10300
|
})
|
|
9216
10301
|
);
|
|
9217
10302
|
}
|
|
10303
|
+
function cellVisual(component, scope) {
|
|
10304
|
+
const { ctx, path: path4 } = scope;
|
|
10305
|
+
const props = component.props ?? {};
|
|
10306
|
+
if (!isNativeVisualProps4(props)) {
|
|
10307
|
+
ctx.unsupported.push({
|
|
10308
|
+
name: "visual",
|
|
10309
|
+
path: path4,
|
|
10310
|
+
detail: "a raster visual reached the compiler; it should have been rasterized during desugaring"
|
|
10311
|
+
});
|
|
10312
|
+
return [];
|
|
10313
|
+
}
|
|
10314
|
+
const group = compileNativeVisualGroup(
|
|
10315
|
+
props,
|
|
10316
|
+
nativeVisualDeps(ctx, path4)
|
|
10317
|
+
);
|
|
10318
|
+
if (!group) return [];
|
|
10319
|
+
const size = visualPlacementPixels(props, group.canvas, {
|
|
10320
|
+
widthPx: CELL_DRAWING_REFERENCE.width,
|
|
10321
|
+
heightPx: CELL_DRAWING_REFERENCE.height
|
|
10322
|
+
});
|
|
10323
|
+
ctx.features.require("drawing-groups", path4);
|
|
10324
|
+
return [
|
|
10325
|
+
{
|
|
10326
|
+
kind: "drawingGroup",
|
|
10327
|
+
widthEmu: pixelsToEmu(size.width),
|
|
10328
|
+
heightEmu: pixelsToEmu(size.height),
|
|
10329
|
+
canvasWidthEmu: group.canvas.widthEmu,
|
|
10330
|
+
canvasHeightEmu: group.canvas.heightEmu,
|
|
10331
|
+
children: group.children,
|
|
10332
|
+
...typeof props.alt === "string" && props.alt ? { altText: props.alt } : {}
|
|
10333
|
+
}
|
|
10334
|
+
];
|
|
10335
|
+
}
|
|
10336
|
+
var CELL_DRAWING_REFERENCE = { width: 300, height: 200 };
|
|
9218
10337
|
function cellImage(component, base, ctx) {
|
|
9219
10338
|
const props = component.props ?? {};
|
|
9220
10339
|
const source = resolveImageSource(props);
|
|
@@ -10330,6 +11449,9 @@ import {
|
|
|
10330
11449
|
clampVisualDpi as clampVisualDpi3,
|
|
10331
11450
|
DEFAULT_VISUAL_DPI as DEFAULT_VISUAL_DPI3
|
|
10332
11451
|
} from "@json-to-office/shared";
|
|
11452
|
+
import {
|
|
11453
|
+
isNativeVisualProps as isNativeVisualProps5
|
|
11454
|
+
} from "@json-to-office/shared-docx";
|
|
10333
11455
|
var DEFAULT_CONCURRENCY = 4;
|
|
10334
11456
|
async function flattenVisuals(doc, options) {
|
|
10335
11457
|
let rasterize = options.rasterize;
|
|
@@ -10375,8 +11497,10 @@ async function flattenVisuals(doc, options) {
|
|
|
10375
11497
|
doc,
|
|
10376
11498
|
async (node) => (
|
|
10377
11499
|
// A disabled visual is left as-is: it is filtered out at render anyway, so
|
|
10378
|
-
// rasterizing it would be wasted work.
|
|
10379
|
-
|
|
11500
|
+
// rasterizing it would be wasted work. A native one is left as-is for a
|
|
11501
|
+
// stronger reason — it is already service-free, and flattening exists to
|
|
11502
|
+
// remove the service dependency, not to trade editable objects for pixels.
|
|
11503
|
+
node.name === "visual" && node.enabled !== false && !isNativeVisualProps5(node.props) ? rasterizeVisual(node, ctx) : void 0
|
|
10380
11504
|
)
|
|
10381
11505
|
);
|
|
10382
11506
|
}
|