File size: 120,198 Bytes
f56a29b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 | /**
* 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="•"/>'
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)
}
}
|