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