1<?php
2
3require_once(HTML2PS_DIR.'css.constants.inc.php');
4
5class CSSPageSelector {
6  var $_type;
7
8  function CSSPageSelector($type) {
9    $this->set_type($type);
10  }
11
12  function get_type() {
13    return $this->_type;
14  }
15
16  function set_type($type) {
17    $this->_type = $type;
18  }
19}
20
21class CSSPageSelectorAll extends CSSPageSelector {
22  function CSSPageSelectorAll() {
23    $this->CSSPageSelector(CSS_PAGE_SELECTOR_ALL);
24  }
25}
26
27class CSSPageSelectorNamed extends CSSPageSelector  {
28  var $_name;
29
30  function CSSPageSelectorNamed($name) {
31    $this->CSSPageSelector(CSS_PAGE_SELECTOR_NAMED);
32    $this->set_name($name);
33  }
34
35  function get_name() {
36    return $this->_name;
37  }
38
39  function set_name($name) {
40    $this->_name = $name;
41  }
42}
43
44class CSSPageSelectorFirst extends CSSPageSelector {
45  function CSSPageSelectorFirst() {
46    $this->CSSPageSelector(CSS_PAGE_SELECTOR_FIRST);
47  }
48}
49
50class CSSPageSelectorLeft extends CSSPageSelector {
51  function CSSPageSelectorLeft() {
52    $this->CSSPageSelector(CSS_PAGE_SELECTOR_LEFT);
53  }
54}
55
56class CSSPageSelectorRight extends CSSPageSelector {
57  function CSSPageSelectorRight() {
58    $this->CSSPageSelector(CSS_PAGE_SELECTOR_RIGHT);
59  }
60}
61
62class CSSAtRulePage {
63  var $selector;
64  var $margin_boxes;
65  var $css;
66
67  function CSSAtRulePage($selector, &$pipeline) {
68    $this->selector = $selector;
69    $this->margin_boxes = array();
70
71    $this->css =& new CSSPropertyCollection();
72  }
73
74  function &getSelector() {
75    return $this->selector;
76  }
77
78  function getAtRuleMarginBoxes() {
79    return $this->margin_boxes;
80  }
81
82  /**
83   * Note that only one margin box rule could be added; subsequent adds
84   * will overwrite existing data
85   */
86  function addAtRuleMarginBox($rule) {
87    $this->margin_boxes[$rule->getSelector()] = $rule;
88  }
89
90  function setCSSProperty($property) {
91    $this->css->add_property($property);
92  }
93}
94
95class CSSAtRuleMarginBox {
96  var $selector;
97  var $css;
98
99  /**
100   * TODO: CSS_TEXT_ALIGN should get  top/bottom values by default for
101   * left-top, left-bottom, right-top and right-bottom boxes
102   */
103  function CSSAtRuleMarginBox($selector, &$pipeline) {
104    $this->selector = $selector;
105
106    $css = "-html2ps-html-content: ''; content: ''; width: auto; height: auto; margin: 0; border: none; padding: 0; font: auto;";
107    $css = $css . $this->_getCSSDefaults($selector);
108
109    $this->css = new CSSRule(array(
110                                   array(SELECTOR_ANY),
111                                   parse_css_properties($css, $null),
112                                   '',
113                                   null),
114                             $pipeline);
115  }
116
117  function getSelector() {
118    return $this->selector;
119  }
120
121  function _getCSSDefaults($selector) {
122    $text_align_handler =& CSS::get_handler(CSS_TEXT_ALIGN);
123    $vertical_align_handler =& CSS::get_handler(CSS_VERTICAL_ALIGN);
124
125    switch ($selector) {
126    case CSS_MARGIN_BOX_SELECTOR_TOP:
127      return 'text-align: left; vertical-align: middle';
128    case CSS_MARGIN_BOX_SELECTOR_TOP_LEFT_CORNER:
129      return 'text-align: right; vertical-align: middle';
130    case CSS_MARGIN_BOX_SELECTOR_TOP_LEFT:
131      return 'text-align: left; vertical-align: middle';
132    case CSS_MARGIN_BOX_SELECTOR_TOP_CENTER:
133      return 'text-align: center; vertical-align: middle';
134    case CSS_MARGIN_BOX_SELECTOR_TOP_RIGHT:
135      return 'text-align: right; vertical-align: middle';
136    case CSS_MARGIN_BOX_SELECTOR_TOP_RIGHT_CORNER:
137      return 'text-align: left; vertical-align: middle';
138    case CSS_MARGIN_BOX_SELECTOR_BOTTOM:
139      return 'text-align: left; vertical-align: middle';
140    case CSS_MARGIN_BOX_SELECTOR_BOTTOM_LEFT_CORNER:
141      return 'text-align: right; vertical-align: middle';
142    case CSS_MARGIN_BOX_SELECTOR_BOTTOM_LEFT:
143      return 'text-align: left; vertical-align: middle';
144    case CSS_MARGIN_BOX_SELECTOR_BOTTOM_CENTER:
145      return 'text-align: center; vertical-align: middle';
146    case CSS_MARGIN_BOX_SELECTOR_BOTTOM_RIGHT:
147      return 'text-align: right; vertical-align: middle';
148    case CSS_MARGIN_BOX_SELECTOR_BOTTOM_RIGHT_CORNER:
149      return 'text-align: left; vertical-align: middle';
150    case CSS_MARGIN_BOX_SELECTOR_LEFT_TOP:
151      return 'text-align: center; vertical-align: top';
152    case CSS_MARGIN_BOX_SELECTOR_LEFT_MIDDLE:
153      return 'text-align: center; vertical-align: middle';
154    case CSS_MARGIN_BOX_SELECTOR_LEFT_BOTTOM:
155      return 'text-align: center; vertical-align: bottom';
156    case CSS_MARGIN_BOX_SELECTOR_RIGHT_TOP:
157      return 'text-align: center; vertical-align: top';
158    case CSS_MARGIN_BOX_SELECTOR_RIGHT_MIDDLE:
159      return 'text-align: center; vertical-align: middle';
160    case CSS_MARGIN_BOX_SELECTOR_RIGHT_BOTTOM:
161      return 'text-align: center; vertical-align: bottom';
162    };
163  }
164
165  function setCSSProperty($property) {
166    $this->css->add_property($property);
167  }
168
169  function &get_css_property($code) {
170    return $this->css->get_property($code);
171  }
172}
173
174/**
175 * Handle  @page rules  in  current CSS  media  As parse_css_media  is
176 * called for  selected media  only, we can  store data to  global CSS
177 * state - no data should be ignored
178 *
179 * at-page rules will be removed after parsing
180 *
181 * @param $css String contains raw CSS data to be processed
182 * @return String CSS text without at-page rules
183 */
184function parse_css_atpage_rules($css, &$css_ruleset) {
185  while (preg_match('/^(.*?)@page(.*)/is', $css, $matches)) {
186    $data = $matches[2];
187    $css = $matches[1].parse_css_atpage_rule(trim($data), $css_ruleset);
188  };
189  return $css;
190}
191
192function parse_css_atpage_rule($css, &$css_ruleset) {
193  /**
194   * Extract selector and left bracket
195   */
196  if (!preg_match('/^(.*?){(.*)$/is', $css, $matches)) {
197    error_log('No selector and/or open bracket found in @page rule');
198    return $css;
199  };
200  $raw_selector = trim($matches[1]);
201  $css          = trim($matches[2]);
202
203  $selector =& parse_css_atpage_selector($raw_selector);
204  $at_rule =& new CSSAtRulePage($selector, $css_ruleset);
205
206  /**
207   * The body of @page rule may contain declaraction (detected by ';'),
208   * margin box at-rule (detected by @top and similar tokens) or } indicating termination of
209   * @page rule
210   */
211  while (preg_match('/^(.*?)(;|@|})(.*)$/is', $css, $matches)) {
212    $raw_prefix = trim($matches[1]);
213    $raw_token  = trim($matches[2]);
214    $raw_suffix = trim($matches[3]);
215
216    switch ($raw_token) {
217    case ';':
218      /**
219       * Normal declaration (text contained in $raw_prefix
220       */
221      parse_css_atpage_declaration($raw_prefix, $at_rule, $css_ruleset);
222      $css = $raw_suffix;
223      break;
224
225    case '@':
226      /**
227       * Margin box at-rule
228       */
229      $css = parse_css_atpage_margin_box($raw_suffix, $at_rule, $css_ruleset);
230      break;
231
232    case '}':
233      /**
234       * End-of-rule
235       */
236      $css_ruleset->add_at_rule_page($at_rule);
237      return $raw_suffix;
238    };
239  };
240
241  /**
242   * Note that we should normally exit via '}' token handler above
243   */
244  error_log('No close bracket found in @page rule');
245  $css_ruleset->add_at_rule_page($at_rule);
246  return $css;
247}
248
249/**
250 * Parses CSS at-page rule selector; syntax of this selector can be seen in
251 * CSS 3 specification at http://www.w3.org/TR/css3-page/#syntax-page-selector
252 *
253 *
254 */
255function &parse_css_atpage_selector($selector) {
256  switch ($selector) {
257  case '':
258    $selector =& new CSSPageSelectorAll();
259    return $selector;
260  case ':first':
261    $selector =& new CSSPageSelectorFirst();
262    return $selector;
263  case ':left':
264    $selector =& new CSSPageSelectorLeft();
265    return $selector;
266  case ':right':
267    $selector =& new CSSPageSelectorRight();
268    return $selector;
269  default:
270    if (CSS::is_identifier($selector)) {
271      $selector =& new CSSPageSelectorNamed($selector);
272      return $selector;
273    } else {
274      error_log(sprintf('Unknown page selector in @page rule: \'%s\'', $selector));
275      $selector =& new CSSPageSelectorAll();
276      return $selector;
277    };
278  };
279}
280
281function parse_css_atpage_margin_box($css, &$at_rule, &$pipeline) {
282  if (!preg_match("/^([-\w]*)\s*{(.*)/is",$css,$matches)) {
283    error_log("Invalid margin box at-rule format");
284    return $css;
285  };
286
287  $raw_margin_box_selector = trim($matches[1]);
288  $css                     = trim($matches[2]);
289
290  $margin_box_selector = parse_css_atpage_margin_box_selector($raw_margin_box_selector);
291  $at_rule_margin_box = new CSSAtRuleMarginBox($margin_box_selector, $pipeline);
292
293  /**
294   * The body of margin box at-rule may contain declaraction (detected
295   * by ';'), or } indicating termination of at-rule
296   */
297  while (preg_match('/^(.*?)(;|})(.*)$/is', $css, $matches)) {
298    $raw_prefix = trim($matches[1]);
299    $raw_token  = trim($matches[2]);
300    $raw_suffix = trim($matches[3]);
301
302    switch ($raw_token) {
303    case ';':
304      /**
305       * Normal declaration (text contained in $raw_prefix
306       */
307      parse_css_atpage_margin_box_declaration($raw_prefix, $at_rule_margin_box, $pipeline);
308      $css = $raw_suffix;
309      break;
310
311    case '}':
312      /**
313       * End-of-rule
314       */
315      $at_rule->addAtRuleMarginBox($at_rule_margin_box);
316      return $raw_suffix;
317    };
318  };
319
320  /**
321   * Note that we should normally exit via '}' token handler above
322   */
323  error_log('No close bracket found in margin box at-rule');
324  $at_rule->addAtRuleMarginBox($at_rule_margin_box);
325  return $css;
326}
327
328function parse_css_atpage_margin_box_selector($css) {
329  switch ($css) {
330  case 'top':
331    return CSS_MARGIN_BOX_SELECTOR_TOP;
332  case 'top-left-corner':
333    return CSS_MARGIN_BOX_SELECTOR_TOP_LEFT_CORNER;
334  case 'top-left':
335    return CSS_MARGIN_BOX_SELECTOR_TOP_LEFT;
336  case 'top-center':
337    return CSS_MARGIN_BOX_SELECTOR_TOP_CENTER;
338  case 'top-right':
339    return CSS_MARGIN_BOX_SELECTOR_TOP_RIGHT;
340  case 'top-right-corner':
341    return CSS_MARGIN_BOX_SELECTOR_TOP_RIGHT_CORNER;
342  case 'bottom':
343    return CSS_MARGIN_BOX_SELECTOR_BOTTOM;
344  case 'bottom-left-corner':
345    return CSS_MARGIN_BOX_SELECTOR_BOTTOM_LEFT_CORNER;
346  case 'bottom-left':
347    return CSS_MARGIN_BOX_SELECTOR_BOTTOM_LEFT;
348  case 'bottom-center':
349    return CSS_MARGIN_BOX_SELECTOR_BOTTOM_CENTER;
350  case 'bottom-right':
351    return CSS_MARGIN_BOX_SELECTOR_BOTTOM_RIGHT;
352  case 'bottom-right-corner':
353    return CSS_MARGIN_BOX_SELECTOR_BOTTOM_RIGHT_CORNER;
354  case 'left-top':
355    return CSS_MARGIN_BOX_SELECTOR_LEFT_TOP;
356  case 'left-middle':
357    return CSS_MARGIN_BOX_SELECTOR_LEFT_MIDDLE;
358  case 'left-bottom':
359    return CSS_MARGIN_BOX_SELECTOR_LEFT_BOTTOM;
360  case 'right-top':
361    return CSS_MARGIN_BOX_SELECTOR_RIGHT_TOP;
362  case 'right-middle':
363    return CSS_MARGIN_BOX_SELECTOR_RIGHT_MIDDLE;
364  case 'right-bottom':
365    return CSS_MARGIN_BOX_SELECTOR_RIGHT_BOTTOM;
366  default:
367    error_log(sprintf('Unrecognized margin box selector: \'%s\'', $css));
368    return CSS_MARGIN_BOX_SELECTOR_TOP;
369  }
370};
371
372function parse_css_atpage_declaration($css, &$at_rule, &$pipeline) {
373  $parsed =& parse_css_property($css, $pipeline);
374
375  if (!is_null($parsed)) {
376    $properties = $parsed->getPropertiesSortedByPriority();
377    foreach ($properties as $property) {
378      $at_rule->setCSSProperty($property);
379    };
380  };
381}
382
383function parse_css_atpage_margin_box_declaration($css, &$at_rule, &$pipeline) {
384  $parsed =& parse_css_property($css, $pipeline);
385
386  if (!is_null($parsed)) {
387    $properties = $parsed->getPropertiesSortedByPriority();
388    foreach ($properties as $property) {
389      $at_rule->setCSSProperty($property);
390    };
391  };
392}
393
394?>