Skip to content

Commit eaaf65e

Browse files
fix: add Preview support for Master Slide backgrounds (#640)
* Supports previewing of background images and colors from MasterSlide * Add support for Images on Master Slides * Moves Image processing into function
1 parent f599f99 commit eaaf65e

3 files changed

Lines changed: 188 additions & 112 deletions

File tree

src/index.spec.tsx

Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,55 @@ import * as React from "react";
22
import { render } from "./index";
33
import PPTX2Json from "pptx2json";
44
import fs = require("fs");
5-
import { Presentation, Slide, Shape, Text, Image, Line, MasterSlide } from "./nodes";
5+
import {
6+
Presentation,
7+
Slide,
8+
Shape,
9+
Text,
10+
Image,
11+
Line,
12+
MasterSlide,
13+
} from "./nodes";
614

715
describe("test render", () => {
816
it("ok", async () => {
917
// Test using a separate component for master slides
1018
const MasterSlides = () => {
1119
return (
12-
<MasterSlide name="MASTER_SLIDE">
13-
<Shape type="rect" style={{ x: 0, y: 5, w: "100%", h: 0.65, backgroundColor: '#003b75' }} />
20+
<MasterSlide
21+
name="MASTER_SLIDE"
22+
style={{
23+
backgroundImage: {
24+
kind: "path",
25+
path: "https://raw.githubusercontent.com/gitbrent/PptxGenJS/master/demos/common/images/starlabs_bkgd.jpg",
26+
},
27+
}}
28+
>
29+
<Shape
30+
type="rect"
31+
style={{
32+
x: 0,
33+
y: 5,
34+
w: "100%",
35+
h: 0.65,
36+
backgroundColor: "#003b75",
37+
}}
38+
/>
39+
<Image
40+
src={{
41+
kind: "path",
42+
path: "https://raw.githubusercontent.com/gitbrent/PptxGenJS/master/demos/common/images/tokyo-subway-route-map.jpg",
43+
}}
44+
style={{
45+
x: 0.2,
46+
y: 0.2,
47+
w: 3,
48+
h: 1.54,
49+
}}
50+
/>
1451
</MasterSlide>
15-
)
16-
}
52+
);
53+
};
1754

1855
const test = (
1956
<Presentation>
@@ -51,8 +88,8 @@ describe("test render", () => {
5188
x2={5}
5289
y2={4.5}
5390
style={{
54-
color: 'red',
55-
width: 2
91+
color: "red",
92+
width: 2,
5693
}}
5794
/>
5895
<Line
@@ -61,8 +98,8 @@ describe("test render", () => {
6198
x2={10}
6299
y2={3}
63100
style={{
64-
color: 'orange',
65-
width: 2
101+
color: "orange",
102+
width: 2,
66103
}}
67104
/>
68105
<Line
@@ -71,8 +108,8 @@ describe("test render", () => {
71108
x2={0}
72109
y2={3}
73110
style={{
74-
color: 'blue',
75-
width: 5
111+
color: "blue",
112+
width: 5,
76113
}}
77114
/>
78115
</Slide>
@@ -106,8 +143,7 @@ describe("test render", () => {
106143
<Image
107144
src={{
108145
kind: "path",
109-
path:
110-
"https://raw.githubusercontent.com/gitbrent/PptxGenJS/master/demos/common/images/tokyo-subway-route-map.jpg",
146+
path: "https://raw.githubusercontent.com/gitbrent/PptxGenJS/master/demos/common/images/tokyo-subway-route-map.jpg",
111147
}}
112148
style={{
113149
x: 0.2,
@@ -130,8 +166,7 @@ describe("test render", () => {
130166
<Image
131167
src={{
132168
kind: "path",
133-
path:
134-
"https://raw.githubusercontent.com/gitbrent/PptxGenJS/master/demos/common/images/tokyo-subway-route-map.jpg",
169+
path: "https://raw.githubusercontent.com/gitbrent/PptxGenJS/master/demos/common/images/tokyo-subway-route-map.jpg",
135170
}}
136171
style={{
137172
x: 0.2,
@@ -144,8 +179,7 @@ describe("test render", () => {
144179
<Image
145180
src={{
146181
kind: "data",
147-
data:
148-
"image/gif;base64,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",
182+
data: "image/gif;base64,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",
149183
}}
150184
style={{
151185
x: 6.4,
@@ -169,8 +203,7 @@ describe("test render", () => {
169203
<Image
170204
src={{
171205
kind: "path",
172-
path:
173-
"https://raw.githubusercontent.com/gitbrent/PptxGenJS/master/demos/common/images/tokyo-subway-route-map.jpg",
206+
path: "https://raw.githubusercontent.com/gitbrent/PptxGenJS/master/demos/common/images/tokyo-subway-route-map.jpg",
174207
}}
175208
style={{
176209
x: 3.3,
@@ -185,8 +218,7 @@ describe("test render", () => {
185218
style={{
186219
backgroundImage: {
187220
kind: "path",
188-
path:
189-
"https://raw.githubusercontent.com/gitbrent/PptxGenJS/master/demos/common/images/starlabs_bkgd.jpg",
221+
path: "https://raw.githubusercontent.com/gitbrent/PptxGenJS/master/demos/common/images/starlabs_bkgd.jpg",
190222
},
191223
}}
192224
>
@@ -205,19 +237,30 @@ describe("test render", () => {
205237
describe("e2e renders", () => {
206238
it("includes metadata tags", async () => {
207239
const json = await renderToJson(
208-
<Presentation author="Author"
240+
<Presentation
241+
author="Author"
209242
company="Company"
210243
revision="Revision"
211244
subject="Subject"
212245
title="Title"
213246
/>
214247
);
215-
expect(json['docProps/app.xml'].Properties.Company).toEqual(["Company"]);
216-
expect(json['docProps/core.xml']['cp:coreProperties']['dc:creator']).toEqual(["Author"]);
217-
expect(json['docProps/core.xml']['cp:coreProperties']['cp:lastModifiedBy']).toEqual(["Author"]);
218-
expect(json['docProps/core.xml']['cp:coreProperties']['dc:title']).toEqual(["Title"]);
219-
expect(json['docProps/core.xml']['cp:coreProperties']['dc:subject']).toEqual(["Subject"]);
220-
expect(json['docProps/core.xml']['cp:coreProperties']['cp:revision']).toEqual(["Revision"]);
248+
expect(json["docProps/app.xml"].Properties.Company).toEqual(["Company"]);
249+
expect(
250+
json["docProps/core.xml"]["cp:coreProperties"]["dc:creator"]
251+
).toEqual(["Author"]);
252+
expect(
253+
json["docProps/core.xml"]["cp:coreProperties"]["cp:lastModifiedBy"]
254+
).toEqual(["Author"]);
255+
expect(json["docProps/core.xml"]["cp:coreProperties"]["dc:title"]).toEqual([
256+
"Title",
257+
]);
258+
expect(
259+
json["docProps/core.xml"]["cp:coreProperties"]["dc:subject"]
260+
).toEqual(["Subject"]);
261+
expect(
262+
json["docProps/core.xml"]["cp:coreProperties"]["cp:revision"]
263+
).toEqual(["Revision"]);
221264
});
222265
});
223266

@@ -226,4 +269,4 @@ async function renderToJson(node: Parameters<typeof render>[0]) {
226269

227270
const pptx2json = new PPTX2Json();
228271
return await pptx2json.buffer2json(rendered);
229-
}
272+
}

src/preview/Preview.tsx

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,17 @@ const getTextStyleForPart = (
8989
margin = `0 ${pointsToPx(style.margin)}px`;
9090
}
9191
}
92-
92+
9393
let verticalAlign: "start" | "center" | "end" | undefined;
9494
switch (style.verticalAlign) {
9595
case "top":
96-
verticalAlign = "start"
96+
verticalAlign = "start";
9797
break;
9898
case "middle":
99-
verticalAlign = "center"
99+
verticalAlign = "center";
100100
break;
101101
case "bottom":
102-
verticalAlign = "end"
102+
verticalAlign = "end";
103103
break;
104104
}
105105

@@ -136,7 +136,9 @@ const getTextStyleForPart = (
136136
: undefined,
137137
transform: style.rotate ? `rotate(${style.rotate}deg)` : undefined,
138138
alignItems: verticalAlign,
139-
backgroundColor: style.backgroundColor ? normalizedColorToCSS(style.backgroundColor) : undefined
139+
backgroundColor: style.backgroundColor
140+
? normalizedColorToCSS(style.backgroundColor)
141+
: undefined,
140142
};
141143
};
142144

@@ -270,9 +272,7 @@ const constrainObjectFit = (
270272
};
271273

272274
const calculatePercentage = (value: any, total: number) =>
273-
typeof value === "number"
274-
? (value / total) * 100
275-
: parseInt(value, 10);
275+
typeof value === "number" ? (value / total) * 100 : parseInt(value, 10);
276276

277277
const SlideObjectPreview = ({
278278
object,
@@ -285,15 +285,15 @@ const SlideObjectPreview = ({
285285
slideWidth: number;
286286
drawBoundingBoxes: boolean;
287287
}) => {
288-
if (object.kind === 'line') {
288+
if (object.kind === "line") {
289289
const { x1, x2, y1, y2 } = object;
290290
const thickness = object.style.width ?? 1;
291291

292292
// from https://stackoverflow.com/a/8673281/13065068
293-
const length = Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
294-
const centerX = ((x1 + x2) / 2) - (length / 2);
295-
const centerY = ((y1 + y2) / 2);
296-
const angle = Math.atan2((y1 - y2), (x1 - x2)) * (180 / Math.PI);
293+
const length = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
294+
const centerX = (x1 + x2) / 2 - length / 2;
295+
const centerY = (y1 + y2) / 2;
296+
const angle = Math.atan2(y1 - y2, x1 - x2) * (180 / Math.PI);
297297

298298
return (
299299
<div
@@ -304,11 +304,12 @@ const SlideObjectPreview = ({
304304
width: `${calculatePercentage(length, dimensions[0])}%`,
305305
transform: `rotate(${angle}deg) translateY(${thickness / 2}px)`,
306306
height: thickness,
307-
backgroundColor: object.style.color ? normalizedColorToCSS(object.style.color) : undefined
307+
backgroundColor: object.style.color
308+
? normalizedColorToCSS(object.style.color)
309+
: undefined,
308310
}}
309-
>
310-
</div>
311-
)
311+
></div>
312+
);
312313
}
313314
const xPercentage = calculatePercentage(object.style.x, dimensions[0]);
314315
const yPercentage = calculatePercentage(object.style.y, dimensions[1]);
@@ -399,19 +400,24 @@ const SlidePreview = ({
399400
}) => {
400401
const ref = React.useRef<HTMLDivElement>(null);
401402
const { width } = useResize(ref);
403+
404+
const backgroundColor = slide.backgroundColor ?? masterSlide?.backgroundColor;
405+
const backgroundImage = slide.backgroundImage ?? masterSlide?.backgroundImage;
406+
402407
return (
403408
<div
404409
ref={ref}
405410
style={{
406411
width: "100%",
407412
height: width / (dimensions[0] / dimensions[1]),
408-
backgroundColor: slide.backgroundColor
409-
? normalizedColorToCSS(slide.backgroundColor)
413+
backgroundColor: backgroundColor
414+
? normalizedColorToCSS(backgroundColor)
410415
: "white",
411416
backgroundImage:
412-
slide.backgroundImage && slide.backgroundImage?.kind === "path"
413-
? `url("${slide.backgroundImage.path}")`
414-
: `url("data:${slide.backgroundImage?.data}")`,
417+
backgroundImage && backgroundImage?.kind === "path"
418+
? `url("${backgroundImage.path}")`
419+
: `url("data:${backgroundImage?.data}")`,
420+
backgroundSize: "contain",
415421
position: "relative",
416422
...slideStyle,
417423
}}
@@ -462,7 +468,10 @@ const Preview = (props: {
462468
<SlidePreview
463469
key={i}
464470
slide={slide}
465-
masterSlide={(slide.masterName && normalized.masterSlides[slide.masterName]) || undefined}
471+
masterSlide={
472+
(slide.masterName && normalized.masterSlides[slide.masterName]) ||
473+
undefined
474+
}
466475
dimensions={layoutToInches(normalized.layout)}
467476
slideStyle={props.slideStyle}
468477
drawBoundingBoxes={!!props.drawBoundingBoxes}

0 commit comments

Comments
 (0)