Style.php 23 KB

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