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 Node, odf, runtime, core*/ 26 27 /** 28 * @constructor 29 */ 30 odf.Formatting = function Formatting() { 31 "use strict"; 32 var /**@type{odf.OdfContainer}*/ 33 odfContainer, 34 /**@type{odf.StyleInfo}*/ 35 styleInfo = new odf.StyleInfo(), 36 /**@const*/ 37 svgns = odf.Namespaces.svgns, 38 /**@const*/ 39 stylens = odf.Namespaces.stylens, 40 /**@const*/ 41 textns = odf.Namespaces.textns, 42 /**@const*/ 43 numberns = odf.Namespaces.numberns, 44 /**@const*/ 45 fons = odf.Namespaces.fons, 46 odfUtils = odf.OdfUtils, 47 domUtils = core.DomUtils, 48 utils = new core.Utils(), 49 cssUnits = new core.CSSUnits(), 50 // TODO: needs to be extended. Possibly created together with CSS from sone default description? 51 /**@const 52 @type {!Object.<!string,!odf.Formatting.StyleData>}*/ 53 builtInDefaultStyleAttributesByFamily = { 54 'paragraph' : { 55 'style:paragraph-properties': { 56 'fo:text-align': 'left' 57 } 58 } 59 }, 60 /**@const*/ 61 defaultPageFormatSettings = { 62 width: "21.001cm", // showing as 21.00 in page format dialog but the value is actually 21.001 in the xml 63 height: "29.7cm", 64 margin: "2cm", 65 padding: "0cm" 66 }; // LO 4.1.1.2's default page format settings. 67 68 /** 69 * Returns a JSON representation of the built-in default style attributes 70 * of a given style family. 71 * Creates a deep copy, so the result can be modified by the callee. 72 * If there are no such attributes, null is returned. 73 * @param {string} styleFamily 74 * @return {!odf.Formatting.StyleData} 75 */ 76 function getSystemDefaultStyleAttributes(styleFamily) { 77 var result, 78 /**@type{!odf.Formatting.StyleData|undefined}*/ 79 builtInDefaultStyleAttributes = builtInDefaultStyleAttributesByFamily[styleFamily]; 80 81 if (builtInDefaultStyleAttributes) { 82 // reusing mergeObjects to copy builtInDefaultStyleAttributes into the result 83 result = utils.mergeObjects({}, builtInDefaultStyleAttributes); 84 } else { 85 result = {}; 86 } 87 88 return result; 89 } 90 this.getSystemDefaultStyleAttributes = getSystemDefaultStyleAttributes; 91 92 /** 93 * @param {!odf.OdfContainer} odfcontainer 94 * @return {undefined} 95 */ 96 this.setOdfContainer = function (odfcontainer) { 97 odfContainer = odfcontainer; 98 }; 99 /** 100 * Returns a font face declarations map, where the key is the style:name and 101 * the value is the svg:font-family or null, if none set but a svg:font-face-uri 102 * @return {!Object.<string,string>} 103 */ 104 function getFontMap() { 105 var fontFaceDecls = odfContainer.rootElement.fontFaceDecls, 106 /**@type {!Object.<string,string>}*/ 107 fontFaceDeclsMap = {}, 108 node, 109 name, 110 family; 111 112 node = fontFaceDecls && fontFaceDecls.firstElementChild; 113 while (node) { 114 name = node.getAttributeNS(stylens, 'name'); 115 if (name) { 116 // add family name as value, or, if there is a 117 // font-face-uri, an empty string 118 family = node.getAttributeNS(svgns, 'font-family'); 119 if (family || node.getElementsByTagNameNS(svgns, 'font-face-uri').length > 0) { 120 fontFaceDeclsMap[name] = family; 121 } 122 } 123 node = node.nextElementSibling; 124 } 125 126 return fontFaceDeclsMap; 127 } 128 this.getFontMap = getFontMap; 129 130 /** 131 * Loop over the <style:style> elements and place the attributes 132 * style:name and style:display-name in an array. 133 * @return {!Array} 134 */ 135 this.getAvailableParagraphStyles = function () { 136 var node = odfContainer.rootElement.styles, 137 p_family, 138 p_name, 139 p_displayName, 140 paragraphStyles = []; 141 node = node && node.firstElementChild; 142 while (node) { 143 if (node.localName === "style" && node.namespaceURI === stylens) { 144 p_family = node.getAttributeNS(stylens, 'family'); 145 if (p_family === "paragraph") { 146 p_name = node.getAttributeNS(stylens, 'name'); 147 p_displayName = node.getAttributeNS(stylens, 'display-name') || p_name; 148 if (p_name && p_displayName) { 149 paragraphStyles.push({ 150 name: p_name, 151 displayName: p_displayName 152 }); 153 } 154 } 155 } 156 node = node.nextElementSibling; 157 } 158 return paragraphStyles; 159 }; 160 161 /** 162 * Returns if the given style is used anywhere in the document. 163 * @param {!Element} styleElement 164 * @return {!boolean} 165 */ 166 this.isStyleUsed = function (styleElement) { 167 var hasDerivedStyles, isUsed, 168 root = odfContainer.rootElement; 169 170 hasDerivedStyles = styleInfo.hasDerivedStyles(root, 171 odf.Namespaces.lookupNamespaceURI, styleElement); 172 173 isUsed = 174 new styleInfo.UsedStyleList(root.styles).uses(styleElement) 175 || new styleInfo.UsedStyleList(root.automaticStyles).uses(styleElement) 176 || new styleInfo.UsedStyleList(root.body).uses(styleElement); 177 178 return hasDerivedStyles || isUsed; 179 }; 180 181 /** 182 * @param {!string} family 183 * @return {?Element} 184 */ 185 function getDefaultStyleElement(family) { 186 var node = odfContainer.rootElement.styles.firstElementChild; 187 188 while (node) { 189 if (node.namespaceURI === stylens 190 && node.localName === "default-style" 191 && node.getAttributeNS(stylens, 'family') === family) { 192 return node; 193 } 194 node = node.nextElementSibling; 195 } 196 return null; 197 } 198 this.getDefaultStyleElement = getDefaultStyleElement; 199 200 /** 201 * Fetch style element associated with the requested name and family 202 * @param {!string} styleName 203 * @param {!string} family 204 * @param {!Array.<!Element>=} styleElements Specific style trees to search. If unspecified will search both automatic 205 * and user-created styles 206 * @return {?Element} 207 */ 208 function getStyleElement(styleName, family, styleElements) { 209 var node, 210 nodeStyleName, 211 styleListElement, 212 i; 213 214 styleElements = styleElements || [odfContainer.rootElement.automaticStyles, odfContainer.rootElement.styles]; 215 for (i = 0; i < styleElements.length; i += 1) { 216 styleListElement = /**@type{!Element}*/(styleElements[i]); 217 node = styleListElement.firstElementChild; 218 while (node) { 219 nodeStyleName = node.getAttributeNS(stylens, 'name'); 220 if (node.namespaceURI === stylens 221 && node.localName === "style" 222 && node.getAttributeNS(stylens, 'family') === family 223 && nodeStyleName === styleName) { 224 return node; 225 } 226 if (family === "list-style" 227 && node.namespaceURI === textns 228 && node.localName === "list-style" 229 && nodeStyleName === styleName) { 230 return node; 231 } 232 if (family === "data" 233 && node.namespaceURI === numberns 234 && nodeStyleName === styleName) { 235 return node; 236 } 237 node = node.nextElementSibling; 238 } 239 } 240 return null; 241 } 242 this.getStyleElement = getStyleElement; 243 244 /** 245 * Returns a JSON representation of the style attributes of a given style element 246 * @param {!Element} styleNode 247 * @return {!odf.Formatting.StyleData} 248 */ 249 function getStyleAttributes(styleNode) { 250 var i, a, map, ai, 251 propertiesMap = {}, 252 propertiesNode = styleNode.firstElementChild; 253 254 while (propertiesNode) { 255 if (propertiesNode.namespaceURI === stylens) { 256 map = propertiesMap[propertiesNode.nodeName] = {}; 257 a = propertiesNode.attributes; 258 for (i = 0; i < a.length; i += 1) { 259 ai = /**@type{!Attr}*/(a.item(i)); 260 map[ai.name] = ai.value; 261 } 262 } 263 propertiesNode = propertiesNode.nextElementSibling; 264 } 265 a = styleNode.attributes; 266 for (i = 0; i < a.length; i += 1) { 267 ai = /**@type{!Attr}*/(a.item(i)); 268 propertiesMap[ai.name] = ai.value; 269 } 270 271 return propertiesMap; 272 } 273 this.getStyleAttributes = getStyleAttributes; 274 275 /** 276 * Returns a JSON representation of the style attributes of a given style element, also containing attributes 277 * inherited from it's ancestry - up to and including the document's default style for the family. 278 * @param {!Element} styleNode 279 * @param {!boolean=} includeSystemDefault True by default. Specify false to suppress inclusion of system defaults 280 * @return {!odf.Formatting.StyleData} 281 */ 282 function getInheritedStyleAttributes(styleNode, includeSystemDefault) { 283 var styleListElement = odfContainer.rootElement.styles, 284 parentStyleName, 285 propertiesMap, 286 inheritedPropertiesMap = {}, 287 styleFamily = styleNode.getAttributeNS(stylens, 'family'), 288 node = styleNode; 289 290 // Iterate through the style ancestry 291 while (node) { 292 propertiesMap = getStyleAttributes(node); 293 // All child properties should override any matching parent properties 294 inheritedPropertiesMap = utils.mergeObjects(propertiesMap, inheritedPropertiesMap); 295 296 parentStyleName = node.getAttributeNS(stylens, 'parent-style-name'); 297 if (parentStyleName) { 298 node = getStyleElement(parentStyleName, styleFamily, [styleListElement]); 299 } else { 300 node = null; 301 } 302 } 303 304 // Next incorporate attributes from the default style 305 node = getDefaultStyleElement(styleFamily); 306 if (node) { 307 propertiesMap = getStyleAttributes(node); 308 // All child properties should override any matching parent properties 309 inheritedPropertiesMap = utils.mergeObjects(propertiesMap, inheritedPropertiesMap); 310 } 311 312 // Last incorporate attributes from the built-in default style 313 if (includeSystemDefault !== false) { 314 propertiesMap = getSystemDefaultStyleAttributes(styleFamily); 315 // All child properties should override any matching parent properties 316 inheritedPropertiesMap = utils.mergeObjects(propertiesMap, inheritedPropertiesMap); 317 } 318 319 return inheritedPropertiesMap; 320 } 321 this.getInheritedStyleAttributes = getInheritedStyleAttributes; 322 323 /** 324 * Get the name of the first common style in the parent style chain. 325 * If none is found, null is returned and you should assume the Default style. 326 * @param {!string} styleName 327 * @return {?string} 328 */ 329 this.getFirstCommonParentStyleNameOrSelf = function (styleName) { 330 var automaticStyleElementList = odfContainer.rootElement.automaticStyles, 331 styleElementList = odfContainer.rootElement.styles, 332 styleElement; 333 334 // first look for automatic style with the name and get its parent style 335 styleElement = getStyleElement(styleName, "paragraph", [automaticStyleElementList]); 336 if (styleElement) { 337 styleName = styleElement.getAttributeNS(stylens, 'parent-style-name'); 338 if (!styleName) { 339 return null; 340 } 341 } 342 // then see if that style is in common styles 343 styleElement = getStyleElement(styleName, "paragraph", [styleElementList]); 344 if (!styleElement) { 345 return null; 346 } 347 return styleName; 348 }; 349 350 /** 351 * Returns if there is an automatic or common paragraph style with the given name. 352 * @param {!string} styleName 353 * @return {!boolean} 354 */ 355 this.hasParagraphStyle = function (styleName) { 356 return Boolean(getStyleElement(styleName, "paragraph")); 357 }; 358 359 /** 360 * Builds up a style chain for a given node by climbing up all parent nodes and checking for style information 361 * @param {!Node} node 362 * @param {!Object.<!string, !Array.<!Object>>=} collectedChains Dictionary to add any new style chains to 363 * @return {!Array.<!Object>|undefined} 364 */ 365 function buildStyleChain(node, collectedChains) { 366 var parent = /**@type{!Element}*/(node.nodeType === Node.TEXT_NODE 367 ? node.parentNode : node), 368 nodeStyles, 369 appliedStyles = [], 370 /**@type{string}*/ 371 chainKey = '', 372 foundContainer = false; 373 374 while (parent && (!odfUtils.isInlineRoot(parent)) && (parent.parentNode !== odfContainer.rootElement)) { 375 if (!foundContainer && odfUtils.isGroupingElement(parent)) { 376 foundContainer = true; 377 } 378 nodeStyles = styleInfo.determineStylesForNode(parent); 379 if (nodeStyles) { 380 appliedStyles.push(nodeStyles); 381 } 382 parent = /**@type{!Element}*/(parent.parentNode); 383 } 384 385 /** 386 * @param {!Array.<!Object.<string,!Object.<string,number>>>} usedStyleMap 387 */ 388 function chainStyles(usedStyleMap) { 389 Object.keys(usedStyleMap).forEach(function (styleFamily) { 390 Object.keys(usedStyleMap[styleFamily]).forEach(function (styleName) { 391 chainKey += '|' + styleFamily + ':' + styleName + '|'; 392 }); 393 }); 394 } 395 if (foundContainer) { 396 appliedStyles.forEach(chainStyles); 397 if (collectedChains) { 398 collectedChains[chainKey] = appliedStyles; 399 } 400 } 401 402 return foundContainer ? appliedStyles : undefined; 403 } 404 405 /** 406 * Returns true if the supplied style node is a common style. 407 * From the OpenDocument spec: 408 * 409 * "Common and automatic styles behave differently in OpenDocument editing consumers. Common 410 * styles are presented to the user as a named set of formatting properties. The formatting 411 * properties of an automatic style are presented to a user as properties of the object to 412 * which the style is applied." 413 * 414 * http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#element-office_automatic-styles 415 * 416 * @param {!Node} styleNode 417 * @return {!boolean} 418 */ 419 function isCommonStyleElement(styleNode) { 420 return styleNode.parentNode === odfContainer.rootElement.styles; 421 } 422 423 /** 424 * Takes a provided style chain and calculates the resulting inherited style, starting from the outer-most to the 425 * inner-most style 426 * @param {!Array.<!Object>} styleChain Ordered list starting from inner-most style to outer-most style 427 * @return {!odf.Formatting.AppliedStyle} 428 */ 429 function calculateAppliedStyle(styleChain) { 430 var mergedChildStyle = { orderedStyles: [], styleProperties: {} }; 431 432 // The complete style is built up by starting at the base known style and merging each new entry 433 // on top of it, so the inner-most style properties override the outer-most 434 styleChain.forEach(function (elementStyleSet) { 435 Object.keys(/**@type{!Object}*/(elementStyleSet)).forEach(function (styleFamily) { 436 // Expect there to only be a single style for a given family per element (e.g., 1 text, 1 paragraph) 437 var styleName = Object.keys(elementStyleSet[styleFamily])[0], 438 styleSummary = { 439 name: styleName, 440 family: styleFamily, 441 displayName: undefined, 442 isCommonStyle: false 443 }, 444 styleElement, 445 parentStyle; 446 447 styleElement = getStyleElement(styleName, styleFamily); 448 if (styleElement) { 449 parentStyle = getInheritedStyleAttributes(/**@type{!Element}*/(styleElement)); 450 mergedChildStyle.styleProperties = utils.mergeObjects(parentStyle, mergedChildStyle.styleProperties); 451 styleSummary.displayName = styleElement.getAttributeNS(stylens, 'display-name') || undefined; 452 styleSummary.isCommonStyle = isCommonStyleElement(styleElement); 453 } else { 454 runtime.log("No style element found for '" + styleName + "' of family '" + styleFamily + "'"); 455 } 456 mergedChildStyle.orderedStyles.push(styleSummary); 457 }); 458 }); 459 return mergedChildStyle; 460 } 461 462 /** 463 * Returns an array of all unique styles in the given text nodes 464 * @param {!Array.<!Node>} nodes 465 * @param {!Object.<!string, !odf.Formatting.AppliedStyle>=} calculatedStylesCache Short-lived cache of calculated styles. 466 * Useful if a function is looking up the style information for multiple nodes without updating 467 * any style definitions. 468 * @return {!Array.<!odf.Formatting.AppliedStyle>} 469 */ 470 function getAppliedStyles(nodes, calculatedStylesCache) { 471 var styleChains = {}, 472 styles = []; 473 474 if (!calculatedStylesCache) { 475 calculatedStylesCache = {}; // Makes the following logic easier as a cache can be assumed to exist 476 } 477 nodes.forEach(function (n) { 478 buildStyleChain(n, styleChains); 479 }); 480 481 Object.keys(styleChains).forEach(function (key) { 482 if (!calculatedStylesCache[key]) { 483 calculatedStylesCache[key] = calculateAppliedStyle(styleChains[key]); 484 } 485 styles.push(calculatedStylesCache[key]); 486 }); 487 return styles; 488 } 489 this.getAppliedStyles = getAppliedStyles; 490 491 /** 492 * Returns a the applied style to the current node 493 * @param {!Node} node 494 * @param {!Object.<!string, !odf.Formatting.AppliedStyle>=} calculatedStylesCache Short-lived cache of calculated styles. 495 * Useful if a function is looking up the style information for multiple nodes without updating 496 * any style definitions. 497 * @return {!odf.Formatting.AppliedStyle|undefined} 498 */ 499 this.getAppliedStylesForElement = function (node, calculatedStylesCache) { 500 return getAppliedStyles([node], calculatedStylesCache)[0]; 501 }; 502 503 /** 504 * Overrides the specific properties on the styleNode from the values in the supplied properties Object. 505 * If a newStylePrefix is supplied, this method will automatically generate a unique name for the style node 506 * @param {!Element} styleNode 507 * @param {!odf.Formatting.StyleData} properties Prefix to put in front of new auto styles 508 */ 509 this.updateStyle = function (styleNode, properties) { 510 var fontName, fontFaceNode, textProperties; 511 512 domUtils.mapObjOntoNode(styleNode, properties, odf.Namespaces.lookupNamespaceURI); 513 514 textProperties = /**@type {!odf.Formatting.StyleData|undefined}*/(properties["style:text-properties"]); 515 fontName = /**@type {!string}*/(textProperties && textProperties["style:font-name"]); 516 if (fontName && !getFontMap().hasOwnProperty(fontName)) { 517 fontFaceNode = styleNode.ownerDocument.createElementNS(stylens, 'style:font-face'); 518 fontFaceNode.setAttributeNS(stylens, 'style:name', fontName); 519 fontFaceNode.setAttributeNS(svgns, 'svg:font-family', fontName); 520 odfContainer.rootElement.fontFaceDecls.appendChild(fontFaceNode); 521 } 522 }; 523 524 /** 525 * Create a style object (JSON-equivalent) that is equivalent to inheriting from the parent 526 * style and family, and applying the specified overrides. 527 * This contains logic for simulating inheritance for automatic styles 528 * @param {!string} parentStyleName 529 * @param {!string} family 530 * @param {!odf.Formatting.StyleData} overrides 531 * @return {!odf.Formatting.StyleData} 532 */ 533 this.createDerivedStyleObject = function(parentStyleName, family, overrides) { 534 var originalStyleElement = /**@type{!Element}*/(getStyleElement(parentStyleName, family)), 535 newStyleObject; 536 runtime.assert(Boolean(originalStyleElement), "No style element found for '" + parentStyleName + "' of family '" + family + "'"); 537 if (isCommonStyleElement(originalStyleElement)) { 538 newStyleObject = { "style:parent-style-name": parentStyleName }; 539 } else { 540 // Automatic styles cannot be inherited from. The way to create a derived style is to clone it entirely 541 newStyleObject = getStyleAttributes(originalStyleElement); 542 } 543 newStyleObject["style:family"] = family; 544 utils.mergeObjects(newStyleObject, overrides); 545 return newStyleObject; 546 }; 547 548 /** 549 * Get the default tab-stop distance defined for this document 550 * See http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#property-style_tab-stop-distance 551 * @return {?{value: !number, unit: !string}} 552 */ 553 this.getDefaultTabStopDistance = function () { 554 var defaultParagraph = getDefaultStyleElement('paragraph'), 555 paragraphProperties = defaultParagraph && defaultParagraph.firstElementChild, 556 tabStopDistance; 557 while (paragraphProperties) { 558 if (paragraphProperties.namespaceURI === stylens && paragraphProperties.localName === "paragraph-properties") { 559 tabStopDistance = paragraphProperties.getAttributeNS(stylens, "tab-stop-distance"); 560 } 561 paragraphProperties = paragraphProperties.nextElementSibling; 562 } 563 564 if (!tabStopDistance) { 565 tabStopDistance = "1.25cm"; // What is the default value for tab stops? Pulled this from LO 4.1.1 566 } 567 return odfUtils.parseNonNegativeLength(tabStopDistance); 568 }; 569 570 /** 571 * Find a master page definition with the specified name 572 * @param {!string} pageName 573 * @return {?Element} 574 */ 575 function getMasterPageElement(pageName) { 576 var node = odfContainer.rootElement.masterStyles.firstElementChild; 577 while (node) { 578 if (node.namespaceURI === stylens 579 && node.localName === "master-page" 580 && node.getAttributeNS(stylens, "name") === pageName) { 581 break; 582 } 583 node = node.nextElementSibling; 584 } 585 return node; 586 } 587 this.getMasterPageElement = getMasterPageElement; 588 589 /** 590 * Gets the associated page layout style node for the given style and family. 591 * @param {!string} styleName 592 * @param {!string} styleFamily either 'paragraph' or 'table' 593 * @return {?Element} 594 */ 595 function getPageLayoutStyleElement(styleName, styleFamily) { 596 var masterPageName, 597 layoutName, 598 pageLayoutElements, 599 /**@type{?Element}*/ 600 node, 601 i, 602 styleElement = getStyleElement(styleName, styleFamily); 603 604 runtime.assert(styleFamily === "paragraph" || styleFamily === "table", 605 "styleFamily must be either paragraph or table"); 606 607 if (styleElement) { 608 masterPageName = styleElement.getAttributeNS(stylens, "master-page-name"); 609 if (masterPageName) { 610 node = getMasterPageElement(masterPageName); 611 if (!node) { 612 runtime.log("WARN: No master page definition found for " + masterPageName); 613 } 614 } 615 // TODO If element has no master-page-name defined find the master-page-name from closest previous sibling 616 // See http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1417948_253892949 617 if (!node) { 618 // Fallback 1: LibreOffice usually puts a page layout in called "Standard" 619 node = getMasterPageElement("Standard"); 620 } 621 if (!node) { 622 // Fallback 2: Find any page style 623 node = /**@type{?Element}*/(odfContainer.rootElement.masterStyles.getElementsByTagNameNS(stylens, "master-page")[0]); 624 if (!node) { 625 // See http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#element-style_master-page 626 // "All documents shall contain at least one master page element." 627 runtime.log("WARN: Document has no master pages defined"); 628 } 629 } 630 631 if (node) { 632 // It would be surprising if we still haven't found a page by now. Still, better safe than sorry! 633 // Note, all warnings are already logged in the above conditions 634 layoutName = node.getAttributeNS(stylens, "page-layout-name"); 635 pageLayoutElements = odfContainer.rootElement.automaticStyles.getElementsByTagNameNS(stylens, "page-layout"); 636 for (i = 0; i < pageLayoutElements.length; i += 1) { 637 node = /**@type{!Element}*/(pageLayoutElements.item(i)); 638 if (node.getAttributeNS(stylens, "name") === layoutName) { 639 return node; 640 } 641 } 642 } 643 } 644 return null; 645 } 646 647 /** 648 * @param {?string|undefined} length 649 * @param {string=} defaultValue 650 * @return {!number|undefined} 651 */ 652 function lengthInPx(length, defaultValue) { 653 var measure; 654 if (length) { 655 measure = cssUnits.convertMeasure(length, "px"); 656 } 657 if (measure === undefined && defaultValue) { 658 measure = cssUnits.convertMeasure(defaultValue, "px"); 659 } 660 return measure; 661 } 662 663 /** 664 * Gets the width and height of content area in pixels. 665 * @param {string} styleName 666 * @param {string} styleFamily 667 * @return {!{width: number, height: number}} Available content size in pixels 668 */ 669 this.getContentSize = function(styleName, styleFamily) { 670 var pageLayoutElement, 671 props, 672 defaultOrientedPageWidth, 673 defaultOrientedPageHeight, 674 pageWidth, 675 pageHeight, 676 margin, 677 marginLeft, 678 marginRight, 679 marginTop, 680 marginBottom, 681 padding, 682 paddingLeft, 683 paddingRight, 684 paddingTop, 685 paddingBottom; 686 687 pageLayoutElement = getPageLayoutStyleElement(styleName, styleFamily); 688 if (!pageLayoutElement) { 689 pageLayoutElement = domUtils.getDirectChild(odfContainer.rootElement.styles, stylens, "default-page-layout"); 690 } 691 props = domUtils.getDirectChild(pageLayoutElement, stylens, "page-layout-properties"); 692 if (props) { 693 // set page's default width and height based on print orientation 694 if (props.getAttributeNS(stylens, "print-orientation") === "landscape") { 695 // swap the default width and height around in landscape 696 defaultOrientedPageWidth = defaultPageFormatSettings.height; 697 defaultOrientedPageHeight = defaultPageFormatSettings.width; 698 } else { 699 defaultOrientedPageWidth = defaultPageFormatSettings.width; 700 defaultOrientedPageHeight = defaultPageFormatSettings.height; 701 } 702 703 pageWidth = lengthInPx(props.getAttributeNS(fons, "page-width"), defaultOrientedPageWidth); 704 pageHeight = lengthInPx(props.getAttributeNS(fons, "page-height"), defaultOrientedPageHeight); 705 706 margin = lengthInPx(props.getAttributeNS(fons, "margin")); 707 if (margin === undefined) { 708 marginLeft = lengthInPx(props.getAttributeNS(fons, "margin-left"), defaultPageFormatSettings.margin); 709 marginRight = lengthInPx(props.getAttributeNS(fons, "margin-right"), defaultPageFormatSettings.margin); 710 marginTop = lengthInPx(props.getAttributeNS(fons, "margin-top"), defaultPageFormatSettings.margin); 711 marginBottom = lengthInPx(props.getAttributeNS(fons, "margin-bottom"), defaultPageFormatSettings.margin); 712 } else { 713 marginLeft = marginRight = marginTop = marginBottom = margin; 714 } 715 716 padding = lengthInPx(props.getAttributeNS(fons, "padding")); 717 if (padding === undefined) { 718 paddingLeft = lengthInPx(props.getAttributeNS(fons, "padding-left"), defaultPageFormatSettings.padding); 719 paddingRight = lengthInPx(props.getAttributeNS(fons, "padding-right"), defaultPageFormatSettings.padding); 720 paddingTop = lengthInPx(props.getAttributeNS(fons, "padding-top"), defaultPageFormatSettings.padding); 721 paddingBottom = lengthInPx(props.getAttributeNS(fons, "padding-bottom"), defaultPageFormatSettings.padding); 722 } else { 723 paddingLeft = paddingRight = paddingTop = paddingBottom = padding; 724 } 725 } else { 726 pageWidth = lengthInPx(defaultPageFormatSettings.width); 727 pageHeight = lengthInPx(defaultPageFormatSettings.height); 728 margin = lengthInPx(defaultPageFormatSettings.margin); 729 marginLeft = marginRight = marginTop = marginBottom = margin; 730 padding = lengthInPx(defaultPageFormatSettings.padding); 731 paddingLeft = paddingRight = paddingTop = paddingBottom = padding; 732 } 733 return { 734 width: pageWidth - marginLeft - marginRight - paddingLeft - paddingRight, 735 height: pageHeight - marginTop - marginBottom - paddingTop - paddingBottom 736 }; 737 }; 738 }; 739 740 /**@typedef{{ 741 name:!string, 742 family:!string, 743 displayName:(!string|undefined), 744 isCommonStyle:!boolean 745 }}*/ 746 odf.Formatting.StyleMetadata; 747 748 /**@typedef{!Object.<!string,(!string|!Object.<!string,!string>)>}*/ 749 odf.Formatting.StyleData; 750 751 /**@typedef{{ 752 orderedStyles:!Array.<!odf.Formatting.StyleMetadata>, 753 styleProperties:!odf.Formatting.StyleData 754 }}*/ 755 odf.Formatting.AppliedStyle; 756