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 while (parent) { 374 if (!foundContainer && odfUtils.isGroupingElement(parent)) { 375 foundContainer = true; 376 } 377 nodeStyles = styleInfo.determineStylesForNode(parent); 378 if (nodeStyles) { 379 appliedStyles.push(nodeStyles); 380 } 381 parent = /**@type{!Element}*/(parent.parentNode); 382 } 383 384 /** 385 * @param {!Array.<!Object.<string,!Object.<string,number>>>} usedStyleMap 386 */ 387 function chainStyles(usedStyleMap) { 388 Object.keys(usedStyleMap).forEach(function (styleFamily) { 389 Object.keys(usedStyleMap[styleFamily]).forEach(function (styleName) { 390 chainKey += '|' + styleFamily + ':' + styleName + '|'; 391 }); 392 }); 393 } 394 if (foundContainer) { 395 appliedStyles.forEach(chainStyles); 396 if (collectedChains) { 397 collectedChains[chainKey] = appliedStyles; 398 } 399 } 400 401 return foundContainer ? appliedStyles : undefined; 402 } 403 404 /** 405 * Returns true if the supplied style node is a common style. 406 * From the OpenDocument spec: 407 * 408 * "Common and automatic styles behave differently in OpenDocument editing consumers. Common 409 * styles are presented to the user as a named set of formatting properties. The formatting 410 * properties of an automatic style are presented to a user as properties of the object to 411 * which the style is applied." 412 * 413 * http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#element-office_automatic-styles 414 * 415 * @param {!Node} styleNode 416 * @return {!boolean} 417 */ 418 function isCommonStyleElement(styleNode) { 419 return styleNode.parentNode === odfContainer.rootElement.styles; 420 } 421 422 /** 423 * Takes a provided style chain and calculates the resulting inherited style, starting from the outer-most to the 424 * inner-most style 425 * @param {!Array.<!Object>} styleChain Ordered list starting from inner-most style to outer-most style 426 * @return {!odf.Formatting.AppliedStyle} 427 */ 428 function calculateAppliedStyle(styleChain) { 429 var mergedChildStyle = { orderedStyles: [], styleProperties: {} }; 430 431 // The complete style is built up by starting at the base known style and merging each new entry 432 // on top of it, so the inner-most style properties override the outer-most 433 styleChain.forEach(function (elementStyleSet) { 434 Object.keys(/**@type{!Object}*/(elementStyleSet)).forEach(function (styleFamily) { 435 // Expect there to only be a single style for a given family per element (e.g., 1 text, 1 paragraph) 436 var styleName = Object.keys(elementStyleSet[styleFamily])[0], 437 styleSummary = { 438 name: styleName, 439 family: styleFamily, 440 displayName: undefined, 441 isCommonStyle: false 442 }, 443 styleElement, 444 parentStyle; 445 446 styleElement = getStyleElement(styleName, styleFamily); 447 if (styleElement) { 448 parentStyle = getInheritedStyleAttributes(/**@type{!Element}*/(styleElement)); 449 mergedChildStyle.styleProperties = utils.mergeObjects(parentStyle, mergedChildStyle.styleProperties); 450 styleSummary.displayName = styleElement.getAttributeNS(stylens, 'display-name') || undefined; 451 styleSummary.isCommonStyle = isCommonStyleElement(styleElement); 452 } else { 453 runtime.log("No style element found for '" + styleName + "' of family '" + styleFamily + "'"); 454 } 455 mergedChildStyle.orderedStyles.push(styleSummary); 456 }); 457 }); 458 return mergedChildStyle; 459 } 460 461 /** 462 * Returns an array of all unique styles in the given text nodes 463 * @param {!Array.<!Node>} nodes 464 * @param {!Object.<!string, !odf.Formatting.AppliedStyle>=} calculatedStylesCache Short-lived cache of calculated styles. 465 * Useful if a function is looking up the style information for multiple nodes without updating 466 * any style definitions. 467 * @return {!Array.<!odf.Formatting.AppliedStyle>} 468 */ 469 function getAppliedStyles(nodes, calculatedStylesCache) { 470 var styleChains = {}, 471 styles = []; 472 473 if (!calculatedStylesCache) { 474 calculatedStylesCache = {}; // Makes the following logic easier as a cache can be assumed to exist 475 } 476 nodes.forEach(function (n) { 477 buildStyleChain(n, styleChains); 478 }); 479 480 Object.keys(styleChains).forEach(function (key) { 481 if (!calculatedStylesCache[key]) { 482 calculatedStylesCache[key] = calculateAppliedStyle(styleChains[key]); 483 } 484 styles.push(calculatedStylesCache[key]); 485 }); 486 return styles; 487 } 488 this.getAppliedStyles = getAppliedStyles; 489 490 /** 491 * Returns a the applied style to the current node 492 * @param {!Node} node 493 * @param {!Object.<!string, !odf.Formatting.AppliedStyle>=} calculatedStylesCache Short-lived cache of calculated styles. 494 * Useful if a function is looking up the style information for multiple nodes without updating 495 * any style definitions. 496 * @return {!odf.Formatting.AppliedStyle|undefined} 497 */ 498 this.getAppliedStylesForElement = function (node, calculatedStylesCache) { 499 return getAppliedStyles([node], calculatedStylesCache)[0]; 500 }; 501 502 /** 503 * Overrides the specific properties on the styleNode from the values in the supplied properties Object. 504 * If a newStylePrefix is supplied, this method will automatically generate a unique name for the style node 505 * @param {!Element} styleNode 506 * @param {!odf.Formatting.StyleData} properties Prefix to put in front of new auto styles 507 */ 508 this.updateStyle = function (styleNode, properties) { 509 var fontName, fontFaceNode, textProperties; 510 511 domUtils.mapObjOntoNode(styleNode, properties, odf.Namespaces.lookupNamespaceURI); 512 513 textProperties = /**@type {!odf.Formatting.StyleData|undefined}*/(properties["style:text-properties"]); 514 fontName = /**@type {!string}*/(textProperties && textProperties["style:font-name"]); 515 if (fontName && !getFontMap().hasOwnProperty(fontName)) { 516 fontFaceNode = styleNode.ownerDocument.createElementNS(stylens, 'style:font-face'); 517 fontFaceNode.setAttributeNS(stylens, 'style:name', fontName); 518 fontFaceNode.setAttributeNS(svgns, 'svg:font-family', fontName); 519 odfContainer.rootElement.fontFaceDecls.appendChild(fontFaceNode); 520 } 521 }; 522 523 /** 524 * Create a style object (JSON-equivalent) that is equivalent to inheriting from the parent 525 * style and family, and applying the specified overrides. 526 * This contains logic for simulating inheritance for automatic styles 527 * @param {!string} parentStyleName 528 * @param {!string} family 529 * @param {!odf.Formatting.StyleData} overrides 530 * @return {!odf.Formatting.StyleData} 531 */ 532 this.createDerivedStyleObject = function(parentStyleName, family, overrides) { 533 var originalStyleElement = /**@type{!Element}*/(getStyleElement(parentStyleName, family)), 534 newStyleObject; 535 runtime.assert(Boolean(originalStyleElement), "No style element found for '" + parentStyleName + "' of family '" + family + "'"); 536 if (isCommonStyleElement(originalStyleElement)) { 537 newStyleObject = { "style:parent-style-name": parentStyleName }; 538 } else { 539 // Automatic styles cannot be inherited from. The way to create a derived style is to clone it entirely 540 newStyleObject = getStyleAttributes(originalStyleElement); 541 } 542 newStyleObject["style:family"] = family; 543 utils.mergeObjects(newStyleObject, overrides); 544 return newStyleObject; 545 }; 546 547 /** 548 * Get the default tab-stop distance defined for this document 549 * See http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#property-style_tab-stop-distance 550 * @return {?{value: !number, unit: !string}} 551 */ 552 this.getDefaultTabStopDistance = function () { 553 var defaultParagraph = getDefaultStyleElement('paragraph'), 554 paragraphProperties = defaultParagraph && defaultParagraph.firstElementChild, 555 tabStopDistance; 556 while (paragraphProperties) { 557 if (paragraphProperties.namespaceURI === stylens && paragraphProperties.localName === "paragraph-properties") { 558 tabStopDistance = paragraphProperties.getAttributeNS(stylens, "tab-stop-distance"); 559 } 560 paragraphProperties = paragraphProperties.nextElementSibling; 561 } 562 563 if (!tabStopDistance) { 564 tabStopDistance = "1.25cm"; // What is the default value for tab stops? Pulled this from LO 4.1.1 565 } 566 return odfUtils.parseNonNegativeLength(tabStopDistance); 567 }; 568 569 /** 570 * Find a master page definition with the specified name 571 * @param {!string} pageName 572 * @return {?Element} 573 */ 574 function getMasterPageElement(pageName) { 575 var node = odfContainer.rootElement.masterStyles.firstElementChild; 576 while (node) { 577 if (node.namespaceURI === stylens 578 && node.localName === "master-page" 579 && node.getAttributeNS(stylens, "name") === pageName) { 580 break; 581 } 582 node = node.nextElementSibling; 583 } 584 return node; 585 } 586 this.getMasterPageElement = getMasterPageElement; 587 588 /** 589 * Gets the associated page layout style node for the given style and family. 590 * @param {!string} styleName 591 * @param {!string} styleFamily either 'paragraph' or 'table' 592 * @return {?Element} 593 */ 594 function getPageLayoutStyleElement(styleName, styleFamily) { 595 var masterPageName, 596 layoutName, 597 pageLayoutElements, 598 /**@type{?Element}*/ 599 node, 600 i, 601 styleElement = getStyleElement(styleName, styleFamily); 602 603 runtime.assert(styleFamily === "paragraph" || styleFamily === "table", 604 "styleFamily must be either paragraph or table"); 605 606 if (styleElement) { 607 masterPageName = styleElement.getAttributeNS(stylens, "master-page-name"); 608 if (masterPageName) { 609 node = getMasterPageElement(masterPageName); 610 if (!node) { 611 runtime.log("WARN: No master page definition found for " + masterPageName); 612 } 613 } 614 // TODO If element has no master-page-name defined find the master-page-name from closest previous sibling 615 // See http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1417948_253892949 616 if (!node) { 617 // Fallback 1: LibreOffice usually puts a page layout in called "Standard" 618 node = getMasterPageElement("Standard"); 619 } 620 if (!node) { 621 // Fallback 2: Find any page style 622 node = /**@type{?Element}*/(odfContainer.rootElement.masterStyles.getElementsByTagNameNS(stylens, "master-page")[0]); 623 if (!node) { 624 // See http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#element-style_master-page 625 // "All documents shall contain at least one master page element." 626 runtime.log("WARN: Document has no master pages defined"); 627 } 628 } 629 630 if (node) { 631 // It would be surprising if we still haven't found a page by now. Still, better safe than sorry! 632 // Note, all warnings are already logged in the above conditions 633 layoutName = node.getAttributeNS(stylens, "page-layout-name"); 634 pageLayoutElements = odfContainer.rootElement.automaticStyles.getElementsByTagNameNS(stylens, "page-layout"); 635 for (i = 0; i < pageLayoutElements.length; i += 1) { 636 node = /**@type{!Element}*/(pageLayoutElements.item(i)); 637 if (node.getAttributeNS(stylens, "name") === layoutName) { 638 return node; 639 } 640 } 641 } 642 } 643 return null; 644 } 645 646 /** 647 * @param {?string|undefined} length 648 * @param {string=} defaultValue 649 * @return {!number|undefined} 650 */ 651 function lengthInPx(length, defaultValue) { 652 var measure; 653 if (length) { 654 measure = cssUnits.convertMeasure(length, "px"); 655 } 656 if (measure === undefined && defaultValue) { 657 measure = cssUnits.convertMeasure(defaultValue, "px"); 658 } 659 return measure; 660 } 661 662 /** 663 * Gets the width and height of content area in pixels. 664 * @param {string} styleName 665 * @param {string} styleFamily 666 * @return {!{width: number, height: number}} Available content size in pixels 667 */ 668 this.getContentSize = function(styleName, styleFamily) { 669 var pageLayoutElement, 670 props, 671 defaultOrientedPageWidth, 672 defaultOrientedPageHeight, 673 pageWidth, 674 pageHeight, 675 margin, 676 marginLeft, 677 marginRight, 678 marginTop, 679 marginBottom, 680 padding, 681 paddingLeft, 682 paddingRight, 683 paddingTop, 684 paddingBottom; 685 686 pageLayoutElement = getPageLayoutStyleElement(styleName, styleFamily); 687 if (!pageLayoutElement) { 688 pageLayoutElement = domUtils.getDirectChild(odfContainer.rootElement.styles, stylens, "default-page-layout"); 689 } 690 props = domUtils.getDirectChild(pageLayoutElement, stylens, "page-layout-properties"); 691 if (props) { 692 // set page's default width and height based on print orientation 693 if (props.getAttributeNS(stylens, "print-orientation") === "landscape") { 694 // swap the default width and height around in landscape 695 defaultOrientedPageWidth = defaultPageFormatSettings.height; 696 defaultOrientedPageHeight = defaultPageFormatSettings.width; 697 } else { 698 defaultOrientedPageWidth = defaultPageFormatSettings.width; 699 defaultOrientedPageHeight = defaultPageFormatSettings.height; 700 } 701 702 pageWidth = lengthInPx(props.getAttributeNS(fons, "page-width"), defaultOrientedPageWidth); 703 pageHeight = lengthInPx(props.getAttributeNS(fons, "page-height"), defaultOrientedPageHeight); 704 705 margin = lengthInPx(props.getAttributeNS(fons, "margin")); 706 if (margin === undefined) { 707 marginLeft = lengthInPx(props.getAttributeNS(fons, "margin-left"), defaultPageFormatSettings.margin); 708 marginRight = lengthInPx(props.getAttributeNS(fons, "margin-right"), defaultPageFormatSettings.margin); 709 marginTop = lengthInPx(props.getAttributeNS(fons, "margin-top"), defaultPageFormatSettings.margin); 710 marginBottom = lengthInPx(props.getAttributeNS(fons, "margin-bottom"), defaultPageFormatSettings.margin); 711 } else { 712 marginLeft = marginRight = marginTop = marginBottom = margin; 713 } 714 715 padding = lengthInPx(props.getAttributeNS(fons, "padding")); 716 if (padding === undefined) { 717 paddingLeft = lengthInPx(props.getAttributeNS(fons, "padding-left"), defaultPageFormatSettings.padding); 718 paddingRight = lengthInPx(props.getAttributeNS(fons, "padding-right"), defaultPageFormatSettings.padding); 719 paddingTop = lengthInPx(props.getAttributeNS(fons, "padding-top"), defaultPageFormatSettings.padding); 720 paddingBottom = lengthInPx(props.getAttributeNS(fons, "padding-bottom"), defaultPageFormatSettings.padding); 721 } else { 722 paddingLeft = paddingRight = paddingTop = paddingBottom = padding; 723 } 724 } else { 725 pageWidth = lengthInPx(defaultPageFormatSettings.width); 726 pageHeight = lengthInPx(defaultPageFormatSettings.height); 727 margin = lengthInPx(defaultPageFormatSettings.margin); 728 marginLeft = marginRight = marginTop = marginBottom = margin; 729 padding = lengthInPx(defaultPageFormatSettings.padding); 730 paddingLeft = paddingRight = paddingTop = paddingBottom = padding; 731 } 732 return { 733 width: pageWidth - marginLeft - marginRight - paddingLeft - paddingRight, 734 height: pageHeight - marginTop - marginBottom - paddingTop - paddingBottom 735 }; 736 }; 737 }; 738 739 /**@typedef{{ 740 name:!string, 741 family:!string, 742 displayName:(!string|undefined), 743 isCommonStyle:!boolean 744 }}*/ 745 odf.Formatting.StyleMetadata; 746 747 /**@typedef{!Object.<!string,(!string|!Object.<!string,!string>)>}*/ 748 odf.Formatting.StyleData; 749 750 /**@typedef{{ 751 orderedStyles:!Array.<!odf.Formatting.StyleMetadata>, 752 styleProperties:!odf.Formatting.StyleData 753 }}*/ 754 odf.Formatting.AppliedStyle; 755