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