PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.11.0
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.11.0
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 All 149 releases
visualizer / vendor / phpoffice / phpspreadsheet / src / PhpSpreadsheet / Style / Style.php

Style.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 3.11.0, at vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php

642 lines 20.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace PhpOffice\PhpSpreadsheet\Style;
4
5 use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
6 use PhpOffice\PhpSpreadsheet\Spreadsheet;
7
8 class Style extends Supervisor
9 {
10 /**
11 * Font.
12 *
13 * @var Font
14 */
15 protected $font;
16
17 /**
18 * Fill.
19 *
20 * @var Fill
21 */
22 protected $fill;
23
24 /**
25 * Borders.
26 *
27 * @var Borders
28 */
29 protected $borders;
30
31 /**
32 * Alignment.
33 *
34 * @var Alignment
35 */
36 protected $alignment;
37
38 /**
39 * Number Format.
40 *
41 * @var NumberFormat
42 */
43 protected $numberFormat;
44
45 /**
46 * Conditional styles.
47 *
48 * @var Conditional[]
49 */
50 protected $conditionalStyles;
51
52 /**
53 * Protection.
54 *
55 * @var Protection
56 */
57 protected $protection;
58
59 /**
60 * Index of style in collection. Only used for real style.
61 *
62 * @var int
63 */
64 protected $index;
65
66 /**
67 * Use Quote Prefix when displaying in cell editor. Only used for real style.
68 *
69 * @var bool
70 */
71 protected $quotePrefix = false;
72
73 /**
74 * Create a new Style.
75 *
76 * @param bool $isSupervisor Flag indicating if this is a supervisor or not
77 * Leave this value at default unless you understand exactly what
78 * its ramifications are
79 * @param bool $isConditional Flag indicating if this is a conditional style or not
80 * Leave this value at default unless you understand exactly what
81 * its ramifications are
82 */
83 public function __construct($isSupervisor = false, $isConditional = false)
84 {
85 parent::__construct($isSupervisor);
86
87 // Initialise values
88 $this->conditionalStyles = [];
89 $this->font = new Font($isSupervisor, $isConditional);
90 $this->fill = new Fill($isSupervisor, $isConditional);
91 $this->borders = new Borders($isSupervisor, $isConditional);
92 $this->alignment = new Alignment($isSupervisor, $isConditional);
93 $this->numberFormat = new NumberFormat($isSupervisor, $isConditional);
94 $this->protection = new Protection($isSupervisor, $isConditional);
95
96 // bind parent if we are a supervisor
97 if ($isSupervisor) {
98 $this->font->bindParent($this);
99 $this->fill->bindParent($this);
100 $this->borders->bindParent($this);
101 $this->alignment->bindParent($this);
102 $this->numberFormat->bindParent($this);
103 $this->protection->bindParent($this);
104 }
105 }
106
107 /**
108 * Get the shared style component for the currently active cell in currently active sheet.
109 * Only used for style supervisor.
110 *
111 * @return Style
112 */
113 public function getSharedComponent()
114 {
115 $activeSheet = $this->getActiveSheet();
116 $selectedCell = $this->getActiveCell(); // e.g. 'A1'
117
118 if ($activeSheet->cellExists($selectedCell)) {
119 $xfIndex = $activeSheet->getCell($selectedCell)->getXfIndex();
120 } else {
121 $xfIndex = 0;
122 }
123
124 return $this->parent->getCellXfByIndex($xfIndex);
125 }
126
127 /**
128 * Get parent. Only used for style supervisor.
129 *
130 * @return Spreadsheet
131 */
132 public function getParent()
133 {
134 return $this->parent;
135 }
136
137 /**
138 * Build style array from subcomponents.
139 *
140 * @param array $array
141 *
142 * @return array
143 */
144 public function getStyleArray($array)
145 {
146 return ['quotePrefix' => $array];
147 }
148
149 /**
150 * Apply styles from array.
151 *
152 * <code>
153 * $spreadsheet->getActiveSheet()->getStyle('B2')->applyFromArray(
154 * [
155 * 'font' => [
156 * 'name' => 'Arial',
157 * 'bold' => true,
158 * 'italic' => false,
159 * 'underline' => Font::UNDERLINE_DOUBLE,
160 * 'strikethrough' => false,
161 * 'color' => [
162 * 'rgb' => '808080'
163 * ]
164 * ],
165 * 'borders' => [
166 * 'bottom' => [
167 * 'borderStyle' => Border::BORDER_DASHDOT,
168 * 'color' => [
169 * 'rgb' => '808080'
170 * ]
171 * ],
172 * 'top' => [
173 * 'borderStyle' => Border::BORDER_DASHDOT,
174 * 'color' => [
175 * 'rgb' => '808080'
176 * ]
177 * ]
178 * ],
179 * 'alignment' => [
180 * 'horizontal' => Alignment::HORIZONTAL_CENTER,
181 * 'vertical' => Alignment::VERTICAL_CENTER,
182 * 'wrapText' => true,
183 * ],
184 * 'quotePrefix' => true
185 * ]
186 * );
187 * </code>
188 *
189 * @param array $pStyles Array containing style information
190 * @param bool $pAdvanced advanced mode for setting borders
191 *
192 * @return Style
193 */
194 public function applyFromArray(array $pStyles, $pAdvanced = true)
195 {
196 if ($this->isSupervisor) {
197 $pRange = $this->getSelectedCells();
198
199 // Uppercase coordinate
200 $pRange = strtoupper($pRange);
201
202 // Is it a cell range or a single cell?
203 if (strpos($pRange, ':') === false) {
204 $rangeA = $pRange;
205 $rangeB = $pRange;
206 } else {
207 list($rangeA, $rangeB) = explode(':', $pRange);
208 }
209
210 // Calculate range outer borders
211 $rangeStart = Coordinate::coordinateFromString($rangeA);
212 $rangeEnd = Coordinate::coordinateFromString($rangeB);
213
214 // Translate column into index
215 $rangeStart[0] = Coordinate::columnIndexFromString($rangeStart[0]);
216 $rangeEnd[0] = Coordinate::columnIndexFromString($rangeEnd[0]);
217
218 // Make sure we can loop upwards on rows and columns
219 if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
220 $tmp = $rangeStart;
221 $rangeStart = $rangeEnd;
222 $rangeEnd = $tmp;
223 }
224
225 // ADVANCED MODE:
226 if ($pAdvanced && isset($pStyles['borders'])) {
227 // 'allBorders' is a shorthand property for 'outline' and 'inside' and
228 // it applies to components that have not been set explicitly
229 if (isset($pStyles['borders']['allBorders'])) {
230 foreach (['outline', 'inside'] as $component) {
231 if (!isset($pStyles['borders'][$component])) {
232 $pStyles['borders'][$component] = $pStyles['borders']['allBorders'];
233 }
234 }
235 unset($pStyles['borders']['allBorders']); // not needed any more
236 }
237 // 'outline' is a shorthand property for 'top', 'right', 'bottom', 'left'
238 // it applies to components that have not been set explicitly
239 if (isset($pStyles['borders']['outline'])) {
240 foreach (['top', 'right', 'bottom', 'left'] as $component) {
241 if (!isset($pStyles['borders'][$component])) {
242 $pStyles['borders'][$component] = $pStyles['borders']['outline'];
243 }
244 }
245 unset($pStyles['borders']['outline']); // not needed any more
246 }
247 // 'inside' is a shorthand property for 'vertical' and 'horizontal'
248 // it applies to components that have not been set explicitly
249 if (isset($pStyles['borders']['inside'])) {
250 foreach (['vertical', 'horizontal'] as $component) {
251 if (!isset($pStyles['borders'][$component])) {
252 $pStyles['borders'][$component] = $pStyles['borders']['inside'];
253 }
254 }
255 unset($pStyles['borders']['inside']); // not needed any more
256 }
257 // width and height characteristics of selection, 1, 2, or 3 (for 3 or more)
258 $xMax = min($rangeEnd[0] - $rangeStart[0] + 1, 3);
259 $yMax = min($rangeEnd[1] - $rangeStart[1] + 1, 3);
260
261 // loop through up to 3 x 3 = 9 regions
262 for ($x = 1; $x <= $xMax; ++$x) {
263 // start column index for region
264 $colStart = ($x == 3) ?
265 Coordinate::stringFromColumnIndex($rangeEnd[0])
266 : Coordinate::stringFromColumnIndex($rangeStart[0] + $x - 1);
267 // end column index for region
268 $colEnd = ($x == 1) ?
269 Coordinate::stringFromColumnIndex($rangeStart[0])
270 : Coordinate::stringFromColumnIndex($rangeEnd[0] - $xMax + $x);
271
272 for ($y = 1; $y <= $yMax; ++$y) {
273 // which edges are touching the region
274 $edges = [];
275 if ($x == 1) {
276 // are we at left edge
277 $edges[] = 'left';
278 }
279 if ($x == $xMax) {
280 // are we at right edge
281 $edges[] = 'right';
282 }
283 if ($y == 1) {
284 // are we at top edge?
285 $edges[] = 'top';
286 }
287 if ($y == $yMax) {
288 // are we at bottom edge?
289 $edges[] = 'bottom';
290 }
291
292 // start row index for region
293 $rowStart = ($y == 3) ?
294 $rangeEnd[1] : $rangeStart[1] + $y - 1;
295
296 // end row index for region
297 $rowEnd = ($y == 1) ?
298 $rangeStart[1] : $rangeEnd[1] - $yMax + $y;
299
300 // build range for region
301 $range = $colStart . $rowStart . ':' . $colEnd . $rowEnd;
302
303 // retrieve relevant style array for region
304 $regionStyles = $pStyles;
305 unset($regionStyles['borders']['inside']);
306
307 // what are the inner edges of the region when looking at the selection
308 $innerEdges = array_diff(['top', 'right', 'bottom', 'left'], $edges);
309
310 // inner edges that are not touching the region should take the 'inside' border properties if they have been set
311 foreach ($innerEdges as $innerEdge) {
312 switch ($innerEdge) {
313 case 'top':
314 case 'bottom':
315 // should pick up 'horizontal' border property if set
316 if (isset($pStyles['borders']['horizontal'])) {
317 $regionStyles['borders'][$innerEdge] = $pStyles['borders']['horizontal'];
318 } else {
319 unset($regionStyles['borders'][$innerEdge]);
320 }
321
322 break;
323 case 'left':
324 case 'right':
325 // should pick up 'vertical' border property if set
326 if (isset($pStyles['borders']['vertical'])) {
327 $regionStyles['borders'][$innerEdge] = $pStyles['borders']['vertical'];
328 } else {
329 unset($regionStyles['borders'][$innerEdge]);
330 }
331
332 break;
333 }
334 }
335
336 // apply region style to region by calling applyFromArray() in simple mode
337 $this->getActiveSheet()->getStyle($range)->applyFromArray($regionStyles, false);
338 }
339 }
340
341 // restore initial cell selection range
342 $this->getActiveSheet()->getStyle($pRange);
343
344 return $this;
345 }
346
347 // SIMPLE MODE:
348 // Selection type, inspect
349 if (preg_match('/^[A-Z]+1:[A-Z]+1048576$/', $pRange)) {
350 $selectionType = 'COLUMN';
351 } elseif (preg_match('/^A\d+:XFD\d+$/', $pRange)) {
352 $selectionType = 'ROW';
353 } else {
354 $selectionType = 'CELL';
355 }
356
357 // First loop through columns, rows, or cells to find out which styles are affected by this operation
358 switch ($selectionType) {
359 case 'COLUMN':
360 $oldXfIndexes = [];
361 for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
362 $oldXfIndexes[$this->getActiveSheet()->getColumnDimensionByColumn($col)->getXfIndex()] = true;
363 }
364
365 break;
366 case 'ROW':
367 $oldXfIndexes = [];
368 for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
369 if ($this->getActiveSheet()->getRowDimension($row)->getXfIndex() == null) {
370 $oldXfIndexes[0] = true; // row without explicit style should be formatted based on default style
371 } else {
372 $oldXfIndexes[$this->getActiveSheet()->getRowDimension($row)->getXfIndex()] = true;
373 }
374 }
375
376 break;
377 case 'CELL':
378 $oldXfIndexes = [];
379 for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
380 for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
381 $oldXfIndexes[$this->getActiveSheet()->getCellByColumnAndRow($col, $row)->getXfIndex()] = true;
382 }
383 }
384
385 break;
386 }
387
388 // clone each of the affected styles, apply the style array, and add the new styles to the workbook
389 $workbook = $this->getActiveSheet()->getParent();
390 foreach ($oldXfIndexes as $oldXfIndex => $dummy) {
391 $style = $workbook->getCellXfByIndex($oldXfIndex);
392 $newStyle = clone $style;
393 $newStyle->applyFromArray($pStyles);
394
395 if ($existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode())) {
396 // there is already such cell Xf in our collection
397 $newXfIndexes[$oldXfIndex] = $existingStyle->getIndex();
398 } else {
399 // we don't have such a cell Xf, need to add
400 $workbook->addCellXf($newStyle);
401 $newXfIndexes[$oldXfIndex] = $newStyle->getIndex();
402 }
403 }
404
405 // Loop through columns, rows, or cells again and update the XF index
406 switch ($selectionType) {
407 case 'COLUMN':
408 for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
409 $columnDimension = $this->getActiveSheet()->getColumnDimensionByColumn($col);
410 $oldXfIndex = $columnDimension->getXfIndex();
411 $columnDimension->setXfIndex($newXfIndexes[$oldXfIndex]);
412 }
413
414 break;
415 case 'ROW':
416 for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
417 $rowDimension = $this->getActiveSheet()->getRowDimension($row);
418 $oldXfIndex = $rowDimension->getXfIndex() === null ?
419 0 : $rowDimension->getXfIndex(); // row without explicit style should be formatted based on default style
420 $rowDimension->setXfIndex($newXfIndexes[$oldXfIndex]);
421 }
422
423 break;
424 case 'CELL':
425 for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
426 for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
427 $cell = $this->getActiveSheet()->getCellByColumnAndRow($col, $row);
428 $oldXfIndex = $cell->getXfIndex();
429 $cell->setXfIndex($newXfIndexes[$oldXfIndex]);
430 }
431 }
432
433 break;
434 }
435 } else {
436 // not a supervisor, just apply the style array directly on style object
437 if (isset($pStyles['fill'])) {
438 $this->getFill()->applyFromArray($pStyles['fill']);
439 }
440 if (isset($pStyles['font'])) {
441 $this->getFont()->applyFromArray($pStyles['font']);
442 }
443 if (isset($pStyles['borders'])) {
444 $this->getBorders()->applyFromArray($pStyles['borders']);
445 }
446 if (isset($pStyles['alignment'])) {
447 $this->getAlignment()->applyFromArray($pStyles['alignment']);
448 }
449 if (isset($pStyles['numberFormat'])) {
450 $this->getNumberFormat()->applyFromArray($pStyles['numberFormat']);
451 }
452 if (isset($pStyles['protection'])) {
453 $this->getProtection()->applyFromArray($pStyles['protection']);
454 }
455 if (isset($pStyles['quotePrefix'])) {
456 $this->quotePrefix = $pStyles['quotePrefix'];
457 }
458 }
459
460 return $this;
461 }
462
463 /**
464 * Get Fill.
465 *
466 * @return Fill
467 */
468 public function getFill()
469 {
470 return $this->fill;
471 }
472
473 /**
474 * Get Font.
475 *
476 * @return Font
477 */
478 public function getFont()
479 {
480 return $this->font;
481 }
482
483 /**
484 * Set font.
485 *
486 * @param Font $font
487 *
488 * @return Style
489 */
490 public function setFont(Font $font)
491 {
492 $this->font = $font;
493
494 return $this;
495 }
496
497 /**
498 * Get Borders.
499 *
500 * @return Borders
501 */
502 public function getBorders()
503 {
504 return $this->borders;
505 }
506
507 /**
508 * Get Alignment.
509 *
510 * @return Alignment
511 */
512 public function getAlignment()
513 {
514 return $this->alignment;
515 }
516
517 /**
518 * Get Number Format.
519 *
520 * @return NumberFormat
521 */
522 public function getNumberFormat()
523 {
524 return $this->numberFormat;
525 }
526
527 /**
528 * Get Conditional Styles. Only used on supervisor.
529 *
530 * @return Conditional[]
531 */
532 public function getConditionalStyles()
533 {
534 return $this->getActiveSheet()->getConditionalStyles($this->getActiveCell());
535 }
536
537 /**
538 * Set Conditional Styles. Only used on supervisor.
539 *
540 * @param Conditional[] $pValue Array of conditional styles
541 *
542 * @return Style
543 */
544 public function setConditionalStyles(array $pValue)
545 {
546 $this->getActiveSheet()->setConditionalStyles($this->getSelectedCells(), $pValue);
547
548 return $this;
549 }
550
551 /**
552 * Get Protection.
553 *
554 * @return Protection
555 */
556 public function getProtection()
557 {
558 return $this->protection;
559 }
560
561 /**
562 * Get quote prefix.
563 *
564 * @return bool
565 */
566 public function getQuotePrefix()
567 {
568 if ($this->isSupervisor) {
569 return $this->getSharedComponent()->getQuotePrefix();
570 }
571
572 return $this->quotePrefix;
573 }
574
575 /**
576 * Set quote prefix.
577 *
578 * @param bool $pValue
579 *
580 * @return Style
581 */
582 public function setQuotePrefix($pValue)
583 {
584 if ($pValue == '') {
585 $pValue = false;
586 }
587 if ($this->isSupervisor) {
588 $styleArray = ['quotePrefix' => $pValue];
589 $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
590 } else {
591 $this->quotePrefix = (bool) $pValue;
592 }
593
594 return $this;
595 }
596
597 /**
598 * Get hash code.
599 *
600 * @return string Hash code
601 */
602 public function getHashCode()
603 {
604 $hashConditionals = '';
605 foreach ($this->conditionalStyles as $conditional) {
606 $hashConditionals .= $conditional->getHashCode();
607 }
608
609 return md5(
610 $this->fill->getHashCode() .
611 $this->font->getHashCode() .
612 $this->borders->getHashCode() .
613 $this->alignment->getHashCode() .
614 $this->numberFormat->getHashCode() .
615 $hashConditionals .
616 $this->protection->getHashCode() .
617 ($this->quotePrefix ? 't' : 'f') .
618 __CLASS__
619 );
620 }
621
622 /**
623 * Get own index in style collection.
624 *
625 * @return int
626 */
627 public function getIndex()
628 {
629 return $this->index;
630 }
631
632 /**
633 * Set own index in style collection.
634 *
635 * @param int $pValue
636 */
637 public function setIndex($pValue)
638 {
639 $this->index = $pValue;
640 }
641 }
642