1 <?php 2 3 declare(strict_types=1); 4 5 namespace OpenSpout\Writer\XLSX\Manager\Style; 6 7 use OpenSpout\Common\Entity\Style\Style; 8 use OpenSpout\Writer\Common\Manager\Style\AbstractStyleRegistry as CommonStyleRegistry; 9 10 /** 11 * @internal 12 */ 13 class StyleRegistry extends CommonStyleRegistry 14 { 15 /** 16 * Mapping between built-in format and the associated numFmtId. 17 * 18 * @see https://msdn.microsoft.com/en-us/library/ff529597(v=office.12).aspx 19 */ 20 private const builtinNumFormatToIdMapping = [ 21 'General' => 0, 22 '0' => 1, 23 '0.00' => 2, 24 '#,##0' => 3, 25 '#,##0.00' => 4, 26 '$#,##0,\-$#,##0' => 5, 27 '$#,##0,[Red]\-$#,##0' => 6, 28 '$#,##0.00,\-$#,##0.00' => 7, 29 '$#,##0.00,[Red]\-$#,##0.00' => 8, 30 '0%' => 9, 31 '0.00%' => 10, 32 '0.00E+00' => 11, 33 '# ?/?' => 12, 34 '# ??/??' => 13, 35 'mm-dd-yy' => 14, 36 'd-mmm-yy' => 15, 37 'd-mmm' => 16, 38 'mmm-yy' => 17, 39 'h:mm AM/PM' => 18, 40 'h:mm:ss AM/PM' => 19, 41 'h:mm' => 20, 42 'h:mm:ss' => 21, 43 'm/d/yy h:mm' => 22, 44 45 '#,##0 ,(#,##0)' => 37, 46 '#,##0 ,[Red](#,##0)' => 38, 47 '#,##0.00,(#,##0.00)' => 39, 48 '#,##0.00,[Red](#,##0.00)' => 40, 49 50 '_("$"* #,##0.00_),_("$"* \(#,##0.00\),_("$"* "-"??_),_(@_)' => 44, 51 'mm:ss' => 45, 52 '[h]:mm:ss' => 46, 53 'mm:ss.0' => 47, 54 55 '##0.0E+0' => 48, 56 '@' => 49, 57 58 '[$-404]e/m/d' => 27, 59 'm/d/yy' => 30, 60 't0' => 59, 61 't0.00' => 60, 62 't#,##0' => 61, 63 't#,##0.00' => 62, 64 't0%' => 67, 65 't0.00%' => 68, 66 't# ?/?' => 69, 67 't# ??/??' => 70, 68 ]; 69 70 /** @var array<string, int> */ 71 private array $registeredFormats = []; 72 73 /** @var array<int, int> [STYLE_ID] => [FORMAT_ID] maps a style to a format declaration */ 74 private array $styleIdToFormatsMappingTable = []; 75 76 /** 77 * If the numFmtId is lower than 0xA4 (164 in decimal) 78 * then it's a built-in number format. 79 * Since Excel is the dominant vendor - we play along here. 80 * 81 * @var int the fill index counter for custom fills 82 */ 83 private int $formatIndex = 164; 84 85 /** @var array<string, int> */ 86 private array $registeredFills = []; 87 88 /** @var array<int, int> [STYLE_ID] => [FILL_ID] maps a style to a fill declaration */ 89 private array $styleIdToFillMappingTable = []; 90 91 /** 92 * Excel preserves two default fills with index 0 and 1 93 * Since Excel is the dominant vendor - we play along here. 94 * 95 * @var int the fill index counter for custom fills 96 */ 97 private int $fillIndex = 2; 98 99 /** @var array<string, int> */ 100 private array $registeredBorders = []; 101 102 /** @var array<int, int> [STYLE_ID] => [BORDER_ID] maps a style to a border declaration */ 103 private array $styleIdToBorderMappingTable = []; 104 105 /** 106 * XLSX specific operations on the registered styles. 107 */ 108 public function registerStyle(Style $style): Style 109 { 110 if ($style->isRegistered()) { 111 return $style; 112 } 113 114 $registeredStyle = parent::registerStyle($style); 115 $this->registerFill($registeredStyle); 116 $this->registerFormat($registeredStyle); 117 $this->registerBorder($registeredStyle); 118 119 return $registeredStyle; 120 } 121 122 /** 123 * @return null|int Format ID associated to the given style ID 124 */ 125 public function getFormatIdForStyleId(int $styleId): ?int 126 { 127 return $this->styleIdToFormatsMappingTable[$styleId] ?? null; 128 } 129 130 /** 131 * @return null|int Fill ID associated to the given style ID 132 */ 133 public function getFillIdForStyleId(int $styleId): ?int 134 { 135 return $this->styleIdToFillMappingTable[$styleId] ?? null; 136 } 137 138 /** 139 * @return null|int Fill ID associated to the given style ID 140 */ 141 public function getBorderIdForStyleId(int $styleId): ?int 142 { 143 return $this->styleIdToBorderMappingTable[$styleId] ?? null; 144 } 145 146 /** 147 * @return array<string, int> 148 */ 149 public function getRegisteredFills(): array 150 { 151 return $this->registeredFills; 152 } 153 154 /** 155 * @return array<string, int> 156 */ 157 public function getRegisteredBorders(): array 158 { 159 return $this->registeredBorders; 160 } 161 162 /** 163 * @return array<string, int> 164 */ 165 public function getRegisteredFormats(): array 166 { 167 return $this->registeredFormats; 168 } 169 170 /** 171 * Register a format definition. 172 */ 173 private function registerFormat(Style $style): void 174 { 175 $styleId = $style->getId(); 176 177 $format = $style->getFormat(); 178 if (null !== $format) { 179 $isFormatRegistered = isset($this->registeredFormats[$format]); 180 181 // We need to track the already registered format definitions 182 if ($isFormatRegistered) { 183 $registeredStyleId = $this->registeredFormats[$format]; 184 $registeredFormatId = $this->styleIdToFormatsMappingTable[$registeredStyleId]; 185 $this->styleIdToFormatsMappingTable[$styleId] = $registeredFormatId; 186 } else { 187 $this->registeredFormats[$format] = $styleId; 188 189 $id = self::builtinNumFormatToIdMapping[$format] ?? $this->formatIndex++; 190 $this->styleIdToFormatsMappingTable[$styleId] = $id; 191 } 192 } else { 193 // The formatId maps a style to a format declaration 194 // When there is no format definition - we default to 0 ( General ) 195 $this->styleIdToFormatsMappingTable[$styleId] = 0; 196 } 197 } 198 199 /** 200 * Register a fill definition. 201 */ 202 private function registerFill(Style $style): void 203 { 204 $styleId = $style->getId(); 205 206 // Currently - only solid backgrounds are supported 207 // so $backgroundColor is a scalar value (RGB Color) 208 $backgroundColor = $style->getBackgroundColor(); 209 210 if (null !== $backgroundColor) { 211 $isBackgroundColorRegistered = isset($this->registeredFills[$backgroundColor]); 212 213 // We need to track the already registered background definitions 214 if ($isBackgroundColorRegistered) { 215 $registeredStyleId = $this->registeredFills[$backgroundColor]; 216 $registeredFillId = $this->styleIdToFillMappingTable[$registeredStyleId]; 217 $this->styleIdToFillMappingTable[$styleId] = $registeredFillId; 218 } else { 219 $this->registeredFills[$backgroundColor] = $styleId; 220 $this->styleIdToFillMappingTable[$styleId] = $this->fillIndex++; 221 } 222 } else { 223 // The fillId maps a style to a fill declaration 224 // When there is no background color definition - we default to 0 225 $this->styleIdToFillMappingTable[$styleId] = 0; 226 } 227 } 228 229 /** 230 * Register a border definition. 231 */ 232 private function registerBorder(Style $style): void 233 { 234 $styleId = $style->getId(); 235 236 if (null !== ($border = $style->getBorder())) { 237 $serializedBorder = serialize($border); 238 239 $isBorderAlreadyRegistered = isset($this->registeredBorders[$serializedBorder]); 240 241 if ($isBorderAlreadyRegistered) { 242 $registeredStyleId = $this->registeredBorders[$serializedBorder]; 243 $registeredBorderId = $this->styleIdToBorderMappingTable[$registeredStyleId]; 244 $this->styleIdToBorderMappingTable[$styleId] = $registeredBorderId; 245 } else { 246 $this->registeredBorders[$serializedBorder] = $styleId; 247 $this->styleIdToBorderMappingTable[$styleId] = \count($this->registeredBorders); 248 } 249 } else { 250 // If no border should be applied - the mapping is the default border: 0 251 $this->styleIdToBorderMappingTable[$styleId] = 0; 252 } 253 } 254 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body