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