|
1 | 1 | import { Color, ImageStyle } from '../types/modify-types'; |
2 | 2 | import XmlElements from './xml-elements'; |
3 | 3 | import { XmlHelper } from './xml-helper'; |
4 | | -import { XmlElement } from '../types/xml-types'; |
| 4 | +import { ShapeBackgroundInfo, XmlElement } from '../types/xml-types'; |
5 | 5 | import { vd } from './general-helper'; |
| 6 | +import { ModifyShapeHelper } from '../index'; |
6 | 7 |
|
7 | 8 | export default class ModifyColorHelper { |
8 | 9 | /** |
@@ -69,4 +70,173 @@ export default class ModifyColorHelper { |
69 | 70 | } |
70 | 71 | return color; |
71 | 72 | }; |
| 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 | + } |
72 | 242 | } |
0 commit comments