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