Skip to content

Commit 9cb21c5

Browse files
committed
chore: minor fixes; get shapeHasBackground info
1 parent d41e61c commit 9cb21c5

File tree

6 files changed

+235
-11
lines changed

6 files changed

+235
-11
lines changed

src/helper/modify-cleanup-helper.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export default class ModifyCleanupHelper {
149149
ModifyCleanupHelper.remove3dEffects(element);
150150
ModifyCleanupHelper.removeFillEffects(element);
151151
ModifyCleanupHelper.removeTextEffects(element);
152+
ModifyCleanupHelper.removeExtLst(element);
152153

153154
// Determine the visual type of the element (picture, chart, etc.)
154155
// Apply type-specific cleanup
@@ -212,6 +213,28 @@ export default class ModifyCleanupHelper {
212213
});
213214
}
214215

216+
/**
217+
* Removes extension list (extLst) elements from PowerPoint shapes
218+
*
219+
* The extLst (Extension List) element in OOXML contains application-specific extensions
220+
* and future compatibility features that may not be supported across all Office versions.
221+
* These extensions can include:
222+
* - Custom drawing effects and transformations
223+
* - Third-party add-in specific properties
224+
* - Version-specific features that may cause rendering inconsistencies
225+
* - Experimental or preview features
226+
*
227+
* Removing extLst helps ensure cross-version compatibility and prevents rendering
228+
* issues when the presentation is opened in different versions of PowerPoint or
229+
* other OOXML-compatible applications.
230+
*
231+
* @param element - The XML element containing extension lists to remove
232+
*/
233+
static removeExtLst(element: XmlElement): void {
234+
const extLst = element.getElementsByTagName('a:extLst').item(0);
235+
XmlHelper.remove(extLst);
236+
}
237+
215238
static clearTextUnderlineToBold(element: XmlElement): void {
216239
const textRuns = element.getElementsByTagName('a:rPr');
217240
XmlHelper.modifyCollection(textRuns, (textRun: XmlElement) => {
@@ -260,7 +283,7 @@ export default class ModifyCleanupHelper {
260283
static clearTextColor(element: XmlElement, color?: Color): void {
261284
const textRuns = element.getElementsByTagName('a:rPr');
262285
XmlHelper.modifyCollection(textRuns, (textRun: XmlElement) => {
263-
if(color) {
286+
if (color) {
264287
ModifyColorHelper.solidFill(color, 0)(textRun);
265288
} else {
266289
// Remove all color-related elements from text run properties

src/helper/modify-color-helper.ts

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Color, ImageStyle } from '../types/modify-types';
22
import XmlElements from './xml-elements';
33
import { XmlHelper } from './xml-helper';
4-
import { XmlElement } from '../types/xml-types';
4+
import { ShapeBackgroundInfo, XmlElement } from '../types/xml-types';
55
import { vd } from './general-helper';
6+
import { ModifyShapeHelper } from '../index';
67

78
export default class ModifyColorHelper {
89
/**
@@ -69,4 +70,173 @@ export default class ModifyColorHelper {
6970
}
7071
return color;
7172
};
73+
74+
/**
75+
* Check if the given element has a background which is non-transparent
76+
* @param element The XML element to check
77+
* @returns ShapeBackgroundInfo
78+
*/
79+
static elementHasBackground(element: XmlElement): ShapeBackgroundInfo {
80+
// Find the shape properties (spPr) element
81+
const spPr = element.getElementsByTagName('p:spPr')[0] ||
82+
element.getElementsByTagName('a:spPr')[0];
83+
84+
if (!spPr) {
85+
// No shape properties found - assume no background
86+
return {
87+
isDark: false
88+
};
89+
}
90+
91+
// Check for different fill types in order of priority
92+
93+
// 1. Check for solid fill
94+
const solidFill = spPr.getElementsByTagName('a:solidFill')[0];
95+
if (solidFill) {
96+
const schemeClr = solidFill.getElementsByTagName('a:schemeClr')[0];
97+
const srgbClr = solidFill.getElementsByTagName('a:srgbClr')[0];
98+
99+
if (schemeClr) {
100+
const schemeValue = schemeClr.getAttribute('val');
101+
return {
102+
color: { type: 'schemeClr', value: schemeValue || 'bg1' },
103+
isDark: ModifyColorHelper.isSchemeColorDark(schemeValue || 'bg1')
104+
};
105+
}
106+
107+
if (srgbClr) {
108+
const rgbValue = srgbClr.getAttribute('val');
109+
return {
110+
color: { type: 'srgbClr', value: rgbValue || 'FFFFFF' },
111+
isDark: ModifyColorHelper.isRgbColorDark(rgbValue || 'FFFFFF')
112+
};
113+
}
114+
115+
// Solid fill exists but no color specified - assume light
116+
return {
117+
isDark: false
118+
};
119+
}
120+
121+
// 2. Check for gradient fill
122+
const gradFill = spPr.getElementsByTagName('a:gradFill')[0];
123+
if (gradFill) {
124+
// For gradient fills, check the first gradient stop
125+
const gsLst = gradFill.getElementsByTagName('a:gsLst')[0];
126+
if (gsLst) {
127+
const firstGs = gsLst.getElementsByTagName('a:gs')[0];
128+
if (firstGs) {
129+
const schemeClr = firstGs.getElementsByTagName('a:schemeClr')[0];
130+
const srgbClr = firstGs.getElementsByTagName('a:srgbClr')[0];
131+
132+
if (schemeClr) {
133+
const schemeValue = schemeClr.getAttribute('val');
134+
return {
135+
color: { type: 'schemeClr', value: schemeValue || 'bg1' },
136+
isDark: ModifyColorHelper.isSchemeColorDark(schemeValue || 'bg1')
137+
};
138+
}
139+
140+
if (srgbClr) {
141+
const rgbValue = srgbClr.getAttribute('val');
142+
return {
143+
color: { type: 'srgbClr', value: rgbValue || 'FFFFFF' },
144+
isDark: ModifyColorHelper.isRgbColorDark(rgbValue || 'FFFFFF')
145+
};
146+
}
147+
}
148+
}
149+
150+
// Gradient fill exists but no color found - assume light
151+
return {
152+
isDark: false
153+
};
154+
}
155+
156+
// 3. Check for pattern fill or other fill types
157+
const pattFill = spPr.getElementsByTagName('a:pattFill')[0];
158+
if (pattFill) {
159+
// Pattern fills typically have a foreground color - check that
160+
const fgClr = pattFill.getElementsByTagName('a:fgClr')[0];
161+
if (fgClr) {
162+
const schemeClr = fgClr.getElementsByTagName('a:schemeClr')[0];
163+
const srgbClr = fgClr.getElementsByTagName('a:srgbClr')[0];
164+
165+
if (schemeClr) {
166+
const schemeValue = schemeClr.getAttribute('val');
167+
return {
168+
color: { type: 'schemeClr', value: schemeValue || 'bg1' },
169+
isDark: ModifyColorHelper.isSchemeColorDark(schemeValue || 'bg1')
170+
};
171+
}
172+
173+
if (srgbClr) {
174+
const rgbValue = srgbClr.getAttribute('val');
175+
return {
176+
color: { type: 'srgbClr', value: rgbValue || 'FFFFFF' },
177+
isDark: ModifyColorHelper.isRgbColorDark(rgbValue || 'FFFFFF')
178+
};
179+
}
180+
}
181+
}
182+
183+
// 4. Check for no fill
184+
const noFill = spPr.getElementsByTagName('a:noFill')[0];
185+
if (noFill) {
186+
// No fill - transparent background
187+
return {
188+
isDark: false
189+
};
190+
}
191+
192+
// No specific fill found - assume default light background
193+
return {
194+
isDark: false
195+
};
196+
}
197+
198+
/**
199+
* Determines if a scheme color is typically dark and would require light text
200+
* @param schemeValue The scheme color value (e.g., 'dk1', 'lt1', 'accent1', etc.)
201+
* @returns true if the color is typically dark
202+
*/
203+
private static isSchemeColorDark(schemeValue: string): boolean {
204+
// Classic dark scheme colors that typically require light/white text
205+
const darkSchemeColors = [
206+
'dk1', // Dark 1 (usually black or very dark)
207+
'dk2', // Dark 2 (usually dark blue)
208+
'accent1', // Often darker colors
209+
'accent2',
210+
'accent4', // Often purple/dark
211+
'tx1', // Text 1 (usually dark)
212+
'tx2' // Text 2 (usually dark)
213+
];
214+
215+
return darkSchemeColors.includes(schemeValue);
216+
}
217+
218+
/**
219+
* Determines if an RGB color is dark based on luminance
220+
* @param rgbValue The RGB hex value (e.g., '000000', 'FF0000')
221+
* @returns true if the color is dark
222+
*/
223+
private static isRgbColorDark(rgbValue: string): boolean {
224+
// Remove '#' if present and ensure we have a valid hex string
225+
const hex = rgbValue.replace('#', '').toUpperCase();
226+
227+
if (hex.length !== 6) {
228+
return false; // Invalid hex, assume light
229+
}
230+
231+
// Convert hex to RGB
232+
const r = parseInt(hex.substring(0, 2), 16);
233+
const g = parseInt(hex.substring(2, 4), 16);
234+
const b = parseInt(hex.substring(4, 6), 16);
235+
236+
// Calculate relative luminance using the standard formula
237+
// Values below 0.5 are considered dark (on a scale of 0-1)
238+
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
239+
240+
return luminance < 0.5;
241+
}
72242
}

src/helper/modify-shape-helper.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,21 @@
22
* @file ModifyShapeHelper provides utility functions for manipulating PowerPoint shapes
33
* through XML modifications.
44
*/
5-
import { ReplaceText, ReplaceTextOptions } from '../types/modify-types';
5+
import { Color, ReplaceText, ReplaceTextOptions } from '../types/modify-types';
66
import { ShapeCoordinates } from '../types/shape-types';
77
import { GeneralHelper } from './general-helper';
88
import TextReplaceHelper from './text-replace-helper';
99
import ModifyTextHelper from './modify-text-helper';
10-
import { ElementVisualType, XmlElement } from '../types/xml-types';
10+
import {
11+
ElementVisualType,
12+
PlaceholderInfo,
13+
ShapeBackgroundInfo,
14+
XmlElement,
15+
} from '../types/xml-types';
1116
import { XmlHelper } from './xml-helper';
1217
import { XmlSlideHelper } from './xml-slide-helper';
18+
import XmlPlaceholderHelper from './xml-placeholder-helper';
19+
import { ModifyColorHelper } from '../index';
1320

1421
/**
1522
* Mapping between user-friendly property names and their corresponding XML structure
@@ -326,4 +333,22 @@ export default class ModifyShapeHelper {
326333
static getElementVisualType(element: XmlElement): ElementVisualType {
327334
return XmlSlideHelper.getElementVisualType(element);
328335
}
336+
337+
/**
338+
* Forward placeholder info function
339+
* @param element The XML element to check
340+
* @returns The PlaceholderInfo object
341+
*/
342+
static getElementPlaceholderInfo(element: XmlElement): PlaceholderInfo {
343+
return XmlPlaceholderHelper.getPlaceholderInfo(element);
344+
}
345+
346+
/**
347+
* Check if the given element has a background which is non-transparent
348+
* @param element The XML element to check
349+
* @returns ShapeBackgroundInfo
350+
*/
351+
static elementHasBackground(element: XmlElement): ShapeBackgroundInfo {
352+
return ModifyColorHelper.elementHasBackground(element)
353+
}
329354
}

src/helper/xml-placeholder-helper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,10 +295,10 @@ export default class XmlPlaceholderHelper {
295295
}
296296

297297
applyPlaceholder(element: ElementInfo, bestMatch: PlaceholderInfo) {
298-
const callback = (element: XmlElement) => {
298+
const applyPlaceholderCallback = (element: XmlElement) => {
299299
XmlPlaceholderHelper.setPlaceholderDefaults(element, bestMatch);
300300
};
301-
this.postApplyModification(element, callback);
301+
this.postApplyModification(element, applyPlaceholderCallback);
302302
this.mappingResult.usedPlaceholders.push(bestMatch);
303303
}
304304

src/helper/xml-slide-helper.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -725,11 +725,6 @@ export class XmlSlideHelper {
725725
return 'table';
726726
}
727727

728-
// Check for 3D models
729-
if (XmlHelper.findElement(element, 'a:scene3d')) {
730-
return '3dModel';
731-
}
732-
733728
// Check for SVG Images
734729
if (
735730
XmlHelper.findElement(element, 'a:svgBlip') ||
@@ -808,6 +803,11 @@ export class XmlSlideHelper {
808803
}
809804
}
810805

806+
// Check for 3D models
807+
if (XmlHelper.findElement(element, 'a:scene3d')) {
808+
return '3dModel';
809+
}
810+
811811
// Default case
812812
return 'unknown';
813813
}

src/types/xml-types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import IArchive from '../interfaces/iarchive';
22
import { TableInfo } from './table-types';
3+
import { Color } from './modify-types';
34

45
export type DefaultAttribute = {
56
Extension: string;
@@ -154,6 +155,11 @@ export type ElementInfo = {
154155
getGroupInfo: () => GroupInfo;
155156
};
156157

158+
export type ShapeBackgroundInfo = {
159+
color?: Color,
160+
isDark: boolean
161+
}
162+
157163
export type LayoutInfo = {
158164
layoutName: string;
159165
placeholders: PlaceholderInfo[];

0 commit comments

Comments
 (0)