UNPKG

node-webodf

Version:

WebODF - JavaScript Document Engine http://webodf.org/

1,039 lines (955 loc) 39 kB
/** * Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com> * * @licstart * This file is part of WebODF. * * WebODF is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License (GNU AGPL) * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * WebODF is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with WebODF. If not, see <http://www.gnu.org/licenses/>. * @licend * * @source: http://www.webodf.org/ * @source: https://github.com/kogmbh/WebODF/ */ /*global odf, runtime, xmldom, core, document*/ /** * @constructor */ odf.Style2CSS = function Style2CSS() { "use strict"; var // helper constants /**@const @type{!string}*/ drawns = odf.Namespaces.drawns, /**@const @type{!string}*/ fons = odf.Namespaces.fons, /**@const @type{!string}*/ officens = odf.Namespaces.officens, /**@const @type{!string}*/ stylens = odf.Namespaces.stylens, /**@const @type{!string}*/ svgns = odf.Namespaces.svgns, /**@const @type{!string}*/ tablens = odf.Namespaces.tablens, /**@const @type{!string}*/ xlinkns = odf.Namespaces.xlinkns, /**@const @type{!string}*/ presentationns = odf.Namespaces.presentationns, /**@const * @type {!string}*/ webodfhelperns = "urn:webodf:names:helper", domUtils = core.DomUtils, styleParseUtils = new odf.StyleParseUtils(), /**@const @type{!Object.<string,string>}*/ familynamespaceprefixes = { 'graphic': 'draw', 'drawing-page': 'draw', 'paragraph': 'text', 'presentation': 'presentation', 'ruby': 'text', 'section': 'text', 'table': 'table', 'table-cell': 'table', 'table-column': 'table', 'table-row': 'table', 'text': 'text', 'list': 'text', 'page': 'office' }, /**@const @type{!Object.<string,!Array.<!string>>}*/ familytagnames = { 'graphic': ['circle', 'connected', 'control', 'custom-shape', 'ellipse', 'frame', 'g', 'line', 'measure', 'page', 'page-thumbnail', 'path', 'polygon', 'polyline', 'rect', 'regular-polygon' ], 'paragraph': ['alphabetical-index-entry-template', 'h', 'illustration-index-entry-template', 'index-source-style', 'object-index-entry-template', 'p', 'table-index-entry-template', 'table-of-content-entry-template', 'user-index-entry-template'], 'presentation': ['caption', 'circle', 'connector', 'control', 'custom-shape', 'ellipse', 'frame', 'g', 'line', 'measure', 'page-thumbnail', 'path', 'polygon', 'polyline', 'rect', 'regular-polygon'], 'drawing-page': ['caption', 'circle', 'connector', 'control', 'page', 'custom-shape', 'ellipse', 'frame', 'g', 'line', 'measure', 'page-thumbnail', 'path', 'polygon', 'polyline', 'rect', 'regular-polygon'], 'ruby': ['ruby', 'ruby-text'], 'section': ['alphabetical-index', 'bibliography', 'illustration-index', 'index-title', 'object-index', 'section', 'table-of-content', 'table-index', 'user-index'], 'table': ['background', 'table'], 'table-cell': ['body', 'covered-table-cell', 'even-columns', 'even-rows', 'first-column', 'first-row', 'last-column', 'last-row', 'odd-columns', 'odd-rows', 'table-cell'], 'table-column': ['table-column'], 'table-row': ['table-row'], 'text': ['a', 'index-entry-chapter', 'index-entry-link-end', 'index-entry-link-start', 'index-entry-page-number', 'index-entry-span', 'index-entry-tab-stop', 'index-entry-text', 'index-title-template', 'linenumbering-configuration', 'list-level-style-number', 'list-level-style-bullet', 'outline-level-style', 'span'], 'list': ['list-item'] }, /**@const @type{!Array.<!Array.<!string>>}*/ textPropertySimpleMapping = [ [ fons, 'color', 'color' ], // this sets the element background, not just the text background [ fons, 'background-color', 'background-color' ], [ fons, 'font-weight', 'font-weight' ], [ fons, 'font-style', 'font-style' ], [ fons, 'font-variant', 'font-variant' ], [ fons, 'letter-spacing', 'letter-spacing' ], [ fons, 'text-transform', 'text-transform' ], [ fons, 'text-shadow', 'text-shadow' ] ], /**@const @type{!Array.<!Array.<!string>>}*/ bgImageSimpleMapping = [ [ stylens, 'repeat', 'background-repeat' ] ], /**@const @type{!Array.<!Array.<!string>>}*/ paragraphPropertySimpleMapping = [ [ fons, 'background-color', 'background-color' ], [ fons, 'text-align', 'text-align' ], [ fons, 'text-indent', 'text-indent' ], [ fons, 'padding', 'padding' ], [ fons, 'padding-left', 'padding-left' ], [ fons, 'padding-right', 'padding-right' ], [ fons, 'padding-top', 'padding-top' ], [ fons, 'padding-bottom', 'padding-bottom' ], [ fons, 'border-left', 'border-left' ], [ fons, 'border-right', 'border-right' ], [ fons, 'border-top', 'border-top' ], [ fons, 'border-bottom', 'border-bottom' ], [ fons, 'margin', 'margin' ], [ fons, 'margin-left', 'margin-left' ], [ fons, 'margin-right', 'margin-right' ], [ fons, 'margin-top', 'margin-top' ], [ fons, 'margin-bottom', 'margin-bottom' ], [ fons, 'border', 'border' ] ], /**@const @type{!Array.<!Array.<!string>>}*/ graphicPropertySimpleMapping = [ [ fons, 'background-color', 'background-color'], [ fons, 'min-height', 'min-height' ], [ drawns, 'stroke', 'border' ], [ svgns, 'stroke-color', 'border-color' ], [ svgns, 'stroke-width', 'border-width' ], [ fons, 'border', 'border' ], [ fons, 'border-left', 'border-left' ], [ fons, 'border-right', 'border-right' ], [ fons, 'border-top', 'border-top' ], [ fons, 'border-bottom', 'border-bottom' ] ], /**@const @type{!Array.<!Array.<!string>>}*/ tablecellPropertySimpleMapping = [ [ fons, 'background-color', 'background-color' ], [ fons, 'border-left', 'border-left' ], [ fons, 'border-right', 'border-right' ], [ fons, 'border-top', 'border-top' ], [ fons, 'border-bottom', 'border-bottom' ], [ fons, 'border', 'border' ] ], /**@const @type{!Array.<!Array.<!string>>}*/ tablecolumnPropertySimpleMapping = [ [ stylens, 'column-width', 'width' ] ], /**@const @type{!Array.<!Array.<!string>>}*/ tablerowPropertySimpleMapping = [ [ stylens, 'row-height', 'height' ], [ fons, 'keep-together', null ] ], /**@const @type{!Array.<!Array.<!string>>}*/ tablePropertySimpleMapping = [ [ stylens, 'width', 'width' ], [ fons, 'margin-left', 'margin-left' ], [ fons, 'margin-right', 'margin-right' ], [ fons, 'margin-top', 'margin-top' ], [ fons, 'margin-bottom', 'margin-bottom' ] ], /**@const @type{!Array.<!Array.<!string>>}*/ pageContentPropertySimpleMapping = [ [ fons, 'background-color', 'background-color' ], [ fons, 'padding', 'padding' ], [ fons, 'padding-left', 'padding-left' ], [ fons, 'padding-right', 'padding-right' ], [ fons, 'padding-top', 'padding-top' ], [ fons, 'padding-bottom', 'padding-bottom' ], [ fons, 'border', 'border' ], [ fons, 'border-left', 'border-left' ], [ fons, 'border-right', 'border-right' ], [ fons, 'border-top', 'border-top' ], [ fons, 'border-bottom', 'border-bottom' ], [ fons, 'margin', 'margin' ], [ fons, 'margin-left', 'margin-left' ], [ fons, 'margin-right', 'margin-right' ], [ fons, 'margin-top', 'margin-top' ], [ fons, 'margin-bottom', 'margin-bottom' ] ], /**@const @type{!Array.<!Array.<!string>>}*/ pageSizePropertySimpleMapping = [ [ fons, 'page-width', 'width' ], [ fons, 'page-height', 'height' ] ], /**@const @type{!Object.<!boolean>}*/ borderPropertyMap = { 'border': true, 'border-left': true, 'border-right': true, 'border-top': true, 'border-bottom': true, 'stroke-width': true }, /**@const @type{!Object.<!boolean>}*/ marginPropertyMap = { 'margin': true, 'margin-left': true, 'margin-right': true, 'margin-top': true, 'margin-bottom': true }, // A font-face declaration map, to be populated once style2css is called. /**@type{!Object.<string,string>}*/ fontFaceDeclsMap = {}, utils = odf.OdfUtils, documentType, odfRoot, defaultFontSize, xpath = xmldom.XPath, cssUnits = new core.CSSUnits(); /** * @param {!string} family * @param {!string} name * @return {?string} */ function createSelector(family, name) { var prefix = familynamespaceprefixes[family], namepart, selector; if (prefix === undefined) { return null; } // If there is no name, it is a default style, in which case style-name shall be used without a value if (name) { namepart = '[' + prefix + '|style-name="' + name + '"]'; } else { namepart = ''; } if (prefix === 'presentation') { prefix = 'draw'; if (name) { namepart = '[presentation|style-name="' + name + '"]'; } else { namepart = ''; } } selector = prefix + '|' + familytagnames[family].join( namepart + ',' + prefix + '|' ) + namepart; return selector; } /** * @param {!string} family * @param {!string} name * @param {!odf.StyleTreeNode} node * @return {!Array.<string>} */ function getSelectors(family, name, node) { var selectors = [], ss, derivedStyles = node.derivedStyles, /**@type{string}*/ n; ss = createSelector(family, name); if (ss !== null) { selectors.push(ss); } for (n in derivedStyles) { if (derivedStyles.hasOwnProperty(n)) { ss = getSelectors(family, n, derivedStyles[n]); selectors = selectors.concat(ss); } } return selectors; } /** * Make sure border width is no less than 1px wide; otherwise border is not rendered. * Only have problems with point unit at the moment. Please add more rule if needed. * @param {!string} value a string contains border attributes eg. 1pt solid black or 1px * @return {!string} */ function fixBorderWidth(value) { var index = value.indexOf(' '), width, theRestOfBorderAttributes; if (index !== -1) { width = value.substring(0, index); theRestOfBorderAttributes = value.substring(index); // everything after the width attribute } else { width = value; theRestOfBorderAttributes = ''; } width = utils.parseLength(width); // According to CSS 2.1, 1px is equal to 0.75pt http://www.w3.org/TR/CSS2/syndata.html#length-units if (width && width.unit === 'pt' && width.value < 0.75) { value = '0.75pt' + theRestOfBorderAttributes; } return value; } /** * Returns the parent style node of a given style node * @param {!Element} styleNode * @return {Element} */ function getParentStyleNode(styleNode) { var parentStyleName = '', parentStyleFamily = '', parentStyleNode = null, xp; if (styleNode.localName === 'default-style') { return null; } parentStyleName = styleNode.getAttributeNS(stylens, 'parent-style-name'); parentStyleFamily = styleNode.getAttributeNS(stylens, 'family'); if (parentStyleName) { xp = "//style:*[@style:name='" + parentStyleName + "'][@style:family='" + parentStyleFamily + "']"; } else { xp = "//style:default-style[@style:family='" + parentStyleFamily + "']"; } parentStyleNode = xpath.getODFElementsWithXPath(/**@type{!Element}*/(odfRoot), xp, odf.Namespaces.lookupNamespaceURI)[0]; return parentStyleNode; } /** * Margins can be a percentage of the parent style. Resolve to * absolute value. * * See http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1419838_253892949 * for further information. * * @param {!Element} props * @param {!string} namespace to use when looking up attributes in parents * @param {!string} name of attribute to lookup in parents * @param {!string} value a string contains margin attributes eg. 1px * @return {!string} */ function fixMargin(props, namespace, name, value) { var length = utils.parseLength(value), multiplier, parentStyle, parentLength, result, properties; if (!length || length.unit !== '%') { return value; } // margin is defined as percentage, traverse up until we find a // non-percentage value. If no parent has a non-percentage value, // no margin will be used. multiplier = (length.value / 100); parentStyle = getParentStyleNode(/**@type{!Element}*/(props.parentNode)); result = "0"; while (parentStyle) { properties = domUtils.getDirectChild(parentStyle, stylens, 'paragraph-properties'); if (properties) { parentLength = utils.parseLength(properties.getAttributeNS(namespace, name)); if (parentLength) { if (parentLength.unit !== '%') { result = (parentLength.value * multiplier) + parentLength.unit; break; } multiplier *= (parentLength.value / 100); } } parentStyle = getParentStyleNode(parentStyle); } return result; } /** * @param {!Element} props * @param {!Array.<!Array.<!string>>} mapping * @return {!string} */ function applySimpleMapping(props, mapping) { var rule = '', i, r, value; for (i = 0; i < mapping.length; i += 1) { r = mapping[i]; value = props.getAttributeNS(r[0], r[1]); if (value) { value = value.trim(); if (borderPropertyMap.hasOwnProperty(r[1])) { value = fixBorderWidth(value); } else if (marginPropertyMap.hasOwnProperty(r[1])) { value = fixMargin(props, r[0], r[1], value); } if (r[2]) { rule += r[2] + ':' + value + ';'; } } } return rule; } /** * Returns the font size attribute value from the text properties of a style node * @param {?Element} styleNode * @return {?{value: !number, unit: !string}} */ function getFontSize(styleNode) { var props = domUtils.getDirectChild(styleNode, stylens, 'text-properties'); if (props) { return utils.parseFoFontSize(props.getAttributeNS(fons, 'font-size')); } return null; } /** * Parse the style:text-position property * http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1420212_253892949 * * Examples: * "sub" * "super 20%" * "10% 20%" * "-15% 50%" * * @param {!string} position * @return {!{verticalTextPosition: !string, fontHeight: (!string|undefined)}} */ function parseTextPosition(position) { var parts = styleParseUtils.parseAttributeList(position); return { verticalTextPosition: parts[0], fontHeight: parts[1] }; } /** * @param {!Element} props * @return {!string} */ function getTextProperties(props) { var rule = '', fontName, fontSize, value, textDecorationLine = '', textDecorationStyle = '', textPosition, fontSizeRule = '', sizeMultiplier = 1, textFamilyStyleNode; rule += applySimpleMapping(props, textPropertySimpleMapping); value = props.getAttributeNS(stylens, 'text-underline-style'); if (value === 'solid') { textDecorationLine += ' underline'; } value = props.getAttributeNS(stylens, 'text-line-through-style'); if (value === 'solid') { textDecorationLine += ' line-through'; } if (textDecorationLine.length) { // CSS2 rule += 'text-decoration:' + textDecorationLine + ';\n'; // CSS3 text-decoration shorthand rule += 'text-decoration-line:' + textDecorationLine + ';\n'; // CSS3 text-decoration shorthand - FF rule += '-moz-text-decoration-line:' + textDecorationLine + ';\n'; } value = props.getAttributeNS(stylens, 'text-line-through-type'); switch (value) { case 'double': textDecorationStyle += ' double'; break; case 'single': textDecorationStyle += ' single'; break; } if (textDecorationStyle) { // CSS3 rule += 'text-decoration-style:' + textDecorationStyle + ';\n'; // CSS3 text-decoration shorthand - FF rule += '-moz-text-decoration-style:' + textDecorationStyle + ';\n'; } fontName = props.getAttributeNS(stylens, 'font-name') || props.getAttributeNS(fons, 'font-family'); if (fontName) { value = fontFaceDeclsMap[fontName]; // TODO: use other information from style:font-face, like style:font-family-generic rule += 'font-family: ' + (value || fontName) + ';'; } value = props.getAttributeNS(stylens, 'text-position'); if (value) { textPosition = parseTextPosition(value); rule += 'vertical-align: ' + textPosition.verticalTextPosition + '\n; '; if (textPosition.fontHeight) { sizeMultiplier = parseFloat(textPosition.fontHeight) / 100; } } if (props.hasAttributeNS(fons, "font-size") || sizeMultiplier !== 1) { // This is actually the font size of the current style. textFamilyStyleNode = /**@type{!Element}*/(props.parentNode); while (textFamilyStyleNode) { fontSize = getFontSize(textFamilyStyleNode); if (fontSize) { // If the current style's font size is a non-% value, then apply the multiplier to get the child style (with the %)'s // actual font size. And now we can stop crawling up the style ancestry since we have a concrete font size. if (fontSize.unit !== '%') { fontSizeRule = 'font-size: ' + (fontSize.value * sizeMultiplier) + fontSize.unit + ';'; break; } // If we got a % font size for the current style, then update the multiplier with it's 'normalized' multiplier sizeMultiplier *= (fontSize.value / 100); } // Crawl up the style ancestry textFamilyStyleNode = getParentStyleNode(textFamilyStyleNode); } // If there was nothing in the ancestry that specified a concrete font size, just apply the multiplier onto the page's default font size. if (!fontSizeRule) { fontSizeRule = 'font-size: ' + parseFloat(defaultFontSize) * sizeMultiplier + cssUnits.getUnits(defaultFontSize) + ';'; } } rule += fontSizeRule; return rule; } /** * @param {!Element} props <style:paragraph-properties/> * @return {!string} */ function getParagraphProperties(props) { var rule = '', bgimage, url, lineHeight; rule += applySimpleMapping(props, paragraphPropertySimpleMapping); bgimage = domUtils.getDirectChild(props, stylens, 'background-image'); if (bgimage) { url = bgimage.getAttributeNS(xlinkns, 'href'); if (url) { rule += "background-image: url('odfkit:" + url + "');"; //rule += "background-repeat: repeat;"; //FIXME test rule += applySimpleMapping(bgimage, bgImageSimpleMapping); } } lineHeight = props.getAttributeNS(fons, 'line-height'); if (lineHeight && lineHeight !== 'normal') { lineHeight = utils.parseFoLineHeight(lineHeight); if (lineHeight.unit !== '%') { rule += 'line-height: ' + lineHeight.value + lineHeight.unit + ';'; } else { rule += 'line-height: ' + lineHeight.value / 100 + ';'; } } return rule; } /*jslint unparam: true*/ /** * @param {*} m * @param {string} r * @param {string} g * @param {string} b * @return {string} */ function matchToRgb(m, r, g, b) { return r + r + g + g + b + b; } /*jslint unparam: false*/ /** * @param {!string} hex * @return {?{ r: number, g: number, b: number}} */ function hexToRgb(hex) { // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") var result, shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; hex = hex.replace(shorthandRegex, matchToRgb); result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } /** * @param {string} n * @return {boolean} */ function isNumber(n) { return !isNaN(parseFloat(n)); } /** * @param {!Element} props * @return {string} */ function getGraphicProperties(props) { var rule = '', alpha, bgcolor, fill; rule += applySimpleMapping(props, graphicPropertySimpleMapping); alpha = props.getAttributeNS(drawns, 'opacity'); fill = props.getAttributeNS(drawns, 'fill'); bgcolor = props.getAttributeNS(drawns, 'fill-color'); if (fill === 'solid' || fill === 'hatch') { if (bgcolor && bgcolor !== 'none') { alpha = isNumber(alpha) ? parseFloat(alpha) / 100 : 1; bgcolor = hexToRgb(bgcolor); if (bgcolor) { rule += "background-color: rgba(" + bgcolor.r + "," + bgcolor.g + "," + bgcolor.b + "," + alpha + ");"; } } else { rule += "background: none;"; } } else if (fill === "none") { rule += "background: none;"; } return rule; } /** * @param {!Element} props * @return {string} */ function getDrawingPageProperties(props) { var rule = ''; rule += applySimpleMapping(props, graphicPropertySimpleMapping); if (props.getAttributeNS(presentationns, 'background-visible') === 'true') { rule += "background: none;"; } return rule; } /** * @param {!Element} props * @return {string} */ function getTableCellProperties(props) { var rule = ''; rule += applySimpleMapping(props, tablecellPropertySimpleMapping); return rule; } /** * @param {!Element} props * @return {string} */ function getTableRowProperties(props) { var rule = ''; rule += applySimpleMapping(props, tablerowPropertySimpleMapping); return rule; } /** * @param {!Element} props * @return {string} */ function getTableColumnProperties(props) { var rule = ''; rule += applySimpleMapping(props, tablecolumnPropertySimpleMapping); return rule; } /** * @param {!Element} props * @return {string} */ function getTableProperties(props) { var rule = '', borderModel; rule += applySimpleMapping(props, tablePropertySimpleMapping); borderModel = props.getAttributeNS(tablens, 'border-model'); if (borderModel === 'collapsing') { rule += 'border-collapse:collapse;'; } else if (borderModel === 'separating') { rule += 'border-collapse:separate;'; } return rule; } /** * Gets a list with the names of all styles derived from the given style, * including the name of the style itself. * @param {!string} styleName * @param {!odf.StyleTreeNode} node * @return {!Array.<!string>} */ function getDerivedStyleNames(styleName, node) { var /** @type{!Array.<!string>} */ styleNames = [styleName], derivedStyles = node.derivedStyles; Object.keys(derivedStyles).forEach(function(styleName) { var dsn = getDerivedStyleNames(styleName, derivedStyles[styleName]); styleNames = styleNames.concat(dsn); }); return styleNames; } /** * Adds rules to control the display of certain frame classes in master pages * when shown in page using the master page. * @param {!CSSStyleSheet} sheet * @param {!string} styleName * @param {!Element} properties * @param {!odf.StyleTreeNode} node * @return {undefined} */ function addDrawPageFrameDisplayRules(sheet, styleName, properties, node) { var /**@const @type {!Array.<!string>}*/ frameClasses = ["page-number", "date-time", "header", "footer"], styleNames = getDerivedStyleNames(styleName, node), /**@type {!Array.<!string>}*/ visibleFrameClasses = [], /**@type {!Array.<!string>}*/ invisibleFrameClasses = []; /** * @param {!Array.<!string>} controlledFrameClasses * @param {!string} visibility * @return {undefined} */ function insertFrameVisibilityRule(controlledFrameClasses, visibility) { var selectors = [], rule; controlledFrameClasses.forEach(function(frameClass) { styleNames.forEach(function(styleName) { selectors.push('draw|page[webodfhelper|page-style-name="'+styleName+'"] draw|frame[presentation|class="'+frameClass+'"]'); }); }); if (selectors.length > 0) { rule = selectors.join(",") + "{visibility:"+visibility+";}"; sheet.insertRule(rule, sheet.cssRules.length); } } frameClasses.forEach(function(frameClass) { var displayValue; displayValue = properties.getAttributeNS(presentationns, 'display-'+frameClass); if (displayValue === 'true') { visibleFrameClasses.push(frameClass); } else if (displayValue === 'false') { invisibleFrameClasses.push(frameClass); } // else the attribute does not exist (returned as ""/null) or has a bad value }); insertFrameVisibilityRule(visibleFrameClasses, "visible"); insertFrameVisibilityRule(invisibleFrameClasses, "hidden"); } /** * @param {!CSSStyleSheet} sheet * @param {string} family * @param {string} name * @param {!odf.StyleTreeNode} node * @return {undefined} */ function addStyleRule(sheet, family, name, node) { var selectors = getSelectors(family, name, node), selector = selectors.join(','), rule = '', properties; properties = domUtils.getDirectChild(node.element, stylens, 'text-properties'); if (properties) { rule += getTextProperties(properties); } properties = domUtils.getDirectChild(node.element, stylens, 'paragraph-properties'); if (properties) { rule += getParagraphProperties(properties); } properties = domUtils.getDirectChild(node.element, stylens, 'graphic-properties'); if (properties) { rule += getGraphicProperties(properties); } properties = domUtils.getDirectChild(node.element, stylens, 'drawing-page-properties'); if (properties) { rule += getDrawingPageProperties(properties); addDrawPageFrameDisplayRules(sheet, name, /**@type{!Element}*/(properties), node); } properties = domUtils.getDirectChild(node.element, stylens, 'table-cell-properties'); if (properties) { rule += getTableCellProperties(properties); } properties = domUtils.getDirectChild(node.element, stylens, 'table-row-properties'); if (properties) { rule += getTableRowProperties(properties); } properties = domUtils.getDirectChild(node.element, stylens, 'table-column-properties'); if (properties) { rule += getTableColumnProperties(properties); } properties = domUtils.getDirectChild(node.element, stylens, 'table-properties'); if (properties) { rule += getTableProperties(properties); } if (rule.length === 0) { return; } rule = selector + '{' + rule + '}'; sheet.insertRule(rule, sheet.cssRules.length); } /** * @param {!CSSStyleSheet} sheet * @param {!Element} node <style:page-layout/>/<style:default-page-layout/> * @return {undefined} */ function addPageStyleRules(sheet, node) { var rule = '', imageProps, url, contentLayoutRule = '', pageSizeRule = '', props = domUtils.getDirectChild(node, stylens, 'page-layout-properties'), stylename, masterStyles, e, masterStyleName; if (!props) { return; } stylename = node.getAttributeNS(stylens, 'name'); rule += applySimpleMapping(props, pageContentPropertySimpleMapping); imageProps = domUtils.getDirectChild(props, stylens, 'background-image'); if (imageProps) { url = imageProps.getAttributeNS(xlinkns, 'href'); if (url) { rule += "background-image: url('odfkit:" + url + "');"; //rule += "background-repeat: repeat;"; //FIXME test rule += applySimpleMapping(imageProps, bgImageSimpleMapping); } } if (documentType === 'presentation') { masterStyles = domUtils.getDirectChild(/**@type{!Element}*/(node.parentNode.parentNode), officens, 'master-styles'); e = masterStyles && masterStyles.firstElementChild; while (e) { // Generate CSS for all the pages that use the master page that use this page-layout if (e.namespaceURI === stylens && e.localName === "master-page" && e.getAttributeNS(stylens, 'page-layout-name') === stylename) { masterStyleName = e.getAttributeNS(stylens, 'name'); contentLayoutRule = 'draw|page[draw|master-page-name="' + masterStyleName + '"] {' + rule + '}'; pageSizeRule = 'office|body, draw|page[draw|master-page-name="' + masterStyleName + '"] {' + applySimpleMapping(props, pageSizePropertySimpleMapping) + ' }'; sheet.insertRule(contentLayoutRule, sheet.cssRules.length); sheet.insertRule(pageSizeRule, sheet.cssRules.length); } e = e.nextElementSibling; } } else if (documentType === 'text') { contentLayoutRule = 'office|text {' + rule + '}'; rule = ''; // TODO: We want to use the simpleMapping for ODTs, but not until we have pagination. // So till then, set only the width. //rule += applySimpleMapping(props, pageSizePropertySimpleMapping); pageSizeRule = 'office|body {' + 'width: ' + props.getAttributeNS(fons, 'page-width') + ';' + '}'; sheet.insertRule(contentLayoutRule, sheet.cssRules.length); sheet.insertRule(pageSizeRule, sheet.cssRules.length); } } /** * @param {!CSSStyleSheet} sheet * @param {string} family * @param {string} name * @param {!odf.StyleTreeNode} node * @return {undefined} */ function addRule(sheet, family, name, node) { if (family === "page") { addPageStyleRules(sheet, node.element); } else { addStyleRule(sheet, family, name, node); } } /** * @param {!CSSStyleSheet} sheet * @param {string} family * @param {string} name * @param {!odf.StyleTreeNode} node * @return {undefined} */ function addRules(sheet, family, name, node) { addRule(sheet, family, name, node); var /**@type{string}*/ n; for (n in node.derivedStyles) { if (node.derivedStyles.hasOwnProperty(n)) { addRules(sheet, family, n, node.derivedStyles[n]); } } } // css vs odf styles // ODF styles occur in families. A family is a group of odf elements to // which an element applies. ODF families can be mapped to a group of css // elements /** * @param {!string} doctype * @param {!Element} rootNode * @param {!CSSStyleSheet} stylesheet * @param {!Object.<string,string>} fontFaceMap * @param {!Object.<string,!Object.<string,!odf.StyleTreeNode>>} styleTree * @return {undefined} */ this.style2css = function (doctype, rootNode, stylesheet, fontFaceMap, styleTree) { var tree, rule, /**@type{string}*/ name, /**@type{string}*/ family; /** * @param {!string} prefix * @param {!string} ns * @return {undefined} */ function insertCSSNamespace(prefix, ns) { rule = '@namespace ' + prefix + ' url(' + ns + ');'; try { stylesheet.insertRule(rule, stylesheet.cssRules.length); } catch (/**@type{!DOMException}*/ignore) { // WebKit can throw an exception here, but it will have // retained the namespace declarations anyway. } } odfRoot = rootNode; // make stylesheet empty while (stylesheet.cssRules.length) { stylesheet.deleteRule(stylesheet.cssRules.length - 1); } // add @odfRoot namespace rules odf.Namespaces.forEachPrefix(insertCSSNamespace); insertCSSNamespace("webodfhelper", webodfhelperns); fontFaceDeclsMap = fontFaceMap; documentType = doctype; defaultFontSize = runtime.getWindow().getComputedStyle(document.body, null).getPropertyValue('font-size') || '12pt'; // add the various styles for (family in familynamespaceprefixes) { if (familynamespaceprefixes.hasOwnProperty(family)) { tree = styleTree[family]; for (name in tree) { if (tree.hasOwnProperty(name)) { addRules(stylesheet, family, name, tree[name]); } } } } }; };