@openpresentation/opf-pptx 0.8.1 → 0.9.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/DEPENDENCY-NOTES.md +4 -0
- package/README.md +2 -2
- package/dist/color-ref.js +97 -0
- package/dist/index.js +39 -21
- package/package.json +4 -4
package/DEPENDENCY-NOTES.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# PptxGenJS runtime provenance
|
|
2
2
|
|
|
3
|
+
## Coordinated `@openpresentation/opf` pin
|
|
4
|
+
|
|
5
|
+
PPTX 0.9.0 depends on published [`@openpresentation/opf@^0.11.0`](https://www.npmjs.com/package/@openpresentation/opf) (reference layer: ColorRef, document `variables`, exported `resolveColorRef()`). CI checks out OPF [`b8a1faf`](https://github.com/OpenPresentation/opf/commit/b8a1faf24203635bd36b09ea19f44bcd53781f16) (`opf-v0.11.0`) for coordinated source linking. Named content colors resolve through core `resolveColorRef()`; eight-digit hex alpha stays a local export concern because core `normalizeHexColor` drops the alpha byte. Native `schemeClr` / theme `clrScheme` writes remain follow-up work.
|
|
6
|
+
|
|
3
7
|
OPF PPTX 0.5.1 ships the exact, unmodified PptxGenJS 4.0.1 ESM distribution in `vendor/pptxgenjs`, with its MIT license, upstream archive integrity and per-file SHA-256 hashes. Its actual JSZip dependency is declared directly. Ordinary npm installations therefore omit the unused image-size parser without requiring consumer overrides. This removes the affected dependency; it does not patch the parser. Published OPF PPTX 0.5.0 retains the older graph.
|
|
4
8
|
|
|
5
9
|
The inspected PptxGenJS 4.0.1 ESM/CommonJS distributions do not import `image-size`. The only sizing-module reference is in a commented, unused helper. OPF supplies image geometry itself. The image-fit follow-up reads bounded PNG/JPEG/GIF/WebP dimension headers and inline JPEG EXIF orientation; it does not load image-size or decode pixels.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OPF PPTX
|
|
2
2
|
|
|
3
|
-
Version 0.
|
|
3
|
+
Version 0.9.0 requires core 0.11.0, including ColorRef / `variables` on content colors. It hex-resolves named colors through core `resolveColorRef()` before PptxGenJS `srgbClr` export. Unrecognized run colors such as `color:'invalid'` still validate and fall back to the theme text color. Native DrawingML `schemeClr` and theme `clrScheme` writes, and native `p:hf` headers/footers, are not in this release. Import still flattens theme colors to hex. Metric, quote and timeline layout placeholders and the corrected text-bullet contract from 0.8.1 are retained.
|
|
4
4
|
|
|
5
5
|
Unfinished prepared shaping work is preserved in the [September 15 roadmap](docs/roadmap-shaping-20260915.md); it is not part of the published runtime.
|
|
6
6
|
|
|
@@ -72,7 +72,7 @@ The first exporter keeps the public API stable while using `pptxgenjs` internall
|
|
|
72
72
|
|
|
73
73
|
- `Slide.title`, `Slide.subtitle`, and `Slide.tag` become editable text boxes, not PowerPoint master placeholders.
|
|
74
74
|
- Root payloads, `blocks[]`, and promoted region keys become editable slide objects in deterministic regions. Promoted keys use the OPF 3x3 region vocabulary (`top`, `middle`, `bottom`, `left`, `center`, `right`).
|
|
75
|
-
- Text, lists, metrics, quotes, timelines, code, tables, and inline-data charts are emitted as editable PowerPoint text, table, and chart objects.
|
|
75
|
+
- Text, lists, metrics, quotes, timelines, code, tables, and inline-data charts are emitted as editable PowerPoint text, table, and chart objects. Content ColorRef values (hex, scheme slots/roles, and `var:<id>`) flatten to `a:srgbClr` via core `resolveColorRef()`; this is hex-resolve, not native `schemeClr` / theme write.
|
|
76
76
|
- Image assets are embedded only when supplied as data URIs, local paths, or host-resolved bytes/paths. Remote asset URLs are never fetched by the runtime path.
|
|
77
77
|
- ZIP entries, generated chart/workbook part names, core-property timestamps, and nested chart workbook timestamps are normalized for reproducible bytes.
|
|
78
78
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Export ColorRef through published core resolveColorRef(). Keep 8-digit hex
|
|
2
|
+
// alpha locally: normalizeHexColor() strips the AA byte, which PptxGenJS needs.
|
|
3
|
+
|
|
4
|
+
import { resolveColorRef as resolveCoreColorRef } from "@openpresentation/opf";
|
|
5
|
+
|
|
6
|
+
const UNRESOLVED = "";
|
|
7
|
+
|
|
8
|
+
function isPlainObject(value) {
|
|
9
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function withHash(value) {
|
|
13
|
+
if (typeof value !== "string") return undefined;
|
|
14
|
+
const trimmed = value.trim();
|
|
15
|
+
if (!trimmed) return undefined;
|
|
16
|
+
return trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function expandShorthand(hex) {
|
|
20
|
+
const raw = hex.replace(/^#/, "");
|
|
21
|
+
if (/^[0-9a-f]{3}$/i.test(raw)) return raw.split("").map((char) => char + char).join("");
|
|
22
|
+
return raw;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Literal #RGB / #RRGGBB / #RRGGBBAA, including pptx-internal hex without '#'. */
|
|
26
|
+
function literalExportHex(entry) {
|
|
27
|
+
if (typeof entry !== "string") return undefined;
|
|
28
|
+
const hashed = withHash(entry.trim());
|
|
29
|
+
if (!hashed) return undefined;
|
|
30
|
+
if (/^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(hashed)) {
|
|
31
|
+
return `#${expandShorthand(hashed).toUpperCase()}`;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Pass document variables through; core accepts hex shorthand or `{ type: "color", value }`. */
|
|
37
|
+
export function resolveVariableColors(variables) {
|
|
38
|
+
return isPlainObject(variables) ? variables : {};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function coreRoles(ctx) {
|
|
42
|
+
const colors = ctx.colors ?? {};
|
|
43
|
+
return {
|
|
44
|
+
background: withHash(colors.background),
|
|
45
|
+
surface: withHash(colors.surface),
|
|
46
|
+
text: withHash(colors.text),
|
|
47
|
+
textSecondary: withHash(colors.mutedText),
|
|
48
|
+
accent: withHash(colors.accent),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function coreOptions(ctx, fallback = UNRESOLVED) {
|
|
53
|
+
return {
|
|
54
|
+
colorScheme: ctx.colorScheme ?? {},
|
|
55
|
+
roles: coreRoles(ctx),
|
|
56
|
+
variables: ctx.variables ?? {},
|
|
57
|
+
fallback,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resolve a ColorRef or TextRun.color string to a #RRGGBB or #RRGGBBAA value.
|
|
63
|
+
* Unrecognized values return undefined so callers can apply the theme fallback.
|
|
64
|
+
*/
|
|
65
|
+
export function resolveColorRefValue(entry, ctx) {
|
|
66
|
+
if (entry === undefined || entry === null || entry === "") return undefined;
|
|
67
|
+
if (typeof entry !== "string") return undefined;
|
|
68
|
+
const trimmed = entry.trim();
|
|
69
|
+
const literal = literalExportHex(trimmed);
|
|
70
|
+
if (literal) return literal;
|
|
71
|
+
const resolved = resolveCoreColorRef(trimmed, coreOptions(ctx, UNRESOLVED));
|
|
72
|
+
return resolved || undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function colorContext(context, fallback) {
|
|
76
|
+
return {
|
|
77
|
+
colorScheme: context.colorScheme,
|
|
78
|
+
colors: context.colors,
|
|
79
|
+
variables: context.variables ?? {},
|
|
80
|
+
fallback: fallback ?? context.colors?.text ?? "000000",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** PptxGenJS export color: six or eight uppercase hex digits without '#'. */
|
|
85
|
+
export function resolveExportColor(entry, ctx) {
|
|
86
|
+
const fallback = ctx.fallback ?? ctx.colors?.text ?? "000000";
|
|
87
|
+
const resolved = resolveColorRefValue(entry, ctx);
|
|
88
|
+
const hex = resolved ?? withHash(fallback) ?? "#000000";
|
|
89
|
+
const raw = expandShorthand(hex.replace(/^#/, ""));
|
|
90
|
+
if (/^[0-9A-F]{6}([0-9A-F]{2})?$/i.test(raw)) return raw.toUpperCase();
|
|
91
|
+
return fallback.replace(/^#/, "").toUpperCase().slice(0, 6);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function exportColorAlpha(entry, ctx) {
|
|
95
|
+
const raw = resolveExportColor(entry, ctx);
|
|
96
|
+
return raw.length === 8 ? (1 - parseInt(raw.slice(6), 16) / 255) * 100 : 0;
|
|
97
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import {importBackground} from './background-import.js';
|
|
|
13
13
|
import { webpToPng } from '#image-fallback';
|
|
14
14
|
import { rasterMetadata, pictureTransform, normalizeImageOrientation } from './image-geometry.js';
|
|
15
15
|
import { layoutTable, composeSlide, fitText, fitRichText, textWidthMeasurer, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textColorForFill, chartColorForFill } from "@openpresentation/opf/composition";
|
|
16
|
+
import { colorContext, resolveExportColor, resolveVariableColors } from "./color-ref.js";
|
|
16
17
|
import PptxGenJS from "../vendor/pptxgenjs/pptxgen.es.js";
|
|
17
18
|
import { unzipSync, zipSync } from "fflate";
|
|
18
19
|
import { XMLParser } from "fast-xml-parser";
|
|
@@ -908,10 +909,15 @@ function resolvePresentationContext(presentation, options) {
|
|
|
908
909
|
accent: normalizeHex(colorScheme.primary ?? colorScheme.accent1 ?? "#2874A6"),
|
|
909
910
|
surface: normalizeHex(colorScheme.surface ?? (darkBackground ? colorScheme.dark2 : colorScheme.light2) ?? "#F8FAFC"),
|
|
910
911
|
border: normalizeHex(colorScheme.accent5 ?? "#CBD5E1")
|
|
911
|
-
}
|
|
912
|
+
},
|
|
913
|
+
variables: resolveVariableColors(presentation.variables)
|
|
912
914
|
};
|
|
913
915
|
}
|
|
914
916
|
|
|
917
|
+
function exportColor(entry, context, fallback) {
|
|
918
|
+
return resolveExportColor(entry, colorContext(context, fallback));
|
|
919
|
+
}
|
|
920
|
+
|
|
915
921
|
function configurePresentation(pptx, presentation, context) {
|
|
916
922
|
pptx.defineLayout({
|
|
917
923
|
name: context.layoutName,
|
|
@@ -977,7 +983,10 @@ async function addSlide(pptx, presentation, opfSlide, slideIndex, context, optio
|
|
|
977
983
|
} else if (item.field === "text" && item.text?.richLines) {
|
|
978
984
|
const alignment=item.text.placement?.alignment??opfSlide.design?.contentAlignment??presentation.design?.contentAlignment??'left';
|
|
979
985
|
for(const [index,line] of item.text.richLines.entries()){
|
|
980
|
-
const runs=line.fragments.map(fragment=>
|
|
986
|
+
const runs=line.fragments.map(fragment=>{
|
|
987
|
+
const runColor=exportColor(fragment.run.color,slideContext,slideContext.colors.text);
|
|
988
|
+
return {text:fragment.text,options:{...nativeFontOptions(fragment.style),fontSize:fragment.fontSize*.75,color:normalizeHex(runColor),underline:fragment.run.underline?{color:normalizeHex(runColor)}:undefined,strike:fragment.run.strikethrough?'sngStrike':undefined,baseline:fragment.baselineShift?-fragment.baselineShift/fragment.fontSize*2000:undefined,hyperlink:fragment.run.link&&/^(https?:|mailto:)/i.test(fragment.run.link)?{url:fragment.run.link}:undefined}};
|
|
989
|
+
});
|
|
981
990
|
const placed=item.text.placement?.lines[index],factor=alignment==='right'?1:alignment==='center'?.5:0;
|
|
982
991
|
const area=placed?{...region,x:(placed.x+line.width*factor-item.box.width*factor)/96,y:placed.y/96,h:placed.height/96}:{...region,y:region.y+line.y/96,h:line.height/96};
|
|
983
992
|
if(runs.length)slide.addText(runs,{...textBoxOptions(area,slideContext,item.text.fontSize*.75),align:alignment,fit:'none',wrap:false,lineSpacingMultiple:1});
|
|
@@ -999,7 +1008,7 @@ function resolveSlideContext(presentation, slide, baseContext, options) {
|
|
|
999
1008
|
|| Math.abs(resolved.dimensions.heightInches - baseContext.dimensions.heightInches) > 1e-6) {
|
|
1000
1009
|
throw new OPFPptxError("mixed-slide-dimensions", "PowerPoint requires one canvas size per presentation. Set dimensions on the deck or export this slide separately.");
|
|
1001
1010
|
}
|
|
1002
|
-
return { ...baseContext, backgroundDefinition: resolved.backgroundDefinition, colorScheme: resolved.colorScheme, fonts: resolved.fonts, colors: resolved.colors, imageFill: effective.design.imageFill ?? "fit" };
|
|
1011
|
+
return { ...baseContext, backgroundDefinition: resolved.backgroundDefinition, colorScheme: resolved.colorScheme, fonts: resolved.fonts, colors: resolved.colors, variables: resolved.variables, imageFill: effective.design.imageFill ?? "fit" };
|
|
1003
1012
|
}
|
|
1004
1013
|
|
|
1005
1014
|
function fieldToType(field) {
|
|
@@ -1066,8 +1075,12 @@ function addTextPayload(slide, value, region, context) {
|
|
|
1066
1075
|
slide.addText(stringifyText(value), textBoxOptions(region, context, 18));
|
|
1067
1076
|
}
|
|
1068
1077
|
|
|
1069
|
-
function richLineRuns(line,color) {
|
|
1070
|
-
|
|
1078
|
+
function richLineRuns(line,color,context) {
|
|
1079
|
+
const fallback=color.replace(/^#/,'');
|
|
1080
|
+
return line.fragments.map(fragment=>{
|
|
1081
|
+
const runColor=exportColor(fragment.run.color,context,fallback);
|
|
1082
|
+
return {text:fragment.text,options:{...nativeFontOptions(fragment.style),fontSize:fragment.fontSize*.75,color:normalizeHex(runColor),underline:fragment.run.underline?{color:normalizeHex(runColor)}:undefined,strike:fragment.run.strikethrough?'sngStrike':undefined,baseline:fragment.baselineShift?-fragment.baselineShift/fragment.fontSize*2000:undefined,hyperlink:fragment.run.link&&/^(https?:|mailto:)/i.test(fragment.run.link)?{url:fragment.run.link}:undefined}};
|
|
1083
|
+
});
|
|
1071
1084
|
}
|
|
1072
1085
|
function addMeasuredList(slide,fit,context) {
|
|
1073
1086
|
for(const entry of fit.listEntries){
|
|
@@ -1078,7 +1091,7 @@ function addMeasuredList(slide,fit,context) {
|
|
|
1078
1091
|
const objectName=first?`OPF list paragraph ${context.listMarkers.size+1}`:undefined;
|
|
1079
1092
|
if(first)context.listMarkers.set(objectName,{fontFamily:entry.marker.style.fontFamily,fontSize:entry.marker.fontSize*.75,color:normalizeHex(context.colors.text)});
|
|
1080
1093
|
const paragraph=first?{bullet:{characterCode:entry.marker.text.codePointAt(0).toString(16).padStart(4,'0'),indent:entry.marker.indent*.75},indentLevel:level}:{bullet:false};
|
|
1081
|
-
const runs=richLineRuns(line,color);
|
|
1094
|
+
const runs=richLineRuns(line,color,context);
|
|
1082
1095
|
if(!runs.length)runs.push({text:'',options:{}});
|
|
1083
1096
|
// Keep paragraph intent identical across runs. ZIP normalization below
|
|
1084
1097
|
// removes the duplicate paragraph-property nodes emitted by PptxGenJS.
|
|
@@ -1210,8 +1223,10 @@ function addTablePayload(slide, table, region, context, options, path) {
|
|
|
1210
1223
|
const rows = layout.rows.map(row => row.cells.map(cell => {
|
|
1211
1224
|
const {header,rich,fit} = cell;
|
|
1212
1225
|
const cellStyle = cell.style ?? {};
|
|
1213
|
-
const
|
|
1214
|
-
const
|
|
1226
|
+
const defaultFillHex = header ? context.colors.accent : context.colors.surface;
|
|
1227
|
+
const baseFill = exportColor(cellStyle.fill ?? defaultFillHex, context, defaultFillHex);
|
|
1228
|
+
const inheritedText = textColorForFill(`#${baseFill}`, header ? "#FFFFFF" : `#${context.colors.text}`);
|
|
1229
|
+
const baseColor = exportColor(cellStyle.color ?? inheritedText.replace(/^#/, ""), context, inheritedText.replace(/^#/, ""));
|
|
1215
1230
|
const alpha = value => value.length === 8 ? (1 - parseInt(value.slice(6), 16) / 255) * 100 : 0;
|
|
1216
1231
|
const text = stringifyText(cell.value), style = cell.textStyle;
|
|
1217
1232
|
const fragments = rich ? fit.richLines.flatMap(line => line.fragments) : [];
|
|
@@ -1219,7 +1234,7 @@ function addTablePayload(slide, table, region, context, options, path) {
|
|
|
1219
1234
|
const run = typeof value === 'string' ? {text:value} : value;
|
|
1220
1235
|
const fragment = fragments.find(item => item.runIndex === index);
|
|
1221
1236
|
const runStyle = fragment?.style ?? resolveTextStyle({...style,fontFamily:run.fontFamily ?? style.fontFamily,fontWeight:run.bold === undefined ? style.fontWeight : run.bold ? 700 : 400,italic:run.italic ?? style.italic}, options.textMeasurement);
|
|
1222
|
-
const rawColor =
|
|
1237
|
+
const rawColor = run.color !== undefined && run.color !== '' ? exportColor(run.color, context, baseColor) : baseColor;
|
|
1223
1238
|
const color = normalizeHex(rawColor), transparency = rawColor.length === 8 ? (1 - parseInt(rawColor.slice(6), 16) / 255) * 100 : 0;
|
|
1224
1239
|
const runOptions = {
|
|
1225
1240
|
...nativeFontOptions(runStyle),fontSize:fragment ? fragment.fontSize * .75 : fit.fontSize * .75,
|
|
@@ -1494,9 +1509,9 @@ function textRuns(value, context, fallbackFontSize) {
|
|
|
1494
1509
|
options: {
|
|
1495
1510
|
bold: run?.bold,
|
|
1496
1511
|
italic: run?.italic,
|
|
1497
|
-
underline: run?.underline ? { color: normalizeHex(run
|
|
1512
|
+
underline: run?.underline ? { color: normalizeHex(exportColor(run?.color, context, context.colors.text)) } : undefined,
|
|
1498
1513
|
strike: run?.strikethrough ? "sngStrike" : undefined,
|
|
1499
|
-
color: normalizeHex(run?.color
|
|
1514
|
+
color: normalizeHex(exportColor(run?.color, context, context.colors.text)),
|
|
1500
1515
|
fontFace: run?.fontFamily ?? context.fonts.body,
|
|
1501
1516
|
fontSize: run?.fontSize ?? fallbackFontSize,
|
|
1502
1517
|
superscript: run?.superscript,
|
|
@@ -1672,18 +1687,19 @@ function resolveDimensions(value) {
|
|
|
1672
1687
|
}
|
|
1673
1688
|
|
|
1674
1689
|
function resolveBackground(value, colorScheme) {
|
|
1690
|
+
const fallback = "FFFFFF";
|
|
1675
1691
|
if (typeof value === "string") {
|
|
1676
|
-
if (value.startsWith("#")) return normalizeHex(value);
|
|
1677
|
-
return normalizeHex(colorScheme[value] ?? colorScheme.background ?? colorScheme.light1 ?? "#FFFFFF");
|
|
1692
|
+
if (value.startsWith("#")) return normalizeHex(value, fallback);
|
|
1693
|
+
return normalizeHex(colorScheme[value] ?? colorScheme.background ?? colorScheme.light1 ?? "#FFFFFF", fallback);
|
|
1678
1694
|
}
|
|
1679
1695
|
if (isPlainObject(value)) {
|
|
1680
|
-
if (value.type === "solid" && value.color) return normalizeHex(value.color);
|
|
1696
|
+
if (value.type === "solid" && value.color) return normalizeHex(value.color, fallback);
|
|
1681
1697
|
if (value.type === "theme" && value.slot) {
|
|
1682
|
-
return normalizeHex(colorScheme[value.slot] ?? colorScheme.light1 ?? "#FFFFFF");
|
|
1698
|
+
return normalizeHex(colorScheme[value.slot] ?? colorScheme.light1 ?? "#FFFFFF", fallback);
|
|
1683
1699
|
}
|
|
1684
|
-
if (value.backgroundColor) return normalizeHex(value.backgroundColor);
|
|
1700
|
+
if (value.backgroundColor) return normalizeHex(value.backgroundColor, fallback);
|
|
1685
1701
|
}
|
|
1686
|
-
return normalizeHex(colorScheme.background ?? colorScheme.light1 ?? "#FFFFFF");
|
|
1702
|
+
return normalizeHex(colorScheme.background ?? colorScheme.light1 ?? "#FFFFFF", fallback);
|
|
1687
1703
|
}
|
|
1688
1704
|
|
|
1689
1705
|
function resolveFonts(fontScheme) {
|
|
@@ -1697,8 +1713,8 @@ function readableTextColor(background, colorScheme) {
|
|
|
1697
1713
|
: normalizeHex(colorScheme.text ?? colorScheme.dark1 ?? "#0F172A");
|
|
1698
1714
|
}
|
|
1699
1715
|
|
|
1700
|
-
function normalizeHex(value) {
|
|
1701
|
-
if (typeof value !== "string") return "
|
|
1716
|
+
function normalizeHex(value, fallback = "000000") {
|
|
1717
|
+
if (typeof value !== "string") return fallback.replace(/^#/, "").toUpperCase().slice(0, 6);
|
|
1702
1718
|
const raw = value.trim().replace(/^#/, "");
|
|
1703
1719
|
if (/^[0-9a-fA-F]{3}$/.test(raw)) {
|
|
1704
1720
|
return raw.split("").map((char) => char + char).join("").toUpperCase();
|
|
@@ -1706,7 +1722,7 @@ function normalizeHex(value) {
|
|
|
1706
1722
|
if (/^[0-9a-fA-F]{6,8}$/.test(raw)) {
|
|
1707
1723
|
return raw.slice(0, 6).toUpperCase();
|
|
1708
1724
|
}
|
|
1709
|
-
return
|
|
1725
|
+
return fallback.replace(/^#/, "").toUpperCase().slice(0, 6);
|
|
1710
1726
|
}
|
|
1711
1727
|
|
|
1712
1728
|
function isDarkHex(value) {
|
|
@@ -1895,7 +1911,9 @@ function normalizePartBytes(path, bytes, context, renameMaps, entries, imageMeta
|
|
|
1895
1911
|
for (const [edge, native] of [['left','lnL'],['right','lnR'],['top','lnT'],['bottom','lnB']]) {
|
|
1896
1912
|
const border = style.borders?.[edge];
|
|
1897
1913
|
if (!border) continue;
|
|
1898
|
-
const
|
|
1914
|
+
const borderHex = exportColor(border.color, context, context.colors.border);
|
|
1915
|
+
const borderOpacity = borderHex.length === 8 ? parseInt(borderHex.slice(6), 16) / 255 : 1;
|
|
1916
|
+
const fill = border.width === 0 ? '<a:noFill/>' : nativeBackgroundFill({type:'solid',color:'#' + borderHex.slice(0, 6), opacity: borderOpacity},{width:1,height:1}, context.colors.border);
|
|
1899
1917
|
const dash = {solid:'solid',dash:'dash',dot:'sysDot'}[border.dash ?? 'solid'];
|
|
1900
1918
|
const line = `<a:${native} w="${Math.round(border.width * table.scale * 9525)}" cap="flat" cmpd="sng" algn="ctr">${fill}<a:prstDash val="${dash}"/></a:${native}>`;
|
|
1901
1919
|
const existing = new RegExp(`<a:${native}\\b[^>]*>[\\s\\S]*?<\\/a:${native}>`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpresentation/opf-pptx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Pure local OPF to PPTX export and PPTX to OPF import tooling.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"sideEffects": false,
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "node scripts/build.mjs",
|
|
40
|
-
"typecheck": "node --check src/index.js && node --check scripts/build.mjs && node --check scripts/validate-package.mjs && node --check test/smoke.mjs && node --check src/image-geometry.js && node --check test/image-fit.mjs && node --check test/image-orientation.mjs && node --check test/table-layout.mjs && node --check test/table-import.mjs && node --check test/object-ids.mjs && node --check test/export-corpus.mjs && node --check test/image-media-type.mjs && node --check src/image-fallback-node.js && node --check src/image-fallback-browser.js && node --check test/webp-fallback.mjs && node --check scripts/build-browser-check.mjs && node --check test/webp-fallback-browser.js && node --check test/image-fallback-boundary.mjs && node --check src/background.js && node --check test/background.mjs && node --check src/image-import.js && node --check test/image-import.mjs && node --check test/native-background.mjs && node --check src/background-import.js && node --check test/background-inheritance.mjs && node --check test/rich-table.mjs && node --check src/table-import.js && node --check test/rich-table-import.mjs && node --check test/rich-table-import-browser.js && node --check test/table-row-sizing.mjs && node --check test/table-styles.mjs && node --check test/table-styles-browser.js && node --check test/styled-table.mjs && node --check src/table-cell-import.js && node --check test/styled-table-import.mjs && node --check test/styled-table-import-browser.js && node --check src/table-border-import.js && node --check test/table-border-styles.mjs && node --check src/card-provenance.js && node --check test/content-cards.mjs && node --check src/chart-workbook.js && node --check test/chart-colors.mjs && node --check test/chart-workbook.mjs && node --check test/native-chart-colors.mjs && node --check src/heading-provenance.js && node --check test/accepted-text.mjs && node --check test/heading-provenance.mjs && node --check src/text-provenance.js && node --check test/plain-whitespace.mjs && node --check src/timeline-provenance.js && node --check test/shared-timeline.mjs && node --check test/shared-furniture.mjs && node --check src/furniture-provenance.js && node --check test/furniture-provenance.mjs",
|
|
41
|
-
"test": "npm run build && npm run test:furniture && node test/plain-whitespace.mjs && node test/accepted-text.mjs && node test/heading-provenance.mjs && node test/chart-colors.mjs && node test/content-cards.mjs && node test/chart-workbook.mjs && node test/dependency-boundary.mjs && npm run build:browser-check && npm run test:styled-table && node test/content-layout.mjs && node test/shared-quote.mjs && npm run test:timeline",
|
|
40
|
+
"typecheck": "node --check src/index.js && node --check src/color-ref.js && node --check scripts/build.mjs && node --check scripts/validate-package.mjs && node --check test/smoke.mjs && node --check src/image-geometry.js && node --check test/image-fit.mjs && node --check test/image-orientation.mjs && node --check test/table-layout.mjs && node --check test/table-import.mjs && node --check test/object-ids.mjs && node --check test/export-corpus.mjs && node --check test/image-media-type.mjs && node --check src/image-fallback-node.js && node --check src/image-fallback-browser.js && node --check test/webp-fallback.mjs && node --check scripts/build-browser-check.mjs && node --check test/webp-fallback-browser.js && node --check test/image-fallback-boundary.mjs && node --check src/background.js && node --check test/background.mjs && node --check src/image-import.js && node --check test/image-import.mjs && node --check test/native-background.mjs && node --check src/background-import.js && node --check test/background-inheritance.mjs && node --check test/rich-table.mjs && node --check src/table-import.js && node --check test/rich-table-import.mjs && node --check test/rich-table-import-browser.js && node --check test/table-row-sizing.mjs && node --check test/table-styles.mjs && node --check test/table-styles-browser.js && node --check test/styled-table.mjs && node --check src/table-cell-import.js && node --check test/styled-table-import.mjs && node --check test/styled-table-import-browser.js && node --check src/table-border-import.js && node --check test/table-border-styles.mjs && node --check src/card-provenance.js && node --check test/content-cards.mjs && node --check src/chart-workbook.js && node --check test/chart-colors.mjs && node --check test/chart-workbook.mjs && node --check test/native-chart-colors.mjs && node --check src/heading-provenance.js && node --check test/accepted-text.mjs && node --check test/heading-provenance.mjs && node --check src/text-provenance.js && node --check test/plain-whitespace.mjs && node --check src/timeline-provenance.js && node --check test/shared-timeline.mjs && node --check test/shared-furniture.mjs && node --check src/furniture-provenance.js && node --check test/furniture-provenance.mjs",
|
|
41
|
+
"test": "npm run build && npm run test:furniture && node test/color-ref-export.mjs && node test/plain-whitespace.mjs && node test/accepted-text.mjs && node test/heading-provenance.mjs && node test/chart-colors.mjs && node test/content-cards.mjs && node test/chart-workbook.mjs && node test/dependency-boundary.mjs && npm run build:browser-check && npm run test:styled-table && node test/content-layout.mjs && node test/shared-quote.mjs && npm run test:timeline",
|
|
42
42
|
"validate": "node scripts/validate-package.mjs",
|
|
43
43
|
"test:packed": "node test/packed-install.mjs",
|
|
44
44
|
"test:compare-published": "node test/compare-published.mjs",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"test:furniture": "node test/shared-furniture.mjs && node test/furniture-provenance.mjs"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@openpresentation/opf": "^0.
|
|
56
|
+
"@openpresentation/opf": "^0.11.0",
|
|
57
57
|
"fast-xml-parser": "^5.8.0",
|
|
58
58
|
"fflate": "^0.8.3",
|
|
59
59
|
"jszip": "3.10.2",
|