@textcortex/slidewise 1.8.0 → 1.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/dist/index.mjs +6121 -5250
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/editor/ElementView.tsx +97 -9
- package/src/lib/pptx/pptxToDeck.ts +1622 -126
- package/src/lib/types.ts +52 -0
package/package.json
CHANGED
|
@@ -65,12 +65,17 @@ function TextView({
|
|
|
65
65
|
: el.vAlign === "middle"
|
|
66
66
|
? "center"
|
|
67
67
|
: "flex-end",
|
|
68
|
+
background: el.background,
|
|
69
|
+
padding: el.padding
|
|
70
|
+
? `${el.padding.t}px ${el.padding.r}px ${el.padding.b}px ${el.padding.l}px`
|
|
71
|
+
: undefined,
|
|
72
|
+
boxSizing: el.padding ? "border-box" : undefined,
|
|
68
73
|
cursor: editing ? "text" : "inherit",
|
|
69
74
|
};
|
|
70
75
|
const inner: React.CSSProperties = {
|
|
71
76
|
width: "100%",
|
|
72
77
|
color: el.color,
|
|
73
|
-
fontFamily: el.fontFamily,
|
|
78
|
+
fontFamily: withGenericFallback(el.fontFamily),
|
|
74
79
|
fontSize: el.fontSize,
|
|
75
80
|
fontWeight: el.fontWeight,
|
|
76
81
|
fontStyle: el.italic ? "italic" : "normal",
|
|
@@ -85,11 +90,40 @@ function TextView({
|
|
|
85
90
|
outline: "none",
|
|
86
91
|
};
|
|
87
92
|
|
|
93
|
+
const backingPath = el.backingPath;
|
|
94
|
+
const positionedOuter: React.CSSProperties = backingPath
|
|
95
|
+
? { ...outer, position: "relative" }
|
|
96
|
+
: outer;
|
|
97
|
+
const innerStacked: React.CSSProperties = backingPath
|
|
98
|
+
? { ...inner, position: "relative", zIndex: 1 }
|
|
99
|
+
: inner;
|
|
100
|
+
const backingSvg = backingPath ? (
|
|
101
|
+
<svg
|
|
102
|
+
viewBox={`0 0 ${backingPath.viewW} ${backingPath.viewH}`}
|
|
103
|
+
preserveAspectRatio="none"
|
|
104
|
+
style={{
|
|
105
|
+
position: "absolute",
|
|
106
|
+
inset: 0,
|
|
107
|
+
width: "100%",
|
|
108
|
+
height: "100%",
|
|
109
|
+
pointerEvents: "none",
|
|
110
|
+
zIndex: 0,
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<path
|
|
114
|
+
d={backingPath.d}
|
|
115
|
+
fill={backingPath.fill}
|
|
116
|
+
fillRule={backingPath.fillRule ?? "nonzero"}
|
|
117
|
+
/>
|
|
118
|
+
</svg>
|
|
119
|
+
) : null;
|
|
120
|
+
|
|
88
121
|
if (editing) {
|
|
89
122
|
return (
|
|
90
|
-
<div style={
|
|
123
|
+
<div style={positionedOuter}>
|
|
124
|
+
{backingSvg}
|
|
91
125
|
<EditableText
|
|
92
|
-
style={
|
|
126
|
+
style={innerStacked}
|
|
93
127
|
initialText={el.text}
|
|
94
128
|
initialRuns={el.runs}
|
|
95
129
|
onCommit={(t, r) => onCommit?.(t, r)}
|
|
@@ -100,8 +134,9 @@ function TextView({
|
|
|
100
134
|
|
|
101
135
|
if (el.runs && el.runs.length) {
|
|
102
136
|
return (
|
|
103
|
-
<div style={
|
|
104
|
-
|
|
137
|
+
<div style={positionedOuter}>
|
|
138
|
+
{backingSvg}
|
|
139
|
+
<div style={innerStacked}>
|
|
105
140
|
{el.runs.map((r, i) => (
|
|
106
141
|
<span key={i} style={runCssStyle(r)}>
|
|
107
142
|
{r.text}
|
|
@@ -113,15 +148,40 @@ function TextView({
|
|
|
113
148
|
}
|
|
114
149
|
|
|
115
150
|
return (
|
|
116
|
-
<div style={
|
|
117
|
-
|
|
151
|
+
<div style={positionedOuter}>
|
|
152
|
+
{backingSvg}
|
|
153
|
+
<div style={innerStacked}>{el.text}</div>
|
|
118
154
|
</div>
|
|
119
155
|
);
|
|
120
156
|
}
|
|
121
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Append a `sans-serif` generic so brand families imported from PPTX
|
|
160
|
+
* (e.g. "EON Office Head") degrade gracefully when the typeface isn't
|
|
161
|
+
* installed locally — without the generic the browser silently picks
|
|
162
|
+
* its default serif. Already-qualified stacks (containing a comma) and
|
|
163
|
+
* plain generics ("serif"/"monospace") pass through untouched.
|
|
164
|
+
*/
|
|
165
|
+
function withGenericFallback(family: string | undefined): string | undefined {
|
|
166
|
+
if (!family) return family;
|
|
167
|
+
if (family.includes(",")) return family;
|
|
168
|
+
const lower = family.trim().toLowerCase();
|
|
169
|
+
if (
|
|
170
|
+
lower === "serif" ||
|
|
171
|
+
lower === "sans-serif" ||
|
|
172
|
+
lower === "monospace" ||
|
|
173
|
+
lower === "cursive" ||
|
|
174
|
+
lower === "fantasy" ||
|
|
175
|
+
lower === "system-ui"
|
|
176
|
+
) {
|
|
177
|
+
return family;
|
|
178
|
+
}
|
|
179
|
+
return `${family}, sans-serif`;
|
|
180
|
+
}
|
|
181
|
+
|
|
122
182
|
function runCssStyle(r: TextRun): React.CSSProperties {
|
|
123
183
|
const s: React.CSSProperties = {};
|
|
124
|
-
if (r.fontFamily) s.fontFamily = r.fontFamily;
|
|
184
|
+
if (r.fontFamily) s.fontFamily = withGenericFallback(r.fontFamily);
|
|
125
185
|
if (r.fontSize) s.fontSize = r.fontSize;
|
|
126
186
|
if (r.fontWeight) s.fontWeight = r.fontWeight;
|
|
127
187
|
if (r.color) s.color = r.color;
|
|
@@ -315,6 +375,27 @@ function sameStyle(a: TextRun, b: TextRun): boolean {
|
|
|
315
375
|
function ShapeView({ el }: { el: ShapeElement }) {
|
|
316
376
|
const stroke = el.stroke ?? "transparent";
|
|
317
377
|
const sw = el.strokeWidth ?? 0;
|
|
378
|
+
// Custom vector path (PPTX <a:custGeom>) takes precedence over the preset
|
|
379
|
+
// kind — the path coordinates already encode the actual silhouette.
|
|
380
|
+
if (el.path) {
|
|
381
|
+
return (
|
|
382
|
+
<svg
|
|
383
|
+
viewBox={`0 0 ${el.path.viewW} ${el.path.viewH}`}
|
|
384
|
+
preserveAspectRatio="none"
|
|
385
|
+
width="100%"
|
|
386
|
+
height="100%"
|
|
387
|
+
>
|
|
388
|
+
<path
|
|
389
|
+
d={el.path.d}
|
|
390
|
+
fill={el.fill}
|
|
391
|
+
fillRule={el.path.fillRule ?? "nonzero"}
|
|
392
|
+
stroke={stroke}
|
|
393
|
+
strokeWidth={sw || undefined}
|
|
394
|
+
vectorEffect="non-scaling-stroke"
|
|
395
|
+
/>
|
|
396
|
+
</svg>
|
|
397
|
+
);
|
|
398
|
+
}
|
|
318
399
|
if (el.shape === "rect" || el.shape === "rounded") {
|
|
319
400
|
return (
|
|
320
401
|
<div
|
|
@@ -474,7 +555,9 @@ function LineView({ el }: { el: LineElement }) {
|
|
|
474
555
|
function TableView({ el }: { el: TableElement }) {
|
|
475
556
|
const cols = el.rows[0]?.length ?? 1;
|
|
476
557
|
// PPTX-faithful: contiguous cells, no inter-cell gap, no rounded corners.
|
|
477
|
-
//
|
|
558
|
+
// Cells share their dividers via inset box-shadows so we draw a single
|
|
559
|
+
// grid line between adjacent cells instead of doubling-up borders.
|
|
560
|
+
const stroke = el.borderColor ?? "rgba(0, 0, 0, 0.12)";
|
|
478
561
|
return (
|
|
479
562
|
<div
|
|
480
563
|
style={{
|
|
@@ -485,6 +568,7 @@ function TableView({ el }: { el: TableElement }) {
|
|
|
485
568
|
height: "100%",
|
|
486
569
|
gap: 0,
|
|
487
570
|
background: "transparent",
|
|
571
|
+
boxShadow: `inset 0 0 0 1px ${stroke}`,
|
|
488
572
|
}}
|
|
489
573
|
>
|
|
490
574
|
{el.rows.flatMap((row, ri) =>
|
|
@@ -504,6 +588,10 @@ function TableView({ el }: { el: TableElement }) {
|
|
|
504
588
|
minHeight: 0,
|
|
505
589
|
overflow: "hidden",
|
|
506
590
|
wordBreak: "break-word",
|
|
591
|
+
borderRight:
|
|
592
|
+
ci < cols - 1 ? `1px solid ${stroke}` : undefined,
|
|
593
|
+
borderBottom:
|
|
594
|
+
ri < el.rows.length - 1 ? `1px solid ${stroke}` : undefined,
|
|
507
595
|
}}
|
|
508
596
|
>
|
|
509
597
|
{cell}
|