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