style.cls.php 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947
  1. <?php
  2. /**
  3. * DOMPDF - PHP5 HTML to PDF renderer
  4. *
  5. * File: $RCSfile: style.cls.php,v $
  6. * Created on: 2004-06-01
  7. *
  8. * Copyright (c) 2004 - Benj Carson <benjcarson@digitaljunkies.ca>
  9. *
  10. * This library is free software; you can redistribute it and/or
  11. * modify it under the terms of the GNU Lesser General Public
  12. * License as published by the Free Software Foundation; either
  13. * version 2.1 of the License, or (at your option) any later version.
  14. *
  15. * This library is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  18. * Lesser General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Lesser General Public License
  21. * along with this library in the file LICENSE.LGPL; if not, write to the
  22. * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
  23. * 02111-1307 USA
  24. *
  25. * Alternatively, you may distribute this software under the terms of the
  26. * PHP License, version 3.0 or later. A copy of this license should have
  27. * been distributed with this file in the file LICENSE.PHP . If this is not
  28. * the case, you can obtain a copy at http://www.php.net/license/3_0.txt.
  29. *
  30. * The latest version of DOMPDF might be available at:
  31. * http://www.dompdf.com/
  32. *
  33. * @link http://www.dompdf.com/
  34. * @copyright 2004 Benj Carson
  35. * @author Benj Carson <benjcarson@digitaljunkies.ca>
  36. * @contributor Helmut Tischer <htischer@weihenstephan.org>
  37. * @package dompdf
  38. *
  39. * Changes
  40. * @contributor Helmut Tischer <htischer@weihenstephan.org>
  41. * @version 0.5.1.htischer.20090507
  42. * - Fix px to pt conversion according to DOMPDF_DPI
  43. * - Recognize css styles with !important attribute, and store !important attribute within style
  44. * - Propagate !important by inherit and sequences of styles with merge.
  45. * - Add missing style property cache flushes for consistent rendering, e.g. on explicte assignments
  46. * - Add important set/get for access from outside of class
  47. * - Fix font_family search path with multiple fonts list in css attribute:
  48. * On missing font, do not immediately fall back to default font,
  49. * but try subsequent fonts in search chain. Only when none found, explicitely
  50. * refer to default font.
  51. * - Allow read of background individual properties
  52. * - Add support for individual styles background-position, background-attachment, background-repeat
  53. * - Complete style components of list-style
  54. * - Add support for combined styles in addition to individual styles
  55. * like {border: red 1px solid;}, { border-width: 1px;}, { border-right-color: red; } ...
  56. * for font, background
  57. * - Propagate attributes including !important from combined style to individual component
  58. * for border, background, padding, margin, font, list_style
  59. * - Refactor common code of border, background, padding, margin, font, list_style
  60. * - Refactor common code of list-style-image and background-image
  61. * - special treatment of css images "none" instead of url(...), otherwise would prepend string "none" with path name
  62. * - Added comments
  63. * - Added debug output
  64. * @contributor Helmut Tischer <htischer@weihenstephan.org>
  65. * @version dompdf_trunk_with_helmut_mods.20090524
  66. * - Allow superflous white space and string delimiter in font search path.
  67. * - Restore lost change of default font of above
  68. * @version 20090610
  69. * - Allow absolute path from web server root as html image reference
  70. * - More accurate handling of css property cache consistency
  71. */
  72. /* $Id: style.cls.php 217 2010-03-11 23:03:57Z ryan.masten $ */
  73. /**
  74. * Represents CSS properties.
  75. *
  76. * The Style class is responsible for handling and storing CSS properties.
  77. * It includes methods to resolve colours and lengths, as well as getters &
  78. * setters for many CSS properites.
  79. *
  80. * Actual CSS parsing is performed in the {@link Stylesheet} class.
  81. *
  82. * @package dompdf
  83. */
  84. class Style {
  85. /**
  86. * Default font size, in points.
  87. *
  88. * @var float
  89. */
  90. static $default_font_size = 12;
  91. /**
  92. * Default line height, as a fraction of the font size.
  93. *
  94. * @var float
  95. */
  96. static $default_line_height = 1.2;
  97. /**
  98. * List of all inline types. Should really be a constant.
  99. *
  100. * @var array
  101. */
  102. static $INLINE_TYPES = array("inline");
  103. /**
  104. * List of all block types. Should really be a constant.
  105. *
  106. * @var array
  107. */
  108. static $BLOCK_TYPES = array("block","inline-block", "table-cell", "list-item");
  109. /**
  110. * List of all table types. Should really be a constant.
  111. *
  112. * @var array;
  113. */
  114. static $TABLE_TYPES = array("table", "inline-table");
  115. /**
  116. * List of valid border styles. Should also really be a constant.
  117. *
  118. * @var array
  119. */
  120. static $BORDER_STYLES = array("none", "hidden", "dotted", "dashed", "solid",
  121. "double", "groove", "ridge", "inset", "outset");
  122. /**
  123. * Default style values.
  124. *
  125. * @link http://www.w3.org/TR/CSS21/propidx.html
  126. *
  127. * @var array
  128. */
  129. static protected $_defaults = null;
  130. /**
  131. * List of inherited properties
  132. *
  133. * @link http://www.w3.org/TR/CSS21/propidx.html
  134. *
  135. * @var array
  136. */
  137. static protected $_inherited = null;
  138. /**
  139. * The stylesheet this style belongs to
  140. *
  141. * @see Stylesheet
  142. * @var Stylesheet
  143. */
  144. protected $_stylesheet; // stylesheet this style is attached to
  145. /**
  146. * Main array of all CSS properties & values
  147. *
  148. * @var array
  149. */
  150. protected $_props;
  151. /* var instead of protected would allow access outside of class */
  152. protected $_important_props;
  153. /**
  154. * Cached property values
  155. *
  156. * @var array
  157. */
  158. protected $_prop_cache;
  159. /**
  160. * Font size of parent element in document tree. Used for relative font
  161. * size resolution.
  162. *
  163. * @var float
  164. */
  165. protected $_parent_font_size; // Font size of parent element
  166. // private members
  167. /**
  168. * True once the font size is resolved absolutely
  169. *
  170. * @var bool
  171. */
  172. private $__font_size_calculated; // Cache flag
  173. /**
  174. * Class constructor
  175. *
  176. * @param Stylesheet $stylesheet the stylesheet this Style is associated with.
  177. */
  178. function __construct(Stylesheet $stylesheet) {
  179. $this->_props = array();
  180. $this->_important_props = array();
  181. $this->_stylesheet = $stylesheet;
  182. $this->_parent_font_size = null;
  183. $this->__font_size_calculated = false;
  184. if ( !isset(self::$_defaults) ) {
  185. // Shorthand
  186. $d =& self::$_defaults;
  187. // All CSS 2.1 properties, and their default values
  188. $d["azimuth"] = "center";
  189. $d["background_attachment"] = "scroll";
  190. $d["background_color"] = "transparent";
  191. $d["background_image"] = "none";
  192. $d["background_position"] = "0% 0%";
  193. $d["background_repeat"] = "repeat";
  194. $d["background"] = "";
  195. $d["border_collapse"] = "separate";
  196. $d["border_color"] = "";
  197. $d["border_spacing"] = "0";
  198. $d["border_style"] = "";
  199. $d["border_top"] = "";
  200. $d["border_right"] = "";
  201. $d["border_bottom"] = "";
  202. $d["border_left"] = "";
  203. $d["border_top_color"] = "";
  204. $d["border_right_color"] = "";
  205. $d["border_bottom_color"] = "";
  206. $d["border_left_color"] = "";
  207. $d["border_top_style"] = "none";
  208. $d["border_right_style"] = "none";
  209. $d["border_bottom_style"] = "none";
  210. $d["border_left_style"] = "none";
  211. $d["border_top_width"] = "medium";
  212. $d["border_right_width"] = "medium";
  213. $d["border_bottom_width"] = "medium";
  214. $d["border_left_width"] = "medium";
  215. $d["border_width"] = "medium";
  216. $d["border"] = "";
  217. $d["bottom"] = "auto";
  218. $d["caption_side"] = "top";
  219. $d["clear"] = "none";
  220. $d["clip"] = "auto";
  221. $d["color"] = "#000000";
  222. $d["content"] = "normal";
  223. $d["counter_increment"] = "none";
  224. $d["counter_reset"] = "none";
  225. $d["cue_after"] = "none";
  226. $d["cue_before"] = "none";
  227. $d["cue"] = "";
  228. $d["cursor"] = "auto";
  229. $d["direction"] = "ltr";
  230. $d["display"] = "inline";
  231. $d["elevation"] = "level";
  232. $d["empty_cells"] = "show";
  233. $d["float"] = "none";
  234. $d["font_family"] = "serif";
  235. $d["font_size"] = "medium";
  236. $d["font_style"] = "normal";
  237. $d["font_variant"] = "normal";
  238. $d["font_weight"] = "normal";
  239. $d["font"] = "";
  240. $d["height"] = "auto";
  241. $d["left"] = "auto";
  242. $d["letter_spacing"] = "normal";
  243. $d["line_height"] = "normal";
  244. $d["list_style_image"] = "none";
  245. $d["list_style_position"] = "outside";
  246. $d["list_style_type"] = "disc";
  247. $d["list_style"] = "";
  248. $d["margin_right"] = "0";
  249. $d["margin_left"] = "0";
  250. $d["margin_top"] = "0";
  251. $d["margin_bottom"] = "0";
  252. $d["margin"] = "";
  253. $d["max_height"] = "none";
  254. $d["max_width"] = "none";
  255. $d["min_height"] = "0";
  256. $d["min_width"] = "0";
  257. $d["orphans"] = "2";
  258. $d["outline_color"] = "invert";
  259. $d["outline_style"] = "none";
  260. $d["outline_width"] = "medium";
  261. $d["outline"] = "";
  262. $d["overflow"] = "visible";
  263. $d["padding_top"] = "0";
  264. $d["padding_right"] = "0";
  265. $d["padding_bottom"] = "0";
  266. $d["padding_left"] = "0";
  267. $d["padding"] = "";
  268. $d["page_break_after"] = "auto";
  269. $d["page_break_before"] = "auto";
  270. $d["page_break_inside"] = "auto";
  271. $d["pause_after"] = "0";
  272. $d["pause_before"] = "0";
  273. $d["pause"] = "";
  274. $d["pitch_range"] = "50";
  275. $d["pitch"] = "medium";
  276. $d["play_during"] = "auto";
  277. $d["position"] = "static";
  278. $d["quotes"] = "";
  279. $d["richness"] = "50";
  280. $d["right"] = "auto";
  281. $d["speak_header"] = "once";
  282. $d["speak_numeral"] = "continuous";
  283. $d["speak_punctuation"] = "none";
  284. $d["speak"] = "normal";
  285. $d["speech_rate"] = "medium";
  286. $d["stress"] = "50";
  287. $d["table_layout"] = "auto";
  288. $d["text_align"] = "left";
  289. $d["text_decoration"] = "none";
  290. $d["text_indent"] = "0";
  291. $d["text_transform"] = "none";
  292. $d["top"] = "auto";
  293. $d["unicode_bidi"] = "normal";
  294. $d["vertical_align"] = "baseline";
  295. $d["visibility"] = "visible";
  296. $d["voice_family"] = "";
  297. $d["volume"] = "medium";
  298. $d["white_space"] = "normal";
  299. $d["widows"] = "2";
  300. $d["width"] = "auto";
  301. $d["word_spacing"] = "normal";
  302. $d["z_index"] = "auto";
  303. // Properties that inherit by default
  304. self::$_inherited = array("azimuth",
  305. "border_collapse",
  306. "border_spacing",
  307. "caption_side",
  308. "color",
  309. "cursor",
  310. "direction",
  311. "elevation",
  312. "empty_cells",
  313. "font_family",
  314. "font_size",
  315. "font_style",
  316. "font_variant",
  317. "font_weight",
  318. "font",
  319. "letter_spacing",
  320. "line_height",
  321. "list_style_image",
  322. "list_style_position",
  323. "list_style_type",
  324. "list_style",
  325. "orphans",
  326. "page_break_inside",
  327. "pitch_range",
  328. "pitch",
  329. "quotes",
  330. "richness",
  331. "speak_header",
  332. "speak_numeral",
  333. "speak_punctuation",
  334. "speak",
  335. "speech_rate",
  336. "stress",
  337. "text_align",
  338. "text_indent",
  339. "text_transform",
  340. "visibility",
  341. "voice_family",
  342. "volume",
  343. "white_space",
  344. "widows",
  345. "word_spacing");
  346. }
  347. }
  348. /**
  349. * "Destructor": forcibly free all references held by this object
  350. */
  351. function dispose() {
  352. unset($this->_stylesheet);
  353. }
  354. /**
  355. * returns the {@link Stylesheet} this Style is associated with.
  356. *
  357. * @return Stylesheet
  358. */
  359. function get_stylesheet() { return $this->_stylesheet; }
  360. /**
  361. * Converts any CSS length value into an absolute length in points.
  362. *
  363. * length_in_pt() takes a single length (e.g. '1em') or an array of
  364. * lengths and returns an absolute length. If an array is passed, then
  365. * the return value is the sum of all elements.
  366. *
  367. * If a reference size is not provided, the default font size is used
  368. * ({@link Style::$default_font_size}).
  369. *
  370. * @param float|array $length the length or array of lengths to resolve
  371. * @param float $ref_size an absolute reference size to resolve percentage lengths
  372. * @return float
  373. */
  374. function length_in_pt($length, $ref_size = null) {
  375. if ( !is_array($length) )
  376. $length = array($length);
  377. if ( !isset($ref_size) )
  378. $ref_size = self::$default_font_size;
  379. $ret = 0;
  380. foreach ($length as $l) {
  381. if ( $l === "auto" )
  382. return "auto";
  383. if ( $l === "none" )
  384. return "none";
  385. // Assume numeric values are already in points
  386. if ( is_numeric($l) ) {
  387. $ret += $l;
  388. continue;
  389. }
  390. if ( $l === "normal" ) {
  391. $ret += $ref_size;
  392. continue;
  393. }
  394. // Border lengths
  395. if ( $l === "thin" ) {
  396. $ret += 0.5;
  397. continue;
  398. }
  399. if ( $l === "medium" ) {
  400. $ret += 1.5;
  401. continue;
  402. }
  403. if ( $l === "thick" ) {
  404. $ret += 2.5;
  405. continue;
  406. }
  407. if ( ($i = mb_strpos($l, "pt")) !== false ) {
  408. $ret += mb_substr($l, 0, $i);
  409. continue;
  410. }
  411. if ( ($i = mb_strpos($l, "px")) !== false ) {
  412. $ret += ( mb_substr($l, 0, $i) * 72 ) / DOMPDF_DPI;
  413. continue;
  414. }
  415. if ( ($i = mb_strpos($l, "em")) !== false ) {
  416. $ret += mb_substr($l, 0, $i) * $this->__get("font_size");
  417. continue;
  418. }
  419. // FIXME: em:ex ratio?
  420. if ( ($i = mb_strpos($l, "ex")) !== false ) {
  421. $ret += mb_substr($l, 0, $i) * $this->__get("font_size");
  422. continue;
  423. }
  424. if ( ($i = mb_strpos($l, "%")) !== false ) {
  425. $ret += mb_substr($l, 0, $i)/100 * $ref_size;
  426. continue;
  427. }
  428. if ( ($i = mb_strpos($l, "in")) !== false ) {
  429. $ret += mb_substr($l, 0, $i) * 72;
  430. continue;
  431. }
  432. if ( ($i = mb_strpos($l, "cm")) !== false ) {
  433. $ret += mb_substr($l, 0, $i) * 72 / 2.54;
  434. continue;
  435. }
  436. if ( ($i = mb_strpos($l, "mm")) !== false ) {
  437. $ret += mb_substr($l, 0, $i) * 72 / 25.4;
  438. continue;
  439. }
  440. if ( ($i = mb_strpos($l, "pc")) !== false ) {
  441. $ret += mb_substr($l, 0, $i) / 12;
  442. continue;
  443. }
  444. // Bogus value
  445. $ret += $ref_size;
  446. }
  447. return $ret;
  448. }
  449. /**
  450. * Set inherited properties in this style using values in $parent
  451. *
  452. * @param Style $parent
  453. */
  454. function inherit(Style $parent) {
  455. // Set parent font size
  456. $this->_parent_font_size = $parent->get_font_size();
  457. foreach (self::$_inherited as $prop) {
  458. //inherit the !important property also.
  459. //if local property is also !important, don't inherit.
  460. if ( isset($parent->_props[$prop]) &&
  461. ( !isset($this->_props[$prop]) ||
  462. ( isset($parent->_important_props[$prop]) && !isset($this->_important_props[$prop]) )
  463. )
  464. ) {
  465. if ( isset($parent->_important_props[$prop]) ) {
  466. $this->_important_props[$prop] = true;
  467. }
  468. //see __set and __get, on all assignments clear cache!
  469. $this->_prop_cache[$prop] = null;
  470. $this->_props[$prop] = $parent->_props[$prop];
  471. }
  472. }
  473. foreach (array_keys($this->_props) as $prop) {
  474. if ( $this->_props[$prop] === "inherit" ) {
  475. if ( isset($parent->_important_props[$prop]) ) {
  476. $this->_important_props[$prop] = true;
  477. }
  478. //do not assign direct, but
  479. //implicite assignment through __set, redirect to specialized, get value with __get
  480. //This is for computing defaults if the parent setting is also missing.
  481. //Therefore do not directly assign the value without __set
  482. //set _important_props before that to be able to propagate.
  483. //see __set and __get, on all assignments clear cache!
  484. //$this->_prop_cache[$prop] = null;
  485. //$this->_props[$prop] = $parent->_props[$prop];
  486. //props_set for more obvious explicite assignment not implemented, because
  487. //too many implicite uses.
  488. // $this->props_set($prop, $parent->$prop);
  489. $this->$prop = $parent->$prop;
  490. }
  491. }
  492. return $this;
  493. }
  494. /**
  495. * Override properties in this style with those in $style
  496. *
  497. * @param Style $style
  498. */
  499. function merge(Style $style) {
  500. //treat the !important attribute
  501. //if old rule has !important attribute, override with new rule only if
  502. //the new rule is also !important
  503. foreach($style->_props as $prop => $val ) {
  504. if (isset($style->_important_props[$prop])) {
  505. $this->_important_props[$prop] = true;
  506. //see __set and __get, on all assignments clear cache!
  507. $this->_prop_cache[$prop] = null;
  508. $this->_props[$prop] = $val;
  509. } else if ( !isset($this->_important_props[$prop]) ) {
  510. //see __set and __get, on all assignments clear cache!
  511. $this->_prop_cache[$prop] = null;
  512. $this->_props[$prop] = $val;
  513. }
  514. }
  515. if ( isset($style->_props["font_size"]) )
  516. $this->__font_size_calculated = false;
  517. }
  518. /**
  519. * Returns an array(r, g, b, "r"=> r, "g"=>g, "b"=>b, "hex"=>"#rrggbb")
  520. * based on the provided CSS colour value.
  521. *
  522. * @param string $colour
  523. * @return array
  524. */
  525. function munge_colour($colour) {
  526. if ( is_array($colour) )
  527. // Assume the array has the right format...
  528. // FIXME: should/could verify this.
  529. return $colour;
  530. $r = 0;
  531. $g = 0;
  532. $b = 0;
  533. // Handle colour names
  534. switch ($colour) {
  535. case "maroon":
  536. $r = 0x80;
  537. break;
  538. case "red":
  539. $r = 0xff;
  540. break;
  541. case "orange":
  542. $r = 0xff;
  543. $g = 0xa5;
  544. break;
  545. case "yellow":
  546. $r = 0xff;
  547. $g = 0xff;
  548. break;
  549. case "olive":
  550. $r = 0x80;
  551. $g = 0x80;
  552. break;
  553. case "purple":
  554. $r = 0x80;
  555. $b = 0x80;
  556. break;
  557. case "fuchsia":
  558. $r = 0xff;
  559. $b = 0xff;
  560. break;
  561. case "white":
  562. $r = $g = $b = 0xff;
  563. break;
  564. case "lime":
  565. $g = 0xff;
  566. break;
  567. case "green":
  568. $g = 0x80;
  569. break;
  570. case "navy":
  571. $b = 0x80;
  572. break;
  573. case "blue":
  574. $b = 0xff;
  575. break;
  576. case "aqua":
  577. $g = 0xff;
  578. $b = 0xff;
  579. break;
  580. case "teal":
  581. $g = 0x80;
  582. $b = 0x80;
  583. break;
  584. case "black":
  585. break;
  586. case "sliver":
  587. $r = $g = $b = 0xc0;
  588. break;
  589. case "gray":
  590. case "grey":
  591. $r = $g = $b = 0x80;
  592. break;
  593. case "transparent":
  594. return "transparent";
  595. default:
  596. if ( mb_strlen($colour) == 4 && $colour[0] === "#" ) {
  597. // #rgb format
  598. $r = hexdec($colour[1] . $colour[1]);
  599. $g = hexdec($colour[2] . $colour[2]);
  600. $b = hexdec($colour[3] . $colour[3]);
  601. } else if ( mb_strlen($colour) == 7 && $colour[0] === "#" ) {
  602. // #rrggbb format
  603. $r = hexdec(mb_substr($colour, 1, 2));
  604. $g = hexdec(mb_substr($colour, 3, 2));
  605. $b = hexdec(mb_substr($colour, 5, 2));
  606. } else if ( mb_strpos($colour, "rgb") !== false ) {
  607. // rgb( r,g,b ) format
  608. $i = mb_strpos($colour, "(");
  609. $j = mb_strpos($colour, ")");
  610. // Bad colour value
  611. if ($i === false || $j === false)
  612. return null;
  613. $triplet = explode(",", mb_substr($colour, $i+1, $j-$i-1));
  614. if (count($triplet) != 3)
  615. return null;
  616. foreach (array_keys($triplet) as $c) {
  617. $triplet[$c] = trim($triplet[$c]);
  618. if ( $triplet[$c]{mb_strlen($triplet[$c]) - 1} === "%" )
  619. $triplet[$c] = round($triplet[$c] * 0.255);
  620. }
  621. list($r, $g, $b) = $triplet;
  622. } else {
  623. // Who knows?
  624. return null;
  625. }
  626. // Clip to 0 - 1
  627. $r = $r < 0 ? 0 : ($r > 255 ? 255 : $r);
  628. $g = $g < 0 ? 0 : ($g > 255 ? 255 : $g);
  629. $b = $b < 0 ? 0 : ($b > 255 ? 255 : $b);
  630. break;
  631. }
  632. // Form array
  633. $arr = array(0 => $r / 0xff, 1 => $g / 0xff, 2 => $b / 0xff,
  634. "r"=>$r / 0xff, "g"=>$g / 0xff, "b"=>$b / 0xff,
  635. "hex" => sprintf("#%02X%02X%02X", $r, $g, $b));
  636. return $arr;
  637. }
  638. /**
  639. * Alias for {@link Style::munge_colour()}
  640. *
  641. * @param string $color
  642. * @return array
  643. */
  644. function munge_color($color) { return CSS_Color::parse($color); }
  645. /* direct access to _important_props array from outside would work only when declared as
  646. * 'var $_important_props;' instead of 'protected $_important_props;'
  647. * Don't call _set/__get on missing attribute. Therefore need a special access.
  648. * Assume that __set will be also called when this is called, so do not check validity again.
  649. * Only created, if !important exists -> always set true.
  650. */
  651. function important_set($prop) {
  652. $prop = str_replace("-", "_", $prop);
  653. $this->_important_props[$prop] = true;
  654. }
  655. function important_get($prop) {
  656. isset($this->_important_props[$prop]);
  657. }
  658. /**
  659. * PHP5 overloaded setter
  660. *
  661. * This function along with {@link Style::__get()} permit a user of the
  662. * Style class to access any (CSS) property using the following syntax:
  663. * <code>
  664. * Style->margin_top = "1em";
  665. * echo (Style->margin_top);
  666. * </code>
  667. *
  668. * __set() automatically calls the provided set function, if one exists,
  669. * otherwise it sets the property directly. Typically, __set() is not
  670. * called directly from outside of this class.
  671. *
  672. * On each modification clear cache to return accurate setting.
  673. * Also affects direct settings not using __set
  674. * For easier finding all assignments, attempted to allowing only explicite assignment:
  675. * Very many uses, e.g. frame_reflower.cls.php -> for now leave as it is
  676. * function __set($prop, $val) {
  677. * throw new DOMPDF_Exception("Implicite replacement of assignment by __set. Not good.");
  678. * }
  679. * function props_set($prop, $val) { ... }
  680. *
  681. * @param string $prop the property to set
  682. * @param mixed $val the value of the property
  683. *
  684. */
  685. function __set($prop, $val) {
  686. global $_dompdf_warnings;
  687. $prop = str_replace("-", "_", $prop);
  688. $this->_prop_cache[$prop] = null;
  689. if ( !isset(self::$_defaults[$prop]) ) {
  690. $_dompdf_warnings[] = "'$prop' is not a valid CSS2 property.";
  691. return;
  692. }
  693. if ( $prop !== "content" && is_string($val) && mb_strpos($val, "url") === false ) {
  694. $val = mb_strtolower(trim(str_replace(array("\n", "\t"), array(" "), $val)));
  695. $val = preg_replace("/([0-9]+) (pt|px|pc|em|ex|in|cm|mm|%)/S", "\\1\\2", $val);
  696. }
  697. $method = "set_$prop";
  698. if ( method_exists($this, $method) )
  699. $this->$method($val);
  700. else
  701. $this->_props[$prop] = $val;
  702. }
  703. /**
  704. * PHP5 overloaded getter
  705. *
  706. * Along with {@link Style::__set()} __get() provides access to all CSS
  707. * properties directly. Typically __get() is not called directly outside
  708. * of this class.
  709. *
  710. * On each modification clear cache to return accurate setting.
  711. * Also affects direct settings not using __set
  712. *
  713. * @param string $prop
  714. * @return mixed
  715. */
  716. function __get($prop) {
  717. if ( !isset(self::$_defaults[$prop]) )
  718. throw new DOMPDF_Exception("'$prop' is not a valid CSS2 property.");
  719. if ( isset($this->_prop_cache[$prop]) && $this->_prop_cache[$prop] != null)
  720. return $this->_prop_cache[$prop];
  721. $method = "get_$prop";
  722. // Fall back on defaults if property is not set
  723. if ( !isset($this->_props[$prop]) )
  724. $this->_props[$prop] = self::$_defaults[$prop];
  725. if ( method_exists($this, $method) )
  726. return $this->_prop_cache[$prop] = $this->$method();
  727. return $this->_prop_cache[$prop] = $this->_props[$prop];
  728. }
  729. /**
  730. * Getter for the 'font-family' CSS property.
  731. *
  732. * Uses the {@link Font_Metrics} class to resolve the font family into an
  733. * actual font file.
  734. *
  735. * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-family
  736. * @return string
  737. */
  738. function get_font_family() {
  739. $DEBUGCSS=DEBUGCSS; //=DEBUGCSS; Allow override of global setting for ad hoc debug
  740. // Select the appropriate font. First determine the subtype, then check
  741. // the specified font-families for a candidate.
  742. // Resolve font-weight
  743. $weight = $this->__get("font_weight");
  744. if ( is_numeric($weight) ) {
  745. if ( $weight < 600 )
  746. $weight = "normal";
  747. else
  748. $weight = "bold";
  749. } else if ( $weight === "bold" || $weight === "bolder" ) {
  750. $weight = "bold";
  751. } else {
  752. $weight = "normal";
  753. }
  754. // Resolve font-style
  755. $font_style = $this->__get("font_style");
  756. if ( $weight === "bold" && ($font_style === "italic" || $font_style === "oblique") )
  757. $subtype = "bold_italic";
  758. else if ( $weight === "bold" && $font_style !== "italic" && $font_style !== "oblique" )
  759. $subtype = "bold";
  760. else if ( $weight !== "bold" && ($font_style === "italic" || $font_style === "oblique") )
  761. $subtype = "italic";
  762. else
  763. $subtype = "normal";
  764. // Resolve the font family
  765. if ($DEBUGCSS) {
  766. print "<pre>[get_font_family:";
  767. print '('.$this->_props["font_family"].'.'.$font_style.'.'.$this->__get("font_weight").'.'.$weight.'.'.$subtype.')';
  768. }
  769. $families = explode(",", $this->_props["font_family"]);
  770. $families = array_map('trim',$families);
  771. reset($families);
  772. $font = null;
  773. while ( current($families) ) {
  774. list(,$family) = each($families);
  775. //remove leading and trailing string delimiters, e.g. on font names with spaces;
  776. //remove leading and trailing whitespace
  777. $family=trim($family," \t\n\r\x0B\"'");
  778. if ($DEBUGCSS) print '('.$family.')';
  779. $font = Font_Metrics::get_font($family, $subtype);
  780. if ( $font ) {
  781. if ($DEBUGCSS) print '('.$font.")get_font_family]\n</pre>";
  782. return $font;
  783. }
  784. }
  785. $family = null;
  786. if ($DEBUGCSS) print '(default)';
  787. $font = Font_Metrics::get_font($family, $subtype);
  788. if ( $font ) {
  789. if ($DEBUGCSS) print '('.$font.")get_font_family]\n</pre>";
  790. return $font;
  791. }
  792. throw new DOMPDF_Exception("Unable to find a suitable font replacement for: '" . $this->_props["font_family"] ."'");
  793. }
  794. /**
  795. * Returns the resolved font size, in points
  796. *
  797. * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size
  798. * @return float
  799. */
  800. function get_font_size() {
  801. if ( $this->__font_size_calculated )
  802. return $this->_props["font_size"];
  803. if ( !isset($this->_props["font_size"]) )
  804. $fs = self::$_defaults["font_size"];
  805. else
  806. $fs = $this->_props["font_size"];
  807. if ( !isset($this->_parent_font_size) )
  808. $this->_parent_font_size = self::$default_font_size;
  809. switch ($fs) {
  810. case "xx-small":
  811. $fs = 3/5 * $this->_parent_font_size;
  812. break;
  813. case "x-small":
  814. $fs = 3/4 * $this->_parent_font_size;
  815. break;
  816. case "smaller":
  817. case "small":
  818. $fs = 8/9 * $this->_parent_font_size;
  819. break;
  820. case "medium":
  821. $fs = $this->_parent_font_size;
  822. break;
  823. case "larger":
  824. case "large":
  825. $fs = 6/5 * $this->_parent_font_size;
  826. break;
  827. case "x-large":
  828. $fs = 3/2 * $this->_parent_font_size;
  829. break;
  830. case "xx-large":
  831. $fs = 2/1 * $this->_parent_font_size;
  832. break;
  833. default:
  834. break;
  835. }
  836. // Ensure relative sizes resolve to something
  837. if ( ($i = mb_strpos($fs, "em")) !== false )
  838. $fs = mb_substr($fs, 0, $i) * $this->_parent_font_size;
  839. else if ( ($i = mb_strpos($fs, "ex")) !== false )
  840. $fs = mb_substr($fs, 0, $i) * $this->_parent_font_size;
  841. else
  842. $fs = $this->length_in_pt($fs);
  843. //see __set and __get, on all assignments clear cache!
  844. $this->_prop_cache["font_size"] = null;
  845. $this->_props["font_size"] = $fs;
  846. $this->__font_size_calculated = true;
  847. return $this->_props["font_size"];
  848. }
  849. /**
  850. * @link http://www.w3.org/TR/CSS21/text.html#propdef-word-spacing
  851. * @return float
  852. */
  853. function get_word_spacing() {
  854. if ( $this->_props["word_spacing"] === "normal" )
  855. return 0;
  856. return $this->_props["word_spacing"];
  857. }
  858. /**
  859. * @link http://www.w3.org/TR/CSS21/visudet.html#propdef-line-height
  860. * @return float
  861. */
  862. function get_line_height() {
  863. if ( $this->_props["line_height"] === "normal" )
  864. return self::$default_line_height * $this->get_font_size();
  865. if ( is_numeric($this->_props["line_height"]) )
  866. return $this->length_in_pt( $this->_props["line_height"] . "%", $this->get_font_size());
  867. return $this->length_in_pt( $this->_props["line_height"], $this->get_font_size() );
  868. }
  869. /**
  870. * Returns the colour as an array
  871. *
  872. * The array has the following format:
  873. * <code>array(r,g,b, "r" => r, "g" => g, "b" => b, "hex" => "#rrggbb")</code>
  874. *
  875. * @link http://www.w3.org/TR/CSS21/colors.html#propdef-color
  876. * @return array
  877. */
  878. function get_color() {
  879. return $this->munge_color( $this->_props["color"] );
  880. }
  881. /**
  882. * Returns the background colour as an array
  883. *
  884. * The returned array has the same format as {@link Style::get_color()}
  885. *
  886. * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-color
  887. * @return array
  888. */
  889. function get_background_color() {
  890. return $this->munge_color( $this->_props["background_color"] );
  891. }
  892. /**
  893. * Returns the background position as an array
  894. *
  895. * The returned array has the following format:
  896. * <code>array(x,y, "x" => x, "y" => y)</code>
  897. *
  898. * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-position
  899. * @return array
  900. */
  901. function get_background_position() {
  902. $tmp = explode(" ", $this->_props["background_position"]);
  903. switch ($tmp[0]) {
  904. case "left":
  905. $x = "0%";
  906. break;
  907. case "right":
  908. $x = "100%";
  909. break;
  910. case "top":
  911. $y = "0%";
  912. break;
  913. case "bottom":
  914. $y = "100%";
  915. break;
  916. case "center":
  917. $x = "50%";
  918. $y = "50%";
  919. break;
  920. default:
  921. $x = $tmp[0];
  922. break;
  923. }
  924. if ( isset($tmp[1]) ) {
  925. switch ($tmp[1]) {
  926. case "left":
  927. $x = "0%";
  928. break;
  929. case "right":
  930. $x = "100%";
  931. break;
  932. case "top":
  933. $y = "0%";
  934. break;
  935. case "bottom":
  936. $y = "100%";
  937. break;
  938. case "center":
  939. if ( $tmp[0] === "left" || $tmp[0] === "right" || $tmp[0] === "center" )
  940. $y = "50%";
  941. else
  942. $x = "50%";
  943. break;
  944. default:
  945. $y = $tmp[1];
  946. break;
  947. }
  948. } else {
  949. $y = "50%";
  950. }
  951. if ( !isset($x) )
  952. $x = "0%";
  953. if ( !isset($y) )
  954. $y = "0%";
  955. return array( 0 => $x, "x" => $x,
  956. 1 => $y, "y" => $y );
  957. }
  958. /**
  959. * Returns the background as it is currently stored
  960. *
  961. * (currently anyway only for completeness.
  962. * not used for further processing)
  963. *
  964. * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment
  965. * @return string
  966. */
  967. function get_background_attachment() {
  968. return $this->_props["background_attachment"];
  969. }
  970. /**
  971. * Returns the background_repeat as it is currently stored
  972. *
  973. * (currently anyway only for completeness.
  974. * not used for further processing)
  975. *
  976. * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat
  977. * @return string
  978. */
  979. function get_background_repeat() {
  980. return $this->_props["background_repeat"];
  981. }
  982. /**
  983. * Returns the background as it is currently stored
  984. *
  985. * (currently anyway only for completeness.
  986. * not used for further processing, but the individual get_background_xxx)
  987. *
  988. * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background
  989. * @return string
  990. */
  991. function get_background() {
  992. return $this->_props["background"];
  993. }
  994. /**#@+
  995. * Returns the border colour as an array
  996. *
  997. * See {@link Style::get_color()}
  998. *
  999. * @link http://www.w3.org/TR/CSS21/box.html#border-color-properties
  1000. * @return array
  1001. */
  1002. function get_border_top_color() {
  1003. if ( $this->_props["border_top_color"] === "" ) {
  1004. //see __set and __get, on all assignments clear cache!
  1005. $this->_prop_cache["border_top_color"] = null;
  1006. $this->_props["border_top_color"] = $this->__get("color");
  1007. }
  1008. return $this->munge_color($this->_props["border_top_color"]);
  1009. }
  1010. function get_border_right_color() {
  1011. if ( $this->_props["border_right_color"] === "" ) {
  1012. //see __set and __get, on all assignments clear cache!
  1013. $this->_prop_cache["border_right_color"] = null;
  1014. $this->_props["border_right_color"] = $this->__get("color");
  1015. }
  1016. return $this->munge_color($this->_props["border_right_color"]);
  1017. }
  1018. function get_border_bottom_color() {
  1019. if ( $this->_props["border_bottom_color"] === "" ) {
  1020. //see __set and __get, on all assignments clear cache!
  1021. $this->_prop_cache["border_bottom_color"] = null;
  1022. $this->_props["border_bottom_color"] = $this->__get("color");
  1023. }
  1024. return $this->munge_color($this->_props["border_bottom_color"]);;
  1025. }
  1026. function get_border_left_color() {
  1027. if ( $this->_props["border_left_color"] === "" ) {
  1028. //see __set and __get, on all assignments clear cache!
  1029. $this->_prop_cache["border_left_color"] = null;
  1030. $this->_props["border_left_color"] = $this->__get("color");
  1031. }
  1032. return $this->munge_color($this->_props["border_left_color"]);
  1033. }
  1034. /**#@-*/
  1035. /**#@+
  1036. * Returns the border width, as it is currently stored
  1037. *
  1038. * @link http://www.w3.org/TR/CSS21/box.html#border-width-properties
  1039. * @return float|string
  1040. */
  1041. function get_border_top_width() {
  1042. $style = $this->__get("border_top_style");
  1043. return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_top_width"]) : 0;
  1044. }
  1045. function get_border_right_width() {
  1046. $style = $this->__get("border_right_style");
  1047. return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_right_width"]) : 0;
  1048. }
  1049. function get_border_bottom_width() {
  1050. $style = $this->__get("border_bottom_style");
  1051. return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_bottom_width"]) : 0;
  1052. }
  1053. function get_border_left_width() {
  1054. $style = $this->__get("border_left_style");
  1055. return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_left_width"]) : 0;
  1056. }
  1057. /**#@-*/
  1058. /**
  1059. * Return an array of all border properties.
  1060. *
  1061. * The returned array has the following structure:
  1062. * <code>
  1063. * array("top" => array("width" => [border-width],
  1064. * "style" => [border-style],
  1065. * "color" => [border-color (array)]),
  1066. * "bottom" ... )
  1067. * </code>
  1068. *
  1069. * @return array
  1070. */
  1071. function get_border_properties() {
  1072. return array("top" => array("width" => $this->__get("border_top_width"),
  1073. "style" => $this->__get("border_top_style"),
  1074. "color" => $this->__get("border_top_color")),
  1075. "bottom" => array("width" => $this->__get("border_bottom_width"),
  1076. "style" => $this->__get("border_bottom_style"),
  1077. "color" => $this->__get("border_bottom_color")),
  1078. "right" => array("width" => $this->__get("border_right_width"),
  1079. "style" => $this->__get("border_right_style"),
  1080. "color" => $this->__get("border_right_color")),
  1081. "left" => array("width" => $this->__get("border_left_width"),
  1082. "style" => $this->__get("border_left_style"),
  1083. "color" => $this->__get("border_left_color")));
  1084. }
  1085. /**
  1086. * Return a single border property
  1087. *
  1088. * @return mixed
  1089. */
  1090. protected function _get_border($side) {
  1091. $color = $this->__get("border_" . $side . "_color");
  1092. return $this->__get("border_" . $side . "_width") . " " .
  1093. $this->__get("border_" . $side . "_style") . " " . $color["hex"];
  1094. }
  1095. /**#@+
  1096. * Return full border properties as a string
  1097. *
  1098. * Border properties are returned just as specified in CSS:
  1099. * <pre>[width] [style] [color]</pre>
  1100. * e.g. "1px solid blue"
  1101. *
  1102. * @link http://www.w3.org/TR/CSS21/box.html#border-shorthand-properties
  1103. * @return string
  1104. */
  1105. function get_border_top() { return $this->_get_border("top"); }
  1106. function get_border_right() { return $this->_get_border("right"); }
  1107. function get_border_bottom() { return $this->_get_border("bottom"); }
  1108. function get_border_left() { return $this->_get_border("left"); }
  1109. /**#@-*/
  1110. /**
  1111. * Returns border spacing as an array
  1112. *
  1113. * The array has the format (h_space,v_space)
  1114. *
  1115. * @link http://www.w3.org/TR/CSS21/tables.html#propdef-border-spacing
  1116. * @return array
  1117. */
  1118. function get_border_spacing() {
  1119. return explode(" ", $this->_props["border_spacing"]);
  1120. }
  1121. /*==============================*/
  1122. /*
  1123. !important attribute
  1124. For basic functionality of the !important attribute with overloading
  1125. of several styles of an element, changes in inherit(), merge() and _parse_properties()
  1126. are sufficient [helpers var $_important_props, __construct(), important_set(), important_get()]
  1127. Only for combined attributes extra treatment needed. See below.
  1128. div { border: 1px red; }
  1129. div { border: solid; } // Not combined! Only one occurence of same style per context
  1130. //
  1131. div { border: 1px red; }
  1132. div a { border: solid; } // Adding to border style ok by inheritance
  1133. //
  1134. div { border-style: solid; } // Adding to border style ok because of different styles
  1135. div { border: 1px red; }
  1136. //
  1137. div { border-style: solid; !important} // border: overrides, even though not !important
  1138. div { border: 1px dashed red; }
  1139. //
  1140. div { border: 1px red; !important }
  1141. div a { border-style: solid; } // Need to override because not set
  1142. Special treatment:
  1143. At individual property like border-top-width need to check whether overriding value is also !important.
  1144. Also store the !important condition for later overrides.
  1145. Since not known who is initiating the override, need to get passed !importan as parameter.
  1146. !important Paramter taken as in the original style in the css file.
  1147. When poperty border !important given, do not mark subsets like border_style as important. Only
  1148. individual properties.
  1149. Note:
  1150. Setting individual property directly from css with e.g. set_border_top_style() is not needed, because
  1151. missing set funcions handled by a generic handler __set(), including the !important.
  1152. Setting individual property of as sub-property is handled below.
  1153. Implementation see at _set_style_side_type()
  1154. Callers _set_style_sides_type(), _set_style_type, _set_style_type_important()
  1155. Related functionality for background, padding, margin, font, list_style
  1156. */
  1157. /* Generalized set function for individual attribute of combined style.
  1158. * With check for !important
  1159. * Applicable for background, border, padding, margin, font, list_style
  1160. * Note: $type has a leading underscore (or is empty), the others not.
  1161. */
  1162. protected function _set_style_side_type($style,$side,$type,$val,$important) {
  1163. if ( !isset($this->_important_props[$style.'_'.$side.$type]) || $important) {
  1164. //see __set and __get, on all assignments clear cache!
  1165. $this->_prop_cache[$style.'_'.$side.$type] = null;
  1166. if ($important) {
  1167. $this->_important_props[$style.'_'.$side.$type] = true;
  1168. }
  1169. $this->_props[$style.'_'.$side.$type] = $val;
  1170. }
  1171. }
  1172. protected function _set_style_sides_type($style,$top,$right,$bottom,$left,$type,$important) {
  1173. $this->_set_style_side_type($style,'top',$type,$top,$important);
  1174. $this->_set_style_side_type($style,'right',$type,$right,$important);
  1175. $this->_set_style_side_type($style,'bottom',$type,$bottom,$important);
  1176. $this->_set_style_side_type($style,'left',$type,$left,$important);
  1177. }
  1178. protected function _set_style_type($style,$type,$val,$important) {
  1179. $arr = explode(" ", $val);
  1180. switch (count($arr)) {
  1181. case 1:
  1182. $this->_set_style_sides_type($style,$arr[0],$arr[0],$arr[0],$arr[0],$type,$important);
  1183. break;
  1184. case 2:
  1185. $this->_set_style_sides_type($style,$arr[0],$arr[1],$arr[0],$arr[1],$type,$important);
  1186. break;
  1187. case 3:
  1188. $this->_set_style_sides_type($style,$arr[0],$arr[1],$arr[1],$arr[2],$type,$important);
  1189. break;
  1190. case 4:
  1191. $this->_set_style_sides_type($style,$arr[0],$arr[1],$arr[2],$arr[3],$type,$important);
  1192. break;
  1193. default:
  1194. break;
  1195. }
  1196. //see __set and __get, on all assignments clear cache!
  1197. $this->_prop_cache[$style.$type] = null;
  1198. $this->_props[$style.$type] = $val;
  1199. }
  1200. protected function _set_style_type_important($style,$type,$val) {
  1201. $this->_set_style_type($style,$type,$val,isset($this->_important_props[$style.$type]));
  1202. }
  1203. /* Anyway only called if _important matches and is assigned
  1204. * E.g. _set_style_side_type($style,$side,'',str_replace("none", "0px", $val),isset($this->_important_props[$style.'_'.$side]));
  1205. */
  1206. protected function _set_style_side_width_important($style,$side,$val) {
  1207. //see __set and __get, on all assignments clear cache!
  1208. $this->_prop_cache[$style.'_'.$side] = null;
  1209. $this->_props[$style.'_'.$side] = str_replace("none", "0px", $val);
  1210. }
  1211. protected function _set_style($style,$val,$important) {
  1212. if ( !isset($this->_important_props[$style]) || $important) {
  1213. if ($important) {
  1214. $this->_important_props[$style] = true;
  1215. }
  1216. //see __set and __get, on all assignments clear cache!
  1217. $this->_prop_cache[$style] = null;
  1218. $this->_props[$style] = $val;
  1219. }
  1220. }
  1221. protected function _image($val) {
  1222. $DEBUGCSS=DEBUGCSS;
  1223. if ( mb_strpos($val, "url") === false ) {
  1224. $path = "none"; //Don't resolve no image -> otherwise would prefix path and no longer recognize as none
  1225. } else {
  1226. $val = preg_replace("/url\(['\"]?([^'\")]+)['\"]?\)/","\\1", trim($val));
  1227. // Resolve the url now in the context of the current stylesheet
  1228. $parsed_url = explode_url($val);
  1229. if ( $parsed_url["protocol"] == "" && $this->_stylesheet->get_protocol() == "" ) {
  1230. if ($parsed_url["path"]{0} === '/' || $parsed_url["path"]{0} === '\\' ) {
  1231. $path = $_SERVER["DOCUMENT_ROOT"].'/';
  1232. } else {
  1233. $path = $this->_stylesheet->get_base_path();
  1234. }
  1235. $path .= $parsed_url["path"] . $parsed_url["file"];
  1236. $path = realpath($path);
  1237. // If realpath returns FALSE then specifically state that there is no background image
  1238. if (!$path) { $path = 'none'; }
  1239. } else {
  1240. $path = build_url($this->_stylesheet->get_protocol(),
  1241. $this->_stylesheet->get_host(),
  1242. $this->_stylesheet->get_base_path(),
  1243. $val);
  1244. }
  1245. }
  1246. if ($DEBUGCSS) {
  1247. print "<pre>[_image\n";
  1248. print_r($parsed_url);
  1249. print $this->_stylesheet->get_protocol()."\n".$this->_stylesheet->get_base_path()."\n".$path."\n";
  1250. print "_image]</pre>";;
  1251. }
  1252. return $path;
  1253. }
  1254. /*======================*/
  1255. /**
  1256. * Sets colour
  1257. *
  1258. * The colour parameter can be any valid CSS colour value
  1259. *
  1260. * @link http://www.w3.org/TR/CSS21/colors.html#propdef-color
  1261. * @param string $colour
  1262. */
  1263. function set_color($colour) {
  1264. $col = $this->munge_colour($colour);
  1265. if ( is_null($col) )
  1266. $col = self::$_defaults["color"];
  1267. //see __set and __get, on all assignments clear cache, not needed on direct set through __set
  1268. $this->_prop_cache["color"] = null;
  1269. $this->_props["color"] = $col["hex"];
  1270. }
  1271. /**
  1272. * Sets the background colour
  1273. *
  1274. * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-color
  1275. * @param string $colour
  1276. */
  1277. function set_background_color($colour) {
  1278. $col = $this->munge_colour($colour);
  1279. if ( is_null($col) )
  1280. $col = self::$_defaults["background_color"];
  1281. //see __set and __get, on all assignments clear cache, not needed on direct set through __set
  1282. $this->_prop_cache["background_color"] = null;
  1283. $this->_props["background_color"] = is_array($col) ? $col["hex"] : $col;
  1284. }
  1285. /**
  1286. * Set the background image url
  1287. *
  1288. * @link http://www.w3.org/TR/CSS21/colors.html#background-properties
  1289. * @param string $url
  1290. */
  1291. function set_background_image($val) {
  1292. //see __set and __get, on all assignments clear cache, not needed on direct set through __set
  1293. $this->_prop_cache["background_image"] = null;
  1294. $this->_props["background_image"] = $this->_image($val);
  1295. }
  1296. /**
  1297. * Sets the background repeat
  1298. *
  1299. * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat
  1300. * @param string $val
  1301. */
  1302. function set_background_repeat($val) {
  1303. if ( is_null($val) )
  1304. $val = self::$_defaults["background_repeat"];
  1305. //see __set and __get, on all assignments clear cache, not needed on direct set through __set
  1306. $this->_prop_cache["background_repeat"] = null;
  1307. $this->_props["background_repeat"] = $val;
  1308. }
  1309. /**
  1310. * Sets the background attachment
  1311. *
  1312. * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment
  1313. * @param string $val
  1314. */
  1315. function set_background_attachment($val) {
  1316. if ( is_null($val) )
  1317. $val = self::$_defaults["background_attachment"];
  1318. //see __set and __get, on all assignments clear cache, not needed on direct set through __set
  1319. $this->_prop_cache["background_attachment"] = null;
  1320. $this->_props["background_attachment"] = $val;
  1321. }
  1322. /**
  1323. * Sets the background position
  1324. *
  1325. * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-position
  1326. * @param string $val
  1327. */
  1328. function set_background_position($val) {
  1329. if ( is_null($val) )
  1330. $val = self::$_defaults["background_position"];
  1331. //see __set and __get, on all assignments clear cache, not needed on direct set through __set
  1332. $this->_prop_cache["background_position"] = null;
  1333. $this->_props["background_position"] = $val;
  1334. }
  1335. /**
  1336. * Sets the background - combined options
  1337. *
  1338. * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background
  1339. * @param string $val
  1340. */
  1341. function set_background($val) {
  1342. $col = null;
  1343. $pos = array();
  1344. $tmp = explode(" ", $val);
  1345. $important = isset($this->_important_props["background"]);
  1346. foreach($tmp as $attr) {
  1347. if (mb_substr($attr, 0, 3) === "url" || $attr === "none") {
  1348. $this->_set_style("background_image", $this->_image($attr), $important);
  1349. } else if ($attr === "fixed" || $attr === "scroll") {
  1350. $this->_set_style("background_attachment", $attr, $important);
  1351. } else if ($attr === "repeat" || $attr === "repeat-x" || $attr === "repeat-y" || $attr === "no-repeat") {
  1352. $this->_set_style("background_repeat", $attr, $important);
  1353. } else if (($col = $this->munge_color($attr)) != null ) {
  1354. $this->_set_style("background_color", is_array($col) ? $col["hex"] : $col, $important);
  1355. } else {
  1356. $pos[] = $attr;
  1357. }
  1358. }
  1359. if (count($pos)) {
  1360. $this->_set_style("background_position",implode(' ',$pos), $important);
  1361. }
  1362. //see __set and __get, on all assignments clear cache, not needed on direct set through __set
  1363. $this->_prop_cache["background"] = null;
  1364. $this->_props["background"] = $val;
  1365. }
  1366. /**
  1367. * Sets the font size
  1368. *
  1369. * $size can be any acceptable CSS size
  1370. *
  1371. * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size
  1372. * @param string|float $size
  1373. */
  1374. function set_font_size($size) {
  1375. $this->__font_size_calculated = false;
  1376. //see __set and __get, on all assignments clear cache, not needed on direct set through __set
  1377. $this->_prop_cache["font_size"] = null;
  1378. $this->_props["font_size"] = $size;
  1379. }
  1380. /**
  1381. * Sets the font style
  1382. *
  1383. * combined attributes
  1384. * set individual attributes also, respecting !important mark
  1385. * exactly this order, separate by space. Multiple fonts separated by comma:
  1386. * font-style, font-variant, font-weight, font-size, line-height, font-family
  1387. *
  1388. * Other than with border and list, existing partial attributes should
  1389. * reset when starting here, even when not mentioned.
  1390. * If individual attribute is !important and explicite or implicite replacement is not,
  1391. * keep individual attribute
  1392. *
  1393. * require whitespace as delimiters for single value attributes
  1394. * On delimiter "/" treat first as font height, second as line height
  1395. * treat all remaining at the end of line as font
  1396. * font-style, font-variant, font-weight, font-size, line-height, font-family
  1397. *
  1398. * missing font-size and font-family might be not allowed, but accept it here and
  1399. * use default (medium size, enpty font name)
  1400. *
  1401. * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style
  1402. * @param $val
  1403. */
  1404. function set_font($val) {
  1405. $this->__font_size_calculated = false;
  1406. //see __set and __get, on all assignments clear cache, not needed on direct set through __set
  1407. $this->_prop_cache["font"] = null;
  1408. $this->_props["font"] = $val;
  1409. $important = isset($this->_important_props["font"]);
  1410. if ( preg_match("/^(italic|oblique|normal)\s*(.*)$/i",$val,$match) ) {
  1411. $this->_set_style("font_style", $match[1], $important);
  1412. $val = $match[2];
  1413. } else {
  1414. $this->_set_style("font_style", self::$_defaults["font_style"], $important);
  1415. }
  1416. if ( preg_match("/^(small-caps|normal)\s*(.*)$/i",$val,$match) ) {
  1417. $this->_set_style("font_variant", $match[1], $important);
  1418. $val = $match[2];
  1419. } else {
  1420. $this->_set_style("font_variant", self::$_defaults["font_variant"], $important);
  1421. }
  1422. //matching numeric value followed by unit -> this is indeed a subsequent font size. Skip!
  1423. if ( preg_match("/^(bold|bolder|lighter|100|200|300|400|500|600|700|800|900|normal)\s*(.*)$/i",$val,$match) &&
  1424. !preg_match("/^(?:pt|px|pc|em|ex|in|cm|mm|%)/",$match[2])
  1425. ) {
  1426. $this->_set_style("font_weight", $match[1], $important);
  1427. $val = $match[2];
  1428. } else {
  1429. $this->_set_style("font_weight", self::$_defaults["font_weight"], $important);
  1430. }
  1431. if ( preg_match("/^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger|\d+\s*(?:pt|px|pc|em|ex|in|cm|mm|%))\s*(.*)$/i",$val,$match) ) {
  1432. $this->_set_style("font_size", $match[1], $important);
  1433. $val = $match[2];
  1434. if (preg_match("/^\/\s*(\d+\s*(?:pt|px|pc|em|ex|in|cm|mm|%))\s*(.*)$/i",$val,$match) ) {
  1435. $this->_set_style("line_height", $match[1], $important);
  1436. $val = $match[2];
  1437. } else {
  1438. $this->_set_style("line_height", self::$_defaults["line_height"], $important);
  1439. }
  1440. } else {
  1441. $this->_set_style("font_size", self::$_defaults["font_size"], $important);
  1442. $this->_set_style("line_height", self::$_defaults["line_height"], $important);
  1443. }
  1444. if(strlen($val) != 0) {
  1445. $this->_set_style("font_family", $val, $important);
  1446. } else {
  1447. $this->_set_style("font_family", self::$_defaults["font_family"], $important);
  1448. }
  1449. }
  1450. /**#@+
  1451. * Sets page break properties
  1452. *
  1453. * @link http://www.w3.org/TR/CSS21/page.html#page-breaks
  1454. * @param string $break
  1455. */
  1456. function set_page_break_before($break) {
  1457. if ($break === "left" || $break === "right")
  1458. $break = "always";
  1459. //see __set and __get, on all assignments clear cache, not needed on direct set through __set
  1460. $this->_prop_cache["page_break_before"] = null;
  1461. $this->_props["page_break_before"] = $break;
  1462. }
  1463. function set_page_break_after($break) {
  1464. if ($break === "left" || $break === "right")
  1465. $break = "always";
  1466. //see __set and __get, on all assignments clear cache, not needed on direct set through __set
  1467. $this->_prop_cache["page_break_after"] = null;
  1468. $this->_props["page_break_after"] = $break;
  1469. }
  1470. /**#@-*/
  1471. //........................................................................
  1472. /**#@+
  1473. * Sets the margin size
  1474. *
  1475. * @link http://www.w3.org/TR/CSS21/box.html#margin-properties
  1476. * @param $val
  1477. */
  1478. function set_margin_top($val) {
  1479. $this->_set_style_side_width_important('margin','top',$val);
  1480. }
  1481. function set_margin_right($val) {
  1482. $this->_set_style_side_width_important('margin','right',$val);
  1483. }
  1484. function set_margin_bottom($val) {
  1485. $this->_set_style_side_width_important('margin','bottom',$val);
  1486. }
  1487. function set_margin_left($val) {
  1488. $this->_set_style_side_width_important('margin','left',$val);
  1489. }
  1490. function set_margin($val) {
  1491. $val = str_replace("none", "0px", $val);
  1492. $this->_set_style_type_important('margin','',$val);
  1493. }
  1494. /**#@-*/
  1495. /**#@+
  1496. * Sets the padding size
  1497. *
  1498. * @link http://www.w3.org/TR/CSS21/box.html#padding-properties
  1499. * @param $val
  1500. */
  1501. function set_padding_top($val) {
  1502. $this->_set_style_side_width_important('padding','top',$val);
  1503. }
  1504. function set_padding_right($val) {
  1505. $this->_set_style_side_width_important('padding','right',$val);
  1506. }
  1507. function set_padding_bottom($val) {
  1508. $this->_set_style_side_width_important('padding','bottom',$val);
  1509. }
  1510. function set_padding_left($val) {
  1511. $this->_set_style_side_width_important('padding','left',$val);
  1512. }
  1513. function set_padding($val) {
  1514. $val = str_replace("none", "0px", $val);
  1515. $this->_set_style_type_important('padding','',$val);
  1516. }
  1517. /**#@-*/
  1518. /**
  1519. * Sets a single border
  1520. *
  1521. * @param string $side
  1522. * @param string $border_spec ([width] [style] [color])
  1523. */
  1524. protected function _set_border($side, $border_spec, $important) {
  1525. $border_spec = str_replace(",", " ", $border_spec);
  1526. $arr = explode(" ", $border_spec);
  1527. // FIXME: handle partial values
  1528. //For consistency of individal and combined properties, and with ie8 and firefox3
  1529. //reset all attributes, even if only partially given
  1530. $this->_set_style_side_type('border',$side,'_style',self::$_defaults['border_'.$side.'_style'],$important);
  1531. $this->_set_style_side_type('border',$side,'_width',self::$_defaults['border_'.$side.'_width'],$important);
  1532. $this->_set_style_side_type('border',$side,'_color',self::$_defaults['border_'.$side.'_color'],$important);
  1533. foreach ($arr as $value) {
  1534. $value = trim($value);
  1535. if ( in_array($value, self::$BORDER_STYLES) ) {
  1536. $this->_set_style_side_type('border',$side,'_style',$value,$important);
  1537. } else if ( preg_match("/[.0-9]+(?:px|pt|pc|em|ex|%|in|mm|cm)|(?:thin|medium|thick)/", $value ) ) {
  1538. $this->_set_style_side_type('border',$side,'_width',$value,$important);
  1539. } else {
  1540. // must be colour
  1541. $this->_set_style_side_type('border',$side,'_color',$value,$important);
  1542. }
  1543. }
  1544. //see __set and __get, on all assignments clear cache!
  1545. $this->_prop_cache['border_'.$side] = null;
  1546. $this->_props['border_'.$side] = $border_spec;
  1547. }
  1548. /**#@+
  1549. * Sets the border styles
  1550. *
  1551. * @link http://www.w3.org/TR/CSS21/box.html#border-properties
  1552. * @param string $val
  1553. */
  1554. function set_border_top($val) { $this->_set_border("top", $val, isset($this->_important_props['border_top'])); }
  1555. function set_border_right($val) { $this->_set_border("right", $val, isset($this->_important_props['border_right'])); }
  1556. function set_border_bottom($val) { $this->_set_border("bottom", $val, isset($this->_important_props['border_bottom'])); }
  1557. function set_border_left($val) { $this->_set_border("left", $val, isset($this->_important_props['border_left'])); }
  1558. function set_border($val) {
  1559. $important = isset($this->_important_props["border"]);
  1560. $this->_set_border("top", $val, $important);
  1561. $this->_set_border("right", $val, $important);
  1562. $this->_set_border("bottom", $val, $important);
  1563. $this->_set_border("left", $val, $important);
  1564. //see __set and __get, on all assignments clear cache, not needed on direct set through __set
  1565. $this->_prop_cache["border"] = null;
  1566. $this->_props["border"] = $val;
  1567. }
  1568. function set_border_width($val) {
  1569. $this->_set_style_type_important('border','_width',$val);
  1570. }
  1571. function set_border_color($val) {
  1572. $this->_set_style_type_important('border','_color',$val);
  1573. }
  1574. function set_border_style($val) {
  1575. $this->_set_style_type_important('border','_style',$val);
  1576. }
  1577. /**#@-*/
  1578. /**
  1579. * Sets the border spacing
  1580. *
  1581. * @link http://www.w3.org/TR/CSS21/box.html#border-properties
  1582. * @param float $val
  1583. */
  1584. function set_border_spacing($val) {
  1585. $arr = explode(" ", $val);
  1586. if ( count($arr) == 1 )
  1587. $arr[1] = $arr[0];
  1588. //see __set and __get, on all assignments clear cache, not needed on direct set through __set
  1589. $this->_prop_cache["border_spacing"] = null;
  1590. $this->_props["border_spacing"] = $arr[0] . " " . $arr[1];
  1591. }
  1592. /**
  1593. * Sets the list style image
  1594. *
  1595. * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image
  1596. * @param $val
  1597. */
  1598. function set_list_style_image($val) {
  1599. //see __set and __get, on all assignments clear cache, not needed on direct set through __set
  1600. $this->_prop_cache["list_style_image"] = null;
  1601. $this->_props["list_style_image"] = $this->_image($val);
  1602. }
  1603. /**
  1604. * Sets the list style
  1605. *
  1606. * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style
  1607. * @param $val
  1608. */
  1609. function set_list_style($val) {
  1610. $important = isset($this->_important_props["list_style"]);
  1611. $arr = explode(" ", str_replace(",", " ", $val));
  1612. static $types = array("disc", "circle", "square", "decimal",
  1613. "decimal-leading-zero", "lower-roman",
  1614. "upper-roman", "lower-greek", "lower-latin",
  1615. "upper-latin", "armenian", "georgian",
  1616. "lower-alpha", "upper-alpha", "hebrew",
  1617. "cjk-ideographic", "hiragana", "katakana",
  1618. "hiragana-iroha", "katakana-iroha", "none");
  1619. static $positions = array("inside", "outside");
  1620. foreach ($arr as $value) {
  1621. /* http://www.w3.org/TR/CSS21/generate.html#list-style
  1622. * A value of 'none' for the 'list-style' property sets both 'list-style-type' and 'list-style-image' to 'none'
  1623. */
  1624. if ($value === "none") {
  1625. $this->_set_style("list_style_type", $value, $important);
  1626. $this->_set_style("list_style_image", $value, $important);
  1627. continue;
  1628. }
  1629. //On setting or merging or inheriting list_style_image as well as list_style_type,
  1630. //and url exists, then url has precedence, otherwise fall back to list_style_type
  1631. //Firefox is wrong here (list_style_image gets overwritten on explicite list_style_type)
  1632. //Internet Explorer 7/8 and dompdf is right.
  1633. if (mb_substr($value, 0, 3) === "url") {
  1634. $this->_set_style("list_style_image", $this->_image($value), $important);
  1635. continue;
  1636. }
  1637. if ( in_array($value, $types) ) {
  1638. $this->_set_style("list_style_type", $value, $important);
  1639. } else if ( in_array($value, $positions) ) {
  1640. $this->_set_style("list_style_position", $value, $important);
  1641. }
  1642. }
  1643. //see __set and __get, on all assignments clear cache, not needed on direct set through __set
  1644. $this->_prop_cache["list_style"] = null;
  1645. $this->_props["list_style"] = $val;
  1646. }
  1647. /**
  1648. * Generate a string representation of the Style
  1649. *
  1650. * This dumps the entire property array into a string via print_r. Useful
  1651. * for debugging.
  1652. *
  1653. * @return string
  1654. */
  1655. /*DEBUGCSS print: see below additional debugging util*/
  1656. function __toString() {
  1657. return print_r(array_merge(array("parent_font_size" => $this->_parent_font_size),
  1658. $this->_props), true);
  1659. }
  1660. /*DEBUGCSS*/ function debug_print()
  1661. /*DEBUGCSS*/ {
  1662. /*DEBUGCSS*/ print "parent_font_size:".$this->_parent_font_size . ";\n";
  1663. /*DEBUGCSS*/ foreach($this->_props as $prop => $val ) {
  1664. /*DEBUGCSS*/ print $prop.':'.$val;
  1665. /*DEBUGCSS*/ if (isset($this->_important_props[$prop])) {
  1666. /*DEBUGCSS*/ print '!important';
  1667. /*DEBUGCSS*/ }
  1668. /*DEBUGCSS*/ print ";\n";
  1669. /*DEBUGCSS*/ }
  1670. /*DEBUGCSS*/ }
  1671. }