Skip to content

Commit 531acf3

Browse files
author
Luke Vella
committed
Add support for groups
1 parent 339f56d commit 531acf3

File tree

9 files changed

+13367
-6065
lines changed

9 files changed

+13367
-6065
lines changed

package-lock.json

Lines changed: 13044 additions & 5971 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core-enums.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,7 @@ export enum MASTER_OBJECTS {
720720

721721
export enum SLIDE_OBJECT_TYPES {
722722
'chart' = 'chart',
723+
'group' = 'group',
723724
'hyperlink' = 'hyperlink',
724725
'image' = 'image',
725726
'media' = 'media',

src/core-interfaces.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* PptxGenJS Interfaces
33
*/
44

5+
import { Group } from './group'
56
import { CHART_NAME, PLACEHOLDER_TYPES, SHAPE_NAME, SLIDE_OBJECT_TYPES, TEXT_HALIGN, TEXT_VALIGN, WRITE_OUTPUT_TYPE } from './core-enums'
67

78
// Core Types
@@ -1316,6 +1317,7 @@ export interface ISlideObject {
13161317
mtype?: MediaType
13171318
mediaRid?: number
13181319
shape?: SHAPE_NAME
1320+
group?: Group
13191321
}
13201322
// PRIVATE ^^^
13211323

@@ -1396,7 +1398,12 @@ export interface ObjectOptions extends ImageProps, PositionProps, ShapeProps, Ta
13961398
colW?: number | number[] // table
13971399
rowH?: number | number[] // table
13981400
}
1399-
export interface SlideBaseProps {
1401+
1402+
// A Container is an object that holds slide objects. i.e. a Slide or Group
1403+
export interface Container {
1404+
_slideObjects?: ISlideObject[]
1405+
}
1406+
export interface SlideBaseProps extends Container {
14001407
_bkgdImgRid?: number
14011408
_margin?: Margin
14021409
_name?: string
@@ -1406,7 +1413,6 @@ export interface SlideBaseProps {
14061413
_relsMedia: ISlideRelMedia[] // needed as we use args:"PresSlide|SlideLayout" often
14071414
_slideNum: number
14081415
_slideNumberProps?: SlideNumberProps
1409-
_slideObjects?: ISlideObject[]
14101416

14111417
background?: BackgroundProps
14121418
/**
@@ -1434,6 +1440,7 @@ export interface PresSlide extends SlideBaseProps {
14341440
addShape: Function
14351441
addTable: Function
14361442
addText: Function
1443+
addGroup: Function
14371444

14381445
/**
14391446
* Background color or image (`fill` | `path` | `data`)

src/gen-objects.ts

Lines changed: 50 additions & 48 deletions
Large diffs are not rendered by default.

src/gen-xml.ts

Lines changed: 82 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -78,44 +78,19 @@ let imageSizingXml = {
7878
}
7979

8080
/**
81-
* Transforms a slide or slideLayout to resulting XML string - Creates `ppt/slide*.xml`
82-
* @param {PresSlide|SlideLayout} slideObject - slide object created within createSlideObject
83-
* @return {string} XML string with <p:cSld> as the root
81+
* Transforms a list of slide objects to an XML string and returns the position and size values for their container.
82+
* @param {ISlideObject[]} slideItemObjs
83+
* @param {PresSlide|SlideLayout} slide
84+
* @returns
8485
*/
85-
function slideObjectToXml(slide: PresSlide | SlideLayout): string {
86-
let strSlideXml: string = slide._name ? '<p:cSld name="' + slide._name + '">' : '<p:cSld>'
87-
let intTableNum: number = 1
88-
89-
// STEP 1: Add background
90-
if (slide.bkgd) {
91-
strSlideXml += genXmlColorSelection(null, slide.bkgd)
92-
} else if (!slide.bkgd && slide._name && slide._name === DEF_PRES_LAYOUT_NAME) {
93-
// NOTE: Default [white] background is needed on slideMaster1.xml to avoid gray background in Keynote (and Finder previews)
94-
strSlideXml += '<p:bg><p:bgRef idx="1001"><a:schemeClr val="bg1"/></p:bgRef></p:bg>'
95-
}
96-
97-
// STEP 2: Add background image (using Strech) (if any)
98-
if (slide._bkgdImgRid) {
99-
// FIXME: We should be doing this in the slideLayout...
100-
strSlideXml +=
101-
'<p:bg>' +
102-
'<p:bgPr><a:blipFill dpi="0" rotWithShape="1">' +
103-
'<a:blip r:embed="rId' +
104-
slide._bkgdImgRid +
105-
'"><a:lum/></a:blip>' +
106-
'<a:srcRect/><a:stretch><a:fillRect/></a:stretch></a:blipFill>' +
107-
'<a:effectLst/></p:bgPr>' +
108-
'</p:bg>'
109-
}
110-
111-
// STEP 3: Continue slide by starting spTree node
112-
strSlideXml += '<p:spTree>'
113-
strSlideXml += '<p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>'
114-
strSlideXml += '<p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/>'
115-
strSlideXml += '<a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr>'
116-
117-
// STEP 4: Loop over all Slide.data objects and add them to this slide
118-
slide._slideObjects.forEach((slideItemObj: ISlideObject, idx: number) => {
86+
function slideItemObjsToXml(slideItemObjs: ISlideObject[], slide: PresSlide | SlideLayout): { str: string, containerX: number, containerY: number, containerCx: number, containerCy: number } {
87+
let strSlideXml = ''
88+
let intTableNum = 1
89+
let containerX = Infinity
90+
let containerY = Infinity
91+
let containerCx = 0
92+
let containerCy = 0
93+
slideItemObjs.forEach((slideItemObj: ISlideObject, idx: number) => {
11994
let x = 0,
12095
y = 0,
12196
cx = getSmartParseNumber('75%', 'X', slide._presLayout),
@@ -142,6 +117,11 @@ function slideObjectToXml(slide: PresSlide | SlideLayout): string {
142117
if (typeof slideItemObj.options.w !== 'undefined') cx = getSmartParseNumber(slideItemObj.options.w, 'X', slide._presLayout)
143118
if (typeof slideItemObj.options.h !== 'undefined') cy = getSmartParseNumber(slideItemObj.options.h, 'Y', slide._presLayout)
144119

120+
containerX = Math.min(containerX, x)
121+
containerY = Math.min(containerY, y)
122+
containerCx = Math.max(containerCx, cx)
123+
containerCy = Math.max(containerCy, cy)
124+
145125
// If using a placeholder then inherit it's position
146126
if (placeholderObj) {
147127
if (placeholderObj.options.x || placeholderObj.options.x === 0) x = getSmartParseNumber(placeholderObj.options.x, 'X', slide._presLayout)
@@ -660,12 +640,77 @@ function slideObjectToXml(slide: PresSlide | SlideLayout): string {
660640
strSlideXml += '</p:graphicFrame>'
661641
break
662642

643+
case SLIDE_OBJECT_TYPES.group:
644+
if (slideItemObj.group._slideObjects.length > 0) {
645+
const {
646+
str: slideItemsXml,
647+
containerX: x,
648+
containerY: y,
649+
containerCx: cx,
650+
containerCy: cy
651+
} = slideItemObjsToXml(slideItemObj.group._slideObjects, slide)
652+
strSlideXml += '<p:grpSp>'
653+
strSlideXml += `<p:nvGrpSpPr><p:cNvPr id="${idx + 1}" name="Group"/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>`
654+
strSlideXml += `<p:grpSpPr><a:xfrm><a:off x="${x}" y="${y}"/><a:ext cx="${cx}" cy="${cy}"/>`
655+
strSlideXml += `<a:chOff x="${x}" y="${y}"/><a:chExt cx="${cx}" cy="${cy}"/></a:xfrm></p:grpSpPr>`
656+
strSlideXml += slideItemsXml
657+
strSlideXml += "</p:grpSp>"
658+
}
659+
break
660+
663661
default:
664662
strSlideXml += ''
665663
break
666664
}
667665
})
666+
return {
667+
str: strSlideXml,
668+
containerX,
669+
containerY,
670+
containerCx,
671+
containerCy,
672+
}
673+
}
674+
675+
/**
676+
* Transforms a slide or slideLayout to resulting XML string - Creates `ppt/slide*.xml`
677+
* @param {PresSlide|SlideLayout} slideObject - slide object created within createSlideObject
678+
* @return {string} XML string with <p:cSld> as the root
679+
*/
680+
function slideObjectToXml(slide: PresSlide | SlideLayout): string {
681+
let strSlideXml: string = slide._name ? '<p:cSld name="' + slide._name + '">' : '<p:cSld>'
668682

683+
// STEP 1: Add background
684+
if (slide.bkgd) {
685+
strSlideXml += genXmlColorSelection(null, slide.bkgd)
686+
} else if (!slide.bkgd && slide._name && slide._name === DEF_PRES_LAYOUT_NAME) {
687+
// NOTE: Default [white] background is needed on slideMaster1.xml to avoid gray background in Keynote (and Finder previews)
688+
strSlideXml += '<p:bg><p:bgRef idx="1001"><a:schemeClr val="bg1"/></p:bgRef></p:bg>'
689+
}
690+
691+
// STEP 2: Add background image (using Strech) (if any)
692+
if (slide._bkgdImgRid) {
693+
// FIXME: We should be doing this in the slideLayout...
694+
strSlideXml +=
695+
'<p:bg>' +
696+
'<p:bgPr><a:blipFill dpi="0" rotWithShape="1">' +
697+
'<a:blip r:embed="rId' +
698+
slide._bkgdImgRid +
699+
'"><a:lum/></a:blip>' +
700+
'<a:srcRect/><a:stretch><a:fillRect/></a:stretch></a:blipFill>' +
701+
'<a:effectLst/></p:bgPr>' +
702+
'</p:bg>'
703+
}
704+
705+
// STEP 3: Continue slide by starting spTree node
706+
strSlideXml += '<p:spTree>'
707+
strSlideXml += '<p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>'
708+
strSlideXml += '<p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/>'
709+
strSlideXml += '<a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr>'
710+
711+
// STEP 4: Loop over all Slide.data objects and add them to this slide
712+
const { str } = slideItemObjsToXml(slide._slideObjects, slide)
713+
strSlideXml += str
669714
// STEP 5: Add slide numbers (if any) last
670715
if (slide._slideNumberProps) {
671716
strSlideXml +=

src/group.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { CHART_NAME, SHAPE_NAME, SLIDE_OBJECT_TYPES } from "./core-enums";
2+
import { IChartMulti, IChartOpts, IChartOptsLib, ImageProps, ISlideObject, MediaProps, PresSlide, ShapeProps, TableProps, TableRow, TextProps, TextPropsOptions } from "./core-interfaces";
3+
import * as genObj from './gen-objects'
4+
5+
export class Group {
6+
public _slideObjects: ISlideObject[]
7+
public _slide: PresSlide
8+
public addSlide: Function
9+
public getSlide: Function
10+
11+
constructor(params: {
12+
slide: PresSlide
13+
addSlide: Function
14+
getSlide
15+
}) {
16+
this._slideObjects = []
17+
this._slide = params.slide
18+
this.addSlide = params.addSlide
19+
this.getSlide = params.getSlide
20+
}
21+
22+
/**
23+
* Add chart to Slide
24+
* @param {CHART_NAME|IChartMulti[]} type - chart type
25+
* @param {object[]} data - data object
26+
* @param {IChartOpts} options - chart options
27+
* @return {Slide} this Slide
28+
*/
29+
addChart(type: CHART_NAME | IChartMulti[], data: any[], options?: IChartOpts): Group {
30+
// FUTURE: TODO-VERSION-4: Remove first arg - only take data and opts, with "type" required on opts
31+
// Set `_type` on IChartOptsLib as its what is used as object is passed around
32+
let optionsWithType: IChartOptsLib = options || {}
33+
optionsWithType._type = type
34+
genObj.addChartDefinition(this, this._slide, type, data, options)
35+
return this
36+
}
37+
38+
/**
39+
* Add image to Slide
40+
* @param {ImageProps} options - image options
41+
* @return {Slide} this Slide
42+
*/
43+
addImage(options: ImageProps): Group {
44+
genObj.addImageDefinition(this, this._slide, options)
45+
return this
46+
}
47+
48+
/**
49+
* Add media (audio/video) to Slide
50+
* @param {MediaProps} options - media options
51+
* @return {Slide} this Slide
52+
*/
53+
addMedia(options: MediaProps): Group {
54+
genObj.addMediaDefinition(this, this._slide, options)
55+
return this
56+
}
57+
58+
/**
59+
* Add shape to Slide
60+
* @param {SHAPE_NAME} shapeName - shape name
61+
* @param {ShapeProps} options - shape options
62+
* @return {Slide} this Slide
63+
*/
64+
addShape(shapeName: SHAPE_NAME, options?: ShapeProps): Group {
65+
// NOTE: As of v3.1.0, <script> users are passing the old shape object from the shapes file (orig to the project)
66+
// But React/TypeScript users are passing the shapeName from an enum, which is a simple string, so lets cast
67+
// <script./> => `pptx.shapes.RECTANGLE` [string] "rect" ... shapeName['name'] = 'rect'
68+
// TypeScript => `pptxgen.shapes.RECTANGLE` [string] "rect" ... shapeName = 'rect'
69+
//let shapeNameDecode = typeof shapeName === 'object' && shapeName['name'] ? shapeName['name'] : shapeName
70+
genObj.addShapeDefinition(this, this._slide, shapeName, options)
71+
return this
72+
}
73+
74+
/**
75+
* Add table to Slide
76+
* @param {TableRow[]} tableRows - table rows
77+
* @param {TableProps} options - table options
78+
* @return {Slide} this Slide
79+
*/
80+
addTable(tableRows: TableRow[], options?: TableProps): Group {
81+
// FUTURE: we pass `this` - we dont need to pass layouts - they can be read from this!
82+
genObj.addTableDefinition(this, this._slide, tableRows, options, this._slide._slideLayout, this._slide._presLayout, this.addSlide, this.getSlide)
83+
return this
84+
}
85+
86+
/**
87+
* Add text to Slide
88+
* @param {string|TextProps[]} text - text string or complex object
89+
* @param {TextPropsOptions} options - text options
90+
* @return {Slide} this Slide
91+
*/
92+
addText(text: string | TextProps[], options?: TextPropsOptions): Group {
93+
let textParam = typeof text === 'string' || typeof text === 'number' ? [{ text: text, options: options } as TextProps] : text
94+
genObj.addTextDefinition(this, this._slide, textParam, options, false)
95+
return this
96+
}
97+
98+
addGroup(): Group {
99+
const group = new Group({
100+
slide: this._slide,
101+
addSlide: this.addSlide,
102+
getSlide: this.getSlide
103+
})
104+
this._slideObjects.push({
105+
_type: SLIDE_OBJECT_TYPES.group,
106+
group
107+
})
108+
return group
109+
}
110+
}

src/pptxgen.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ export default class PptxGenJS implements IPresentationProps {
333333
addShape: null,
334334
addTable: null,
335335
addText: null,
336+
addGroup: null,
336337
//
337338
_name: null,
338339
_presLayout: this._presLayout,

0 commit comments

Comments
 (0)