| OLD | NEW |
| (Empty) | |
| 1 // Simple implementation of SVG sizing |
| 2 |
| 3 setup({explicit_done: true}); |
| 4 |
| 5 var SVGSizing = (function() { |
| 6 function parseLength(l) { |
| 7 var match = /^([-+]?[0-9]+|[-+]?[0-9]*\.[0-9]+)(px|%)?$/.exec(l); |
| 8 if (!match) |
| 9 return null; |
| 10 return new Length(Number(match[1]), match[2] ? match[2] : "px"); |
| 11 } |
| 12 |
| 13 function parseViewBox(input) { |
| 14 if (!input) |
| 15 return null; |
| 16 |
| 17 var arr = input.split(' '); |
| 18 return arr.map(function(a) { return parseInt(a); }); |
| 19 } |
| 20 |
| 21 // Only px and % are used |
| 22 function convertToPx(input, percentRef) { |
| 23 if (input == null) |
| 24 return null; |
| 25 var length = parseLength(input); |
| 26 if (length.amount == 0) |
| 27 return 0; |
| 28 if (!length.unit) |
| 29 length.unit = "px"; |
| 30 if (length.unit == "%" && percentRef === undefined) |
| 31 return null; |
| 32 return length.amount * { px: 1, |
| 33 "%": percentRef/100}[length.unit]; |
| 34 } |
| 35 |
| 36 function Length(amount, unit) { |
| 37 this.amount = amount; |
| 38 this.unit = unit; |
| 39 } |
| 40 |
| 41 function describe(data) { |
| 42 function dumpObject(obj) { |
| 43 var r = ""; |
| 44 for (var property in obj) { |
| 45 if (obj.hasOwnProperty(property)) { |
| 46 var value = obj[property]; |
| 47 if (typeof value == 'string') |
| 48 value = "'" + value + "'"; |
| 49 else if (value == null) |
| 50 value = "null"; |
| 51 else if (typeof value == 'object') |
| 52 { |
| 53 if (value instanceof Array) |
| 54 value = "[" + value + "]"; |
| 55 else |
| 56 value = "{" + dumpObject(value) + "}"; |
| 57 } |
| 58 |
| 59 if (value != "null") |
| 60 r += property + ": " + value + ", "; |
| 61 } |
| 62 } |
| 63 return r; |
| 64 } |
| 65 var result = dumpObject(data); |
| 66 if (result == "") |
| 67 return "(initial values)"; |
| 68 return result; |
| 69 } |
| 70 |
| 71 function mapPresentationalHintLength(testData, cssProperty, attr) { |
| 72 if (attr) { |
| 73 var l = parseLength(attr); |
| 74 if (l) |
| 75 testData.style[cssProperty] = l.amount + l.unit; |
| 76 } |
| 77 } |
| 78 |
| 79 function computedWidthIsAuto(testData) { |
| 80 return !testData.style["width"] || testData.style["width"] == 'auto'; |
| 81 } |
| 82 |
| 83 function computedHeightIsAuto(testData) { |
| 84 return !testData.style["height"] || testData.style["height"] == 'auto' |
| |
| 85 (parseLength(testData.style["height"]).unit == '%' && |
| 86 containerComputedHeightIsAuto(testData)); |
| 87 } |
| 88 |
| 89 function containerComputedWidthIsAuto(testData) { |
| 90 return !testData.config.containerWidthStyle || |
| 91 testData.config.containerWidthStyle == 'auto'; |
| 92 } |
| 93 |
| 94 function containerComputedHeightIsAuto(testData) { |
| 95 return !testData.config.containerHeightStyle || |
| 96 testData.config.containerHeightStyle == 'auto'; |
| 97 } |
| 98 |
| 99 function intrinsicInformation(testData) { |
| 100 if (testData.config.placeholder == 'iframe') |
| 101 return {}; |
| 102 |
| 103 var w = convertToPx(testData.config.svgWidthAttr) || 0; |
| 104 var h = convertToPx(testData.config.svgHeightAttr) || 0; |
| 105 var r = 0; |
| 106 if (w && h) { |
| 107 r = w / h; |
| 108 } else { |
| 109 var vb = parseViewBox(testData.config.svgViewBoxAttr); |
| 110 if (vb) { |
| 111 r = vb[2] / vb[3]; |
| 112 } |
| 113 if (r) { |
| 114 if (!w && h) |
| 115 w = h * r; |
| 116 else if (!h && w) |
| 117 h = w / r; |
| 118 } |
| 119 } |
| 120 return { width: w, height: h, ratio: r }; |
| 121 }; |
| 122 |
| 123 function contentAttributeForPlaceholder(testData) { |
| 124 if (testData.config.placeholder == 'object') |
| 125 return "data"; |
| 126 else |
| 127 return "src"; |
| 128 } |
| 129 |
| 130 function TestData(config) { |
| 131 this.config = config; |
| 132 this.name = describe(config); |
| 133 this.style = {}; |
| 134 if (config.placeholder) { |
| 135 mapPresentationalHintLength(this, "width", config.placeholderWidthAt
tr); |
| 136 mapPresentationalHintLength(this, "height", config.placeholderHeight
Attr); |
| 137 } else { |
| 138 if (config.svgWidthStyle) |
| 139 this.style["width"] = config.svgWidthStyle; |
| 140 else |
| 141 mapPresentationalHintLength(this, "width", config.svgWidthAttr); |
| 142 |
| 143 if (config.svgHeightStyle) |
| 144 this.style["height"] = config.svgHeightStyle; |
| 145 else |
| 146 mapPresentationalHintLength(this, "height", config.svgHeightAttr
); |
| 147 } |
| 148 } |
| 149 |
| 150 TestData.prototype.computeInlineReplacedSize = function(outerWidth, outerHei
ght) { |
| 151 var intrinsic = intrinsicInformation(this); |
| 152 var self = this; |
| 153 |
| 154 // http://www.w3.org/TR/CSS2/visudet.html#inline-replaced-height |
| 155 function calculateUsedHeight() { |
| 156 if (computedHeightIsAuto(self)) { |
| 157 if (computedWidthIsAuto(self) && intrinsic.height) |
| 158 return intrinsic.height; |
| 159 if (intrinsic.ratio) |
| 160 return calculateUsedWidth() / intrinsic.ratio; |
| 161 if (intrinsic.height) |
| 162 return intrinsic.height; |
| 163 return 150; |
| 164 } |
| 165 |
| 166 return convertToPx(self.style["height"], |
| 167 convertToPx(self.config.containerHeightStyle, |
| 168 outerHeight)); |
| 169 } |
| 170 |
| 171 // http://www.w3.org/TR/CSS2/visudet.html#inline-replaced-width |
| 172 function calculateUsedWidth() { |
| 173 if (computedWidthIsAuto(self)) { |
| 174 if (computedHeightIsAuto(self) && intrinsic.width) |
| 175 return intrinsic.width; |
| 176 if (!computedHeightIsAuto(self) && intrinsic.ratio) |
| 177 return calculateUsedHeight() * intrinsic.ratio; |
| 178 if (computedHeightIsAuto(self) && intrinsic.ratio) { |
| 179 if (containerComputedWidthIsAuto(self)) { |
| 180 // Note: While this is actually undefined in CSS |
| 181 // 2.1, use the suggested value by examining the |
| 182 // ancestor widths. |
| 183 return outerWidth; |
| 184 } else { |
| 185 return convertToPx(self.config.containerWidthStyle, |
| 186 outerWidth); |
| 187 } |
| 188 } |
| 189 if (intrinsic.width) |
| 190 return intrinsic.width; |
| 191 return 300; |
| 192 } |
| 193 |
| 194 if (containerComputedWidthIsAuto(self)) |
| 195 return convertToPx(self.style["width"], outerWidth); |
| 196 else |
| 197 return convertToPx(self.style["width"], |
| 198 convertToPx(self.config.containerWidthStyle, |
| 199 outerWidth)); |
| 200 } |
| 201 return { width: calculateUsedWidth(), |
| 202 height: calculateUsedHeight() }; |
| 203 }; |
| 204 |
| 205 TestData.prototype.buildContainer = function (placeholder, options) { |
| 206 options = options || {}; |
| 207 |
| 208 var container = document.createElement("div"); |
| 209 |
| 210 container.id = "container"; |
| 211 if (this.config.containerWidthStyle) |
| 212 container.style.width = this.config.containerWidthStyle; |
| 213 |
| 214 if (this.config.containerHeightStyle) |
| 215 container.style.height = this.config.containerHeightStyle; |
| 216 |
| 217 if (options.pretty) |
| 218 container.appendChild(document.createTextNode("\n\t\t")); |
| 219 container.appendChild(placeholder); |
| 220 if (options.pretty) |
| 221 container.appendChild(document.createTextNode("\n\t")); |
| 222 |
| 223 return container; |
| 224 }; |
| 225 |
| 226 TestData.prototype.buildSVGOrPlaceholder = function (options) { |
| 227 options = options || {}; |
| 228 var self = this; |
| 229 |
| 230 if (this.config.placeholder) { |
| 231 var generateSVGURI = function(testData, encoder) { |
| 232 var res = '<svg xmlns="http://www.w3.org/2000/svg"'; |
| 233 function addAttr(attr, prop) { |
| 234 if (testData.config[prop]) |
| 235 res += ' ' + attr + '="' + testData.config[prop] + '"'; |
| 236 } |
| 237 addAttr("width", "svgWidthAttr"); |
| 238 addAttr("height", "svgHeightAttr"); |
| 239 addAttr("viewBox", "svgViewBoxAttr"); |
| 240 res += '></svg>'; |
| 241 return 'data:image/svg+xml' + encoder(res); |
| 242 }; |
| 243 var placeholder = document.createElement(this.config.placeholder); |
| 244 if (options.pretty) { |
| 245 placeholder.appendChild(document.createTextNode("\n\t\t\t")); |
| 246 placeholder.appendChild( |
| 247 document.createComment( |
| 248 generateSVGURI(this, function(x) { return "," + x; }))); |
| 249 placeholder.appendChild(document.createTextNode("\n\t\t")); |
| 250 } |
| 251 placeholder.setAttribute("id", "test"); |
| 252 if (this.config.placeholderWidthAttr) |
| 253 placeholder.setAttribute("width", this.config.placeholderWidthAt
tr); |
| 254 if (this.config.placeholderHeightAttr) |
| 255 placeholder.setAttribute("height", this.config.placeholderHeight
Attr); |
| 256 placeholder.setAttribute(contentAttributeForPlaceholder(this), |
| 257 generateSVGURI(this, function(x) { |
| 258 return ";base64," + btoa(x); |
| 259 })); |
| 260 return placeholder; |
| 261 } else { |
| 262 var svgElement = document.createElementNS("http://www.w3.org/2000/sv
g", "svg"); |
| 263 svgElement.setAttribute("id", "test"); |
| 264 if (self.config.svgWidthStyle) |
| 265 svgElement.style.width = self.config.svgWidthStyle; |
| 266 if (self.config.svgHeightStyle) |
| 267 svgElement.style.height = self.config.svgHeightStyle; |
| 268 if (self.config.svgWidthAttr) |
| 269 svgElement.setAttribute("width", self.config.svgWidthAttr); |
| 270 if (self.config.svgHeightAttr) |
| 271 svgElement.setAttribute("height", self.config.svgHeightAttr); |
| 272 if (self.config.svgViewBoxAttr) |
| 273 svgElement.setAttribute("viewBox", self.config.svgViewBoxAttr); |
| 274 return svgElement; |
| 275 } |
| 276 }; |
| 277 |
| 278 TestData.prototype.buildDemo = function (expectedRect, id) { |
| 279 // Non-essential debugging tool |
| 280 var self = this; |
| 281 |
| 282 function buildDemoSerialization() { |
| 283 var outerWidth = 800; |
| 284 var outerHeight = 600; |
| 285 |
| 286 var options = { pretty: true }; |
| 287 var container = |
| 288 self.buildContainer(self.buildSVGOrPlaceholder(options), opt
ions); |
| 289 |
| 290 var root = document.createElement("html"); |
| 291 var style = document.createElement("style"); |
| 292 |
| 293 style.textContent = "\n" + |
| 294 "\tbody { margin: 0; font-family: sans-serif }\n" + |
| 295 "\tiframe { border: none }\n" + |
| 296 "\t#expected {\n" + |
| 297 "\t\twidth: " + (expectedRect.width) + "px; height: " |
| 298 + (expectedRect.height) + "px;\n" + |
| 299 "\t\tborder: 10px solid lime; position: absolute;\n" + |
| 300 "\t\tbackground-color: red }\n" + |
| 301 "\t#testContainer { position: absolute;\n" + |
| 302 "\t\ttop: 10px; left: 10px; width: " + outerWidth + "px;\n" + |
| 303 "\t\theight: " + outerHeight + "px }\n" + |
| 304 "\t#test { background-color: green }\n" + |
| 305 "\t.result { position: absolute; top: 0; right: 0;\n" + |
| 306 "\t\tbackground-color: hsla(0,0%, 0%, 0.85); border-radius: 0.5e
m;\n" + |
| 307 "\t\tpadding: 0.5em; border: 0.25em solid black }\n" + |
| 308 "\t.pass { color: lime }\n" + |
| 309 "\t.fail { color: red }\n"; |
| 310 |
| 311 root.appendChild(document.createTextNode("\n")); |
| 312 root.appendChild(style); |
| 313 root.appendChild(document.createTextNode("\n")); |
| 314 |
| 315 var script = document.createElement("script"); |
| 316 script.textContent = "\n" + |
| 317 "onload = function() {\n" + |
| 318 "\tvar svgRect =\n" + |
| 319 "\t\tdocument.querySelector('#test').getBoundingClientRect();\n"
+ |
| 320 "\tpassed = (svgRect.width == " + expectedRect.width + " && " + |
| 321 "svgRect.height == " + expectedRect.height + ");\n" + |
| 322 "\tdocument.body.insertAdjacentHTML('beforeEnd',\n" + |
| 323 "\t\t'<span class=\"result '+ (passed ? 'pass' : 'fail') " + |
| 324 "+ '\">' + (passed ? 'Pass' : 'Fail') + '</span>');\n" + |
| 325 "};\n"; |
| 326 |
| 327 root.appendChild(script); |
| 328 root.appendChild(document.createTextNode("\n")); |
| 329 |
| 330 var expectedElement = document.createElement("div"); |
| 331 expectedElement.id = "expected"; |
| 332 root.appendChild(expectedElement); |
| 333 root.appendChild(document.createTextNode("\n")); |
| 334 |
| 335 var testContainer = document.createElement("div"); |
| 336 testContainer.id = "testContainer"; |
| 337 testContainer.appendChild(document.createTextNode("\n\t")); |
| 338 testContainer.appendChild(container); |
| 339 testContainer.appendChild(document.createTextNode("\n")); |
| 340 root.appendChild(testContainer); |
| 341 root.appendChild(document.createTextNode("\n")); |
| 342 |
| 343 return "<!DOCTYPE html>\n" + root.outerHTML; |
| 344 } |
| 345 |
| 346 function pad(n, width, z) { |
| 347 z = z || '0'; |
| 348 n = n + ''; |
| 349 return n.length >= width ? n : new Array(width - n.length + 1).join(
z) + n; |
| 350 } |
| 351 |
| 352 function heightToDescription(height) { |
| 353 if (!height || height == "auto") |
| 354 return "auto"; |
| 355 if (parseLength(height).unit == '%') |
| 356 return "percentage"; |
| 357 return "fixed"; |
| 358 } |
| 359 |
| 360 var demoRoot = document.querySelector('#demo'); |
| 361 if (demoRoot) { |
| 362 var demo = buildDemoSerialization(); |
| 363 var iframe = document.createElement('iframe'); |
| 364 iframe.style.width = (Math.max(900, expectedRect.width)) + "px"; |
| 365 iframe.style.height = (Math.max(400, expectedRect.height)) + "px"; |
| 366 iframe.src = "data:text/html;charset=utf-8," + encodeURIComponent(de
mo); |
| 367 demoRoot.appendChild(iframe); |
| 368 demoRoot.insertAdjacentHTML( |
| 369 'beforeEnd', |
| 370 '<p><a href="data:application/octet-stream;charset=utf-8;base64,
' + |
| 371 btoa(demo) + '" download="svg-in-' + this.config.placeholder
+ "-" + |
| 372 heightToDescription(this.config.placeholderHeightAttr) + "-"
+ pad(id, 3) + |
| 373 '.html">Download</a></p>'); |
| 374 } |
| 375 }; |
| 376 |
| 377 return { |
| 378 TestData: TestData, |
| 379 doCombinationTest: function(values, func, testSingleId) { |
| 380 function computeConfig(id) { |
| 381 id--; |
| 382 var multiplier = 1; |
| 383 var config = {}; |
| 384 for (var i=0; i<values.length; i++) { |
| 385 // Compute offset into current array |
| 386 var ii = (Math.floor(id / multiplier)) % values[i][1].length
; |
| 387 // Set corresponding value |
| 388 config[values[i][0]] = values[i][1][ii]; |
| 389 // Compute new multiplier |
| 390 multiplier *= values[i][1].length; |
| 391 } |
| 392 if (id >= multiplier) |
| 393 return null; |
| 394 return config; |
| 395 } |
| 396 |
| 397 function cont(id) { |
| 398 var config = computeConfig(id); |
| 399 if (config && (!testSingleId || testSingleId == id)) { |
| 400 var next = function() {func(config, id, cont)}; |
| 401 // Make sure we don't blow the stack, without too much slown
ess |
| 402 if (id % 20 === 0) { |
| 403 setTimeout(next, 0); |
| 404 } else { |
| 405 next(); |
| 406 } |
| 407 } else { |
| 408 done(); |
| 409 } |
| 410 }; |
| 411 |
| 412 if (testSingleId) |
| 413 cont(testSingleId); |
| 414 else |
| 415 cont(1); |
| 416 } |
| 417 }; |
| 418 })(); |
| OLD | NEW |