1 /**
  2  * Copyright (C) 2012-2014 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 runtime, gui*/
 26 
 27 /**
 28  * @constructor
 29  * @param {!Array.<!odf.Formatting.AppliedStyle>} styles
 30  */
 31 gui.StyleSummary = function StyleSummary(styles) {
 32     "use strict";
 33     var propertyValues = {};
 34 
 35     /**
 36      * Get all values for the section + propertyName across all supplied styles. If a
 37      * one or more styles do not have a defined value for the specified propertyName, the
 38      * returned array will contain an "undefined" value to indicate the property is
 39      * missing on some of the styles.
 40      *
 41      * @param {!string} section Section (e.g., style:text-properties)
 42      * @param {!string} propertyName Property (e.g., fo:font-weight)
 43      * @return {!Array.<!string|undefined>}
 44      */
 45     function getPropertyValues(section, propertyName) {
 46         var cacheKey = section + "|" + propertyName,
 47             /**@type{Array.<!string>}*/values;
 48         if (!propertyValues.hasOwnProperty(cacheKey)) {
 49             values = [];
 50             styles.forEach(function (style) {
 51                 var styleSection = /**@type{!Object.<!string, !string>}*/(style.styleProperties[section]),
 52                     value = styleSection && styleSection[propertyName];
 53                 if (values.indexOf(value) === -1) {
 54                     values.push(value);
 55                 }
 56             });
 57             propertyValues[cacheKey] = values;
 58         }
 59         return propertyValues[cacheKey];
 60     }
 61     this.getPropertyValues = getPropertyValues;
 62 
 63     /**
 64      * Create a lazily-loaded, cached lookup function that returns true if all section + propertyName
 65      * values are contained in the supplied acceptedPropertyValues array
 66      *
 67      * @param {!string} section Section (e.g., style:text-properties)
 68      * @param {!string} propertyName Property (e.g., fo:font-weight)
 69      * @param {!Array.<!string>} acceptedPropertyValues Array of accepted values
 70      * @return {!function():!boolean} Returns true if all values are in the accepted property values
 71      */
 72     function lazilyLoaded(section, propertyName, acceptedPropertyValues) {
 73         return function () {
 74             var existingPropertyValues = getPropertyValues(section, propertyName);
 75             // As a small optimization, check accepted vs. existing lengths first.
 76             // If there are more existing values than accepted, this function should return
 77             // false as there are definitely some non-acceptable values.
 78             return acceptedPropertyValues.length >= existingPropertyValues.length
 79                 // Next, ensure each existing property value appears in the accepted properties array
 80                 && existingPropertyValues.every(function (v) { return acceptedPropertyValues.indexOf(v) !== -1; });
 81         };
 82     }
 83 
 84     /**
 85      * Return the common value for a section + propertyName if it has an identical value in all
 86      * supplied styles. If there are multiple values, or one or more styles do not have either
 87      * the section or propertyName present, this function will return undefined.
 88      *
 89      * @param {!string} section Section (e.g., style:text-properties)
 90      * @param {!string} propertyName Property (e.g., fo:font-weight)
 91      * @return {string|undefined}
 92      */
 93     function getCommonValue(section, propertyName) {
 94         var values = getPropertyValues(section, propertyName);
 95         return values.length === 1 ? values[0] : undefined;
 96     }
 97     this.getCommonValue = getCommonValue;
 98 
 99     /**
100      * Returns true if all styles specify text as bold; otherwise false.
101      * @return {!boolean}
102      */
103     this.isBold = lazilyLoaded("style:text-properties", "fo:font-weight", ["bold"]);
104 
105     /**
106      * Returns true if all styles specify text as italic; otherwise false.
107      * @return {!boolean}
108      */
109     this.isItalic = lazilyLoaded("style:text-properties", "fo:font-style", ["italic"]);
110 
111     /**
112      * Returns true if all styles specify text as underlined; otherwise false.
113      * @return {!boolean}
114      */
115     this.hasUnderline = lazilyLoaded("style:text-properties", "style:text-underline-style", ["solid"]);
116 
117     /**
118      * Returns true if all styles specify text as strike-through; otherwise false.
119      * @return {!boolean}
120      */
121     this.hasStrikeThrough = lazilyLoaded("style:text-properties", "style:text-line-through-style", ["solid"]);
122 
123     /**
124      * Returns the common font size in the supplied styles; otherwise undefined if there is no common font size
125      * @return {number|undefined}
126      */
127     this.fontSize = function () {
128         var stringFontSize = getCommonValue('style:text-properties', 'fo:font-size');
129         return /**@type{number|undefined}*/(stringFontSize && parseFloat(stringFontSize)); // TODO: support other units besides pt!
130     };
131 
132     /**
133      * Returns the common font name in the supplied styles; otherwise undefined if there is no common font name
134      * @return {string|undefined}
135      */
136     this.fontName = function () {
137         return getCommonValue('style:text-properties', 'style:font-name');
138     };
139 
140     /**
141      * Returns true if all styles are left aligned; otherwise false.
142      * @return {!boolean}
143      */
144     this.isAlignedLeft = lazilyLoaded("style:paragraph-properties", "fo:text-align", ["left", "start"]);
145 
146     /**
147      * Returns true if all styles are center aligned; otherwise false.
148      * @return {!boolean}
149      */
150     this.isAlignedCenter = lazilyLoaded("style:paragraph-properties", "fo:text-align", ["center"]);
151 
152     /**
153      * Returns true if all styles are right aligned; otherwise false.
154      * @return {!boolean}
155      */
156     this.isAlignedRight = lazilyLoaded("style:paragraph-properties", "fo:text-align", ["right", "end"]);
157 
158     /**
159      * Returns true if all styles are justified; otherwise false.
160      * @return {!boolean}
161      */
162     this.isAlignedJustified = lazilyLoaded("style:paragraph-properties", "fo:text-align", ["justify"]);
163     /**
164      * @type{!Object.<string,function():*>}
165      */
166     this.text = {
167         isBold: this.isBold,
168         isItalic: this.isItalic,
169         hasUnderline: this.hasUnderline,
170         hasStrikeThrough: this.hasStrikeThrough,
171         fontSize: this.fontSize,
172         fontName: this.fontName
173     };
174     /**
175      * @type{!Object.<string,function():*>}
176      */
177     this.paragraph = {
178         isAlignedLeft: this.isAlignedLeft,
179         isAlignedCenter: this.isAlignedCenter,
180         isAlignedRight: this.isAlignedRight,
181         isAlignedJustified: this.isAlignedJustified
182     };
183 };
184