@shbernal/pptxgenjs 5.1.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3137,6 +3137,10 @@ while (n === a[++i] && n === a[++i] && n === a[++i] && n === a[++i] && n === a[+
3137
3137
  const EMU_PER_INCH = 914400;
3138
3138
  const EMU_PER_POINT = 12700;
3139
3139
  const POINTS_PER_INCH = 72;
3140
+ /** A bare number larger than this (in inches) is almost certainly a mistake — likely a raw EMU
3141
+ * value passed where inches are expected. We interpret it as inches (the documented contract) but
3142
+ * warn, pointing at the explicit `"<n>emu"` form. ~1000in is far beyond any real slide. */
3143
+ const IMPLAUSIBLE_INCHES = 1e3;
3140
3144
  function inchesToEmu(inches) {
3141
3145
  assertFiniteNumber(inches, "inches");
3142
3146
  return Math.round(inches * EMU_PER_INCH);
@@ -3150,6 +3154,46 @@ function pixelsToEmu(pixels, dpi) {
3150
3154
  assertPositiveFiniteNumber(dpi, "dpi");
3151
3155
  return inchesToEmu(pixels / dpi);
3152
3156
  }
3157
+ /**
3158
+ * Resolve a percentage of an axis length to EMU.
3159
+ * @param percent - percentage value (e.g. `50` for 50%)
3160
+ * @param axisEmu - the axis length in EMU (slide width for x/w, height for y/h)
3161
+ */
3162
+ function percentToEmu(percent, axisEmu) {
3163
+ assertFiniteNumber(percent, "percent");
3164
+ assertFiniteNumber(axisEmu, "axisEmu");
3165
+ return Math.round(percent / 100 * axisEmu);
3166
+ }
3167
+ /**
3168
+ * The single user-coordinate → EMU boundary. Convert each user-supplied coordinate exactly once.
3169
+ *
3170
+ * Accepts (see {@link Coord}):
3171
+ * - a bare `number` → **always inches** (the documented unit); no magnitude guessing
3172
+ * - `"<n>%"` → percentage of `axisEmu`
3173
+ * - `"<n>in"` / `"<n>pt"` / `"<n>emu"` → explicit units (the escape hatch for non-inch values)
3174
+ *
3175
+ * Throws on non-finite or unparseable input rather than silently emitting a degenerate 0-size.
3176
+ * @param value - user coordinate
3177
+ * @param axisEmu - axis length in EMU, used only to resolve percentages
3178
+ */
3179
+ function coordToEmu(value, axisEmu) {
3180
+ if (typeof value === "number") {
3181
+ assertFiniteNumber(value, "coordinate");
3182
+ if (Math.abs(value) > IMPLAUSIBLE_INCHES) console.warn(`PptxGenJS: coordinate ${value} interpreted as ${value} inches. A bare number is always inches; if you meant EMU, pass it as a string like "${Math.round(value)}emu".`);
3183
+ return inchesToEmu(value);
3184
+ }
3185
+ const match = /^\s*(-?\d*\.?\d+)\s*(%|in|pt|emu)\s*$/.exec(value);
3186
+ if (!match) throw new Error(`PptxGenJS: invalid coordinate "${value}". Expected a number (inches) or a string like "50%", "5in", "72pt", or "914400emu".`);
3187
+ const n = Number(match[1]);
3188
+ switch (match[2]) {
3189
+ case "%": return percentToEmu(n, axisEmu);
3190
+ case "in": return inchesToEmu(n);
3191
+ case "pt": return pointsToEmu(n);
3192
+ default:
3193
+ assertFiniteNumber(n, "coordinate");
3194
+ return Math.round(n);
3195
+ }
3196
+ }
3153
3197
  function emuToInches(emu) {
3154
3198
  assertFiniteNumber(emu, "emu");
3155
3199
  return emu / EMU_PER_INCH;
@@ -3433,7 +3477,7 @@ let ShapeType = /* @__PURE__ */ function(ShapeType) {
3433
3477
  ShapeType["flowChartSort"] = "flowChartSort";
3434
3478
  ShapeType["flowChartSummingJunction"] = "flowChartSummingJunction";
3435
3479
  ShapeType["flowChartTerminator"] = "flowChartTerminator";
3436
- ShapeType["folderCorner"] = "folderCorner";
3480
+ ShapeType["foldedCorner"] = "foldedCorner";
3437
3481
  ShapeType["frame"] = "frame";
3438
3482
  ShapeType["funnel"] = "funnel";
3439
3483
  ShapeType["gear6"] = "gear6";
@@ -3636,7 +3680,7 @@ let SHAPE_TYPE = /* @__PURE__ */ function(SHAPE_TYPE) {
3636
3680
  SHAPE_TYPE["FLOWCHART_STORED_DATA"] = "flowChartOnlineStorage";
3637
3681
  SHAPE_TYPE["FLOWCHART_SUMMING_JUNCTION"] = "flowChartSummingJunction";
3638
3682
  SHAPE_TYPE["FLOWCHART_TERMINATOR"] = "flowChartTerminator";
3639
- SHAPE_TYPE["FOLDED_CORNER"] = "folderCorner";
3683
+ SHAPE_TYPE["FOLDED_CORNER"] = "foldedCorner";
3640
3684
  SHAPE_TYPE["FRAME"] = "frame";
3641
3685
  SHAPE_TYPE["FUNNEL"] = "funnel";
3642
3686
  SHAPE_TYPE["GEAR_6"] = "gear6";
@@ -3671,10 +3715,6 @@ let SHAPE_TYPE = /* @__PURE__ */ function(SHAPE_TYPE) {
3671
3715
  SHAPE_TYPE["LINE_CALLOUT_3_ACCENT_BAR"] = "accentCallout3";
3672
3716
  SHAPE_TYPE["LINE_CALLOUT_3_BORDER_AND_ACCENT_BAR"] = "accentBorderCallout3";
3673
3717
  SHAPE_TYPE["LINE_CALLOUT_3_NO_BORDER"] = "callout3";
3674
- SHAPE_TYPE["LINE_CALLOUT_4"] = "borderCallout4";
3675
- SHAPE_TYPE["LINE_CALLOUT_4_ACCENT_BAR"] = "accentCallout3=4";
3676
- SHAPE_TYPE["LINE_CALLOUT_4_BORDER_AND_ACCENT_BAR"] = "accentBorderCallout4";
3677
- SHAPE_TYPE["LINE_CALLOUT_4_NO_BORDER"] = "callout4";
3678
3718
  SHAPE_TYPE["LINE"] = "line";
3679
3719
  SHAPE_TYPE["LINE_INVERSE"] = "lineInv";
3680
3720
  SHAPE_TYPE["MATH_DIVIDE"] = "mathDivide";
@@ -3742,6 +3782,31 @@ let SHAPE_TYPE = /* @__PURE__ */ function(SHAPE_TYPE) {
3742
3782
  SHAPE_TYPE["WAVE"] = "wave";
3743
3783
  return SHAPE_TYPE;
3744
3784
  }({});
3785
+ /**
3786
+ * Valid ECMA-376 `ST_ShapeType` presets that are not surfaced with a friendly
3787
+ * `SHAPE_TYPE` name. They are still legal geometries PowerPoint renders, so the
3788
+ * preset-validation set must accept them.
3789
+ */
3790
+ const EXTRA_SHAPE_PRESETS = [
3791
+ "straightConnector1",
3792
+ "bentConnector2",
3793
+ "bentConnector3",
3794
+ "bentConnector4",
3795
+ "bentConnector5",
3796
+ "curvedConnector2",
3797
+ "curvedConnector3",
3798
+ "curvedConnector4",
3799
+ "curvedConnector5"
3800
+ ];
3801
+ /**
3802
+ * Every shape geometry name PptxGenJS can serialize without corrupting the
3803
+ * package: the OOXML preset geometries (`ST_ShapeType` — `SHAPE_TYPE` values
3804
+ * plus the unexposed connectors above) and `custGeom` (freeform paths, emitted
3805
+ * as `<a:custGeom>` rather than `<a:prstGeom>`). Used to reject bogus presets
3806
+ * before they become an invalid `<a:prstGeom prst="...">` that triggers
3807
+ * PowerPoint's "needs repair" dialog and drops the shape.
3808
+ */
3809
+ const VALID_SHAPE_PRESETS = new Set([...Object.values(SHAPE_TYPE), ...EXTRA_SHAPE_PRESETS]);
3745
3810
  let CHART_TYPE = /* @__PURE__ */ function(CHART_TYPE) {
3746
3811
  CHART_TYPE["AREA"] = "area";
3747
3812
  CHART_TYPE["BAR"] = "bar";
@@ -3811,7 +3876,98 @@ let BULLET_TYPES = /* @__PURE__ */ function(BULLET_TYPES) {
3811
3876
  BULLET_TYPES["TRIANGLE"] = "&#x25B6;";
3812
3877
  return BULLET_TYPES;
3813
3878
  }({});
3879
+ /**
3880
+ * Built-in PowerPoint table style IDs.
3881
+ *
3882
+ * Each value is the GUID that goes into `<a:tableStyleId>` inside `<a:tblPr>`.
3883
+ * These 74 GUIDs are the complete set defined in [MS-OE376] §5.1.6.10 and are
3884
+ * resolved by PowerPoint without embedding any style definition in the file.
3885
+ *
3886
+ * Only these enum members are supported. Raw GUID strings are not accepted;
3887
+ * use `TABLE_STYLE` members exclusively.
3888
+ */
3889
+ let TABLE_STYLE = /* @__PURE__ */ function(TABLE_STYLE) {
3890
+ TABLE_STYLE["NO_STYLE_NO_GRID"] = "{2D5ABB26-0587-4C30-8999-92F81FD0307C}";
3891
+ TABLE_STYLE["NO_STYLE_TABLE_GRID"] = "{5940675A-B579-460E-94D1-54222C63F5DA}";
3892
+ TABLE_STYLE["THEMED_STYLE_1_ACCENT_1"] = "{3C2FFA5D-87B4-456A-9821-1D502468CF0F}";
3893
+ TABLE_STYLE["THEMED_STYLE_1_ACCENT_2"] = "{284E427A-3D55-4303-BF80-6455036E1DE7}";
3894
+ TABLE_STYLE["THEMED_STYLE_1_ACCENT_3"] = "{69C7853C-536D-4A76-A0AE-DD22124D55A5}";
3895
+ TABLE_STYLE["THEMED_STYLE_1_ACCENT_4"] = "{775DCB02-9BB8-47FD-8907-85C794F793BA}";
3896
+ TABLE_STYLE["THEMED_STYLE_1_ACCENT_5"] = "{35758FB7-9AC5-4552-8A53-C91805E547FA}";
3897
+ TABLE_STYLE["THEMED_STYLE_1_ACCENT_6"] = "{08FB837D-C827-4EFA-A057-4D05807E0F7C}";
3898
+ TABLE_STYLE["THEMED_STYLE_2_ACCENT_1"] = "{D113A9D2-9D6B-4929-AA2D-F23B5EE8CBE7}";
3899
+ TABLE_STYLE["THEMED_STYLE_2_ACCENT_2"] = "{18603FDC-E32A-4AB5-989C-0864C3EAD2B8}";
3900
+ TABLE_STYLE["THEMED_STYLE_2_ACCENT_3"] = "{306799F8-075E-4A3A-A7F6-7FBC6576F1A4}";
3901
+ TABLE_STYLE["THEMED_STYLE_2_ACCENT_4"] = "{E269D01E-BC32-4049-B463-5C60D7B0CCD2}";
3902
+ TABLE_STYLE["THEMED_STYLE_2_ACCENT_5"] = "{327F97BB-C833-4FB7-BDE5-3F7075034690}";
3903
+ TABLE_STYLE["THEMED_STYLE_2_ACCENT_6"] = "{638B1855-1B75-4FBE-930C-398BA8C253C6}";
3904
+ TABLE_STYLE["LIGHT_STYLE_1"] = "{9D7B26C5-4107-4FEC-AEDC-1716B250A1EF}";
3905
+ TABLE_STYLE["LIGHT_STYLE_1_ACCENT_1"] = "{3B4B98B0-60AC-42C2-AFA5-B58CD77FA1E5}";
3906
+ TABLE_STYLE["LIGHT_STYLE_1_ACCENT_2"] = "{0E3FDE45-AF77-4B5C-9715-49D594BDF05E}";
3907
+ TABLE_STYLE["LIGHT_STYLE_1_ACCENT_3"] = "{C083E6E3-FA7D-4D7B-A595-EF9225AFEA82}";
3908
+ TABLE_STYLE["LIGHT_STYLE_1_ACCENT_4"] = "{D27102A9-8310-4765-A935-A1911B00CA55}";
3909
+ TABLE_STYLE["LIGHT_STYLE_1_ACCENT_5"] = "{5FD0F851-EC5A-4D38-B0AD-8093EC10F338}";
3910
+ TABLE_STYLE["LIGHT_STYLE_1_ACCENT_6"] = "{68D230F3-CF80-4859-8CE7-A43EE81993B5}";
3911
+ TABLE_STYLE["LIGHT_STYLE_2"] = "{7E9639D4-E3E2-4D34-9284-5A2195B3D0D7}";
3912
+ TABLE_STYLE["LIGHT_STYLE_2_ACCENT_1"] = "{69012ECD-51FC-41F1-AA8D-1B2483CD663E}";
3913
+ TABLE_STYLE["LIGHT_STYLE_2_ACCENT_2"] = "{72833802-FEF1-4C79-8D5D-14CF1EAF98D9}";
3914
+ TABLE_STYLE["LIGHT_STYLE_2_ACCENT_3"] = "{F2DE63D5-997A-4646-A377-4702673A728D}";
3915
+ TABLE_STYLE["LIGHT_STYLE_2_ACCENT_4"] = "{17292A2E-F333-43FB-9621-5CBBE7FDCDCB}";
3916
+ TABLE_STYLE["LIGHT_STYLE_2_ACCENT_5"] = "{5A111915-BE36-4E01-A7E5-04B1672EAD32}";
3917
+ TABLE_STYLE["LIGHT_STYLE_2_ACCENT_6"] = "{912C8C85-51F0-491E-9774-3900AFEF0FD7}";
3918
+ TABLE_STYLE["LIGHT_STYLE_3"] = "{616DA210-FB5B-4158-B5E0-FEB733F419BA}";
3919
+ TABLE_STYLE["LIGHT_STYLE_3_ACCENT_1"] = "{BC89EF96-8CEA-46FF-86C4-4CE0E7609802}";
3920
+ TABLE_STYLE["LIGHT_STYLE_3_ACCENT_2"] = "{5DA37D80-6434-44D0-A028-1B22A696006F}";
3921
+ TABLE_STYLE["LIGHT_STYLE_3_ACCENT_3"] = "{8799B23B-EC83-4686-B30A-512413B5E67A}";
3922
+ TABLE_STYLE["LIGHT_STYLE_3_ACCENT_4"] = "{ED083AE6-46FA-4A59-8FB0-9F97EB10719F}";
3923
+ TABLE_STYLE["LIGHT_STYLE_3_ACCENT_5"] = "{BDBED569-4797-4DF1-A0F4-6AAB3CD982D8}";
3924
+ TABLE_STYLE["LIGHT_STYLE_3_ACCENT_6"] = "{E8B1032C-EA38-4F05-BA0D-38AFFFC7BED3}";
3925
+ TABLE_STYLE["MEDIUM_STYLE_1"] = "{793D81CF-94F2-401A-BA57-92F5A7B2D0C5}";
3926
+ TABLE_STYLE["MEDIUM_STYLE_1_ACCENT_1"] = "{B301B821-A1FF-4177-AEE7-76D212191A09}";
3927
+ TABLE_STYLE["MEDIUM_STYLE_1_ACCENT_2"] = "{9DCAF9ED-07DC-4A11-8D7F-57B35C25682E}";
3928
+ TABLE_STYLE["MEDIUM_STYLE_1_ACCENT_3"] = "{1FECB4D8-DB02-4DC6-A0A2-4F2EBAE1DC90}";
3929
+ TABLE_STYLE["MEDIUM_STYLE_1_ACCENT_4"] = "{1E171933-4619-4E11-9A3F-F7608DF75F80}";
3930
+ TABLE_STYLE["MEDIUM_STYLE_1_ACCENT_5"] = "{FABFCF23-3B69-468F-B69F-88F6DE6A72F2}";
3931
+ TABLE_STYLE["MEDIUM_STYLE_1_ACCENT_6"] = "{10A1B5D5-9B99-4C35-A422-299274C87663}";
3932
+ TABLE_STYLE["MEDIUM_STYLE_2"] = "{073A0DAA-6AF3-43AB-8588-CEC1D06C72B9}";
3933
+ TABLE_STYLE["MEDIUM_STYLE_2_ACCENT_1"] = "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}";
3934
+ TABLE_STYLE["MEDIUM_STYLE_2_ACCENT_2"] = "{21E4AEA4-8DFA-4A89-87EB-49C32662AFE0}";
3935
+ TABLE_STYLE["MEDIUM_STYLE_2_ACCENT_3"] = "{F5AB1C69-6EDB-4FF4-983F-18BD219EF322}";
3936
+ TABLE_STYLE["MEDIUM_STYLE_2_ACCENT_4"] = "{00A15C55-8517-42AA-B614-E9B94910E393}";
3937
+ TABLE_STYLE["MEDIUM_STYLE_2_ACCENT_5"] = "{7DF18680-E054-41AD-8BC1-D1AEF772440D}";
3938
+ TABLE_STYLE["MEDIUM_STYLE_2_ACCENT_6"] = "{93296810-A885-4BE3-A3E7-6D5BEEA58F35}";
3939
+ TABLE_STYLE["MEDIUM_STYLE_3"] = "{8EC20E35-A176-4012-BC5E-935CFFF8708E}";
3940
+ TABLE_STYLE["MEDIUM_STYLE_3_ACCENT_1"] = "{6E25E649-3F16-4E02-A733-19D2CDBF48F0}";
3941
+ TABLE_STYLE["MEDIUM_STYLE_3_ACCENT_2"] = "{85BE263C-DBD7-4A20-BB59-AAB30ACAA65A}";
3942
+ TABLE_STYLE["MEDIUM_STYLE_3_ACCENT_3"] = "{EB344D84-9AFB-497E-A393-DC336BA19D2E}";
3943
+ TABLE_STYLE["MEDIUM_STYLE_3_ACCENT_4"] = "{EB9631B5-78F2-41C9-869B-9F39066F8104}";
3944
+ TABLE_STYLE["MEDIUM_STYLE_3_ACCENT_5"] = "{74C1A8A3-306A-4EB7-A6B1-4F7E0EB9C5D6}";
3945
+ TABLE_STYLE["MEDIUM_STYLE_3_ACCENT_6"] = "{2A488322-F2BA-4B5B-9748-0D474271808F}";
3946
+ TABLE_STYLE["MEDIUM_STYLE_4"] = "{D7AC3CCA-C797-4891-BE02-D94E43425B78}";
3947
+ TABLE_STYLE["MEDIUM_STYLE_4_ACCENT_1"] = "{69CF1AB2-1976-4502-BF36-3FF5EA218861}";
3948
+ TABLE_STYLE["MEDIUM_STYLE_4_ACCENT_2"] = "{8A107856-5554-42FB-B03E-39F5DBC370BA}";
3949
+ TABLE_STYLE["MEDIUM_STYLE_4_ACCENT_3"] = "{0505E3EF-67EA-436B-97B2-0124C06EBD24}";
3950
+ TABLE_STYLE["MEDIUM_STYLE_4_ACCENT_4"] = "{C4B1156A-380E-4F78-BDF5-A606A8083BF9}";
3951
+ TABLE_STYLE["MEDIUM_STYLE_4_ACCENT_5"] = "{22838BEF-8BB2-4498-84A7-C5851F593DF1}";
3952
+ TABLE_STYLE["MEDIUM_STYLE_4_ACCENT_6"] = "{16D9F66E-5EB9-4882-86FB-DCBF35E3C3E4}";
3953
+ TABLE_STYLE["DARK_STYLE_1"] = "{E8034E78-7F5D-4C2E-B375-FC64B27BC917}";
3954
+ TABLE_STYLE["DARK_STYLE_1_ACCENT_1"] = "{125E5076-3810-47DD-B79F-674D7AD40C01}";
3955
+ TABLE_STYLE["DARK_STYLE_1_ACCENT_2"] = "{37CE84F3-28C3-443E-9E96-99CF82512B78}";
3956
+ TABLE_STYLE["DARK_STYLE_1_ACCENT_3"] = "{D03447BB-5D67-496B-8E87-E561075AD55C}";
3957
+ TABLE_STYLE["DARK_STYLE_1_ACCENT_4"] = "{E929F9F4-4A8F-4326-A1B4-22849713DDAB}";
3958
+ TABLE_STYLE["DARK_STYLE_1_ACCENT_5"] = "{8FD4443E-F989-4FC4-A0C8-D5A2AF1F390B}";
3959
+ TABLE_STYLE["DARK_STYLE_1_ACCENT_6"] = "{AF606853-7671-496A-8E4F-DF71F8EC918B}";
3960
+ TABLE_STYLE["DARK_STYLE_2"] = "{5202B0CA-FC54-4496-8BCA-5EF66A818D29}";
3961
+ /** Header uses Accent 2, body rows use Accent 1 */
3962
+ TABLE_STYLE["DARK_STYLE_2_ACCENT_1_ACCENT_2"] = "{0660B408-B3CF-4A94-85FC-2B1E0A45F4A2}";
3963
+ /** Header uses Accent 4, body rows use Accent 3 */
3964
+ TABLE_STYLE["DARK_STYLE_2_ACCENT_3_ACCENT_4"] = "{91EBBBCC-DAD2-459C-BE2E-F6DE35CF9A28}";
3965
+ /** Header uses Accent 6, body rows use Accent 5 */
3966
+ TABLE_STYLE["DARK_STYLE_2_ACCENT_5_ACCENT_6"] = "{46F890A9-2807-4EBB-B81D-B2AA78EC7F39}";
3967
+ return TABLE_STYLE;
3968
+ }({});
3814
3969
  const IMG_BROKEN = "data:image/png;base64,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";
3970
+ const IMG_SVG_PLACEHOLDER = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
3815
3971
  const IMG_PLAYBTN = "data:image/png;base64,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AAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKREkRUAAACwrUpLSwuGDRvWfMCAAS26du3avKysrLiioqKkZcuWzZs1a1bcvHnz0tLS0rJsNtusuLi4ebNmzUoLCgo+8/eijY2N9Zs3b66pra2tqqur21xTU1NdVVVVs2nTptqNGzdWbdiwoeYvf/nL5hUrVlQtWLBgw6xZs6pqamoaJQYAAEDaKYABAACIiIghQ4aUHnTQQW379u3bql27dq3at2/fpkWLFq2bN29eWVpa2qpZs2bNCwsLm2ez2fLCwsLyoqKi8sLCwtKknK+hoaG6vr6+qqGh4S91dXV/aWhoqNq8eXNVTU3NuqqqqvUbNmxYu2rVqjWrV69e99Zbb6177rnnPpgzZ06NTwYAAABJogAGAADIA8OGDWt+xBFHdBwwYECnLl26dGjdunXHFi1adCgtLe1YUlLSvlmzZq0KCgqK07yDwsLCssLCwrKIaPdp/zuNjY21mzdvXrdp06ZVNTU172/YsGHl2rVr31+2bNnKBQsWrHjyySffnzVrVpVPGAAAAE1Fpuexsd9HfaF+ZcSal0ptCAAAIAE6deqUPf744zvtueeeXbp3796lbdu2XSorKzuXlpZ2KS0t7VBYWFhhSztGQ0PDxpqampU1NTXL169fv+yDDz5Y9s477yybPXv2sj/96U8rVqxYUWdLAAAAbE9t9q6Jog4f/TUFMAAAQEJks9nMt7/97Y4jRozo1bdv397t2rXrXl5e3rWsrKxzcXFx+4gosKUmp7G2tnZVTU3Nso0bNy5btWrV0tdff/2tJ598cvG999672noAAADYFgpgAACAhPne977X6a9Fb/v27Xu1bNmyV1lZWa8kvXOXLauvr9/wl7/8ZdG6desWL1u2bNHChQsX/fGPf1w8derUjbYDAADAliiAAQAAmqhsNps59dRTuxx66KH9+/Tp87n27dv3Ly8v719UVOSRzXlq06ZNKzZu3Pj6+++//8abb775xqOPPvrG3XffvcpmAAAA+CsFMAAAQBNx6qmndvniF784qHfv3v3btWv3uYqKis8VFhaW2wxbUl9fv37Dhg1vfPDBB68vXrz4jccee2z+jTfeuNxmAAAA8pMCGAAAYBc45phjWn/rW9/aq3///kPatGnTv6Kiop9HOLO9NDQ0VG/cuPGtNWvWLFy4cOGcO+6445WHHnporc0AAACknwIYAABgJzjjjDO6f+lLX9qrV69eg1u3bj2orKysR0RkbIadJFddXb103bp18xcvXjz30UcffeXqq69+x1oAAADSRwEMAACwnZWWlhb86le/2u3QQw8d1r17931btmw5qLCwsMxmaEoaGhqqP/zww/nvvPPOzGeeeWbW2LFj36ipqWm0GQAAgGRTAAMAAGwHP/7xj7t+9atf3bdXr15D27Ztu1c2m21jKyRJXV3dmg8++OCVRYsWvfznP/95xh/+8IdltgIAAJA8CmAAAIBtcOKJJ7Y75ZRTDujXr9+w1q1bD81ms61shTSpq6tbt3bt2pfffPPNWbfccsvUe++9d7WtAAAANH0KYAAAgE+hoqKi4IILLhg0YsSI/bp27bpfy5YtB2YymUKbIR/kcrmGDz/8cP6777474/nnn59x4YUXvrZx40aPiwYAAGiCFMAAAAAf4/jjj2/7/e9//8D+/fsf2Lp1630KCgpKbAUiGhsbN61fv37eW2+9NeWGG2545u67715lKwAAAE2DAhgAAOB/ZLPZzAUXXPC5I4888sDu3bsfWFFRsVtEFNgMbFl1dfWSd999d8qsWbNmnnvuuS+vW7euwVYAAAB2DQUwAACQ10pLSwsuvfTSQYcccsjBXbt2HVFWVtbDVmDb1dbWrnr//fdfmDp16uRf/vKXL65evbreVgAAAHYeBTAAAJB3Bg0aVHrBBRd8fs899zywQ4cOBxQVFbWwFdj+Ghsba9euXTtrzpw5T59//vmTX3755WpbAQAA2LEUwAAAQF4YNmxY8/POO+/gIUOGHOZ9vrDz/W0ZfNFFFz07a9asKlsBAADY/hTAAABAarVq1arwyiuv3HfEiBEjO3TocFBhYWGZrcCu19DQUP3+++8/O2XKlIk/+clPZm7cuLHRVgAAALYPBTAAAJAqrVq1Kvztb3+7/3777Xd4x44dRxQWFpbbCjRdDQ0NG99///0pM2bMeOqHP/zhC8pgAACAz0YBDAAApMJZZ53V45vf/OaRvXr1GllaWtrVRiB5ampq3l28ePHEO++8c9LVV1/9jo0AAABsPQUwAACQWMOHDy+/6KKLvjB48OCjW7RoMdBGID0+/PDDV+fNmzfhvPPOe3L69Ol/sREAAIBPRwEMAAAkSqtWrQpvuOGGQ/bbb79/atOmzX6ZTCZrK5BeuVyubs2aNTNmzJjx2JgxYyavW7euwVYAAAA+ngIYAABIhB//+Mddv/e9732lZ8+e/1RcXNzWRiD/1NbWfvD2228/dssttzz029/+9l0bAQAA+L8UwAAAQJNVUVFRcO21137+4IMPPrZ169b7ZTKZAlsBIqJxzZo1M59//vnxp5122hR3BQMAAPx/CmAAAKDJOeWUUzqefvrpx/bu3ftL2Wy2jY0AH6e+vn7j0qVLH/vd7373x+uvv36ZjQAAAPlOAQwAADQJ2Ww2c+uttx5wyCGHnNC6deu9I8LdvsDWaFy7du1L06ZN+/OPfvSjZ1evXl1vJQAAQD5SAAMAALtU//79S6655pp/2nPPPY8tLy/vayPAZ1VTU7NswYIF488999wHp06dutFGAACAfKIABgAAdomf//znPU855ZQTu3btemRhYWGZjQDbW2NjY92KFSuevOWWW+689NJLF9kIAACQDxTAAADATuMxz8Cusn79+rlPP/30f5188slT6+rqcjYCAACklQIYAADY4fr27Vv8hz/84a+Pee5nI8CuUlNT8+68efPu/8EPfvDgwoULN9kIAACQNgpgAABghxkyZEjpNddc89XBgwefWFxc3MFGgKaitrZ21dy5c+/5yU9+8uc5c+bU2AgAAJAWWyqAPYoNAADYJqNHj+4wb968n06ZMuXRYcOGnaH8BZqa4uLi9sOGDTtjypQpj86bN++nJ510UntbAQAA0s4dwAAAwFY599xze33/+9//dufOnY/IZDJZGwGSIpfL1S1fvvzJG2644fbLLrvsbRsBAACSyiOgAQCAz+y8887r+53vfOfbHTt2PDyTyRTaCJBUuVyuYcWKFU/cdNNN//XrX/96sY0AAABJowAGAAC22WWXXTboG9/4xg9at249zDaAtFm7du2su++++9pzzjnnNdsAAACSQgEMAABsNcUvkE8UwQAAQJIogAEAgE9N8Qvks7Vr18665557rvv5z38+3zYAAICmaksFcGHlwOj6UV9orIqoWZG1PQAAyBO/+MUvet9xxx3nHHrooT8pLS3tYiNAPiotLe2y7777HvP973+/X1lZ2ZIpU6assxUAAKCpKetcHwXlH/01BTAAAOS5M844o/u99957zpe//OWflZeX94qIjK0AeS5TXl7e8+CDDx71/e9/v3dEvDVjxowPrQUAAGgqFMAAAMD/ceKJJ7a77777fjJq1Kh/KS8v7xOKX4B/lCkvL+99+OGHj/rWt77VfvXq1Qvnz59fbS0AAMCutqUC2DuAAQAgzwwdOrTs+uuvP6l///4nFRYWltkI20NjY2Ns2rQpqquro6amJurr62PTpk2xefPmqK+vj+rq6qivr4/NmzfHpk2boqGhYZv/fxUWFkZJSUk0a9YsioqKoqysLIqKiqJZs2ZRUlISRUVFUVpa+r9/FRQUCIjtoqGhoeq11167a8yYMffMmTOnxkYAAIBdZUvvAFYAAwBAnujUqVP2nnvuGbXXXnudnM1mK22Ej9PQ0BAbN26MDRs2/J+/Nm7cGBs3boyamprYtGlTbNq0KWpqaqK2trbJnqe4uDhKSkqitLT0f/9eUVERFRUV0aJFi//zV0VFRRQWFvog8LHq6urWvvjii7eceOKJf169enW9jQAAADubAhgAAPLcXXfdddAXv/jF00tLS7vZRn7L5XKxYcOGWLt2baxbty7Wrl37d3+tW7cuNmzYkPd7atGiRbRu3TpatWoVrVu3jjZt2vzvf27dunW0aNHCh4morq5e+sgjj1zzne98Z6ptAAAAO5MCGAAA8tTVV189+MQTTzyzoqJioG3kj8bGxli5cmUsX748Pvjgg1i9evX//n3t2rXR2NhoSZ9RYWFhtGrVKtq1axdt27b937937tw5OnTo4LHTeWbDhg3z77333qvOPPPMebYBAADsDApgAADIM1/72tfaXHrppad27979qIjQRKVUQ0NDrFq1KlasWBHvv//+//595cqVTfqRzGlXXFwcHTp0iI4dO0bnzp2jY8eO0alTp2jXrp1HS6dYLpdrfOeddx76+c9/fv2ECRPW2QgAALAjKYABACBP9OrVq9ldd931jT322OM7hYWFZTaSHh9++GG88847sXTp0njvvfdixYoVsXr16mhoaLCchCgsLIz27dtHp06dolu3btG9e/fo3r27x0mnTENDQ9W8efNu++Y3v/nHJUuWbLYRAABgR1AAAwBAHrjrrrtG/NM//dOZJSUlXWwj2davXx9Lly6Nd955539L3w8//NBiUqqysvJ/y+C//tWqVSuLSbiamppljz322G9Gjx49xTYAAIDtTQEMAAAp9qtf/arPD3/4w5+1atVqL9tIno0bN8aSJUvirbfeikWLFsV7770XmzZtspg8V1JSEl27do0+ffpE3759o3fv3lFeXm4xCbRu3bqXr7322ivGjh27yDYAAIDtRQEMAAApNGjQoNI77rjju7vttttJBQUFWRtJhtWrV8ebb74ZixcvjiVLlsTy5cujsbHRYtiigoKC6Ny5c/Tu3Tt69+4d/fr1i7Zt21pMQjQ2Nta98cYbd33rW9+6ff78+TU2AgAAfFYKYAAASJHS0tKCBx988Jj99tvvn7PZbBsbaboaGhri7bffjrfeeisWLFgQS5YscXcv201FRUX06tUr+vbtG3379o2ePXtGYWGhxTRhdXV1a2bMmHHjV77ylYdqamr85gcAALDNFMAAAJASp59+erdf/vKX51ZWVu5jG03T6tWr47XXXouFCxfGm2++GRs3brQUdooWLVpE3759Y8CAATFw4EB3CDdh69evf/E//uM//vPqq69+xzYAAIBtoQAGAICEGzRoUOm99977w969ex+byWTc4teErF+/PubNmxcLFiyIN954Q+FLk9GiRYvo169fDBgwIPbYY4+orKy0lCYkl8s1LF68eNyJJ554rcdCAwAAW0sBDAAACXbNNdcMOemkk35RVlbWyzZ2vVwuF++++27MnTs3XnvttViyZIl3+NLkFRQURK9evWLQoEExePDg6Natm6U0EdXV1UvuvvvuX//kJz+ZYxsAAMCnpQAGAIAEOuqoo1r99re//VmHDh0Ot41da9OmTTF79uyYO3duLFy4MKqqqiyFRGvevHn0798/Bg8eHHvuuWeUlJRYyi62cuXKp04//fTLJ0yYsM42AACAT6IABgCAhBk3btwRRxxxxFnZbLaNbewaVVVVMXfu3Jg7d27Mnz8/amtrLYVUKi4ujoEDB8bgwYNj8ODBUV5ebim7SF1d3ZqnnnrqqlGjRj1hGwAAwJYogAEAICFOOeWUjhdddNEvW7duvZ9t7HwrV66MWbNmxdy5c+Odd96JXC5nKeSdzp07x9577x3Dhg2LDh06WMgusHbt2hnnnXfepbfccsv7tgEAAHwUBTAAADRxpaWlBU899dQ3Bw8e/L2CggLPYt2JVqxYES+99FK89NJLsXz5cguBv/HXMnjvvfeOTp06WchO1NjYuGnu3Lk3H3744XfV1NR40TgAAPB3FMAAANCEjR49usOll176yzZt2gy3jZ1j/fr18eKLL8bMmTNj6dKlFgKfQs+ePWPfffeNYcOGRYsWLSxkJ1mzZs0L55577q/vvvvuVbYBAAD8lQIYAACaoIqKioKJEyd+c/Dgwd8vKCgotpEda8OGDfHiiy/G9OnTlb7wGfXo0SOGDx8ew4YNi4qKCgvZwdwNDAAA/CMFMAAANDGnnHJKx7Fjx/5rZWXlMNvYcerr6+PVV1+NGTNmxLx586Kurs5SYDvKZrMxZMiQ2HfffWP33XePwsJCS9mB1q5dO+MXv/jFv995550rbQMAAPKbAhgAAJqIbDabeeKJJ47fZ599fuSu3x0jl8vFwoULY/r06TF79uzYtGmTpcBOUFpaGkOGDInhw4fHgAEDLGQHaWhoqJ42bdo1Rx555J9tAwAA8pcCGAAAmoDjjz++7ZVXXvmr1q1be9fvDrBmzZqYNm1azJw5M1audHMc7EodO3aMz3/+87H//vt7X/CO+3fetDPPPPOScePGfWAbAACQfxTAAACwi9100037HXvssf9WXFzc1ja2n1wuF6+99lo8//zzMW/evKivr7cUaEKKiopizz33jBEjRsTnPve5yGQylrId1dbWrvrjH/948Q9+8INZtgEAAPlFAQwAALvIkCFDSu+///5zunTp8k+2sf2sXbs2Jk+eHNOnT48PP/zQQiABKisrY8SIEXHIIYdEeXm5hWxHy5Yte+zrX//6f86ZM6fGNgAAID9sqQAurBwYXT/qC41VETUrsrYHAADb6IILLtjt97///VVt2rQZZhvbx+LFi2P8+PFx9913xxtvvBG1tbWWAgmxadOmeOONN+LZZ5+NtWvXRps2bTweejtp0aJFv5NOOumg0tLSuc8+++xaGwEAgPQr61wfBR/zu7XuAAYAgO0sm81mJk2a9PVhw4b9pKCgwG9VfkZ1dXUxY8aMeOaZZ+K9996zEEiRfv36xSGHHBJDhw6NgoICC/mMGhsbN8+YMeOaL37xi+Pq6upyNgIAAOnlEdAAALCTHH/88W2vuuqqCyorK/exjc9mzZo18dRTT8XUqVNj06ZNFgIpVlFREZ///OfjsMMOi8rKSgv5jNavXz/r9NNPv3DcuHEf2AYAAKSTAhgAAHaC22677fNf+9rXzstms5W2se0WLVoUjz/+eMybNy9yOTewQT4pKiqKIUOGxBFHHBG9e/e2kM+grq5u3QMPPHDRySefPM02AAAgfRTAAACwA1VUVBQ8/fTTpwwcOPCUTCbjGabbIJfLxauvvhpPPvlkLFy40EIgz2UymRgwYEAcccQRMWjQIAvZ9n+3Ns6fP/+Www8//JaNGzc22ggAAKTHlgrgwsqB0fWjvtBYFVGzwuvKAABgS0488cR2EyZMuLx79+5fzmQyGRvZOo2NjTFr1qy49dZb48knn4wPPvC0UuC/rV69OmbMmBFz5syJ0tLS6NSpU/jX7NbJZDKZ9u3bD/3+978/dPny5TNfffXValsBAIB0KOtcHwXlH/O9gDuAAQBg29x66637H3vssRcWFRW1sI2tU1NTE0899VQ8++yzsWHDBgsBPlGLFi3i4IMPjsMPPzxKS/28YmvV19d/OG7cuPNPPvnk6bYBAADJ5xHQAACwHWWz2cyzzz77rSFDhvzAI5+3zqZNm2Ly5Mnx1FNPKX6BbdKiRYs47LDD4pBDDlEEb6VcLtfwyiuvXHfooYfeWVdX5yXrAACQYApgAADYTo455pjW11133cWVlZV728ant2HDhnj88cdjypQpUVtbayHAZ1ZcXBwHHnhgfPGLX4wWLTyIYWusWbNm2re//e3zn3nmGb+JAwAACeUdwAAAsB1cfvnlu1900UW/LS8v72cbn05VVVVMmDAhbrnllnjzzTejoaHBUoDtoqGhIZYsWRLPPfdc1NTURI8ePSKb9XOMT6OsrKzb17/+9SPbtm0774knnlhtIwAAkMDreu8ABgCAz+bhhx/+8qGHHnpOQUFBsW18sk2bNsUzzzwTTzzxRFRVVVkIsMOVl5fHkUceGYccckgUF/tX9afR2Ni46emnn/71Mccc87htAABAsngENAAAbKN27doVTZ48+YxevXodZxufrK6uLp5++umYOHGi4hfYJSoqKuKLX/xiHHzwwe4I/pQWLVr0x4MOOuiadevWeUwDAAAkhEdAAwDANjj22GPbPvzww7/p2LHjobaxZXV1dfHkk0/GddddF3Pnzo26ujpLAXaJzZs3x2uvvRbPPfdcRET06NEjCgsLLWYLWrduvfv3vve9fd9+++1pCxYsqLYRAABo+rb0CGgFMAAAfITLL7989wsuuOB3zZs372UbH6+xsTGmTJkS119/fbzyyiuKX6DJ2Lx5cyxYsCCmT58excXF0a1bt8hkMhbzMUpKSjp8+ctfPrJt27ZzvBcYAACaPu8ABgCArTB+/Pgjv/CFL/xLQUFBiW18vAULFsT48eNj6dKllgE0eT169IivfOUrMWjQIMvYgsbGxpqJEydecuyxxz5pGwAA0HR5BzAAAHwK7dq1K3ruued+1qNHj6/axsdbtGhR3H///bF48WLLABKnV69ecdxxx0WfPn0sYwuWLl3654MOOujy1atX19sGAAA0Pd4BDAAAn2DYsGHNn3766V936tTpC7bx0TZs2BD33Xdf/PGPf4y1a9daCJBI69evj2nTpsW6deuiZ8+eUVLiYQ8fpbKysv+3v/3t/lOmTJmyfPlyz/cHAIAmxjuAAQBgC372s5/1uP76669t0aKF54J+hJqamhg/fnzcfPPN8fbbb0cul7MUINFyuVy888478cwzz0RVVVX07t07slk/A/lHZWVl3U488cTD6+rqZkyfPv1DGwEAgCZ0va4ABgCAj3bFFVfscdZZZ11dXFzcwTb+Xi6XixkzZsR1110XCxYsiMbGRksBUqWxsTGWLFkSM2bMiPLy8ujSpUtkMhmL+RvZbLbFQQcddHibNm1mP/HEE6ttBAAAmoYtFcDeAQwAQN6aNGnSqAMOOODsTCZTaBt/b9GiRXHPPffEu+++axlA3ujWrVucdNJJ0bt3b8v4B7lcrm7y5Mm//vKXv/yIbQAAwK63pXcAK4ABAMg7paWlBTNnzjyzT58+x9vG39uwYUOMGzcuZsyY4VHPQF7KZDKx3377xde//vWoqKiwkH+waNGiP+27775X1dTUeCwEAADsQgpgAAD4H926dctOnjz5V506dRppG/9fLpeLqVOnxp///OfYuHGjhQB5r6KiIkaNGhX777+/x0L/g+XLlz9+6KGHXvLuu+/W2QYAAOwaWyqAvQMYAIC8MXz48PInnnjiynbt2o2wjf/vnXfeiWuvvTaee+652Lx5s4UARMTmzZtjzpw58dprr0XPnj2jRYsWlvI/Kioq+n7rW98aMnXq1Ofee+89f3AAAMAusKV3ACuAAQDIC9/+9rc73n777X9o0aLFANv4b1VVVXHXXXfFvffeG+vXr7cQgI+wbt26eP7552P9+vWx2267RVFRkaVERElJSefjjjvuoA8++GDKK6+88hcbAQCAnUsBDABAXjv//PP7XXzxxX8oKSnpbBv/bfr06XHttdfGokWLLAPgU3jnnXdi2rRp0bp16+jc2R8nERHZbLbyC1/4whElJSUvTp48eY2NAADAzqMABgAgb/3ud7/b60c/+tFVRUVFrWwjYs2aNXHzzTfHpEmTora21kIAtkJtbW289NJL8c4770Tfvn2jtLQ073dSWFhYNnz48C/26dNn4UMPPbTMpwQAAHYOBTAAAHnp1ltv3f+b3/zmfxYWFjbP913kcrl4/vnn4/rrr4/ly5f7cAB8BitXroxp06ZFRUVFdOvWLTKZTF7vo6CgIDto0KBDBw0atOiBBx54xycEAAB2vC0VwJmex8Z+H/WF+pURa17ym6wAACTTww8//KXDDjvsXzKZTN6/rPGDDz6I22+/Pd544w0fDIDtbMCAAfGtb30r2rRpk/e7yOVyjVOmTPn1yJEjH/LJAACAHavN3jVR1OGjv6YABgAgdV555ZXTPve5z30r3/fQ0NAQjz32WDz++ONRV1fngwGwg2Sz2Tj66KPjC1/4QhQUFOT9Pl5//fU79tprr9/7ZAAAwI6jAAYAIC9ks9nMyy+/fFafPn2Oz/ddvPvuu3HbbbfFe++954MBsJN069YtvvOd70S3bt3yfhdLliy5f5999rmypqam0ScDAAC2PwUwAACpV1paWjBr1qyzevfufVw+7yGXy8WTTz4ZDz74oLt+AXaBbDYbxxxzTBxxxBF5fzfw0qVLHxg6dOjlSmAAANj+FMAAAKRar169mk2ePHlsu3btDsrnPaxcuTJuueWWePvtt30oAHaxnj17ximnnBIdOnTI6z2sXr16yiGHHPIvS5Ys2exTAQAA28+WCuDCyoHR9aO+0FgVUbMia3sAADRpQ4cOLXvqqacub9Omzf75uoNcLhfPPPNMXH/99bF27VofCoAmYP369TFlypQoKSmJnj17RiaTycs9NG/evPtJJ500ZPLkyc+sWLHCoykAAGA7KetcHwXlH/01BTAAAIk1ZMiQ0kceeeSKVq1a7Z2vO6iuro7bb789nnjiiWhs9IRNgKaksbEx5s+fH++//34MGDAgstn8/DlLaWlpp6997WuDn3rqqadXrlxZ75MBAACfnQIYAIDUOfTQQ1s8+OCDv2/ZsuUe+bqDOXPmxNVXX+2RzwBN3PLly+OFF16Ijh075u0joUtLSzudcMIJ+7/00ktPv/3227U+FQAA8NkogAEASJVhw4Y1v++++37TsmXLQfl4/vr6+hg/fnz88Y9/jNpaP0MHSILNmzfHiy++GJs3b47ddtstCgoK8m4HxcXFbY866qg9n3vuuaeXL1/ucdAAAPAZKIABAEiNI488snLcuHG/b9GixcB8PP97770XV111VcyZM8eHASCBFi1aFC+//HL069cvWrRokXfnLykp6XDcccftP2fOnGcWLVq0yScCAAC2jQIYAIBUOPLIIyvvvPPO35aXl++Wj+d/+umn48Ybb4wPP/zQhwEgwf7yl7/ECy+8ECUlJdGrV6+8O3+zZs3aHHXUUfspgQEAYNspgAEASLxjjz227W233faH5s2b98m3s1dVVcXNN98cTz31VDQ2NvowAKRAY2NjzJ8/P5YtWxYDBgyIZs2a5dX5mzVr1uaYY4458M0333xm4cKFNT4RAACwdRTAAAAk2qGHHtritttuuzofy9+33347rrnmmli8eLEPAkAKvf/++/HKK69Enz59orKyMq/Ons1mK4888sh9Zs6c+dTSpUs3+zQAAMCnpwAGACCxjjjiiJb33nvvteXl5f3y6dy5XC4mTZoUN998c1RVVfkgAKRYVVVVTJ06NbLZbPTp0ycymUzenL24uLjtV7/61c+/8sorTy1evLjWpwEAAD4dBTAAAIl06KGHtrj33nt/l2/lb3V1ddx0000xefLkyOVyPggAeSCXy8WCBQvi3Xffjd133z2y2fz5mUyzZs1aH3300fvNmDHjSXcCAwDAp6MABgAgcYYOHVo2fvz4qysqKgbk07mXLVsWV111lUc+A+SplStXxiuvvBKf+9znoqKiIm/O3axZszZHH3300GeeeebJFStW1PkkAADAlimAAQBIlCFDhpQ++uij17Rs2XL3fDr31KlT49prr42NGzf6EADksaqqqpg+fXq0bds2unTpkjfnLikpaT9q1KihTz755JMrV66s90kAAICPt6UCuMB6AABoSjp16pSdMGHCv1dWVu6RL2dubGyMcePGxR133BF1dW56AiCitrY2br755hg/fnw0NjbmzbkrKyv3mDBhwr9369bNXQkAALCNFMAAADQZrVq1Kpw+ffolbdq02T9fzlxdXR2/+93vYtKkSd73C8DfyeVy8fjjj8fvf//7qK6uzptzt2nTZv8pU6Zc0qpVq0KfAgAA2HoKYAAAmoSKioqC2bNnX9KuXbuD8uXMS5cujYsuuijmz5/vAwDAx3r11VfjoosuiqVLl+bNmdu1a3fQ7Nmz/72iosLPrgAAYCu5iAYAoEmYOXPmz9q1a3dIvpz35ZdfjiuuuCLWrVsnfAA+0bp16+KKK66Il19+OW/O3K5du4Nnzpz5M+kDAMDWUQADALDLvfjii2N69OgxKh/Omsvl4oEHHogbbrghamtrhQ/Ap1ZbWxs33HBDPPDAA3nz2oAePXqMevHFF8dIHwAAPj0FMAAAu9SkSZO+NnDgwFPy4ax1dXVx8803x8SJE73vF4BtksvlYuLEiXHLLbdEXV1dXpx54MCBJ0+aNOlr0gcAgE9HAQwAwC7z6KOPHnXggQeekw9nXbduXfz617+OWbNmCR6Az2zmzJnx61//Ol9eJZA58MADz3n00UePkjwAAHyywsqB0fWjvtBYFVGzImtDAADsEDfeeOO+Rx999EWZTKYw7Wddvnx5XHXVVbFy5UrBA7DdbNiwIWbPnh0DBw6MioqKtB8307179/179uz56sMPP7xc+gAA5LuyzvVRUP7RX1MAAwCw011xxRV7fPe7372qoKCgWdrPOmfOnPjtb38bGzduFDwA2111dXVMmzYtOnfuHB07dkz1WTOZTOHuu+9+eJs2bV6aNGnSKukDAJDPFMAAADQZZ5xxRvef/exnvy0sLCxP+1knTJgQd999d9TX1wsegB2moaEhXnrppchms9G3b99UnzWTyRTttddeB/3lL395dubMmRukDwBAvlIAAwDQJBx00EEVf/jDH64pLi7ulOZz5nK5eOCBB+Kxxx4TOgA77c+eBQsWRF1dXfTv3z8ymUxqz1pQUFBywAEHDJs+ffqkpUuXbpY+AAD5aEsFcIH1AACwMwwaNKj0vvvuu7qsrKxXms9ZV1cX1113XUyaNEnoAOx0EydOjOuvvz7q6upSfc6ysrJef/rTn67u379/idQBAODvKYABANjhKioqCh577LGLKyoqBqb5nNXV1XHNNdfE7NmzhQ7ALvPKK6/ElVdeGVVVVak+Z4sWLQZOnDhxbEVFhZ9vAQDA33CBDADADjdz5syftW3b9sA0n3HdunVx2WWXxRtvvCFwAHa5xYsXx2WXXRZr165N9TnbtWt34MyZM38mcQAA+P8UwAAA7FBPPvnkqB49eoxK8xlXrVoVV1xxRSxfvlzgADQZK1asiCuuuCJWrlyZ6nP26NFj1KRJk0ZJHAAA/lth5cDo+lFfaKyKqFmRtSEAALbZjTfeuO+XvvSlCzOZTGp/8fDdd9+NK6+8MtatWydwAJqc6urqmDVrVvTv3z8qKytTe85u3boN79mz57yHH37Yb2MBAJAXyjrXR0H5R39NAQwAwA5x3nnn9T311FOvLigoKE7rGV977bW45pprorq6WuAANFmbN2+OGTNmRI8ePaJ9+/apPGMmkykYNGjQIYWFhVOee+45v5UFAEDqKYABANipjjrqqFb/8R//8YdmzZq1SusZX3755bj++uujrq5O4AA0eQ0NDfHSSy9Fp06dolOnTqk8Y0FBQXbYsGGfnz9//qQ33nhjk9QBAEizLRXA3gEMAMB21a1bt+wNN9zwnyUlJR3TesYpU6bEjTfeGPX19QIHIDHq6+vjxhtvjKlTp6b2jCUlJZ1uuOGG/+jWrZu7GgAAyFsKYAAAtqunn376XyorK/dI6/kmTZoUd955ZzQ2NgobgMRpbGyMO+64I5588snUnrGysnLw008//UtpAwCQrxTAAABsN88///w3unTp8k9pPd/EiRNj3LhxkcvlhA1AYuVyubj//vtTXQJ36dLlS88+++yJ0gYAIB95BzAAANvFTTfdNPzII488L5PJZNJ4vsceeyzGjx8vaABS47XXXotmzZpF3759U3m+zp0779urV695Dz/88DJpAwCQNlt6B7ACGACAz+wXv/hF7x/+8IdXFxQUNEvj+R544IF45JFHBA1A6ixYsCDq6upiwIABqTtbJpPJDBo06ODGxsbnpk6dul7aAACkiQIYAIAd5oADDqj43e9+99tmzZq1TeP5xo0bF5MmTRI0AKm1aNGi2Lx5cwwcODB1ZysoKMjut99+w5577rnH33vvvc3SBgAgLbZUAHsHMAAA2yybzWbuvPPOfyktLe2exvNNmDBB+QtAXpg0aVI89NBDqTxbaWlpj3vuuedfstlsRtIAAOQDBTAAANvs+eef/06HDh0OTePZHn744Xj44YeFDEDeeOSRR+LPf/5zKs/WoUOHw5599tlvSxkAgHygAAYAYJvcd999hw8ePPjUNJ7t/vvvjwkTJggZgLzz2GOPxX333ZfKs+25554/+NOf/nSYlAEASDvvAAYAYKudccYZ3ceMGXN5QUFBcdrONnHixHjkkUeEDEDeWrx4cWSz2ejbt2/ajpbp06fPvn/5y18mz5w5c4OkAQBIsi29A1gBDADAVhk2bFjzG2+88Q/NmjVrl7azPfroo6l99CUAbI2FCxdGUVFR9OvXL1XnKigoKD7wwAP3e/LJJx9dsWJFnaQBAEiqLRXAHgENAMBWuffee39ZWlraPW3nevzxx+PBBx8UMAD8jz//+c8xceLE1J2rtLS0x3333fdLCQMAkFYKYAAAPrVJkyaN6tSp0xEpPFeMHz9ewADwD8aPHx+TJ09O3bk6der0hUmTJn1VwgAApJFHQAMA8Kmcd955fU888cR/z2QyRWk618yZM+Puu+8WMAB8jNdeey06duwYnTt3TtW5unbtuk9BQcHzzz333DopAwCQNN4BDADAZ3LEEUe0vOKKK67NZrOVaTrXyy+/HDfffHPkcjkhA8DHyOVyMXv27OjSpUt06tQpNefKZDJF++yzz/CpU6c+9u67726WNAAASeIdwAAAbLNsNpu55ZZb/q2kpKRjms61YMGCuPnmm6OxsVHIAPAJGhsb4+abb44333wzVecqLS3tcvfdd5+fzWYzUgYAIC0UwAAAbNGkSZO+3rZt2wPTdKZly5bFDTfcEPX19QIGgE+prq4urr322li+fHmqztWuXbsDH3/88VESBgAgLTwCGgCAj3XZZZcN+upXvzo2k8mk5hcH33///bjyyiujqqpKwACwlerq6uLll1+OIUOGRHl5eWrO1aVLl31LS0unPvPMM2ukDABAEngENAAAW61///4lJ5988q8ymUxRWs60YcOG+P3vfx8bN24UMABso40bN8bvfve7VP15WlBQkP3hD394ft++fYslDABA4q9vrQAAgI/y4IMPnl1WVtYrLeeprq6O3/zmN7Fq1SrhAsBntGrVqrjyyiujuro6NWcqKyvr8/DDD58lXQAAkk4BDADA/zF+/Pgju3XrdnRazlNfX5/KdxYCwK60fPnyuO6666K+vj41Z+rRo8dXx40bd4R0AQBIMgUwAAB/53vf+16nI4444py0nCeXy8Vtt90Wb7zxhnABYDt7/fXX47bbbotcLpeaMx155JHnfvvb3+4oXQAAkkoBDADA/6qoqCi4+OKLLywsLCxPy5nGjx8fs2bNEi4A7CCzZs2Khx56KDXnKSwsrPj1r399QUVFhZ+bAQCQSC5kAQD4XxMnThxdWVk5OC3nef7552PixImCBYAd7LHHHosXXnghNeeprKzc89FHHz1RsgAAJFFh5cDo+lFfaKyKqFmRtSEAgDxxwQUX7DZq1KgLM5lMYRrO8+qrr8Ytt9ySqkdSAkBT/7O3d+/e0a5du1Scp2PHjkNzudxzU6ZMWSddAACamrLO9VHwMc/wcwcwAADRt2/f4h//+McXZzKZVPwG4HvvvRc33HBDNDY2ChcAdpKGhoa47rrrYtmyZak4T0FBQfbss88e27dv32LpAgCQqGtZKwAAYPz48T8qKyvrkYazbNiwIX7/+99HbW2tYAFgJ9u0aVP8/ve/j40bN6biPGVlZb3GjRs3RrIAACSJAhgAIM/ddNNNw/v06XN8Gs5SX18f1157baxdu1awALCLrFmzJq699tqor69PxXn69ev3jd///vdDJQsAQFIogAEA8thBBx1Uceyxx/5rRGTScJ477rgjFi9eLFgA2MUWLVoUd955Z1qOU/CNb3zj34YNG9ZcsgAAJOIC1goAAPLXzTfffFZxcXG7NJxl4sSJMX36dKECQBMxbdq0mDRpUirOUlJS0unOO+88Q6oAACSBAhgAIE/913/914FdunT5UhrO8tprr8Wf//xnoQJAEzN+/PhYsGBBKs7SrVu3o2+66abhUgUAoKlTAAMA5KEvfelLlV/5yld+lYazrFixIq6//vpobGwULAA0MY2NjXHdddfFihUr0nCczHHHHfergw46qEKyAAA0ZQpgAIA8dPXVV5+ezWYrk36OmpqauPbaa2PTpk1CBYAmatOmTXHttddGTU1N4s+SzWbb3njjjT+RKgAATZkCGAAgz9x6663Du3Tp8uWknyOXy8Utt9wSK1euFCoANHErV66MW2+9NXK5XOLP4lHQAAA0dQpgAIA8MnTo0LKvfvWrv0jDWSZMmBBz584VKgAkxJw5c+Kxxx5LxVlGjRr1i6FDh5ZJFQCApkgBDACQR+64444fFRcXd0z6OV5++eV45JFHBAoACfPQQw+l4he4SkpKOt5xxx0/lCgAAE2RAhgAIE9cfvnlu/fs2XNU0s/xwQcfxB133JGKR0gCQL7J5XJx2223xZo1axJ/lp49ex57+eWX7y5VAACaGgUwAEAe6NatW/a73/3uv2YymURf/9XX18cNN9wQ1dXVQgWAhKqqqoobb7wx6uvrE32OTCZT8N3vfvdX3bp1y0oVAICmRAEMAJAHxo8ff0pZWVmvpJ/jnnvuiaVLlwoUABJuyZIlcd999yX+HGVlZT3Hjx9/ikQBAGhKFMAAACn385//vOeAAQNGJ/0c06dPjylTpggUAFJi8uTJMWPGjMSfY8CAAaN//vOf95QoAABNhQIYACDFstls5qyzzjo3k8kk+tGEK1asiLvvvlugAJAyd911V6xYsSLRZ8hkMtmzzjrr3Gw2m5EoAABNgQIYACDFxo0b98XKysq9knyG2trauOGGG6K2tlagAJAyf/1zfvPmzYk+R2Vl5V7jxo0bKVEAAJoCBTAAQEoNHz68/OCDDz4t6ee4//77Y/ny5QIFgJRavnx5jBs3LvHnGDFixI+HDRvWXKIAAOxqCmAAgJS69dZbT8tms22TfIYZM2bEc889J0wASLnJkyfHzJkzE32G4uLitrfffvtp0gQAYFdTAAMApNBVV121R48ePb6S5DOsXLky7rrrLmECQJ64++6744MPPkj0GXr27PnVK664Yg9pAgCwKymAAQBSprS0tOAb3/jGT5N8rdfY2Bi333679/4CQB6pqamJ2267LRobG5N8jIJvfvObZ5aWlvqZGwAAu+6i1AoAANJlwoQJX6uoqBiQ5DOMHz8+Fi1aJEwAyDNvvvlmPPjgg4k+Q4sWLQY9+OCDx0gTAIBdRQEMAJAiRx55ZOWwYcN+kOQzzJ07N5544glhAkCemjhxYixYsCDRZxg+fPiPjjjiiJbSBABgV1AAAwCkyBVXXHFyUVFRRVLnr6qqijvvvDNyuZwwASBP5XK5uP3226O6ujqxZygqKmrxm9/85mRpAgCwKyiAAQBS4vzzz+/Xu3fv45J8httvvz0+/PBDYQJAnlu3bl3cfvvtiT5D7969jz///PP7SRMAgJ1NAQwAkALZbDZz6qmn/jyTyST2+m769OkxZ84cYQIAERExe/bsmDFjRmLnz2QyBaeeeurPs9lsRpoAAOxMCmAAgBT44x//eERlZeXgpM6/du3auPfeewUJAPyde+65J9atW5fY+SsrKwf/6U9/+oIkAQDYmRTAAAAJ17dv3+JDDjnkR0k+w9133x01NTXCBAD+Tk1NTdx9992JPsPBBx/8o759+xZLEwCAnUUBDACQcHfdddc3S0pKOiV1/smTJ8e8efMECQB8pLlz58azzz6b2PlLSko63nPPPd+SJAAAO4sCGAAgwb70pS9VDhw48KSkzr9mzZoYP368IAGALXrggQdizZo1iZ2/f//+Jx111FGtJAkAwM6gAAYASLArrrji1MLCwvIkzp7L5eK2226LTZs2CRIA2KJNmzbFbbfdFrlcLpHzFxYWll1++eU/kCQAADuDAhgAIKF+8Ytf9O7evftXkjr/s88+G2+88YYgAYBP5Y033ojnn38+sfN369bt6F/96ld9JAkAwI6mAAYASKgf/vCHP8pkMom8nvvggw/igQceECIAsFXGjRsX69atS+TsmUym4NRTT/2xFAEA2NEUwAAACXTdddcNa9eu3YFJnD2Xy8Udd9wRtbW1ggQAtsqmTZvizjvvTOz8bdq02f+mm27aT5IAAOxICmAAgIQpLS0t+NrXvnZ6Uud/4YUXYuHChYIEALbJq6++GjNmzEjs/Mccc8zpFRUVfiYHAMAO42ITACBhbr/99oMrKip2S+LsGzZsiHHjxgkRAPhM7r///qiqqkrk7OXl5X3/67/+6wgpAgCwoyiAAQASpKKiouCwww47Nanz33vvvYn9YS0A0HRs2LAh7r///sTOf9BBB/1zq1atCiUJAMCOoAAGAEiQ+++//+iysrKeSZx9zpw58dJLLwkRANguXnjhhViwYEEiZy8tLe32xz/+8StSBABgR1AAAwAkRN++fYv33Xfff07i7LW1tXHvvfcKEQDYru6+++6oq6tL5Oz77bffKf379y+RIgAA25sCGAAgIW6++eZRxcXFbZM4+yOPPBJr164VIgCwXa1atSoee+yxRM6ezWbb3njjjV+TIgAA25sCGAAgAYYOHVq21157fSeJs7/33nvxxBNPCBEA2CEmTpwYK1asSOTsQ4YM+c7QoUPLpAgAwPakAAYASIBrr732xKKiosqkzZ3L5eKee+6JxsZGIQIAO0R9fX3cddddkcvlEjd7UVFR5bXXXnuCFAEA2J4UwAAATdwBBxxQMWDAgG8kcfYZM2bEW2+9JUQAYId6880348UXX0zk7AMGDPjG8OHDy6UIAMD2ogAGAGjirrrqqhOKiooqkjb3pk2b4oEHHhAgALBT3H///VFbW5u4uYuKilpcffXV7gIGAGC7UQADADRhBx10UEX//v0Teffvww8/HB9++KEQAYCdYv369TFhwoREzj5w4MBvHHDAARVSBABge1AAAwA0Yf/5n/95bGFhYfOkzb1q1aqYPHmyAAGAnerpp5+O1atXJ27uwsLC8ssuu2yUBAEA2B4UwAAATdQBBxxQMWjQoNFJnP3uu++O+vp6IQIAO1V9fX3cddddiZx99913/+bQoUPLpAgAwGelAAYAaKIuv/zyYwsLC8uTNvfcuXNjwYIFAgQAdokFCxbE3LlzEzd3UVFRi9/97ndflyAAAJ+VAhgAoAkaOnRo2aBBgxL37t+6urr405/+JEAAYJf605/+FHV1dYmbe/fdd//mkCFDSiUIAMBnoQAGAGiCfvOb33ylqKioZdLmfu655xL53j0AIF1Wr14dzz33XOLmLioqann11VcfLUEAAD4LBTAAQBPTq1evZoMHD/5m0uaurq6ORx55RIAAQJPwyCOPRHV1deLmHjJkyLe6deuWlSAAANtKAQwA0MTcdNNNxxQXF7dN2twTJkyIqqoqAQIATUJVVVUifzmtuLi43a233uouYAAAtpkCGACgCWnVqlXhXnvtdVLS5l61alU8++yzAgQAmpTJkyfHqlWrEjf30KFDR7dq1apQggAAbAsFMABAE3LLLbccXlJS0jlpcz/44INRX18vQACgSamvr48HH3wwcXOXlJR0vummmw6VIAAA20IBDADQRGSz2cwBBxzw7aTNvWjRonjppZcECAA0SS+99FIsXrw4cXOPGDHiO9lsNiNBAAC2lgIYAKCJuOaaa/YuLy/vm7S5H3roocjlcgIEAJqkXC6XyLuAy8vL+1111VV7SRAAgK2lAAYAaCK+8pWvfDdpM8+bNy8WLlwoPACgSVu4cGG8+uqrrg8BAMgLCmAAgCbgsssuG1RZWblPkmbO5XIxfvx44QEAifDAAw8k7qklrVu33veSSy7pLz0AALaGAhgAoAkYNWrUCUmbefbs2bFs2TLhAQCJsGzZsnjllVcSN/cJJ5xwovQAANgaCmAAgF3sn//5nzt37NjxiCTN3NjYGA888IDwAIBEGT9+fDQ0NCRq5k6dOn1h9OjRHaQHAMCnpQAGANjFfvSjH30tk8kk6rps2rRpsWrVKuEBAImyatWqeOGFFxI1cyaTKfzpT386SnoAAHxaCmAAgF1o0KBBpX369Plqkmaur6+PCRMmCA8ASKQJEyZEXV1dombu27fvV/r27VssPQAAPg0FMADALnTZZZcdXlRUVJGkmadOnRpr164VHgCQSOvXr48pU6YkauaioqLK3/zmN0dIDwCAT0MBDACwi2Sz2cy+++57UpJmrqurc/cvAJB4jz76aOLuAt5///1PymazGekBAPBJFMAAALvI1VdfPbSsrKx3kmaeMmVKbNiwQXgAQKJt2LAhnn/++UTNXFZW1ueqq67aS3oAAHwSBTAAwC7y5S9/+bgkzVtfXx8TJ04UHACQCo8//nji7gL+0pe+dLzkAAD4JApgAIBdYPTo0R3atm07IkkzT5s2LdatWyc8ACAVPvzww5g+fXqiZm7fvv2I0aNHd5AeAABbogAGANgFfvrTn47KZDKFSZm3vr4+HnnkEcEBAKnyyCOPRH19fWLmzWQyhT/96U+/JjkAALZEAQwAsJN16tQp26dPn6OTNLO7fwGANFq3bl1MmzYtUTP36dPnmE6dOmWlBwDAx1EAAwDsZFddddUB2Wy2dVLmbWxsjEmTJgmOVOvYsWN06OCJmgD5aNKkSdHY2JiYebPZbOurrrrqAMkBAPBxFMAAADvZiBEjvp6keV988cVYtWqV4Ei1Ll26xIUXXhinnXZadO3a1UIA8siqVavipZdecj0JAEBqKIABAHaiM844o3tlZeXeSZk3l8vFxIkTBUdeyGQyMXjw4PjVr34VY8aMcUcwQB55/PHHI5fLJWbeysrKvc8444zukgMA4KMogAEAdqJTTjnlqxGRScq8CxYsiPfee09w5JVMJhN77713XHjhhTFmzJho3769pQCk3HvvvRcLFy5M1B9X/3NdCQAA/4cCGABgJ+nVq1ezXr16fTlJM3v3L/nsr0XwBRdcECeffHK0bdvWUgBSLGnXPb169fpyr169mkkOAIB/pAAGANhJrrjiioOLiopaJmXeBN4JAztEYWFhDB8+PC688MIYPXp0VFZWWgpACi1YsCCWLVuWmHmLiopaXnnllYdIDgCAf6QABgDYSYYPH/6VJM2btHfhwY5WVFQUI0aMiEsuuSRGjx4dLVu2tBSAFMnlcvH4448naub99tvvK5IDAOAfKYABAHaC0aNHd6isrByalHnXrl0bL7/8suDgI/y1CL744ovjhBNOiBYtWlgKQEq89NJLsW7dusTMW1lZudfo0aM7SA4AgL+lAAYA2AlOP/30o5J07fXMM89EQ0OD4GALiouL47DDDouxY8fGqFGjoqyszFIAEq6hoSGeeeaZJI1c8D/XmQAA8P8vEq0AAGDHymazmX79+n05KfPW1tbGlClTBAefUnFxcYwcOTIuvfTSGDVqVJSWlloKQII9//zzUVtbm5h5+/Xr9+VsNpuRHAAAf6UABgDYwX7zm9/sWVJS0jkp886YMSOqq6sFB1uppKQkRo4cGZdcckkcffTRUVJSYikACVRdXR0zZ85M0p8/na+44orBkgMA4K8UwAAAO9gXvvCFLyVl1lwuF08//bTQ4DNo3rx5HHXUUXHJJZfEyJEjI5vNWgpAwjz11FORy+USM++RRx75ZakBAPBXCmAAgB1oyJAhpZ07dz4iKfO+/vrrsWLFCsHBdlBeXh6jRo2KSy+9VBEMkDArVqyI119/PTHzdunS5fD+/ft79AQAABGhAAYA2KHGjh17aGFhYWJeCOruX9j+KioqYtSoUXHxxRfH4YcfHkVFRZYC4LpouyosLGz+H//xHwdLDQCACAUwAMAOteeeex6ZlFnXrl0b8+bNExrsIK1atYrjjz8+LrroohgxYkQUFPh2DKApmzdvXqxZsyYx8+61115HSg0AgAgFMADADnPMMce0bt269b5Jmfe5556LxsZGwcEO1qZNmxg9enRcfPHFimCAJqyxsTGee+65JP35MvyYY45pLTkAAPykAQBgBznzzDMPz2Qyibje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FpafffbZvaQFAJA+CmAAgE9p//3375+EOV9//XVhAXmjvLw8Ro0aFf/+7/8eI0eOjGw2aymA67FP6fOf//xAaQEApI8CGADgU+rRo8fuTX3G2traePvtt4UF5J2KiooYNWpUXHzxxXH44Yc3+XdvAum2ePHiRLySo1evXoOkBQCQPgpgAIBPqXXr1k3+DoklS5ZEQ0ODsIC81apVqzj++OPj4osvjhEjRkRBgW97gZ2voaEhlixZ0uTnbNOmjQIYACCFfCcMAPApDBkypLR58+a9m/qcb775prAAIqJ169YxevToGDt2rCIYcF32MZo3b95n0KBBpdICAEgX3wEDAHwKp556av9MJtPkr53eeustYQH8jTZt2sTo0aPjvPPOi+HDhyuCAddlfyOTyRT84Ac/+Jy0AADSxXe+AACfwuDBg5v84/Hq6+tj0aJFwgL4CJ06dYqTTz45/u3f/i323nvvyGQylgLsUIsXL07Eqzn23HPPgdICAEgXBTAAwKfQpUuXAU19xnfeeSfq6uqEBbAFnTt3jjFjxiiCgR2utrY23n333SRc53oPMABAyiiAAQA+hZYtW/Zv6jN6/DPAp9elS5cYM2ZMnHvuuTF48GALAfL2+iwJ17kAAGwdBTAAwCcYPnx4eUlJSeemPqfHPwNsvV69esVpp50W55xzTvTvrwMB8u/6rLS0tPPw4cPLpQUAkB4KYACAT/Ctb31rt4ho8s8IXbx4sbAAtlGfPn3irLPOinPOOSd22203CwG2i4T8gl7m29/+dj9pAQCkhwIYAOAT7L777k2+CVi7dm1s2LBBWACfUZ8+feLss8+OM888M3r27GkhwGfy4Ycfxrp165r8nAMHDlQAAwCkSJEVAABsWadOnZr8D8TefvttQQFsRwMGDIgBAwbEggULYvz48bF06VJLAbb5Oq1Vq1audwEA2GkUwAAAn6CyslIBDJCnBgwYEP3794958+bFQw89FO+++66lAFtlyZIlsddeezX1613PvgcASBEFMADAFnTq1CnbvHnzXk19ziVLlggLYAfJZDIxePDg2GOPPeLll1+OBx98MFauXGkxQGqu05o3b967Xbt2RatXr66XGABA8nkHMADAFowZM6ZnJpPJNuUZGxsbPZoUYCfIZDKx9957x4UXXhhjxoyJ9u3bWwrwiZYuXRqNjY1NesaCgoLsqaee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k8nE7NmzY+LEibFmzRoNAXif/VNSzJo161kTAwBongTAAAC70O233/7KOeecszU/P78022sdMGBAtG/f3tWu0My9+uqrMWnSpFi1apVmALyPjh07xgEHHJCIWhsbG6u/8Y1v/NXUAACaJwEwAMAutHLlyob169fP6tat2+hsrzWVSsXhhx8ekyZNMjhohubMmROTJ0+OJUuWaAbAh3D44YdHKpVKRK3r16+fVVFR0WhqAADNU54WAADsWq+++mpirts77LDDDAyamTfffDP+/d//PX7yk58IfwE+pFQqFSNGjEhMva+88sozpgYA0Hw5AQwAsIvddNNNz5166qnV+fn5rbO91m7dukX//v1j4cKFBgc5btmyZfHYY4/F/PnzNQPgIzrggAOic+fOiai1sbFxy4033jjL1AAAmi8BMADALrZy5cqG8vLyGT179vx0Euo9+uijBcCQw5YvXx7jxo0T/AJ8wv1SUpSXlz9TXl7eYGoAAM2XABgAYDeYMWPGUxdccEEiAuAhQ4ZE69ato7q62uAgh6xcuTImTJgQc+fOjUwmoyEAH1ObNm3i0EMPTUy9zz777FOmBgDQvPkGMADAbvDVr371lYaGhk1JqLWgoCCOOOIIQ4McsW7duvj5z38ed955Z8yZM0f4C/AJjRw5MvLz8xNRa0NDw8abbrrpVVMDAGjenAAGANgNKisrm1atWjW9T58+5ySh3qOPPjqefPJJQREk2Pr16+Oxxx6L2bNn+3cZYBdJpVIxatSoxNS7cuXKadXV1WmTAwBo3pwABgDYTaZNm5aY6/c6duwYAwYMMDRIoA0bNsSvf/3r+Nd//dd49dVXhb8Au9CAAQOiY8eOian36aefftLUAAAQAAMA7CZf+9rX5tTX11ckpd6jjjrK0CBBqqqq4v77749vf/vb8eKLL0ZTU5OmAOxiRx55ZGJqra+vX/eNb3zjdVMDAMAV0AAAu0ltbW16xYoVT++///6fT0K9hx56aLRt2zaqqqoMD7LYli1bYsqUKfHcc8/F9u3bNQRgN2nbtm0MGTIkMfUuX778qdraWtc/AwDgBDAAwO70xz/+cUJSas3Pz4/jjjvO0CBL1dTUxKOPPhq33XZb/OUvfxH+Auxmxx57bOTn5yel3Myvf/3rCaYGAECEABgAYLe66667llZXV89PSr1HH310tGjRwuAgi7wd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qATAAABZ4umnn968fPnyxF3xN3r06GjXrp0BAgBZqUOHDnH88ccnru5ly5ZNmD59+hYTBADgoxIAAwBkkbvvvvtPmUymKUk1FxYWxumnn254AEBWOvPMMxP3yYpMJtN41113/dH0AAD4OATAAABZ5Be/+MWatWvXTkta3UcccUT06tXLAAGArNK7d+847LDDEld3eXn5X+6///51JggAwMchAAYAyDIPP/zwn5JWcyqVijPPPNPwAICsMmbMmEilUomr+8EHH/yT6QEA8HEJgAEAsszXv/71NzZs2DAraXUPHDgwDj74YAMEALLCoEGDYsCAAYmru6KiYuatt966wAQBAPi4BMAAAFlo3Lhxv01i3WPGjIm8PFtMAGDvysvLizFjxiSy9j//+c+/NUEAAD7RflgLAACyz/XXXz+nqqrqr0mru1evXnHUUUcZIACwVx1zzDHRvXv3xNW9adOmV2666aa5JggAwCchAAYAyFJ/+tOf7k1i3WPGjInS0lIDBAD2ijZt2sRZZ52VyNofeOCBe00QAIBPSgAMAJClbrrpprlJPAVcXFwcZ555pgECAHvFWWedFS1btkxc3Zs2bXrl5ptvnmeCAAB8UgJgAIAsNm7cuF8lse5Ro0ZF7969DRAA2KP69OkTI0eOTGTtjz322K9MEACAXUEADACQxcaOHTu7qqrqb0mrO5VKxfnnnx+pVMoQAYA9tv/4/Oc/n8j9R2Vl5d+uueaav5kiAAC7ggAYACDLTZ069bdJrLtv374xZMgQAwQA9ojDDjsssTeQTJ48+TcmCADAriIABgDIcpdeeumLVVVVryax9s9//vNRXFxsiADAblVaWhrnn39+Imuvqqr66+WXX/6SKQIAsKsIgAEAEuChhx76WRLrLisri9NPP90AAYDd6qyzzoqSkpIklp753e9+91MTBABgVxIAAwAkwA033DB3w4YNs5JY+3HHHRd9+vQxRABgt+jbt28cddRRiay9oqJi1te//vU3TBEAgF1JAAwAkBA///nPfxoR6aTVnUql4vOf/3zk5dl6AgC7Vn5+flx00UWRSqWSWH76F7/4xX+bIgAAu5q3cAAACXHHHXe8tW7duulJrL13795xzDHHGCIAsEudcMIJ0b1790TWXl5e/pc77rjjLVMEAGBXEwADACTI3XfffW8mk2lKYu1nnXVWtG3b1hAB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onv37vEv//IvccABBzSrdWcymaYnnnji9s997nPPeAoAAODDEQADAJBYDz/88NJjjjlmY+/evY+KiGZzFKq0tDSGDx8eq1evjvXr13sQAHLcwIED45prrol27do1t6VnZsyY8YOzzjprqqcAAAA+PAEwAACJdv/99795zDHHVO6zzz5HRDMKgVu0aBGHHXZYtGzZMhYuXBjpdNrDAJBjCgoK4pxzzonzzz8/WrRo0dyWn37uuefuOuWUUyZ4EgAA4KMRAAMAkHi///3v5w8bNmxF3759j2lO10GnUqno27dvDB06NBYvXhxbtmzxMADkiJ49e8YNN9wQgwcPblbf+434x7XPU6ZM+fbpp58+2ZMAAAAfnQAYAICc8OCDDy4ZNmzYin79+jWrEDgionXr1nHEEUdEfX19LF261MMAkGCpVCpOOOGEuOKKK6JNmzbNbv07wt9vnXPOOX/xNAAAwMcjAAYAIGc89NBDS4YNG7a8X79+xza3EDg/Pz8OPvjg6NWrV8yfPz8aGho8EAAJU1JSEpdffnmccMIJkZ+f3+zWn8lkGp944olvffazn53maQAAgI9PAAwAQE556KGHlo4cOXJdnz59RqWa252ZEdG1a9cYNmxYLFu2LCorKz0QAAnRt2/fuO6662K//fZrluvPZDLpp59++rvnnHPO054GAAD4ZATAAADknD/96U+LDj300KX7779/s7sOOiKiuLg4jjzyyCgpKYk333wz0um0hwIgSxUUFMRnP/vZuPDCC6OkpKRZ9iCdTjc88sgjt5x//vnTPREAAPDJCYABAMhJDz/88NId3wQelUqlmt09mqlUKvr06RMHH3xwLFy4MLZt2+ahAMgynTt3jrFjx8bQoUOjGV5aERH/CH+feOKJ2y+88MLnPBEAALBrCIABAMhZDz300JIePXq8NmjQoGPz8vJaNMcetG3bNkaNGhVNTU2xZMkSDwVAFkilUjF69Oi48soro0OHDs22D01NTdt++9vf/stll132oqcCAAB2HQEwAAA5bdKkSeXdu3efM3jw4GYbAufn58eAAQOiV69esWDBgti+fbsHA2Avad26dVx66aVx/PHHR35+frPtQ2NjY/WvfvWrf7nuuute81QAAMCuJQAGACDnTZ48eW1jY+OMI4444uiCgoKS5tqHrl27xqhRo2Lbtm2xcuVKDwbAHpRKpWLUqFExduzY6NWrV7PuRX19/fo77rjjK9/61rcWejIAAGDXEwADANAsPP/881UbNmx45rjjjjuqsLCwrLn2obCwMAYNGhT77bdfLF68OGpraz0cALtZ+/bt44orrogTTzwxCgub9/uU2tralTfffPPVP/nJT1Z7MgAAYPcQAAMA0Gz87W9/27p27doZJ5xwwsjCwsK2zbkXnTp1ipEjR0Z1dbXTwAC70ciRI+Pqq6+OHj16NPte1NTULL/hhhuu/e1vf7vOkwEAALuPABgAgGbltdde2zpv3rynTznllKFFRUWdmnMvCgsL49BDD4399tsvFi1a5DQwwC709qnfk08+udmf+o2I2Lx587yLL774unHjxm30dAAAwO71fgFwat9zYsTOftC4LmLjq610DwCAxOrVq1fhs88++69du3Y9QTciGhoaYurUqTF58uRobGzUEICPqaCgIE499dQYPXq04HeH8vLyp4499tjvrly5skE3AABg9+swrDYKuuz8ZwJgAAByWuvWrfNeeumlm/fdd9+zdeMfVq9eHffff38sWbJEMwA+ov322y8uuugi1z2/w8KFC38/fPjwnzY0NGR0AwAA9gwBMAAAzd7zzz9/8aGHHnp1RKR0IyKTycTMmTPjz3/+c9TV1WkIwAcoKSmJ8847L0aMGBGplL9Kdki//PLL9xx77LEPagUAAOxZ7xcA+wYwAADNwn333Tfn+OOP39yrV6/DQwgcqVQqevfuHcOHD49169ZFRUWFhwTgPRxyyCExduzY6N+/v/B3h0wm0/jMM8/828knnzxONwAAYM97v28AC4ABAGg2fve7370xePDgJf369Ts6lUrl60hEcXFxjBgxInr06BFLly6N2tpaTQHYoUOHDvGFL3whzjzzzCguLtaQHZqammoeeOCBWz73uc9N1w0AANg7BMAAALDDww8/vKygoOC54cOHH1FQUFCqI//QrVu3OO6446K0tDQWL14cTU1NmgI0Wy1btoxzzjknLr300ujevbuGvENtbe2K22677arbbrvtDd0AAIC9RwAMAADv8Oyzz25avHjx0yeddNKQoqKiTjryD3l5edGnT58YOXJkbN26NVatWqUpQLNz+OGHx1e+8pUYMGBA5OXlacg7VFVV/e3CCy+8/oEHHvDdAAAA2MsEwAAA8C7z58+vefbZZ58+44wz+hcX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OigOOOCAKCoq0pQs0djYuGXmzJk/PvPMMyc2NDS4XgAAAPjQBMAAAJADjjzyyNY/+9nPrujbt++5EZGnI9mluro6Fi5cGPPnz4958+ZFZWWlprBXtGvXLg455JAYMGBA9O/fP1q39nsjWSj91ltv/fmqq676xaxZs6q1AwAA+KgEwAAAkEN++ctfjhgzZsyNrVq16q0b2SmdTsfKlStj0aJFsXDhwli8eHFs27ZNY9gtSkpKol+/ftG/f//Yf//9o1evXpGX53dEslVNTc3yRx999EdXXnnlK7oBAAB8XAJgAADIMd26dSt8+OGHPzd48ODL8vPzbdyzXCaTiTVr1sTChQtj0aJFsWjRotiyZYvG8LGUlZVF//79/xn6du/ePVKplMZkuaampprXXnvtV2PGjHmgoqKiUUcAAIBPQgAMAAA56rjjjiv7r//6r8tdC508mzdvjuXLl8eKFSti+fLlsXjx4qipqdEY/pfi4uLo169f9O7dO/bZZ5/Yd999o6ysTGMSJJPJpJcsWfLn66677pfTp0/3mx8AAMAuIQAGAIAc99Of/nTIueeee3NpaWlf3UimxsbGWLlyZSxdujSWLVsWy5cvj3Xr1kUmk9GcZiKVSkWXLl1in332iT59+kSfPn2iV69eUVBQoDkJtXXr1sUPPvjgj6699tq/6wYAALArCYABAKAZaNeuXf64cePOGTp06BUFBQWtdST56uvrY+XKlf88KbxixYpYu3ZtpNNpzUm4vLy86Nq1a+yzzz7//KdXr17RsmVLzckBjY2NW/7617/+4pxzznm0srKySUcAAIBdTQAMAADNyIknntjmnnvuuXzfffcdk0qlHB3MMdu3b481a9bEmjVrYu3atbF27dooLy+PDRs2CIazUF5eXnTs2DG6desWXbt2jW7dukW3bt2iR48eUVhYqEE5JpPJNC5ZsuRR1z0DAAC7mwAYAACaoa9+9av7Xnfdddd16NDhCN3IfY2Njf8MhNetWxfr1q2LioqKWLduXWzbtk2DdrPS0tLo3LnzP//p0qVLdO3aNbp27eoK52Ziw4YNM//rv/7rxz/60Y+W6wYAALC7CYABAKAZ++UvfznirLPOuq64uNj3gZupmpqaWL9+/T//qaioiMrKyqisrIxNmzZFY2OjJn2AgoKCaN++fbRr1y7at28fHTt2jC5dukSnTp2ic+fOUVxcrEnN1NatW98aP378PVdcccXLugEAAOwpAmAAAGjm2rVrl//ggw+eOWLEiCsLCwvb6gjvtHnz5n+GwZs2bYrKysqorq6OLVu2xJYtW6K6ujqqq6sjk8nk3NpTqVS0bt06WrduHWVlZdGmTZsoLS39X2Fvu3btok2bNh4U/peGhobKl1566efnnHPO+OrqavevAwAAe5QAGAAAiIiIoUOHFt97770XHHjggRfk5+c7ssiHlk6n/xkEV1dXR01Nzfv+k8lkora2NtLpdNTX10dTU1PU1dXt0u8U5+XlRcuWLSM/Pz+KiooiLy8vWrVqFalUKoqLi3f6T0lJSbRq1eqfgW9paWnk5eUZMB9aU1NTzYIFC/745S9/+Y+zZ8+u0REAAGBvEAADAAD/y2mnndb2rrvuurRPnz5n5+XlFeoIe9LbgfDbtm/f/r7XUBcUFESLFi3++b/fDnxhT8pkMg1LliwZd8stt/xq0qRJVToCAADsTQJgAABgp0477bS2d95554X777//5wTBAP9XJpNpWLhw4QO33nrrHwS/AABAtni/ADi/7UHRc2c/SG+LqC33/gcAAHLZokWL6u69995X0un0swcddFCnkpKS3roC8A8VFRUz/7//7/+77aKLLpq6aNGiOh0BAACyRXH3xsgr3fnPBMAAAEDMnDmz8u67736qqalpWr9+/Ypbt27dN5VKpXQGaG4ymUx6zZo1U+65555/Peeccx6cOXNmpa4AAADZRgAMAAB8KDNnzqz88Y9//Gw6nZ4uCAaak7eD37vvvvtfzz///McEvwAAQDYTAAMAAB+JIBhoLgS/AABAEgmAAQCAj+XtILisrOyFvn37diwuLu4VEYJgIBdkKioqnrv33nu/PWbMmEcEvwAAQJK8XwCc2vecGLGzHzSui9j4aivdAwAA/unqq6/u8ZWvfOX8Pn36nJWXl9dCR4CkSafT9UuXLh3/X//1Xw/84he/WKMjAABAEnUYVhsFXXb+MwEwAADwkZ1xxhntb7/99rMPPPDA8/Pz81vrCJDtmpqaqhcsWPDgd77znUcmTpzotC8AAJBoAmAAAGC3GD58eMkPf/jDzwwePPjioqKijjoCZJv6+voNr7322u9vvPHGx2fPnl2jIwAAQC4QAAMAALvV0KFDi++5556zDjnkkPOKioq66giwt9XV1a19/fXXH7z++uvHC34BAIBcIwAGAAD2iFatWuXdc889w0455ZTzO3bseJSOAHtYZsOGDbOmTJny4PXXX/9qbW1tWksAAIBcJAAGAAD2uH/913/tf/7555/dq1ev0/Ly8lroCLC7pNPp+pUrV05+4IEHHvnOd76zSEcAAIBcJwAGAAD2mjPOOKP97bfffvYBBxzw2YKCgjY6AuwqjY2NVW+++eafv/e97z06YcKETToCAAA0FwJgAABgrxs+fHjJ97///dGDBg0aU1paur+OAB9XdXX1wtdee+3Rr371q1Nfe+21Wh0BAACaGwEwAACQVb761a/ue8EFF3y6b9++ZxUUFLTWEeCDNDY2bnnrrbfG/+EPf5j4ox/9aLmOAAAAzZkAGAAAyEpDhw4t/sEPfnDy4MGDz27dunV/HQHerbq6+s2XX375weuuu+7ppUuXbtcRAAAAATAAAJDlCgsLU3ffffeQ0aNHn9G1a9fj8vLyinQFmq90Ol1XXl4+bcqUKROuvfbav+sIAADA/yYABgAAEqNPnz4t/v3f/33UyJEjz2rfvv2nIiKlK9AsZDZt2vTXF1544bGvfe1rzzntCwAA8N4EwAAAQCJddNFFXa6++uqTDzzwwLNbtmzZTUcg99TV1a1ZsGDBuJ/+9KdP3n///et0BAAA4IMJgAEAgETr1q1b4d13333k4YcffmqHDh2OyMvLK9QVSK50Ot2wcePGWbNmzZp8/fXXz6qoqGjUFQAAgA9PAAwAAOSMo48+uvWtt956/CGHHHJKu3btBkdEnq5AIqQrKytfmzdv3uQ777xz+owZM6q1BAAA4OMRAAMAADnpuOOOK/vGN75x/CGHHHJa27ZtB4bvBUO2yVRVVc2dN2/epO9///vTpk+fvkVLAAAAPjkBMAAAkPNuu+22vmedddZJffr0Oa5Vq1a9dQT2ntra2uXLly+f/uijjz51xx13vKUjAAAAu5YAGAAAaFa+9KUvdbv44ouP7t+//wlOBsMekamqqpq7cOHCv/z+97+fcd9995VrCQAAwO4jAAYAAJqtSy65pOtll112jDAYdrl0VVXVvIULF/7lV7/61bO/+93v1moJAADAniEABgAAiIirr766x/nnnz9q//33P7JNmzZDUqlUga7Ah5fJZBoqKyv/vnjx4uf+9Kc/zbr33ntX6woAAMCeJwAGAAB4l379+hV97WtfGzRy5MhRPXv2PLaoqKizrsD/VV9fv37VqlXPvPDCC8/9+7//+5zFixfX6woAAMDeJQAGAAB4H61bt8678847Bx599NFHde/efURpaen+4apomq/M1q1bF69Zs+bF5557btY3v/nNOdXV1WltAQAAyB4CYAAAgI9g8ODBrcaOHXvI8OHDD+vevfvw1q1bHxACYXJXprq6+s01a9a88sorr7z8k5/8ZN5rr71Wqy0AAADZSwAMAADwCVx//fX7nHHGGYf169dvePv27Yfm5+e31hWSrLGxsXrjxo2vvvXWWy+PHz/+lR//+McrdQUAACA5BMAAAAC70Je//OUe55xzzvA+ffoM7tix45CioqKuukI2q6+vX7t27doXFy9ePGfixImv3Xvvvat1BQAAILkEwAAAALvRl7/85R6f+cxnBvfr129Qly5dDm/ZsqVAmL2qrq5u7bp16wS+AAAAOUoADAAAsIe0atUq75prrtnn2GOPPXi//fY7uH379oeUlpb2TaVS+brDbpKuqalZtnHjxnlvvfXW3GeffXbef/3Xfy2vra1Naw0AAEBuEgADAADsRQceeGDLq6+++oAhQ4Yc3LNnz4Pbtm17sGuj+biampqqq6qqXi8vL583Z86ceb/85S/nvfjii1t1BgAAoPkQAAMAAGSZoUOHFn/xi1/cf9CgQQf26NHjwHbt2h3YqlWr3qlUKk932CFdU1OzvLKycsHq1asXzJkzZ8Gf//znJTNmzKjWGgAAgOZNAAwAAJAAxx13XNkFF1xw4MEHH3xA165dDywtLd23pKRkn1QqVag7uS2TyTRs27ZtRXV19dJ169YtfOONNxZOmDBh4YQJEzbpDgAAAO8mAAYAAEiw8847r+OJJ57Yp3///vt16dKlT5s2bfZr3bp1v/z8/GLdSZampqaa6urqxZs3b16ybt26pQsXLlzy9NNPL33ooYc26A4AAAAflgAYAAAgx7Rr1y7/kksu6TFkyJCe++67b89OnTr1Kisr61VcXNyzZcuW3VKpVL4u7R2ZTKaprq6uvKamZtWWLVtWVlRUrFy6dOnK2bNnr7r//vvXVFZWNukSAAAAn4QAGAAAoBnp1KlTwec+97luQ4cO7bnPPvv0aNeuXafWrVt3Li4u7tqyZcvORUVFnfLy8lro1MeTTqe319fXr6+rq6uoqalZW11dvb6y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AACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoMY3JP//9K8My551lzpAk293a7QAAAAAAAAD8iIw3r+SL7TpPxuSt4/ibJJuz20QDBgAAAAAAAHhzrJPp6NvHYcyyjLl7dJTPbl/Jp6sPLufSsM+5ec7q5YOrF0OeP/SVaAAAAAAAAIA3weH7u6zfm79dWDIMSw6225w785vsx3XyzasOby5ssz69c4sAAAAAAAAAr9n69C6bC9sT93a7rKdnORin5Otxynzi1JQcfiwCAwAAAAAAALxO69O7HH68TaaT98cp85R8vbp3Nfszv8q0LHn7xMF1sjk/ZzUPmZ/MmWefhAYAAAAAAAD4oayPdjn7223Gn7x6ZrXPnetX8mBKkpv3cufDd/Pufn5FL56SzcVtNheT3dNt8tQlAwAAAAAAAHyv1sm0ySv/9XtsNWZ3I/lHkgzHi7/4XY7WYz5yiwAAAAAAAAA/Ivtcv/3nPEyS1fHa42t5+vNLyX7JoRsCAAAAAAAAePPt1/nbV3/KvePn1Xc371/NYxEYAAAAAAAA4M0yTHk2rvNo2WdzvHZqzN9v/SF3vju3evng/at5fPDLPDu1yuGyZHSVAAAAAAAAAK/XOGe4ueSvZ4e8M8xZbVe5ceuPufvy3Oqkw4+v5emDX+fuuTlLhhwsEYIBAAAAAAAAXpclGR8OuXP0TR4cPM/9z/+SRyfNDf/1TZezOp/87KdjzizJZkhO7edMy/w/nAUAAAAAAADg/zaMWcZkP8/ZLsmLacr2/MV8+cnvs/tP5/4FmLjAq1ifcioAAAAASUVORK5CYII=";
3816
3972
  //#endregion
3817
3973
  //#region src/gen-utils.ts
@@ -3819,27 +3975,20 @@ const IMG_PLAYBTN = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB4AAAAVnCAYAA
3819
3975
  * PptxGenJS: Utility Methods
3820
3976
  */
3821
3977
  /**
3822
- * Translates any type of `x`/`y`/`w`/`h` prop to EMU
3823
- * - guaranteed to return a result regardless of undefined, null, etc. (0)
3824
- * - {number} - 12800 (EMU)
3825
- * - {number} - 0.5 (inches)
3826
- * - {string} - "75%"
3827
- * @param {number|string} size - numeric ("5.5") or percentage ("90%")
3828
- * @param {'X' | 'Y'} xyDir - direction
3829
- * @param {PresLayout} layout - presentation layout
3830
- * @returns {number} calculated size
3978
+ * Resolve a user `Coord` (x/y/w/h) to EMU — the single user-coordinate → EMU boundary.
3979
+ * - bare `number` **inches** (no magnitude guessing); `"<n>%"` → percent of the slide axis;
3980
+ * `"<n>in"`/`"<n>pt"`/`"<n>emu"` explicit units (see {@link Coord} / {@link coordToEmu})
3981
+ * - `null`/`undefined` 0 (callers may omit a coordinate)
3982
+ * - throws on a non-finite number rather than silently collapsing the object to zero size
3983
+ * @param {Coord|null|undefined} size - user coordinate
3984
+ * @param {'X' | 'Y'} xyDir - axis (selects slide width vs height for percentages)
3985
+ * @param {PresLayout} layout - presentation layout (EMU dimensions)
3986
+ * @returns {Emu} resolved EMU value
3831
3987
  */
3832
3988
  function getSmartParseNumber(size, xyDir, layout) {
3833
- if (typeof size === "string" && !isNaN(Number(size))) size = Number(size);
3989
+ if (size === null || size === void 0) return 0;
3834
3990
  if (typeof size === "number" && !isFinite(size)) throw new Error(`Invalid ${xyDir || "coordinate"} value: expected a finite number but received ${String(size)}. This usually means a layout dimension was read from a missing property (e.g. \`layout.width\` returning \`undefined\`). Use \`slide.width\`/\`slide.height\` or \`STANDARD_LAYOUTS.<NAME>.width\`/\`.height\` (inches).`);
3835
- if (typeof size === "number" && size < 100) return inch2Emu(size);
3836
- if (typeof size === "number" && size >= 100) return size;
3837
- if (typeof size === "string" && size.includes("%")) {
3838
- if (xyDir && xyDir === "X") return Math.round(parseFloat(size) / 100 * layout.width);
3839
- if (xyDir && xyDir === "Y") return Math.round(parseFloat(size) / 100 * layout.height);
3840
- return Math.round(parseFloat(size) / 100 * layout.width);
3841
- }
3842
- return 0;
3991
+ return coordToEmu(size, xyDir === "Y" ? layout.height : layout.width);
3843
3992
  }
3844
3993
  /**
3845
3994
  * Basic UUID Generator Adapted
@@ -3912,14 +4061,16 @@ function getDuplicateObjectNames(names) {
3912
4061
  return Array.from(dupes);
3913
4062
  }
3914
4063
  /**
3915
- * Convert inches into EMU
3916
- * @param {number|string} inches - as string or number
3917
- * @returns {number} EMU value
4064
+ * Convert inches into EMU.
4065
+ * - accepts a number (inches) or a numeric/`"<n>in"` string
4066
+ * - no magnitude guessing: values are always treated as inches (use {@link coordToEmu} for
4067
+ * user coordinates that may carry other units)
4068
+ * @param {number|string} inches - inches as number or string
4069
+ * @returns {Emu} EMU value
3918
4070
  */
3919
4071
  function inch2Emu(inches) {
3920
- if (typeof inches === "number" && inches > 100) return inches;
3921
4072
  if (typeof inches === "string") inches = Number(inches.replace(/in*/gi, ""));
3922
- return Math.round(EMU * inches);
4073
+ return inchesToEmu(inches);
3923
4074
  }
3924
4075
  /**
3925
4076
  * Convert `pt` into points (using `ONEPT`)
@@ -3931,6 +4082,33 @@ function valToPts(pt) {
3931
4082
  return isNaN(points) ? 0 : Math.round(points * ONEPT);
3932
4083
  }
3933
4084
  /**
4085
+ * Convert a transparency percentage (0-100) into a schema-valid `<a:alpha>` value
4086
+ * (ST_PositiveFixedPercentage, 0-100000). Out-of-range transparency yields an
4087
+ * alpha that PowerPoint rejects as needing repair, so clamp into range and warn.
4088
+ */
4089
+ function transparencyToAlpha(transparency) {
4090
+ const pct = Math.min(100, Math.max(0, transparency));
4091
+ if (pct !== transparency) console.warn(`Warning: transparency ${transparency} is outside the valid range 0-100; using ${pct}.`);
4092
+ return Math.round((100 - pct) * 1e3);
4093
+ }
4094
+ /** Convert an opacity (0-1) into a schema-valid `<a:alpha>` value (0-100000); clamps + warns on out-of-range input. */
4095
+ function opacityToAlpha(opacity) {
4096
+ const o = Math.min(1, Math.max(0, opacity));
4097
+ if (o !== opacity) console.warn(`Warning: opacity ${opacity} is outside the valid range 0-1; using ${o}.`);
4098
+ return Math.round(o * 1e5);
4099
+ }
4100
+ /**
4101
+ * Convert a line width (points) to EMU clamped into ST_LineWidth (0..20116800 EMU,
4102
+ * i.e. 0-1584pt). Out-of-range widths make PowerPoint report the package as needing
4103
+ * repair, so clamp into range and warn.
4104
+ */
4105
+ function lineWidthToEmu(widthPts) {
4106
+ const raw = valToPts(widthPts);
4107
+ const clamped = Math.min(20116800, Math.max(0, raw));
4108
+ if (clamped !== raw) console.warn(`Warning: line width ${widthPts} is outside the valid range 0-1584pt; using ${clamped / ONEPT}.`);
4109
+ return clamped;
4110
+ }
4111
+ /**
3934
4112
  * Convert degrees (0..360) to PowerPoint `rot` value
3935
4113
  * @param {number} d degrees
3936
4114
  * @returns {number} calculated `rot` value
@@ -3978,8 +4156,10 @@ function createColorElement(colorStr, innerElements) {
3978
4156
  }
3979
4157
  let colorVal = (colorStr || "").replace("#", "");
3980
4158
  if (/^[0-9a-fA-F]{8}$/.test(colorVal)) {
3981
- const alphaHex = colorVal.slice(6, 8);
3982
- innerElements = `<a:alpha val="${Math.round(parseInt(alphaHex, 16) / 255 * 1e5)}"/>${innerElements || ""}`;
4159
+ if (!innerElements?.includes("<a:alpha")) {
4160
+ const alphaHex = colorVal.slice(6, 8);
4161
+ innerElements = `<a:alpha val="${Math.round(parseInt(alphaHex, 16) / 255 * 1e5)}"/>${innerElements || ""}`;
4162
+ }
3983
4163
  colorVal = colorVal.slice(0, 6);
3984
4164
  }
3985
4165
  if (!REGEX_HEX_COLOR.test(colorVal) && colorVal !== "bg1" && colorVal !== "bg2" && colorVal !== "tx1" && colorVal !== "tx2" && colorVal !== "accent1" && colorVal !== "accent2" && colorVal !== "accent3" && colorVal !== "accent4" && colorVal !== "accent5" && colorVal !== "accent6") {
@@ -4005,12 +4185,38 @@ function createGlowElement(options, defaults) {
4005
4185
  };
4006
4186
  const size = Math.round(opts.size * ONEPT);
4007
4187
  const color = opts.color || "000000";
4008
- const opacity = Math.round((opts.opacity ?? 0) * 1e5);
4188
+ const opacity = opacityToAlpha(opts.opacity ?? 0);
4009
4189
  strXml += `<a:glow rad="${size}">`;
4010
4190
  strXml += createColorElement(color, `<a:alpha val="${opacity}"/>`);
4011
4191
  strXml += "</a:glow>";
4012
4192
  return strXml;
4013
4193
  }
4194
+ /**
4195
+ * Creates an `a:outerShdw`/`a:innerShdw` element for a text run or shape.
4196
+ * Returns the shadow element only (no wrapping `a:effectLst`) so callers can
4197
+ * combine it with other effects (e.g. glow) inside a single `a:effectLst`.
4198
+ * @param {ShadowProps} options shadow properties
4199
+ * @param {ShadowProps} defaults defaults for unspecified properties in `options`
4200
+ * @see http://officeopenxml.com/drwSp-effects.php
4201
+ * @returns {string} XML string, or '' when type is 'none'
4202
+ */
4203
+ function createShadowElement$1(options, defaults) {
4204
+ const opts = {
4205
+ ...defaults,
4206
+ ...options
4207
+ };
4208
+ if (opts.type === "none") return "";
4209
+ const type = opts.type || "outer";
4210
+ const blur = valToPts(opts.blur ?? 0);
4211
+ const offset = valToPts(opts.offset ?? 0);
4212
+ const angle = Math.round((opts.angle ?? 0) * 6e4);
4213
+ const opacity = Math.round((opts.opacity ?? .75) * 1e5);
4214
+ const color = opts.color || "000000";
4215
+ let strXml = `<a:${type}Shdw ${type === "outer" ? "sx=\"100000\" sy=\"100000\" kx=\"0\" ky=\"0\" algn=\"bl\" rotWithShape=\"0\" " : ""}blurRad="${blur}" dist="${offset}" dir="${angle}">`;
4216
+ strXml += createColorElement(color, `<a:alpha val="${opacity}"/>`);
4217
+ strXml += `</a:${type}Shdw>`;
4218
+ return strXml;
4219
+ }
4014
4220
  function boolToXml(value) {
4015
4221
  return value ? "1" : "0";
4016
4222
  }
@@ -4021,8 +4227,8 @@ function normalizeGradientAngle(angle) {
4021
4227
  }
4022
4228
  function gradientStopColorAdjustments(stop) {
4023
4229
  let internalElements = "";
4024
- if (stop.alpha) internalElements += `<a:alpha val="${Math.round((100 - stop.alpha) * 1e3)}"/>`;
4025
- if (stop.transparency) internalElements += `<a:alpha val="${Math.round((100 - stop.transparency) * 1e3)}"/>`;
4230
+ if (stop.alpha) internalElements += `<a:alpha val="${transparencyToAlpha(stop.alpha)}"/>`;
4231
+ if (stop.transparency) internalElements += `<a:alpha val="${transparencyToAlpha(stop.transparency)}"/>`;
4026
4232
  return internalElements;
4027
4233
  }
4028
4234
  function normalizeGradientStops(stops) {
@@ -4057,10 +4263,32 @@ function genXmlGradientFill(gradient) {
4057
4263
  return strXml;
4058
4264
  }
4059
4265
  /**
4266
+ * Create a native DrawingML pattern fill.
4267
+ * @param {PatternFillProps} pattern pattern fill options
4268
+ * @returns XML string
4269
+ */
4270
+ function genXmlPatternFill(pattern) {
4271
+ if (!pattern) throw new Error("Pattern fill requires a pattern object.");
4272
+ const fgColor = pattern.fgColor ?? "000000";
4273
+ const bgColor = pattern.bgColor ?? "FFFFFF";
4274
+ return `<a:pattFill prst="${pattern.preset}"><a:fgClr>${createColorElement(fgColor)}</a:fgClr><a:bgClr>${createColorElement(bgColor)}</a:bgClr></a:pattFill>`;
4275
+ }
4276
+ /**
4060
4277
  * Create color selection
4061
4278
  * @param {Color | ShapeFillProps | ShapeLineProps} props fill props
4062
4279
  * @returns XML string
4063
4280
  */
4281
+ /**
4282
+ * Map a friendly `LineCap` value to the OOXML `cap` attribute value (`flat`/`sq`/`rnd`).
4283
+ * @param {LineCap} [lineCap] - line cap style (defaults to `flat`)
4284
+ * @returns {string} value for the `cap` attribute on `<a:ln>`
4285
+ */
4286
+ function createLineCap(lineCap) {
4287
+ if (!lineCap || lineCap === "flat") return "flat";
4288
+ else if (lineCap === "square") return "sq";
4289
+ else if (lineCap === "round") return "rnd";
4290
+ else throw new Error(`Invalid line cap: ${String(lineCap)}`);
4291
+ }
4064
4292
  function genXmlColorSelection(props) {
4065
4293
  let fillType = "solid";
4066
4294
  let colorVal = "";
@@ -4071,8 +4299,8 @@ function genXmlColorSelection(props) {
4071
4299
  else {
4072
4300
  if (props.type) fillType = props.type;
4073
4301
  if (props.color) colorVal = props.color;
4074
- if (props.alpha) internalElements += `<a:alpha val="${Math.round((100 - props.alpha) * 1e3)}"/>`;
4075
- if (props.transparency) internalElements += `<a:alpha val="${Math.round((100 - props.transparency) * 1e3)}"/>`;
4302
+ if (props.alpha) internalElements += `<a:alpha val="${transparencyToAlpha(props.alpha)}"/>`;
4303
+ if (props.transparency) internalElements += `<a:alpha val="${transparencyToAlpha(props.transparency)}"/>`;
4076
4304
  }
4077
4305
  switch (fillType) {
4078
4306
  case "solid":
@@ -4081,6 +4309,9 @@ function genXmlColorSelection(props) {
4081
4309
  case "gradient":
4082
4310
  outText += genXmlGradientFill(typeof props === "string" ? void 0 : props.gradient);
4083
4311
  break;
4312
+ case "pattern":
4313
+ outText += genXmlPatternFill(typeof props === "string" ? void 0 : props.pattern);
4314
+ break;
4084
4315
  default:
4085
4316
  outText += "";
4086
4317
  break;
@@ -4146,6 +4377,123 @@ function svgMarkupToDataUri(svg) {
4146
4377
  for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
4147
4378
  return `data:image/svg+xml;base64,${btoa(binary)}`;
4148
4379
  }
4380
+ /**
4381
+ * Decode a base64 image payload (raw base64 or a `data:` URI) to bytes.
4382
+ * - tolerant of the `data:[mime];base64,` prefix and of whitespace in the payload
4383
+ * @param {string} b64 - base64 string or data URI
4384
+ * @returns {Uint8Array | null} decoded bytes, or `null` when the payload is empty/undecodable
4385
+ */
4386
+ function decodeBase64ToBytes(b64) {
4387
+ if (!b64) return null;
4388
+ const comma = b64.indexOf("base64,");
4389
+ const payload = (comma >= 0 ? b64.slice(comma + 7) : b64).replace(/\s/g, "");
4390
+ if (!payload) return null;
4391
+ try {
4392
+ const binary = atob(payload);
4393
+ const bytes = new Uint8Array(binary.length);
4394
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
4395
+ return bytes;
4396
+ } catch {
4397
+ return null;
4398
+ }
4399
+ }
4400
+ /**
4401
+ * Read the intrinsic pixel dimensions of a raster image from its header bytes.
4402
+ * - synchronous: parses only file-format headers, never decodes pixels
4403
+ * - supports PNG, JPEG, GIF, BMP, and WebP (VP8 / VP8L / VP8X)
4404
+ * - vector (SVG) and unrecognized formats return `null` (no intrinsic pixel size)
4405
+ *
4406
+ * Used by image `sizing: 'cover' | 'contain'` to compute an aspect-correct
4407
+ * `<a:srcRect>` crop from the *natural* image ratio rather than the displayed box.
4408
+ * @param {string} dataB64 - base64 image payload or `data:` URI
4409
+ * @returns {{ w: number, h: number } | null} natural pixel size, or `null` when unmeasurable
4410
+ */
4411
+ function getImageSizeFromBase64(dataB64) {
4412
+ const b = decodeBase64ToBytes(dataB64);
4413
+ if (!b || b.length < 24) return null;
4414
+ if (b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
4415
+ const w = b[16] << 24 | b[17] << 16 | b[18] << 8 | b[19];
4416
+ const h = b[20] << 24 | b[21] << 16 | b[22] << 8 | b[23];
4417
+ return w > 0 && h > 0 ? {
4418
+ w,
4419
+ h
4420
+ } : null;
4421
+ }
4422
+ if (b[0] === 71 && b[1] === 73 && b[2] === 70) {
4423
+ const w = b[6] | b[7] << 8;
4424
+ const h = b[8] | b[9] << 8;
4425
+ return w > 0 && h > 0 ? {
4426
+ w,
4427
+ h
4428
+ } : null;
4429
+ }
4430
+ if (b[0] === 66 && b[1] === 77) {
4431
+ const w = b[18] | b[19] << 8 | b[20] << 16 | b[21] << 24;
4432
+ const h = b[22] | b[23] << 8 | b[24] << 16 | b[25] << 24;
4433
+ const aw = Math.abs(w);
4434
+ const ah = Math.abs(h);
4435
+ return aw > 0 && ah > 0 ? {
4436
+ w: aw,
4437
+ h: ah
4438
+ } : null;
4439
+ }
4440
+ if (b[0] === 82 && b[1] === 73 && b[2] === 70 && b[3] === 70 && b[8] === 87 && b[9] === 69 && b[10] === 66 && b[11] === 80) {
4441
+ const fourCC = String.fromCharCode(b[12], b[13], b[14], b[15]);
4442
+ if (fourCC === "VP8 " && b.length >= 30) {
4443
+ const w = (b[26] | b[27] << 8) & 16383;
4444
+ const h = (b[28] | b[29] << 8) & 16383;
4445
+ return w > 0 && h > 0 ? {
4446
+ w,
4447
+ h
4448
+ } : null;
4449
+ }
4450
+ if (fourCC === "VP8L" && b.length >= 25) {
4451
+ const bits = b[21] | b[22] << 8 | b[23] << 16 | b[24] << 24;
4452
+ const w = (bits & 16383) + 1;
4453
+ const h = (bits >> 14 & 16383) + 1;
4454
+ return w > 0 && h > 0 ? {
4455
+ w,
4456
+ h
4457
+ } : null;
4458
+ }
4459
+ if (fourCC === "VP8X" && b.length >= 30) {
4460
+ const w = (b[24] | b[25] << 8 | b[26] << 16) + 1;
4461
+ const h = (b[27] | b[28] << 8 | b[29] << 16) + 1;
4462
+ return w > 0 && h > 0 ? {
4463
+ w,
4464
+ h
4465
+ } : null;
4466
+ }
4467
+ return null;
4468
+ }
4469
+ if (b[0] === 255 && b[1] === 216) {
4470
+ let i = 2;
4471
+ while (i + 9 < b.length) {
4472
+ if (b[i] !== 255) {
4473
+ i++;
4474
+ continue;
4475
+ }
4476
+ const marker = b[i + 1];
4477
+ if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
4478
+ const h = b[i + 5] << 8 | b[i + 6];
4479
+ const w = b[i + 7] << 8 | b[i + 8];
4480
+ return w > 0 && h > 0 ? {
4481
+ w,
4482
+ h
4483
+ } : null;
4484
+ }
4485
+ if (marker >= 208 && marker <= 217 || marker === 1) {
4486
+ i += 2;
4487
+ continue;
4488
+ }
4489
+ const segLen = b[i + 2] << 8 | b[i + 3];
4490
+ if (segLen < 2) break;
4491
+ i += 2 + segLen;
4492
+ }
4493
+ return null;
4494
+ }
4495
+ return null;
4496
+ }
4149
4497
  //#endregion
4150
4498
  //#region src/gen-tables.ts
4151
4499
  /**
@@ -4199,52 +4547,62 @@ function parseTextToLines(cell, colWidth, verbose) {
4199
4547
  */
4200
4548
  let newLine = [];
4201
4549
  inputCells.forEach((cell) => {
4202
- if (typeof cell.text === "string") {
4203
- if (cell.text.split("\n").length > 1) cell.text.split("\n").forEach((textLine) => {
4204
- newLine.push({
4550
+ if (typeof cell.text !== "string") return;
4551
+ if (cell.text.includes("\n")) {
4552
+ const parts = cell.text.split("\n");
4553
+ parts.forEach((part, partIdx) => {
4554
+ if (partIdx === parts.length - 1) newLine.push({
4205
4555
  _type: "tablecell",
4206
- text: textLine,
4207
- options: {
4208
- ...cell.options,
4209
- breakLine: true
4210
- }
4556
+ text: part,
4557
+ options: cell.options
4211
4558
  });
4559
+ else {
4560
+ newLine.push({
4561
+ _type: "tablecell",
4562
+ text: part,
4563
+ options: {
4564
+ ...cell.options,
4565
+ breakLine: true
4566
+ }
4567
+ });
4568
+ inputLines1.push(newLine);
4569
+ newLine = [];
4570
+ }
4212
4571
  });
4213
- else newLine.push({
4214
- _type: "tablecell",
4215
- text: cell.text.trim(),
4216
- options: cell.options
4217
- });
4218
- if (cell.options?.breakLine) {
4219
- if (verbose) console.log(`inputCells: new line > ${JSON.stringify(newLine)}`);
4220
- inputLines1.push(newLine);
4221
- newLine = [];
4222
- }
4223
- }
4224
- if (newLine.length > 0) {
4572
+ } else newLine.push({
4573
+ _type: "tablecell",
4574
+ text: cell.text.trim(),
4575
+ options: cell.options
4576
+ });
4577
+ if (cell.options?.breakLine) {
4578
+ if (verbose) console.log(`inputCells: new line > ${JSON.stringify(newLine)}`);
4225
4579
  inputLines1.push(newLine);
4226
4580
  newLine = [];
4227
4581
  }
4228
4582
  });
4583
+ if (newLine.length > 0) {
4584
+ inputLines1.push(newLine);
4585
+ newLine = [];
4586
+ }
4229
4587
  if (verbose) {
4230
4588
  console.log(`[2/4] inputLines1 (${inputLines1.length})`);
4231
4589
  inputLines1.forEach((line, idx) => console.log(`[2/4] [${idx + 1}] line: ${JSON.stringify(line)}`));
4232
4590
  }
4233
4591
  inputLines1.forEach((line) => {
4592
+ const lineTokens = [];
4234
4593
  line.forEach((cell) => {
4235
- const lineCells = [];
4236
4594
  const lineWords = String(cell.text).split(" ");
4237
4595
  lineWords.forEach((word, idx) => {
4238
4596
  const cellProps = { ...cell.options };
4239
4597
  if (cellProps?.breakLine) cellProps.breakLine = idx + 1 === lineWords.length;
4240
- lineCells.push({
4598
+ lineTokens.push({
4241
4599
  _type: "tablecell",
4242
4600
  text: word + (idx + 1 < lineWords.length ? " " : ""),
4243
4601
  options: cellProps
4244
4602
  });
4245
4603
  });
4246
- inputLines2.push(lineCells);
4247
4604
  });
4605
+ inputLines2.push(lineTokens);
4248
4606
  });
4249
4607
  if (verbose) {
4250
4608
  console.log(`[3/4] inputLines2 (${inputLines2.length})`);
@@ -4352,6 +4710,7 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
4352
4710
  numCols += Number(cellOpts?.colspan ? cellOpts.colspan : 1);
4353
4711
  });
4354
4712
  if (tableProps.verbose) console.log(`| numCols ......................................... = ${numCols}`);
4713
+ const colSpanDepths = new Array(numCols).fill(0);
4355
4714
  if (!tablePropW && tableProps.colW) {
4356
4715
  tableCalcW = Array.isArray(tableProps.colW) ? tableProps.colW.reduce((p, n) => p + n) * EMU : tableProps.colW * numCols || 0;
4357
4716
  if (tableProps.verbose) console.log(`| tableCalcW ...................................... = ${tableCalcW / EMU}`);
@@ -4372,6 +4731,7 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
4372
4731
  }
4373
4732
  let newTableRowSlide = { rows: [] };
4374
4733
  tableRows.forEach((row, iRow) => {
4734
+ const hasActiveRowSpan = colSpanDepths.some((d) => d > 0);
4375
4735
  const rowCellLines = [];
4376
4736
  let maxCellMarTopEmu = 0;
4377
4737
  let maxCellMarBtmEmu = 0;
@@ -4468,7 +4828,7 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
4468
4828
  rowCellLines.forEach((cell) => {
4469
4829
  if (cell._lineHeight >= emuLineMaxH) emuLineMaxH = cell._lineHeight;
4470
4830
  });
4471
- if (emuTabCurrH + emuLineMaxH > emuSlideTabH) {
4831
+ if (emuTabCurrH + emuLineMaxH > emuSlideTabH && !hasActiveRowSpan) {
4472
4832
  if (tableProps.verbose) {
4473
4833
  console.log("\n|-----------------------------------------------------------------------|");
4474
4834
  console.log(`|-- NEW SLIDE CREATED (currTabH+currLineH > maxH) => ${(emuTabCurrH / EMU).toFixed(2)} + ${(srcCell._lineHeight / EMU).toFixed(2)} > ${emuSlideTabH / EMU}`);
@@ -4512,6 +4872,16 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
4512
4872
  if (rowCellLines.map((cell) => cell._lines.length).reduce((prev, next) => prev + next) === 0) isDone = true;
4513
4873
  }
4514
4874
  if (currTableRow.length > 0) newTableRowSlide.rows.push(currTableRow);
4875
+ const occupiedBefore = [...colSpanDepths];
4876
+ let colCursor = 0;
4877
+ row.forEach((cell) => {
4878
+ while (colCursor < numCols && occupiedBefore[colCursor] > 0) colCursor++;
4879
+ const cellColspan = cell.options?.colspan ?? 1;
4880
+ const cellRowspan = cell.options?.rowspan ?? 1;
4881
+ if (cellRowspan > 1) for (let c = 0; c < cellColspan && colCursor + c < numCols; c++) colSpanDepths[colCursor + c] = cellRowspan;
4882
+ colCursor += cellColspan;
4883
+ });
4884
+ for (let c = 0; c < numCols; c++) if (colSpanDepths[c] > 0) colSpanDepths[c]--;
4515
4885
  if (tableProps.verbose) console.log(`- SLIDE [${tableRowSlides.length}]: ROW [${iRow}]: ...COMPLETE ...... emuTabCurrH = ${(emuTabCurrH / EMU).toFixed(2)} ( emuSlideTabH = ${(emuSlideTabH / EMU).toFixed(2)} )`);
4516
4886
  });
4517
4887
  tableRowSlides.push(newTableRowSlide);
@@ -4524,6 +4894,28 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
4524
4894
  return tableRowSlides;
4525
4895
  }
4526
4896
  /**
4897
+ * Convert a computed CSS border (width string + color string) from `getComputedStyle` into a
4898
+ * pptx `BorderProps`.
4899
+ *
4900
+ * Preserves *fractional* widths: a hairline CSS border such as `0.5px` must not be rounded to
4901
+ * `0pt` and silently vanish — the table serializer (`valToPts`) emits fractional points just
4902
+ * fine, so there is no reason to integer-round here (upstream gitbrent/PptxGenJS#1235). A
4903
+ * computed width of `0` (or a non-finite value) yields `{ type: 'none' }` so we never emit a
4904
+ * zero-width line.
4905
+ * @param {string} widthStr - computed `border-<side>-width`, e.g. `"0.5px"`
4906
+ * @param {string} colorStr - computed `border-<side>-color`, e.g. `"rgb(102, 102, 102)"`
4907
+ * @returns {BorderProps} border props for the cell side
4908
+ */
4909
+ function htmlBorderToProps(widthStr, colorStr) {
4910
+ const pt = Number(String(widthStr).replace("px", ""));
4911
+ if (!isFinite(pt) || pt <= 0) return { type: "none" };
4912
+ const arrRGB = String(colorStr).replace(/\s+/gi, "").replace("rgba(", "").replace("rgb(", "").replace(")", "").split(",");
4913
+ return {
4914
+ pt,
4915
+ color: rgbToHex(Number(arrRGB[0]), Number(arrRGB[1]), Number(arrRGB[2]))
4916
+ };
4917
+ }
4918
+ /**
4527
4919
  * Reproduces an HTML table as a PowerPoint table - including column widths, style, etc. - creates 1 or more slides as needed
4528
4920
  * @param {TableToSlidesHost} pptx - pptxgenjs instance
4529
4921
  * @param {string} tabEleId - HTMLElementID of the table
@@ -4671,12 +5063,8 @@ function genTableToSlides(pptx, tabEleId, options = {}, masterSlide) {
4671
5063
  "bottom",
4672
5064
  "left"
4673
5065
  ].forEach((val, idxb) => {
4674
- const intBorderW = Math.round(Number(window.getComputedStyle(cell).getPropertyValue("border-" + val + "-width").replace("px", "")));
4675
- const arrRGB = window.getComputedStyle(cell).getPropertyValue("border-" + val + "-color").replace(/\s+/gi, "").replace("rgba(", "").replace("rgb(", "").replace(")", "").split(",");
4676
- cellBorder[idxb] = {
4677
- pt: intBorderW,
4678
- color: rgbToHex(Number(arrRGB[0]), Number(arrRGB[1]), Number(arrRGB[2]))
4679
- };
5066
+ const style = window.getComputedStyle(cell);
5067
+ cellBorder[idxb] = htmlBorderToProps(style.getPropertyValue("border-" + val + "-width"), style.getPropertyValue("border-" + val + "-color"));
4680
5068
  });
4681
5069
  cellOpts.border = cellBorder;
4682
5070
  }
@@ -4746,6 +5134,8 @@ function genTableToSlides(pptx, tabEleId, options = {}, masterSlide) {
4746
5134
  */
4747
5135
  /** counter for included charts (used for index in their filenames) */
4748
5136
  let _chartCounter = 0;
5137
+ /** DPI PowerPoint assumes when sizing an inserted raster image (natural pixels / 96 == inches) */
5138
+ const IMAGE_NATURAL_DPI = 96;
4749
5139
  function normalizeBorderTuple(border) {
4750
5140
  return Array.isArray(border) ? border : [
4751
5141
  border,
@@ -4767,6 +5157,7 @@ function createSlideMaster(props, target) {
4767
5157
  else if ("image" in object) addImageDefinition(tgt, object.image);
4768
5158
  else if ("line" in object) addShapeDefinition(tgt, "line", object.line);
4769
5159
  else if ("rect" in object) addShapeDefinition(tgt, "rect", object.rect);
5160
+ else if ("roundRect" in object) addShapeDefinition(tgt, "roundRect", object.roundRect);
4770
5161
  else if ("text" in object) addTextDefinition(tgt, [{ text: object.text.text }], object.text.options || {}, false);
4771
5162
  else if ("placeholder" in object) {
4772
5163
  const placeholder = object.placeholder;
@@ -4781,6 +5172,26 @@ function createSlideMaster(props, target) {
4781
5172
  if (props.slideNumber && typeof props.slideNumber === "object") target._slideNumberProps = props.slideNumber;
4782
5173
  }
4783
5174
  /**
5175
+ * Round and clamp an integer chart percentage/angle option into a schema-valid range.
5176
+ *
5177
+ * Several chart attributes are bounded integer types whose out-of-range values make
5178
+ * PowerPoint report the package as needing repair: `<c:overlap>` (ST_Overlap, -100..100),
5179
+ * `<c:gapWidth>`/`<c:gapDepth>` (ST_GapAmount, 0..500), `<c:holeSize>` (ST_HoleSize, 10..90)
5180
+ * and `<c:firstSliceAng>` (ST_FirstSliceAng, 0..360). Missing/non-numeric input returns
5181
+ * `undefined` so the caller can apply its own default; an out-of-range value is clamped
5182
+ * and a warning is emitted (per the library's warn-rather-than-degrade policy).
5183
+ * @param value - caller-supplied option value
5184
+ * @param min - inclusive lower bound
5185
+ * @param max - inclusive upper bound
5186
+ * @param name - option name, for the warning message
5187
+ */
5188
+ function clampChartPct(value, min, max, name) {
5189
+ if (typeof value !== "number" || isNaN(value)) return void 0;
5190
+ const clamped = Math.min(max, Math.max(min, Math.round(value)));
5191
+ if (clamped !== value) console.warn(`Warning: ${name} ${value} is outside the valid range ${min}-${max}; using ${clamped}.`);
5192
+ return clamped;
5193
+ }
5194
+ /**
4784
5195
  * Generate the chart based on input data.
4785
5196
  * OOXML Chart Spec: ISO/IEC 29500-1:2016(E)
4786
5197
  *
@@ -4952,7 +5363,13 @@ function addChartDefinition(target, type, data, opt) {
4952
5363
  "marker",
4953
5364
  "filled"
4954
5365
  ].includes(options.radarStyle || "")) options.radarStyle = "standard";
4955
- options.lineDataSymbolSize = options.lineDataSymbolSize && !isNaN(options.lineDataSymbolSize) ? options.lineDataSymbolSize : 6;
5366
+ {
5367
+ const rawSymbolSize = options.lineDataSymbolSize;
5368
+ const hasSymbolSize = rawSymbolSize != null && !isNaN(rawSymbolSize);
5369
+ const symbolSize = Math.min(72, Math.max(2, Math.round(hasSymbolSize ? rawSymbolSize : 6)));
5370
+ if (hasSymbolSize && symbolSize !== rawSymbolSize) console.warn(`Warning: lineDataSymbolSize ${rawSymbolSize} is outside the valid marker size range (integer 2-72); using ${symbolSize}.`);
5371
+ options.lineDataSymbolSize = symbolSize;
5372
+ }
4956
5373
  options.lineDataSymbolLineSize = options.lineDataSymbolLineSize && !isNaN(options.lineDataSymbolLineSize) ? valToPts(options.lineDataSymbolLineSize) : valToPts(.75);
4957
5374
  const chartLayout = options.layout;
4958
5375
  if (chartLayout) [
@@ -5002,8 +5419,11 @@ function addChartDefinition(target, type, data, opt) {
5002
5419
  options.v3DRotY = typeof options.v3DRotY === "number" && !isNaN(options.v3DRotY) && options.v3DRotY >= 0 && options.v3DRotY <= 360 ? options.v3DRotY : 30;
5003
5420
  options.v3DRAngAx = options.v3DRAngAx || !options.v3DRAngAx ? options.v3DRAngAx : true;
5004
5421
  options.v3DPerspective = typeof options.v3DPerspective === "number" && !isNaN(options.v3DPerspective) && options.v3DPerspective >= 0 && options.v3DPerspective <= 240 ? options.v3DPerspective : 30;
5005
- options.barGapWidthPct = typeof options.barGapWidthPct === "number" && !isNaN(options.barGapWidthPct) && options.barGapWidthPct >= 0 && options.barGapWidthPct <= 1e3 ? options.barGapWidthPct : 150;
5006
- options.barGapDepthPct = typeof options.barGapDepthPct === "number" && !isNaN(options.barGapDepthPct) && options.barGapDepthPct >= 0 && options.barGapDepthPct <= 1e3 ? options.barGapDepthPct : 150;
5422
+ options.barGapWidthPct = clampChartPct(options.barGapWidthPct, 0, 500, "barGapWidthPct") ?? 150;
5423
+ options.barGapDepthPct = clampChartPct(options.barGapDepthPct, 0, 500, "barGapDepthPct") ?? 150;
5424
+ options.barOverlapPct = clampChartPct(options.barOverlapPct, -100, 100, "barOverlapPct");
5425
+ options.holeSize = clampChartPct(options.holeSize, 10, 90, "holeSize");
5426
+ options.firstSliceAng = clampChartPct(options.firstSliceAng, 0, 360, "firstSliceAng");
5007
5427
  options.chartColors = Array.isArray(options.chartColors) ? options.chartColors : options._type === "pie" || options._type === "doughnut" ? PIECHART_COLORS : BARCHART_COLORS;
5008
5428
  options.chartColorsOpacity = options.chartColorsOpacity && !isNaN(options.chartColorsOpacity) ? options.chartColorsOpacity : void 0;
5009
5429
  options.border = options.border && typeof options.border === "object" ? options.border : void 0;
@@ -5092,23 +5512,38 @@ function addImageDefinition(target, opt) {
5092
5512
  else if (strImageData?.toLowerCase().includes("image/svg+xml")) strImgExtn = "svg";
5093
5513
  newObject._type = "image";
5094
5514
  newObject.image = strImagePath || "preencoded.png";
5515
+ let defWidth = intWidth;
5516
+ let defHeight = intHeight;
5517
+ if ((!intWidth || !intHeight) && strImageData && strImgExtn !== "svg") {
5518
+ const natural = getImageSizeFromBase64(strImageData);
5519
+ if (natural) {
5520
+ if (!intWidth && !intHeight) {
5521
+ defWidth = natural.w / IMAGE_NATURAL_DPI;
5522
+ defHeight = natural.h / IMAGE_NATURAL_DPI;
5523
+ } else if (typeof intWidth === "number" && intWidth && !intHeight) defHeight = intWidth * (natural.h / natural.w);
5524
+ else if (typeof intHeight === "number" && intHeight && !intWidth) defWidth = intHeight * (natural.w / natural.h);
5525
+ }
5526
+ }
5095
5527
  const objectOptions = {
5096
5528
  x: intPosX || 0,
5097
5529
  y: intPosY || 0,
5098
- w: intWidth || 1,
5099
- h: intHeight || 1,
5530
+ w: defWidth || 1,
5531
+ h: defHeight || 1,
5100
5532
  altText: opt.altText || "",
5101
5533
  rounding: typeof opt.rounding === "boolean" ? opt.rounding : false,
5102
5534
  shape: opt.shape,
5103
5535
  points: opt.points,
5104
5536
  rectRadius: opt.rectRadius,
5537
+ shapeAdjust: opt.shapeAdjust,
5105
5538
  sizing,
5106
5539
  placeholder: opt.placeholder,
5107
5540
  rotate: opt.rotate || 0,
5108
5541
  flipV: opt.flipV || false,
5109
5542
  flipH: opt.flipH || false,
5110
5543
  transparency: opt.transparency || 0,
5544
+ duotone: opt.duotone,
5111
5545
  objectName,
5546
+ objectLock: opt.objectLock,
5112
5547
  shadow: correctShadowOptions(opt.shadow)
5113
5548
  };
5114
5549
  newObject.options = objectOptions;
@@ -5138,7 +5573,10 @@ function addImageDefinition(target, opt) {
5138
5573
  });
5139
5574
  newObject.imageRid = imageRelId + 1;
5140
5575
  } else {
5141
- const dupeItem = target._relsMedia.find((item) => item.path && item.path === strImagePath && item.type === "image/" + strImgExtn && !item.isDuplicate);
5576
+ const dupeItem = target._relsMedia.find((item) => {
5577
+ if (item.isDuplicate || !item.Target || item.type !== "image/" + strImgExtn) return false;
5578
+ return strImagePath ? item.path === strImagePath : !!strImageData && item.data === strImageData;
5579
+ });
5142
5580
  target._relsMedia.push({
5143
5581
  path: strImagePath || "preencoded." + strImgExtn,
5144
5582
  type: "image/" + strImgExtn,
@@ -5157,7 +5595,7 @@ function addImageDefinition(target, opt) {
5157
5595
  type: "hyperlink",
5158
5596
  data: objHyperlink.slide ? "slide" : "dummy",
5159
5597
  rId: imageRelId,
5160
- Target: objHyperlink.url || String(objHyperlink.slide)
5598
+ Target: objHyperlink.url ? encodeXmlEntities(objHyperlink.url) : String(objHyperlink.slide)
5161
5599
  });
5162
5600
  objHyperlink._rId = imageRelId;
5163
5601
  newObject.hyperlink = objHyperlink;
@@ -5196,6 +5634,7 @@ function addMediaDefinition(target, opt) {
5196
5634
  slideData.options.h = intSizeY;
5197
5635
  slideData.options.objectName = objectName;
5198
5636
  if (opt.altText) slideData.options.altText = opt.altText;
5637
+ if (opt.objectLock) slideData.options.objectLock = opt.objectLock;
5199
5638
  /**
5200
5639
  * NOTE:
5201
5640
  * - rId starts at 2 (hence the intRels+1 below) as slideLayout.xml is rId=1!
@@ -5225,7 +5664,10 @@ function addMediaDefinition(target, opt) {
5225
5664
  Target: `../media/image-${target._slideNum}-${target._relsMedia.length + 1}.png`
5226
5665
  });
5227
5666
  } else {
5228
- const dupeItem = target._relsMedia.find((item) => item.path && item.path === strPath && item.type === strType + "/" + strExtn && !item.isDuplicate);
5667
+ const dupeItem = target._relsMedia.find((item) => {
5668
+ if (item.isDuplicate || !item.Target || item.type !== strType + "/" + strExtn) return false;
5669
+ return strPath ? item.path === strPath : !!strData && item.data === strData;
5670
+ });
5229
5671
  const relId1 = getNewRelId(target);
5230
5672
  target._relsMedia.push({
5231
5673
  path: strPath || "preencoded" + strExtn,
@@ -5290,12 +5732,14 @@ function addShapeDefinition(target, shapeName, opts) {
5290
5732
  const options = typeof opts === "object" ? opts : {};
5291
5733
  options.line = options.line || { type: "none" };
5292
5734
  options.shadow = correctShadowOptions(options.shadow);
5735
+ const resolvedShapeName = typeof shapeName === "string" && SHAPE_NAME_ALIASES[shapeName] ? SHAPE_NAME_ALIASES[shapeName] : shapeName;
5293
5736
  const newObject = {
5294
5737
  _type: "text",
5295
- shape: (typeof shapeName === "string" && SHAPE_NAME_ALIASES[shapeName] ? SHAPE_NAME_ALIASES[shapeName] : shapeName) || "rect",
5738
+ shape: resolvedShapeName || "rect",
5296
5739
  options
5297
5740
  };
5298
5741
  if (!shapeName) throw new Error("Missing/Invalid shape parameter! Example: `addShape(pptxgen.shapes.LINE, {x:1, y:1, w:1, h:1});`");
5742
+ if (!VALID_SHAPE_PRESETS.has(resolvedShapeName)) throw new Error(`Invalid shape "${String(shapeName)}"! Use a value from \`pptxgen.shapes.*\` (e.g. \`pptxgen.shapes.RECTANGLE\`). PowerPoint can't render unknown preset geometries and will drop the shape during repair.`);
5299
5743
  const newLineOpts = {
5300
5744
  type: options.line.type || "solid",
5301
5745
  color: options.line.color || "333333",
@@ -5392,9 +5836,8 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
5392
5836
  }
5393
5837
  arrRows.push(newRow);
5394
5838
  });
5395
- opt.x = getSmartParseNumber(opt.x || (opt.x === 0 ? 0 : EMU / 2), "X", presLayout);
5396
- opt.y = getSmartParseNumber(opt.y || (opt.y === 0 ? 0 : EMU / 2), "Y", presLayout);
5397
- if (opt.h) opt.h = getSmartParseNumber(opt.h, "Y", presLayout);
5839
+ if (opt.x === void 0 || opt.x === null) opt.x = .5;
5840
+ if (opt.y === void 0 || opt.y === null) opt.y = .5;
5398
5841
  opt.fontSize = opt.fontSize || 12;
5399
5842
  opt.margin = opt.margin === 0 || opt.margin ? opt.margin : DEF_CELL_MARGIN_IN;
5400
5843
  if (typeof opt.margin === "number") opt.margin = [
@@ -5463,12 +5906,7 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
5463
5906
  console.warn("addTable: mismatch: (colW.length != data.length) Therefore, defaulting to evenly distributed col widths.");
5464
5907
  opt.colW = void 0;
5465
5908
  }
5466
- } else if (opt.w) opt.w = getSmartParseNumber(opt.w, "X", presLayout);
5467
- else opt.w = Math.floor((presLayout._sizeW || presLayout.width) / EMU - arrTableMargin[1] - arrTableMargin[3]);
5468
- if (opt.x && opt.x < 20) opt.x = inch2Emu(opt.x);
5469
- if (opt.y && opt.y < 20) opt.y = inch2Emu(opt.y);
5470
- if (opt.w && typeof opt.w === "number" && opt.w < 20) opt.w = inch2Emu(opt.w);
5471
- if (opt.h && typeof opt.h === "number" && opt.h < 20) opt.h = inch2Emu(opt.h);
5909
+ } else if (opt.w) {} else opt.w = Math.floor((presLayout._sizeW || presLayout.width) / EMU - arrTableMargin[1] - arrTableMargin[3]);
5472
5910
  arrRows.forEach((row) => {
5473
5911
  row.forEach((cell, idy) => {
5474
5912
  if (typeof cell === "number" || typeof cell === "string") row[idy] = {
@@ -5496,7 +5934,7 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
5496
5934
  if (opt.autoPageRepeatHeader) opt._arrObjTabHeadRows = arrRows.filter((_row, idx) => idx < (opt.autoPageHeaderRows || 1));
5497
5935
  getSlidesForTableRows(arrRows, opt, presLayout, slideLayout).forEach((slide, idx) => {
5498
5936
  if (!getSlide(target._slideNum + idx)) slides.push(addSlide({ masterName: slideLayout?._name || void 0 }));
5499
- if (idx > 0) opt.y = inch2Emu(opt.autoPageSlideStartY || opt.newSlideStartY || arrTableMargin[0]);
5937
+ if (idx > 0) opt.y = opt.autoPageSlideStartY || opt.newSlideStartY || arrTableMargin[0];
5500
5938
  {
5501
5939
  const newSlide = getSlide(target._slideNum + idx);
5502
5940
  opt.autoPage = false;
@@ -5568,6 +6006,10 @@ function addTextDefinition(target, text, opts, isPlaceholder) {
5568
6006
  itemOpts._bodyProp.anchor = !itemOpts.placeholder ? "ctr" : void 0;
5569
6007
  itemOpts._bodyProp.vert = itemOpts.vert;
5570
6008
  itemOpts._bodyProp.wrap = typeof itemOpts.wrap === "boolean" ? itemOpts.wrap : true;
6009
+ if (itemOpts.columns !== void 0) if (typeof itemOpts.columns !== "number" || isNaN(itemOpts.columns) || itemOpts.columns < 1 || itemOpts.columns > 16) console.warn("Warning: text `columns` must be a number 1-16 (ignoring value)");
6010
+ else itemOpts._bodyProp.numCol = Math.round(itemOpts.columns);
6011
+ if (itemOpts.columnSpacing !== void 0) if (typeof itemOpts.columnSpacing !== "number" || isNaN(itemOpts.columnSpacing) || itemOpts.columnSpacing < 0) console.warn("Warning: text `columnSpacing` must be a number >= 0 (ignoring value)");
6012
+ else itemOpts._bodyProp.spcCol = valToPts(itemOpts.columnSpacing);
5571
6013
  if (itemOpts.inset && !isNaN(Number(itemOpts.inset)) || itemOpts.inset === 0) {
5572
6014
  itemOpts._bodyProp.lIns = inch2Emu(itemOpts.inset);
5573
6015
  itemOpts._bodyProp.rIns = inch2Emu(itemOpts.inset);
@@ -5915,7 +6357,7 @@ async function createExcelWorksheet(chartObject, zip) {
5915
6357
  if (chartObject.opts._type === "bubble" || chartObject.opts._type === "bubble3D") strSharedStrings += `<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${intBubbleCols}" uniqueCount="${intBubbleCols}">`;
5916
6358
  else if (chartObject.opts._type === "scatter") strSharedStrings += `<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${data.length}" uniqueCount="${data.length}">`;
5917
6359
  else if (IS_MULTI_CAT_AXES) {
5918
- let totCount = data.length;
6360
+ let totCount = data.length + 1;
5919
6361
  data[0].labels.forEach((arrLabel) => totCount += arrLabel.filter((label) => label && label !== "").length);
5920
6362
  strSharedStrings += `<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${totCount}" uniqueCount="${totCount}">`;
5921
6363
  strSharedStrings += "<si><t/></si>";
@@ -5983,7 +6425,7 @@ async function createExcelWorksheet(chartObject, zip) {
5983
6425
  strSheetXml += "<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" mc:Ignorable=\"x14ac\" xmlns:x14ac=\"http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac\">";
5984
6426
  if (chartObject.opts._type === "bubble" || chartObject.opts._type === "bubble3D") strSheetXml += `<dimension ref="A1:${getExcelColName(intBubbleCols)}${data[0].values.length + 1}"/>`;
5985
6427
  else if (chartObject.opts._type === "scatter") strSheetXml += `<dimension ref="A1:${getExcelColName(data.length)}${data[0].values.length + 1}"/>`;
5986
- else strSheetXml += `<dimension ref="A1:${getExcelColName(data.length + 1)}${data[0].values.length + 1}"/>`;
6428
+ else strSheetXml += `<dimension ref="A1:${getExcelColName(data.length + data[0].labels.length)}${data[0].values.length + 1}"/>`;
5987
6429
  strSheetXml += "<sheetViews><sheetView tabSelected=\"1\" workbookViewId=\"0\"><selection activeCell=\"B1\" sqref=\"B1\"/></sheetView></sheetViews>";
5988
6430
  strSheetXml += "<sheetFormatPr baseColWidth=\"10\" defaultRowHeight=\"16\"/>";
5989
6431
  if (chartObject.opts._type === "bubble" || chartObject.opts._type === "bubble3D") {
@@ -6035,82 +6477,28 @@ async function createExcelWorksheet(chartObject, zip) {
6035
6477
  strSheetXml += "</row>";
6036
6478
  });
6037
6479
  } else {
6038
- strSheetXml += `<row r="1" spans="1:${data.length + data[0].labels.length}">`;
6039
- for (let idx = 0; idx < data[0].labels.length; idx++) strSheetXml += `<c r="${getExcelColName(idx + 1)}1" t="s"><v>0</v></c>`;
6040
- for (let idx = data[0].labels.length - 1; idx < data.length + data[0].labels.length - 1; idx++) strSheetXml += `<c r="${getExcelColName(idx + data[0].labels.length)}1" t="s"><v>${idx}</v></c>`;
6041
- strSheetXml += "</row>";
6042
- /**
6043
- * @example INPUT
6044
- * const LABELS = [
6045
- * ["Gear", "Berg", "Motr", "Swch", "Plug", "Cord", "Pump", "Leak", "Seal"],
6046
- * ["Mech", "", "", "Elec", "", "", "Hydr", "", ""],
6047
- * ];
6048
- * const arrDataRegions = [
6049
- * { name: "West", labels: LABELS, values: [11, 8, 3, 0, 11, 3, 0, 0, 0] },
6050
- * { name: "Ctrl", labels: LABELS, values: [0, 11, 6, 19, 12, 5, 0, 0, 0] },
6051
- * { name: "East", labels: LABELS, values: [0, 3, 2, 0, 0, 0, 4, 3, 1] },
6052
- * ];
6053
- */
6054
- /**
6055
- * @example OUTPUT EXCEL SHEET
6056
- * |/|---A--|---B--|---C--|---D--|---E--|
6057
- * |1| | | West | Ctrl | East |
6058
- * |2| Mech | Gear | ## | ## | ## |
6059
- * |3| | Brng | ## | ## | ## |
6060
- * |4| | Motr | ## | ## | ## |
6061
- * |5| Elec | Swch | ## | ## | ## |
6062
- * |6| | Plug | ## | ## | ## |
6063
- * |7| | Cord | ## | ## | ## |
6064
- * |8| Hydr | Pump | ## | ## | ## |
6065
- * |9| | Leak | ## | ## | ## |
6066
- *|10| | Seal | ## | ## | ## |
6067
- */
6068
- /**
6069
- * @example OUTPUT EXCEL SHEET XML
6070
- * <row r="1" spans="1:5">
6071
- * <c r="A1" t="s"><v>0</v></c>
6072
- * <c r="B1" t="s"><v>0</v></c>
6073
- * <c r="C1" t="s"><v>1</v></c>
6074
- * <c r="D1" t="s"><v>2</v></c>
6075
- * <c r="E1" t="s"><v>3</v></c>
6076
- * </row>
6077
- * <row r="2" spans="1:5">
6078
- * <c r="A2" t="s"><v>4</v></c>
6079
- * <c r="B2" t="s"><v>7</v></c>
6080
- * <c r="C2" ><v>###</v></c>
6081
- * </row>
6082
- * <row r="3" spans="1:5">
6083
- * <c r="A3" />
6084
- * <c r="B3" t="s"><v>8</v></c>
6085
- * <c r="C3" ><v>###</v></c>
6086
- * </row>
6087
- */
6088
- /**
6089
- * @example SHARED-STRINGS
6090
- * 1=West, 2=Ctrl, 3=East, 4=Mech, 5=Elec, 6=Mydr, 7=Gear, 8=Brng, [...], 15=Seal
6091
- */
6092
- /**
6093
- * const LABELS = [
6094
- * ["Gear", "Berg", "Motr", "Swch", "Plug", "Cord", "Pump", "Leak", "Seal"],
6095
- * ["Mech", "", "", "Elec", "", "", "Hydr", "", ""],
6096
- * ["2010", "", "", "", "", "", "", "", ""],
6097
- * ];
6098
- */
6099
6480
  const TOT_SER = data.length;
6100
6481
  const TOT_CAT = data[0].labels[0].length;
6101
6482
  const TOT_LVL = data[0].labels.length;
6483
+ const revLabelGroups = data[0].labels.slice().reverse();
6484
+ const ssLabelMap = /* @__PURE__ */ new Map();
6485
+ let ssIdx = TOT_SER + 1;
6486
+ revLabelGroups.forEach((labelsGroup, revLevelIdx) => {
6487
+ labelsGroup.forEach((label, rowIdx) => {
6488
+ if (label && label !== "") ssLabelMap.set(`${revLevelIdx}:${rowIdx}`, ssIdx++);
6489
+ });
6490
+ });
6491
+ strSheetXml += `<row r="1" spans="1:${TOT_SER + TOT_LVL}">`;
6492
+ for (let col = 1; col <= TOT_LVL; col++) strSheetXml += `<c r="${getExcelColName(col)}1" t="s"><v>0</v></c>`;
6493
+ for (let ser = 0; ser < TOT_SER; ser++) strSheetXml += `<c r="${getExcelColName(TOT_LVL + ser + 1)}1" t="s"><v>${ser + 1}</v></c>`;
6494
+ strSheetXml += "</row>";
6102
6495
  for (let idx = 0; idx < TOT_CAT; idx++) {
6103
6496
  strSheetXml += `<row r="${idx + 2}" spans="1:${TOT_SER + TOT_LVL}">`;
6104
- let totLabels = TOT_SER;
6105
- const revLabelGroups = data[0].labels.slice().reverse();
6106
6497
  revLabelGroups.forEach((labelsGroup, idy) => {
6107
- if (labelsGroup[idx]) {
6108
- const totGrpLbls = idy === 0 ? 1 : revLabelGroups[idy - 1].filter((label) => label && label !== "").length;
6109
- totLabels += totGrpLbls;
6110
- strSheetXml += `<c r="${getExcelColName(idx + 1 + idy)}${idx + 2}" t="s"><v>${totLabels}</v></c>`;
6111
- }
6498
+ const colLabel = labelsGroup[idx];
6499
+ if (colLabel && colLabel !== "") strSheetXml += `<c r="${getExcelColName(idy + 1)}${idx + 2}" t="s"><v>${ssLabelMap.get(`${idy}:${idx}`)}</v></c>`;
6112
6500
  });
6113
- for (let idy = 0; idy < TOT_SER; idy++) strSheetXml += `<c r="${getExcelColName(TOT_LVL + idy + 1)}${idx + 2}"><v>${data[idy].values[idx] || 0}</v></c>`;
6501
+ for (let idy = 0; idy < TOT_SER; idy++) strSheetXml += `<c r="${getExcelColName(TOT_LVL + idy + 1)}${idx + 2}"><v>${data[idy].values[idx] ?? ""}</v></c>`;
6114
6502
  strSheetXml += "</row>";
6115
6503
  }
6116
6504
  }
@@ -6131,6 +6519,22 @@ async function createExcelWorksheet(chartObject, zip) {
6131
6519
  });
6132
6520
  }
6133
6521
  /**
6522
+ * Emit the `<a:latin>/<a:ea>/<a:cs>` font trio for a chart text run.
6523
+ *
6524
+ * In DrawingML run properties a typeface applies only to the script class of
6525
+ * its element: `<a:latin>` covers Latin/ASCII, `<a:ea>` covers East Asian, and
6526
+ * `<a:cs>` covers complex scripts. Emitting `<a:latin>` alone leaves East Asian
6527
+ * (e.g. Chinese) and complex-script glyphs falling back to the theme font, so a
6528
+ * user-specified font never takes effect for that text — most visibly on
6529
+ * PowerPoint for Mac. Stamping the same typeface onto all three classes is what
6530
+ * choosing a font in PowerPoint's UI does (upstream gitbrent/PptxGenJS#1420).
6531
+ * @param {string} typeface - font face name
6532
+ * @return {string} `<a:latin/><a:ea/><a:cs/>` XML
6533
+ */
6534
+ function createChartTextFonts(typeface) {
6535
+ return `<a:latin typeface="${typeface}"/><a:ea typeface="${typeface}"/><a:cs typeface="${typeface}"/>`;
6536
+ }
6537
+ /**
6134
6538
  * Main entry point method for create charts
6135
6539
  * @see: http://www.datypic.com/sc/ooxml/s-dml-chart.xsd.html
6136
6540
  * @param {ISlideRelChart} rel - chart object
@@ -6140,6 +6544,10 @@ function makeXmlCharts(rel) {
6140
6544
  let strXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
6141
6545
  let usesSecondaryValAxis = false;
6142
6546
  let usesSecondaryCatAxis = false;
6547
+ let primaryCatAxisValType = null;
6548
+ let secondaryCatAxisValType = null;
6549
+ let primaryCatAxisHasCategoryChart = false;
6550
+ let secondaryCatAxisHasCategoryChart = false;
6143
6551
  strXml += "<c:chartSpace xmlns:c=\"http://schemas.openxmlformats.org/drawingml/2006/chart\" xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">";
6144
6552
  strXml += "<c:date1904 val=\"0\"/>";
6145
6553
  strXml += `<c:roundedCorners val="${rel.opts.chartArea.roundedCorners ? "1" : "0"}"/>`;
@@ -6152,6 +6560,8 @@ function makeXmlCharts(rel) {
6152
6560
  fontSize: rel.opts.titleFontSize || 18,
6153
6561
  titleAlign: rel.opts.titleAlign,
6154
6562
  titleBold: rel.opts.titleBold,
6563
+ titleItalic: rel.opts.titleItalic,
6564
+ titleUnderline: rel.opts.titleUnderline,
6155
6565
  titlePos: rel.opts.titlePos,
6156
6566
  titleRotate: rel.opts.titleRotate
6157
6567
  }, rel.opts.x, rel.opts.y);
@@ -6184,18 +6594,37 @@ function makeXmlCharts(rel) {
6184
6594
  const catAxisId = options.secondaryCatAxis ? AXIS_ID_CATEGORY_SECONDARY : AXIS_ID_CATEGORY_PRIMARY;
6185
6595
  usesSecondaryValAxis = usesSecondaryValAxis || options.secondaryValAxis;
6186
6596
  usesSecondaryCatAxis = usesSecondaryCatAxis || options.secondaryCatAxis;
6597
+ const usesValueXAxis = type.type === "scatter" || type.type === "bubble" || type.type === "bubble3D";
6598
+ if (options.secondaryCatAxis) if (usesValueXAxis) secondaryCatAxisValType = type.type;
6599
+ else secondaryCatAxisHasCategoryChart = true;
6600
+ else if (usesValueXAxis) primaryCatAxisValType = type.type;
6601
+ else primaryCatAxisHasCategoryChart = true;
6187
6602
  strXml += makeChartType(type.type, type.data, options, valAxisId, catAxisId);
6188
6603
  });
6189
6604
  else strXml += makeChartType(rel.opts._type, rel.data, rel.opts, AXIS_ID_VALUE_PRIMARY, AXIS_ID_CATEGORY_PRIMARY);
6190
6605
  if (rel.opts._type !== "pie" && rel.opts._type !== "doughnut") {
6191
6606
  if (rel.opts.valAxes && rel.opts.valAxes.length > 1 && !usesSecondaryValAxis) throw new Error("Secondary axis must be used by one of the multiple charts");
6607
+ const comboCatAxisType = (isSecondary) => {
6608
+ const valType = isSecondary ? secondaryCatAxisValType : primaryCatAxisValType;
6609
+ const hasCategoryChart = isSecondary ? secondaryCatAxisHasCategoryChart : primaryCatAxisHasCategoryChart;
6610
+ if (!valType) return {};
6611
+ if (hasCategoryChart) {
6612
+ console.warn(`A category-based chart and a scatter/bubble chart cannot share the same ${isSecondary ? "secondary" : "primary"} category axis; emitting a category axis. Put the scatter/bubble series on a separate axis.`);
6613
+ return {};
6614
+ }
6615
+ return { _type: valType };
6616
+ };
6192
6617
  if (rel.opts.catAxes) {
6193
6618
  if (!rel.opts.valAxes || rel.opts.valAxes.length !== rel.opts.catAxes.length) throw new Error("There must be the same number of value and category axes.");
6194
6619
  strXml += makeCatAxis({
6195
6620
  ...rel.opts,
6196
- ...rel.opts.catAxes[0]
6621
+ ...rel.opts.catAxes[0],
6622
+ ...comboCatAxisType(false)
6197
6623
  }, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY);
6198
- } else strXml += makeCatAxis(rel.opts, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY);
6624
+ } else strXml += makeCatAxis({
6625
+ ...rel.opts,
6626
+ ...comboCatAxisType(false)
6627
+ }, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY);
6199
6628
  if (rel.opts.valAxes) {
6200
6629
  strXml += makeValAxis({
6201
6630
  ...rel.opts,
@@ -6212,9 +6641,13 @@ function makeXmlCharts(rel) {
6212
6641
  }
6213
6642
  if (rel.opts?.catAxes && rel.opts?.catAxes[1]) strXml += makeCatAxis({
6214
6643
  ...rel.opts,
6215
- ...rel.opts.catAxes[1]
6644
+ ...rel.opts.catAxes[1],
6645
+ ...comboCatAxisType(true)
6646
+ }, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_VALUE_SECONDARY);
6647
+ else if (usesSecondaryCatAxis && (!rel.opts.catAxes || !rel.opts.catAxes[1])) strXml += makeCatAxis({
6648
+ ...rel.opts,
6649
+ ...comboCatAxisType(true)
6216
6650
  }, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_VALUE_SECONDARY);
6217
- else if (usesSecondaryCatAxis && (!rel.opts.catAxes || !rel.opts.catAxes[1])) strXml += makeCatAxis(rel.opts, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_VALUE_SECONDARY);
6218
6651
  }
6219
6652
  if (rel.opts.showDataTable) {
6220
6653
  strXml += "<c:dTable>";
@@ -6253,6 +6686,13 @@ function makeXmlCharts(rel) {
6253
6686
  if (rel.opts.showLegend) {
6254
6687
  strXml += "<c:legend>";
6255
6688
  strXml += "<c:legendPos val=\"" + rel.opts.legendPos + "\"/>";
6689
+ if (Array.isArray(rel.opts._type)) {
6690
+ let seriesIdx = 0;
6691
+ rel.opts._type.forEach((type) => {
6692
+ if (type.options?.showLegend === false) for (let i = 0; i < type.data.length; i++) strXml += `<c:legendEntry><c:idx val="${seriesIdx + i}"/><c:delete val="1"/></c:legendEntry>`;
6693
+ seriesIdx += type.data.length;
6694
+ });
6695
+ }
6256
6696
  strXml += "<c:overlay val=\"0\"/>";
6257
6697
  if (rel.opts.legendFontFace || rel.opts.legendFontSize || rel.opts.legendColor) {
6258
6698
  strXml += "<c:txPr>";
@@ -6262,8 +6702,7 @@ function makeXmlCharts(rel) {
6262
6702
  strXml += " <a:pPr>";
6263
6703
  strXml += rel.opts.legendFontSize ? `<a:defRPr sz="${Math.round(Number(rel.opts.legendFontSize) * 100)}">` : "<a:defRPr>";
6264
6704
  if (rel.opts.legendColor) strXml += genXmlColorSelection(rel.opts.legendColor);
6265
- if (rel.opts.legendFontFace) strXml += "<a:latin typeface=\"" + rel.opts.legendFontFace + "\"/>";
6266
- if (rel.opts.legendFontFace) strXml += "<a:cs typeface=\"" + rel.opts.legendFontFace + "\"/>";
6705
+ if (rel.opts.legendFontFace) strXml += createChartTextFonts(rel.opts.legendFontFace);
6267
6706
  strXml += " </a:defRPr>";
6268
6707
  strXml += " </a:pPr>";
6269
6708
  strXml += " <a:endParaRPr lang=\"en-US\"/>";
@@ -6301,6 +6740,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6301
6740
  let idxColLtr = 1;
6302
6741
  let optsChartData;
6303
6742
  let strXml = "";
6743
+ const valFmtCode = encodeXmlEntities(opts.valLabelFormatCode || opts.dataTableFormatCode || opts.dataLabelFormatCode || "General");
6304
6744
  switch (chartType) {
6305
6745
  case "area":
6306
6746
  case "bar":
@@ -6328,20 +6768,23 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6328
6768
  strXml += " <c:strCache><c:ptCount val=\"1\"/><c:pt idx=\"0\"><c:v>" + encodeXmlEntities(obj.name) + "</c:v></c:pt></c:strCache>";
6329
6769
  strXml += " </c:strRef>";
6330
6770
  strXml += " </c:tx>";
6331
- const seriesColor = opts.chartColors ? opts.chartColors[colorIndex % opts.chartColors.length] : null;
6771
+ const seriesOverride = opts.seriesOptions?.[obj._dataIndex];
6772
+ const seriesColor = seriesOverride?.color ?? (opts.chartColors ? opts.chartColors[colorIndex % opts.chartColors.length] : null);
6332
6773
  strXml += " <c:spPr>";
6333
6774
  if (seriesColor === "transparent") strXml += "<a:noFill/>";
6334
6775
  else if (opts.chartColorsOpacity) strXml += "<a:solidFill>" + createColorElement(seriesColor, `<a:alpha val="${Math.round(opts.chartColorsOpacity * 1e3)}"/>`) + "</a:solidFill>";
6335
6776
  else strXml += "<a:solidFill>" + createColorElement(seriesColor) + "</a:solidFill>";
6336
- if (chartType === "line" || chartType === "radar") if (opts.lineSize === 0) strXml += "<a:ln><a:noFill/></a:ln>";
6337
- else {
6338
- strXml += `<a:ln w="${valToPts(opts.lineSize)}" cap="${createLineCap(opts.lineCap)}"><a:solidFill>${createColorElement(seriesColor)}</a:solidFill>`;
6339
- strXml += `<a:prstDash val="${opts.lineDashValues?.[colorIndex] ?? opts.lineDash ?? "solid"}"/><a:round/></a:ln>`;
6340
- }
6341
- else if (opts.dataBorder) strXml += `<a:ln w="${valToPts(opts.dataBorder.pt)}" cap="${createLineCap(opts.lineCap)}"><a:solidFill>${createColorElement(opts.dataBorder.color)}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>`;
6777
+ if (chartType === "line" || chartType === "radar") {
6778
+ const effectiveLineSize = seriesOverride?.lineSize ?? opts.lineSize;
6779
+ if (effectiveLineSize === 0) strXml += "<a:ln><a:noFill/></a:ln>";
6780
+ else {
6781
+ strXml += `<a:ln w="${valToPts(effectiveLineSize)}" cap="${createLineCap(opts.lineCap)}"><a:solidFill>${createColorElement(seriesColor)}</a:solidFill>`;
6782
+ strXml += `<a:prstDash val="${opts.lineDashValues?.[colorIndex] ?? opts.lineDash ?? "solid"}"/><a:round/></a:ln>`;
6783
+ }
6784
+ } else if (opts.dataBorder) strXml += `<a:ln w="${valToPts(opts.dataBorder.pt)}" cap="${createLineCap(opts.lineCap)}"><a:solidFill>${createColorElement(opts.dataBorder.color)}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>`;
6342
6785
  strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
6343
6786
  strXml += " </c:spPr>";
6344
- if (chartType !== "line" && chartType !== "radar") strXml += " <c:invertIfNegative val=\"0\"/>";
6787
+ if (chartType === "bar" || chartType === "bar3D") strXml += " <c:invertIfNegative val=\"0\"/>";
6345
6788
  if (chartType === "line" || chartType === "radar") {
6346
6789
  strXml += "<c:marker>";
6347
6790
  strXml += " <c:symbol val=\"" + opts.lineDataSymbol + "\"/>";
@@ -6356,14 +6799,26 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6356
6799
  strXml += " </c:spPr>";
6357
6800
  strXml += "</c:marker>";
6358
6801
  }
6802
+ {
6803
+ const barVaryColors = (chartType === "bar" || chartType === "bar3D") && data.length === 1 && (opts.chartColors && opts.chartColors !== BARCHART_COLORS && opts.chartColors.length > 1 || opts.invertedColors?.length) ? opts.chartColors || BARCHART_COLORS : null;
6804
+ strXml += makeSeriesDataPointsXml(chartType, obj, opts, barVaryColors);
6805
+ }
6359
6806
  if (chartType !== "radar") {
6807
+ const lblColor = seriesOverride?.dataLabelColor ?? opts.dataLabelColor ?? "000000";
6808
+ const lblBold = seriesOverride?.dataLabelFontBold ?? opts.dataLabelFontBold ?? false;
6809
+ const lblItalic = seriesOverride?.dataLabelFontItalic ?? opts.dataLabelFontItalic ?? false;
6810
+ const lblSize = seriesOverride?.dataLabelFontSize ?? opts.dataLabelFontSize ?? 12;
6811
+ const lblFace = seriesOverride?.dataLabelFontFace ?? opts.dataLabelFontFace ?? "Arial";
6360
6812
  strXml += "<c:dLbls>";
6813
+ if (obj.customLabels?.length) obj.customLabels.forEach((lbl, idx) => {
6814
+ if (lbl) strXml += makeCustomDLblXml(idx, lbl, opts);
6815
+ });
6361
6816
  strXml += `<c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || "General"}" sourceLinked="0"/>`;
6362
6817
  if (opts.dataLabelBkgrdColors) strXml += `<c:spPr><a:solidFill>${createColorElement(seriesColor)}</a:solidFill></c:spPr>`;
6363
6818
  strXml += "<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr>";
6364
- strXml += `<a:defRPr b="${opts.dataLabelFontBold ? 1 : 0}" i="${opts.dataLabelFontItalic ? 1 : 0}" strike="noStrike" sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" u="none">`;
6365
- strXml += `<a:solidFill>${createColorElement(opts.dataLabelColor || "000000")}</a:solidFill>`;
6366
- strXml += `<a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
6819
+ strXml += `<a:defRPr b="${lblBold ? 1 : 0}" i="${lblItalic ? 1 : 0}" strike="noStrike" sz="${Math.round(lblSize * 100)}" u="none">`;
6820
+ strXml += `<a:solidFill>${createColorElement(lblColor)}</a:solidFill>`;
6821
+ strXml += createChartTextFonts(lblFace);
6367
6822
  strXml += "</a:defRPr></a:pPr></a:p></c:txPr>";
6368
6823
  if (opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
6369
6824
  strXml += "<c:showLegendKey val=\"0\"/>";
@@ -6372,29 +6827,6 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6372
6827
  strXml += `<c:showLeaderLines val="${opts.showLeaderLines ? "1" : "0"}"/>`;
6373
6828
  strXml += "</c:dLbls>";
6374
6829
  }
6375
- if ((chartType === "bar" || chartType === "bar3D") && data.length === 1 && (opts.chartColors && opts.chartColors !== BARCHART_COLORS && opts.chartColors.length > 1 || opts.invertedColors?.length)) obj.values.forEach((value, index) => {
6376
- const arrColors = value < 0 ? opts.invertedColors || opts.chartColors || BARCHART_COLORS : opts.chartColors || [];
6377
- strXml += " <c:dPt>";
6378
- strXml += ` <c:idx val="${index}"/>`;
6379
- strXml += " <c:invertIfNegative val=\"0\"/>";
6380
- strXml += " <c:bubble3D val=\"0\"/>";
6381
- strXml += " <c:spPr>";
6382
- if (opts.lineSize === 0) strXml += "<a:ln><a:noFill/></a:ln>";
6383
- else if (chartType === "bar") {
6384
- strXml += "<a:solidFill>";
6385
- strXml += " <a:srgbClr val=\"" + arrColors[index % arrColors.length] + "\"/>";
6386
- strXml += "</a:solidFill>";
6387
- } else {
6388
- strXml += "<a:ln>";
6389
- strXml += " <a:solidFill>";
6390
- strXml += " <a:srgbClr val=\"" + arrColors[index % arrColors.length] + "\"/>";
6391
- strXml += " </a:solidFill>";
6392
- strXml += "</a:ln>";
6393
- }
6394
- strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
6395
- strXml += " </c:spPr>";
6396
- strXml += " </c:dPt>";
6397
- });
6398
6830
  strXml += "<c:cat>";
6399
6831
  if (opts.catLabelFormatCode) {
6400
6832
  strXml += " <c:numRef>";
@@ -6431,10 +6863,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6431
6863
  strXml += " <c:numRef>";
6432
6864
  strXml += `<c:f>Sheet1!$${getExcelColName(obj._dataIndex + obj.labels.length + 1)}$2:$${getExcelColName(obj._dataIndex + obj.labels.length + 1)}$${obj.labels[0].length + 1}</c:f>`;
6433
6865
  strXml += " <c:numCache>";
6434
- strXml += " <c:formatCode>" + (opts.valLabelFormatCode || opts.dataTableFormatCode || "General") + "</c:formatCode>";
6866
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6435
6867
  strXml += ` <c:ptCount val="${obj.labels[0].length}"/>`;
6436
6868
  obj.values.forEach((value, idx) => {
6437
- if (value != null) strXml += `<c:pt idx="${idx}"><c:v>${value}</c:v></c:pt>`;
6869
+ strXml += numCachePt(idx, value);
6438
6870
  });
6439
6871
  strXml += " </c:numCache>";
6440
6872
  strXml += " </c:numRef>";
@@ -6450,7 +6882,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6450
6882
  strXml += " <a:p><a:pPr>";
6451
6883
  strXml += ` <a:defRPr b="${opts.dataLabelFontBold ? 1 : 0}" i="${opts.dataLabelFontItalic ? 1 : 0}" strike="noStrike" sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" u="none">`;
6452
6884
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6453
- strXml += " <a:latin typeface=\"" + (opts.dataLabelFontFace || "Arial") + "\"/>";
6885
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6454
6886
  strXml += " </a:defRPr>";
6455
6887
  strXml += " </a:pPr></a:p>";
6456
6888
  strXml += " </c:txPr>";
@@ -6466,6 +6898,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6466
6898
  if (chartType === "bar") {
6467
6899
  strXml += ` <c:gapWidth val="${opts.barGapWidthPct}"/>`;
6468
6900
  strXml += ` <c:overlap val="${opts.barOverlapPct != null ? opts.barOverlapPct : (opts.barGrouping || "").includes("tacked") ? 100 : 0}"/>`;
6901
+ strXml += createSerLinesElement(opts.barSeriesLine);
6469
6902
  } else if (chartType === "bar3D") {
6470
6903
  strXml += ` <c:gapWidth val="${opts.barGapWidthPct}"/>`;
6471
6904
  strXml += ` <c:gapDepth val="${opts.barGapDepthPct}"/>`;
@@ -6517,6 +6950,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6517
6950
  strXml += "<a:effectLst/>";
6518
6951
  strXml += "</c:spPr>";
6519
6952
  strXml += "</c:marker>";
6953
+ {
6954
+ const scatterVaryColors = data.length === 1 && opts.chartColors !== BARCHART_COLORS ? opts.chartColors || BARCHART_COLORS : null;
6955
+ strXml += makeSeriesDataPointsXml(chartType, obj, opts, scatterVaryColors);
6956
+ }
6520
6957
  if (opts.showLabel) {
6521
6958
  const chartUuid = getUuid("-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
6522
6959
  if (obj.labels[0] && (opts.dataLabelFormatScatter === "custom" || opts.dataLabelFormatScatter === "customXY")) {
@@ -6535,13 +6972,13 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6535
6972
  strXml += " <a:pPr>";
6536
6973
  strXml += ` <a:defRPr sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" b="${opts.dataLabelFontBold ? "1" : "0"}" i="${opts.dataLabelFontItalic ? "1" : "0"}" u="none" strike="noStrike">`;
6537
6974
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6538
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
6975
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6539
6976
  strXml += " </a:defRPr>";
6540
6977
  strXml += " </a:pPr>";
6541
6978
  strXml += " <a:r>";
6542
6979
  strXml += ` <a:rPr lang="${opts.lang || "en-US"}" sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" b="${opts.dataLabelFontBold ? "1" : "0"}" i="${opts.dataLabelFontItalic ? "1" : "0"}" u="none" strike="noStrike" dirty="0">`;
6543
6980
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6544
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
6981
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6545
6982
  strXml += " </a:rPr>";
6546
6983
  strXml += " <a:t>" + encodeXmlEntities(label) + "</a:t>";
6547
6984
  strXml += " </a:r>";
@@ -6621,7 +7058,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6621
7058
  strXml += " <a:pPr>";
6622
7059
  strXml += ` <a:defRPr sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" b="${opts.dataLabelFontBold ? "1" : "0"}" i="${opts.dataLabelFontItalic ? "1" : "0"}" u="none" strike="noStrike">`;
6623
7060
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6624
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
7061
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6625
7062
  strXml += " </a:defRPr>";
6626
7063
  strXml += " </a:pPr>";
6627
7064
  strXml += ` <a:endParaRPr lang="${opts.lang || "en-US"}"/>`;
@@ -6642,31 +7079,14 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6642
7079
  strXml += "</c:dLbls>";
6643
7080
  }
6644
7081
  }
6645
- if (data.length === 1 && opts.chartColors !== BARCHART_COLORS) obj.values.forEach((value, index) => {
6646
- const arrColors = value < 0 ? opts.invertedColors || opts.chartColors || BARCHART_COLORS : opts.chartColors || [];
6647
- strXml += " <c:dPt>";
6648
- strXml += ` <c:idx val="${index}"/>`;
6649
- strXml += " <c:invertIfNegative val=\"0\"/>";
6650
- strXml += " <c:bubble3D val=\"0\"/>";
6651
- strXml += " <c:spPr>";
6652
- if (opts.lineSize === 0) strXml += "<a:ln><a:noFill/></a:ln>";
6653
- else {
6654
- strXml += "<a:solidFill>";
6655
- strXml += " <a:srgbClr val=\"" + arrColors[index % arrColors.length] + "\"/>";
6656
- strXml += "</a:solidFill>";
6657
- }
6658
- strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
6659
- strXml += " </c:spPr>";
6660
- strXml += " </c:dPt>";
6661
- });
6662
7082
  strXml += "<c:xVal>";
6663
7083
  strXml += " <c:numRef>";
6664
7084
  strXml += ` <c:f>Sheet1!$A$2:$A$${data[0].values.length + 1}</c:f>`;
6665
7085
  strXml += " <c:numCache>";
6666
- strXml += " <c:formatCode>General</c:formatCode>";
7086
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6667
7087
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
6668
7088
  data[0].values.forEach((value, idx) => {
6669
- if (value != null) strXml += `<c:pt idx="${idx}"><c:v>${value}</c:v></c:pt>`;
7089
+ strXml += numCachePt(idx, value);
6670
7090
  });
6671
7091
  strXml += " </c:numCache>";
6672
7092
  strXml += " </c:numRef>";
@@ -6675,10 +7095,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6675
7095
  strXml += " <c:numRef>";
6676
7096
  strXml += ` <c:f>Sheet1!$${getExcelColName(idx + 2)}$2:$${getExcelColName(idx + 2)}$${data[0].values.length + 1}</c:f>`;
6677
7097
  strXml += " <c:numCache>";
6678
- strXml += " <c:formatCode>General</c:formatCode>";
7098
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6679
7099
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
6680
7100
  data[0].values.forEach((_value, idx) => {
6681
- if (obj.values[idx] != null) strXml += `<c:pt idx="${idx}"><c:v>${obj.values[idx]}</c:v></c:pt>`;
7101
+ strXml += numCachePt(idx, obj.values[idx]);
6682
7102
  });
6683
7103
  strXml += " </c:numCache>";
6684
7104
  strXml += " </c:numRef>";
@@ -6694,7 +7114,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6694
7114
  strXml += " <a:p><a:pPr>";
6695
7115
  strXml += ` <a:defRPr b="${opts.dataLabelFontBold ? "1" : "0"}" i="${opts.dataLabelFontItalic ? "1" : "0"}" strike="noStrike" sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" u="none">`;
6696
7116
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6697
- strXml += " <a:latin typeface=\"" + (opts.dataLabelFontFace || "Arial") + "\"/>";
7117
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6698
7118
  strXml += " </a:defRPr>";
6699
7119
  strXml += " </a:pPr></a:p>";
6700
7120
  strXml += " </c:txPr>";
@@ -6744,10 +7164,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6744
7164
  strXml += " <c:numRef>";
6745
7165
  strXml += ` <c:f>Sheet1!$A$2:$A$${data[0].values.length + 1}</c:f>`;
6746
7166
  strXml += " <c:numCache>";
6747
- strXml += " <c:formatCode>General</c:formatCode>";
7167
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6748
7168
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
6749
7169
  data[0].values.forEach((value, idx) => {
6750
- strXml += `<c:pt idx="${idx}"><c:v>${value || value === 0 ? value : ""}</c:v></c:pt>`;
7170
+ strXml += numCachePt(idx, value);
6751
7171
  });
6752
7172
  strXml += " </c:numCache>";
6753
7173
  strXml += " </c:numRef>";
@@ -6757,10 +7177,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6757
7177
  strXml += `<c:f>Sheet1!$${getExcelColName(idxColLtr + 1)}$2:$${getExcelColName(idxColLtr + 1)}$${data[0].values.length + 1}</c:f>`;
6758
7178
  idxColLtr++;
6759
7179
  strXml += " <c:numCache>";
6760
- strXml += " <c:formatCode>General</c:formatCode>";
7180
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6761
7181
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
6762
7182
  data[0].values.forEach((_value, idx) => {
6763
- strXml += `<c:pt idx="${idx}"><c:v>${obj.values[idx] || obj.values[idx] === 0 ? obj.values[idx] : ""}</c:v></c:pt>`;
7183
+ strXml += numCachePt(idx, obj.values[idx]);
6764
7184
  });
6765
7185
  strXml += " </c:numCache>";
6766
7186
  strXml += " </c:numRef>";
@@ -6773,7 +7193,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6773
7193
  strXml += " <c:formatCode>General</c:formatCode>";
6774
7194
  strXml += ` <c:ptCount val="${obj.sizes.length}"/>`;
6775
7195
  obj.sizes.forEach((value, idx) => {
6776
- strXml += `<c:pt idx="${idx}"><c:v>${value ?? ""}</c:v></c:pt>`;
7196
+ strXml += numCachePt(idx, value);
6777
7197
  });
6778
7198
  strXml += " </c:numCache>";
6779
7199
  strXml += " </c:numRef>";
@@ -6786,12 +7206,12 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6786
7206
  strXml += "<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr>";
6787
7207
  strXml += `<a:defRPr b="${opts.dataLabelFontBold ? 1 : 0}" i="${opts.dataLabelFontItalic ? 1 : 0}" strike="noStrike" sz="${Math.round(Math.round(opts.dataLabelFontSize || 12) * 100)}" u="none">`;
6788
7208
  strXml += `<a:solidFill>${createColorElement(opts.dataLabelColor || "000000")}</a:solidFill>`;
6789
- strXml += `<a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
7209
+ strXml += createChartTextFonts(opts.dataLabelFontFace || "Arial");
6790
7210
  strXml += "</a:defRPr></a:pPr></a:p></c:txPr>";
6791
7211
  if (opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
6792
7212
  strXml += "<c:showLegendKey val=\"0\"/>";
6793
7213
  strXml += `<c:showVal val="${opts.showValue ? "1" : "0"}"/>`;
6794
- strXml += `<c:showCatName val="0"/><c:showSerName val="${opts.showSerName ? "1" : "0"}"/><c:showPercent val="0"/><c:showBubbleSize val="0"/>`;
7214
+ strXml += `<c:showCatName val="0"/><c:showSerName val="${opts.showSerName ? "1" : "0"}"/><c:showPercent val="0"/><c:showBubbleSize val="${opts.showBubbleSize ? "1" : "0"}"/>`;
6795
7215
  strXml += "<c:extLst>";
6796
7216
  strXml += " <c:ext uri=\"{CE6537A1-D6FC-4f65-9D91-7224C49458BB}\" xmlns:c15=\"http://schemas.microsoft.com/office/drawing/2012/chart\">";
6797
7217
  strXml += " <c15:showLeaderLines val=\"" + (opts.showLeaderLines ? "1" : "0") + "\"/>";
@@ -6825,33 +7245,37 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6825
7245
  else strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
6826
7246
  strXml += " </c:spPr>";
6827
7247
  optsChartData.labels[0].forEach((_label, idx) => {
7248
+ const ptStyle = optsChartData.pointStyles?.[idx];
6828
7249
  strXml += "<c:dPt>";
6829
7250
  strXml += ` <c:idx val="${idx}"/>`;
6830
7251
  strXml += " <c:bubble3D val=\"0\"/>";
6831
7252
  strXml += " <c:spPr>";
6832
- strXml += `<a:solidFill>${createColorElement(opts.chartColors[idx + 1 > opts.chartColors.length ? Math.floor(Math.random() * opts.chartColors.length) : idx])}</a:solidFill>`;
6833
- if (opts.dataBorder) strXml += `<a:ln w="${valToPts(opts.dataBorder.pt)}" cap="flat"><a:solidFill>${createColorElement(opts.dataBorder.color)}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>`;
7253
+ strXml += `<a:solidFill>${createColorElement(ptStyle?.fill || opts.chartColors[idx + 1 > opts.chartColors.length ? Math.floor(Math.random() * opts.chartColors.length) : idx])}</a:solidFill>`;
7254
+ if (ptStyle?.border) strXml += createChartBorderLine(ptStyle.border);
7255
+ else if (opts.dataBorder) strXml += `<a:ln w="${valToPts(opts.dataBorder.pt)}" cap="flat"><a:solidFill>${createColorElement(opts.dataBorder.color)}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>`;
6834
7256
  strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
6835
7257
  strXml += " </c:spPr>";
6836
7258
  strXml += "</c:dPt>";
6837
7259
  });
6838
7260
  strXml += "<c:dLbls>";
6839
7261
  optsChartData.labels[0].forEach((_label, idx) => {
7262
+ const customLbl = optsChartData.customLabels?.[idx];
6840
7263
  strXml += "<c:dLbl>";
6841
7264
  strXml += ` <c:idx val="${idx}"/>`;
7265
+ if (customLbl) strXml += `<c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:r><a:rPr lang="${opts.lang || "en-US"}" dirty="0"/><a:t>${encodeXmlEntities(customLbl)}</a:t></a:r></a:p></c:rich></c:tx>`;
6842
7266
  strXml += ` <c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || "General"}" sourceLinked="0"/>`;
6843
7267
  strXml += " <c:spPr/><c:txPr>";
6844
7268
  strXml += " <a:bodyPr/><a:lstStyle/>";
6845
7269
  strXml += " <a:p><a:pPr>";
6846
7270
  strXml += ` <a:defRPr sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" b="${opts.dataLabelFontBold ? 1 : 0}" i="${opts.dataLabelFontItalic ? 1 : 0}" u="none" strike="noStrike">`;
6847
7271
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6848
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
7272
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6849
7273
  strXml += " </a:defRPr>";
6850
7274
  strXml += " </a:pPr></a:p>";
6851
7275
  strXml += " </c:txPr>";
6852
7276
  if (chartType === "pie" && opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
6853
7277
  strXml += " <c:showLegendKey val=\"0\"/>";
6854
- strXml += " <c:showVal val=\"" + (opts.showValue ? "1" : "0") + "\"/>";
7278
+ strXml += " <c:showVal val=\"" + (customLbl ? "0" : opts.showValue ? "1" : "0") + "\"/>";
6855
7279
  strXml += " <c:showCatName val=\"" + (opts.showLabel ? "1" : "0") + "\"/>";
6856
7280
  strXml += " <c:showSerName val=\"" + (opts.showSerName ? "1" : "0") + "\"/>";
6857
7281
  strXml += " <c:showPercent val=\"" + (opts.showPercent ? "1" : "0") + "\"/>";
@@ -6866,7 +7290,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6866
7290
  strXml += " <a:pPr>";
6867
7291
  strXml += ` <a:defRPr sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" b="${opts.dataLabelFontBold ? "1" : "0"}" i="${opts.dataLabelFontItalic ? "1" : "0"}" u="none" strike="noStrike">`;
6868
7292
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6869
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
7293
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6870
7294
  strXml += " </a:defRPr>";
6871
7295
  strXml += " </a:pPr>";
6872
7296
  strXml += " </a:p>";
@@ -6895,6 +7319,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6895
7319
  strXml += " <c:numRef>";
6896
7320
  strXml += ` <c:f>Sheet1!$B$2:$B$${optsChartData.labels[0].length + 1}</c:f>`;
6897
7321
  strXml += " <c:numCache>";
7322
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6898
7323
  strXml += ` <c:ptCount val="${optsChartData.labels[0].length}"/>`;
6899
7324
  optsChartData.values.forEach((value, idx) => {
6900
7325
  strXml += `<c:pt idx="${idx}"><c:v>${value || value === 0 ? value : ""}</c:v></c:pt>`;
@@ -6970,7 +7395,7 @@ function makeCatAxis(opts, axisId, valAxisId) {
6970
7395
  strXml += " <a:pPr>";
6971
7396
  strXml += ` <a:defRPr sz="${Math.round((opts.catAxisLabelFontSize || 12) * 100)}" b="${opts.catAxisLabelFontBold ? 1 : 0}" i="${opts.catAxisLabelFontItalic ? 1 : 0}" u="none" strike="noStrike">`;
6972
7397
  strXml += " <a:solidFill>" + createColorElement(opts.catAxisLabelColor || "000000") + "</a:solidFill>";
6973
- strXml += " <a:latin typeface=\"" + (opts.catAxisLabelFontFace || "Arial") + "\"/>";
7398
+ strXml += " " + createChartTextFonts(opts.catAxisLabelFontFace || "Arial");
6974
7399
  strXml += " </a:defRPr>";
6975
7400
  strXml += " </a:pPr>";
6976
7401
  strXml += " <a:endParaRPr lang=\"" + (opts.lang || "en-US") + "\"/>";
@@ -7063,7 +7488,7 @@ function makeValAxis(opts, valAxisId) {
7063
7488
  strXml += " <a:pPr>";
7064
7489
  strXml += ` <a:defRPr sz="${Math.round((opts.valAxisLabelFontSize || 12) * 100)}" b="${opts.valAxisLabelFontBold ? 1 : 0}" i="${opts.valAxisLabelFontItalic ? 1 : 0}" u="none" strike="noStrike">`;
7065
7490
  strXml += " <a:solidFill>" + createColorElement(opts.valAxisLabelColor || "000000") + "</a:solidFill>";
7066
- strXml += " <a:latin typeface=\"" + (opts.valAxisLabelFontFace || "Arial") + "\"/>";
7491
+ strXml += " " + createChartTextFonts(opts.valAxisLabelFontFace || "Arial");
7067
7492
  strXml += " </a:defRPr>";
7068
7493
  strXml += " </a:pPr>";
7069
7494
  strXml += " <a:endParaRPr lang=\"" + (opts.lang || "en-US") + "\"/>";
@@ -7119,7 +7544,7 @@ function makeSerAxis(opts, axisId, valAxisId) {
7119
7544
  strXml += " <a:pPr>";
7120
7545
  strXml += ` <a:defRPr sz="${Math.round((opts.serAxisLabelFontSize || 12) * 100)}" b="${opts.serAxisLabelFontBold ? "1" : "0"}" i="${opts.serAxisLabelFontItalic ? "1" : "0"}" u="none" strike="noStrike">`;
7121
7546
  strXml += ` <a:solidFill>${createColorElement(opts.serAxisLabelColor || "000000")}</a:solidFill>`;
7122
- strXml += ` <a:latin typeface="${opts.serAxisLabelFontFace || "Arial"}"/>`;
7547
+ strXml += " " + createChartTextFonts(opts.serAxisLabelFontFace || "Arial");
7123
7548
  strXml += " </a:defRPr>";
7124
7549
  strXml += " </a:pPr>";
7125
7550
  strXml += " <a:endParaRPr lang=\"" + (opts.lang || "en-US") + "\"/>";
@@ -7159,17 +7584,31 @@ function genXmlTitle(opts, chartX, chartY) {
7159
7584
  const rotate = opts.titleRotate ? `<a:bodyPr rot="${convertRotationDegrees(opts.titleRotate)}"/>` : "<a:bodyPr/>";
7160
7585
  const sizeAttr = opts.fontSize ? `sz="${Math.round(opts.fontSize * 100)}"` : "";
7161
7586
  const titleBold = opts.titleBold ? 1 : 0;
7587
+ const titleItalic = opts.titleItalic ? 1 : 0;
7588
+ const titleUnderline = opts.titleUnderline ? "sng" : "none";
7162
7589
  let layout = "<c:layout/>";
7163
- if (opts.titlePos && typeof opts.titlePos.x === "number" && typeof opts.titlePos.y === "number") {
7164
- const totalX = opts.titlePos.x + chartX;
7165
- const totalY = opts.titlePos.y + chartY;
7166
- let valX = totalX === 0 ? 0 : totalX * (totalX / 5) / 10;
7167
- if (valX >= 1) valX = valX / 10;
7168
- if (valX >= .1) valX = valX / 10;
7169
- let valY = totalY === 0 ? 0 : totalY * (totalY / 5) / 10;
7170
- if (valY >= 1) valY = valY / 10;
7171
- if (valY >= .1) valY = valY / 10;
7172
- layout = `<c:layout><c:manualLayout><c:xMode val="edge"/><c:yMode val="edge"/><c:x val="${valX}"/><c:y val="${valY}"/></c:manualLayout></c:layout>`;
7590
+ const hasX = opts.titlePos && typeof opts.titlePos.x === "number";
7591
+ const hasY = opts.titlePos && typeof opts.titlePos.y === "number";
7592
+ if (hasX || hasY) {
7593
+ let modes = "";
7594
+ let vals = "";
7595
+ if (hasX) {
7596
+ const totalX = opts.titlePos.x + chartX;
7597
+ let valX = totalX === 0 ? 0 : totalX * (totalX / 5) / 10;
7598
+ if (valX >= 1) valX = valX / 10;
7599
+ if (valX >= .1) valX = valX / 10;
7600
+ modes += "<c:xMode val=\"edge\"/>";
7601
+ vals += `<c:x val="${valX}"/>`;
7602
+ }
7603
+ if (hasY) {
7604
+ const totalY = opts.titlePos.y + chartY;
7605
+ let valY = totalY === 0 ? 0 : totalY * (totalY / 5) / 10;
7606
+ if (valY >= 1) valY = valY / 10;
7607
+ if (valY >= .1) valY = valY / 10;
7608
+ modes += "<c:yMode val=\"edge\"/>";
7609
+ vals += `<c:y val="${valY}"/>`;
7610
+ }
7611
+ layout = `<c:layout><c:manualLayout>${modes}${vals}</c:manualLayout></c:layout>`;
7173
7612
  }
7174
7613
  return `<c:title>
7175
7614
  <c:tx>
@@ -7178,15 +7617,15 @@ function genXmlTitle(opts, chartX, chartY) {
7178
7617
  <a:lstStyle/>
7179
7618
  <a:p>
7180
7619
  ${align}
7181
- <a:defRPr ${sizeAttr} b="${titleBold}" i="0" u="none" strike="noStrike">
7620
+ <a:defRPr ${sizeAttr} b="${titleBold}" i="${titleItalic}" u="${titleUnderline}" strike="noStrike">
7182
7621
  <a:solidFill>${createColorElement(opts.color || "000000")}</a:solidFill>
7183
- <a:latin typeface="${opts.fontFace || "Arial"}"/>
7622
+ ${createChartTextFonts(opts.fontFace || "Arial")}
7184
7623
  </a:defRPr>
7185
7624
  </a:pPr>
7186
7625
  <a:r>
7187
- <a:rPr ${sizeAttr} b="${titleBold}" i="0" u="none" strike="noStrike">
7626
+ <a:rPr ${sizeAttr} b="${titleBold}" i="${titleItalic}" u="${titleUnderline}" strike="noStrike">
7188
7627
  <a:solidFill>${createColorElement(opts.color || "000000")}</a:solidFill>
7189
- <a:latin typeface="${opts.fontFace || "Arial"}"/>
7628
+ ${createChartTextFonts(opts.fontFace || "Arial")}
7190
7629
  </a:rPr>
7191
7630
  <a:t>${encodeXmlEntities(opts.title) || ""}</a:t>
7192
7631
  </a:r>
@@ -7260,11 +7699,105 @@ function createGridLineElement(glOpts) {
7260
7699
  strXml += "</c:majorGridlines>";
7261
7700
  return strXml;
7262
7701
  }
7263
- function createLineCap(lineCap) {
7264
- if (!lineCap || lineCap === "flat") return "flat";
7265
- else if (lineCap === "square") return "sq";
7266
- else if (lineCap === "round") return "rnd";
7267
- else throw new Error(`Invalid chart line cap: ${lineCap}`);
7702
+ /**
7703
+ * Build a `<c:pt>` numeric-cache data point, or '' to leave a gap.
7704
+ *
7705
+ * `<c:v>` inside a `<c:numCache>` is an `xsd:double`; emitting `NaN`, `Infinity`
7706
+ * or an empty string yields an invalid value that makes PowerPoint report the
7707
+ * package as needing repair. Null/undefined are intentional gaps and are skipped
7708
+ * silently (a sparse, idx-keyed cache is valid); other non-finite numbers are
7709
+ * skipped with a warning, per the library's "warn rather than emit a degenerate
7710
+ * result" policy.
7711
+ * @param idx - zero-based data-point index (emitted as `idx`)
7712
+ * @param value - numeric value (or null/undefined gap)
7713
+ */
7714
+ function numCachePt(idx, value) {
7715
+ if (value == null) return "";
7716
+ if (!Number.isFinite(value)) {
7717
+ console.warn(`Warning: chart value "${value}" at index ${idx} is not a finite number; data point omitted.`);
7718
+ return "";
7719
+ }
7720
+ return `<c:pt idx="${idx}"><c:v>${value}</c:v></c:pt>`;
7721
+ }
7722
+ /**
7723
+ * Build a `<c:serLines>` ("Series Lines") element for a bar chart.
7724
+ * @param opt - `true` for PowerPoint automatic styling, an {@link OptsChartGridLine}
7725
+ * to customize the line, or falsy / `{ style: 'none' }` to omit the element.
7726
+ */
7727
+ function createSerLinesElement(opt) {
7728
+ if (!opt) return "";
7729
+ if (opt === true) return "<c:serLines/>";
7730
+ if (opt.style === "none") return "";
7731
+ let strXml = "<c:serLines><c:spPr>";
7732
+ strXml += `<a:ln w="${valToPts(opt.size || DEF_CHART_GRIDLINE.size)}" cap="${createLineCap(opt.cap || DEF_CHART_GRIDLINE.cap)}">`;
7733
+ strXml += `<a:solidFill><a:srgbClr val="${opt.color || DEF_CHART_GRIDLINE.color}"/></a:solidFill>`;
7734
+ strXml += `<a:prstDash val="${opt.style || DEF_CHART_GRIDLINE.style}"/><a:round/>`;
7735
+ strXml += "</a:ln></c:spPr></c:serLines>";
7736
+ return strXml;
7737
+ }
7738
+ function makeCustomDLblXml(idx, text, opts) {
7739
+ const sz = Math.round((opts.dataLabelFontSize || 12) * 100);
7740
+ const bold = opts.dataLabelFontBold ? "1" : "0";
7741
+ const italic = opts.dataLabelFontItalic ? "1" : "0";
7742
+ const color = createColorElement(opts.dataLabelColor || "000000");
7743
+ const face = opts.dataLabelFontFace || "Arial";
7744
+ const lang = opts.lang || "en-US";
7745
+ return `<c:dLbl><c:idx val="${idx}"/><c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:pPr><a:defRPr sz="${sz}" b="${bold}" i="${italic}" u="none" strike="noStrike"><a:solidFill>${color}</a:solidFill>${createChartTextFonts(face)}</a:defRPr></a:pPr><a:r><a:rPr lang="${lang}" sz="${sz}" b="${bold}" i="${italic}" u="none" strike="noStrike" dirty="0"><a:solidFill>${color}</a:solidFill>${createChartTextFonts(face)}</a:rPr><a:t>${encodeXmlEntities(text)}</a:t></a:r></a:p></c:rich></c:tx><c:showLegendKey val="0"/><c:showVal val="0"/><c:showCatName val="0"/><c:showSerName val="0"/><c:showPercent val="0"/><c:showBubbleSize val="0"/></c:dLbl>`;
7746
+ }
7747
+ /**
7748
+ * Build an `<a:ln>` border element from a per-data-point `BorderProps`.
7749
+ * @param border - point border style (`type`, `color`, `pt`)
7750
+ */
7751
+ function createChartBorderLine(border) {
7752
+ if (border.type === "none") return "<a:ln><a:noFill/></a:ln>";
7753
+ const dash = border.type === "dash" ? "dash" : "solid";
7754
+ return `<a:ln w="${valToPts(border.pt ?? 1)}" cap="flat"><a:solidFill>${createColorElement(border.color || "666666")}</a:solidFill><a:prstDash val="${dash}"/><a:round/></a:ln>`;
7755
+ }
7756
+ /**
7757
+ * Build `<c:dPt>` entries for a series in the bar/line/area/scatter loops.
7758
+ *
7759
+ * Merges two sources into a single `c:dPt` per index so we never emit a
7760
+ * duplicate `<c:idx>` (which corrupts the chart):
7761
+ * - legacy single-series color-vary fills (bar/scatter), supplied via `varyColors`
7762
+ * - per-point `pointStyles` border/fill overrides
7763
+ *
7764
+ * Must be emitted in schema position *before* `c:dLbls` (CT_*Ser order).
7765
+ * RADAR is skipped: extra per-point markup historically corrupts the chart.
7766
+ *
7767
+ * @param chartType - series chart type
7768
+ * @param obj - series data (reads `values`, `pointStyles`)
7769
+ * @param opts - chart options (fill/shadow/lineSize context)
7770
+ * @param varyColors - color array when single-series color-vary applies, else `null`
7771
+ */
7772
+ function makeSeriesDataPointsXml(chartType, obj, opts, varyColors) {
7773
+ if (chartType === "radar") return "";
7774
+ const pointStyles = obj.pointStyles;
7775
+ if (!varyColors && !pointStyles?.length) return "";
7776
+ const isBar = chartType === "bar" || chartType === "bar3D";
7777
+ const isScatter = chartType === "scatter";
7778
+ let xml = "";
7779
+ obj.values.forEach((value, index) => {
7780
+ const ptStyle = pointStyles?.[index];
7781
+ const arrColors = varyColors ? value < 0 ? opts.invertedColors || opts.chartColors || BARCHART_COLORS : varyColors : null;
7782
+ const fillColor = ptStyle?.fill || (arrColors ? arrColors[index % arrColors.length] : null);
7783
+ const border = ptStyle?.border;
7784
+ if (!fillColor && !border) return;
7785
+ xml += "<c:dPt>";
7786
+ xml += `<c:idx val="${index}"/>`;
7787
+ if (isBar) xml += "<c:invertIfNegative val=\"0\"/>";
7788
+ xml += "<c:bubble3D val=\"0\"/>";
7789
+ xml += "<c:spPr>";
7790
+ if ((isBar || isScatter) && opts.lineSize === 0 && !border && !ptStyle?.fill) xml += "<a:ln><a:noFill/></a:ln>";
7791
+ else {
7792
+ if (fillColor) if (chartType === "bar3D") xml += `<a:ln><a:solidFill>${createColorElement(fillColor)}</a:solidFill></a:ln>`;
7793
+ else xml += `<a:solidFill>${createColorElement(fillColor)}</a:solidFill>`;
7794
+ if (border) xml += createChartBorderLine(border);
7795
+ }
7796
+ xml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
7797
+ xml += "</c:spPr>";
7798
+ xml += "</c:dPt>";
7799
+ });
7800
+ return xml;
7268
7801
  }
7269
7802
  //#endregion
7270
7803
  //#region src/gen-media.ts
@@ -7313,6 +7846,37 @@ function encodeSlideMediaRels(layout, runtime) {
7313
7846
  /**
7314
7847
  * PptxGenJS: XML Generation
7315
7848
  */
7849
+ const _warnedTextRangeMsgs = /* @__PURE__ */ new Set();
7850
+ function warnTextRangeOnce(msg) {
7851
+ if (_warnedTextRangeMsgs.has(msg)) return;
7852
+ _warnedTextRangeMsgs.add(msg);
7853
+ console.warn(msg);
7854
+ }
7855
+ /**
7856
+ * Clamp a font size (points) into ST_TextFontSize (1-4000pt) and return it in
7857
+ * hundredths of a point for the `sz` attribute. Out-of-range sizes make
7858
+ * PowerPoint report the package as needing repair (e.g. `sz` > 400000 or < 100).
7859
+ */
7860
+ function clampFontSizeSz(fontSizePts) {
7861
+ const raw = Math.round(fontSizePts * 100);
7862
+ const clamped = Math.min(4e5, Math.max(100, raw));
7863
+ if (clamped !== raw) warnTextRangeOnce(`Warning: fontSize ${fontSizePts} is outside the valid range 1-4000pt; using ${clamped / 100}.`);
7864
+ return clamped;
7865
+ }
7866
+ /** Clamp character spacing (points) into ST_TextPoint (-4000..4000pt); returns hundredths for the `spc` attribute. */
7867
+ function clampCharSpacingSpc(charSpacingPts) {
7868
+ const raw = Math.round(charSpacingPts * 100);
7869
+ const clamped = Math.min(4e5, Math.max(-4e5, raw));
7870
+ if (clamped !== raw) warnTextRangeOnce(`Warning: charSpacing ${charSpacingPts} is outside the valid range -4000..4000pt; using ${clamped / 100}.`);
7871
+ return clamped;
7872
+ }
7873
+ /** Clamp line spacing (points) into ST_TextSpacingPoint (0..1584pt); returns hundredths for `<a:spcPts val>`. */
7874
+ function clampLineSpacingPts(lineSpacingPts) {
7875
+ const raw = Math.round(lineSpacingPts * 100);
7876
+ const clamped = Math.min(158400, Math.max(0, raw));
7877
+ if (clamped !== raw) warnTextRangeOnce(`Warning: lineSpacing ${lineSpacingPts} is outside the valid range 0-1584pt; using ${clamped / 100}.`);
7878
+ return clamped;
7879
+ }
7316
7880
  const ImageSizingXml = {
7317
7881
  cover: function(imgSize, boxDim) {
7318
7882
  const imgRatio = imgSize.h / imgSize.w;
@@ -7337,6 +7901,15 @@ const ImageSizingXml = {
7337
7901
  const r = imgSize.w - (boxDim.x + boxDim.w);
7338
7902
  const t = boxDim.y;
7339
7903
  const b = imgSize.h - (boxDim.y + boxDim.h);
7904
+ if (l < 0 || r < 0 || t < 0 || b < 0) {
7905
+ const over = [
7906
+ l < 0 && `x (${l < 0 ? -l : 0} past left edge)`,
7907
+ r < 0 && `x+w (${-r} past right edge)`,
7908
+ t < 0 && `y (${-t} past top edge)`,
7909
+ b < 0 && `y+h (${-b} past bottom edge)`
7910
+ ].filter(Boolean).join(", ");
7911
+ throw new Error(`addImage sizing.type 'crop': crop window overflows image bounds — ${over}. Ensure x≥0, y≥0, x+w≤w, y+h≤h.`);
7912
+ }
7340
7913
  return `<a:srcRect l="${Math.round(1e5 * (l / imgSize.w))}" r="${Math.round(1e5 * (r / imgSize.w))}" t="${Math.round(1e5 * (t / imgSize.h))}" b="${Math.round(1e5 * (b / imgSize.h))}"/><a:stretch/>`;
7341
7914
  }
7342
7915
  };
@@ -7349,18 +7922,91 @@ const ImageSizingXml = {
7349
7922
  * @param {number} cy - shape height (EMU), used to scale `rectRadius`
7350
7923
  * @return {string} `<a:prstGeom>` XML
7351
7924
  */
7925
+ const RECT_RADIUS_ADJ1_SHAPES = new Set(["round2SameRect", "round2DiagRect"]);
7926
+ const SHAPE_LOCK_ATTRS = [
7927
+ "noGrp",
7928
+ "noSelect",
7929
+ "noRot",
7930
+ "noChangeAspect",
7931
+ "noMove",
7932
+ "noResize",
7933
+ "noEditPoints",
7934
+ "noAdjustHandles",
7935
+ "noChangeArrowheads",
7936
+ "noChangeShapeType",
7937
+ "noTextEdit"
7938
+ ];
7939
+ const PICTURE_LOCK_ATTRS = [
7940
+ "noGrp",
7941
+ "noSelect",
7942
+ "noRot",
7943
+ "noChangeAspect",
7944
+ "noMove",
7945
+ "noResize",
7946
+ "noEditPoints",
7947
+ "noAdjustHandles",
7948
+ "noChangeArrowheads",
7949
+ "noChangeShapeType",
7950
+ "noCrop"
7951
+ ];
7952
+ const GRAPHIC_FRAME_LOCK_ATTRS = [
7953
+ "noGrp",
7954
+ "noDrilldown",
7955
+ "noSelect",
7956
+ "noChangeAspect",
7957
+ "noMove",
7958
+ "noResize"
7959
+ ];
7960
+ /**
7961
+ * Serialize an object-lock element (`a:spLocks` / `a:picLocks` / `a:graphicFrameLocks`).
7962
+ * Only flags set to `true` AND valid for this element type are emitted; a flag set on an
7963
+ * unsupported element type is dropped with a warning (silent coercion is a footgun).
7964
+ * @param tag - locking element tag, e.g. `'a:spLocks'`
7965
+ * @param allowed - attribute names this element type supports, in desired emit order
7966
+ * @param locks - merged lock flags (callers fold any hard-coded default in first)
7967
+ * @param objectName - for the warning message
7968
+ * @returns the locking element string, or `''` when no applicable flag is set
7969
+ */
7970
+ function genXmlObjectLock(tag, allowed, locks, objectName) {
7971
+ if (!locks) return "";
7972
+ const lockMap = locks;
7973
+ for (const key of Object.keys(lockMap)) if (lockMap[key] && !allowed.includes(key)) console.warn(`Warning: objectLock.${key} is not supported on <${tag}> (object "${objectName ?? ""}") and was ignored.`);
7974
+ const attrs = allowed.filter((name) => lockMap[name] === true).map((name) => `${name}="1"`);
7975
+ return attrs.length > 0 ? `<${tag} ${attrs.join(" ")}/>` : "";
7976
+ }
7352
7977
  function genXmlPresetGeom(shapeName, options, cx, cy) {
7353
- let strXml = `<a:prstGeom prst="${shapeName}"><a:avLst>`;
7354
- if (options.rectRadius) strXml += `<a:gd name="adj" fmla="val ${Math.round(options.rectRadius * EMU * 1e5 / Math.min(cx, cy))}"/>`;
7355
- else if (options.angleRange) {
7978
+ if (!VALID_SHAPE_PRESETS.has(shapeName)) throw new Error(`Invalid shape "${String(shapeName)}"! Use a value from \`pptxgen.shapes.*\` (e.g. \`pptxgen.shapes.RECTANGLE\`). PowerPoint can't render unknown preset geometries and will drop the shape during repair.`);
7979
+ let avLst = "";
7980
+ const emittedAdjNames = /* @__PURE__ */ new Set();
7981
+ const emitGuide = (name, fmlaVal) => {
7982
+ avLst += `<a:gd name="${name}" fmla="val ${fmlaVal}"/>`;
7983
+ emittedAdjNames.add(name);
7984
+ };
7985
+ if (options.rectRadius) {
7986
+ const adjVal = Math.round(options.rectRadius * EMU * 1e5 / Math.min(cx, cy));
7987
+ if (RECT_RADIUS_ADJ1_SHAPES.has(shapeName)) {
7988
+ emitGuide("adj1", adjVal);
7989
+ emitGuide("adj2", 0);
7990
+ } else emitGuide("adj", adjVal);
7991
+ } else if (options.angleRange) {
7356
7992
  for (let i = 0; i < 2; i++) {
7357
7993
  const angle = options.angleRange[i];
7358
- strXml += `<a:gd name="adj${i + 1}" fmla="val ${convertRotationDegrees(angle)}" />`;
7994
+ emitGuide(`adj${i + 1}`, convertRotationDegrees(angle));
7359
7995
  }
7360
- if (options.arcThicknessRatio) strXml += `<a:gd name="adj3" fmla="val ${Math.round(options.arcThicknessRatio * 5e4)}" />`;
7996
+ if (options.arcThicknessRatio) emitGuide("adj3", Math.round(options.arcThicknessRatio * 5e4));
7361
7997
  }
7362
- strXml += "</a:avLst></a:prstGeom>";
7363
- return strXml;
7998
+ if (options.shapeAdjust) (Array.isArray(options.shapeAdjust) ? options.shapeAdjust : [options.shapeAdjust]).forEach((adj) => {
7999
+ if (!adj || typeof adj.name !== "string" || adj.name.length === 0 || typeof adj.value !== "number" || !isFinite(adj.value)) {
8000
+ console.warn(`Warning: shapeAdjust entry ${JSON.stringify(adj)} is invalid (needs { name:string, value:number }) and was ignored.`);
8001
+ return;
8002
+ }
8003
+ if (emittedAdjNames.has(adj.name)) {
8004
+ console.warn(`Warning: shapeAdjust "${adj.name}" was ignored because rectRadius/angleRange already set that handle.`);
8005
+ return;
8006
+ }
8007
+ emitGuide(adj.name, Math.round(adj.value * 1e5));
8008
+ });
8009
+ return `<a:prstGeom prst="${shapeName}"><a:avLst>${avLst}</a:avLst></a:prstGeom>`;
7364
8010
  }
7365
8011
  /**
7366
8012
  * Emit an `<a:custGeom>` for a freeform path built from `points`.
@@ -7413,6 +8059,45 @@ function genXmlCustGeom(points, cx, cy, layout) {
7413
8059
  }
7414
8060
  const PLACEHOLDER_TYPE_MAP = PLACEHOLDER_TYPES;
7415
8061
  /**
8062
+ * Emit the `<a:lnL>/<a:lnR>/<a:lnT>/<a:lnB>` border children of an `<a:tcPr>` for a table cell.
8063
+ * Shared by normal cells and the dummy span (`_hmerge`/`_vmerge`) cells so a merged region's
8064
+ * outer edges render with the same border as its origin cell (Issue #680).
8065
+ * @param {BorderProps[]} cellBorder - 4-tuple of border props in [top, right, bottom, left] order
8066
+ * @return {string} concatenated border element XML, in the LRTB document order PowerPoint expects
8067
+ */
8068
+ function genTableCellBorderXml(cellBorder) {
8069
+ let strXml = "";
8070
+ [
8071
+ {
8072
+ idx: 3,
8073
+ name: "lnL"
8074
+ },
8075
+ {
8076
+ idx: 1,
8077
+ name: "lnR"
8078
+ },
8079
+ {
8080
+ idx: 0,
8081
+ name: "lnT"
8082
+ },
8083
+ {
8084
+ idx: 2,
8085
+ name: "lnB"
8086
+ }
8087
+ ].forEach((obj) => {
8088
+ const border = cellBorder[obj.idx];
8089
+ if (!border) return;
8090
+ const cap = createLineCap(border.cap);
8091
+ if (border.type !== "none") {
8092
+ strXml += `<a:${obj.name} w="${valToPts(border.pt)}" cap="${cap}" cmpd="sng" algn="ctr">`;
8093
+ strXml += `<a:solidFill>${createColorElement(border.color)}</a:solidFill>`;
8094
+ strXml += `<a:prstDash val="${border.type === "dash" ? "sysDash" : "solid"}"/><a:round/><a:headEnd type="none" w="med" len="med"/><a:tailEnd type="none" w="med" len="med"/>`;
8095
+ strXml += `</a:${obj.name}>`;
8096
+ } else strXml += `<a:${obj.name} w="0" cap="${cap}" cmpd="sng" algn="ctr"><a:noFill/></a:${obj.name}>`;
8097
+ });
8098
+ return strXml;
8099
+ }
8100
+ /**
7416
8101
  * Transforms a slide or slideLayout to resulting XML string - Creates `ppt/slide*.xml`
7417
8102
  * @param {PresSlideInternal|SlideLayoutInternal} slideObject - slide object created within createSlideObject
7418
8103
  * @return {string} XML string with <p:cSld> as the root
@@ -7471,9 +8156,16 @@ function slideObjectToXml(slide) {
7471
8156
  intColCnt += cellOpts?.colspan ? Number(cellOpts.colspan) : 1;
7472
8157
  });
7473
8158
  strXml = `<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id="${intTableNum * slide._slideNum + 1}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || "")}"/>`;
7474
- 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>";
8159
+ strXml += `<p:cNvGraphicFramePr>${genXmlObjectLock("a:graphicFrameLocks", GRAPHIC_FRAME_LOCK_ATTRS, {
8160
+ noGrp: true,
8161
+ ...slideItemObj.options.objectLock
8162
+ }, slideItemObj.options.objectName)}</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>`;
7475
8163
  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>`;
7476
- strXml += "<a:graphic><a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/table\"><a:tbl><a:tblPr/>";
8164
+ {
8165
+ const tblPrAttrs = (objTabOpts.hasHeader ? " firstRow=\"1\"" : "") + (objTabOpts.hasFooter ? " lastRow=\"1\"" : "") + (objTabOpts.hasBandedRows ? " bandRow=\"1\"" : "") + (objTabOpts.hasBandedColumns ? " bandCol=\"1\"" : "") + (objTabOpts.hasFirstColumn ? " firstCol=\"1\"" : "") + (objTabOpts.hasLastColumn ? " lastCol=\"1\"" : "");
8166
+ const tblPr = objTabOpts.tableStyle ? `<a:tblPr${tblPrAttrs}><a:tableStyleId>${objTabOpts.tableStyle}</a:tableStyleId></a:tblPr>` : `<a:tblPr${tblPrAttrs}/>`;
8167
+ strXml += `<a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table"><a:tbl>${tblPr}`;
8168
+ }
7477
8169
  if (Array.isArray(objTabOpts.colW)) {
7478
8170
  strXml += "<a:tblGrid>";
7479
8171
  for (let col = 0; col < intColCnt; col++) {
@@ -7499,7 +8191,8 @@ function slideObjectToXml(slide) {
7499
8191
  return {
7500
8192
  _type: "tablecell",
7501
8193
  options: { rowspan },
7502
- _hmerge: true
8194
+ _hmerge: true,
8195
+ _spanOrigin: cell
7503
8196
  };
7504
8197
  });
7505
8198
  cells.splice(cIdx + 1, 0, ...vMergeCells);
@@ -7515,12 +8208,14 @@ function slideObjectToXml(slide) {
7515
8208
  const colspan = cell.options?.colspan;
7516
8209
  const _hmerge = cell._hmerge;
7517
8210
  if (rowspan && rowspan > 1) {
8211
+ const _spanOrigin = cell._spanOrigin || cell;
7518
8212
  const hMergeCell = {
7519
8213
  _type: "tablecell",
7520
8214
  options: { colspan },
7521
8215
  _rowContinue: rowspan - 1,
7522
8216
  _vmerge: true,
7523
- _hmerge
8217
+ _hmerge,
8218
+ _spanOrigin
7524
8219
  };
7525
8220
  nextRow.splice(cIdx, 0, hMergeCell);
7526
8221
  }
@@ -7530,7 +8225,7 @@ function slideObjectToXml(slide) {
7530
8225
  let intRowH = 0;
7531
8226
  if (Array.isArray(objTabOpts.rowH) && objTabOpts.rowH[rIdx]) intRowH = inch2Emu(Number(objTabOpts.rowH[rIdx]));
7532
8227
  else if (objTabOpts.rowH && !isNaN(Number(objTabOpts.rowH))) intRowH = inch2Emu(Number(objTabOpts.rowH));
7533
- 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);
8228
+ else if (slideItemObj.options.cy || slideItemObj.options.h) intRowH = Math.round((slideItemObj.options.h ? cy : typeof slideItemObj.options.cy === "number" ? slideItemObj.options.cy : 1) / arrTabRows.length);
7534
8229
  strXml += `<a:tr h="${intRowH}">`;
7535
8230
  cells.forEach((cellObj) => {
7536
8231
  const cell = cellObj;
@@ -7543,7 +8238,17 @@ function slideObjectToXml(slide) {
7543
8238
  let cellSpanAttrStr = Object.entries(cellSpanAttrs).filter(([, v]) => !!v).map(([k, v]) => `${String(k)}="${String(v)}"`).join(" ");
7544
8239
  if (cellSpanAttrStr) cellSpanAttrStr = " " + cellSpanAttrStr;
7545
8240
  if (cell._hmerge || cell._vmerge) {
7546
- strXml += `<a:tc${cellSpanAttrStr}><a:tcPr/></a:tc>`;
8241
+ const origin = cell._spanOrigin;
8242
+ let spanPrXml = "";
8243
+ if (origin) {
8244
+ const originOpts = origin.options || {};
8245
+ const originBorder = Array.isArray(originOpts.border) ? originOpts.border : null;
8246
+ if (originBorder) spanPrXml += genTableCellBorderXml(originBorder);
8247
+ let spanFill = origin._optImp?.fill?.color ? origin._optImp.fill.color : origin._optImp?.fill && typeof origin._optImp.fill === "string" ? origin._optImp.fill : "";
8248
+ spanFill = spanFill || originOpts.fill ? originOpts.fill : "";
8249
+ if (spanFill) spanPrXml += genXmlColorSelection(spanFill);
8250
+ }
8251
+ strXml += `<a:tc${cellSpanAttrStr}><a:tcPr>${spanPrXml}</a:tcPr></a:tc>`;
7547
8252
  return;
7548
8253
  }
7549
8254
  const cellOpts = cell.options || {};
@@ -7587,32 +8292,7 @@ function slideObjectToXml(slide) {
7587
8292
  else cellMarginXml = ` marL="${inch2Emu(cellMargin[3])}" marR="${inch2Emu(cellMargin[1])}" marT="${inch2Emu(cellMargin[0])}" marB="${inch2Emu(cellMargin[2])}"`;
7588
8293
  strXml += `<a:tc${cellSpanAttrStr}>${genXmlTextBody(cell)}<a:tcPr${cellMarginXml}${cellValign}${cellTextDir}>`;
7589
8294
  const cellBorder = Array.isArray(cellOpts.border) ? cellOpts.border : null;
7590
- if (cellBorder) [
7591
- {
7592
- idx: 3,
7593
- name: "lnL"
7594
- },
7595
- {
7596
- idx: 1,
7597
- name: "lnR"
7598
- },
7599
- {
7600
- idx: 0,
7601
- name: "lnT"
7602
- },
7603
- {
7604
- idx: 2,
7605
- name: "lnB"
7606
- }
7607
- ].forEach((obj) => {
7608
- const border = cellBorder[obj.idx];
7609
- if (border.type !== "none") {
7610
- strXml += `<a:${obj.name} w="${valToPts(border.pt)}" cap="flat" cmpd="sng" algn="ctr">`;
7611
- strXml += `<a:solidFill>${createColorElement(border.color)}</a:solidFill>`;
7612
- strXml += `<a:prstDash val="${border.type === "dash" ? "sysDash" : "solid"}"/><a:round/><a:headEnd type="none" w="med" len="med"/><a:tailEnd type="none" w="med" len="med"/>`;
7613
- strXml += `</a:${obj.name}>`;
7614
- } else strXml += `<a:${obj.name} w="0" cap="flat" cmpd="sng" algn="ctr"><a:noFill/></a:${obj.name}>`;
7615
- });
8295
+ if (cellBorder) strXml += genTableCellBorderXml(cellBorder);
7616
8296
  strXml += cellFill;
7617
8297
  strXml += " </a:tcPr>";
7618
8298
  strXml += " </a:tc>";
@@ -7631,10 +8311,10 @@ function slideObjectToXml(slide) {
7631
8311
  if (!slideItemObj.options.line && cy === 0) cy = EMU * .3;
7632
8312
  if (!slideItemObj.options._bodyProp) slideItemObj.options._bodyProp = {};
7633
8313
  if (slideItemObj.options.margin && Array.isArray(slideItemObj.options.margin)) {
7634
- slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin[0] || 0);
8314
+ slideItemObj.options._bodyProp.tIns = valToPts(slideItemObj.options.margin[0] || 0);
7635
8315
  slideItemObj.options._bodyProp.rIns = valToPts(slideItemObj.options.margin[1] || 0);
7636
8316
  slideItemObj.options._bodyProp.bIns = valToPts(slideItemObj.options.margin[2] || 0);
7637
- slideItemObj.options._bodyProp.tIns = valToPts(slideItemObj.options.margin[3] || 0);
8317
+ slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin[3] || 0);
7638
8318
  } else if (typeof slideItemObj.options.margin === "number") {
7639
8319
  slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin);
7640
8320
  slideItemObj.options._bodyProp.rIns = valToPts(slideItemObj.options.margin);
@@ -7646,7 +8326,11 @@ function slideObjectToXml(slide) {
7646
8326
  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) : ""}"/>`;
7647
8327
  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"/>`;
7648
8328
  strSlideXml += "</p:cNvPr>";
7649
- strSlideXml += "<p:cNvSpPr" + (slideItemObj.options?.isTextBox ? " txBox=\"1\"/>" : "/>");
8329
+ {
8330
+ const spLockXml = genXmlObjectLock("a:spLocks", SHAPE_LOCK_ATTRS, slideItemObj.options.objectLock, slideItemObj.options.objectName);
8331
+ strSlideXml += "<p:cNvSpPr" + (slideItemObj.options?.isTextBox ? " txBox=\"1\"" : "");
8332
+ strSlideXml += spLockXml ? `>${spLockXml}</p:cNvSpPr>` : "/>";
8333
+ }
7650
8334
  strSlideXml += `<p:nvPr>${slideItemObj._type === "placeholder" ? genXmlPlaceholder(slideItemObj) : genXmlPlaceholder(placeholderObj)}</p:nvPr>`;
7651
8335
  strSlideXml += "</p:nvSpPr><p:spPr>";
7652
8336
  strSlideXml += `<a:xfrm${locationAttr}>`;
@@ -7656,7 +8340,8 @@ function slideObjectToXml(slide) {
7656
8340
  else strSlideXml += genXmlPresetGeom(slideItemObj.shape, slideItemObj.options, cx, cy);
7657
8341
  strSlideXml += slideItemObj.options.fill ? genXmlColorSelection(slideItemObj.options.fill) : "<a:noFill/>";
7658
8342
  if (slideItemObj.options.line) {
7659
- strSlideXml += slideItemObj.options.line.width ? `<a:ln w="${valToPts(slideItemObj.options.line.width)}">` : "<a:ln>";
8343
+ const lnAttrs = (slideItemObj.options.line.width ? ` w="${lineWidthToEmu(slideItemObj.options.line.width)}"` : "") + (slideItemObj.options.line.cap ? ` cap="${createLineCap(slideItemObj.options.line.cap)}"` : "");
8344
+ strSlideXml += `<a:ln${lnAttrs}>`;
7660
8345
  if (slideItemObj.options.line.color) strSlideXml += genXmlColorSelection(slideItemObj.options.line);
7661
8346
  if (slideItemObj.options.line.dashType) strSlideXml += `<a:prstDash val="${slideItemObj.options.line.dashType}"/>`;
7662
8347
  if (slideItemObj.options.line.beginArrowType) strSlideXml += `<a:headEnd type="${slideItemObj.options.line.beginArrowType}"/>`;
@@ -7689,13 +8374,17 @@ function slideObjectToXml(slide) {
7689
8374
  if (slideItemObj.hyperlink?.url) strSlideXml += `<a:hlinkClick r:id="rId${slideItemObj.hyperlink._rId}" tooltip="${slideItemObj.hyperlink.tooltip ? encodeXmlEntities(slideItemObj.hyperlink.tooltip) : ""}"/>`;
7690
8375
  if (slideItemObj.hyperlink?.slide) strSlideXml += `<a:hlinkClick r:id="rId${slideItemObj.hyperlink._rId}" tooltip="${slideItemObj.hyperlink.tooltip ? encodeXmlEntities(slideItemObj.hyperlink.tooltip) : ""}" action="ppaction://hlinksldjump"/>`;
7691
8376
  strSlideXml += " </p:cNvPr>";
7692
- strSlideXml += " <p:cNvPicPr><a:picLocks noChangeAspect=\"1\"/></p:cNvPicPr>";
8377
+ strSlideXml += ` <p:cNvPicPr>${genXmlObjectLock("a:picLocks", PICTURE_LOCK_ATTRS, {
8378
+ noChangeAspect: true,
8379
+ ...slideItemObj.options.objectLock
8380
+ }, slideItemObj.options.objectName)}</p:cNvPicPr>`;
7693
8381
  strSlideXml += " <p:nvPr>" + genXmlPlaceholder(placeholderObj) + "</p:nvPr>";
7694
8382
  strSlideXml += " </p:nvPicPr>";
7695
8383
  strSlideXml += "<p:blipFill>";
7696
8384
  if ((slide._relsMedia || []).find((rel) => rel.rId === slideItemObj.imageRid)?.extn === "svg") {
7697
8385
  strSlideXml += `<a:blip r:embed="rId${slideItemObj.imageRid - 1}">`;
7698
8386
  strSlideXml += slideItemObj.options.transparency ? ` <a:alphaModFix amt="${Math.round((100 - slideItemObj.options.transparency) * 1e3)}"/>` : "";
8387
+ strSlideXml += slideItemObj.options.duotone ? `<a:duotone>${createColorElement(slideItemObj.options.duotone.shadow)}${createColorElement(slideItemObj.options.duotone.highlight)}</a:duotone>` : "";
7699
8388
  strSlideXml += " <a:extLst>";
7700
8389
  strSlideXml += " <a:ext uri=\"{96DAC541-7B7A-43D3-8B79-37D633B846F1}\">";
7701
8390
  strSlideXml += ` <asvg:svgBlip xmlns:asvg="http://schemas.microsoft.com/office/drawing/2016/SVG/main" r:embed="rId${slideItemObj.imageRid}"/>`;
@@ -7705,6 +8394,7 @@ function slideObjectToXml(slide) {
7705
8394
  } else {
7706
8395
  strSlideXml += `<a:blip r:embed="rId${slideItemObj.imageRid}">`;
7707
8396
  strSlideXml += slideItemObj.options.transparency ? `<a:alphaModFix amt="${Math.round((100 - slideItemObj.options.transparency) * 1e3)}"/>` : "";
8397
+ strSlideXml += slideItemObj.options.duotone ? `<a:duotone>${createColorElement(slideItemObj.options.duotone.shadow)}${createColorElement(slideItemObj.options.duotone.highlight)}</a:duotone>` : "";
7708
8398
  strSlideXml += "</a:blip>";
7709
8399
  }
7710
8400
  if (sizing?.type) {
@@ -7712,10 +8402,17 @@ function slideObjectToXml(slide) {
7712
8402
  const boxH = sizing.h ? getSmartParseNumber(sizing.h, "Y", slide._presLayout) : cy;
7713
8403
  const boxX = getSmartParseNumber(sizing.x || 0, "X", slide._presLayout);
7714
8404
  const boxY = getSmartParseNumber(sizing.y || 0, "Y", slide._presLayout);
7715
- strSlideXml += ImageSizingXml[sizing.type]({
8405
+ let cropSize = {
7716
8406
  w: imgWidth,
7717
8407
  h: imgHeight
7718
- }, {
8408
+ };
8409
+ if (sizing.type === "cover" || sizing.type === "contain") {
8410
+ const relData = (slide._relsMedia || []).find((rel) => rel.rId === slideItemObj.imageRid)?.data;
8411
+ const natural = typeof relData === "string" ? getImageSizeFromBase64(relData) : null;
8412
+ if (natural) cropSize = natural;
8413
+ else console.warn(`Warning: sizing '${sizing.type}' could not measure natural dimensions for image "${slideItemObj.options.objectName}"; falling back to displayed aspect ratio (crop may be inexact). Provide a raster image (PNG/JPEG/GIF/BMP/WebP) to enable an aspect-correct crop.`);
8414
+ }
8415
+ strSlideXml += ImageSizingXml[sizing.type](cropSize, {
7719
8416
  w: boxW,
7720
8417
  h: boxH,
7721
8418
  x: boxX,
@@ -7755,7 +8452,7 @@ function slideObjectToXml(slide) {
7755
8452
  strSlideXml += "<p:pic>";
7756
8453
  strSlideXml += " <p:nvPicPr>";
7757
8454
  strSlideXml += `<p:cNvPr id="${slideItemObj.mediaRid + 2}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || "")}"/>`;
7758
- strSlideXml += " <p:cNvPicPr/>";
8455
+ strSlideXml += ` <p:cNvPicPr>${genXmlObjectLock("a:picLocks", PICTURE_LOCK_ATTRS, slideItemObj.options.objectLock, slideItemObj.options.objectName)}</p:cNvPicPr>`;
7759
8456
  strSlideXml += " <p:nvPr>";
7760
8457
  strSlideXml += ` <a:videoFile r:link="rId${slideItemObj.mediaRid}"/>`;
7761
8458
  strSlideXml += " </p:nvPr>";
@@ -7770,7 +8467,10 @@ function slideObjectToXml(slide) {
7770
8467
  strSlideXml += "<p:pic>";
7771
8468
  strSlideXml += " <p:nvPicPr>";
7772
8469
  strSlideXml += `<p:cNvPr id="${slideItemObj.mediaRid + 2}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || "")}"><a:hlinkClick r:id="" action="ppaction://media"/></p:cNvPr>`;
7773
- strSlideXml += " <p:cNvPicPr><a:picLocks noChangeAspect=\"1\"/></p:cNvPicPr>";
8470
+ strSlideXml += ` <p:cNvPicPr>${genXmlObjectLock("a:picLocks", PICTURE_LOCK_ATTRS, {
8471
+ noChangeAspect: true,
8472
+ ...slideItemObj.options.objectLock
8473
+ }, slideItemObj.options.objectName)}</p:cNvPicPr>`;
7774
8474
  strSlideXml += " <p:nvPr>";
7775
8475
  strSlideXml += ` <a:videoFile r:link="rId${slideItemObj.mediaRid}"/>`;
7776
8476
  strSlideXml += " <p:extLst>";
@@ -7834,7 +8534,7 @@ function slideObjectToXml(slide) {
7834
8534
  strSlideXml += "/>";
7835
8535
  strSlideXml += " <a:lstStyle><a:lvl1pPr>";
7836
8536
  if (slide._slideNumberProps.fontFace || slide._slideNumberProps.fontSize || slide._slideNumberProps.color) {
7837
- strSlideXml += `<a:defRPr sz="${Math.round((slide._slideNumberProps.fontSize || 12) * 100)}">`;
8537
+ strSlideXml += `<a:defRPr sz="${clampFontSizeSz(slide._slideNumberProps.fontSize || 12)}">`;
7838
8538
  if (slide._slideNumberProps.color) strSlideXml += genXmlColorSelection(slide._slideNumberProps.color);
7839
8539
  if (slide._slideNumberProps.fontFace) strSlideXml += `<a:latin typeface="${slide._slideNumberProps.fontFace}"/><a:ea typeface="${slide._slideNumberProps.fontFace}"/><a:cs typeface="${slide._slideNumberProps.fontFace}"/>`;
7840
8540
  strSlideXml += "</a:defRPr>";
@@ -7923,7 +8623,7 @@ function genXmlParagraphProperties(textObj, isDefault) {
7923
8623
  paragraphPropXml += "";
7924
8624
  break;
7925
8625
  }
7926
- if (textObj.options.lineSpacing) strXmlLnSpc = `<a:lnSpc><a:spcPts val="${Math.round(textObj.options.lineSpacing * 100)}"/></a:lnSpc>`;
8626
+ if (textObj.options.lineSpacing) strXmlLnSpc = `<a:lnSpc><a:spcPts val="${clampLineSpacingPts(textObj.options.lineSpacing)}"/></a:lnSpc>`;
7927
8627
  else if (textObj.options.lineSpacingMultiple) strXmlLnSpc = `<a:lnSpc><a:spcPct val="${Math.round(textObj.options.lineSpacingMultiple * 1e5)}"/></a:lnSpc>`;
7928
8628
  if (textObj.options.indentLevel && !isNaN(Number(textObj.options.indentLevel)) && textObj.options.indentLevel > 0) paragraphPropXml += ` lvl="${textObj.options.indentLevel}"`;
7929
8629
  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>`;
@@ -7977,7 +8677,7 @@ function genXmlTextRunProperties(opts, isDefault) {
7977
8677
  let runProps = "";
7978
8678
  const runPropsTag = isDefault ? "a:defRPr" : "a:rPr";
7979
8679
  runProps += "<" + runPropsTag + " lang=\"" + (opts.lang ? opts.lang : "en-US") + "\"" + (opts.lang ? " altLang=\"en-US\"" : "");
7980
- runProps += opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : "";
8680
+ runProps += opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "";
7981
8681
  runProps += opts?.bold ? ` b="${opts.bold ? "1" : "0"}"` : "";
7982
8682
  runProps += opts?.italic ? ` i="${opts.italic ? "1" : "0"}"` : "";
7983
8683
  runProps += opts?.strike ? ` strike="${typeof opts.strike === "string" ? opts.strike : "sngStrike"}"` : "";
@@ -7988,17 +8688,23 @@ function genXmlTextRunProperties(opts, isDefault) {
7988
8688
  if (opts.baseline) runProps += ` baseline="${Math.round(opts.baseline * 50)}"`;
7989
8689
  else if (opts.subscript) runProps += " baseline=\"-40000\"";
7990
8690
  else if (opts.superscript) runProps += " baseline=\"30000\"";
7991
- runProps += opts.charSpacing ? ` spc="${Math.round(opts.charSpacing * 100)}" kern="0"` : "";
8691
+ runProps += opts.charSpacing ? ` spc="${clampCharSpacingSpc(opts.charSpacing)}" kern="0"` : "";
7992
8692
  runProps += " dirty=\"0\">";
7993
- if (opts.color || opts.fontFace || opts.outline || typeof opts.underline === "object" && opts.underline.color) {
7994
- if (opts.outline && typeof opts.outline === "object") runProps += `<a:ln w="${valToPts(opts.outline.size || .75)}">${genXmlColorSelection(opts.outline.color || "FFFFFF")}</a:ln>`;
8693
+ const hasShadow = !!opts.shadow && opts.shadow.type !== "none";
8694
+ if (opts.color || opts.fontFace || opts.outline || opts.glow || hasShadow || typeof opts.underline === "object" && opts.underline.color) {
8695
+ if (opts.outline && typeof opts.outline === "object") runProps += `<a:ln w="${lineWidthToEmu(opts.outline.size || .75)}">${genXmlColorSelection(opts.outline.color || "FFFFFF")}</a:ln>`;
7995
8696
  if (opts.color) runProps += genXmlColorSelection({
7996
8697
  color: opts.color,
7997
8698
  transparency: opts.transparency
7998
8699
  });
8700
+ if (opts.glow || hasShadow) {
8701
+ runProps += "<a:effectLst>";
8702
+ if (opts.glow) runProps += createGlowElement(opts.glow, DEF_TEXT_GLOW);
8703
+ if (hasShadow) runProps += createShadowElement$1(opts.shadow, DEF_TEXT_SHADOW);
8704
+ runProps += "</a:effectLst>";
8705
+ }
7999
8706
  if (opts.highlight) runProps += `<a:highlight>${createColorElement(opts.highlight)}</a:highlight>`;
8000
8707
  if (typeof opts.underline === "object" && opts.underline.color) runProps += `<a:uFill>${genXmlColorSelection(opts.underline.color)}</a:uFill>`;
8001
- if (opts.glow) runProps += `<a:effectLst>${createGlowElement(opts.glow, DEF_TEXT_GLOW)}</a:effectLst>`;
8002
8708
  if (opts.fontFace) 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"/>`;
8003
8709
  }
8004
8710
  if (opts.hyperlink) {
@@ -8028,6 +8734,28 @@ function genXmlTextRun(textObj) {
8028
8734
  return `<a:r>${genXmlTextRunProperties(textObj.options, false)}<a:t>${encodeXmlEntities(String(textObj.text))}</a:t></a:r>`;
8029
8735
  }
8030
8736
  /**
8737
+ * Builds `<a:normAutofit>` with explicit fontScale/lnSpcReduction for "shrink text on overflow"
8738
+ * @param {TextFitShrinkProps} fit - shrink fit options
8739
+ * @return {string} XML string (`<a:normAutofit .../>`)
8740
+ * @see ECMA-376 CT_TextNormAutofit (attributes in 1000ths of a percent)
8741
+ */
8742
+ function genXmlNormAutofit(fit) {
8743
+ let attrs = "";
8744
+ const pct = (val, name) => {
8745
+ if (val === void 0 || val === null) return null;
8746
+ if (typeof val !== "number" || isNaN(val) || val < 0 || val > 100) {
8747
+ console.warn(`Warning: fit.${name} must be a number between 0 and 100 (percent); received ${String(val)} - attribute ignored.`);
8748
+ return null;
8749
+ }
8750
+ return Math.round(val * 1e3);
8751
+ };
8752
+ const fontScale = pct(fit.fontScale, "fontScale");
8753
+ if (fontScale !== null) attrs += ` fontScale="${fontScale}"`;
8754
+ const lnSpcReduction = pct(fit.lnSpcReduction, "lnSpcReduction");
8755
+ if (lnSpcReduction !== null) attrs += ` lnSpcReduction="${lnSpcReduction}"`;
8756
+ return `<a:normAutofit${attrs}/>`;
8757
+ }
8758
+ /**
8031
8759
  * Builds `<a:bodyPr></a:bodyPr>` tag for "genXmlTextBody()"
8032
8760
  * @param {ISlideObject | TableCell} slideObject - various options
8033
8761
  * @return {string} XML string
@@ -8040,6 +8768,8 @@ function genXmlBodyProperties(slideObject) {
8040
8768
  if (slideObject.options._bodyProp.tIns || slideObject.options._bodyProp.tIns === 0) bodyProperties += ` tIns="${slideObject.options._bodyProp.tIns}"`;
8041
8769
  if (slideObject.options._bodyProp.rIns || slideObject.options._bodyProp.rIns === 0) bodyProperties += ` rIns="${slideObject.options._bodyProp.rIns}"`;
8042
8770
  if (slideObject.options._bodyProp.bIns || slideObject.options._bodyProp.bIns === 0) bodyProperties += ` bIns="${slideObject.options._bodyProp.bIns}"`;
8771
+ if (slideObject.options._bodyProp.numCol) bodyProperties += ` numCol="${slideObject.options._bodyProp.numCol}"`;
8772
+ if (slideObject.options._bodyProp.spcCol) bodyProperties += ` spcCol="${slideObject.options._bodyProp.spcCol}"`;
8043
8773
  bodyProperties += " rtlCol=\"0\"";
8044
8774
  if (slideObject.options._bodyProp.anchor) bodyProperties += " anchor=\"" + slideObject.options._bodyProp.anchor + "\"";
8045
8775
  if (slideObject.options._bodyProp.vert) bodyProperties += " vert=\"" + slideObject.options._bodyProp.vert + "\"";
@@ -8050,9 +8780,11 @@ function genXmlBodyProperties(slideObject) {
8050
8780
  * @see: http://www.datypic.com/sc/ooxml/g-a_EG_TextAutofit.html
8051
8781
  */
8052
8782
  if (slideObject.options.fit) {
8053
- if (slideObject.options.fit === "none") bodyProperties += "";
8054
- else if (slideObject.options.fit === "shrink") bodyProperties += "<a:normAutofit/>";
8055
- else if (slideObject.options.fit === "resize") bodyProperties += "<a:spAutoFit/>";
8783
+ const fit = slideObject.options.fit;
8784
+ if (fit === "none") bodyProperties += "";
8785
+ else if (fit === "shrink") bodyProperties += "<a:normAutofit/>";
8786
+ else if (fit === "resize") bodyProperties += "<a:spAutoFit/>";
8787
+ else if (typeof fit === "object" && fit.type === "shrink") bodyProperties += genXmlNormAutofit(fit);
8056
8788
  }
8057
8789
  if (slideObject.options.shrinkText) bodyProperties += "<a:normAutofit/>";
8058
8790
  bodyProperties += slideObject.options._bodyProp.autoFit ? "<a:spAutoFit/>" : "";
@@ -8111,14 +8843,19 @@ function genXmlTextBody(slideObj) {
8111
8843
  itext.options = itext.options || opts || {};
8112
8844
  if (idx === 0 && itext.options && !itext.options.bullet && opts.bullet) itext.options.bullet = opts.bullet;
8113
8845
  if (typeof itext.text === "string" || typeof itext.text === "number") itext.text = itext.text.toString().replace(/\r*\n/g, "\r\n");
8114
- if (itext.text.includes("\r\n") && itext.text.match(/\n$/g) === null) itext.text.split("\r\n").forEach((line) => {
8115
- itext.options.breakLine = true;
8116
- arrTextObjects.push({
8117
- text: line,
8118
- options: itext.options
8846
+ if (itext.text.includes("\r\n") && itext.text.match(/\n$/g) === null) {
8847
+ const lines = itext.text.split("\r\n");
8848
+ lines.forEach((line, lineIdx) => {
8849
+ const isLast = lineIdx === lines.length - 1;
8850
+ arrTextObjects.push({
8851
+ text: line,
8852
+ options: {
8853
+ ...itext.options,
8854
+ breakLine: isLast ? itext.options.breakLine : true
8855
+ }
8856
+ });
8119
8857
  });
8120
- });
8121
- else arrTextObjects.push(itext);
8858
+ } else arrTextObjects.push(itext);
8122
8859
  });
8123
8860
  const arrLines = [];
8124
8861
  let arrTexts = [];
@@ -8183,13 +8920,13 @@ function genXmlTextBody(slideObj) {
8183
8920
  }
8184
8921
  });
8185
8922
  if (slideObj._type === "tablecell" && (opts.fontSize || opts.fontFace)) if (opts.fontFace) {
8186
- strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : "") + " dirty=\"0\">";
8923
+ strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "") + " dirty=\"0\">";
8187
8924
  strSlideXml += `<a:latin typeface="${opts.fontFace}" charset="0"/>`;
8188
8925
  strSlideXml += `<a:ea typeface="${opts.fontFace}" charset="0"/>`;
8189
8926
  strSlideXml += `<a:cs typeface="${opts.fontFace}" charset="0"/>`;
8190
8927
  strSlideXml += "</a:endParaRPr>";
8191
- } else strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : "") + " dirty=\"0\"/>";
8192
- else if (reqsClosingFontSize) strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : "") + " dirty=\"0\"/>";
8928
+ } else strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "") + " dirty=\"0\"/>";
8929
+ else if (reqsClosingFontSize) strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "") + " dirty=\"0\"/>";
8193
8930
  else strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}" dirty="0"/>`;
8194
8931
  strSlideXml += "</a:p>";
8195
8932
  });
@@ -8220,7 +8957,7 @@ function genXmlPlaceholder(placeholderObj) {
8220
8957
  * @param {PresSlideInternal} masterSlide - master slide
8221
8958
  * @returns XML
8222
8959
  */
8223
- function makeXmlContTypes(slides, slideLayouts, masterSlide) {
8960
+ function makeXmlContTypes(slides, slideLayouts, masterSlide, hasCustomProps) {
8224
8961
  let strXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n";
8225
8962
  strXml += "<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">";
8226
8963
  strXml += "<Default Extension=\"xml\" ContentType=\"application/xml\"/>";
@@ -8270,6 +9007,7 @@ function makeXmlContTypes(slides, slideLayouts, masterSlide) {
8270
9007
  });
8271
9008
  strXml += " <Override PartName=\"/docProps/core.xml\" ContentType=\"application/vnd.openxmlformats-package.core-properties+xml\"/>";
8272
9009
  strXml += " <Override PartName=\"/docProps/app.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.extended-properties+xml\"/>";
9010
+ if (hasCustomProps) strXml += " <Override PartName=\"/docProps/custom.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.custom-properties+xml\"/>";
8273
9011
  strXml += "</Types>";
8274
9012
  return strXml;
8275
9013
  }
@@ -8277,13 +9015,15 @@ function makeXmlContTypes(slides, slideLayouts, masterSlide) {
8277
9015
  * Creates `_rels/.rels`
8278
9016
  * @returns XML
8279
9017
  */
8280
- function makeXmlRootRels() {
8281
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
9018
+ function makeXmlRootRels(hasCustomProps) {
9019
+ let xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
8282
9020
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
8283
9021
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
8284
9022
  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
8285
- <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="ppt/presentation.xml"/>
8286
- </Relationships>`;
9023
+ <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="ppt/presentation.xml"/>`;
9024
+ if (hasCustomProps) xml += "\n <Relationship Id=\"rId4\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties\" Target=\"docProps/custom.xml\"/>";
9025
+ xml += "\n </Relationships>";
9026
+ return xml;
8287
9027
  }
8288
9028
  /**
8289
9029
  * Creates `docProps/app.xml`
@@ -8349,6 +9089,23 @@ function makeXmlCore(title, subject, author, revision) {
8349
9089
  <dcterms:modified xsi:type="dcterms:W3CDTF">${(/* @__PURE__ */ new Date()).toISOString().replace(/\.\d\d\dZ/, "Z")}</dcterms:modified>
8350
9090
  </cp:coreProperties>`;
8351
9091
  }
9092
+ const CUSTOM_PROPS_FMTID = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
9093
+ /**
9094
+ * Creates `docProps/custom.xml`
9095
+ * @param props - custom property name/value pairs
9096
+ * @returns XML
9097
+ */
9098
+ function makeXmlCustomProperties(props) {
9099
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
9100
+ <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">${props.map(({ name, value }, idx) => {
9101
+ let valueXml;
9102
+ if (typeof value === "boolean") valueXml = `<vt:bool>${value}</vt:bool>`;
9103
+ else if (value instanceof Date) valueXml = `<vt:filetime>${value.toISOString().replace(/\.\d{3}Z$/, "Z")}</vt:filetime>`;
9104
+ else if (typeof value === "number") valueXml = Number.isInteger(value) ? `<vt:i4>${value}</vt:i4>` : `<vt:r8>${value}</vt:r8>`;
9105
+ else valueXml = `<vt:lpwstr>${encodeXmlEntities(String(value))}</vt:lpwstr>`;
9106
+ return `<property fmtid="${CUSTOM_PROPS_FMTID}" pid="${idx + 2}" name="${encodeXmlEntities(name)}">${valueXml}</property>`;
9107
+ }).join("")}</Properties>`;
9108
+ }
8352
9109
  /**
8353
9110
  * Creates `ppt/_rels/presentation.xml.rels`
8354
9111
  * @param {PresSlideInternal[]} slides - Presenation Slides
@@ -8525,7 +9282,7 @@ function makeXmlTheme(pres) {
8525
9282
  */
8526
9283
  function makeXmlPresentation(pres) {
8527
9284
  let strXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
8528
- <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">`;
9285
+ <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"${pres.firstSlideNum !== 1 ? ` firstSlideNum="${pres.firstSlideNum}"` : ""}>`;
8529
9286
  strXml += "<p:sldMasterIdLst><p:sldMasterId id=\"2147483648\" r:id=\"rId1\"/></p:sldMasterIdLst>";
8530
9287
  strXml += `<p:notesMasterIdLst><p:notesMasterId r:id="rId${pres.slides.length + 2}"/></p:notesMasterIdLst>`;
8531
9288
  strXml += "<p:sldIdLst>";
@@ -8564,9 +9321,96 @@ function makeXmlPresProps() {
8564
9321
  * @see: http://openxmldeveloper.org/discussions/formats/f/13/p/2398/8107.aspx
8565
9322
  * @return {string} XML
8566
9323
  */
8567
- function makeXmlTableStyles() {
8568
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
8569
- <a:tblStyleLst xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" def="{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}"/>`;
9324
+ function makeXmlTableStyles(tableStyles = []) {
9325
+ const open = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
9326
+ <a:tblStyleLst xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" def="{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}"`;
9327
+ if (!tableStyles || tableStyles.length === 0) return `${open}/>`;
9328
+ let strXml = `${open}>`;
9329
+ tableStyles.forEach(({ guid, def }) => {
9330
+ strXml += `<a:tblStyle styleId="${guid}" styleName="${encodeXmlEntities(def.name)}">`;
9331
+ [
9332
+ ["wholeTbl", def.wholeTbl],
9333
+ ["band1H", def.band1H],
9334
+ ["band2H", def.band2H],
9335
+ ["band1V", def.band1V],
9336
+ ["band2V", def.band2V],
9337
+ ["lastCol", def.lastCol],
9338
+ ["firstCol", def.firstCol],
9339
+ ["lastRow", def.lastRow],
9340
+ ["firstRow", def.firstRow]
9341
+ ].forEach(([name, region]) => {
9342
+ if (region) strXml += genXmlTableStyleRegion(name, region);
9343
+ });
9344
+ strXml += "</a:tblStyle>";
9345
+ });
9346
+ strXml += "</a:tblStyleLst>";
9347
+ return strXml;
9348
+ }
9349
+ /**
9350
+ * Build one `CT_TablePartStyle` region (e.g. `firstRow`, `band1H`) for a custom table style.
9351
+ * Emits `tcTxStyle` (text) before `tcStyle` (cell fill/borders) per the schema sequence.
9352
+ * @param {string} name - region element name
9353
+ * @param {TableStyleRegionProps} region - region styling
9354
+ * @return {string} XML
9355
+ */
9356
+ function genXmlTableStyleRegion(name, region) {
9357
+ let xml = `<a:${name}>`;
9358
+ if (region.bold !== void 0 || region.italic !== void 0 || region.color) {
9359
+ const b = region.bold ? " b=\"on\"" : "";
9360
+ const i = region.italic ? " i=\"on\"" : "";
9361
+ xml += `<a:tcTxStyle${b}${i}><a:fontRef idx="minor"/>`;
9362
+ xml += region.color ? createColorElement(region.color) : "";
9363
+ xml += "</a:tcTxStyle>";
9364
+ }
9365
+ if (region.border !== void 0 || region.fill !== void 0) {
9366
+ xml += "<a:tcStyle>";
9367
+ if (region.border !== void 0) xml += genXmlTableStyleBorders(region.border);
9368
+ if (region.fill !== void 0) xml += `<a:fill><a:solidFill>${createColorElement(region.fill)}</a:solidFill></a:fill>`;
9369
+ xml += "</a:tcStyle>";
9370
+ }
9371
+ xml += `</a:${name}>`;
9372
+ return xml;
9373
+ }
9374
+ /**
9375
+ * Build the `tcBdr` border block for a custom table style region.
9376
+ * A single `BorderProps` styles all four sides plus the interior grid lines; a
9377
+ * TRBL array styles only the four outer sides. Sides are emitted in schema order.
9378
+ * @param {BorderProps | BorderProps[]} border - border definition
9379
+ * @return {string} XML
9380
+ */
9381
+ function genXmlTableStyleBorders(border) {
9382
+ let sides;
9383
+ if (Array.isArray(border)) {
9384
+ const [top, right, bottom, left] = border;
9385
+ sides = [
9386
+ ["left", left],
9387
+ ["right", right],
9388
+ ["top", top],
9389
+ ["bottom", bottom]
9390
+ ];
9391
+ } else sides = [
9392
+ ["left", border],
9393
+ ["right", border],
9394
+ ["top", border],
9395
+ ["bottom", border],
9396
+ ["insideH", border],
9397
+ ["insideV", border]
9398
+ ];
9399
+ let xml = "<a:tcBdr>";
9400
+ sides.forEach(([side, b]) => {
9401
+ if (!b) return;
9402
+ xml += `<a:${side}>`;
9403
+ if (b.type === "none") xml += "<a:ln><a:noFill/></a:ln>";
9404
+ else {
9405
+ xml += `<a:ln w="${lineWidthToEmu(b.pt ?? 1)}" cap="flat" cmpd="sng" algn="ctr">`;
9406
+ xml += `<a:solidFill>${createColorElement(b.color ?? "666666")}</a:solidFill>`;
9407
+ xml += `<a:prstDash val="${b.type === "dash" ? "sysDash" : "solid"}"/>`;
9408
+ xml += "</a:ln>";
9409
+ }
9410
+ xml += `</a:${side}>`;
9411
+ });
9412
+ xml += "</a:tcBdr>";
9413
+ return xml;
8570
9414
  }
8571
9415
  /**
8572
9416
  * Creates `ppt/viewProps.xml`
@@ -8636,7 +9480,7 @@ function makeXmlViewProps() {
8636
9480
  * @see https://docs.microsoft.com/en-us/office/open-xml/structure-of-a-presentationml-document
8637
9481
  * @see https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2010/hh273476(v=office.14)
8638
9482
  */
8639
- const VERSION = "5.1.0";
9483
+ const VERSION = "5.3.0";
8640
9484
  function standardLayoutToPresLayout(layout) {
8641
9485
  return {
8642
9486
  name: layout.name,
@@ -8737,6 +9581,14 @@ var PptxGenJS$1 = class {
8737
9581
  get title() {
8738
9582
  return this._title;
8739
9583
  }
9584
+ /** Slide number shown on the first slide (maps to firstSlideNum in presentation.xml) */
9585
+ _firstSlideNum;
9586
+ set firstSlideNum(value) {
9587
+ this._firstSlideNum = value;
9588
+ }
9589
+ get firstSlideNum() {
9590
+ return this._firstSlideNum;
9591
+ }
8740
9592
  /**
8741
9593
  * Whether Right-to-Left (RTL) mode is enabled
8742
9594
  * @type {boolean}
@@ -8763,6 +9615,9 @@ var PptxGenJS$1 = class {
8763
9615
  get sections() {
8764
9616
  return this._sections;
8765
9617
  }
9618
+ /** custom document properties stored in docProps/custom.xml */
9619
+ _customProperties;
9620
+ _tableStyles;
8766
9621
  /** slide layout definition objects, used for generating slide layout files */
8767
9622
  _slideLayouts;
8768
9623
  get slideLayouts() {
@@ -8772,6 +9627,7 @@ var PptxGenJS$1 = class {
8772
9627
  return {
8773
9628
  author: this.author,
8774
9629
  company: this.company,
9630
+ firstSlideNum: this.firstSlideNum,
8775
9631
  layout: this.layout,
8776
9632
  masterSlide: this._masterSlide,
8777
9633
  presLayout: this.presLayout,
@@ -8856,6 +9712,7 @@ var PptxGenJS$1 = class {
8856
9712
  width: this.LAYOUTS[DEF_PRES_LAYOUT].width,
8857
9713
  height: this.LAYOUTS[DEF_PRES_LAYOUT].height
8858
9714
  };
9715
+ this._firstSlideNum = 1;
8859
9716
  this._rtlMode = false;
8860
9717
  this._slideLayouts = [{
8861
9718
  _margin: DEF_SLIDE_MARGIN_IN,
@@ -8871,6 +9728,8 @@ var PptxGenJS$1 = class {
8871
9728
  }];
8872
9729
  this._slides = [];
8873
9730
  this._sections = [];
9731
+ this._customProperties = [];
9732
+ this._tableStyles = [];
8874
9733
  this._masterSlide = {
8875
9734
  addChart: null,
8876
9735
  addImage: null,
@@ -8899,7 +9758,8 @@ var PptxGenJS$1 = class {
8899
9758
  */
8900
9759
  addNewSlide = (options) => {
8901
9760
  const nextOptions = options || {};
8902
- nextOptions.sectionTitle = this._sections.length > 0 && this._sections[this._sections.length - 1]._slides.some((slide) => slide._slideNum === this._slides[this._slides.length - 1]._slideNum) ? this._sections[this._sections.length - 1].title : null;
9761
+ const lastSlide = this._slides[this._slides.length - 1];
9762
+ nextOptions.sectionTitle = this._sections.find((sect) => sect._slides.some((s) => s._slideNum === lastSlide._slideNum))?.title ?? null;
8903
9763
  return this.addSlide(nextOptions);
8904
9764
  };
8905
9765
  /**
@@ -8952,6 +9812,18 @@ var PptxGenJS$1 = class {
8952
9812
  });
8953
9813
  arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(this._masterSlide, this._runtime));
8954
9814
  return await Promise.all(arrMediaPromises).then(async () => {
9815
+ const canonicalMediaTargets = /* @__PURE__ */ new Map();
9816
+ for (const target of [
9817
+ ...this._slides,
9818
+ ...this._slideLayouts,
9819
+ this._masterSlide
9820
+ ]) for (const rel of target._relsMedia || []) {
9821
+ if (rel.type === "online" || rel.type === "hyperlink" || typeof rel.data !== "string" || !rel.data) continue;
9822
+ const key = (rel.extn || "") + "\0" + rel.data;
9823
+ const canonical = canonicalMediaTargets.get(key);
9824
+ if (canonical) rel.Target = canonical;
9825
+ else canonicalMediaTargets.set(key, rel.Target);
9826
+ }
8955
9827
  this._slides.forEach((slide) => {
8956
9828
  if (slide._slideLayout) addPlaceholdersToSlideLayouts(slide);
8957
9829
  });
@@ -8969,16 +9841,18 @@ var PptxGenJS$1 = class {
8969
9841
  zip.folder("ppt/theme");
8970
9842
  zip.folder("ppt/notesMasters").folder("_rels");
8971
9843
  zip.folder("ppt/notesSlides").folder("_rels");
8972
- zip.file("[Content_Types].xml", makeXmlContTypes(this._slides, this._slideLayouts, this._masterSlide));
8973
- zip.file("_rels/.rels", makeXmlRootRels());
9844
+ const hasCustomProps = this._customProperties.length > 0;
9845
+ zip.file("[Content_Types].xml", makeXmlContTypes(this._slides, this._slideLayouts, this._masterSlide, hasCustomProps));
9846
+ zip.file("_rels/.rels", makeXmlRootRels(hasCustomProps));
8974
9847
  zip.file("docProps/app.xml", makeXmlApp(this._slides, this.company));
8975
9848
  zip.file("docProps/core.xml", makeXmlCore(this.title, this.subject, this.author, this.revision));
9849
+ if (hasCustomProps) zip.file("docProps/custom.xml", makeXmlCustomProperties(this._customProperties));
8976
9850
  zip.file("ppt/_rels/presentation.xml.rels", makeXmlPresentationRels(this._slides));
8977
9851
  zip.file("ppt/theme/theme1.xml", makeXmlTheme(this.internalPresentation));
8978
9852
  zip.file("ppt/theme/theme2.xml", makeXmlTheme(this.internalPresentation));
8979
9853
  zip.file("ppt/presentation.xml", makeXmlPresentation(this.internalPresentation));
8980
9854
  zip.file("ppt/presProps.xml", makeXmlPresProps());
8981
- zip.file("ppt/tableStyles.xml", makeXmlTableStyles());
9855
+ zip.file("ppt/tableStyles.xml", makeXmlTableStyles(this._tableStyles));
8982
9856
  zip.file("ppt/viewProps.xml", makeXmlViewProps());
8983
9857
  this._slideLayouts.forEach((layout, idx) => {
8984
9858
  zip.file(`ppt/slideLayouts/slideLayout${idx + 1}.xml`, makeXmlLayout(layout));
@@ -9058,6 +9932,19 @@ var PptxGenJS$1 = class {
9058
9932
  return await this._runtime.writeFile(fileName, data);
9059
9933
  }
9060
9934
  /**
9935
+ * Set a custom document property stored in `docProps/custom.xml`.
9936
+ * Calling with the same name replaces the existing value.
9937
+ * @param name - property name
9938
+ * @param value - string, integer/float number, boolean, or Date
9939
+ */
9940
+ setCustomProperty(name, value) {
9941
+ this._customProperties = this._customProperties.filter((p) => p.name !== name);
9942
+ this._customProperties.push({
9943
+ name,
9944
+ value
9945
+ });
9946
+ }
9947
+ /**
9061
9948
  * Add a new Section to Presentation
9062
9949
  * @param {ISectionProps} section - section properties
9063
9950
  * @example pptx.addSection({ title:'Charts' });
@@ -9166,6 +10053,31 @@ var PptxGenJS$1 = class {
9166
10053
  if (newLayout._slideNumberProps && !this._masterSlide._slideNumberProps) this._masterSlide._slideNumberProps = newLayout._slideNumberProps;
9167
10054
  }
9168
10055
  /**
10056
+ * Register a reusable custom table style and return its GUID.
10057
+ * The style is written to `ppt/tableStyles.xml` and is editable in PowerPoint's
10058
+ * Table Styles gallery. Pass the returned GUID as `TableProps.tableStyle`, and use
10059
+ * the `has*` flags (`hasHeader`, `hasBandedRows`, …) to activate the matching regions.
10060
+ * @param {TableStyleProps} props - custom table style definition (requires `name`)
10061
+ * @returns {string} braced GUID to use as `tableStyle`
10062
+ * @example
10063
+ * const brand = pptx.defineTableStyle({
10064
+ * name: 'Brand Banded',
10065
+ * firstRow: { fill:'1A2B3C', color:'FFFFFF', bold:true },
10066
+ * band1H: { fill:'EAF1F8' },
10067
+ * })
10068
+ * slide.addTable(rows, { tableStyle: brand, hasHeader:true, hasBandedRows:true })
10069
+ */
10070
+ defineTableStyle(props) {
10071
+ if (!props || typeof props !== "object") throw new Error("defineTableStyle() requires a `{ name, ... }` object argument");
10072
+ if (!props.name || typeof props.name !== "string") throw new Error("defineTableStyle() requires a non-empty `name`");
10073
+ const guid = `{${getUuid("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx").toUpperCase()}}`;
10074
+ this._tableStyles.push({
10075
+ guid,
10076
+ def: props
10077
+ });
10078
+ return guid;
10079
+ }
10080
+ /**
9169
10081
  * Reproduces an HTML table as a PowerPoint table - including column widths, style, etc. - creates 1 or more slides as needed
9170
10082
  * @param {string} eleId - table HTML element ID
9171
10083
  * @param {TableToSlidesProps} options - generation options
@@ -9199,7 +10111,7 @@ async function createSvgPngPreview(rel) {
9199
10111
  return await new Promise((resolve, reject) => {
9200
10112
  const image = new Image();
9201
10113
  const fail = (reason) => {
9202
- rel.data = IMG_BROKEN;
10114
+ rel.data = IMG_SVG_PLACEHOLDER;
9203
10115
  reject(/* @__PURE__ */ new Error(`ERROR! Unable to load image (image.onerror): ${rel.path}${reason ? ` - ${String(reason)}` : ""}`));
9204
10116
  };
9205
10117
  image.onload = () => {
@@ -9224,7 +10136,7 @@ async function createSvgPngPreview(rel) {
9224
10136
  }
9225
10137
  };
9226
10138
  image.onerror = () => fail();
9227
- image.src = typeof rel.data === "string" ? rel.data : IMG_BROKEN;
10139
+ image.src = typeof rel.data === "string" ? rel.data : IMG_SVG_PLACEHOLDER;
9228
10140
  });
9229
10141
  }
9230
10142
  async function writeFile(fileName, data) {
@@ -9243,6 +10155,19 @@ async function writeFile(fileName, data) {
9243
10155
  return fileName;
9244
10156
  }
9245
10157
  //#endregion
10158
+ //#region src/core-interfaces.ts
10159
+ /** Factory for a single inline text run. Prevents `as never` casts when building mixed-style run arrays. */
10160
+ function textRun(text, options) {
10161
+ return options !== void 0 ? {
10162
+ text,
10163
+ options
10164
+ } : { text };
10165
+ }
10166
+ /** Wraps a run array so TypeScript accepts it as `TextProps[]` without a cast. */
10167
+ function textRuns(runs) {
10168
+ return runs;
10169
+ }
10170
+ //#endregion
9246
10171
  //#region src/browser.ts
9247
10172
  var PptxGenJS = class extends PptxGenJS$1 {
9248
10173
  constructor() {
@@ -9250,6 +10175,6 @@ var PptxGenJS = class extends PptxGenJS$1 {
9250
10175
  }
9251
10176
  };
9252
10177
  //#endregion
9253
- export { AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_SERIES_PRIMARY, AXIS_ID_VALUE_PRIMARY, AXIS_ID_VALUE_SECONDARY, AlignH, AlignV, BARCHART_COLORS, BULLET_TYPES, CHART_TYPE, CRLF, ChartType, DEF_BULLET_MARGIN, DEF_CELL_BORDER, DEF_CELL_MARGIN_IN, DEF_CELL_MARGIN_PT, DEF_CHART_BORDER, DEF_CHART_GRIDLINE, DEF_FONT_COLOR, DEF_FONT_SIZE, DEF_FONT_TITLE_SIZE, DEF_PRES_LAYOUT, DEF_PRES_LAYOUT_NAME, DEF_SHAPE_LINE_COLOR, DEF_SHAPE_SHADOW, DEF_SLIDE_BKGD, DEF_SLIDE_MARGIN_IN, DEF_TEXT_GLOW, DEF_TEXT_SHADOW, EMU, EMU_PER_INCH, EMU_PER_POINT, IMG_BROKEN, IMG_PLAYBTN, LAYOUT_IDX_SERIES_BASE, LETTERS, LINEH_MODIFIER, MASTER_OBJECTS, ONEPT, OutputType, PIECHART_COLORS, PLACEHOLDER_TYPES, POINTS_PER_INCH, PptxGenJS, PptxGenJS as Presentation, PptxGenJS as default, REGEX_HEX_COLOR, SCHEME_COLOR_NAMES, SHAPE_TYPE, SLDNUMFLDID, SLIDE_OBJECT_TYPES, STANDARD_LAYOUTS, SchemeColor, ShapeType, TEXT_HALIGN, TEXT_VALIGN, emuToInches, emuToPixels, emuToPoints, inchesToEmu, pixelsToEmu, pointsToEmu };
10178
+ export { AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_SERIES_PRIMARY, AXIS_ID_VALUE_PRIMARY, AXIS_ID_VALUE_SECONDARY, AlignH, AlignV, BARCHART_COLORS, BULLET_TYPES, CHART_TYPE, CRLF, ChartType, DEF_BULLET_MARGIN, DEF_CELL_BORDER, DEF_CELL_MARGIN_IN, DEF_CELL_MARGIN_PT, DEF_CHART_BORDER, DEF_CHART_GRIDLINE, DEF_FONT_COLOR, DEF_FONT_SIZE, DEF_FONT_TITLE_SIZE, DEF_PRES_LAYOUT, DEF_PRES_LAYOUT_NAME, DEF_SHAPE_LINE_COLOR, DEF_SHAPE_SHADOW, DEF_SLIDE_BKGD, DEF_SLIDE_MARGIN_IN, DEF_TEXT_GLOW, DEF_TEXT_SHADOW, EMU, EMU_PER_INCH, EMU_PER_POINT, IMG_BROKEN, IMG_PLAYBTN, IMG_SVG_PLACEHOLDER, LAYOUT_IDX_SERIES_BASE, LETTERS, LINEH_MODIFIER, MASTER_OBJECTS, ONEPT, OutputType, PIECHART_COLORS, PLACEHOLDER_TYPES, POINTS_PER_INCH, PptxGenJS, PptxGenJS as Presentation, PptxGenJS as default, REGEX_HEX_COLOR, SCHEME_COLOR_NAMES, SHAPE_TYPE, SLDNUMFLDID, SLIDE_OBJECT_TYPES, STANDARD_LAYOUTS, SchemeColor, ShapeType, TABLE_STYLE, TEXT_HALIGN, TEXT_VALIGN, VALID_SHAPE_PRESETS, coordToEmu, emuToInches, emuToPixels, emuToPoints, inchesToEmu, percentToEmu, pixelsToEmu, pointsToEmu, textRun, textRuns };
9254
10179
 
9255
10180
  //# sourceMappingURL=standalone.js.map