1 /**
  2  * Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
  3  *
  4  * @licstart
  5  * This file is part of WebODF.
  6  *
  7  * WebODF is free software: you can redistribute it and/or modify it
  8  * under the terms of the GNU Affero General Public License (GNU AGPL)
  9  * as published by the Free Software Foundation, either version 3 of
 10  * the License, or (at your option) any later version.
 11  *
 12  * WebODF is distributed in the hope that it will be useful, but
 13  * WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  * GNU Affero General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU Affero General Public License
 18  * along with WebODF.  If not, see <http://www.gnu.org/licenses/>.
 19  * @licend
 20  *
 21  * @source: http://www.webodf.org/
 22  * @source: https://github.com/kogmbh/WebODF/
 23  */
 24 
 25 /*global odf, runtime, xmldom, core, document*/
 26 
 27 /**
 28  * @constructor
 29  */
 30 odf.Style2CSS = function Style2CSS() {
 31     "use strict";
 32     var // helper constants
 33         /**@const
 34            @type{!string}*/
 35         drawns = odf.Namespaces.drawns,
 36         /**@const
 37            @type{!string}*/
 38         fons = odf.Namespaces.fons,
 39         /**@const
 40            @type{!string}*/
 41         officens = odf.Namespaces.officens,
 42         /**@const
 43            @type{!string}*/
 44         stylens = odf.Namespaces.stylens,
 45         /**@const
 46            @type{!string}*/
 47         svgns = odf.Namespaces.svgns,
 48         /**@const
 49            @type{!string}*/
 50         tablens = odf.Namespaces.tablens,
 51         /**@const
 52            @type{!string}*/
 53         xlinkns = odf.Namespaces.xlinkns,
 54         /**@const
 55            @type{!string}*/
 56         presentationns = odf.Namespaces.presentationns,
 57         domUtils = new core.DomUtils(),
 58 
 59         /**@const
 60            @type{!Object.<string,string>}*/
 61         familynamespaceprefixes = {
 62             'graphic': 'draw',
 63             'drawing-page': 'draw',
 64             'paragraph': 'text',
 65             'presentation': 'presentation',
 66             'ruby': 'text',
 67             'section': 'text',
 68             'table': 'table',
 69             'table-cell': 'table',
 70             'table-column': 'table',
 71             'table-row': 'table',
 72             'text': 'text',
 73             'list': 'text',
 74             'page': 'office'
 75         },
 76 
 77         /**@const
 78            @type{!Object.<string,!Array.<!string>>}*/
 79         familytagnames = {
 80             'graphic': ['circle', 'connected', 'control', 'custom-shape',
 81                 'ellipse', 'frame', 'g', 'line', 'measure', 'page',
 82                 'page-thumbnail', 'path', 'polygon', 'polyline', 'rect',
 83                 'regular-polygon' ],
 84             'paragraph': ['alphabetical-index-entry-template', 'h',
 85                 'illustration-index-entry-template', 'index-source-style',
 86                 'object-index-entry-template', 'p',
 87                 'table-index-entry-template', 'table-of-content-entry-template',
 88                 'user-index-entry-template'],
 89             'presentation': ['caption', 'circle', 'connector', 'control',
 90                 'custom-shape', 'ellipse', 'frame', 'g', 'line', 'measure',
 91                 'page-thumbnail', 'path', 'polygon', 'polyline', 'rect',
 92                 'regular-polygon'],
 93             'drawing-page': ['caption', 'circle', 'connector', 'control', 'page',
 94                 'custom-shape', 'ellipse', 'frame', 'g', 'line', 'measure',
 95                 'page-thumbnail', 'path', 'polygon', 'polyline', 'rect',
 96                 'regular-polygon'],
 97             'ruby': ['ruby', 'ruby-text'],
 98             'section': ['alphabetical-index', 'bibliography',
 99                 'illustration-index', 'index-title', 'object-index', 'section',
100                 'table-of-content', 'table-index', 'user-index'],
101             'table': ['background', 'table'],
102             'table-cell': ['body', 'covered-table-cell', 'even-columns',
103                 'even-rows', 'first-column', 'first-row', 'last-column',
104                 'last-row', 'odd-columns', 'odd-rows', 'table-cell'],
105             'table-column': ['table-column'],
106             'table-row': ['table-row'],
107             'text': ['a', 'index-entry-chapter', 'index-entry-link-end',
108                 'index-entry-link-start', 'index-entry-page-number',
109                 'index-entry-span', 'index-entry-tab-stop', 'index-entry-text',
110                 'index-title-template', 'linenumbering-configuration',
111                 'list-level-style-number', 'list-level-style-bullet',
112                 'outline-level-style', 'span'],
113             'list': ['list-item']
114         },
115 
116         /**@const
117            @type{!Array.<!Array.<!string>>}*/
118         textPropertySimpleMapping = [
119             [ fons, 'color', 'color' ],
120             // this sets the element background, not just the text background
121             [ fons, 'background-color', 'background-color' ],
122             [ fons, 'font-weight', 'font-weight' ],
123             [ fons, 'font-style', 'font-style' ]
124         ],
125 
126         /**@const
127            @type{!Array.<!Array.<!string>>}*/
128         bgImageSimpleMapping = [
129             [ stylens, 'repeat', 'background-repeat' ]
130         ],
131 
132         /**@const
133            @type{!Array.<!Array.<!string>>}*/
134         paragraphPropertySimpleMapping = [
135             [ fons, 'background-color', 'background-color' ],
136             [ fons, 'text-align', 'text-align' ],
137             [ fons, 'text-indent', 'text-indent' ],
138             [ fons, 'padding', 'padding' ],
139             [ fons, 'padding-left', 'padding-left' ],
140             [ fons, 'padding-right', 'padding-right' ],
141             [ fons, 'padding-top', 'padding-top' ],
142             [ fons, 'padding-bottom', 'padding-bottom' ],
143             [ fons, 'border-left', 'border-left' ],
144             [ fons, 'border-right', 'border-right' ],
145             [ fons, 'border-top', 'border-top' ],
146             [ fons, 'border-bottom', 'border-bottom' ],
147             [ fons, 'margin', 'margin' ],
148             [ fons, 'margin-left', 'margin-left' ],
149             [ fons, 'margin-right', 'margin-right' ],
150             [ fons, 'margin-top', 'margin-top' ],
151             [ fons, 'margin-bottom', 'margin-bottom' ],
152             [ fons, 'border', 'border' ]
153         ],
154 
155         /**@const
156            @type{!Array.<!Array.<!string>>}*/
157         graphicPropertySimpleMapping = [
158             [ fons, 'background-color', 'background-color'],
159             [ fons, 'min-height', 'min-height' ],
160             [ drawns, 'stroke', 'border' ],
161             [ svgns, 'stroke-color', 'border-color' ],
162             [ svgns, 'stroke-width', 'border-width' ],
163             [ fons, 'border', 'border' ],
164             [ fons, 'border-left', 'border-left' ],
165             [ fons, 'border-right', 'border-right' ],
166             [ fons, 'border-top', 'border-top' ],
167             [ fons, 'border-bottom', 'border-bottom' ]
168         ],
169 
170         /**@const
171            @type{!Array.<!Array.<!string>>}*/
172         tablecellPropertySimpleMapping = [
173             [ fons, 'background-color', 'background-color' ],
174             [ fons, 'border-left', 'border-left' ],
175             [ fons, 'border-right', 'border-right' ],
176             [ fons, 'border-top', 'border-top' ],
177             [ fons, 'border-bottom', 'border-bottom' ],
178             [ fons, 'border', 'border' ]
179         ],
180 
181         /**@const
182            @type{!Array.<!Array.<!string>>}*/
183         tablecolumnPropertySimpleMapping = [
184             [ stylens, 'column-width', 'width' ]
185         ],
186 
187         /**@const
188            @type{!Array.<!Array.<!string>>}*/
189         tablerowPropertySimpleMapping = [
190             [ stylens, 'row-height', 'height' ],
191             [ fons, 'keep-together', null ]
192         ],
193 
194         /**@const
195            @type{!Array.<!Array.<!string>>}*/
196         tablePropertySimpleMapping = [
197             [ stylens, 'width', 'width' ],
198             [ fons, 'margin-left', 'margin-left' ],
199             [ fons, 'margin-right', 'margin-right' ],
200             [ fons, 'margin-top', 'margin-top' ],
201             [ fons, 'margin-bottom', 'margin-bottom' ]
202         ],
203 
204         /**@const
205            @type{!Array.<!Array.<!string>>}*/
206         pageContentPropertySimpleMapping = [
207             [ fons, 'background-color', 'background-color' ],
208             [ fons, 'padding', 'padding' ],
209             [ fons, 'padding-left', 'padding-left' ],
210             [ fons, 'padding-right', 'padding-right' ],
211             [ fons, 'padding-top', 'padding-top' ],
212             [ fons, 'padding-bottom', 'padding-bottom' ],
213             [ fons, 'border', 'border' ],
214             [ fons, 'border-left', 'border-left' ],
215             [ fons, 'border-right', 'border-right' ],
216             [ fons, 'border-top', 'border-top' ],
217             [ fons, 'border-bottom', 'border-bottom' ],
218             [ fons, 'margin', 'margin' ],
219             [ fons, 'margin-left', 'margin-left' ],
220             [ fons, 'margin-right', 'margin-right' ],
221             [ fons, 'margin-top', 'margin-top' ],
222             [ fons, 'margin-bottom', 'margin-bottom' ]
223         ],
224 
225         /**@const
226            @type{!Array.<!Array.<!string>>}*/
227         pageSizePropertySimpleMapping = [
228             [ fons, 'page-width', 'width' ],
229             [ fons, 'page-height', 'height' ]
230         ],
231 
232         /**@const
233            @type{!Object.<!boolean>}*/
234         borderPropertyMap = {
235             'border': true,
236             'border-left': true,
237             'border-right': true,
238             'border-top': true,
239             'border-bottom': true,
240             'stroke-width': true
241         },
242 
243         // A font-face declaration map, to be populated once style2css is called.
244         /**@type{!Object.<string,string>}*/
245         fontFaceDeclsMap = {},
246         utils = new odf.OdfUtils(),
247         documentType,
248         odfRoot,
249         defaultFontSize,
250         xpath = xmldom.XPath,
251         cssUnits = new core.CSSUnits();
252 
253     /**
254      * @param {!string} family
255      * @param {!string} name
256      * @return {?string}
257      */
258     function createSelector(family, name) {
259         var prefix = familynamespaceprefixes[family],
260             namepart,
261             selector;
262         if (prefix === undefined) {
263             return null;
264         }
265 
266         // If there is no name, it is a default style, in which case style-name shall be used without a value
267         if (name) {
268             namepart = '[' + prefix + '|style-name="' + name + '"]';
269         } else {
270             namepart = '';
271         }
272         if (prefix === 'presentation') {
273             prefix = 'draw';
274             if (name) {
275                 namepart = '[presentation|style-name="' + name + '"]';
276             } else {
277                 namepart = '';
278             }
279         }
280         selector = prefix + '|' + familytagnames[family].join(
281             namepart + ',' + prefix + '|'
282         ) + namepart;
283         return selector;
284     }
285     /**
286      * @param {!string} family
287      * @param {!string} name
288      * @param {!odf.StyleTreeNode} node
289      * @return {!Array.<string>}
290      */
291     function getSelectors(family, name, node) {
292         var selectors = [], ss,
293             derivedStyles = node.derivedStyles,
294             /**@type{string}*/
295             n;
296         ss = createSelector(family, name);
297         if (ss !== null) {
298             selectors.push(ss);
299         }
300         for (n in derivedStyles) {
301             if (derivedStyles.hasOwnProperty(n)) {
302                 ss = getSelectors(family, n, derivedStyles[n]);
303                 selectors = selectors.concat(ss);
304             }
305         }
306         return selectors;
307     }
308     /**
309      * Make sure border width is no less than 1px wide; otherwise border is not rendered.
310      * Only have problems with point unit at the moment. Please add more rule if needed.
311      * @param {!string} value a string contains border attributes eg. 1pt solid black or 1px
312      * @return {!string}
313      */
314     function fixBorderWidth(value) {
315         var index = value.indexOf(' '),
316             width, theRestOfBorderAttributes;
317 
318         if (index !== -1) {
319             width = value.substring(0, index);
320             theRestOfBorderAttributes = value.substring(index); // everything after the width attribute
321         } else {
322             width = value;
323             theRestOfBorderAttributes = '';
324         }
325 
326         width = utils.parseLength(width);
327         // According to CSS 2.1, 1px is equal to 0.75pt http://www.w3.org/TR/CSS2/syndata.html#length-units
328         if (width && width.unit === 'pt' && width.value < 0.75) {
329             value = '0.75pt' + theRestOfBorderAttributes;
330         }
331         return value;
332     }
333     /**
334      * @param {!Element} props
335      * @param {!Array.<!Array.<!string>>} mapping
336      * @return {!string}
337      */
338     function applySimpleMapping(props, mapping) {
339         var rule = '', i, r, value;
340         for (i = 0; i < mapping.length; i += 1) {
341             r = mapping[i];
342             value = props.getAttributeNS(r[0], r[1]);
343 
344             if (value) {
345                 value = value.trim();
346 
347                 if (borderPropertyMap.hasOwnProperty(r[1])) {
348                     value = fixBorderWidth(value);
349                 }
350                 if (r[2]) {
351                     rule += r[2] + ':' + value + ';';
352                 }
353             }
354         }
355         return rule;
356     }
357 
358     /**
359      * Returns the font size attribute value from the text properties of a style node
360      * @param {?Element} styleNode
361      * @return {?{value: !number, unit: !string}}
362      */
363     function getFontSize(styleNode) {
364         var props = domUtils.getDirectChild(styleNode, stylens, 'text-properties');
365         if (props) {
366             return utils.parseFoFontSize(props.getAttributeNS(fons, 'font-size'));
367         }
368         return null;
369     }
370 
371     /**
372      * Returns the parent style node of a given style node
373      * @param {!Element} styleNode
374      * @return {Element}
375      */
376     function getParentStyleNode(styleNode) {
377         var parentStyleName = '',
378             parentStyleFamily = '',
379             parentStyleNode = null,
380             xp;
381 
382         if (styleNode.localName === 'default-style') {
383             return null;
384         }
385 
386         parentStyleName = styleNode.getAttributeNS(stylens, 'parent-style-name');
387         parentStyleFamily = styleNode.getAttributeNS(stylens, 'family');
388         
389         if (parentStyleName) {
390             xp = "//style:*[@style:name='" + parentStyleName + "'][@style:family='" + parentStyleFamily + "']";
391         } else {
392             xp = "//style:default-style[@style:family='" + parentStyleFamily + "']";
393         }
394         parentStyleNode = xpath.getODFElementsWithXPath(/**@type{!Element}*/(odfRoot), xp, odf.Namespaces.lookupNamespaceURI)[0];
395         return parentStyleNode;
396     }
397     /**
398      * @param {!Element} props
399      * @return {!string}
400      */
401     function getTextProperties(props) {
402         var rule = '', fontName, fontSize, value, textDecoration = '',
403             fontSizeRule = '',
404             sizeMultiplier = 1,
405             parentStyle;
406 
407         rule += applySimpleMapping(props, textPropertySimpleMapping);
408 
409         value = props.getAttributeNS(stylens, 'text-underline-style');
410         if (value === 'solid') {
411             textDecoration += ' underline';
412         }
413         value = props.getAttributeNS(stylens, 'text-line-through-style');
414         if (value === 'solid') {
415             textDecoration += ' line-through';
416         }
417 
418         if (textDecoration.length) {
419             textDecoration = 'text-decoration:' + textDecoration + ';';
420             rule += textDecoration;
421         }
422 
423         fontName = props.getAttributeNS(stylens, 'font-name')
424             || props.getAttributeNS(fons, 'font-family');
425         if (fontName) {
426             value = fontFaceDeclsMap[fontName];
427             // TODO: use other information from style:font-face, like style:font-family-generic
428             rule += 'font-family: ' + (value || fontName) + ';';
429         }
430 
431         parentStyle = /**@type{!Element}*/(props.parentNode);
432         fontSize = getFontSize(parentStyle);
433         // This is actually the font size of the current style.
434         if (!fontSize) {
435             return rule;
436         }
437 
438         while (parentStyle) {
439             fontSize = getFontSize(parentStyle);
440             if (fontSize) {
441                 // If the current style's font size is a non-% value, then apply the multiplier to get the child style (with the %)'s
442                 // actual font size. And now we can stop crawling up the style ancestry since we have a concrete font size.
443                 if (fontSize.unit !== '%') {
444                     fontSizeRule = 'font-size: ' + (fontSize.value * sizeMultiplier) + fontSize.unit + ';';
445                     break;
446                 }
447                 // If we got a % font size for the current style, then update the multiplier with it's 'normalized' multiplier
448                 sizeMultiplier *= (fontSize.value / 100);
449             }
450             // Crawl up the style ancestry
451             parentStyle = getParentStyleNode(parentStyle);
452         }
453 
454         // If there was nothing in the ancestry that specified a concrete font size, just apply the multiplier onto the page's default font size.
455         if (!fontSizeRule) {
456             fontSizeRule = 'font-size: ' + parseFloat(defaultFontSize) * sizeMultiplier + cssUnits.getUnits(defaultFontSize) + ';';
457         }
458 
459         rule += fontSizeRule;
460         return rule;
461     }
462     /**
463      * @param {!Element} props <style:paragraph-properties/>
464      * @return {!string}
465      */
466     function getParagraphProperties(props) {
467         var rule = '', bgimage, url, lineHeight;
468         rule += applySimpleMapping(props, paragraphPropertySimpleMapping);
469         bgimage = domUtils.getDirectChild(props, stylens, 'background-image');
470         if (bgimage) {
471             url = bgimage.getAttributeNS(xlinkns, 'href');
472             if (url) {
473                 rule += "background-image: url('odfkit:" + url + "');";
474                 //rule += "background-repeat: repeat;"; //FIXME test
475                 rule += applySimpleMapping(bgimage, bgImageSimpleMapping);
476             }
477         }
478 
479         lineHeight = props.getAttributeNS(fons, 'line-height');
480         if (lineHeight && lineHeight !== 'normal') {
481             lineHeight = utils.parseFoLineHeight(lineHeight);
482             if (lineHeight.unit !== '%') {
483                 rule += 'line-height: ' + lineHeight.value + lineHeight.unit + ';';
484             } else {
485                 rule += 'line-height: ' + lineHeight.value / 100 + ';';
486             }
487         }
488 
489         return rule;
490     }
491 
492 /*jslint unparam: true*/
493     /**
494      * @param {*} m
495      * @param {string} r
496      * @param {string} g
497      * @param {string} b
498      * @return {string}
499      */
500     function matchToRgb(m, r, g, b) {
501         return r + r + g + g + b + b;
502     }
503 /*jslint unparam: false*/
504 
505     /**
506      * @param {!string} hex
507      * @return {?{ r: number, g: number, b: number}}
508      */
509     function hexToRgb(hex) {
510         // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
511         var result,
512             shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
513         hex = hex.replace(shorthandRegex, matchToRgb);
514 
515         result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
516         return result ? {
517             r: parseInt(result[1], 16),
518             g: parseInt(result[2], 16),
519             b: parseInt(result[3], 16)
520         } : null;
521     }
522 
523     /**
524      * @param {string} n
525      * @return {boolean}
526      */
527     function isNumber(n) {
528         return !isNaN(parseFloat(n));
529     }
530 
531    /**
532      * @param {!Element} props
533      * @return {string}
534      */
535     function getGraphicProperties(props) {
536         var rule = '', alpha, bgcolor, fill;
537 
538         rule += applySimpleMapping(props, graphicPropertySimpleMapping);
539         alpha = props.getAttributeNS(drawns, 'opacity');
540         fill = props.getAttributeNS(drawns, 'fill');
541         bgcolor = props.getAttributeNS(drawns, 'fill-color');
542 
543         if (fill === 'solid' || fill === 'hatch') {
544             if (bgcolor && bgcolor !== 'none') {
545                 alpha = isNumber(alpha) ? parseFloat(alpha) / 100 : 1;
546                 bgcolor = hexToRgb(bgcolor);
547                 if (bgcolor) {
548                     rule += "background-color: rgba("
549                         + bgcolor.r + ","
550                         + bgcolor.g + ","
551                         + bgcolor.b + ","
552                         + alpha + ");";
553                 }
554             } else {
555                 rule += "background: none;";
556             }
557         } else if (fill === "none") {
558             rule += "background: none;";
559         }
560 
561         return rule;
562     }
563    /**
564      * @param {!Element} props
565      * @return {string}
566      */
567     function getDrawingPageProperties(props) {
568         var rule = '';
569 
570         rule += applySimpleMapping(props, graphicPropertySimpleMapping);
571         if (props.getAttributeNS(presentationns, 'background-visible') === 'true') {
572             rule += "background: none;";
573         }
574         return rule;
575     }
576     /**
577      * @param {!Element} props
578      * @return {string}
579      */
580     function getTableCellProperties(props) {
581         var rule = '';
582         rule += applySimpleMapping(props, tablecellPropertySimpleMapping);
583         return rule;
584     }
585     /**
586      * @param {!Element} props
587      * @return {string}
588      */
589     function getTableRowProperties(props) {
590         var rule = '';
591         rule += applySimpleMapping(props, tablerowPropertySimpleMapping);
592         return rule;
593     }
594     /**
595      * @param {!Element} props
596      * @return {string}
597      */
598     function getTableColumnProperties(props) {
599         var rule = '';
600         rule += applySimpleMapping(props, tablecolumnPropertySimpleMapping);
601         return rule;
602     }
603     /**
604      * @param {!Element} props
605      * @return {string}
606      */
607     function getTableProperties(props) {
608         var rule = '', borderModel;
609         rule += applySimpleMapping(props, tablePropertySimpleMapping);
610         borderModel = props.getAttributeNS(tablens, 'border-model');
611 
612         if (borderModel === 'collapsing') {
613             rule += 'border-collapse:collapse;';
614         } else if (borderModel === 'separating') {
615             rule += 'border-collapse:separate;';
616         }
617 
618         return rule;
619     }
620     /**
621      * @param {!CSSStyleSheet} sheet
622      * @param {string} family
623      * @param {string} name
624      * @param {!odf.StyleTreeNode} node
625      * @return {undefined}
626      */
627     function addStyleRule(sheet, family, name, node) {
628         var selectors = getSelectors(family, name, node),
629             selector = selectors.join(','),
630             rule = '',
631             properties;
632         properties = domUtils.getDirectChild(node.element, stylens, 'text-properties');
633         if (properties) {
634             rule += getTextProperties(properties);
635         }
636         properties = domUtils.getDirectChild(node.element,
637                 stylens, 'paragraph-properties');
638         if (properties) {
639             rule += getParagraphProperties(properties);
640         }
641         properties = domUtils.getDirectChild(node.element,
642                  stylens, 'graphic-properties');
643         if (properties) {
644             rule += getGraphicProperties(properties);
645         }
646         properties = domUtils.getDirectChild(node.element,
647                  stylens, 'drawing-page-properties');
648         if (properties) {
649             rule += getDrawingPageProperties(properties);
650         }
651         properties = domUtils.getDirectChild(node.element,
652                  stylens, 'table-cell-properties');
653         if (properties) {
654             rule += getTableCellProperties(properties);
655         }
656         properties = domUtils.getDirectChild(node.element,
657                  stylens, 'table-row-properties');
658         if (properties) {
659             rule += getTableRowProperties(properties);
660         }
661         properties = domUtils.getDirectChild(node.element,
662                  stylens, 'table-column-properties');
663         if (properties) {
664             rule += getTableColumnProperties(properties);
665         }
666         properties = domUtils.getDirectChild(node.element,
667                  stylens, 'table-properties');
668         if (properties) {
669             rule += getTableProperties(properties);
670         }
671         if (rule.length === 0) {
672             return;
673         }
674         rule = selector + '{' + rule + '}';
675         sheet.insertRule(rule, sheet.cssRules.length);
676     }
677 
678     /**
679      * @param {!CSSStyleSheet} sheet
680      * @param {!Element} node <style:page-layout/>/<style:default-page-layout/>
681      * @return {undefined}
682      */
683     function addPageStyleRules(sheet, node) {
684         var rule = '', imageProps, url,
685             contentLayoutRule = '',
686             pageSizeRule = '',
687             props = domUtils.getDirectChild(node, stylens, 'page-layout-properties'),
688             stylename,
689             masterStyles,
690             e,
691             masterStyleName;
692         if (!props) {
693             return;
694         }
695         stylename = node.getAttributeNS(stylens, 'name');
696 
697         rule += applySimpleMapping(props, pageContentPropertySimpleMapping);
698         imageProps = domUtils.getDirectChild(props, stylens, 'background-image');
699         if (imageProps) {
700             url = imageProps.getAttributeNS(xlinkns, 'href');
701             if (url) {
702                 rule += "background-image: url('odfkit:" + url + "');";
703                 //rule += "background-repeat: repeat;"; //FIXME test
704                 rule += applySimpleMapping(imageProps, bgImageSimpleMapping);
705             }
706         }
707 
708         if (documentType === 'presentation') {
709             masterStyles = domUtils.getDirectChild(/**@type{!Element}*/(node.parentNode.parentNode), officens, 'master-styles');
710             e = masterStyles && masterStyles.firstElementChild;
711             while (e) {
712                 // Generate CSS for all the pages that use the master page that use this page-layout
713                 if (e.namespaceURI === stylens && e.localName === "master-page"
714                         && e.getAttributeNS(stylens, 'page-layout-name')
715                             === stylename) {
716                     masterStyleName = e.getAttributeNS(stylens, 'name');
717 
718                     contentLayoutRule = 'draw|page[draw|master-page-name=' + masterStyleName + '] {' + rule + '}';
719                     pageSizeRule = 'office|body, draw|page[draw|master-page-name=' + masterStyleName + '] {'
720                             + applySimpleMapping(props, pageSizePropertySimpleMapping)
721                             + ' }';
722 
723                     sheet.insertRule(contentLayoutRule, sheet.cssRules.length);
724                     sheet.insertRule(pageSizeRule, sheet.cssRules.length);
725                 }
726                 e = e.nextElementSibling;
727             }
728 
729         } else if (documentType === 'text') {
730             contentLayoutRule = 'office|text {' + rule + '}';
731             rule = '';
732 
733             // TODO: We want to use the simpleMapping for ODTs, but not until we have pagination.
734             // So till then, set only the width.
735             //rule += applySimpleMapping(props, pageSizePropertySimpleMapping);
736             pageSizeRule = 'office|body {'
737                 + 'width: ' + props.getAttributeNS(fons, 'page-width') + ';'
738                 + '}';
739 
740             sheet.insertRule(contentLayoutRule, sheet.cssRules.length);
741             sheet.insertRule(pageSizeRule, sheet.cssRules.length);
742         }
743 
744     }
745 
746     /**
747      * @param {!CSSStyleSheet} sheet
748      * @param {string} family
749      * @param {string} name
750      * @param {!odf.StyleTreeNode} node
751      * @return {undefined}
752      */
753     function addRule(sheet, family, name, node) {
754         if (family === "page") {
755             addPageStyleRules(sheet, node.element);
756         } else {
757             addStyleRule(sheet, family, name, node);
758         }
759     }
760     /**
761      * @param {!CSSStyleSheet} sheet
762      * @param {string} family
763      * @param {string} name
764      * @param {!odf.StyleTreeNode} node
765      * @return {undefined}
766      */
767     function addRules(sheet, family, name, node) {
768         addRule(sheet, family, name, node);
769         var /**@type{string}*/
770             n;
771         for (n in node.derivedStyles) {
772             if (node.derivedStyles.hasOwnProperty(n)) {
773                 addRules(sheet, family, n, node.derivedStyles[n]);
774             }
775         }
776     }
777 
778     // css vs odf styles
779     // ODF styles occur in families. A family is a group of odf elements to
780     // which an element applies. ODF families can be mapped to a group of css
781     // elements
782 
783     /**
784      * @param {!string} doctype
785      * @param {!Element} rootNode
786      * @param {!CSSStyleSheet} stylesheet
787      * @param {!Object.<string,string>} fontFaceMap
788      * @param {!Object.<string,!Object.<string,!odf.StyleTreeNode>>} styleTree
789      * @return {undefined}
790      */
791     this.style2css = function (doctype, rootNode, stylesheet, fontFaceMap, styleTree) {
792         var tree, rule,
793             /**@type{string}*/
794             name,
795             /**@type{string}*/
796             family;
797 
798         odfRoot = rootNode;
799 
800         // make stylesheet empty
801         while (stylesheet.cssRules.length) {
802             stylesheet.deleteRule(stylesheet.cssRules.length - 1);
803         }
804 
805         // add @odfRoot namespace rules
806         odf.Namespaces.forEachPrefix(function (prefix, ns) {
807             rule = '@namespace ' + prefix + ' url(' + ns + ');';
808             try {
809                 stylesheet.insertRule(rule, stylesheet.cssRules.length);
810             } catch (/**@type{!DOMException}*/ignore) {
811                 // WebKit can throw an exception here, but it will have
812                 // retained the namespace declarations anyway.
813             }
814         });
815 
816         fontFaceDeclsMap = fontFaceMap;
817         documentType = doctype;
818         defaultFontSize = runtime.getWindow().getComputedStyle(document.body, null).getPropertyValue('font-size') || '12pt';
819 
820         // add the various styles
821         for (family in familynamespaceprefixes) {
822             if (familynamespaceprefixes.hasOwnProperty(family)) {
823                 tree = styleTree[family];
824                 for (name in tree) {
825                     if (tree.hasOwnProperty(name)) {
826                         addRules(stylesheet, family, name, tree[name]);
827                     }
828                 }
829             }
830         }
831     };
832 };
833