Nitish kumar
Upload folder using huggingface_hub
c20f20c verified
/**
* PptxGenJS: XML Generation
*/
import {
BULLET_TYPES,
CRLF,
DEF_BULLET_MARGIN,
DEF_CELL_MARGIN_IN,
DEF_PRES_LAYOUT_NAME,
DEF_TEXT_GLOW,
DEF_TEXT_SHADOW,
EMU,
LAYOUT_IDX_SERIES_BASE,
PLACEHOLDER_TYPES,
SLDNUMFLDID,
SLIDE_OBJECT_TYPES,
} from './core-enums'
import {
IPresentationProps,
ISlideObject,
ISlideRel,
ISlideRelChart,
ISlideRelMedia,
ObjectOptions,
PresSlide,
ShadowProps,
SlideLayout,
TableCell,
TableCellProps,
TextProps,
TextPropsOptions,
} from './core-interfaces'
import {
convertRotationDegrees,
createColorElement,
createGlowElement,
encodeXmlEntities,
genXmlColorSelection,
getSmartParseNumber,
getUuid,
inch2Emu,
valToPts,
} from './gen-utils'
const ImageSizingXml = {
cover: function (imgSize: { w: number, h: number }, boxDim: { w: number, h: number, x: number, y: number }) {
const imgRatio = imgSize.h / imgSize.w
const boxRatio = boxDim.h / boxDim.w
const isBoxBased = boxRatio > imgRatio
const width = isBoxBased ? boxDim.h / imgRatio : boxDim.w
const height = isBoxBased ? boxDim.h : boxDim.w * imgRatio
const hzPerc = Math.round(1e5 * 0.5 * (1 - boxDim.w / width))
const vzPerc = Math.round(1e5 * 0.5 * (1 - boxDim.h / height))
return `<a:srcRect l="${hzPerc}" r="${hzPerc}" t="${vzPerc}" b="${vzPerc}"/><a:stretch/>`
},
contain: function (imgSize: { w: number, h: number }, boxDim: { w: number, h: number, x: number, y: number }) {
const imgRatio = imgSize.h / imgSize.w
const boxRatio = boxDim.h / boxDim.w
const widthBased = boxRatio > imgRatio
const width = widthBased ? boxDim.w : boxDim.h / imgRatio
const height = widthBased ? boxDim.w * imgRatio : boxDim.h
const hzPerc = Math.round(1e5 * 0.5 * (1 - boxDim.w / width))
const vzPerc = Math.round(1e5 * 0.5 * (1 - boxDim.h / height))
return `<a:srcRect l="${hzPerc}" r="${hzPerc}" t="${vzPerc}" b="${vzPerc}"/><a:stretch/>`
},
crop: function (imgSize: { w: number, h: number }, boxDim: { w: number, h: number, x: number, y: number }) {
const l = boxDim.x
const r = imgSize.w - (boxDim.x + boxDim.w)
const t = boxDim.y
const b = imgSize.h - (boxDim.y + boxDim.h)
const lPerc = Math.round(1e5 * (l / imgSize.w))
const rPerc = Math.round(1e5 * (r / imgSize.w))
const tPerc = Math.round(1e5 * (t / imgSize.h))
const bPerc = Math.round(1e5 * (b / imgSize.h))
return `<a:srcRect l="${lPerc}" r="${rPerc}" t="${tPerc}" b="${bPerc}"/><a:stretch/>`
},
}
/**
* Transforms a slide or slideLayout to resulting XML string - Creates `ppt/slide*.xml`
* @param {PresSlide|SlideLayout} slideObject - slide object created within createSlideObject
* @return {string} XML string with <p:cSld> as the root
*/
function slideObjectToXml (slide: PresSlide | SlideLayout): string {
let strSlideXml: string = slide._name ? '<p:cSld name="' + slide._name + '">' : '<p:cSld>'
let intTableNum = 1
// STEP 1: Add background color/image (ensure only a single `<p:bg>` tag is created, ex: when master-baskground has both `color` and `path`)
if (slide._bkgdImgRid) {
strSlideXml += `<p:bg><p:bgPr><a:blipFill dpi="0" rotWithShape="1"><a:blip r:embed="rId${slide._bkgdImgRid}"><a:lum/></a:blip><a:srcRect/><a:stretch><a:fillRect/></a:stretch></a:blipFill><a:effectLst/></p:bgPr></p:bg>`
} else if (slide.background?.color) {
strSlideXml += `<p:bg><p:bgPr>${genXmlColorSelection(slide.background)}</p:bgPr></p:bg>`
} else if (!slide.bkgd && slide._name && slide._name === DEF_PRES_LAYOUT_NAME) {
// NOTE: Default [white] background is needed on slideMaster1.xml to avoid gray background in Keynote (and Finder previews)
strSlideXml += '<p:bg><p:bgRef idx="1001"><a:schemeClr val="bg1"/></p:bgRef></p:bg>'
}
// STEP 2: Continue slide by starting spTree node
strSlideXml += '<p:spTree>'
strSlideXml += '<p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>'
strSlideXml += '<p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/>'
strSlideXml += '<a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr>'
// STEP 3: Loop over all Slide.data objects and add them to this slide
slide._slideObjects.forEach((slideItemObj: ISlideObject, idx: number) => {
let x = 0
let y = 0
let cx = getSmartParseNumber('75%', 'X', slide._presLayout)
let cy = 0
let placeholderObj: ISlideObject
let locationAttr = ''
let arrTabRows: TableCell[][] = null
let objTabOpts: ObjectOptions = null
let intColCnt = 0
let intColW = 0
let cellOpts: TableCellProps = null
let strXml: string = null
const sizing: ObjectOptions['sizing'] = slideItemObj.options?.sizing
const rounding = slideItemObj.options?.rounding
if (
(slide as PresSlide)._slideLayout !== undefined &&
(slide as PresSlide)._slideLayout._slideObjects !== undefined &&
slideItemObj.options &&
slideItemObj.options.placeholder
) {
placeholderObj = (slide as PresSlide)._slideLayout._slideObjects.filter(
(object: ISlideObject) => object.options.placeholder === slideItemObj.options.placeholder
)[0]
}
// A: Set option vars
slideItemObj.options = slideItemObj.options || {}
if (typeof slideItemObj.options.x !== 'undefined') x = getSmartParseNumber(slideItemObj.options.x, 'X', slide._presLayout)
if (typeof slideItemObj.options.y !== 'undefined') y = getSmartParseNumber(slideItemObj.options.y, 'Y', slide._presLayout)
if (typeof slideItemObj.options.w !== 'undefined') cx = getSmartParseNumber(slideItemObj.options.w, 'X', slide._presLayout)
if (typeof slideItemObj.options.h !== 'undefined') cy = getSmartParseNumber(slideItemObj.options.h, 'Y', slide._presLayout)
// Set w/h now that smart parse is done
let imgWidth = cx
let imgHeight = cy
// If using a placeholder then inherit it's position
if (placeholderObj) {
if (placeholderObj.options.x || placeholderObj.options.x === 0) x = getSmartParseNumber(placeholderObj.options.x, 'X', slide._presLayout)
if (placeholderObj.options.y || placeholderObj.options.y === 0) y = getSmartParseNumber(placeholderObj.options.y, 'Y', slide._presLayout)
if (placeholderObj.options.w || placeholderObj.options.w === 0) cx = getSmartParseNumber(placeholderObj.options.w, 'X', slide._presLayout)
if (placeholderObj.options.h || placeholderObj.options.h === 0) cy = getSmartParseNumber(placeholderObj.options.h, 'Y', slide._presLayout)
}
//
if (slideItemObj.options.flipH) locationAttr += ' flipH="1"'
if (slideItemObj.options.flipV) locationAttr += ' flipV="1"'
if (slideItemObj.options.rotate) locationAttr += ` rot="${convertRotationDegrees(slideItemObj.options.rotate)}"`
// B: Add OBJECT to the current Slide
switch (slideItemObj._type) {
case SLIDE_OBJECT_TYPES.table:
arrTabRows = slideItemObj.arrTabRows
objTabOpts = slideItemObj.options
intColCnt = 0
intColW = 0
// Calc number of columns
// NOTE: Cells may have a colspan, so merely taking the length of the [0] (or any other) row is not
// ....: sufficient to determine column count. Therefore, check each cell for a colspan and total cols as reqd
arrTabRows[0].forEach(cell => {
cellOpts = cell.options || null
intColCnt += cellOpts?.colspan ? Number(cellOpts.colspan) : 1
})
// STEP 1: Start Table XML
// NOTE: Non-numeric cNvPr id values will trigger "presentation needs repair" type warning in MS-PPT-2013
strXml = `<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id="${intTableNum * slide._slideNum + 1}" name="${slideItemObj.options.objectName}"/>`
strXml +=
'<p:cNvGraphicFramePr><a:graphicFrameLocks noGrp="1"/></p:cNvGraphicFramePr>' +
' <p:nvPr><p:extLst><p:ext uri="{D42A27DB-BD31-4B8C-83A1-F6EECF244321}"><p14:modId xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main" val="1579011935"/></p:ext></p:extLst></p:nvPr>' +
'</p:nvGraphicFramePr>'
strXml += `<p:xfrm><a:off x="${x || (x === 0 ? 0 : EMU)}" y="${y || (y === 0 ? 0 : EMU)}"/><a:ext cx="${cx || (cx === 0 ? 0 : EMU)}" cy="${cy || EMU
}"/></p:xfrm>`
strXml += '<a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table"><a:tbl><a:tblPr/>'
// + ' <a:tblPr bandRow="1"/>';
// TODO: Support banded rows, first/last row, etc.
// NOTE: Banding, etc. only shows when using a table style! (or set alt row color if banding)
// <a:tblPr firstCol="0" firstRow="0" lastCol="0" lastRow="0" bandCol="0" bandRow="1">
// STEP 2: Set column widths
// Evenly distribute cols/rows across size provided when applicable (calc them if only overall dimensions were provided)
// A: Col widths provided?
// B: Table Width provided without colW? Then distribute cols
if (Array.isArray(objTabOpts.colW)) {
strXml += '<a:tblGrid>'
for (let col = 0; col < intColCnt; col++) {
let w = inch2Emu(objTabOpts.colW[col])
if (w == null || isNaN(w)) {
w = (typeof slideItemObj.options.w === 'number' ? slideItemObj.options.w : 1) / intColCnt
}
strXml += `<a:gridCol w="${Math.round(w)}"/>`
}
strXml += '</a:tblGrid>'
} else {
intColW = objTabOpts.colW ? objTabOpts.colW : EMU
if (slideItemObj.options.w && !objTabOpts.colW) intColW = Math.round((typeof slideItemObj.options.w === 'number' ? slideItemObj.options.w : 1) / intColCnt)
strXml += '<a:tblGrid>'
for (let colw = 0; colw < intColCnt; colw++) {
strXml += `<a:gridCol w="${intColW}"/>`
}
strXml += '</a:tblGrid>'
}
// STEP 3: Build our row arrays into an actual grid to match the XML we will be building next (ISSUE #36)
// Note row arrays can arrive "lopsided" as in row1:[1,2,3] row2:[3] when first two cols rowspan!,
// so a simple loop below in XML building wont suffice to build table correctly.
// We have to build an actual grid now
/*
EX: (A0:rowspan=3, B1:rowspan=2, C1:colspan=2)
/------|------|------|------\
| A0 | B0 | C0 | D0 |
| | B1 | C1 | |
| | | C2 | D2 |
\------|------|------|------/
*/
// A: add _hmerge cell for colspan. should reserve rowspan
arrTabRows.forEach(cells => {
for (let cIdx = 0; cIdx < cells.length;) {
const cell = cells[cIdx]
const colspan = cell.options?.colspan
const rowspan = cell.options?.rowspan
if (colspan && colspan > 1) {
const vMergeCells = new Array(colspan - 1).fill(undefined).map(() => {
return { _type: SLIDE_OBJECT_TYPES.tablecell, options: { rowspan }, _hmerge: true } as const
})
cells.splice(cIdx + 1, 0, ...vMergeCells)
cIdx += colspan
} else {
cIdx += 1
}
}
})
// B: add _vmerge cell for rowspan. should reserve colspan/_hmerge
arrTabRows.forEach((cells, rIdx) => {
const nextRow = arrTabRows[rIdx + 1]
if (!nextRow) return
cells.forEach((cell, cIdx) => {
const rowspan = cell._rowContinue || cell.options?.rowspan
const colspan = cell.options?.colspan
const _hmerge = cell._hmerge
if (rowspan && rowspan > 1) {
const hMergeCell = { _type: SLIDE_OBJECT_TYPES.tablecell, options: { colspan }, _rowContinue: rowspan - 1, _vmerge: true, _hmerge } as const
nextRow.splice(cIdx, 0, hMergeCell)
}
})
})
// STEP 4: Build table rows/cells
arrTabRows.forEach((cells, rIdx) => {
// A: Table Height provided without rowH? Then distribute rows
let intRowH = 0 // IMPORTANT: Default must be zero for auto-sizing to work
if (Array.isArray(objTabOpts.rowH) && objTabOpts.rowH[rIdx]) intRowH = inch2Emu(Number(objTabOpts.rowH[rIdx]))
else if (objTabOpts.rowH && !isNaN(Number(objTabOpts.rowH))) intRowH = inch2Emu(Number(objTabOpts.rowH))
else if (slideItemObj.options.cy || slideItemObj.options.h) {
intRowH = Math.round(
(slideItemObj.options.h ? inch2Emu(slideItemObj.options.h) : typeof slideItemObj.options.cy === 'number' ? slideItemObj.options.cy : 1) /
arrTabRows.length
)
}
// B: Start row
strXml += `<a:tr h="${intRowH}">`
// C: Loop over each CELL
cells.forEach(cellObj => {
const cell: TableCell = cellObj
const cellSpanAttrs = {
rowSpan: cell.options?.rowspan > 1 ? cell.options.rowspan : undefined,
gridSpan: cell.options?.colspan > 1 ? cell.options.colspan : undefined,
vMerge: cell._vmerge ? 1 : undefined,
hMerge: cell._hmerge ? 1 : undefined,
}
let cellSpanAttrStr = Object.keys(cellSpanAttrs)
.map(k => [k, cellSpanAttrs[k]])
.filter(([, v]) => !!v)
.map(([k, v]) => `${String(k)}="${String(v)}"`)
.join(' ')
if (cellSpanAttrStr) cellSpanAttrStr = ' ' + cellSpanAttrStr
// 1: COLSPAN/ROWSPAN: Add dummy cells for any active colspan/rowspan
if (cell._hmerge || cell._vmerge) {
strXml += `<a:tc${cellSpanAttrStr}><a:tcPr/></a:tc>`
return
}
// 2: OPTIONS: Build/set cell options
const cellOpts = cell.options || {}
cell.options = cellOpts
// B: Inherit some options from table when cell options dont exist
// @see: http://officeopenxml.com/drwTableCellProperties-alignment.php
;['align', 'bold', 'border', 'color', 'fill', 'fontFace', 'fontSize', 'margin', 'textDirection', 'underline', 'valign'].forEach(name => {
if (objTabOpts[name] && !cellOpts[name] && cellOpts[name] !== 0) cellOpts[name] = objTabOpts[name]
})
const cellValign = cellOpts.valign
? ` anchor="${cellOpts.valign.replace(/^c$/i, 'ctr').replace(/^m$/i, 'ctr').replace('center', 'ctr').replace('middle', 'ctr').replace('top', 't').replace('btm', 'b').replace('bottom', 'b')}"`
: ''
const cellTextDir = (cellOpts.textDirection && cellOpts.textDirection !== 'horz') ? ` vert="${cellOpts.textDirection}"` : ''
let fillColor =
cell._optImp?.fill?.color
? cell._optImp.fill.color
: cell._optImp?.fill && typeof cell._optImp.fill === 'string'
? cell._optImp.fill
: ''
fillColor = fillColor || cellOpts.fill ? cellOpts.fill : ''
const cellFill = fillColor ? genXmlColorSelection(fillColor) : ''
let cellMargin = cellOpts.margin === 0 || cellOpts.margin ? cellOpts.margin : DEF_CELL_MARGIN_IN
if (!Array.isArray(cellMargin) && typeof cellMargin === 'number') cellMargin = [cellMargin, cellMargin, cellMargin, cellMargin]
/** FUTURE: DEPRECATED:
* - Backwards-Compat: Oops! Discovered we were still using points for cell margin before v3.8.0 (UGH!)
* - We cant introduce a breaking change before v4.0, so...
*/
let cellMarginXml = ''
if (cellMargin[0] >= 1) {
cellMarginXml = ` marL="${valToPts(cellMargin[3])}" marR="${valToPts(cellMargin[1])}" marT="${valToPts(cellMargin[0])}" marB="${valToPts(
cellMargin[2]
)}"`
} else {
cellMarginXml = ` marL="${inch2Emu(cellMargin[3])}" marR="${inch2Emu(cellMargin[1])}" marT="${inch2Emu(cellMargin[0])}" marB="${inch2Emu(
cellMargin[2]
)}"`
}
// FUTURE: Cell NOWRAP property (textwrap: add to a:tcPr (horzOverflow="overflow" or whatever options exist)
// 4: Set CELL content and properties ==================================
strXml += `<a:tc${cellSpanAttrStr}>${genXmlTextBody(cell)}<a:tcPr${cellMarginXml}${cellValign}${cellTextDir}>`
// strXml += `<a:tc${cellColspan}${cellRowspan}>${genXmlTextBody(cell)}<a:tcPr${cellMarginXml}${cellValign}${cellTextDir}>`
// FIXME: 20200525: ^^^
// <a:tcPr marL="38100" marR="38100" marT="38100" marB="38100" vert="vert270">
// 5: Borders: Add any borders
if (cellOpts.border && Array.isArray(cellOpts.border)) {
// NOTE: *** IMPORTANT! *** LRTB order matters! (Reorder a line below to watch the borders go wonky in MS-PPT-2013!!)
[
{ idx: 3, name: 'lnL' },
{ idx: 1, name: 'lnR' },
{ idx: 0, name: 'lnT' },
{ idx: 2, name: 'lnB' },
].forEach(obj => {
if (cellOpts.border[obj.idx].type !== 'none') {
strXml += `<a:${obj.name} w="${valToPts(cellOpts.border[obj.idx].pt)}" cap="flat" cmpd="sng" algn="ctr">`
strXml += `<a:solidFill>${createColorElement(cellOpts.border[obj.idx].color)}</a:solidFill>`
strXml += `<a:prstDash val="${cellOpts.border[obj.idx].type === 'dash' ? 'sysDash' : 'solid'
}"/><a:round/><a:headEnd type="none" w="med" len="med"/><a:tailEnd type="none" w="med" len="med"/>`
strXml += `</a:${obj.name}>`
} else {
strXml += `<a:${obj.name} w="0" cap="flat" cmpd="sng" algn="ctr"><a:noFill/></a:${obj.name}>`
}
})
}
// 6: Close cell Properties & Cell
strXml += cellFill
strXml += ' </a:tcPr>'
strXml += ' </a:tc>'
})
// D: Complete row
strXml += '</a:tr>'
})
// STEP 5: Complete table
strXml += ' </a:tbl>'
strXml += ' </a:graphicData>'
strXml += ' </a:graphic>'
strXml += '</p:graphicFrame>'
// STEP 6: Set table XML
strSlideXml += strXml
// LAST: Increment counter
intTableNum++
break
case SLIDE_OBJECT_TYPES.text:
case SLIDE_OBJECT_TYPES.placeholder:
// Lines can have zero cy, but text should not
if (!slideItemObj.options.line && cy === 0) cy = EMU * 0.3
// Margin/Padding/Inset for textboxes
if (!slideItemObj.options._bodyProp) slideItemObj.options._bodyProp = {}
if (slideItemObj.options.margin && Array.isArray(slideItemObj.options.margin)) {
slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin[0] || 0)
slideItemObj.options._bodyProp.rIns = valToPts(slideItemObj.options.margin[1] || 0)
slideItemObj.options._bodyProp.bIns = valToPts(slideItemObj.options.margin[2] || 0)
slideItemObj.options._bodyProp.tIns = valToPts(slideItemObj.options.margin[3] || 0)
} else if (typeof slideItemObj.options.margin === 'number') {
slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin)
slideItemObj.options._bodyProp.rIns = valToPts(slideItemObj.options.margin)
slideItemObj.options._bodyProp.bIns = valToPts(slideItemObj.options.margin)
slideItemObj.options._bodyProp.tIns = valToPts(slideItemObj.options.margin)
}
// A: Start SHAPE =======================================================
strSlideXml += '<p:sp>'
// B: The addition of the "txBox" attribute is the sole determiner of if an object is a shape or textbox
strSlideXml += `<p:nvSpPr><p:cNvPr id="${idx + 2}" name="${slideItemObj.options.objectName}">`
// <Hyperlink>
if (slideItemObj.options.hyperlink?.url) {
strSlideXml += `<a:hlinkClick r:id="rId${slideItemObj.options.hyperlink._rId}" tooltip="${slideItemObj.options.hyperlink.tooltip ? encodeXmlEntities(slideItemObj.options.hyperlink.tooltip) : ''}"/>`
}
if (slideItemObj.options.hyperlink?.slide) {
strSlideXml += `<a:hlinkClick r:id="rId${slideItemObj.options.hyperlink._rId}" tooltip="${slideItemObj.options.hyperlink.tooltip ? encodeXmlEntities(slideItemObj.options.hyperlink.tooltip) : ''}" action="ppaction://hlinksldjump"/>`
}
// </Hyperlink>
strSlideXml += '</p:cNvPr>'
strSlideXml += '<p:cNvSpPr' + (slideItemObj.options?.isTextBox ? ' txBox="1"/>' : '/>')
strSlideXml += `<p:nvPr>${slideItemObj._type === 'placeholder' ? genXmlPlaceholder(slideItemObj) : genXmlPlaceholder(placeholderObj)}</p:nvPr>`
strSlideXml += '</p:nvSpPr><p:spPr>'
strSlideXml += `<a:xfrm${locationAttr}>`
strSlideXml += `<a:off x="${x}" y="${y}"/>`
strSlideXml += `<a:ext cx="${cx}" cy="${cy}"/></a:xfrm>`
if (slideItemObj.shape === 'custGeom') {
strSlideXml += '<a:custGeom><a:avLst />'
strSlideXml += '<a:gdLst>'
strSlideXml += '</a:gdLst>'
strSlideXml += '<a:ahLst />'
strSlideXml += '<a:cxnLst>'
strSlideXml += '</a:cxnLst>'
strSlideXml += '<a:rect l="l" t="t" r="r" b="b" />'
strSlideXml += '<a:pathLst>'
strSlideXml += `<a:path w="${cx}" h="${cy}">`
slideItemObj.options.points?.forEach((point, i) => {
if ('curve' in point) {
switch (point.curve.type) {
case 'arc':
strSlideXml += `<a:arcTo hR="${getSmartParseNumber(point.curve.hR, 'Y', slide._presLayout)}" wR="${getSmartParseNumber(
point.curve.wR,
'X',
slide._presLayout
)}" stAng="${convertRotationDegrees(point.curve.stAng)}" swAng="${convertRotationDegrees(point.curve.swAng)}" />`
break
case 'cubic':
strSlideXml += `<a:cubicBezTo>
<a:pt x="${getSmartParseNumber(point.curve.x1, 'X', slide._presLayout)}" y="${getSmartParseNumber(point.curve.y1, 'Y', slide._presLayout)}" />
<a:pt x="${getSmartParseNumber(point.curve.x2, 'X', slide._presLayout)}" y="${getSmartParseNumber(point.curve.y2, 'Y', slide._presLayout)}" />
<a:pt x="${getSmartParseNumber(point.x, 'X', slide._presLayout)}" y="${getSmartParseNumber(point.y, 'Y', slide._presLayout)}" />
</a:cubicBezTo>`
break
case 'quadratic':
strSlideXml += `<a:quadBezTo>
<a:pt x="${getSmartParseNumber(point.curve.x1, 'X', slide._presLayout)}" y="${getSmartParseNumber(point.curve.y1, 'Y', slide._presLayout)}" />
<a:pt x="${getSmartParseNumber(point.x, 'X', slide._presLayout)}" y="${getSmartParseNumber(point.y, 'Y', slide._presLayout)}" />
</a:quadBezTo>`
break
default:
break
}
} else if ('close' in point) {
strSlideXml += '<a:close />'
} else if (point.moveTo || i === 0) {
strSlideXml += `<a:moveTo><a:pt x="${getSmartParseNumber(point.x, 'X', slide._presLayout)}" y="${getSmartParseNumber(
point.y,
'Y',
slide._presLayout
)}" /></a:moveTo>`
} else {
strSlideXml += `<a:lnTo><a:pt x="${getSmartParseNumber(point.x, 'X', slide._presLayout)}" y="${getSmartParseNumber(
point.y,
'Y',
slide._presLayout
)}" /></a:lnTo>`
}
})
strSlideXml += '</a:path>'
strSlideXml += '</a:pathLst>'
strSlideXml += '</a:custGeom>'
} else {
strSlideXml += '<a:prstGeom prst="' + slideItemObj.shape + '"><a:avLst>'
if (slideItemObj.options.rectRadius) {
strSlideXml += `<a:gd name="adj" fmla="val ${Math.round((slideItemObj.options.rectRadius * EMU * 100000) / Math.min(cx, cy))}"/>`
} else if (slideItemObj.options.angleRange) {
for (let i = 0; i < 2; i++) {
const angle = slideItemObj.options.angleRange[i]
strSlideXml += `<a:gd name="adj${i + 1}" fmla="val ${convertRotationDegrees(angle)}" />`
}
if (slideItemObj.options.arcThicknessRatio) {
strSlideXml += `<a:gd name="adj3" fmla="val ${Math.round(slideItemObj.options.arcThicknessRatio * 50000)}" />`
}
}
strSlideXml += '</a:avLst></a:prstGeom>'
}
// Option: FILL
strSlideXml += slideItemObj.options.fill ? genXmlColorSelection(slideItemObj.options.fill) : '<a:noFill/>'
// shape Type: LINE: line color
if (slideItemObj.options.line) {
strSlideXml += slideItemObj.options.line.width ? `<a:ln w="${valToPts(slideItemObj.options.line.width)}">` : '<a:ln>'
if (slideItemObj.options.line.color) strSlideXml += genXmlColorSelection(slideItemObj.options.line)
if (slideItemObj.options.line.dashType) strSlideXml += `<a:prstDash val="${slideItemObj.options.line.dashType}"/>`
if (slideItemObj.options.line.beginArrowType) strSlideXml += `<a:headEnd type="${slideItemObj.options.line.beginArrowType}"/>`
if (slideItemObj.options.line.endArrowType) strSlideXml += `<a:tailEnd type="${slideItemObj.options.line.endArrowType}"/>`
// FUTURE: `endArrowSize` < a: headEnd type = "arrow" w = "lg" len = "lg" /> 'sm' | 'med' | 'lg'(values are 1 - 9, making a 3x3 grid of w / len possibilities)
strSlideXml += '</a:ln>'
}
// EFFECTS > SHADOW: REF: @see http://officeopenxml.com/drwSp-effects.php
if (slideItemObj.options.shadow && slideItemObj.options.shadow.type !== 'none') {
slideItemObj.options.shadow.type = slideItemObj.options.shadow.type || 'outer'
slideItemObj.options.shadow.blur = valToPts(slideItemObj.options.shadow.blur || 8)
slideItemObj.options.shadow.offset = valToPts(slideItemObj.options.shadow.offset || 4)
slideItemObj.options.shadow.angle = Math.round((slideItemObj.options.shadow.angle || 270) * 60000)
slideItemObj.options.shadow.opacity = Math.round((slideItemObj.options.shadow.opacity || 0.75) * 100000)
slideItemObj.options.shadow.color = slideItemObj.options.shadow.color || DEF_TEXT_SHADOW.color
strSlideXml += '<a:effectLst>'
strSlideXml += ` <a:${slideItemObj.options.shadow.type}Shdw ${slideItemObj.options.shadow.type === 'outer' ? 'sx="100000" sy="100000" kx="0" ky="0" algn="bl" rotWithShape="0"' : ''} blurRad="${slideItemObj.options.shadow.blur}" dist="${slideItemObj.options.shadow.offset}" dir="${slideItemObj.options.shadow.angle}">`
strSlideXml += ` <a:srgbClr val="${slideItemObj.options.shadow.color}">`
strSlideXml += ` <a:alpha val="${slideItemObj.options.shadow.opacity}"/></a:srgbClr>`
strSlideXml += ' </a:outerShdw>'
strSlideXml += '</a:effectLst>'
}
/* TODO: FUTURE: Text wrapping (copied from MS-PPTX export)
// Commented out b/c i'm not even sure this works - current code produces text that wraps in shapes and textboxes, so...
if ( slideItemObj.options.textWrap ) {
strSlideXml += '<a:extLst>'
+ '<a:ext uri="{C572A759-6A51-4108-AA02-DFA0A04FC94B}">'
+ '<ma14:wrappingTextBoxFlag xmlns:ma14="http://schemas.microsoft.com/office/mac/drawingml/2011/main" val="1"/>'
+ '</a:ext>'
+ '</a:extLst>';
}
*/
// B: Close shape Properties
strSlideXml += '</p:spPr>'
// C: Add formatted text (text body "bodyPr")
strSlideXml += genXmlTextBody(slideItemObj)
// LAST: Close SHAPE =======================================================
strSlideXml += '</p:sp>'
break
case SLIDE_OBJECT_TYPES.image:
strSlideXml += '<p:pic>'
strSlideXml += ' <p:nvPicPr>'
strSlideXml += `<p:cNvPr id="${idx + 2}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(
slideItemObj.options.altText || slideItemObj.image
)}">`
if (slideItemObj.hyperlink?.url) {
strSlideXml += `<a:hlinkClick r:id="rId${slideItemObj.hyperlink._rId}" tooltip="${slideItemObj.hyperlink.tooltip ? encodeXmlEntities(slideItemObj.hyperlink.tooltip) : ''
}"/>`
}
if (slideItemObj.hyperlink?.slide) {
strSlideXml += `<a:hlinkClick r:id="rId${slideItemObj.hyperlink._rId}" tooltip="${slideItemObj.hyperlink.tooltip ? encodeXmlEntities(slideItemObj.hyperlink.tooltip) : ''
}" action="ppaction://hlinksldjump"/>`
}
strSlideXml += ' </p:cNvPr>'
strSlideXml += ' <p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr>'
strSlideXml += ' <p:nvPr>' + genXmlPlaceholder(placeholderObj) + '</p:nvPr>'
strSlideXml += ' </p:nvPicPr>'
strSlideXml += '<p:blipFill>'
// NOTE: This works for both cases: either `path` or `data` contains the SVG
if (
(slide._relsMedia || []).filter(rel => rel.rId === slideItemObj.imageRid)[0] &&
(slide._relsMedia || []).filter(rel => rel.rId === slideItemObj.imageRid)[0].extn === 'svg'
) {
strSlideXml += `<a:blip r:embed="rId${slideItemObj.imageRid - 1}">`
strSlideXml += slideItemObj.options.transparency ? ` <a:alphaModFix amt="${Math.round((100 - slideItemObj.options.transparency) * 1000)}"/>` : ''
strSlideXml += ' <a:extLst>'
strSlideXml += ' <a:ext uri="{96DAC541-7B7A-43D3-8B79-37D633B846F1}">'
strSlideXml += ` <asvg:svgBlip xmlns:asvg="http://schemas.microsoft.com/office/drawing/2016/SVG/main" r:embed="rId${slideItemObj.imageRid}"/>`
strSlideXml += ' </a:ext>'
strSlideXml += ' </a:extLst>'
strSlideXml += '</a:blip>'
} else {
strSlideXml += `<a:blip r:embed="rId${slideItemObj.imageRid}">`
strSlideXml += slideItemObj.options.transparency ? `<a:alphaModFix amt="${Math.round((100 - slideItemObj.options.transparency) * 1000)}"/>` : ''
strSlideXml += '</a:blip>'
}
if (sizing?.type) {
const boxW = sizing.w ? getSmartParseNumber(sizing.w, 'X', slide._presLayout) : cx
const boxH = sizing.h ? getSmartParseNumber(sizing.h, 'Y', slide._presLayout) : cy
const boxX = getSmartParseNumber(sizing.x || 0, 'X', slide._presLayout)
const boxY = getSmartParseNumber(sizing.y || 0, 'Y', slide._presLayout)
strSlideXml += ImageSizingXml[sizing.type]({ w: imgWidth, h: imgHeight }, { w: boxW, h: boxH, x: boxX, y: boxY })
imgWidth = boxW
imgHeight = boxH
} else {
strSlideXml += ' <a:stretch><a:fillRect/></a:stretch>'
}
strSlideXml += '</p:blipFill>'
strSlideXml += '<p:spPr>'
strSlideXml += ' <a:xfrm' + locationAttr + '>'
strSlideXml += ` <a:off x="${x}" y="${y}"/>`
strSlideXml += ` <a:ext cx="${imgWidth}" cy="${imgHeight}"/>`
strSlideXml += ' </a:xfrm>'
strSlideXml += ` <a:prstGeom prst="${rounding ? 'ellipse' : 'rect'}"><a:avLst/></a:prstGeom>`
// EFFECTS > SHADOW: REF: @see http://officeopenxml.com/drwSp-effects.php
if (slideItemObj.options.shadow && slideItemObj.options.shadow.type !== 'none') {
slideItemObj.options.shadow.type = slideItemObj.options.shadow.type || 'outer'
slideItemObj.options.shadow.blur = valToPts(slideItemObj.options.shadow.blur || 8)
slideItemObj.options.shadow.offset = valToPts(slideItemObj.options.shadow.offset || 4)
slideItemObj.options.shadow.angle = Math.round((slideItemObj.options.shadow.angle || 270) * 60000)
slideItemObj.options.shadow.opacity = Math.round((slideItemObj.options.shadow.opacity || 0.75) * 100000)
slideItemObj.options.shadow.color = slideItemObj.options.shadow.color || DEF_TEXT_SHADOW.color
strSlideXml += '<a:effectLst>'
strSlideXml += `<a:${slideItemObj.options.shadow.type}Shdw ${slideItemObj.options.shadow.type === 'outer' ? 'sx="100000" sy="100000" kx="0" ky="0" algn="bl" rotWithShape="0"' : ''} blurRad="${slideItemObj.options.shadow.blur}" dist="${slideItemObj.options.shadow.offset}" dir="${slideItemObj.options.shadow.angle}">`
strSlideXml += `<a:srgbClr val="${slideItemObj.options.shadow.color}">`
strSlideXml += `<a:alpha val="${slideItemObj.options.shadow.opacity}"/></a:srgbClr>`
strSlideXml += `</a:${slideItemObj.options.shadow.type}Shdw>`
strSlideXml += '</a:effectLst>'
}
strSlideXml += '</p:spPr>'
strSlideXml += '</p:pic>'
break
case SLIDE_OBJECT_TYPES.media:
if (slideItemObj.mtype === 'online') {
strSlideXml += '<p:pic>'
strSlideXml += ' <p:nvPicPr>'
// IMPORTANT: <p:cNvPr id="" value is critical - if its not the same number as preview image `rId`, PowerPoint throws error!
strSlideXml += `<p:cNvPr id="${slideItemObj.mediaRid + 2}" name="${slideItemObj.options.objectName}"/>`
strSlideXml += ' <p:cNvPicPr/>'
strSlideXml += ' <p:nvPr>'
strSlideXml += ` <a:videoFile r:link="rId${slideItemObj.mediaRid}"/>`
strSlideXml += ' </p:nvPr>'
strSlideXml += ' </p:nvPicPr>'
// NOTE: `blip` is diferent than videos; also there's no preview "p:extLst" above but exists in videos
strSlideXml += ` <p:blipFill><a:blip r:embed="rId${slideItemObj.mediaRid + 1}"/><a:stretch><a:fillRect/></a:stretch></p:blipFill>` // NOTE: Preview image is required!
strSlideXml += ' <p:spPr>'
strSlideXml += ` <a:xfrm${locationAttr}><a:off x="${x}" y="${y}"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm>`
strSlideXml += ' <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>'
strSlideXml += ' </p:spPr>'
strSlideXml += '</p:pic>'
} else {
strSlideXml += '<p:pic>'
strSlideXml += ' <p:nvPicPr>'
// IMPORTANT: <p:cNvPr id="" value is critical - if not the same number as preiew image rId, PowerPoint throws error!
strSlideXml += `<p:cNvPr id="${slideItemObj.mediaRid + 2}" name="${slideItemObj.options.objectName
}"><a:hlinkClick r:id="" action="ppaction://media"/></p:cNvPr>`
strSlideXml += ' <p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr>'
strSlideXml += ' <p:nvPr>'
strSlideXml += ` <a:videoFile r:link="rId${slideItemObj.mediaRid}"/>`
strSlideXml += ' <p:extLst>'
strSlideXml += ' <p:ext uri="{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}">'
strSlideXml += ` <p14:media xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main" r:embed="rId${slideItemObj.mediaRid + 1}"/>`
strSlideXml += ' </p:ext>'
strSlideXml += ' </p:extLst>'
strSlideXml += ' </p:nvPr>'
strSlideXml += ' </p:nvPicPr>'
strSlideXml += ` <p:blipFill><a:blip r:embed="rId${slideItemObj.mediaRid + 2}"/><a:stretch><a:fillRect/></a:stretch></p:blipFill>` // NOTE: Preview image is required!
strSlideXml += ' <p:spPr>'
strSlideXml += ` <a:xfrm${locationAttr}><a:off x="${x}" y="${y}"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm>`
strSlideXml += ' <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>'
strSlideXml += ' </p:spPr>'
strSlideXml += '</p:pic>'
}
break
case SLIDE_OBJECT_TYPES.chart:
strSlideXml += '<p:graphicFrame>'
strSlideXml += ' <p:nvGraphicFramePr>'
strSlideXml += ` <p:cNvPr id="${idx + 2}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || '')}"/>`
strSlideXml += ' <p:cNvGraphicFramePr/>'
strSlideXml += ` <p:nvPr>${genXmlPlaceholder(placeholderObj)}</p:nvPr>`
strSlideXml += ' </p:nvGraphicFramePr>'
strSlideXml += ` <p:xfrm><a:off x="${x}" y="${y}"/><a:ext cx="${cx}" cy="${cy}"/></p:xfrm>`
strSlideXml += ' <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">'
strSlideXml += ' <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/chart">'
strSlideXml += ` <c:chart r:id="rId${slideItemObj.chartRid}" xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart"/>`
strSlideXml += ' </a:graphicData>'
strSlideXml += ' </a:graphic>'
strSlideXml += '</p:graphicFrame>'
break
case SLIDE_OBJECT_TYPES.formula: {
const fmtFontSz = slideItemObj.options?.fontSize ? ` sz="${Math.round(slideItemObj.options.fontSize * 100)}"` : ''
const mjcVal = slideItemObj.formulaAlign === 'left' ? 'left' : slideItemObj.formulaAlign === 'right' ? 'right' : 'centerGroup'
strSlideXml += '<mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">'
strSlideXml += '<mc:Choice Requires="a14">'
strSlideXml += '<p:sp>'
strSlideXml += ' <p:nvSpPr>'
strSlideXml += ` <p:cNvPr id="${idx + 2}" name="${slideItemObj.options?.objectName || ''}"/>`
strSlideXml += ' <p:cNvSpPr/>'
strSlideXml += ' <p:nvPr/>'
strSlideXml += ' </p:nvSpPr>'
strSlideXml += ' <p:spPr>'
strSlideXml += ` <a:xfrm><a:off x="${x}" y="${y}"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm>`
strSlideXml += ' <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>'
strSlideXml += ' </p:spPr>'
strSlideXml += ' <p:txBody>'
strSlideXml += ' <a:bodyPr wrap="square" rtlCol="0"><a:spAutoFit/></a:bodyPr>'
strSlideXml += ' <a:lstStyle/>'
strSlideXml += ' <a:p>'
strSlideXml += ` <a:pPr><a:defRPr${fmtFontSz}/></a:pPr>`
strSlideXml += ' <a14:m>'
strSlideXml += ' <m:oMathPara>'
strSlideXml += ` <m:oMathParaPr><m:jc m:val="${mjcVal}"/></m:oMathParaPr>`
strSlideXml += ` ${slideItemObj.formula}`
strSlideXml += ' </m:oMathPara>'
strSlideXml += ' </a14:m>'
strSlideXml += ' </a:p>'
strSlideXml += ' </p:txBody>'
strSlideXml += '</p:sp>'
strSlideXml += '</mc:Choice>'
strSlideXml += '<mc:Fallback>'
strSlideXml += '<p:sp>'
strSlideXml += ' <p:nvSpPr>'
strSlideXml += ` <p:cNvPr id="${idx + 2}" name="${slideItemObj.options?.objectName || ''}"/>`
strSlideXml += ' <p:cNvSpPr/>'
strSlideXml += ' <p:nvPr/>'
strSlideXml += ' </p:nvSpPr>'
strSlideXml += ' <p:spPr>'
strSlideXml += ` <a:xfrm><a:off x="${x}" y="${y}"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm>`
strSlideXml += ' <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>'
strSlideXml += ' </p:spPr>'
strSlideXml += ' <p:txBody>'
strSlideXml += ' <a:bodyPr wrap="square" rtlCol="0"><a:spAutoFit/></a:bodyPr>'
strSlideXml += ' <a:lstStyle/>'
strSlideXml += ' <a:p><a:r><a:rPr lang="en-US" dirty="0"/><a:t>[Formula]</a:t></a:r></a:p>'
strSlideXml += ' </p:txBody>'
strSlideXml += '</p:sp>'
strSlideXml += '</mc:Fallback>'
strSlideXml += '</mc:AlternateContent>'
break
}
default:
strSlideXml += ''
break
}
})
// STEP 4: Add slide numbers (if any) last
if (slide._slideNumberProps) {
// Set some defaults (done here b/c SlideNumber canbe added to masters or slides and has numerous entry points)
if (!slide._slideNumberProps.align) slide._slideNumberProps.align = 'left'
strSlideXml += '<p:sp>'
strSlideXml += ' <p:nvSpPr>'
strSlideXml += ' <p:cNvPr id="25" name="Slide Number Placeholder 0"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr>'
strSlideXml += ' <p:nvPr><p:ph type="sldNum" sz="quarter" idx="4294967295"/></p:nvPr>'
strSlideXml += ' </p:nvSpPr>'
strSlideXml += ' <p:spPr>'
strSlideXml += '<a:xfrm>' +
`<a:off x="${getSmartParseNumber(slide._slideNumberProps.x, 'X', slide._presLayout)}" y="${getSmartParseNumber(slide._slideNumberProps.y, 'Y', slide._presLayout)}"/>` +
`<a:ext cx="${slide._slideNumberProps.w ? getSmartParseNumber(slide._slideNumberProps.w, 'X', slide._presLayout) : '800000'}" cy="${slide._slideNumberProps.h ? getSmartParseNumber(slide._slideNumberProps.h, 'Y', slide._presLayout) : '300000'}"/>` +
'</a:xfrm>' +
' <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>' +
' <a:extLst><a:ext uri="{C572A759-6A51-4108-AA02-DFA0A04FC94B}"><ma14:wrappingTextBoxFlag val="0" xmlns:ma14="http://schemas.microsoft.com/office/mac/drawingml/2011/main"/></a:ext></a:extLst>' +
'</p:spPr>'
strSlideXml += '<p:txBody>'
strSlideXml += '<a:bodyPr'
if (slide._slideNumberProps.margin && Array.isArray(slide._slideNumberProps.margin)) {
strSlideXml += ` lIns="${valToPts(slide._slideNumberProps.margin[3] || 0)}"`
strSlideXml += ` tIns="${valToPts(slide._slideNumberProps.margin[0] || 0)}"`
strSlideXml += ` rIns="${valToPts(slide._slideNumberProps.margin[1] || 0)}"`
strSlideXml += ` bIns="${valToPts(slide._slideNumberProps.margin[2] || 0)}"`
} else if (typeof slide._slideNumberProps.margin === 'number') {
strSlideXml += ` lIns="${valToPts(slide._slideNumberProps.margin || 0)}"`
strSlideXml += ` tIns="${valToPts(slide._slideNumberProps.margin || 0)}"`
strSlideXml += ` rIns="${valToPts(slide._slideNumberProps.margin || 0)}"`
strSlideXml += ` bIns="${valToPts(slide._slideNumberProps.margin || 0)}"`
}
if (slide._slideNumberProps.valign) {
strSlideXml += ` anchor="${slide._slideNumberProps.valign.replace('top', 't').replace('middle', 'ctr').replace('bottom', 'b')}"`
}
strSlideXml += '/>'
strSlideXml += ' <a:lstStyle><a:lvl1pPr>'
if (slide._slideNumberProps.fontFace || slide._slideNumberProps.fontSize || slide._slideNumberProps.color) {
strSlideXml += `<a:defRPr sz="${Math.round((slide._slideNumberProps.fontSize || 12) * 100)}">`
if (slide._slideNumberProps.color) strSlideXml += genXmlColorSelection(slide._slideNumberProps.color)
if (slide._slideNumberProps.fontFace) { strSlideXml += `<a:latin typeface="${slide._slideNumberProps.fontFace}"/><a:ea typeface="${slide._slideNumberProps.fontFace}"/><a:cs typeface="${slide._slideNumberProps.fontFace}"/>` }
strSlideXml += '</a:defRPr>'
}
strSlideXml += '</a:lvl1pPr></a:lstStyle>'
strSlideXml += '<a:p>'
if (slide._slideNumberProps.align.startsWith('l')) strSlideXml += '<a:pPr algn="l"/>'
else if (slide._slideNumberProps.align.startsWith('c')) strSlideXml += '<a:pPr algn="ctr"/>'
else if (slide._slideNumberProps.align.startsWith('r')) strSlideXml += '<a:pPr algn="r"/>'
else strSlideXml += '<a:pPr algn="l"/>'
strSlideXml += `<a:fld id="${SLDNUMFLDID}" type="slidenum"><a:rPr b="${slide._slideNumberProps.bold ? 1 : 0}" lang="en-US"/>`
strSlideXml += `<a:t>${slide._slideNum}</a:t></a:fld><a:endParaRPr lang="en-US"/></a:p>`
strSlideXml += '</p:txBody></p:sp>'
}
// STEP 5: Close spTree and finalize slide XML
strSlideXml += '</p:spTree>'
strSlideXml += '</p:cSld>'
// LAST: Return
return strSlideXml
}
/**
* Transforms slide relations to XML string.
* Extra relations that are not dynamic can be passed using the 2nd arg (e.g. theme relation in master file).
* These relations use rId series that starts with 1-increased maximum of rIds used for dynamic relations.
* @param {PresSlide | SlideLayout} slide - slide object whose relations are being transformed
* @param {{ target: string; type: string }[]} defaultRels - array of default relations
* @return {string} XML
*/
function slideObjectRelationsToXml (slide: PresSlide | SlideLayout, defaultRels: Array<{ target: string, type: string }>): string {
let lastRid = 0 // stores maximum rId used for dynamic relations
let strXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + CRLF + '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
// STEP 1: Add all rels for this Slide
slide._rels.forEach((rel: ISlideRel) => {
lastRid = Math.max(lastRid, rel.rId)
if (rel.type.toLowerCase().includes('hyperlink')) {
if (rel.data === 'slide') {
strXml += `<Relationship Id="rId${rel.rId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slide${rel.Target}.xml"/>`
} else {
strXml += `<Relationship Id="rId${rel.rId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="${rel.Target}" TargetMode="External"/>`
}
} else if (rel.type.toLowerCase().includes('notesSlide')) {
strXml += `<Relationship Id="rId${rel.rId}" Target="${rel.Target}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide"/>`
}
})
; (slide._relsChart || []).forEach((rel: ISlideRelChart) => {
lastRid = Math.max(lastRid, rel.rId)
strXml += `<Relationship Id="rId${rel.rId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart" Target="${rel.Target}"/>`
})
; (slide._relsMedia || []).forEach((rel: ISlideRelMedia) => {
const relRid = rel.rId.toString()
lastRid = Math.max(lastRid, rel.rId)
if (rel.type.toLowerCase().includes('image')) {
strXml += '<Relationship Id="rId' + relRid + '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="' + rel.Target + '"/>'
} else if (rel.type.toLowerCase().includes('audio')) {
// As media has *TWO* rel entries per item, check for first one, if found add second rel with alt style
if (strXml.includes(' Target="' + rel.Target + '"')) {
strXml += '<Relationship Id="rId' + relRid + '" Type="http://schemas.microsoft.com/office/2007/relationships/media" Target="' + rel.Target + '"/>'
} else {
strXml += '<Relationship Id="rId' + relRid + '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/audio" Target="' + rel.Target + '"/>'
}
} else if (rel.type.toLowerCase().includes('video')) {
// As media has *TWO* rel entries per item, check for first one, if found add second rel with alt style
if (strXml.includes(' Target="' + rel.Target + '"')) {
strXml += '<Relationship Id="rId' + relRid + '" Type="http://schemas.microsoft.com/office/2007/relationships/media" Target="' + rel.Target + '"/>'
} else {
strXml += '<Relationship Id="rId' + relRid + '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/video" Target="' + rel.Target + '"/>'
}
} else if (rel.type.toLowerCase().includes('online')) {
// As media has *TWO* rel entries per item, check for first one, if found add second rel with alt style
if (strXml.includes(' Target="' + rel.Target + '"')) {
strXml += '<Relationship Id="rId' + relRid + '" Type="http://schemas.microsoft.com/office/2007/relationships/image" Target="' + rel.Target + '"/>'
} else {
strXml += '<Relationship Id="rId' + relRid + '" Target="' + rel.Target + '" TargetMode="External" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/video"/>'
}
}
})
// STEP 2: Add default rels
defaultRels.forEach((rel, idx) => {
strXml += `<Relationship Id="rId${lastRid + idx + 1}" Type="${rel.type}" Target="${rel.target}"/>`
})
strXml += '</Relationships>'
return strXml
}
/**
* Generate XML Paragraph Properties
* @param {ISlideObject|TextProps} textObj - text object
* @param {boolean} isDefault - array of default relations
* @return {string} XML
*/
function genXmlParagraphProperties (textObj: ISlideObject | TextProps, isDefault: boolean): string {
let strXmlBullet = ''
let strXmlLnSpc = ''
let strXmlParaSpc = ''
let strXmlTabStops = ''
const tag = isDefault ? 'a:lvl1pPr' : 'a:pPr'
let bulletMarL = valToPts(DEF_BULLET_MARGIN)
let paragraphPropXml = `<${tag}${textObj.options.rtlMode ? ' rtl="1" ' : ''}`
// A: Build paragraphProperties
{
// OPTION: align
if (textObj.options.align) {
switch (textObj.options.align) {
case 'left':
paragraphPropXml += ' algn="l"'
break
case 'right':
paragraphPropXml += ' algn="r"'
break
case 'center':
paragraphPropXml += ' algn="ctr"'
break
case 'justify':
paragraphPropXml += ' algn="just"'
break
default:
paragraphPropXml += ''
break
}
}
if (textObj.options.lineSpacing) {
strXmlLnSpc = `<a:lnSpc><a:spcPts val="${Math.round(textObj.options.lineSpacing * 100)}"/></a:lnSpc>`
} else if (textObj.options.lineSpacingMultiple) {
strXmlLnSpc = `<a:lnSpc><a:spcPct val="${Math.round(textObj.options.lineSpacingMultiple * 100000)}"/></a:lnSpc>`
}
// OPTION: indent
if (textObj.options.indentLevel && !isNaN(Number(textObj.options.indentLevel)) && textObj.options.indentLevel > 0) {
paragraphPropXml += ` lvl="${textObj.options.indentLevel}"`
}
// OPTION: Paragraph Spacing: Before/After
if (textObj.options.paraSpaceBefore && !isNaN(Number(textObj.options.paraSpaceBefore)) && textObj.options.paraSpaceBefore > 0) {
strXmlParaSpc += `<a:spcBef><a:spcPts val="${Math.round(textObj.options.paraSpaceBefore * 100)}"/></a:spcBef>`
}
if (textObj.options.paraSpaceAfter && !isNaN(Number(textObj.options.paraSpaceAfter)) && textObj.options.paraSpaceAfter > 0) {
strXmlParaSpc += `<a:spcAft><a:spcPts val="${Math.round(textObj.options.paraSpaceAfter * 100)}"/></a:spcAft>`
}
// OPTION: bullet
// NOTE: OOXML uses the unicode character set for Bullets
// EX: Unicode Character 'BULLET' (U+2022) ==> '<a:buChar char="&#x2022;"/>'
if (typeof textObj.options.bullet === 'object') {
if (textObj?.options?.bullet?.indent) bulletMarL = valToPts(textObj.options.bullet.indent)
if (textObj.options.bullet.type) {
if (textObj.options.bullet.type.toString().toLowerCase() === 'number') {
paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL
}" indent="-${bulletMarL}"`
strXmlBullet = `<a:buSzPct val="100000"/><a:buFont typeface="+mj-lt"/><a:buAutoNum type="${textObj.options.bullet.style || 'arabicPeriod'}" startAt="${textObj.options.bullet.numberStartAt || textObj.options.bullet.startAt || '1'
}"/>`
}
} else if (textObj.options.bullet.characterCode) {
let bulletCode = `&#x${textObj.options.bullet.characterCode};`
// Check value for hex-ness (s/b 4 char hex)
if (!/^[0-9A-Fa-f]{4}$/.test(textObj.options.bullet.characterCode)) {
console.warn('Warning: `bullet.characterCode should be a 4-digit unicode charatcer (ex: 22AB)`!')
bulletCode = BULLET_TYPES.DEFAULT
}
paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL
}" indent="-${bulletMarL}"`
strXmlBullet = '<a:buSzPct val="100000"/><a:buChar char="' + bulletCode + '"/>'
} else if (textObj.options.bullet.code) {
// @deprecated `bullet.code` v3.3.0
let bulletCode = `&#x${textObj.options.bullet.code};`
// Check value for hex-ness (s/b 4 char hex)
if (!/^[0-9A-Fa-f]{4}$/.test(textObj.options.bullet.code)) {
console.warn('Warning: `bullet.code should be a 4-digit hex code (ex: 22AB)`!')
bulletCode = BULLET_TYPES.DEFAULT
}
paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL
}" indent="-${bulletMarL}"`
strXmlBullet = '<a:buSzPct val="100000"/><a:buChar char="' + bulletCode + '"/>'
} else {
paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL
}" indent="-${bulletMarL}"`
strXmlBullet = `<a:buSzPct val="100000"/><a:buChar char="${BULLET_TYPES.DEFAULT}"/>`
}
} else if (textObj.options.bullet) {
paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL
}" indent="-${bulletMarL}"`
strXmlBullet = `<a:buSzPct val="100000"/><a:buChar char="${BULLET_TYPES.DEFAULT}"/>`
} else if (!textObj.options.bullet) {
// We only add this when the user explicitely asks for no bullet, otherwise, it can override the master defaults!
paragraphPropXml += ' indent="0" marL="0"' // FIX: ISSUE#589 - specify zero indent and marL or default will be hanging paragraph
strXmlBullet = '<a:buNone/>'
}
// OPTION: tabStops
if (textObj.options.tabStops && Array.isArray(textObj.options.tabStops)) {
const tabStopsXml = textObj.options.tabStops.map(stop => `<a:tab pos="${inch2Emu(stop.position || 1)}" algn="${stop.alignment || 'l'}"/>`).join('')
strXmlTabStops = `<a:tabLst>${tabStopsXml}</a:tabLst>`
}
// B: Close Paragraph-Properties
// IMPORTANT: strXmlLnSpc, strXmlParaSpc, and strXmlBullet require strict ordering - anything out of order is ignored. (PPT-Online, PPT for Mac)
paragraphPropXml += '>' + strXmlLnSpc + strXmlParaSpc + strXmlBullet + strXmlTabStops
if (isDefault) paragraphPropXml += genXmlTextRunProperties(textObj.options, true)
paragraphPropXml += '</' + tag + '>'
}
return paragraphPropXml
}
/**
* Generate XML Text Run Properties (`a:rPr`)
* @param {ObjectOptions|TextPropsOptions} opts - text options
* @param {boolean} isDefault - whether these are the default text run properties
* @return {string} XML
*/
function genXmlTextRunProperties (opts: ObjectOptions | TextPropsOptions, isDefault: boolean): string {
let runProps = ''
const runPropsTag = isDefault ? 'a:defRPr' : 'a:rPr'
// BEGIN runProperties (ex: `<a:rPr lang="en-US" sz="1600" b="1" dirty="0">`)
runProps += '<' + runPropsTag + ' lang="' + (opts.lang ? opts.lang : 'en-US') + '"' + (opts.lang ? ' altLang="en-US"' : '')
runProps += opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : '' // NOTE: Use round so sizes like '7.5' wont cause corrupt presentations
runProps += opts?.bold ? ` b="${opts.bold ? '1' : '0'}"` : ''
runProps += opts?.italic ? ` i="${opts.italic ? '1' : '0'}"` : ''
runProps += opts?.strike ? ` strike="${typeof opts.strike === 'string' ? opts.strike : 'sngStrike'}"` : ''
if (typeof opts.underline === 'object' && opts.underline?.style) {
runProps += ` u="${opts.underline.style}"`
} else if (typeof opts.underline === 'string') {
// DEPRECATED: opts.underline is an object as of v3.5.0
runProps += ` u="${String(opts.underline)}"`
} else if (opts.hyperlink) {
runProps += ' u="sng"'
}
if (opts.baseline) {
runProps += ` baseline="${Math.round(opts.baseline * 50)}"`
} else if (opts.subscript) {
runProps += ' baseline="-40000"'
} else if (opts.superscript) {
runProps += ' baseline="30000"'
}
runProps += opts.charSpacing ? ` spc="${Math.round(opts.charSpacing * 100)}" kern="0"` : '' // IMPORTANT: Also disable kerning; otherwise text won't actually expand
runProps += ' dirty="0">'
// Color / Font / Highlight / Outline are children of <a:rPr>, so add them now before closing the runProperties tag
if (opts.color || opts.fontFace || opts.outline || (typeof opts.underline === 'object' && opts.underline.color)) {
if (opts.outline && typeof opts.outline === 'object') {
runProps += `<a:ln w="${valToPts(opts.outline.size || 0.75)}">${genXmlColorSelection(opts.outline.color || 'FFFFFF')}</a:ln>`
}
if (opts.color) runProps += genXmlColorSelection({ color: opts.color, transparency: opts.transparency })
if (opts.highlight) runProps += `<a:highlight>${createColorElement(opts.highlight)}</a:highlight>`
if (typeof opts.underline === 'object' && opts.underline.color) runProps += `<a:uFill>${genXmlColorSelection(opts.underline.color)}</a:uFill>`
if (opts.glow) runProps += `<a:effectLst>${createGlowElement(opts.glow, DEF_TEXT_GLOW)}</a:effectLst>`
if (opts.fontFace) {
// NOTE: 'cs' = Complex Script, 'ea' = East Asian (use "-120" instead of "0" - per Issue #174); ea must come first (Issue #174)
runProps += `<a:latin typeface="${opts.fontFace}" pitchFamily="34" charset="0"/><a:ea typeface="${opts.fontFace}" pitchFamily="34" charset="-122"/><a:cs typeface="${opts.fontFace}" pitchFamily="34" charset="-120"/>`
}
}
// Hyperlink support
if (opts.hyperlink) {
if (typeof opts.hyperlink !== 'object') throw new Error('ERROR: text `hyperlink` option should be an object. Ex: `hyperlink:{url:\'https://github.com\'}` ')
else if (!opts.hyperlink.url && !opts.hyperlink.slide) throw new Error('ERROR: \'hyperlink requires either `url` or `slide`\'')
else if (opts.hyperlink.url) {
// runProps += '<a:uFill>'+ genXmlColorSelection('0000FF') +'</a:uFill>'; // Breaks PPT2010! (Issue#74)
runProps += `<a:hlinkClick r:id="rId${opts.hyperlink._rId}" invalidUrl="" action="" tgtFrame="" tooltip="${opts.hyperlink.tooltip ? encodeXmlEntities(opts.hyperlink.tooltip) : ''
}" history="1" highlightClick="0" endSnd="0"${opts.color ? '>' : '/>'}`
} else if (opts.hyperlink.slide) {
runProps += `<a:hlinkClick r:id="rId${opts.hyperlink._rId}" action="ppaction://hlinksldjump" tooltip="${opts.hyperlink.tooltip ? encodeXmlEntities(opts.hyperlink.tooltip) : ''
}"${opts.color ? '>' : '/>'}`
}
if (opts.color) {
runProps += ' <a:extLst>'
runProps += ' <a:ext uri="{A12FA001-AC4F-418D-AE19-62706E023703}">'
runProps += ' <ahyp:hlinkClr xmlns:ahyp="http://schemas.microsoft.com/office/drawing/2018/hyperlinkcolor" val="tx"/>'
runProps += ' </a:ext>'
runProps += ' </a:extLst>'
runProps += '</a:hlinkClick>'
}
}
// END runProperties
runProps += `</${runPropsTag}>`
return runProps
}
/**
* Build textBody text runs [`<a:r></a:r>`] for paragraphs [`<a:p>`]
* @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:
/*
<a:p>
<a:pPr algn="r"/>
<a:endParaRPr lang="en-US" sz="3600" dirty="0"/>
</a:p>
*/
// NOT this:
/*
<a:p>
<a:pPr algn="r"/>
<a:r>
<a:rPr lang="en-US" sz="3600" dirty="0">
<a:solidFill>
<a:schemeClr val="accent5"/>
</a:solidFill>
<a:latin typeface="Times" pitchFamily="34" charset="0"/>
<a:ea typeface="Times" pitchFamily="34" charset="-122"/>
<a:cs typeface="Times" pitchFamily="34" charset="-120"/>
</a:rPr>
<a:t></a:t>
</a:r>
<a:endParaRPr lang="en-US" dirty="0"/>
</a:p>
*/
// Return paragraph with text run
return textObj.text ? `<a:r>${genXmlTextRunProperties(textObj.options, false)}<a:t>${encodeXmlEntities(textObj.text)}</a:t></a:r>` : ''
}
/**
* Builds `<a:bodyPr></a:bodyPr>` tag for "genXmlTextBody()"
* @param {ISlideObject | TableCell} slideObject - various options
* @return {string} XML string
*/
function genXmlBodyProperties (slideObject: ISlideObject | TableCell): string {
let bodyProperties = '<a:bodyPr'
if (slideObject && slideObject._type === SLIDE_OBJECT_TYPES.text && slideObject.options._bodyProp) {
// PPT-2019 EX: <a:bodyPr wrap="square" lIns="1270" tIns="1270" rIns="1270" bIns="1270" rtlCol="0" anchor="ctr"/>
// 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 <a:bodyPr element
bodyProperties += '>'
/**
* 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 '<a:noAutofit/>' 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 += '<a:normAutofit fontScale="85000" lnSpcReduction="20000"/>' // MS-PPT > Format shape > Text Options: "Shrink text on overflow"
else if (slideObject.options.fit === 'shrink') bodyProperties += '<a:normAutofit/>'
else if (slideObject.options.fit === 'resize') bodyProperties += '<a:spAutoFit/>'
}
//
// DEPRECATED: below (@deprecated v3.3.0)
if (slideObject.options.shrinkText) bodyProperties += '<a:normAutofit/>' // 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 '<a:noAutofit/>' in lieu of '' below causes issues in PPT-2013
*/
bodyProperties += slideObject.options._bodyProp.autoFit ? '<a:spAutoFit/>' : ''
// LAST: Close _bodyProp
bodyProperties += '</a:bodyPr>'
} else {
// DEFAULT:
bodyProperties += ' wrap="square" rtlCol="0">'
bodyProperties += '</a:bodyPr>'
}
// LAST: Return Close _bodyProp
return slideObject._type === SLIDE_OBJECT_TYPES.tablecell ? '<a:bodyPr/>' : 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 <p>-aragraph's
* @note Bullets are a paragragh-level formatting device
* @template
* <p:txBody>
* <a:bodyPr wrap="square" rtlCol="0">
* <a:spAutoFit/>
* </a:bodyPr>
* <a:lstStyle/>
* <a:p>
* <a:pPr algn="ctr"/>
* <a:r>
* <a:rPr lang="en-US" dirty="0" err="1"/>
* <a:t>textbox text</a:t>
* </a:r>
* <a:endParaRPr lang="en-US" dirty="0"/>
* </a:p>
* </p:txBody>
* @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 ? '<a:txBody>' : '<p:txBody>'
// 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 += '<a:lstStyle><a:lvl1pPr algn="l"/></a:lstStyle>'
else if (slideObj._type === 'placeholder') strSlideXml += `<a:lstStyle>${genXmlParagraphProperties(slideObj, true)}</a:lstStyle>`
else strSlideXml += '<a:lstStyle/>'
}
/* 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 += '<a:p>'
// NOTE: `rtlMode` is like other opts, its propagated up to each text:options, so just check the 1st one
let paragraphPropXml = `<a:pPr ${line[0].options?.rtlMode ? ' rtl="1" ' : ''}`
// B: Start paragraph, loop over lines and add text runs
line.forEach((textObj, idx) => {
// 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 += '<a:br/>'
}
// 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('<a:pPr></a:pPr>', '') // 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 += `<a:endParaRPr lang="${opts.lang || 'en-US'}"` + (opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : '') + ' dirty="0">'
strSlideXml += `<a:latin typeface="${opts.fontFace}" charset="0"/>`
strSlideXml += `<a:ea typeface="${opts.fontFace}" charset="0"/>`
strSlideXml += `<a:cs typeface="${opts.fontFace}" charset="0"/>`
strSlideXml += '</a:endParaRPr>'
} else {
strSlideXml += `<a:endParaRPr lang="${opts.lang || 'en-US'}"` + (opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : '') + ' dirty="0"/>'
}
} else if (reqsClosingFontSize) {
// Empty [lineBreak] lines should not contain runProp, however, they need to specify fontSize in `endParaRPr`
strSlideXml += `<a:endParaRPr lang="${opts.lang || 'en-US'}"` + (opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : '') + ' dirty="0"/>'
} else {
strSlideXml += `<a:endParaRPr lang="${opts.lang || 'en-US'}" dirty="0"/>` // Added 20180101 to address PPT-2007 issues
}
// D: End paragraph
strSlideXml += '</a:p>'
})
// IMPORTANT: An empty txBody will cause "needs repair" error! Add <p> content if missing.
// [FIXED in v3.13.0]: This fixes issue with table auto-paging where some cells w/b empty on subsequent pages.
/*
<a:txBody>
<a:bodyPr/>
<a:lstStyle/>
</a:txBody>
*/
if (strSlideXml.indexOf('<a:p>') === -1) {
strSlideXml += '<a:p><a:endParaRPr/></a:p>'
}
// STEP 7: Close the textBody
strSlideXml += slideObj._type === SLIDE_OBJECT_TYPES.tablecell ? '</a:txBody>' : '</p:txBody>'
// 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 `<p:ph
${placeholderIdx ? ' idx="' + placeholderIdx.toString() + '"' : ''}
${placeholderType && PLACEHOLDER_TYPES[placeholderType] ? ` type="${placeholderType}"` : ''}
${placeholderObj.text && placeholderObj.text.length > 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 = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + CRLF
strXml += '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
strXml += '<Default Extension="xml" ContentType="application/xml"/>'
strXml += '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
strXml += '<Default Extension="jpeg" ContentType="image/jpeg"/>'
strXml += '<Default Extension="jpg" ContentType="image/jpg"/>'
strXml += '<Default Extension="svg" ContentType="image/svg+xml"/>'
// STEP 1: Add standard/any media types used in Presentation
strXml += '<Default Extension="png" ContentType="image/png"/>'
strXml += '<Default Extension="gif" ContentType="image/gif"/>'
strXml += '<Default Extension="m4v" ContentType="video/mp4"/>' // NOTE: Hard-Code this extension as it wont be created in loop below (as extn !== type)
strXml += '<Default Extension="mp4" ContentType="video/mp4"/>' // 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 += '<Default Extension="' + rel.extn + '" ContentType="' + rel.type + '"/>'
}
})
})
strXml += '<Default Extension="vml" ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing"/>'
strXml += '<Default Extension="xlsx" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"/>'
// STEP 2: Add presentation and slide master(s)/slide(s)
strXml += '<Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>'
strXml += '<Override PartName="/ppt/notesMasters/notesMaster1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml"/>'
slides.forEach((slide, idx) => {
strXml += `<Override PartName="/ppt/slideMasters/slideMaster${idx + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"/>`
strXml += `<Override PartName="/ppt/slides/slide${idx + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>`
// Add charts if any
slide._relsChart.forEach(rel => {
strXml += `<Override PartName="${rel.Target}" ContentType="application/vnd.openxmlformats-officedocument.drawingml.chart+xml"/>`
})
})
// STEP 3: Core PPT
strXml += '<Override PartName="/ppt/presProps.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presProps+xml"/>'
strXml += '<Override PartName="/ppt/viewProps.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml"/>'
strXml += '<Override PartName="/ppt/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>'
strXml += '<Override PartName="/ppt/tableStyles.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml"/>'
// STEP 4: Add Slide Layouts
slideLayouts.forEach((layout, idx) => {
strXml += `<Override PartName="/ppt/slideLayouts/slideLayout${idx + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/>`
; (layout._relsChart || []).forEach(rel => {
strXml += ' <Override PartName="' + rel.Target + '" ContentType="application/vnd.openxmlformats-officedocument.drawingml.chart+xml"/>'
})
})
// STEP 5: Add notes slide(s)
slides.forEach((_slide, idx) => {
strXml += `<Override PartName="/ppt/notesSlides/notesSlide${idx + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml"/>`
})
// STEP 6: Add rels
masterSlide._relsChart.forEach(rel => {
strXml += ' <Override PartName="' + rel.Target + '" ContentType="application/vnd.openxmlformats-officedocument.drawingml.chart+xml"/>'
})
masterSlide._relsMedia.forEach(rel => {
if (rel.type !== 'image' && rel.type !== 'online' && rel.type !== 'chart' && rel.extn !== 'm4v' && !strXml.includes(rel.type)) { strXml += ' <Default Extension="' + rel.extn + '" ContentType="' + rel.type + '"/>' }
})
// LAST: Finish XML (Resume core)
strXml += ' <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>'
strXml += ' <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>'
strXml += '</Types>'
return strXml
}
/**
* Creates `_rels/.rels`
* @returns XML
*/
export function makeXmlRootRels (): string {
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>${CRLF}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="ppt/presentation.xml"/>
</Relationships>`
}
/**
* 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 `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>${CRLF}<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
<TotalTime>0</TotalTime>
<Words>0</Words>
<Application>Microsoft Office PowerPoint</Application>
<PresentationFormat>On-screen Show (16:9)</PresentationFormat>
<Paragraphs>0</Paragraphs>
<Slides>${slides.length}</Slides>
<Notes>${slides.length}</Notes>
<HiddenSlides>0</HiddenSlides>
<MMClips>0</MMClips>
<ScaleCrop>false</ScaleCrop>
<HeadingPairs>
<vt:vector size="6" baseType="variant">
<vt:variant><vt:lpstr>Fonts Used</vt:lpstr></vt:variant>
<vt:variant><vt:i4>2</vt:i4></vt:variant>
<vt:variant><vt:lpstr>Theme</vt:lpstr></vt:variant>
<vt:variant><vt:i4>1</vt:i4></vt:variant>
<vt:variant><vt:lpstr>Slide Titles</vt:lpstr></vt:variant>
<vt:variant><vt:i4>${slides.length}</vt:i4></vt:variant>
</vt:vector>
</HeadingPairs>
<TitlesOfParts>
<vt:vector size="${slides.length + 1 + 2}" baseType="lpstr">
<vt:lpstr>Arial</vt:lpstr>
<vt:lpstr>Calibri</vt:lpstr>
<vt:lpstr>Office Theme</vt:lpstr>
${slides.map((_slideObj, idx) => `<vt:lpstr>Slide ${idx + 1}</vt:lpstr>`).join('')}
</vt:vector>
</TitlesOfParts>
<Company>${company}</Company>
<LinksUpToDate>false</LinksUpToDate>
<SharedDoc>false</SharedDoc>
<HyperlinksChanged>false</HyperlinksChanged>
<AppVersion>16.0000</AppVersion>
</Properties>`
}
/**
* 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 `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dc:title>${encodeXmlEntities(title)}</dc:title>
<dc:subject>${encodeXmlEntities(subject)}</dc:subject>
<dc:creator>${encodeXmlEntities(author)}</dc:creator>
<cp:lastModifiedBy>${encodeXmlEntities(author)}</cp:lastModifiedBy>
<cp:revision>${revision}</cp:revision>
<dcterms:created xsi:type="dcterms:W3CDTF">${new Date().toISOString().replace(/\.\d\d\dZ/, 'Z')}</dcterms:created>
<dcterms:modified xsi:type="dcterms:W3CDTF">${new Date().toISOString().replace(/\.\d\d\dZ/, 'Z')}</dcterms:modified>
</cp:coreProperties>`
}
/**
* Creates `ppt/_rels/presentation.xml.rels`
* @param {PresSlide[]} slides - Presenation Slides
* @returns XML
*/
export function makeXmlPresentationRels (slides: PresSlide[]): string {
let intRelNum = 1
let strXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + CRLF
strXml += '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
strXml += '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" Target="slideMasters/slideMaster1.xml"/>'
for (let idx = 1; idx <= slides.length; idx++) {
strXml += `<Relationship Id="rId${++intRelNum}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide${idx}.xml"/>`
}
intRelNum++
strXml +=
`<Relationship Id="rId${intRelNum + 0}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster" Target="notesMasters/notesMaster1.xml"/>` +
`<Relationship Id="rId${intRelNum + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps" Target="presProps.xml"/>` +
`<Relationship Id="rId${intRelNum + 2}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps" Target="viewProps.xml"/>` +
`<Relationship Id="rId${intRelNum + 3}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>` +
`<Relationship Id="rId${intRelNum + 4}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles" Target="tableStyles.xml"/>` +
'</Relationships>'
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 (
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>${CRLF}` +
'<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" ' +
'xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" ' +
'xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" ' +
'xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main"' +
`${slide?.hidden ? ' show="0"' : ''}>` +
`${slideObjectToXml(slide)}` +
'<p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr></p:sld>'
)
}
/**
* 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 `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>${CRLF}<p:notesMaster xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:cSld><p:bg><p:bgRef idx="1001"><a:schemeClr val="bg1"/></p:bgRef></p:bg><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr><p:sp><p:nvSpPr><p:cNvPr id="2" name="Header Placeholder 1"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="hdr" sz="quarter"/></p:nvPr></p:nvSpPr><p:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="2971800" cy="458788"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr><p:txBody><a:bodyPr vert="horz" lIns="91440" tIns="45720" rIns="91440" bIns="45720" rtlCol="0"/><a:lstStyle><a:lvl1pPr algn="l"><a:defRPr sz="1200"/></a:lvl1pPr></a:lstStyle><a:p><a:endParaRPr lang="en-US"/></a:p></p:txBody></p:sp><p:sp><p:nvSpPr><p:cNvPr id="3" name="Date Placeholder 2"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="dt" idx="1"/></p:nvPr></p:nvSpPr><p:spPr><a:xfrm><a:off x="3884613" y="0"/><a:ext cx="2971800" cy="458788"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr><p:txBody><a:bodyPr vert="horz" lIns="91440" tIns="45720" rIns="91440" bIns="45720" rtlCol="0"/><a:lstStyle><a:lvl1pPr algn="r"><a:defRPr sz="1200"/></a:lvl1pPr></a:lstStyle><a:p><a:fld id="{5282F153-3F37-0F45-9E97-73ACFA13230C}" type="datetimeFigureOut"><a:rPr lang="en-US"/><a:t>7/23/19</a:t></a:fld><a:endParaRPr lang="en-US"/></a:p></p:txBody></p:sp><p:sp><p:nvSpPr><p:cNvPr id="4" name="Slide Image Placeholder 3"/><p:cNvSpPr><a:spLocks noGrp="1" noRot="1" noChangeAspect="1"/></p:cNvSpPr><p:nvPr><p:ph type="sldImg" idx="2"/></p:nvPr></p:nvSpPr><p:spPr><a:xfrm><a:off x="685800" y="1143000"/><a:ext cx="5486400" cy="3086100"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/><a:ln w="12700"><a:solidFill><a:prstClr val="black"/></a:solidFill></a:ln></p:spPr><p:txBody><a:bodyPr vert="horz" lIns="91440" tIns="45720" rIns="91440" bIns="45720" rtlCol="0" anchor="ctr"/><a:lstStyle/><a:p><a:endParaRPr lang="en-US"/></a:p></p:txBody></p:sp><p:sp><p:nvSpPr><p:cNvPr id="5" name="Notes Placeholder 4"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="body" sz="quarter" idx="3"/></p:nvPr></p:nvSpPr><p:spPr><a:xfrm><a:off x="685800" y="4400550"/><a:ext cx="5486400" cy="3600450"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr><p:txBody><a:bodyPr vert="horz" lIns="91440" tIns="45720" rIns="91440" bIns="45720" rtlCol="0"/><a:lstStyle/><a:p><a:pPr lvl="0"/><a:r><a:rPr lang="en-US"/><a:t>Click to edit Master text styles</a:t></a:r></a:p><a:p><a:pPr lvl="1"/><a:r><a:rPr lang="en-US"/><a:t>Second level</a:t></a:r></a:p><a:p><a:pPr lvl="2"/><a:r><a:rPr lang="en-US"/><a:t>Third level</a:t></a:r></a:p><a:p><a:pPr lvl="3"/><a:r><a:rPr lang="en-US"/><a:t>Fourth level</a:t></a:r></a:p><a:p><a:pPr lvl="4"/><a:r><a:rPr lang="en-US"/><a:t>Fifth level</a:t></a:r></a:p></p:txBody></p:sp><p:sp><p:nvSpPr><p:cNvPr id="6" name="Footer Placeholder 5"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="ftr" sz="quarter" idx="4"/></p:nvPr></p:nvSpPr><p:spPr><a:xfrm><a:off x="0" y="8685213"/><a:ext cx="2971800" cy="458787"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr><p:txBody><a:bodyPr vert="horz" lIns="91440" tIns="45720" rIns="91440" bIns="45720" rtlCol="0" anchor="b"/><a:lstStyle><a:lvl1pPr algn="l"><a:defRPr sz="1200"/></a:lvl1pPr></a:lstStyle><a:p><a:endParaRPr lang="en-US"/></a:p></p:txBody></p:sp><p:sp><p:nvSpPr><p:cNvPr id="7" name="Slide Number Placeholder 6"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="sldNum" sz="quarter" idx="5"/></p:nvPr></p:nvSpPr><p:spPr><a:xfrm><a:off x="3884613" y="8685213"/><a:ext cx="2971800" cy="458787"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr><p:txBody><a:bodyPr vert="horz" lIns="91440" tIns="45720" rIns="91440" bIns="45720" rtlCol="0" anchor="b"/><a:lstStyle><a:lvl1pPr algn="r"><a:defRPr sz="1200"/></a:lvl1pPr></a:lstStyle><a:p><a:fld id="{CE5E9CC1-C706-0F49-92D6-E571CC5EEA8F}" type="slidenum"><a:rPr lang="en-US"/><a:t>‹#›</a:t></a:fld><a:endParaRPr lang="en-US"/></a:p></p:txBody></p:sp></p:spTree><p:extLst><p:ext uri="{BB962C8B-B14F-4D97-AF65-F5344CB8AC3E}"><p14:creationId xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main" val="1024086991"/></p:ext></p:extLst></p:cSld><p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/><p:notesStyle><a:lvl1pPr marL="0" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1200" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl1pPr><a:lvl2pPr marL="457200" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1200" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl2pPr><a:lvl3pPr marL="914400" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1200" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl3pPr><a:lvl4pPr marL="1371600" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1200" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl4pPr><a:lvl5pPr marL="1828800" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1200" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl5pPr><a:lvl6pPr marL="2286000" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1200" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl6pPr><a:lvl7pPr marL="2743200" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1200" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl7pPr><a:lvl8pPr marL="3200400" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1200" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl8pPr><a:lvl9pPr marL="3657600" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1200" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl9pPr></p:notesStyle></p:notesMaster>`
}
/**
* 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 (
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>${CRLF}<p:notes xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr><p:sp><p:nvSpPr><p:cNvPr id="2" name="Slide Image Placeholder 1"/><p:cNvSpPr><a:spLocks noGrp="1" noRot="1" noChangeAspect="1"/></p:cNvSpPr><p:nvPr><p:ph type="sldImg"/></p:nvPr></p:nvSpPr><p:spPr/></p:sp><p:sp><p:nvSpPr><p:cNvPr id="3" name="Notes Placeholder 2"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="body" idx="1"/></p:nvPr></p:nvSpPr><p:spPr/><p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r><a:rPr lang="en-US" dirty="0"/><a:t>${encodeXmlEntities(getNotesFromSlide(slide))}</a:t></a:r><a:endParaRPr lang="en-US" dirty="0"/></a:p></p:txBody></p:sp><p:sp><p:nvSpPr><p:cNvPr id="4" name="Slide Number Placeholder 3"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="sldNum" sz="quarter" idx="10"/></p:nvPr></p:nvSpPr><p:spPr/><p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:fld id="${SLDNUMFLDID}" type="slidenum"><a:rPr lang="en-US"/><a:t>${slide._slideNum}</a:t></a:fld><a:endParaRPr lang="en-US"/></a:p></p:txBody></p:sp></p:spTree><p:extLst><p:ext uri="{BB962C8B-B14F-4D97-AF65-F5344CB8AC3E}"><p14:creationId xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main" val="1024086991"/></p:ext></p:extLst></p:cSld><p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr></p:notes>`
)
}
/**
* 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 `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<p:sldLayout xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" preserve="1">
${slideObjectToXml(layout)}
<p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr></p:sldLayout>`
}
/**
* 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) => `<p:sldLayoutId id="${LAYOUT_IDX_SERIES_BASE + idx}" r:id="rId${slide._rels.length + idx + 1}"/>`)
let strXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + CRLF
strXml +=
'<p:sldMaster xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">'
strXml += slideObjectToXml(slide)
strXml +=
'<p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/>'
strXml += '<p:sldLayoutIdLst>' + layoutDefs.join('') + '</p:sldLayoutIdLst>'
strXml += '<p:hf sldNum="0" hdr="0" ftr="0" dt="0"/>'
strXml +=
'<p:txStyles>' +
' <p:titleStyle>' +
' <a:lvl1pPr algn="ctr" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:spcBef><a:spcPct val="0"/></a:spcBef><a:buNone/><a:defRPr sz="4400" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mj-lt"/><a:ea typeface="+mj-ea"/><a:cs typeface="+mj-cs"/></a:defRPr></a:lvl1pPr>' +
' </p:titleStyle>' +
' <p:bodyStyle>' +
' <a:lvl1pPr marL="342900" indent="-342900" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:spcBef><a:spcPct val="20000"/></a:spcBef><a:buFont typeface="Arial" pitchFamily="34" charset="0"/><a:buChar char="•"/><a:defRPr sz="3200" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl1pPr>' +
' <a:lvl2pPr marL="742950" indent="-285750" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:spcBef><a:spcPct val="20000"/></a:spcBef><a:buFont typeface="Arial" pitchFamily="34" charset="0"/><a:buChar char="–"/><a:defRPr sz="2800" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl2pPr>' +
' <a:lvl3pPr marL="1143000" indent="-228600" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:spcBef><a:spcPct val="20000"/></a:spcBef><a:buFont typeface="Arial" pitchFamily="34" charset="0"/><a:buChar char="•"/><a:defRPr sz="2400" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl3pPr>' +
' <a:lvl4pPr marL="1600200" indent="-228600" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:spcBef><a:spcPct val="20000"/></a:spcBef><a:buFont typeface="Arial" pitchFamily="34" charset="0"/><a:buChar char="–"/><a:defRPr sz="2000" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl4pPr>' +
' <a:lvl5pPr marL="2057400" indent="-228600" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:spcBef><a:spcPct val="20000"/></a:spcBef><a:buFont typeface="Arial" pitchFamily="34" charset="0"/><a:buChar char="»"/><a:defRPr sz="2000" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl5pPr>' +
' <a:lvl6pPr marL="2514600" indent="-228600" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:spcBef><a:spcPct val="20000"/></a:spcBef><a:buFont typeface="Arial" pitchFamily="34" charset="0"/><a:buChar char="•"/><a:defRPr sz="2000" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl6pPr>' +
' <a:lvl7pPr marL="2971800" indent="-228600" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:spcBef><a:spcPct val="20000"/></a:spcBef><a:buFont typeface="Arial" pitchFamily="34" charset="0"/><a:buChar char="•"/><a:defRPr sz="2000" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl7pPr>' +
' <a:lvl8pPr marL="3429000" indent="-228600" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:spcBef><a:spcPct val="20000"/></a:spcBef><a:buFont typeface="Arial" pitchFamily="34" charset="0"/><a:buChar char="•"/><a:defRPr sz="2000" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl8pPr>' +
' <a:lvl9pPr marL="3886200" indent="-228600" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:spcBef><a:spcPct val="20000"/></a:spcBef><a:buFont typeface="Arial" pitchFamily="34" charset="0"/><a:buChar char="•"/><a:defRPr sz="2000" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl9pPr>' +
' </p:bodyStyle>' +
' <p:otherStyle>' +
' <a:defPPr><a:defRPr lang="en-US"/></a:defPPr>' +
' <a:lvl1pPr marL="0" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl1pPr>' +
' <a:lvl2pPr marL="457200" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl2pPr>' +
' <a:lvl3pPr marL="914400" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl3pPr>' +
' <a:lvl4pPr marL="1371600" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl4pPr>' +
' <a:lvl5pPr marL="1828800" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl5pPr>' +
' <a:lvl6pPr marL="2286000" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl6pPr>' +
' <a:lvl7pPr marL="2743200" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl7pPr>' +
' <a:lvl8pPr marL="3200400" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl8pPr>' +
' <a:lvl9pPr marL="3657600" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl9pPr>' +
' </p:otherStyle>' +
'</p:txStyles>'
strXml += '</p:sldMaster>'
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 `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster" Target="../notesMasters/notesMaster1.xml"/>
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="../slides/slide${slideNumber}.xml"/>
</Relationships>`
}
/**
* 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 `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>${CRLF}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="../theme/theme1.xml"/>
</Relationships>`
}
/**
* 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 ? `<a:latin typeface="${pres.theme?.headFontFace}"/>` : '<a:latin typeface="Calibri Light" panose="020F0302020204030204"/>'
const minorFont = pres.theme?.bodyFontFace ? `<a:latin typeface="${pres.theme?.bodyFontFace}"/>` : '<a:latin typeface="Calibri" panose="020F0502020204030204"/>'
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme"><a:themeElements><a:clrScheme name="Office"><a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1><a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="44546A"/></a:dk2><a:lt2><a:srgbClr val="E7E6E6"/></a:lt2><a:accent1><a:srgbClr val="4472C4"/></a:accent1><a:accent2><a:srgbClr val="ED7D31"/></a:accent2><a:accent3><a:srgbClr val="A5A5A5"/></a:accent3><a:accent4><a:srgbClr val="FFC000"/></a:accent4><a:accent5><a:srgbClr val="5B9BD5"/></a:accent5><a:accent6><a:srgbClr val="70AD47"/></a:accent6><a:hlink><a:srgbClr val="0563C1"/></a:hlink><a:folHlink><a:srgbClr val="954F72"/></a:folHlink></a:clrScheme><a:fontScheme name="Office"><a:majorFont>${majorFont}<a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック Light"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线 Light"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Times New Roman"/><a:font script="Hebr" typeface="Times New Roman"/><a:font script="Thai" typeface="Angsana New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="MoolBoran"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Times New Roman"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:majorFont><a:minorFont>${minorFont}<a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Arial"/><a:font script="Hebr" typeface="Arial"/><a:font script="Thai" typeface="Cordia New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="DaunPenh"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Arial"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:minorFont></a:fontScheme><a:fmtScheme name="Office"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:lumMod val="110000"/><a:satMod val="105000"/><a:tint val="67000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="103000"/><a:tint val="73000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="109000"/><a:tint val="81000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:satMod val="103000"/><a:lumMod val="102000"/><a:tint val="94000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:satMod val="110000"/><a:lumMod val="100000"/><a:shade val="100000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="99000"/><a:satMod val="120000"/><a:shade val="78000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w="6350" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="12700" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="19050" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="57150" dist="19050" dir="5400000" algn="ctr" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="63000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="93000"/><a:satMod val="150000"/><a:shade val="98000"/><a:lumMod val="102000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:tint val="98000"/><a:satMod val="130000"/><a:shade val="90000"/><a:lumMod val="103000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="63000"/><a:satMod val="120000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults/><a:extraClrSchemeLst/><a:extLst><a:ext uri="{05A4C25C-085E-4340-85A3-A5531E510DB2}"><thm15:themeFamily xmlns:thm15="http://schemas.microsoft.com/office/thememl/2012/main" name="Office Theme" id="{62F939B6-93AF-4DB8-9C6B-D6C7DFDC589F}" vid="{4A3C46E8-61CC-4603-A589-7422A47A8E4A}"/></a:ext></a:extLst></a:theme>`
}
/**
* 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 =
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>${CRLF}` +
'<p:presentation xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" ' +
`xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" ${pres.rtlMode ? 'rtl="1"' : ''} saveSubsetFonts="1" autoCompressPictures="0">`
// STEP 1: Add slide master (SPEC: tag 1 under <presentation>)
strXml += '<p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="rId1"/></p:sldMasterIdLst>'
// STEP 2: Add all Slides (SPEC: tag 3 under <presentation>)
strXml += '<p:sldIdLst>'
pres.slides.forEach(slide => (strXml += `<p:sldId id="${slide._slideId}" r:id="rId${slide._rId}"/>`))
strXml += '</p:sldIdLst>'
// STEP 3: Add Notes Master (SPEC: tag 2 under <presentation>)
// (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 `<p:sldIdLst>` 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 += `<p:notesMasterIdLst><p:notesMasterId r:id="rId${pres.slides.length + 2}"/></p:notesMasterIdLst>`
// STEP 4: Add sizes
strXml += `<p:sldSz cx="${pres.presLayout.width}" cy="${pres.presLayout.height}"/>`
strXml += `<p:notesSz cx="${pres.presLayout.height}" cy="${pres.presLayout.width}"/>`
// STEP 5: Add text styles
strXml += '<p:defaultTextStyle>'
for (let idy = 1; idy < 10; idy++) {
strXml +=
`<a:lvl${idy}pPr marL="${(idy - 1) * 457200}" algn="l" defTabSz="914400" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1">` +
'<a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/><a:ea typeface="+mn-ea"/><a:cs typeface="+mn-cs"/>' +
`</a:defRPr></a:lvl${idy}pPr>`
}
strXml += '</p:defaultTextStyle>'
// STEP 6: Add Sections (if any)
if (pres.sections && pres.sections.length > 0) {
strXml += '<p:extLst><p:ext uri="{521415D9-36F7-43E2-AB2F-B90AF26B5E84}">'
strXml += '<p14:sectionLst xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main">'
pres.sections.forEach(sect => {
strXml += `<p14:section name="${encodeXmlEntities(sect.title)}" id="{${getUuid('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')}}"><p14:sldIdLst>`
sect._slides.forEach(slide => (strXml += `<p14:sldId id="${slide._slideId}"/>`))
strXml += '</p14:sldIdLst></p14:section>'
})
strXml += '</p14:sectionLst></p:ext>'
strXml += '<p:ext uri="{EFAFB233-063F-42B5-8137-9DF3F51BA10A}"><p15:sldGuideLst xmlns:p15="http://schemas.microsoft.com/office/powerpoint/2012/main"/></p:ext>'
strXml += '</p:extLst>'
}
// Done
strXml += '</p:presentation>'
return strXml
}
/**
* Create `ppt/presProps.xml`
* @return {string} XML
*/
export function makeXmlPresProps (): string {
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>${CRLF}<p:presentationPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"/>`
}
/**
* Create `ppt/tableStyles.xml`
* @see: http://openxmldeveloper.org/discussions/formats/f/13/p/2398/8107.aspx
* @return {string} XML
*/
export function makeXmlTableStyles (): string {
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>${CRLF}<a:tblStyleLst xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" def="{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}"/>`
}
/**
* Creates `ppt/viewProps.xml`
* @return {string} XML
*/
export function makeXmlViewProps (): string {
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>${CRLF}<p:viewPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:normalViewPr horzBarState="maximized"><p:restoredLeft sz="15611"/><p:restoredTop sz="94610"/></p:normalViewPr><p:slideViewPr><p:cSldViewPr snapToGrid="0" snapToObjects="1"><p:cViewPr varScale="1"><p:scale><a:sx n="136" d="100"/><a:sy n="136" d="100"/></p:scale><p:origin x="216" y="312"/></p:cViewPr><p:guideLst/></p:cSldViewPr></p:slideViewPr><p:notesTextViewPr><p:cViewPr><p:scale><a:sx n="1" d="1"/><a:sy n="1" d="1"/></p:scale><p:origin x="0" y="0"/></p:cViewPr></p:notesTextViewPr><p:gridSpacing cx="76200" cy="76200"/></p:viewPr>`
}
/**
* 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)
}
}