1<?php 2 3namespace PhpOffice\PhpSpreadsheet\Writer\Xls; 4 5use PhpOffice\PhpSpreadsheet\Style\Alignment; 6use PhpOffice\PhpSpreadsheet\Style\Borders; 7use PhpOffice\PhpSpreadsheet\Style\Protection; 8use PhpOffice\PhpSpreadsheet\Style\Style; 9use PhpOffice\PhpSpreadsheet\Writer\Xls\Style\CellAlignment; 10use PhpOffice\PhpSpreadsheet\Writer\Xls\Style\CellBorder; 11use PhpOffice\PhpSpreadsheet\Writer\Xls\Style\CellFill; 12 13// Original file header of PEAR::Spreadsheet_Excel_Writer_Format (used as the base for this class): 14// ----------------------------------------------------------------------------------------- 15// /* 16// * Module written/ported by Xavier Noguer <xnoguer@rezebra.com> 17// * 18// * The majority of this is _NOT_ my code. I simply ported it from the 19// * PERL Spreadsheet::WriteExcel module. 20// * 21// * The author of the Spreadsheet::WriteExcel module is John McNamara 22// * <jmcnamara@cpan.org> 23// * 24// * I _DO_ maintain this code, and John McNamara has nothing to do with the 25// * porting of this code to PHP. Any questions directly related to this 26// * class library should be directed to me. 27// * 28// * License Information: 29// * 30// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets 31// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com 32// * 33// * This library is free software; you can redistribute it and/or 34// * modify it under the terms of the GNU Lesser General Public 35// * License as published by the Free Software Foundation; either 36// * version 2.1 of the License, or (at your option) any later version. 37// * 38// * This library is distributed in the hope that it will be useful, 39// * but WITHOUT ANY WARRANTY; without even the implied warranty of 40// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 41// * Lesser General Public License for more details. 42// * 43// * You should have received a copy of the GNU Lesser General Public 44// * License along with this library; if not, write to the Free Software 45// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 46// */ 47class Xf 48{ 49 /** 50 * Style XF or a cell XF ? 51 * 52 * @var bool 53 */ 54 private $isStyleXf; 55 56 /** 57 * Index to the FONT record. Index 4 does not exist. 58 * 59 * @var int 60 */ 61 private $fontIndex; 62 63 /** 64 * An index (2 bytes) to a FORMAT record (number format). 65 * 66 * @var int 67 */ 68 private $numberFormatIndex; 69 70 /** 71 * 1 bit, apparently not used. 72 * 73 * @var int 74 */ 75 private $textJustLast; 76 77 /** 78 * The cell's foreground color. 79 * 80 * @var int 81 */ 82 private $foregroundColor; 83 84 /** 85 * The cell's background color. 86 * 87 * @var int 88 */ 89 private $backgroundColor; 90 91 /** 92 * Color of the bottom border of the cell. 93 * 94 * @var int 95 */ 96 private $bottomBorderColor; 97 98 /** 99 * Color of the top border of the cell. 100 * 101 * @var int 102 */ 103 private $topBorderColor; 104 105 /** 106 * Color of the left border of the cell. 107 * 108 * @var int 109 */ 110 private $leftBorderColor; 111 112 /** 113 * Color of the right border of the cell. 114 * 115 * @var int 116 */ 117 private $rightBorderColor; 118 119 /** 120 * @var int 121 */ 122 private $diag; 123 124 /** 125 * @var int 126 */ 127 private $diagColor; 128 129 /** 130 * @var Style 131 */ 132 private $style; 133 134 /** 135 * Constructor. 136 * 137 * @param Style $style The XF format 138 */ 139 public function __construct(Style $style) 140 { 141 $this->isStyleXf = false; 142 $this->fontIndex = 0; 143 144 $this->numberFormatIndex = 0; 145 146 $this->textJustLast = 0; 147 148 $this->foregroundColor = 0x40; 149 $this->backgroundColor = 0x41; 150 151 $this->diag = 0; 152 153 $this->bottomBorderColor = 0x40; 154 $this->topBorderColor = 0x40; 155 $this->leftBorderColor = 0x40; 156 $this->rightBorderColor = 0x40; 157 $this->diagColor = 0x40; 158 $this->style = $style; 159 } 160 161 /** 162 * Generate an Excel BIFF XF record (style or cell). 163 * 164 * @return string The XF record 165 */ 166 public function writeXf() 167 { 168 // Set the type of the XF record and some of the attributes. 169 if ($this->isStyleXf) { 170 $style = 0xFFF5; 171 } else { 172 $style = self::mapLocked($this->style->getProtection()->getLocked()); 173 $style |= self::mapHidden($this->style->getProtection()->getHidden()) << 1; 174 } 175 176 // Flags to indicate if attributes have been set. 177 $atr_num = ($this->numberFormatIndex != 0) ? 1 : 0; 178 $atr_fnt = ($this->fontIndex != 0) ? 1 : 0; 179 $atr_alc = ((int) $this->style->getAlignment()->getWrapText()) ? 1 : 0; 180 $atr_bdr = (CellBorder::style($this->style->getBorders()->getBottom()) || 181 CellBorder::style($this->style->getBorders()->getTop()) || 182 CellBorder::style($this->style->getBorders()->getLeft()) || 183 CellBorder::style($this->style->getBorders()->getRight())) ? 1 : 0; 184 $atr_pat = ($this->foregroundColor != 0x40) ? 1 : 0; 185 $atr_pat = ($this->backgroundColor != 0x41) ? 1 : $atr_pat; 186 $atr_pat = CellFill::style($this->style->getFill()) ? 1 : $atr_pat; 187 $atr_prot = self::mapLocked($this->style->getProtection()->getLocked()) 188 | self::mapHidden($this->style->getProtection()->getHidden()); 189 190 // Zero the default border colour if the border has not been set. 191 if (CellBorder::style($this->style->getBorders()->getBottom()) == 0) { 192 $this->bottomBorderColor = 0; 193 } 194 if (CellBorder::style($this->style->getBorders()->getTop()) == 0) { 195 $this->topBorderColor = 0; 196 } 197 if (CellBorder::style($this->style->getBorders()->getRight()) == 0) { 198 $this->rightBorderColor = 0; 199 } 200 if (CellBorder::style($this->style->getBorders()->getLeft()) == 0) { 201 $this->leftBorderColor = 0; 202 } 203 if (CellBorder::style($this->style->getBorders()->getDiagonal()) == 0) { 204 $this->diagColor = 0; 205 } 206 207 $record = 0x00E0; // Record identifier 208 $length = 0x0014; // Number of bytes to follow 209 210 $ifnt = $this->fontIndex; // Index to FONT record 211 $ifmt = $this->numberFormatIndex; // Index to FORMAT record 212 213 // Alignment 214 $align = CellAlignment::horizontal($this->style->getAlignment()); 215 $align |= CellAlignment::wrap($this->style->getAlignment()) << 3; 216 $align |= CellAlignment::vertical($this->style->getAlignment()) << 4; 217 $align |= $this->textJustLast << 7; 218 219 $used_attrib = $atr_num << 2; 220 $used_attrib |= $atr_fnt << 3; 221 $used_attrib |= $atr_alc << 4; 222 $used_attrib |= $atr_bdr << 5; 223 $used_attrib |= $atr_pat << 6; 224 $used_attrib |= $atr_prot << 7; 225 226 $icv = $this->foregroundColor; // fg and bg pattern colors 227 $icv |= $this->backgroundColor << 7; 228 229 $border1 = CellBorder::style($this->style->getBorders()->getLeft()); // Border line style and color 230 $border1 |= CellBorder::style($this->style->getBorders()->getRight()) << 4; 231 $border1 |= CellBorder::style($this->style->getBorders()->getTop()) << 8; 232 $border1 |= CellBorder::style($this->style->getBorders()->getBottom()) << 12; 233 $border1 |= $this->leftBorderColor << 16; 234 $border1 |= $this->rightBorderColor << 23; 235 236 $diagonalDirection = $this->style->getBorders()->getDiagonalDirection(); 237 $diag_tl_to_rb = $diagonalDirection == Borders::DIAGONAL_BOTH 238 || $diagonalDirection == Borders::DIAGONAL_DOWN; 239 $diag_tr_to_lb = $diagonalDirection == Borders::DIAGONAL_BOTH 240 || $diagonalDirection == Borders::DIAGONAL_UP; 241 $border1 |= $diag_tl_to_rb << 30; 242 $border1 |= $diag_tr_to_lb << 31; 243 244 $border2 = $this->topBorderColor; // Border color 245 $border2 |= $this->bottomBorderColor << 7; 246 $border2 |= $this->diagColor << 14; 247 $border2 |= CellBorder::style($this->style->getBorders()->getDiagonal()) << 21; 248 $border2 |= CellFill::style($this->style->getFill()) << 26; 249 250 $header = pack('vv', $record, $length); 251 252 //BIFF8 options: identation, shrinkToFit and text direction 253 $biff8_options = $this->style->getAlignment()->getIndent(); 254 $biff8_options |= (int) $this->style->getAlignment()->getShrinkToFit() << 4; 255 256 $data = pack('vvvC', $ifnt, $ifmt, $style, $align); 257 $data .= pack('CCC', self::mapTextRotation($this->style->getAlignment()->getTextRotation()), $biff8_options, $used_attrib); 258 $data .= pack('VVv', $border1, $border2, $icv); 259 260 return $header . $data; 261 } 262 263 /** 264 * Is this a style XF ? 265 * 266 * @param bool $value 267 */ 268 public function setIsStyleXf($value): void 269 { 270 $this->isStyleXf = $value; 271 } 272 273 /** 274 * Sets the cell's bottom border color. 275 * 276 * @param int $colorIndex Color index 277 */ 278 public function setBottomColor($colorIndex): void 279 { 280 $this->bottomBorderColor = $colorIndex; 281 } 282 283 /** 284 * Sets the cell's top border color. 285 * 286 * @param int $colorIndex Color index 287 */ 288 public function setTopColor($colorIndex): void 289 { 290 $this->topBorderColor = $colorIndex; 291 } 292 293 /** 294 * Sets the cell's left border color. 295 * 296 * @param int $colorIndex Color index 297 */ 298 public function setLeftColor($colorIndex): void 299 { 300 $this->leftBorderColor = $colorIndex; 301 } 302 303 /** 304 * Sets the cell's right border color. 305 * 306 * @param int $colorIndex Color index 307 */ 308 public function setRightColor($colorIndex): void 309 { 310 $this->rightBorderColor = $colorIndex; 311 } 312 313 /** 314 * Sets the cell's diagonal border color. 315 * 316 * @param int $colorIndex Color index 317 */ 318 public function setDiagColor($colorIndex): void 319 { 320 $this->diagColor = $colorIndex; 321 } 322 323 /** 324 * Sets the cell's foreground color. 325 * 326 * @param int $colorIndex Color index 327 */ 328 public function setFgColor($colorIndex): void 329 { 330 $this->foregroundColor = $colorIndex; 331 } 332 333 /** 334 * Sets the cell's background color. 335 * 336 * @param int $colorIndex Color index 337 */ 338 public function setBgColor($colorIndex): void 339 { 340 $this->backgroundColor = $colorIndex; 341 } 342 343 /** 344 * Sets the index to the number format record 345 * It can be date, time, currency, etc... 346 * 347 * @param int $numberFormatIndex Index to format record 348 */ 349 public function setNumberFormatIndex($numberFormatIndex): void 350 { 351 $this->numberFormatIndex = $numberFormatIndex; 352 } 353 354 /** 355 * Set the font index. 356 * 357 * @param int $value Font index, note that value 4 does not exist 358 */ 359 public function setFontIndex($value): void 360 { 361 $this->fontIndex = $value; 362 } 363 364 /** 365 * Map to BIFF8 codes for text rotation angle. 366 * 367 * @param int $textRotation 368 * 369 * @return int 370 */ 371 private static function mapTextRotation($textRotation) 372 { 373 if ($textRotation >= 0) { 374 return $textRotation; 375 } 376 if ($textRotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) { 377 return Alignment::TEXTROTATION_STACK_EXCEL; 378 } 379 380 return 90 - $textRotation; 381 } 382 383 private const LOCK_ARRAY = [ 384 Protection::PROTECTION_INHERIT => 1, 385 Protection::PROTECTION_PROTECTED => 1, 386 Protection::PROTECTION_UNPROTECTED => 0, 387 ]; 388 389 /** 390 * Map locked values. 391 * 392 * @param string $locked 393 * 394 * @return int 395 */ 396 private static function mapLocked($locked) 397 { 398 return array_key_exists($locked, self::LOCK_ARRAY) ? self::LOCK_ARRAY[$locked] : 1; 399 } 400 401 private const HIDDEN_ARRAY = [ 402 Protection::PROTECTION_INHERIT => 0, 403 Protection::PROTECTION_PROTECTED => 1, 404 Protection::PROTECTION_UNPROTECTED => 0, 405 ]; 406 407 /** 408 * Map hidden. 409 * 410 * @param string $hidden 411 * 412 * @return int 413 */ 414 private static function mapHidden($hidden) 415 { 416 return array_key_exists($hidden, self::HIDDEN_ARRAY) ? self::HIDDEN_ARRAY[$hidden] : 0; 417 } 418} 419