1 /** 2 * Copyright (C) 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, odf*/ 26 27 /** 28 * Object that retrieves properties lazily and caches them. 29 * If the element does not define the property it is retrieved from a parent 30 * LazyStyleProperties object. 31 * An object with getters functions is passed into the constructor. There must 32 * be a getter function for each property. 33 * @constructor 34 * @param {!odf.LazyStyleProperties|undefined} parent 35 * @param {!Object.<!string,function():*>} getters 36 */ 37 odf.LazyStyleProperties = function (parent, getters) { 38 "use strict"; 39 var /**@type{!Object.<!string,*>}*/ 40 data = {}; 41 /** 42 * Retrieve a value by name. 43 * The getter for the value must be defined in the getters object. 44 * If the getter returns undefined and a parent object is provided, the 45 * value is gotten from the parent object. 46 * @param {!string} name 47 * @return {*} 48 */ 49 this.value = function (name) { 50 var /**@type{*}*/ 51 v; 52 if (data.hasOwnProperty(name)) { 53 v = data[name]; 54 } else { 55 v = getters[name](); 56 if (v === undefined && parent) { 57 v = parent.value(name); 58 } 59 data[name] = v; 60 } 61 return v; 62 }; 63 /** 64 * Give a new parent to the LazyStyleProperties. 65 * The cache is invalidated when this is done. 66 * @param {!odf.LazyStyleProperties|undefined} p 67 * @return {undefined} 68 */ 69 this.reset = function (p) { 70 parent = p; 71 data = {}; 72 }; 73 }; 74 /** 75 * A collection of helper functions for parsing style attributes. 76 * @constructor 77 */ 78 odf.StyleParseUtils = function () { 79 "use strict"; 80 var stylens = odf.Namespaces.stylens; 81 /** 82 * Returns the length split as value and unit, from an ODF attribute. 83 * If the length does not match the regular expression, null is returned. 84 * @param {?string|undefined} length 85 * @return {?{value:!number,unit:!string}} 86 */ 87 function splitLength(length) { 88 var re = /(-?[0-9]*[0-9][0-9]*(\.[0-9]*)?|0+\.[0-9]*[1-9][0-9]*|\.[0-9]*[1-9][0-9]*)((cm)|(mm)|(in)|(pt)|(pc)|(px))/, 89 m = re.exec(length); 90 if (!m) { 91 return null; 92 } 93 return {value: parseFloat(m[1]), unit: m[3]}; 94 } 95 /** 96 * Convert a unit in a string to number of pixels at 96 dpi. 97 * If the input value has unit 'px' or is a number, the number is taken as 98 * is. Other allowed unit: cm, mm, pt, pc. 99 * If the value cannot be parsed, the value undefined is returned. 100 * @param {?string|undefined} val 101 * @return {!number|undefined} 102 */ 103 function parseLength(val) { 104 var n, length, unit; 105 length = splitLength(val); 106 unit = length && length.unit; 107 if (unit === "px") { 108 n = length.value; 109 } else if (unit === "cm") { 110 n = length.value / 2.54 * 96; 111 } else if (unit === "mm") { 112 n = length.value / 25.4 * 96; 113 } else if (unit === "in") { 114 n = length.value * 96; 115 } else if (unit === "pt") { 116 n = length.value / 0.75; 117 } else if (unit === "pc") { 118 n = length.value * 16; 119 } 120 return n; 121 } 122 this.parseLength = parseLength; 123 /** 124 * Parse a percentage of the form -?([0-9]+(\.[0-9]*)?|\.[0-9]+)%. 125 * If parsing fails undefined is returned. 126 * @param {?string|undefined} value 127 * @return {!number|undefined} 128 */ 129 function parsePercent(value) { 130 var v; 131 if (value) { 132 v = parseFloat(value.substr(0, value.indexOf("%"))); 133 if (isNaN(v)) { 134 v = undefined; 135 } 136 } 137 return v; 138 } 139 /** 140 * Parse a value that is a positive length or a percentage. 141 * If parsing fails undefined is returned. 142 * @param {?string|undefined} value 143 * @param {!string} name 144 * @param {!odf.LazyStyleProperties|undefined} parent 145 * @return {!number|undefined} 146 */ 147 function parsePositiveLengthOrPercent(value, name, parent) { 148 var v = parsePercent(value), 149 parentValue; 150 if (v !== undefined) { 151 if (parent) { 152 parentValue = parent.value(name); 153 } 154 if (parentValue === undefined) { 155 v = undefined; 156 } else { 157 v *= /**@type{!number}*/(parentValue) / 100; 158 } 159 } else { 160 v = parseLength(value); 161 } 162 return v; 163 } 164 this.parsePositiveLengthOrPercent = parsePositiveLengthOrPercent; 165 /** 166 * Find a child element from the ODF style namespace with the given local 167 * name. 168 * The search is started after the given previousPropertyElement or, if 169 * previousPropertyElement is not given, from the first child element. 170 * @param {!string} name 171 * @param {!Element} styleElement 172 * @param {?Element=} previousPropertyElement 173 * @return {?Element} 174 */ 175 function getPropertiesElement(name, styleElement, previousPropertyElement) { 176 var e = previousPropertyElement 177 ? previousPropertyElement.nextElementSibling 178 : styleElement.firstElementChild; 179 while (e !== null && (e.localName !== name || e.namespaceURI !== stylens)) { 180 e = e.nextElementSibling; 181 } 182 return e; 183 } 184 this.getPropertiesElement = getPropertiesElement; 185 186 187 /** 188 * Split a space-separated attribute list into it's list items. Ignores leading & trailing 189 * whitespace, and collapses excessive internal whitespace. If the input text is null, undefined 190 * or pure whitespace, an empty array will be returned. 191 * 192 * @param {?string|undefined} text 193 * @return {!Array.<!string>} 194 */ 195 /*jslint regexp: true*/ 196 function parseAttributeList(text) { 197 if (text) { 198 text = text.replace(/^\s*(.*?)\s*$/g, "$1"); // Trim leading + trailing whitespace 199 } 200 // Calling split on an empty string returns a [""]. Avoid this by only attempting to split if the 201 // string is non-zero-length 202 return text && text.length > 0 ? text.split(/\s+/) : []; 203 } 204 /*jslint regexp: false*/ 205 this.parseAttributeList = parseAttributeList; 206 }; 207