| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import JSZip from 'jszip' |
| import Slide from './slide' |
| import { |
| AlignH, |
| AlignV, |
| CHART_TYPE, |
| ChartType, |
| DEF_PRES_LAYOUT, |
| DEF_PRES_LAYOUT_NAME, |
| DEF_SLIDE_MARGIN_IN, |
| EMU, |
| OutputType, |
| SCHEME_COLOR_NAMES, |
| SHAPE_TYPE, |
| SchemeColor, |
| ShapeType, |
| WRITE_OUTPUT_TYPE, |
| } from './core-enums' |
| import { |
| AddSlideProps, |
| IPresentationProps, |
| PresLayout, |
| PresSlide, |
| SectionProps, |
| SlideLayout, |
| SlideMasterProps, |
| SlideNumberProps, |
| TableToSlidesProps, |
| ThemeProps, |
| WriteBaseProps, |
| WriteFileProps, |
| WriteProps, |
| } from './core-interfaces' |
| import * as genCharts from './gen-charts' |
| import * as genObj from './gen-objects' |
| import * as genMedia from './gen-media' |
| import * as genTable from './gen-tables' |
| import * as genXml from './gen-xml' |
|
|
| const VERSION = '4.0.1' |
|
|
| export default class PptxGenJS implements IPresentationProps { |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| private _layout: string |
| public set layout(value: string) { |
| const newLayout: PresLayout = this.LAYOUTS[value] |
|
|
| if (newLayout) { |
| this._layout = value |
| this._presLayout = newLayout |
| } else { |
| throw new Error('UNKNOWN-LAYOUT') |
| } |
| } |
|
|
| public get layout(): string { |
| return this._layout |
| } |
|
|
| |
| |
| |
| private readonly _version: string = VERSION |
| public get version(): string { |
| return this._version |
| } |
|
|
| |
| |
| |
| private _author: string |
| public set author(value: string) { |
| this._author = value |
| } |
|
|
| public get author(): string { |
| return this._author |
| } |
|
|
| |
| |
| |
| private _company: string |
| public set company(value: string) { |
| this._company = value |
| } |
|
|
| public get company(): string { |
| return this._company |
| } |
|
|
| |
| |
| |
| |
| private _revision: string |
| public set revision(value: string) { |
| this._revision = value |
| } |
|
|
| public get revision(): string { |
| return this._revision |
| } |
|
|
| |
| |
| |
| private _subject: string |
| public set subject(value: string) { |
| this._subject = value |
| } |
|
|
| public get subject(): string { |
| return this._subject |
| } |
|
|
| |
| |
| |
| private _theme: ThemeProps |
| public set theme(value: ThemeProps) { |
| this._theme = value |
| } |
|
|
| public get theme(): ThemeProps { |
| return this._theme |
| } |
|
|
| |
| |
| |
| private _title: string |
| public set title(value: string) { |
| this._title = value |
| } |
|
|
| public get title(): string { |
| return this._title |
| } |
|
|
| |
| |
| |
| |
| private _rtlMode: boolean |
| public set rtlMode(value: boolean) { |
| this._rtlMode = value |
| } |
|
|
| public get rtlMode(): boolean { |
| return this._rtlMode |
| } |
|
|
| |
| private readonly _masterSlide: PresSlide |
| public get masterSlide(): PresSlide { |
| return this._masterSlide |
| } |
|
|
| |
| private readonly _slides: PresSlide[] |
| public get slides(): PresSlide[] { |
| return this._slides |
| } |
|
|
| |
| private readonly _sections: SectionProps[] |
| public get sections(): SectionProps[] { |
| return this._sections |
| } |
|
|
| |
| private readonly _slideLayouts: SlideLayout[] |
| public get slideLayouts(): SlideLayout[] { |
| return this._slideLayouts |
| } |
|
|
| private LAYOUTS: { [key: string]: PresLayout } |
|
|
| |
| private readonly _alignH = AlignH |
| public get AlignH(): typeof AlignH { |
| return this._alignH |
| } |
|
|
| private readonly _alignV = AlignV |
| public get AlignV(): typeof AlignV { |
| return this._alignV |
| } |
|
|
| private readonly _chartType = ChartType |
| public get ChartType(): typeof ChartType { |
| return this._chartType |
| } |
|
|
| private readonly _outputType = OutputType |
| public get OutputType(): typeof OutputType { |
| return this._outputType |
| } |
|
|
| private _presLayout: PresLayout |
| public get presLayout(): PresLayout { |
| return this._presLayout |
| } |
|
|
| private readonly _schemeColor = SchemeColor |
| public get SchemeColor(): typeof SchemeColor { |
| return this._schemeColor |
| } |
|
|
| private readonly _shapeType = ShapeType |
| public get ShapeType(): typeof ShapeType { |
| return this._shapeType |
| } |
|
|
| |
| |
| |
| private readonly _charts = CHART_TYPE |
| public get charts(): typeof CHART_TYPE { |
| return this._charts |
| } |
|
|
| |
| |
| |
| private readonly _colors = SCHEME_COLOR_NAMES |
| public get colors(): typeof SCHEME_COLOR_NAMES { |
| return this._colors |
| } |
|
|
| |
| |
| |
| private readonly _shapes = SHAPE_TYPE |
| public get shapes(): typeof SHAPE_TYPE { |
| return this._shapes |
| } |
|
|
| constructor() { |
| const layout4x3: PresLayout = { name: 'screen4x3', width: 9144000, height: 6858000 } |
| const layout16x9: PresLayout = { name: 'screen16x9', width: 9144000, height: 5143500 } |
| const layout16x10: PresLayout = { name: 'screen16x10', width: 9144000, height: 5715000 } |
| const layoutWide: PresLayout = { name: 'custom', width: 12192000, height: 6858000 } |
| |
| this.LAYOUTS = { |
| LAYOUT_4x3: layout4x3, |
| LAYOUT_16x9: layout16x9, |
| LAYOUT_16x10: layout16x10, |
| LAYOUT_WIDE: layoutWide, |
| } |
|
|
| |
| this._author = 'PptxGenJS' |
| this._company = 'PptxGenJS' |
| this._revision = '1' |
| this._subject = 'PptxGenJS Presentation' |
| this._title = 'PptxGenJS Presentation' |
| |
| this._presLayout = { |
| name: this.LAYOUTS[DEF_PRES_LAYOUT].name, |
| _sizeW: this.LAYOUTS[DEF_PRES_LAYOUT].width, |
| _sizeH: this.LAYOUTS[DEF_PRES_LAYOUT].height, |
| width: this.LAYOUTS[DEF_PRES_LAYOUT].width, |
| height: this.LAYOUTS[DEF_PRES_LAYOUT].height, |
| } |
| this._rtlMode = false |
| |
| this._slideLayouts = [ |
| { |
| _margin: DEF_SLIDE_MARGIN_IN, |
| _name: DEF_PRES_LAYOUT_NAME, |
| _presLayout: this._presLayout, |
| _rels: [], |
| _relsChart: [], |
| _relsMedia: [], |
| _slide: null, |
| _slideNum: 1000, |
| _slideNumberProps: null, |
| _slideObjects: [], |
| }, |
| ] |
| this._slides = [] |
| this._sections = [] |
| this._masterSlide = { |
| addChart: null, |
| addImage: null, |
| addMedia: null, |
| addNotes: null, |
| addShape: null, |
| addTable: null, |
| addText: null, |
| |
| _name: null, |
| _presLayout: this._presLayout, |
| _rId: null, |
| _rels: [], |
| _relsChart: [], |
| _relsMedia: [], |
| _slideId: null, |
| _slideLayout: null, |
| _slideNum: null, |
| _slideNumberProps: null, |
| _slideObjects: [], |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| private readonly addNewSlide = (options?: AddSlideProps): PresSlide => { |
| |
| const sectAlreadyInUse = |
| this.sections.length > 0 && |
| this.sections[this.sections.length - 1]._slides.filter(slide => slide._slideNum === this.slides[this.slides.length - 1]._slideNum).length > 0 |
|
|
| options.sectionTitle = sectAlreadyInUse ? this.sections[this.sections.length - 1].title : null |
|
|
| return this.addSlide(options) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| private readonly getSlide = (slideNum: number): PresSlide => this.slides.filter(slide => slide._slideNum === slideNum)[0] |
|
|
| |
| |
| |
| |
| private readonly setSlideNumber = (slideNum: SlideNumberProps): void => { |
| |
| this.masterSlide._slideNumberProps = slideNum |
|
|
| |
| this.slideLayouts.filter(layout => layout._name === DEF_PRES_LAYOUT_NAME)[0]._slideNumberProps = slideNum |
| } |
|
|
| |
| |
| |
| |
| |
| |
| private readonly createChartMediaRels = (slide: PresSlide | SlideLayout, zip: JSZip, chartPromises: Promise<string>[]): void => { |
| slide._relsChart.forEach(rel => chartPromises.push(genCharts.createExcelWorksheet(rel, zip))) |
| slide._relsMedia.forEach(rel => { |
| if (rel.type !== 'online' && rel.type !== 'hyperlink') { |
| |
| let data: string = rel.data && typeof rel.data === 'string' ? rel.data : '' |
|
|
| |
| if (!data.includes(',') && !data.includes(';')) data = 'image/png;base64,' + data |
| else if (!data.includes(',')) data = 'image/png;base64,' + data |
| else if (!data.includes(';')) data = 'image/png;' + data |
|
|
| |
| zip.file(rel.Target.replace('..', 'ppt'), data.split(',').pop(), { base64: true }) |
| } |
| }) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| private readonly writeFileToBrowser = async (exportName: string, blobContent: Blob): Promise<string> => { |
| |
| const eleLink = document.createElement('a') |
| eleLink.setAttribute('style', 'display:none;') |
| eleLink.dataset.interception = 'off' |
| document.body.appendChild(eleLink) |
|
|
| |
| |
| if (window.URL.createObjectURL) { |
| const url = window.URL.createObjectURL(new Blob([blobContent], { type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' })) |
| eleLink.href = url |
| eleLink.download = exportName |
| eleLink.click() |
|
|
| |
| setTimeout(() => { |
| window.URL.revokeObjectURL(url) |
| document.body.removeChild(eleLink) |
| }, 100) |
|
|
| |
| return await Promise.resolve(exportName) |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| private readonly exportPresentation = async (props: WriteProps): Promise<string | ArrayBuffer | Blob | Buffer | Uint8Array> => { |
| const arrChartPromises: Promise<string>[] = [] |
| let arrMediaPromises: Promise<string>[] = [] |
| const zip = new JSZip() |
|
|
| |
| this.slides.forEach(slide => { |
| arrMediaPromises = arrMediaPromises.concat(genMedia.encodeSlideMediaRels(slide)) |
| }) |
| this.slideLayouts.forEach(layout => { |
| arrMediaPromises = arrMediaPromises.concat(genMedia.encodeSlideMediaRels(layout)) |
| }) |
| arrMediaPromises = arrMediaPromises.concat(genMedia.encodeSlideMediaRels(this.masterSlide)) |
|
|
| |
| return await Promise.all(arrMediaPromises).then(async () => { |
| |
| this.slides.forEach(slide => { |
| if (slide._slideLayout) genObj.addPlaceholdersToSlideLayouts(slide) |
| }) |
|
|
| |
| zip.folder('_rels') |
| zip.folder('docProps') |
| zip.folder('ppt').folder('_rels') |
| zip.folder('ppt/charts').folder('_rels') |
| zip.folder('ppt/embeddings') |
| zip.folder('ppt/media') |
| zip.folder('ppt/slideLayouts').folder('_rels') |
| zip.folder('ppt/slideMasters').folder('_rels') |
| zip.folder('ppt/slides').folder('_rels') |
| zip.folder('ppt/theme') |
| zip.folder('ppt/notesMasters').folder('_rels') |
| zip.folder('ppt/notesSlides').folder('_rels') |
| zip.file('[Content_Types].xml', genXml.makeXmlContTypes(this.slides, this.slideLayouts, this.masterSlide)) |
| zip.file('_rels/.rels', genXml.makeXmlRootRels()) |
| zip.file('docProps/app.xml', genXml.makeXmlApp(this.slides, this.company)) |
| zip.file('docProps/core.xml', genXml.makeXmlCore(this.title, this.subject, this.author, this.revision)) |
| zip.file('ppt/_rels/presentation.xml.rels', genXml.makeXmlPresentationRels(this.slides)) |
| zip.file('ppt/theme/theme1.xml', genXml.makeXmlTheme(this)) |
| zip.file('ppt/presentation.xml', genXml.makeXmlPresentation(this)) |
| zip.file('ppt/presProps.xml', genXml.makeXmlPresProps()) |
| zip.file('ppt/tableStyles.xml', genXml.makeXmlTableStyles()) |
| zip.file('ppt/viewProps.xml', genXml.makeXmlViewProps()) |
|
|
| |
| this.slideLayouts.forEach((layout, idx) => { |
| zip.file(`ppt/slideLayouts/slideLayout${idx + 1}.xml`, genXml.makeXmlLayout(layout)) |
| zip.file(`ppt/slideLayouts/_rels/slideLayout${idx + 1}.xml.rels`, genXml.makeXmlSlideLayoutRel(idx + 1, this.slideLayouts)) |
| }) |
| this.slides.forEach((slide, idx) => { |
| zip.file(`ppt/slides/slide${idx + 1}.xml`, genXml.makeXmlSlide(slide)) |
| zip.file(`ppt/slides/_rels/slide${idx + 1}.xml.rels`, genXml.makeXmlSlideRel(this.slides, this.slideLayouts, idx + 1)) |
| |
| zip.file(`ppt/notesSlides/notesSlide${idx + 1}.xml`, genXml.makeXmlNotesSlide(slide)) |
| zip.file(`ppt/notesSlides/_rels/notesSlide${idx + 1}.xml.rels`, genXml.makeXmlNotesSlideRel(idx + 1)) |
| }) |
| zip.file('ppt/slideMasters/slideMaster1.xml', genXml.makeXmlMaster(this.masterSlide, this.slideLayouts)) |
| zip.file('ppt/slideMasters/_rels/slideMaster1.xml.rels', genXml.makeXmlMasterRel(this.masterSlide, this.slideLayouts)) |
| zip.file('ppt/notesMasters/notesMaster1.xml', genXml.makeXmlNotesMaster()) |
| zip.file('ppt/notesMasters/_rels/notesMaster1.xml.rels', genXml.makeXmlNotesMasterRel()) |
|
|
| |
| this.slideLayouts.forEach(layout => { |
| this.createChartMediaRels(layout, zip, arrChartPromises) |
| }) |
| this.slides.forEach(slide => { |
| this.createChartMediaRels(slide, zip, arrChartPromises) |
| }) |
| this.createChartMediaRels(this.masterSlide, zip, arrChartPromises) |
|
|
| |
| return await Promise.all(arrChartPromises).then(async () => { |
| if (props.outputType === 'STREAM') { |
| |
| return await zip.generateAsync({ type: 'nodebuffer', compression: props.compression ? 'DEFLATE' : 'STORE' }) |
| } else if (props.outputType) { |
| |
| return await zip.generateAsync({ type: props.outputType }) |
| } else { |
| |
| return await zip.generateAsync({ type: 'blob', compression: props.compression ? 'DEFLATE' : 'STORE' }) |
| } |
| }) |
| }) |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| |
| async stream(props?: WriteBaseProps): Promise<string | ArrayBuffer | Blob | Buffer | Uint8Array> { |
| return await this.exportPresentation({ |
| compression: props?.compression, |
| outputType: 'STREAM', |
| }) |
| } |
|
|
| |
| |
| |
| |
| |
| async write(props?: WriteProps | WRITE_OUTPUT_TYPE): Promise<string | ArrayBuffer | Blob | Buffer | Uint8Array> { |
| |
| const propsOutpType = typeof props === 'object' && props?.outputType ? props.outputType : props ? (props as WRITE_OUTPUT_TYPE) : null |
| const propsCompress = typeof props === 'object' && props?.compression ? props.compression : false |
|
|
| return await this.exportPresentation({ |
| compression: propsCompress, |
| outputType: propsOutpType, |
| }) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async writeFile(props?: WriteFileProps | string): Promise<string> { |
| |
| const isNode = typeof process !== 'undefined' && !!process.versions?.node && process.release?.name === 'node' |
|
|
| |
| if (typeof props === 'string') { |
| |
| console.warn('[WARNING] writeFile(string) is deprecated - pass { fileName } instead.') |
| props = { fileName: props } |
| } |
| const { fileName: rawName = 'Presentation.pptx', compression = false } = props as WriteFileProps |
| const fileName = rawName.toLowerCase().endsWith('.pptx') ? rawName : `${rawName}.pptx` |
|
|
| |
| const outputType = isNode ? ('nodebuffer' as const) : null |
| const data = await this.exportPresentation({ compression, outputType }) |
|
|
| |
| if (isNode) { |
| |
| const { promises: fs } = await import( 'node:fs') |
| const { writeFile } = fs |
| await writeFile(fileName, data as Buffer) |
| return fileName |
| } |
|
|
| |
| await this.writeFileToBrowser(fileName, data as Blob) |
| return fileName |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| |
| addSection(section: SectionProps): void { |
| if (!section) console.warn('addSection requires an argument') |
| else if (!section.title) console.warn('addSection requires a title') |
|
|
| const newSection: SectionProps = { |
| _type: 'user', |
| _slides: [], |
| title: section.title, |
| } |
|
|
| if (section.order) this.sections.splice(section.order, 0, newSection) |
| else this._sections.push(newSection) |
| } |
|
|
| |
| |
| |
| |
| |
| addSlide(options?: AddSlideProps): PresSlide { |
| |
| const masterSlideName = typeof options === 'string' ? options : options?.masterName ? options.masterName : '' |
| let slideLayout: SlideLayout = { |
| _name: this.LAYOUTS[DEF_PRES_LAYOUT].name, |
| _presLayout: this.presLayout, |
| _rels: [], |
| _relsChart: [], |
| _relsMedia: [], |
| _slideNum: this.slides.length + 1, |
| } |
|
|
| if (masterSlideName) { |
| const tmpLayout = this.slideLayouts.filter(layout => layout._name === masterSlideName)[0] |
| if (tmpLayout) slideLayout = tmpLayout |
| } |
|
|
| const newSlide = new Slide({ |
| addSlide: this.addNewSlide, |
| getSlide: this.getSlide, |
| presLayout: this.presLayout, |
| setSlideNum: this.setSlideNumber, |
| slideId: this.slides.length + 256, |
| slideRId: this.slides.length + 2, |
| slideNumber: this.slides.length + 1, |
| slideLayout, |
| }) |
|
|
| |
| this._slides.push(newSlide) |
|
|
| |
| |
| |
| if (options?.sectionTitle) { |
| const sect = this.sections.filter(section => section.title === options.sectionTitle)[0] |
| if (!sect) console.warn(`addSlide: unable to find section with title: "${options.sectionTitle}"`) |
| else sect._slides.push(newSlide) |
| } else if (this.sections && this.sections.length > 0 && (!options?.sectionTitle)) { |
| const lastSect = this._sections[this.sections.length - 1] |
|
|
| |
| if (lastSect._type === 'default') lastSect._slides.push(newSlide) |
| |
| else { |
| this._sections.push({ |
| title: `Default-${this.sections.filter(sect => sect._type === 'default').length + 1}`, |
| _type: 'default', |
| _slides: [newSlide], |
| }) |
| } |
| } |
|
|
| return newSlide |
| } |
|
|
| |
| |
| |
| |
| |
| defineLayout(layout: PresLayout): void { |
| |
| if (!layout) console.warn('defineLayout requires `{name, width, height}`') |
| else if (!layout.name) console.warn('defineLayout requires `name`') |
| else if (!layout.width) console.warn('defineLayout requires `width`') |
| else if (!layout.height) console.warn('defineLayout requires `height`') |
| else if (typeof layout.height !== 'number') console.warn('defineLayout `height` should be a number (inches)') |
| else if (typeof layout.width !== 'number') console.warn('defineLayout `width` should be a number (inches)') |
|
|
| this.LAYOUTS[layout.name] = { |
| name: layout.name, |
| _sizeW: Math.round(Number(layout.width) * EMU), |
| _sizeH: Math.round(Number(layout.height) * EMU), |
| width: Math.round(Number(layout.width) * EMU), |
| height: Math.round(Number(layout.height) * EMU), |
| } |
| } |
|
|
| |
| |
| |
| |
| defineSlideMaster(props: SlideMasterProps): void { |
| |
| const propsClone = JSON.parse(JSON.stringify(props)) |
| if (!propsClone.title) throw new Error('defineSlideMaster() object argument requires a `title` value. (https://gitbrent.github.io/PptxGenJS/docs/masters.html)') |
|
|
| const newLayout: SlideLayout = { |
| _margin: propsClone.margin || DEF_SLIDE_MARGIN_IN, |
| _name: propsClone.title, |
| _presLayout: this.presLayout, |
| _rels: [], |
| _relsChart: [], |
| _relsMedia: [], |
| _slide: null, |
| _slideNum: 1000 + this.slideLayouts.length + 1, |
| _slideNumberProps: propsClone.slideNumber || null, |
| _slideObjects: [], |
| background: propsClone.background || null, |
| bkgd: propsClone.bkgd || null, |
| } |
|
|
| |
| genObj.createSlideMaster(propsClone, newLayout) |
|
|
| |
| this.slideLayouts.push(newLayout) |
|
|
| |
| if (propsClone.background || propsClone.bkgd) genObj.addBackgroundDefinition(propsClone.background, newLayout) |
|
|
| |
| if (newLayout._slideNumberProps && !this.masterSlide._slideNumberProps) this.masterSlide._slideNumberProps = newLayout._slideNumberProps |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| |
| tableToSlides(eleId: string, options: TableToSlidesProps = {}): void { |
| |
| genTable.genTableToSlides( |
| this, |
| eleId, |
| options, |
| options?.masterSlideName ? this.slideLayouts.filter(layout => layout._name === options.masterSlideName)[0] : null |
| ) |
| } |
| } |
|
|