1<?php 2 3namespace PhpOffice\PhpSpreadsheet\Writer\Xls; 4 5use PhpOffice\PhpSpreadsheet\Style\Alignment; 6use PhpOffice\PhpSpreadsheet\Style\Border; 7use PhpOffice\PhpSpreadsheet\Style\Borders; 8use PhpOffice\PhpSpreadsheet\Style\Fill; 9use PhpOffice\PhpSpreadsheet\Style\Protection; 10use PhpOffice\PhpSpreadsheet\Style\Style; 11 12// Original file header of PEAR::Spreadsheet_Excel_Writer_Format (used as the base for this class): 13// ----------------------------------------------------------------------------------------- 14// /* 15// * Module written/ported by Xavier Noguer <xnoguer@rezebra.com> 16// * 17// * The majority of this is _NOT_ my code. I simply ported it from the 18// * PERL Spreadsheet::WriteExcel module. 19// * 20// * The author of the Spreadsheet::WriteExcel module is John McNamara 21// * <jmcnamara@cpan.org> 22// * 23// * I _DO_ maintain this code, and John McNamara has nothing to do with the 24// * porting of this code to PHP. Any questions directly related to this 25// * class library should be directed to me. 26// * 27// * License Information: 28// * 29// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets 30// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com 31// * 32// * This library is free software; you can redistribute it and/or 33// * modify it under the terms of the GNU Lesser General Public 34// * License as published by the Free Software Foundation; either 35// * version 2.1 of the License, or (at your option) any later version. 36// * 37// * This library is distributed in the hope that it will be useful, 38// * but WITHOUT ANY WARRANTY; without even the implied warranty of 39// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 40// * Lesser General Public License for more details. 41// * 42// * You should have received a copy of the GNU Lesser General Public 43// * License along with this library; if not, write to the Free Software 44// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 45// */ 46class Xf 47{ 48 /** 49 * Style XF or a cell XF ? 50 * 51 * @var bool 52 */ 53 private $isStyleXf; 54 55 /** 56 * Index to the FONT record. Index 4 does not exist. 57 * 58 * @var int 59 */ 60 private $fontIndex; 61 62 /** 63 * An index (2 bytes) to a FORMAT record (number format). 64 * 65 * @var int 66 */ 67 private $numberFormatIndex; 68 69 /** 70 * 1 bit, apparently not used. 71 * 72 * @var int 73 */ 74 private $textJustLast; 75 76 /** 77 * The cell's foreground color. 78 * 79 * @var int 80 */ 81 private $foregroundColor; 82 83 /** 84 * The cell's background color. 85 * 86 * @var int 87 */ 88 private $backgroundColor; 89 90 /** 91 * Color of the bottom border of the cell. 92 * 93 * @var int 94 */ 95 private $bottomBorderColor; 96 97 /** 98 * Color of the top border of the cell. 99 * 100 * @var int 101 */ 102 private $topBorderColor; 103 104 /** 105 * Color of the left border of the cell. 106 * 107 * @var int 108 */ 109 private $leftBorderColor; 110 111 /** 112 * Color of the right border of the cell. 113 * 114 * @var int 115 */ 116 private $rightBorderColor; 117 118 /** 119 * Constructor. 120 * 121 * @param Style $style The XF format 122 */ 123 public function __construct(Style $style) 124 { 125 $this->isStyleXf = false; 126 $this->fontIndex = 0; 127 128 $this->numberFormatIndex = 0; 129 130 $this->textJustLast = 0; 131 132 $this->foregroundColor = 0x40; 133 $this->backgroundColor = 0x41; 134 135 $this->_diag = 0; 136 137 $this->bottomBorderColor = 0x40; 138 $this->topBorderColor = 0x40; 139 $this->leftBorderColor = 0x40; 140 $this->rightBorderColor = 0x40; 141 $this->_diag_color = 0x40; 142 $this->_style = $style; 143 } 144 145 /** 146 * Generate an Excel BIFF XF record (style or cell). 147 * 148 * @return string The XF record 149 */ 150 public function writeXf() 151 { 152 // Set the type of the XF record and some of the attributes. 153 if ($this->isStyleXf) { 154 $style = 0xFFF5; 155 } else { 156 $style = self::mapLocked($this->_style->getProtection()->getLocked()); 157 $style |= self::mapHidden($this->_style->getProtection()->getHidden()) << 1; 158 } 159 160 // Flags to indicate if attributes have been set. 161 $atr_num = ($this->numberFormatIndex != 0) ? 1 : 0; 162 $atr_fnt = ($this->fontIndex != 0) ? 1 : 0; 163 $atr_alc = ((int) $this->_style->getAlignment()->getWrapText()) ? 1 : 0; 164 $atr_bdr = (self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) || 165 self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) || 166 self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()) || 167 self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle())) ? 1 : 0; 168 $atr_pat = (($this->foregroundColor != 0x40) || 169 ($this->backgroundColor != 0x41) || 170 self::mapFillType($this->_style->getFill()->getFillType())) ? 1 : 0; 171 $atr_prot = self::mapLocked($this->_style->getProtection()->getLocked()) 172 | self::mapHidden($this->_style->getProtection()->getHidden()); 173 174 // Zero the default border colour if the border has not been set. 175 if (self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) == 0) { 176 $this->bottomBorderColor = 0; 177 } 178 if (self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) == 0) { 179 $this->topBorderColor = 0; 180 } 181 if (self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle()) == 0) { 182 $this->rightBorderColor = 0; 183 } 184 if (self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()) == 0) { 185 $this->leftBorderColor = 0; 186 } 187 if (self::mapBorderStyle($this->_style->getBorders()->getDiagonal()->getBorderStyle()) == 0) { 188 $this->_diag_color = 0; 189 } 190 191 $record = 0x00E0; // Record identifier 192 $length = 0x0014; // Number of bytes to follow 193 194 $ifnt = $this->fontIndex; // Index to FONT record 195 $ifmt = $this->numberFormatIndex; // Index to FORMAT record 196 197 $align = $this->mapHAlign($this->_style->getAlignment()->getHorizontal()); // Alignment 198 $align |= (int) $this->_style->getAlignment()->getWrapText() << 3; 199 $align |= self::mapVAlign($this->_style->getAlignment()->getVertical()) << 4; 200 $align |= $this->textJustLast << 7; 201 202 $used_attrib = $atr_num << 2; 203 $used_attrib |= $atr_fnt << 3; 204 $used_attrib |= $atr_alc << 4; 205 $used_attrib |= $atr_bdr << 5; 206 $used_attrib |= $atr_pat << 6; 207 $used_attrib |= $atr_prot << 7; 208 209 $icv = $this->foregroundColor; // fg and bg pattern colors 210 $icv |= $this->backgroundColor << 7; 211 212 $border1 = self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()); // Border line style and color 213 $border1 |= self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle()) << 4; 214 $border1 |= self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) << 8; 215 $border1 |= self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) << 12; 216 $border1 |= $this->leftBorderColor << 16; 217 $border1 |= $this->rightBorderColor << 23; 218 219 $diagonalDirection = $this->_style->getBorders()->getDiagonalDirection(); 220 $diag_tl_to_rb = $diagonalDirection == Borders::DIAGONAL_BOTH 221 || $diagonalDirection == Borders::DIAGONAL_DOWN; 222 $diag_tr_to_lb = $diagonalDirection == Borders::DIAGONAL_BOTH 223 || $diagonalDirection == Borders::DIAGONAL_UP; 224 $border1 |= $diag_tl_to_rb << 30; 225 $border1 |= $diag_tr_to_lb << 31; 226 227 $border2 = $this->topBorderColor; // Border color 228 $border2 |= $this->bottomBorderColor << 7; 229 $border2 |= $this->_diag_color << 14; 230 $border2 |= self::mapBorderStyle($this->_style->getBorders()->getDiagonal()->getBorderStyle()) << 21; 231 $border2 |= self::mapFillType($this->_style->getFill()->getFillType()) << 26; 232 233 $header = pack('vv', $record, $length); 234 235 //BIFF8 options: identation, shrinkToFit and text direction 236 $biff8_options = $this->_style->getAlignment()->getIndent(); 237 $biff8_options |= (int) $this->_style->getAlignment()->getShrinkToFit() << 4; 238 239 $data = pack('vvvC', $ifnt, $ifmt, $style, $align); 240 $data .= pack('CCC', self::mapTextRotation($this->_style->getAlignment()->getTextRotation()), $biff8_options, $used_attrib); 241 $data .= pack('VVv', $border1, $border2, $icv); 242 243 return $header . $data; 244 } 245 246 /** 247 * Is this a style XF ? 248 * 249 * @param bool $value 250 */ 251 public function setIsStyleXf($value) 252 { 253 $this->isStyleXf = $value; 254 } 255 256 /** 257 * Sets the cell's bottom border color. 258 * 259 * @param int $colorIndex Color index 260 */ 261 public function setBottomColor($colorIndex) 262 { 263 $this->bottomBorderColor = $colorIndex; 264 } 265 266 /** 267 * Sets the cell's top border color. 268 * 269 * @param int $colorIndex Color index 270 */ 271 public function setTopColor($colorIndex) 272 { 273 $this->topBorderColor = $colorIndex; 274 } 275 276 /** 277 * Sets the cell's left border color. 278 * 279 * @param int $colorIndex Color index 280 */ 281 public function setLeftColor($colorIndex) 282 { 283 $this->leftBorderColor = $colorIndex; 284 } 285 286 /** 287 * Sets the cell's right border color. 288 * 289 * @param int $colorIndex Color index 290 */ 291 public function setRightColor($colorIndex) 292 { 293 $this->rightBorderColor = $colorIndex; 294 } 295 296 /** 297 * Sets the cell's diagonal border color. 298 * 299 * @param int $colorIndex Color index 300 */ 301 public function setDiagColor($colorIndex) 302 { 303 $this->_diag_color = $colorIndex; 304 } 305 306 /** 307 * Sets the cell's foreground color. 308 * 309 * @param int $colorIndex Color index 310 */ 311 public function setFgColor($colorIndex) 312 { 313 $this->foregroundColor = $colorIndex; 314 } 315 316 /** 317 * Sets the cell's background color. 318 * 319 * @param int $colorIndex Color index 320 */ 321 public function setBgColor($colorIndex) 322 { 323 $this->backgroundColor = $colorIndex; 324 } 325 326 /** 327 * Sets the index to the number format record 328 * It can be date, time, currency, etc... 329 * 330 * @param int $numberFormatIndex Index to format record 331 */ 332 public function setNumberFormatIndex($numberFormatIndex) 333 { 334 $this->numberFormatIndex = $numberFormatIndex; 335 } 336 337 /** 338 * Set the font index. 339 * 340 * @param int $value Font index, note that value 4 does not exist 341 */ 342 public function setFontIndex($value) 343 { 344 $this->fontIndex = $value; 345 } 346 347 /** 348 * Map of BIFF2-BIFF8 codes for border styles. 349 * 350 * @var array of int 351 */ 352 private static $mapBorderStyles = [ 353 Border::BORDER_NONE => 0x00, 354 Border::BORDER_THIN => 0x01, 355 Border::BORDER_MEDIUM => 0x02, 356 Border::BORDER_DASHED => 0x03, 357 Border::BORDER_DOTTED => 0x04, 358 Border::BORDER_THICK => 0x05, 359 Border::BORDER_DOUBLE => 0x06, 360 Border::BORDER_HAIR => 0x07, 361 Border::BORDER_MEDIUMDASHED => 0x08, 362 Border::BORDER_DASHDOT => 0x09, 363 Border::BORDER_MEDIUMDASHDOT => 0x0A, 364 Border::BORDER_DASHDOTDOT => 0x0B, 365 Border::BORDER_MEDIUMDASHDOTDOT => 0x0C, 366 Border::BORDER_SLANTDASHDOT => 0x0D, 367 ]; 368 369 /** 370 * Map border style. 371 * 372 * @param string $borderStyle 373 * 374 * @return int 375 */ 376 private static function mapBorderStyle($borderStyle) 377 { 378 if (isset(self::$mapBorderStyles[$borderStyle])) { 379 return self::$mapBorderStyles[$borderStyle]; 380 } 381 382 return 0x00; 383 } 384 385 /** 386 * Map of BIFF2-BIFF8 codes for fill types. 387 * 388 * @var array of int 389 */ 390 private static $mapFillTypes = [ 391 Fill::FILL_NONE => 0x00, 392 Fill::FILL_SOLID => 0x01, 393 Fill::FILL_PATTERN_MEDIUMGRAY => 0x02, 394 Fill::FILL_PATTERN_DARKGRAY => 0x03, 395 Fill::FILL_PATTERN_LIGHTGRAY => 0x04, 396 Fill::FILL_PATTERN_DARKHORIZONTAL => 0x05, 397 Fill::FILL_PATTERN_DARKVERTICAL => 0x06, 398 Fill::FILL_PATTERN_DARKDOWN => 0x07, 399 Fill::FILL_PATTERN_DARKUP => 0x08, 400 Fill::FILL_PATTERN_DARKGRID => 0x09, 401 Fill::FILL_PATTERN_DARKTRELLIS => 0x0A, 402 Fill::FILL_PATTERN_LIGHTHORIZONTAL => 0x0B, 403 Fill::FILL_PATTERN_LIGHTVERTICAL => 0x0C, 404 Fill::FILL_PATTERN_LIGHTDOWN => 0x0D, 405 Fill::FILL_PATTERN_LIGHTUP => 0x0E, 406 Fill::FILL_PATTERN_LIGHTGRID => 0x0F, 407 Fill::FILL_PATTERN_LIGHTTRELLIS => 0x10, 408 Fill::FILL_PATTERN_GRAY125 => 0x11, 409 Fill::FILL_PATTERN_GRAY0625 => 0x12, 410 Fill::FILL_GRADIENT_LINEAR => 0x00, // does not exist in BIFF8 411 Fill::FILL_GRADIENT_PATH => 0x00, // does not exist in BIFF8 412 ]; 413 414 /** 415 * Map fill type. 416 * 417 * @param string $fillType 418 * 419 * @return int 420 */ 421 private static function mapFillType($fillType) 422 { 423 if (isset(self::$mapFillTypes[$fillType])) { 424 return self::$mapFillTypes[$fillType]; 425 } 426 427 return 0x00; 428 } 429 430 /** 431 * Map of BIFF2-BIFF8 codes for horizontal alignment. 432 * 433 * @var array of int 434 */ 435 private static $mapHAlignments = [ 436 Alignment::HORIZONTAL_GENERAL => 0, 437 Alignment::HORIZONTAL_LEFT => 1, 438 Alignment::HORIZONTAL_CENTER => 2, 439 Alignment::HORIZONTAL_RIGHT => 3, 440 Alignment::HORIZONTAL_FILL => 4, 441 Alignment::HORIZONTAL_JUSTIFY => 5, 442 Alignment::HORIZONTAL_CENTER_CONTINUOUS => 6, 443 ]; 444 445 /** 446 * Map to BIFF2-BIFF8 codes for horizontal alignment. 447 * 448 * @param string $hAlign 449 * 450 * @return int 451 */ 452 private function mapHAlign($hAlign) 453 { 454 if (isset(self::$mapHAlignments[$hAlign])) { 455 return self::$mapHAlignments[$hAlign]; 456 } 457 458 return 0; 459 } 460 461 /** 462 * Map of BIFF2-BIFF8 codes for vertical alignment. 463 * 464 * @var array of int 465 */ 466 private static $mapVAlignments = [ 467 Alignment::VERTICAL_TOP => 0, 468 Alignment::VERTICAL_CENTER => 1, 469 Alignment::VERTICAL_BOTTOM => 2, 470 Alignment::VERTICAL_JUSTIFY => 3, 471 ]; 472 473 /** 474 * Map to BIFF2-BIFF8 codes for vertical alignment. 475 * 476 * @param string $vAlign 477 * 478 * @return int 479 */ 480 private static function mapVAlign($vAlign) 481 { 482 if (isset(self::$mapVAlignments[$vAlign])) { 483 return self::$mapVAlignments[$vAlign]; 484 } 485 486 return 2; 487 } 488 489 /** 490 * Map to BIFF8 codes for text rotation angle. 491 * 492 * @param int $textRotation 493 * 494 * @return int 495 */ 496 private static function mapTextRotation($textRotation) 497 { 498 if ($textRotation >= 0) { 499 return $textRotation; 500 } elseif ($textRotation == -165) { 501 return 255; 502 } elseif ($textRotation < 0) { 503 return 90 - $textRotation; 504 } 505 } 506 507 /** 508 * Map locked. 509 * 510 * @param string $locked 511 * 512 * @return int 513 */ 514 private static function mapLocked($locked) 515 { 516 switch ($locked) { 517 case Protection::PROTECTION_INHERIT: 518 return 1; 519 case Protection::PROTECTION_PROTECTED: 520 return 1; 521 case Protection::PROTECTION_UNPROTECTED: 522 return 0; 523 default: 524 return 1; 525 } 526 } 527 528 /** 529 * Map hidden. 530 * 531 * @param string $hidden 532 * 533 * @return int 534 */ 535 private static function mapHidden($hidden) 536 { 537 switch ($hidden) { 538 case Protection::PROTECTION_INHERIT: 539 return 0; 540 case Protection::PROTECTION_PROTECTED: 541 return 1; 542 case Protection::PROTECTION_UNPROTECTED: 543 return 0; 544 default: 545 return 0; 546 } 547 } 548} 549