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         /**@const
 58          * @type {!string}*/
 59         webodfhelperns = "urn:webodf:names:helper",
 60         domUtils = core.DomUtils,
 61         styleParseUtils = new odf.StyleParseUtils(),
 62 
 63         /**@const
 64            @type{!Object.<string,string>}*/
 65         familynamespaceprefixes = {
 66             'graphic': 'draw',
 67             'drawing-page': 'draw',
 68             'paragraph': 'text',
 69             'presentation': 'presentation',
 70             'ruby': 'text',
 71             'section': 'text',
 72             'table': 'table',
 73             'table-cell': 'table',
 74             'table-column': 'table',
 75             'table-row': 'table',
 76             'text': 'text',
 77             'list': 'text',
 78             'page': 'office'
 79         },
 80 
 81         /**@const
 82            @type{!Object.<string,!Array.<!string>>}*/
 83         familytagnames = {
 84             'graphic': ['circle', 'connected', 'control', 'custom-shape',
 85                 'ellipse', 'frame', 'g', 'line', 'measure', 'page',
 86                 'page-thumbnail', 'path', 'polygon', 'polyline', 'rect',
 87                 'regular-polygon' ],
 88             'paragraph': ['alphabetical-index-entry-template', 'h',
 89                 'illustration-index-entry-template', 'index-source-style',
 90                 'object-index-entry-template', 'p',
 91                 'table-index-entry-template', 'table-of-content-entry-template',
 92                 'user-index-entry-template'],
 93             'presentation': ['caption', 'circle', 'connector', 'control',
 94                 'custom-shape', 'ellipse', 'frame', 'g', 'line', 'measure',
 95                 'page-thumbnail', 'path', 'polygon', 'polyline', 'rect',
 96                 'regular-polygon'],
 97             'drawing-page': ['caption', 'circle', 'connector', 'control', 'page',
 98                 'custom-shape', 'ellipse', 'frame', 'g', 'line', 'measure',
 99                 'page-thumbnail', 'path', 'polygon', 'polyline', 'rect',
100                 'regular-polygon'],
101             'ruby': ['ruby', 'ruby-text'],
102             'section': ['alphabetical-index', 'bibliography',
103                 'illustration-index', 'index-title', 'object-index', 'section',
104                 'table-of-content', 'table-index', 'user-index'],
105             'table': ['background', 'table'],
106             'table-cell': ['body', 'covered-table-cell', 'even-columns',
107                 'even-rows', 'first-column', 'first-row', 'last-column',
108                 'last-row', 'odd-columns', 'odd-rows', 'table-cell'],
109             'table-column': ['table-column'],
110             'table-row': ['table-row'],
111             'text': ['a', 'index-entry-chapter', 'index-entry-link-end',
112                 'index-entry-link-start', 'index-entry-page-number',
113                 'index-entry-span', 'index-entry-tab-stop', 'index-entry-text',
114                 'index-title-template', 'linenumbering-configuration',
115                 'list-level-style-number', 'list-level-style-bullet',
116                 'outline-level-style', 'span'],
117             'list': ['list-item']
118         },
119 
120         /**@const
121            @type{!Array.<!Array.<!string>>}*/
122         textPropertySimpleMapping = [
123             [ fons, 'color', 'color' ],
124             // this sets the element background, not just the text background
125             [ fons, 'background-color', 'background-color' ],
126             [ fons, 'font-weight', 'font-weight' ],
127             [ fons, 'font-style', 'font-style' ]
128         ],
129 
130         /**@const
131            @type{!Array.<!Array.<!string>>}*/
132         bgImageSimpleMapping = [
133             [ stylens, 'repeat', 'background-repeat' ]
134         ],
135 
136         /**@const
137            @type{!Array.<!Array.<!string>>}*/
138         paragraphPropertySimpleMapping = [
139             [ fons, 'background-color', 'background-color' ],
140             [ fons, 'text-align', 'text-align' ],
141             [ fons, 'text-indent', 'text-indent' ],
142             [ fons, 'padding', 'padding' ],
143             [ fons, 'padding-left', 'padding-left' ],
144             [ fons, 'padding-right', 'padding-right' ],
145             [ fons, 'padding-top', 'padding-top' ],
146             [ fons, 'padding-bottom', 'padding-bottom' ],
147             [ fons, 'border-left', 'border-left' ],
148             [ fons, 'border-right', 'border-right' ],
149             [ fons, 'border-top', 'border-top' ],
150             [ fons, 'border-bottom', 'border-bottom' ],
151             [ fons, 'margin', 'margin' ],
152             [ fons, 'margin-left', 'margin-left' ],
153             [ fons, 'margin-right', 'margin-right' ],
154             [ fons, 'margin-top', 'margin-top' ],
155             [ fons, 'margin-bottom', 'margin-bottom' ],
156             [ fons, 'border', 'border' ]
157         ],
158 
159         /**@const
160            @type{!Array.<!Array.<!string>>}*/
161         graphicPropertySimpleMapping = [
162             [ fons, 'background-color', 'background-color'],
163             [ fons, 'min-height', 'min-height' ],
164             [ drawns, 'stroke', 'border' ],
165             [ svgns, 'stroke-color', 'border-color' ],
166             [ svgns, 'stroke-width', 'border-width' ],
167             [ fons, 'border', 'border' ],
168             [ fons, 'border-left', 'border-left' ],
169             [ fons, 'border-right', 'border-right' ],
170             [ fons, 'border-top', 'border-top' ],
171             [ fons, 'border-bottom', 'border-bottom' ]
172         ],
173 
174         /**@const
175            @type{!Array.<!Array.<!string>>}*/
176         tablecellPropertySimpleMapping = [
177             [ fons, 'background-color', 'background-color' ],
178             [ fons, 'border-left', 'border-left' ],
179             [ fons, 'border-right', 'border-right' ],
180             [ fons, 'border-top', 'border-top' ],
181             [ fons, 'border-bottom', 'border-bottom' ],
182             [ fons, 'border', 'border' ]
183         ],
184 
185         /**@const
186            @type{!Array.<!Array.<!string>>}*/
187         tablecolumnPropertySimpleMapping = [
188             [ stylens, 'column-width', 'width' ]
189         ],
190 
191         /**@const
192            @type{!Array.<!Array.<!string>>}*/
193         tablerowPropertySimpleMapping = [
194             [ stylens, 'row-height', 'height' ],
195             [ fons, 'keep-together', null ]
196         ],
197 
198         /**@const
199            @type{!Array.<!Array.<!string>>}*/
200         tablePropertySimpleMapping = [
201             [ stylens, 'width', 'width' ],
202             [ fons, 'margin-left', 'margin-left' ],
203             [ fons, 'margin-right', 'margin-right' ],
204             [ fons, 'margin-top', 'margin-top' ],
205             [ fons, 'margin-bottom', 'margin-bottom' ]
206         ],
207 
208         /**@const
209            @type{!Array.<!Array.<!string>>}*/
210         pageContentPropertySimpleMapping = [
211             [ fons, 'background-color', 'background-color' ],
212             [ fons, 'padding', 'padding' ],
213             [ fons, 'padding-left', 'padding-left' ],
214             [ fons, 'padding-right', 'padding-right' ],
215             [ fons, 'padding-top', 'padding-top' ],
216             [ fons, 'padding-bottom', 'padding-bottom' ],
217             [ fons, 'border', 'border' ],
218             [ fons, 'border-left', 'border-left' ],
219             [ fons, 'border-right', 'border-right' ],
220             [ fons, 'border-top', 'border-top' ],
221             [ fons, 'border-bottom', 'border-bottom' ],
222             [ fons, 'margin', 'margin' ],
223             [ fons, 'margin-left', 'margin-left' ],
224             [ fons, 'margin-right', 'margin-right' ],
225             [ fons, 'margin-top', 'margin-top' ],
226             [ fons, 'margin-bottom', 'margin-bottom' ]
227         ],
228 
229         /**@const
230            @type{!Array.<!Array.<!string>>}*/
231         pageSizePropertySimpleMapping = [
232             [ fons, 'page-width', 'width' ],
233             [ fons, 'page-height', 'height' ]
234         ],
235 
236         /**@const
237            @type{!Object.<!boolean>}*/
238         borderPropertyMap = {
239             'border': true,
240             'border-left': true,
241             'border-right': true,
242             'border-top': true,
243             'border-bottom': true,
244             'stroke-width': true
245         },
246 
247         /**@const
248            @type{!Object.<!boolean>}*/
249         marginPropertyMap = {
250             'margin': true,
251             'margin-left': true,
252             'margin-right': true,
253             'margin-top': true,
254             'margin-bottom': true
255         },
256 
257         // A font-face declaration map, to be populated once style2css is called.
258         /**@type{!Object.<string,string>}*/
259         fontFaceDeclsMap = {},
260         utils = odf.OdfUtils,
261         documentType,
262         odfRoot,
263         defaultFontSize,
264         xpath = xmldom.XPath,
265         cssUnits = new core.CSSUnits();
266 
267     /**
268      * @param {!string} family
269      * @param {!string} name
270      * @return {?string}
271      */
272     function createSelector(family, name) {
273         var prefix = familynamespaceprefixes[family],
274             namepart,
275             selector;
276         if (prefix === undefined) {
277             return null;
278         }
279 
280         // If there is no name, it is a default style, in which case style-name shall be used without a value
281         if (name) {
282             namepart = '[' + prefix + '|style-name="' + name + '"]';
283         } else {
284             namepart = '';
285         }
286         if (prefix === 'presentation') {
287             prefix = 'draw';
288             if (name) {
289                 namepart = '[presentation|style-name="' + name + '"]';
290             } else {
291                 namepart = '';
292             }
293         }
294         selector = prefix + '|' + familytagnames[family].join(
295             namepart + ',' + prefix + '|'
296         ) + namepart;
297         return selector;
298     }
299     /**
300      * @param {!string} family
301      * @param {!string} name
302      * @param {!odf.StyleTreeNode} node
303      * @return {!Array.<string>}
304      */
305     function getSelectors(family, name, node) {
306         var selectors = [], ss,
307             derivedStyles = node.derivedStyles,
308             /**@type{string}*/
309             n;
310         ss = createSelector(family, name);
311         if (ss !== null) {
312             selectors.push(ss);
313         }
314         for (n in derivedStyles) {
315             if (derivedStyles.hasOwnProperty(n)) {
316                 ss = getSelectors(family, n, derivedStyles[n]);
317                 selectors = selectors.concat(ss);
318             }
319         }
320         return selectors;
321     }
322     /**
323      * Make sure border width is no less than 1px wide; otherwise border is not rendered.
324      * Only have problems with point unit at the moment. Please add more rule if needed.
325      * @param {!string} value a string contains border attributes eg. 1pt solid black or 1px
326      * @return {!string}
327      */
328     function fixBorderWidth(value) {
329         var index = value.indexOf(' '),
330             width, theRestOfBorderAttributes;
331 
332         if (index !== -1) {
333             width = value.substring(0, index);
334             theRestOfBorderAttributes = value.substring(index); // everything after the width attribute
335         } else {
336             width = value;
337             theRestOfBorderAttributes = '';
338         }
339 
340         width = utils.parseLength(width);
341         // According to CSS 2.1, 1px is equal to 0.75pt http://www.w3.org/TR/CSS2/syndata.html#length-units
342         if (width && width.unit === 'pt' && width.value < 0.75) {
343             value = '0.75pt' + theRestOfBorderAttributes;
344         }
345         return value;
346     }
347 
348     /**
349      * Returns the parent style node of a given style node
350      * @param {!Element} styleNode
351      * @return {Element}
352      */
353     function getParentStyleNode(styleNode) {
354         var parentStyleName = '',
355             parentStyleFamily = '',
356             parentStyleNode = null,
357             xp;
358 
359         if (styleNode.localName === 'default-style') {
360             return null;
361         }
362 
363         parentStyleName = styleNode.getAttributeNS(stylens, 'parent-style-name');
364         parentStyleFamily = styleNode.getAttributeNS(stylens, 'family');
365 
366         if (parentStyleName) {
367             xp = "//style:*[@style:name='" + parentStyleName + "'][@style:family='" + parentStyleFamily + "']";
368         } else {
369             xp = "//style:default-style[@style:family='" + parentStyleFamily + "']";
370         }
371         parentStyleNode = xpath.getODFElementsWithXPath(/**@type{!Element}*/(odfRoot), xp, odf.Namespaces.lookupNamespaceURI)[0];
372         return parentStyleNode;
373     }
374 
375     /**
376      * Margins can be a percentage of the parent style. Resolve to
377      * absolute value.
378      *
379      * See http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1419838_253892949
380      * for further information.
381      *
382      * @param {!Element} props
383      * @param {!string} namespace to use when looking up attributes in parents
384      * @param {!string} name of attribute to lookup in parents
385      * @param {!string} value a string contains margin attributes eg. 1px
386      * @return {!string}
387      */
388     function fixMargin(props, namespace, name, value) {
389         var length = utils.parseLength(value),
390             multiplier,
391             parentStyle,
392             parentLength,
393             result,
394             properties;
395         if (!length || length.unit !== '%') {
396             return value;
397         }
398 
399         // margin is defined as percentage, traverse up until we find a
400         // non-percentage value. If no parent has a non-percentage value,
401         // no margin will be used.
402         multiplier = (length.value / 100);
403         parentStyle = getParentStyleNode(/**@type{!Element}*/(props.parentNode));
404         result = "0";
405         while (parentStyle) {
406             properties = domUtils.getDirectChild(parentStyle, stylens, 'paragraph-properties');
407             if (properties) {
408                 parentLength = utils.parseLength(properties.getAttributeNS(namespace, name));
409                 if (parentLength) {
410                     if (parentLength.unit !== '%') {
411                         result = (parentLength.value * multiplier) + parentLength.unit;
412                         break;
413                     }
414                     multiplier *= (parentLength.value / 100);
415                 }
416             }
417             parentStyle = getParentStyleNode(parentStyle);
418         }
419         return result;
420     }
421 
422     /**
423      * @param {!Element} props
424      * @param {!Array.<!Array.<!string>>} mapping
425      * @return {!string}
426      */
427     function applySimpleMapping(props, mapping) {
428         var rule = '', i, r, value;
429         for (i = 0; i < mapping.length; i += 1) {
430             r = mapping[i];
431             value = props.getAttributeNS(r[0], r[1]);
432 
433             if (value) {
434                 value = value.trim();
435 
436                 if (borderPropertyMap.hasOwnProperty(r[1])) {
437                     value = fixBorderWidth(value);
438                 } else if (marginPropertyMap.hasOwnProperty(r[1])) {
439                     value = fixMargin(props, r[0], r[1], value);
440                 }
441                 if (r[2]) {
442                     rule += r[2] + ':' + value + ';';
443                 }
444             }
445         }
446         return rule;
447     }
448 
449     /**
450      * Returns the font size attribute value from the text properties of a style node
451      * @param {?Element} styleNode
452      * @return {?{value: !number, unit: !string}}
453      */
454     function getFontSize(styleNode) {
455         var props = domUtils.getDirectChild(styleNode, stylens, 'text-properties');
456         if (props) {
457             return utils.parseFoFontSize(props.getAttributeNS(fons, 'font-size'));
458         }
459         return null;
460     }
461 
462     /**
463      * Parse the style:text-position property
464      * http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1420212_253892949
465      *
466      * Examples:
467      * "sub"
468      * "super 20%"
469      * "10% 20%"
470      * "-15% 50%"
471      *
472      * @param {!string} position
473      * @return {!{verticalTextPosition: !string, fontHeight: (!string|undefined)}}
474      */
475     function parseTextPosition(position) {
476         var parts = styleParseUtils.parseAttributeList(position);
477         return {
478             verticalTextPosition: parts[0],
479             fontHeight: parts[1]
480         };
481     }
482     /**
483      * @param {!Element} props
484      * @return {!string}
485      */
486     function getTextProperties(props) {
487         var rule = '',
488             fontName,
489             fontSize,
490             value,
491             textDecorationLine = '',
492             textDecorationStyle = '',
493             textPosition,
494             fontSizeRule = '',
495             sizeMultiplier = 1,
496             textFamilyStyleNode;
497 
498         rule += applySimpleMapping(props, textPropertySimpleMapping);
499 
500         value = props.getAttributeNS(stylens, 'text-underline-style');
501         if (value === 'solid') {
502             textDecorationLine += ' underline';
503         }
504         value = props.getAttributeNS(stylens, 'text-line-through-style');
505         if (value === 'solid') {
506             textDecorationLine += ' line-through';
507         }
508 
509         if (textDecorationLine.length) {
510             // CSS2
511             rule += 'text-decoration:' + textDecorationLine + ';\n';
512             // CSS3 text-decoration shorthand
513             rule += 'text-decoration-line:' + textDecorationLine + ';\n';
514             // CSS3 text-decoration shorthand - FF
515             rule += '-moz-text-decoration-line:' + textDecorationLine + ';\n';
516         }
517 
518         value = props.getAttributeNS(stylens, 'text-line-through-type');
519         switch (value) {
520             case 'double':
521                 textDecorationStyle += ' double';
522                 break;
523             case 'single':
524                 textDecorationStyle += ' single';
525                 break;
526         }
527         if (textDecorationStyle) {
528             // CSS3
529             rule += 'text-decoration-style:' + textDecorationStyle + ';\n';
530             // CSS3 text-decoration shorthand - FF
531             rule += '-moz-text-decoration-style:' + textDecorationStyle + ';\n';
532         }
533 
534         fontName = props.getAttributeNS(stylens, 'font-name')
535             || props.getAttributeNS(fons, 'font-family');
536         if (fontName) {
537             value = fontFaceDeclsMap[fontName];
538             // TODO: use other information from style:font-face, like style:font-family-generic
539             rule += 'font-family: ' + (value || fontName) + ';';
540         }
541 
542         value = props.getAttributeNS(stylens, 'text-position');
543         if (value) {
544             textPosition = parseTextPosition(value);
545             rule += 'vertical-align: ' + textPosition.verticalTextPosition + '\n; ';
546             if (textPosition.fontHeight) {
547                 sizeMultiplier = parseFloat(textPosition.fontHeight) / 100;
548             }
549         }
550 
551         if (props.hasAttributeNS(fons, "font-size") || sizeMultiplier !== 1) {
552             // This is actually the font size of the current style.
553             textFamilyStyleNode = /**@type{!Element}*/(props.parentNode);
554             while (textFamilyStyleNode) {
555                 fontSize = getFontSize(textFamilyStyleNode);
556                 if (fontSize) {
557                     // If the current style's font size is a non-% value, then apply the multiplier to get the child style (with the %)'s
558                     // actual font size. And now we can stop crawling up the style ancestry since we have a concrete font size.
559                     if (fontSize.unit !== '%') {
560                         fontSizeRule = 'font-size: ' + (fontSize.value * sizeMultiplier) + fontSize.unit + ';';
561                         break;
562                     }
563                     // If we got a % font size for the current style, then update the multiplier with it's 'normalized' multiplier
564                     sizeMultiplier *= (fontSize.value / 100);
565                 }
566                 // Crawl up the style ancestry
567                 textFamilyStyleNode = getParentStyleNode(textFamilyStyleNode);
568             }
569             // If there was nothing in the ancestry that specified a concrete font size, just apply the multiplier onto the page's default font size.
570             if (!fontSizeRule) {
571                 fontSizeRule = 'font-size: ' + parseFloat(defaultFontSize) * sizeMultiplier + cssUnits.getUnits(defaultFontSize) + ';';
572             }
573         }
574 
575         rule += fontSizeRule;
576 
577         return rule;
578     }
579     /**
580      * @param {!Element} props <style:paragraph-properties/>
581      * @return {!string}
582      */
583     function getParagraphProperties(props) {
584         var rule = '', bgimage, url, lineHeight;
585         rule += applySimpleMapping(props, paragraphPropertySimpleMapping);
586         bgimage = domUtils.getDirectChild(props, stylens, 'background-image');
587         if (bgimage) {
588             url = bgimage.getAttributeNS(xlinkns, 'href');
589             if (url) {
590                 rule += "background-image: url('odfkit:" + url + "');";
591                 //rule += "background-repeat: repeat;"; //FIXME test
592                 rule += applySimpleMapping(bgimage, bgImageSimpleMapping);
593             }
594         }
595 
596         lineHeight = props.getAttributeNS(fons, 'line-height');
597         if (lineHeight && lineHeight !== 'normal') {
598             lineHeight = utils.parseFoLineHeight(lineHeight);
599             if (lineHeight.unit !== '%') {
600                 rule += 'line-height: ' + lineHeight.value + lineHeight.unit + ';';
601             } else {
602                 rule += 'line-height: ' + lineHeight.value / 100 + ';';
603             }
604         }
605 
606         return rule;
607     }
608 
609 /*jslint unparam: true*/
610     /**
611      * @param {*} m
612      * @param {string} r
613      * @param {string} g
614      * @param {string} b
615      * @return {string}
616      */
617     function matchToRgb(m, r, g, b) {
618         return r + r + g + g + b + b;
619     }
620 /*jslint unparam: false*/
621 
622     /**
623      * @param {!string} hex
624      * @return {?{ r: number, g: number, b: number}}
625      */
626     function hexToRgb(hex) {
627         // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
628         var result,
629             shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
630         hex = hex.replace(shorthandRegex, matchToRgb);
631 
632         result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
633         return result ? {
634             r: parseInt(result[1], 16),
635             g: parseInt(result[2], 16),
636             b: parseInt(result[3], 16)
637         } : null;
638     }
639 
640     /**
641      * @param {string} n
642      * @return {boolean}
643      */
644     function isNumber(n) {
645         return !isNaN(parseFloat(n));
646     }
647 
648    /**
649      * @param {!Element} props
650      * @return {string}
651      */
652     function getGraphicProperties(props) {
653         var rule = '', alpha, bgcolor, fill;
654 
655         rule += applySimpleMapping(props, graphicPropertySimpleMapping);
656         alpha = props.getAttributeNS(drawns, 'opacity');
657         fill = props.getAttributeNS(drawns, 'fill');
658         bgcolor = props.getAttributeNS(drawns, 'fill-color');
659 
660         if (fill === 'solid' || fill === 'hatch') {
661             if (bgcolor && bgcolor !== 'none') {
662                 alpha = isNumber(alpha) ? parseFloat(alpha) / 100 : 1;
663                 bgcolor = hexToRgb(bgcolor);
664                 if (bgcolor) {
665                     rule += "background-color: rgba("
666                         + bgcolor.r + ","
667                         + bgcolor.g + ","
668                         + bgcolor.b + ","
669                         + alpha + ");";
670                 }
671             } else {
672                 rule += "background: none;";
673             }
674         } else if (fill === "none") {
675             rule += "background: none;";
676         }
677 
678         return rule;
679     }
680    /**
681      * @param {!Element} props
682      * @return {string}
683      */
684     function getDrawingPageProperties(props) {
685         var rule = '';
686 
687         rule += applySimpleMapping(props, graphicPropertySimpleMapping);
688         if (props.getAttributeNS(presentationns, 'background-visible') === 'true') {
689             rule += "background: none;";
690         }
691         return rule;
692     }
693     /**
694      * @param {!Element} props
695      * @return {string}
696      */
697     function getTableCellProperties(props) {
698         var rule = '';
699         rule += applySimpleMapping(props, tablecellPropertySimpleMapping);
700         return rule;
701     }
702     /**
703      * @param {!Element} props
704      * @return {string}
705      */
706     function getTableRowProperties(props) {
707         var rule = '';
708         rule += applySimpleMapping(props, tablerowPropertySimpleMapping);
709         return rule;
710     }
711     /**
712      * @param {!Element} props
713      * @return {string}
714      */
715     function getTableColumnProperties(props) {
716         var rule = '';
717         rule += applySimpleMapping(props, tablecolumnPropertySimpleMapping);
718         return rule;
719     }
720     /**
721      * @param {!Element} props
722      * @return {string}
723      */
724     function getTableProperties(props) {
725         var rule = '', borderModel;
726         rule += applySimpleMapping(props, tablePropertySimpleMapping);
727         borderModel = props.getAttributeNS(tablens, 'border-model');
728 
729         if (borderModel === 'collapsing') {
730             rule += 'border-collapse:collapse;';
731         } else if (borderModel === 'separating') {
732             rule += 'border-collapse:separate;';
733         }
734 
735         return rule;
736     }
737 
738     /**
739      * Gets a list with the names of all styles derived from the given style,
740      * including the name of the style itself.
741      * @param {!string} styleName
742      * @param {!odf.StyleTreeNode} node
743      * @return {!Array.<!string>}
744      */
745     function getDerivedStyleNames(styleName, node) {
746         var /** @type{!Array.<!string>} */
747             styleNames = [styleName],
748             derivedStyles = node.derivedStyles;
749 
750         Object.keys(derivedStyles).forEach(function(styleName) {
751             var dsn = getDerivedStyleNames(styleName, derivedStyles[styleName]);
752             styleNames = styleNames.concat(dsn);
753         });
754 
755         return styleNames;
756     }
757 
758     /**
759      * Adds rules to control the display of certain frame classes in master pages
760      * when shown in page using the master page.
761      * @param {!CSSStyleSheet} sheet
762      * @param {!string} styleName
763      * @param {!Element} properties
764      * @param {!odf.StyleTreeNode} node
765      * @return {undefined}
766      */
767     function addDrawPageFrameDisplayRules(sheet, styleName, properties, node) {
768         var /**@const
769                @type {!Array.<!string>}*/
770             frameClasses = ["page-number", "date-time", "header", "footer"],
771             styleNames = getDerivedStyleNames(styleName, node),
772             /**@type {!Array.<!string>}*/
773             visibleFrameClasses = [],
774             /**@type {!Array.<!string>}*/
775             invisibleFrameClasses = [];
776 
777         /**
778          * @param {!Array.<!string>} controlledFrameClasses
779          * @param {!string} visibility
780          * @return {undefined}
781          */
782         function insertFrameVisibilityRule(controlledFrameClasses, visibility) {
783             var selectors = [],
784                 rule;
785             controlledFrameClasses.forEach(function(frameClass) {
786                 styleNames.forEach(function(styleName) {
787                     selectors.push('draw|page[webodfhelper|page-style-name="'+styleName+'"] draw|frame[presentation|class="'+frameClass+'"]');
788                 });
789             });
790             if (selectors.length > 0) {
791                 rule = selectors.join(",") + "{visibility:"+visibility+";}";
792                 sheet.insertRule(rule, sheet.cssRules.length);
793             }
794         }
795 
796         frameClasses.forEach(function(frameClass) {
797             var displayValue;
798 
799             displayValue = properties.getAttributeNS(presentationns, 'display-'+frameClass);
800             if (displayValue === 'true') {
801                 visibleFrameClasses.push(frameClass);
802             } else if (displayValue === 'false') {
803                 invisibleFrameClasses.push(frameClass);
804             } // else the attribute does not exist (returned as ""/null) or has a bad value
805 
806         });
807 
808         insertFrameVisibilityRule(visibleFrameClasses, "visible");
809         insertFrameVisibilityRule(invisibleFrameClasses, "hidden");
810     }
811 
812     /**
813      * @param {!CSSStyleSheet} sheet
814      * @param {string} family
815      * @param {string} name
816      * @param {!odf.StyleTreeNode} node
817      * @return {undefined}
818      */
819     function addStyleRule(sheet, family, name, node) {
820         var selectors = getSelectors(family, name, node),
821             selector = selectors.join(','),
822             rule = '',
823             properties;
824 
825         properties = domUtils.getDirectChild(node.element, stylens, 'text-properties');
826         if (properties) {
827             rule += getTextProperties(properties);
828         }
829         properties = domUtils.getDirectChild(node.element,
830                 stylens, 'paragraph-properties');
831         if (properties) {
832             rule += getParagraphProperties(properties);
833         }
834         properties = domUtils.getDirectChild(node.element,
835                  stylens, 'graphic-properties');
836         if (properties) {
837             rule += getGraphicProperties(properties);
838         }
839         properties = domUtils.getDirectChild(node.element,
840                  stylens, 'drawing-page-properties');
841         if (properties) {
842             rule += getDrawingPageProperties(properties);
843             addDrawPageFrameDisplayRules(sheet, name, /**@type{!Element}*/(properties), node);
844         }
845         properties = domUtils.getDirectChild(node.element,
846                  stylens, 'table-cell-properties');
847         if (properties) {
848             rule += getTableCellProperties(properties);
849         }
850         properties = domUtils.getDirectChild(node.element,
851                  stylens, 'table-row-properties');
852         if (properties) {
853             rule += getTableRowProperties(properties);
854         }
855         properties = domUtils.getDirectChild(node.element,
856                  stylens, 'table-column-properties');
857         if (properties) {
858             rule += getTableColumnProperties(properties);
859         }
860         properties = domUtils.getDirectChild(node.element,
861                  stylens, 'table-properties');
862         if (properties) {
863             rule += getTableProperties(properties);
864         }
865         if (rule.length === 0) {
866             return;
867         }
868         rule = selector + '{' + rule + '}';
869         sheet.insertRule(rule, sheet.cssRules.length);
870     }
871 
872     /**
873      * @param {!CSSStyleSheet} sheet
874      * @param {!Element} node <style:page-layout/>/<style:default-page-layout/>
875      * @return {undefined}
876      */
877     function addPageStyleRules(sheet, node) {
878         var rule = '', imageProps, url,
879             contentLayoutRule = '',
880             pageSizeRule = '',
881             props = domUtils.getDirectChild(node, stylens, 'page-layout-properties'),
882             stylename,
883             masterStyles,
884             e,
885             masterStyleName;
886         if (!props) {
887             return;
888         }
889         stylename = node.getAttributeNS(stylens, 'name');
890 
891         rule += applySimpleMapping(props, pageContentPropertySimpleMapping);
892         imageProps = domUtils.getDirectChild(props, stylens, 'background-image');
893         if (imageProps) {
894             url = imageProps.getAttributeNS(xlinkns, 'href');
895             if (url) {
896                 rule += "background-image: url('odfkit:" + url + "');";
897                 //rule += "background-repeat: repeat;"; //FIXME test
898                 rule += applySimpleMapping(imageProps, bgImageSimpleMapping);
899             }
900         }
901 
902         if (documentType === 'presentation') {
903             masterStyles = domUtils.getDirectChild(/**@type{!Element}*/(node.parentNode.parentNode), officens, 'master-styles');
904             e = masterStyles && masterStyles.firstElementChild;
905             while (e) {
906                 // Generate CSS for all the pages that use the master page that use this page-layout
907                 if (e.namespaceURI === stylens && e.localName === "master-page"
908                         && e.getAttributeNS(stylens, 'page-layout-name')
909                             === stylename) {
910                     masterStyleName = e.getAttributeNS(stylens, 'name');
911 
912                     contentLayoutRule = 'draw|page[draw|master-page-name="' + masterStyleName + '"] {' + rule + '}';
913                     pageSizeRule = 'office|body, draw|page[draw|master-page-name="' + masterStyleName + '"] {'
914                             + applySimpleMapping(props, pageSizePropertySimpleMapping)
915                             + ' }';
916 
917                     sheet.insertRule(contentLayoutRule, sheet.cssRules.length);
918                     sheet.insertRule(pageSizeRule, sheet.cssRules.length);
919                 }
920                 e = e.nextElementSibling;
921             }
922 
923         } else if (documentType === 'text') {
924             contentLayoutRule = 'office|text {' + rule + '}';
925             rule = '';
926 
927             // TODO: We want to use the simpleMapping for ODTs, but not until we have pagination.
928             // So till then, set only the width.
929             //rule += applySimpleMapping(props, pageSizePropertySimpleMapping);
930             pageSizeRule = 'office|body {'
931                 + 'width: ' + props.getAttributeNS(fons, 'page-width') + ';'
932                 + '}';
933 
934             sheet.insertRule(contentLayoutRule, sheet.cssRules.length);
935             sheet.insertRule(pageSizeRule, sheet.cssRules.length);
936         }
937 
938     }
939 
940     /**
941      * @param {!CSSStyleSheet} sheet
942      * @param {string} family
943      * @param {string} name
944      * @param {!odf.StyleTreeNode} node
945      * @return {undefined}
946      */
947     function addRule(sheet, family, name, node) {
948         if (family === "page") {
949             addPageStyleRules(sheet, node.element);
950         } else {
951             addStyleRule(sheet, family, name, node);
952         }
953     }
954     /**
955      * @param {!CSSStyleSheet} sheet
956      * @param {string} family
957      * @param {string} name
958      * @param {!odf.StyleTreeNode} node
959      * @return {undefined}
960      */
961     function addRules(sheet, family, name, node) {
962         addRule(sheet, family, name, node);
963         var /**@type{string}*/
964             n;
965         for (n in node.derivedStyles) {
966             if (node.derivedStyles.hasOwnProperty(n)) {
967                 addRules(sheet, family, n, node.derivedStyles[n]);
968             }
969         }
970     }
971 
972     // css vs odf styles
973     // ODF styles occur in families. A family is a group of odf elements to
974     // which an element applies. ODF families can be mapped to a group of css
975     // elements
976 
977     /**
978      * @param {!string} doctype
979      * @param {!Element} rootNode
980      * @param {!CSSStyleSheet} stylesheet
981      * @param {!Object.<string,string>} fontFaceMap
982      * @param {!Object.<string,!Object.<string,!odf.StyleTreeNode>>} styleTree
983      * @return {undefined}
984      */
985     this.style2css = function (doctype, rootNode, stylesheet, fontFaceMap, styleTree) {
986         var tree, rule,
987             /**@type{string}*/
988             name,
989             /**@type{string}*/
990             family;
991 
992         /**
993          * @param {!string} prefix
994          * @param {!string} ns
995          * @return {undefined}
996          */
997         function insertCSSNamespace(prefix, ns) {
998             rule = '@namespace ' + prefix + ' url(' + ns + ');';
999             try {
1000                 stylesheet.insertRule(rule, stylesheet.cssRules.length);
1001             } catch (/**@type{!DOMException}*/ignore) {
1002                 // WebKit can throw an exception here, but it will have
1003                 // retained the namespace declarations anyway.
1004             }
1005         }
1006 
1007         odfRoot = rootNode;
1008 
1009         // make stylesheet empty
1010         while (stylesheet.cssRules.length) {
1011             stylesheet.deleteRule(stylesheet.cssRules.length - 1);
1012         }
1013 
1014         // add @odfRoot namespace rules
1015         odf.Namespaces.forEachPrefix(insertCSSNamespace);
1016         insertCSSNamespace("webodfhelper", webodfhelperns);
1017 
1018         fontFaceDeclsMap = fontFaceMap;
1019         documentType = doctype;
1020         defaultFontSize = runtime.getWindow().getComputedStyle(document.body, null).getPropertyValue('font-size') || '12pt';
1021 
1022         // add the various styles
1023         for (family in familynamespaceprefixes) {
1024             if (familynamespaceprefixes.hasOwnProperty(family)) {
1025                 tree = styleTree[family];
1026                 for (name in tree) {
1027                     if (tree.hasOwnProperty(name)) {
1028                         addRules(stylesheet, family, name, tree[name]);
1029                     }
1030                 }
1031             }
1032         }
1033     };
1034 };
1035