`]
* @param {TextProps} textObj - Text object
* @return {string} XML string
*/
function genXmlTextRun (textObj: TextProps): string {
// NOTE: Dont create full rPr runProps for empty [lineBreak] runs
// Why? The size of the lineBreak wont match (eg: below it will be 18px instead of the correct 36px)
// Do this:
/*
*/
// NOT this:
/*
*/
// Return paragraph with text run
return textObj.text ? `${genXmlTextRunProperties(textObj.options, false)}${encodeXmlEntities(textObj.text)}` : ''
}
/**
* Builds `` tag for "genXmlTextBody()"
* @param {ISlideObject | TableCell} slideObject - various options
* @return {string} XML string
*/
function genXmlBodyProperties (slideObject: ISlideObject | TableCell): string {
let bodyProperties = '
// A: Enable or disable textwrapping none or square
bodyProperties += slideObject.options._bodyProp.wrap ? ' wrap="square"' : ' wrap="none"'
// B: Textbox margins [padding]
if (slideObject.options._bodyProp.lIns || slideObject.options._bodyProp.lIns === 0) bodyProperties += ` lIns="${slideObject.options._bodyProp.lIns}"`
if (slideObject.options._bodyProp.tIns || slideObject.options._bodyProp.tIns === 0) bodyProperties += ` tIns="${slideObject.options._bodyProp.tIns}"`
if (slideObject.options._bodyProp.rIns || slideObject.options._bodyProp.rIns === 0) bodyProperties += ` rIns="${slideObject.options._bodyProp.rIns}"`
if (slideObject.options._bodyProp.bIns || slideObject.options._bodyProp.bIns === 0) bodyProperties += ` bIns="${slideObject.options._bodyProp.bIns}"`
// C: Add rtl after margins
bodyProperties += ' rtlCol="0"'
// D: Add anchorPoints
if (slideObject.options._bodyProp.anchor) bodyProperties += ' anchor="' + slideObject.options._bodyProp.anchor + '"' // VALS: [t,ctr,b]
if (slideObject.options._bodyProp.vert) bodyProperties += ' vert="' + slideObject.options._bodyProp.vert + '"' // VALS: [eaVert,horz,mongolianVert,vert,vert270,wordArtVert,wordArtVertRtl]
// E: Close '
/**
* F: Text Fit/AutoFit/Shrink option
* @see: http://officeopenxml.com/drwSp-text-bodyPr-fit.php
* @see: http://www.datypic.com/sc/ooxml/g-a_EG_TextAutofit.html
*/
if (slideObject.options.fit) {
// NOTE: Use of '' instead of '' causes issues in PPT-2013!
if (slideObject.options.fit === 'none') bodyProperties += ''
// NOTE: Shrink does not work automatically - PowerPoint calculates the `fontScale` value dynamically upon resize
// else if (slideObject.options.fit === 'shrink') bodyProperties += '' // MS-PPT > Format shape > Text Options: "Shrink text on overflow"
else if (slideObject.options.fit === 'shrink') bodyProperties += ''
else if (slideObject.options.fit === 'resize') bodyProperties += ''
}
//
// DEPRECATED: below (@deprecated v3.3.0)
if (slideObject.options.shrinkText) bodyProperties += '' // MS-PPT > Format shape > Text Options: "Shrink text on overflow"
/* DEPRECATED: below (@deprecated v3.3.0)
* MS-PPT > Format shape > Text Options: "Resize shape to fit text" [spAutoFit]
* NOTE: Use of '' in lieu of '' below causes issues in PPT-2013
*/
bodyProperties += slideObject.options._bodyProp.autoFit ? '' : ''
// LAST: Close _bodyProp
bodyProperties += ''
} else {
// DEFAULT:
bodyProperties += ' wrap="square" rtlCol="0">'
bodyProperties += ''
}
// LAST: Return Close _bodyProp
return slideObject._type === SLIDE_OBJECT_TYPES.tablecell ? '' : bodyProperties
}
/**
* Generate the XML for text and its options (bold, bullet, etc) including text runs (word-level formatting)
* @param {ISlideObject|TableCell} slideObj - slideObj or tableCell
* @note PPT text lines [lines followed by line-breaks] are created using -aragraph's
* @note Bullets are a paragragh-level formatting device
* @template
*
*
*
*
*
*
*
*
*
* textbox text
*
*
*
*
* @returns XML containing the param object's text and formatting
*/
export function genXmlTextBody (slideObj: ISlideObject | TableCell): string {
const opts: ObjectOptions = slideObj.options || {}
let tmpTextObjects: TextProps[] = []
const arrTextObjects: TextProps[] = []
// FIRST: Shapes without text, etc. may be sent here during build, but have no text to render so return an empty string
if (opts && slideObj._type !== SLIDE_OBJECT_TYPES.tablecell && (typeof slideObj.text === 'undefined' || slideObj.text === null)) return ''
// STEP 1: Start textBody
let strSlideXml = slideObj._type === SLIDE_OBJECT_TYPES.tablecell ? '' : ''
// STEP 2: Add bodyProperties
{
// A: 'bodyPr'
strSlideXml += genXmlBodyProperties(slideObj)
// B: 'lstStyle'
// NOTE: shape type 'LINE' has different text align needs (a lstStyle.lvl1pPr between bodyPr and p)
// FIXME: LINE horiz-align doesnt work (text is always to the left inside line) (FYI: the PPT code diff is substantial!)
if (opts.h === 0 && opts.line && opts.align) strSlideXml += ''
else if (slideObj._type === 'placeholder') strSlideXml += `${genXmlParagraphProperties(slideObj, true)}`
else strSlideXml += ''
}
/* STEP 3: Modify slideObj.text to array
CASES:
addText( 'string' ) // string
addText( 'line1\n line2' ) // string with lineBreak
addText( {text:'word1'} ) // TextProps object
addText( ['barry','allen'] ) // array of strings
addText( [{text:'word1'}, {text:'word2'}] ) // TextProps object array
addText( [{text:'line1\n line2'}, {text:'end word'}] ) // TextProps object array with lineBreak
*/
if (typeof slideObj.text === 'string' || typeof slideObj.text === 'number') {
// Handle cases 1,2
tmpTextObjects.push({ text: slideObj.text.toString(), options: opts || {} })
} else if (slideObj.text && !Array.isArray(slideObj.text) && typeof slideObj.text === 'object' && Object.keys(slideObj.text).includes('text')) {
// } else if (!Array.isArray(slideObj.text) && slideObj.text!.hasOwnProperty('text')) { // 20210706: replaced with below as ts compiler rejected it
// Handle case 3
tmpTextObjects.push({ text: slideObj.text || '', options: slideObj.options || {} })
} else if (Array.isArray(slideObj.text)) {
// Handle cases 4,5,6
// NOTE: use cast as text is TextProps[]|TableCell[] and their `options` dont overlap (they share the same TextBaseProps though)
tmpTextObjects = (slideObj.text as TextProps[]).map(item => ({ text: item.text, options: item.options }))
}
// STEP 4: Iterate over text objects, set text/options, break into pieces if '\n'/breakLine found
tmpTextObjects.forEach((itext, idx) => {
if (!itext.text) itext.text = ''
// A: Set options
itext.options = itext.options || opts || {}
if (idx === 0 && itext.options && !itext.options.bullet && opts.bullet) itext.options.bullet = opts.bullet
// B: Cast to text-object and fix line-breaks (if needed)
if (typeof itext.text === 'string' || typeof itext.text === 'number') {
// 1: Convert "\n" or any variation into CRLF
itext.text = itext.text.toString().replace(/\r*\n/g, CRLF)
}
// C: If text string has line-breaks, then create a separate text-object for each (much easier than dealing with split inside a loop below)
// NOTE: Filter for trailing lineBreak prevents the creation of an empty textObj as the last item
if (itext.text.includes(CRLF) && itext.text.match(/\n$/g) === null) {
itext.text.split(CRLF).forEach(line => {
itext.options.breakLine = true
arrTextObjects.push({ text: line, options: itext.options })
})
} else {
arrTextObjects.push(itext)
}
})
// STEP 5: Group textObj into lines by checking for lineBreak, bullets, alignment change, etc.
const arrLines: TextProps[][] = []
let arrTexts: TextProps[] = []
arrTextObjects.forEach((textObj, idx) => {
// A: Align or Bullet trigger new line
if (arrTexts.length > 0 && (textObj.options.align || opts.align)) {
// Only start a new paragraph when align *changes*
if (textObj.options.align !== arrTextObjects[idx - 1].options.align) {
arrLines.push(arrTexts)
arrTexts = []
}
} else if (arrTexts.length > 0 && textObj.options.bullet && arrTexts.length > 0) {
arrLines.push(arrTexts)
arrTexts = []
textObj.options.breakLine = false // For cases with both `bullet` and `brekaLine` - prevent double lineBreak
}
// B: Add this text to current line
arrTexts.push(textObj)
// C: BreakLine begins new line **after** adding current text
if (arrTexts.length > 0 && textObj.options.breakLine) {
// Avoid starting a para right as loop is exhausted
if (idx + 1 < arrTextObjects.length) {
arrLines.push(arrTexts)
arrTexts = []
}
}
// D: Flush buffer
if (idx + 1 === arrTextObjects.length) arrLines.push(arrTexts)
})
// STEP 6: Loop over each line and create paragraph props, text run, etc.
arrLines.forEach(line => {
let reqsClosingFontSize = false
// A: Start paragraph, add paraProps
strSlideXml += ''
// NOTE: `rtlMode` is like other opts, its propagated up to each text:options, so just check the 1st one
let paragraphPropXml = ` {
// A: Set line index
textObj.options._lineIdx = idx
// A.1: Add soft break if not the first run of the line.
if (idx > 0 && textObj.options.softBreakBefore) {
strSlideXml += ''
}
// B: Inherit pPr-type options from parent shape's `options`
textObj.options.align = textObj.options.align || opts.align
textObj.options.lineSpacing = textObj.options.lineSpacing || opts.lineSpacing
textObj.options.lineSpacingMultiple = textObj.options.lineSpacingMultiple || opts.lineSpacingMultiple
textObj.options.indentLevel = textObj.options.indentLevel || opts.indentLevel
textObj.options.paraSpaceBefore = textObj.options.paraSpaceBefore || opts.paraSpaceBefore
textObj.options.paraSpaceAfter = textObj.options.paraSpaceAfter || opts.paraSpaceAfter
paragraphPropXml = genXmlParagraphProperties(textObj, false)
strSlideXml += paragraphPropXml.replace('', '') // IMPORTANT: Empty "pPr" blocks will generate needs-repair/corrupt msg
// C: Inherit any main options (color, fontSize, etc.)
// NOTE: We only pass the text.options to genXmlTextRun (not the Slide.options),
// so the run building function cant just fallback to Slide.color, therefore, we need to do that here before passing options below.
// FILTER RULE: Hyperlinks should not inherit `color` from main options (let PPT default to local color, eg: blue on MacOS)
Object.entries(opts).filter(([key]) => !(textObj.options.hyperlink && key === 'color')).forEach(([key, val]) => {
// if (textObj.options.hyperlink && key === 'color') null
// NOTE: This loop will pick up unecessary keys (`x`, etc.), but it doesnt hurt anything
if (key !== 'bullet' && !textObj.options[key]) textObj.options[key] = val
})
// D: Add formatted textrun
strSlideXml += genXmlTextRun(textObj)
// E: Flag close fontSize for empty [lineBreak] elements
if ((!textObj.text && opts.fontSize) || textObj.options.fontSize) {
reqsClosingFontSize = true
opts.fontSize = opts.fontSize || textObj.options.fontSize
}
})
/* C: Append 'endParaRPr' (when needed) and close current open paragraph
* NOTE: (ISSUE#20, ISSUE#193): Add 'endParaRPr' with font/size props or PPT default (Arial/18pt en-us) is used making row "too tall"/not honoring options
*/
if (slideObj._type === SLIDE_OBJECT_TYPES.tablecell && (opts.fontSize || opts.fontFace)) {
if (opts.fontFace) {
strSlideXml += `'
strSlideXml += ``
strSlideXml += ``
strSlideXml += ``
strSlideXml += ''
} else {
strSlideXml += `'
}
} else if (reqsClosingFontSize) {
// Empty [lineBreak] lines should not contain runProp, however, they need to specify fontSize in `endParaRPr`
strSlideXml += `'
} else {
strSlideXml += `` // Added 20180101 to address PPT-2007 issues
}
// D: End paragraph
strSlideXml += ''
})
// IMPORTANT: An empty txBody will cause "needs repair" error! Add content if missing.
// [FIXED in v3.13.0]: This fixes issue with table auto-paging where some cells w/b empty on subsequent pages.
/*
*/
if (strSlideXml.indexOf('') === -1) {
strSlideXml += ''
}
// STEP 7: Close the textBody
strSlideXml += slideObj._type === SLIDE_OBJECT_TYPES.tablecell ? '
' : ''
// LAST: Return XML
return strSlideXml
}
/**
* Generate an XML Placeholder
* @param {ISlideObject} placeholderObj
* @returns XML
*/
export function genXmlPlaceholder (placeholderObj: ISlideObject): string {
if (!placeholderObj) return ''
const placeholderIdx = placeholderObj.options?._placeholderIdx ? placeholderObj.options._placeholderIdx : ''
const placeholderTyp = placeholderObj.options?._placeholderType ? placeholderObj.options._placeholderType : ''
const placeholderType: string = placeholderTyp && PLACEHOLDER_TYPES[placeholderTyp] ? (PLACEHOLDER_TYPES[placeholderTyp]).toString() : ''
return ` 0 ? ' hasCustomPrompt="1"' : ''}
/>`
}
// XML-GEN: First 6 functions create the base /ppt files
/**
* Generate XML ContentType
* @param {PresSlide[]} slides - slides
* @param {SlideLayout[]} slideLayouts - slide layouts
* @param {PresSlide} masterSlide - master slide
* @returns XML
*/
export function makeXmlContTypes (slides: PresSlide[], slideLayouts: SlideLayout[], masterSlide?: PresSlide): string {
let strXml = '' + CRLF
strXml += ''
strXml += ''
strXml += ''
strXml += ''
strXml += ''
strXml += ''
// STEP 1: Add standard/any media types used in Presentation
strXml += ''
strXml += ''
strXml += '' // NOTE: Hard-Code this extension as it wont be created in loop below (as extn !== type)
strXml += '' // NOTE: Hard-Code this extension as it wont be created in loop below (as extn !== type)
slides.forEach(slide => {
(slide._relsMedia || []).forEach(rel => {
if (rel.type !== 'image' && rel.type !== 'online' && rel.type !== 'chart' && rel.extn !== 'm4v' && !strXml.includes(rel.type)) {
strXml += ''
}
})
})
strXml += ''
strXml += ''
// STEP 2: Add presentation and slide master(s)/slide(s)
strXml += ''
strXml += ''
slides.forEach((slide, idx) => {
strXml += ``
strXml += ``
// Add charts if any
slide._relsChart.forEach(rel => {
strXml += ``
})
})
// STEP 3: Core PPT
strXml += ''
strXml += ''
strXml += ''
strXml += ''
// STEP 4: Add Slide Layouts
slideLayouts.forEach((layout, idx) => {
strXml += ``
; (layout._relsChart || []).forEach(rel => {
strXml += ' '
})
})
// STEP 5: Add notes slide(s)
slides.forEach((_slide, idx) => {
strXml += ``
})
// STEP 6: Add rels
masterSlide._relsChart.forEach(rel => {
strXml += ' '
})
masterSlide._relsMedia.forEach(rel => {
if (rel.type !== 'image' && rel.type !== 'online' && rel.type !== 'chart' && rel.extn !== 'm4v' && !strXml.includes(rel.type)) { strXml += ' ' }
})
// LAST: Finish XML (Resume core)
strXml += ' '
strXml += ' '
strXml += ''
return strXml
}
/**
* Creates `_rels/.rels`
* @returns XML
*/
export function makeXmlRootRels (): string {
return `${CRLF}
`
}
/**
* Creates `docProps/app.xml`
* @param {PresSlide[]} slides - Presenation Slides
* @param {string} company - "Company" metadata
* @returns XML
*/
export function makeXmlApp (slides: PresSlide[], company: string): string {
return `${CRLF}
0
0
Microsoft Office PowerPoint
On-screen Show (16:9)
0
${slides.length}
${slides.length}
0
0
false
Fonts Used
2
Theme
1
Slide Titles
${slides.length}
Arial
Calibri
Office Theme
${slides.map((_slideObj, idx) => `Slide ${idx + 1}`).join('')}
${company}
false
false
false
16.0000
`
}
/**
* Creates `docProps/core.xml`
* @param {string} title - metadata data
* @param {string} subject - metadata data
* @param {string} author - metadata value
* @param {string} revision - metadata value
* @returns XML
*/
export function makeXmlCore (title: string, subject: string, author: string, revision: string): string {
return `
${encodeXmlEntities(title)}
${encodeXmlEntities(subject)}
${encodeXmlEntities(author)}
${encodeXmlEntities(author)}
${revision}
${new Date().toISOString().replace(/\.\d\d\dZ/, 'Z')}
${new Date().toISOString().replace(/\.\d\d\dZ/, 'Z')}
`
}
/**
* Creates `ppt/_rels/presentation.xml.rels`
* @param {PresSlide[]} slides - Presenation Slides
* @returns XML
*/
export function makeXmlPresentationRels (slides: PresSlide[]): string {
let intRelNum = 1
let strXml = '' + CRLF
strXml += ''
strXml += ''
for (let idx = 1; idx <= slides.length; idx++) {
strXml += ``
}
intRelNum++
strXml +=
`` +
`` +
`` +
`` +
`` +
''
return strXml
}
// XML-GEN: Functions that run 1-N times (once for each Slide)
/**
* Generates XML for the slide file (`ppt/slides/slide1.xml`)
* @param {PresSlide} slide - the slide object to transform into XML
* @return {string} XML
*/
export function makeXmlSlide (slide: PresSlide): string {
return (
`${CRLF}` +
'` +
`${slideObjectToXml(slide)}` +
''
)
}
/**
* Get text content of Notes from Slide
* @param {PresSlide} slide - the slide object to transform into XML
* @return {string} notes text
*/
export function getNotesFromSlide (slide: PresSlide): string {
let notesText = ''
slide._slideObjects.forEach(data => {
if (data._type === SLIDE_OBJECT_TYPES.notes) notesText += data?.text && data.text[0] ? data.text[0].text : ''
})
return notesText.replace(/\r*\n/g, CRLF)
}
/**
* Generate XML for Notes Master (notesMaster1.xml)
* @returns {string} XML
*/
export function makeXmlNotesMaster (): string {
return `${CRLF}7/23/19Click to edit Master text stylesSecond levelThird levelFourth levelFifth level‹#›`
}
/**
* Creates Notes Slide (`ppt/notesSlides/notesSlide1.xml`)
* @param {PresSlide} slide - the slide object to transform into XML
* @return {string} XML
*/
export function makeXmlNotesSlide (slide: PresSlide): string {
return (
`${CRLF}${encodeXmlEntities(getNotesFromSlide(slide))}${slide._slideNum}`
)
}
/**
* Generates the XML layout resource from a layout object
* @param {SlideLayout} layout - slide layout (master)
* @return {string} XML
*/
export function makeXmlLayout (layout: SlideLayout): string {
return `
${slideObjectToXml(layout)}
`
}
/**
* Creates Slide Master 1 (`ppt/slideMasters/slideMaster1.xml`)
* @param {PresSlide} slide - slide object that represents master slide layout
* @param {SlideLayout[]} layouts - slide layouts
* @return {string} XML
*/
export function makeXmlMaster (slide: PresSlide, layouts: SlideLayout[]): string {
// NOTE: Pass layouts as static rels because they are not referenced any time
const layoutDefs = layouts.map((_layoutDef, idx) => ``)
let strXml = '' + CRLF
strXml +=
''
strXml += slideObjectToXml(slide)
strXml +=
''
strXml += '' + layoutDefs.join('') + ''
strXml += ''
strXml +=
'' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
''
strXml += ''
return strXml
}
/**
* Generates XML string for a slide layout relation file
* @param {number} layoutNumber - 1-indexed number of a layout that relations are generated for
* @param {SlideLayout[]} slideLayouts - Slide Layouts
* @return {string} XML
*/
export function makeXmlSlideLayoutRel (layoutNumber: number, slideLayouts: SlideLayout[]): string {
return slideObjectRelationsToXml(slideLayouts[layoutNumber - 1], [
{
target: '../slideMasters/slideMaster1.xml',
type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster',
},
])
}
/**
* Creates `ppt/_rels/slide*.xml.rels`
* @param {PresSlide[]} slides
* @param {SlideLayout[]} slideLayouts - Slide Layout(s)
* @param {number} `slideNumber` 1-indexed number of a layout that relations are generated for
* @return {string} XML
*/
export function makeXmlSlideRel (slides: PresSlide[], slideLayouts: SlideLayout[], slideNumber: number): string {
return slideObjectRelationsToXml(slides[slideNumber - 1], [
{
target: `../slideLayouts/slideLayout${getLayoutIdxForSlide(slides, slideLayouts, slideNumber)}.xml`,
type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout',
},
{
target: `../notesSlides/notesSlide${slideNumber}.xml`,
type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide',
},
])
}
/**
* Generates XML string for a slide relation file.
* @param {number} slideNumber - 1-indexed number of a layout that relations are generated for
* @return {string} XML
*/
export function makeXmlNotesSlideRel (slideNumber: number): string {
return `
`
}
/**
* Creates `ppt/slideMasters/_rels/slideMaster1.xml.rels`
* @param {PresSlide} masterSlide - Slide object
* @param {SlideLayout[]} slideLayouts - Slide Layouts
* @return {string} XML
*/
export function makeXmlMasterRel (masterSlide: PresSlide, slideLayouts: SlideLayout[]): string {
const defaultRels = slideLayouts.map((_layoutDef, idx) => ({
target: `../slideLayouts/slideLayout${idx + 1}.xml`,
type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout',
}))
defaultRels.push({ target: '../theme/theme1.xml', type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme' })
return slideObjectRelationsToXml(masterSlide, defaultRels)
}
/**
* Creates `ppt/notesMasters/_rels/notesMaster1.xml.rels`
* @return {string} XML
*/
export function makeXmlNotesMasterRel (): string {
return `${CRLF}
`
}
/**
* For the passed slide number, resolves name of a layout that is used for.
* @param {PresSlide[]} slides - srray of slides
* @param {SlideLayout[]} slideLayouts - array of slideLayouts
* @param {number} slideNumber
* @return {number} slide number
*/
function getLayoutIdxForSlide (slides: PresSlide[], slideLayouts: SlideLayout[], slideNumber: number): number {
for (let i = 0; i < slideLayouts.length; i++) {
if (slideLayouts[i]._name === slides[slideNumber - 1]._slideLayout._name) {
return i + 1
}
}
// IMPORTANT: Return 1 (for `slideLayout1.xml`) when no def is found
// So all objects are in Layout1 and every slide that references it uses this layout.
return 1
}
// XML-GEN: Last 5 functions create root /ppt files
/**
* Creates `ppt/theme/theme1.xml`
* @return {string} XML
*/
export function makeXmlTheme (pres: IPresentationProps): string {
const majorFont = pres.theme?.headFontFace ? `` : ''
const minorFont = pres.theme?.bodyFontFace ? `` : ''
return `${majorFont}${minorFont}`
}
/**
* Create presentation file (`ppt/presentation.xml`)
* @see https://docs.microsoft.com/en-us/office/open-xml/structure-of-a-presentationml-document
* @see http://www.datypic.com/sc/ooxml/t-p_CT_Presentation.html
* @param {IPresentationProps} pres - presentation
* @return {string} XML
*/
export function makeXmlPresentation (pres: IPresentationProps): string {
let strXml =
`${CRLF}` +
'`
// STEP 1: Add slide master (SPEC: tag 1 under )
strXml += ''
// STEP 2: Add all Slides (SPEC: tag 3 under )
strXml += ''
pres.slides.forEach(slide => (strXml += ``))
strXml += ''
// STEP 3: Add Notes Master (SPEC: tag 2 under )
// (NOTE: length+2 is from `presentation.xml.rels` func (since we have to match this rId, we just use same logic))
// IMPORTANT: In this order (matches PPT2019) PPT will give corruption message on open!
// IMPORTANT: Placing this before `` causes warning in modern powerpoint!
// IMPORTANT: Presentations open without warning Without this line, however, the pres isnt preview in Finder anymore or viewable in iOS!
strXml += ``
// STEP 4: Add sizes
strXml += ``
strXml += ``
// STEP 5: Add text styles
strXml += ''
for (let idy = 1; idy < 10; idy++) {
strXml +=
`` +
'' +
``
}
strXml += ''
// STEP 6: Add Sections (if any)
if (pres.sections && pres.sections.length > 0) {
strXml += ''
strXml += ''
pres.sections.forEach(sect => {
strXml += ``
sect._slides.forEach(slide => (strXml += ``))
strXml += ''
})
strXml += ''
strXml += ''
strXml += ''
}
// Done
strXml += ''
return strXml
}
/**
* Create `ppt/presProps.xml`
* @return {string} XML
*/
export function makeXmlPresProps (): string {
return `${CRLF}`
}
/**
* Create `ppt/tableStyles.xml`
* @see: http://openxmldeveloper.org/discussions/formats/f/13/p/2398/8107.aspx
* @return {string} XML
*/
export function makeXmlTableStyles (): string {
return `${CRLF}`
}
/**
* Creates `ppt/viewProps.xml`
* @return {string} XML
*/
export function makeXmlViewProps (): string {
return `${CRLF}`
}
/**
* Checks shadow options passed by user and performs corrections if needed.
* @param {ShadowProps} shadowProps - shadow options
*/
export function correctShadowOptions (shadowProps: ShadowProps): void {
if (!shadowProps || typeof shadowProps !== 'object') {
// console.warn("`shadow` options must be an object. Ex: `{shadow: {type:'none'}}`")
return
}
// OPT: `type`
if (shadowProps.type !== 'outer' && shadowProps.type !== 'inner' && shadowProps.type !== 'none') {
console.warn('Warning: shadow.type options are `outer`, `inner` or `none`.')
shadowProps.type = 'outer'
}
// OPT: `angle`
if (shadowProps.angle) {
// A: REALITY-CHECK
if (isNaN(Number(shadowProps.angle)) || shadowProps.angle < 0 || shadowProps.angle > 359) {
console.warn('Warning: shadow.angle can only be 0-359')
shadowProps.angle = 270
}
// B: ROBUST: Cast any type of valid arg to int: '12', 12.3, etc. -> 12
shadowProps.angle = Math.round(Number(shadowProps.angle))
}
// OPT: `opacity`
if (shadowProps.opacity) {
// A: REALITY-CHECK
if (isNaN(Number(shadowProps.opacity)) || shadowProps.opacity < 0 || shadowProps.opacity > 1) {
console.warn('Warning: shadow.opacity can only be 0-1')
shadowProps.opacity = 0.75
}
// B: ROBUST: Cast any type of valid arg to int: '12', 12.3, etc. -> 12
shadowProps.opacity = Number(shadowProps.opacity)
}
}