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