// Simple implementation of SVG sizing setup({explicit_done: true}); var SVGSizing = (function() { function parseLength(l) { var match = /^([-+]?[0-9]+|[-+]?[0-9]*\.[0-9]+)(px|%)?$/.exec(l); if (!match) return null; return new Length(Number(match[1]), match[2] ? match[2] : "px"); } function parseViewBox(input) { if (!input) return null; var arr = input.split(' '); return arr.map(function(a) { return parseInt(a); }); } // Only px and % are used function convertToPx(input, percentRef) { if (input == null) return null; var length = parseLength(input); if (length.amount == 0) return 0; if (!length.unit) length.unit = "px"; if (length.unit == "%" && percentRef === undefined) return null; return length.amount * { px: 1, "%": percentRef/100}[length.unit]; } function Length(amount, unit) { this.amount = amount; this.unit = unit; } function describe(data) { function dumpObject(obj) { var r = ""; for (var property in obj) { if (obj.hasOwnProperty(property)) { var value = obj[property]; if (typeof value == 'string') value = "'" + value + "'"; else if (value == null) value = "null"; else if (typeof value == 'object') { if (value instanceof Array) value = "[" + value + "]"; else value = "{" + dumpObject(value) + "}"; } if (value != "null") r += property + ": " + value + ", "; } } return r; } var result = dumpObject(data); if (result == "") return "(initial values)"; return result; } function mapPresentationalHintLength(testData, cssProperty, attr) { if (attr) { var l = parseLength(attr); if (l) testData.style[cssProperty] = l.amount + l.unit; } } function computedWidthIsAuto(testData) { return !testData.style["width"] || testData.style["width"] == 'auto'; } function computedHeightIsAuto(testData) { return !testData.style["height"] || testData.style["height"] == 'auto' || (parseLength(testData.style["height"]).unit == '%' && containerComputedHeightIsAuto(testData)); } function containerComputedWidthIsAuto(testData) { return !testData.config.containerWidthStyle || testData.config.containerWidthStyle == 'auto'; } function containerComputedHeightIsAuto(testData) { return !testData.config.containerHeightStyle || testData.config.containerHeightStyle == 'auto'; } function intrinsicInformation(testData) { if (testData.config.placeholder == 'iframe') return {}; var w = convertToPx(testData.config.svgWidthAttr) || 0; var h = convertToPx(testData.config.svgHeightAttr) || 0; var r = 0; if (w && h) { r = w / h; } else { var vb = parseViewBox(testData.config.svgViewBoxAttr); if (vb) { r = vb[2] / vb[3]; } if (r) { if (!w && h) w = h * r; else if (!h && w) h = w / r; } } return { width: w, height: h, ratio: r }; }; function contentAttributeForPlaceholder(testData) { if (testData.config.placeholder == 'object') return "data"; else return "src"; } function TestData(config) { this.config = config; this.name = describe(config); this.style = {}; if (config.placeholder) { mapPresentationalHintLength(this, "width", config.placeholderWidthAttr); mapPresentationalHintLength(this, "height", config.placeholderHeightAttr); } else { if (config.svgWidthStyle) this.style["width"] = config.svgWidthStyle; else mapPresentationalHintLength(this, "width", config.svgWidthAttr); if (config.svgHeightStyle) this.style["height"] = config.svgHeightStyle; else mapPresentationalHintLength(this, "height", config.svgHeightAttr); } } TestData.prototype.computeInlineReplacedSize = function(outerWidth, outerHeight) { var intrinsic = intrinsicInformation(this); var self = this; // http://www.w3.org/TR/CSS2/visudet.html#inline-replaced-height function calculateUsedHeight() { if (computedHeightIsAuto(self)) { if (computedWidthIsAuto(self) && intrinsic.height) return intrinsic.height; if (intrinsic.ratio) return calculateUsedWidth() / intrinsic.ratio; if (intrinsic.height) return intrinsic.height; return 150; } return convertToPx(self.style["height"], convertToPx(self.config.containerHeightStyle, outerHeight)); } // http://www.w3.org/TR/CSS2/visudet.html#inline-replaced-width function calculateUsedWidth() { if (computedWidthIsAuto(self)) { if (computedHeightIsAuto(self) && intrinsic.width) return intrinsic.width; if (!computedHeightIsAuto(self) && intrinsic.ratio) return calculateUsedHeight() * intrinsic.ratio; if (computedHeightIsAuto(self) && intrinsic.ratio) { if (containerComputedWidthIsAuto(self)) { // Note: While this is actually undefined in CSS // 2.1, use the suggested value by examining the // ancestor widths. return outerWidth; } else { return convertToPx(self.config.containerWidthStyle, outerWidth); } } if (intrinsic.width) return intrinsic.width; return 300; } if (containerComputedWidthIsAuto(self)) return convertToPx(self.style["width"], outerWidth); else return convertToPx(self.style["width"], convertToPx(self.config.containerWidthStyle, outerWidth)); } return { width: calculateUsedWidth(), height: calculateUsedHeight() }; }; TestData.prototype.buildContainer = function (placeholder, options) { options = options || {}; var container = document.createElement("div"); container.id = "container"; if (this.config.containerWidthStyle) container.style.width = this.config.containerWidthStyle; if (this.config.containerHeightStyle) container.style.height = this.config.containerHeightStyle; if (options.pretty) container.appendChild(document.createTextNode("\n\t\t")); container.appendChild(placeholder); if (options.pretty) container.appendChild(document.createTextNode("\n\t")); return container; }; TestData.prototype.buildSVGOrPlaceholder = function (options) { options = options || {}; var self = this; if (this.config.placeholder) { var generateSVGURI = function(testData, encoder) { var res = ''; return 'data:image/svg+xml' + encoder(res); }; var placeholder = document.createElement(this.config.placeholder); if (options.pretty) { placeholder.appendChild(document.createTextNode("\n\t\t\t")); placeholder.appendChild( document.createComment( generateSVGURI(this, function(x) { return "," + x; }))); placeholder.appendChild(document.createTextNode("\n\t\t")); } placeholder.setAttribute("id", "test"); if (this.config.placeholderWidthAttr) placeholder.setAttribute("width", this.config.placeholderWidthAttr); if (this.config.placeholderHeightAttr) placeholder.setAttribute("height", this.config.placeholderHeightAttr); placeholder.setAttribute(contentAttributeForPlaceholder(this), generateSVGURI(this, function(x) { return ";base64," + btoa(x); })); return placeholder; } else { var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svgElement.setAttribute("id", "test"); if (self.config.svgWidthStyle) svgElement.style.width = self.config.svgWidthStyle; if (self.config.svgHeightStyle) svgElement.style.height = self.config.svgHeightStyle; if (self.config.svgWidthAttr) svgElement.setAttribute("width", self.config.svgWidthAttr); if (self.config.svgHeightAttr) svgElement.setAttribute("height", self.config.svgHeightAttr); if (self.config.svgViewBoxAttr) svgElement.setAttribute("viewBox", self.config.svgViewBoxAttr); return svgElement; } }; TestData.prototype.buildDemo = function (expectedRect, id) { // Non-essential debugging tool var self = this; function buildDemoSerialization() { var outerWidth = 800; var outerHeight = 600; var options = { pretty: true }; var container = self.buildContainer(self.buildSVGOrPlaceholder(options), options); var root = document.createElement("html"); var style = document.createElement("style"); style.textContent = "\n" + "\tbody { margin: 0; font-family: sans-serif }\n" + "\tiframe { border: none }\n" + "\t#expected {\n" + "\t\twidth: " + (expectedRect.width) + "px; height: " + (expectedRect.height) + "px;\n" + "\t\tborder: 10px solid lime; position: absolute;\n" + "\t\tbackground-color: red }\n" + "\t#testContainer { position: absolute;\n" + "\t\ttop: 10px; left: 10px; width: " + outerWidth + "px;\n" + "\t\theight: " + outerHeight + "px }\n" + "\t#test { background-color: green }\n" + "\t.result { position: absolute; top: 0; right: 0;\n" + "\t\tbackground-color: hsla(0,0%, 0%, 0.85); border-radius: 0.5em;\n" + "\t\tpadding: 0.5em; border: 0.25em solid black }\n" + "\t.pass { color: lime }\n" + "\t.fail { color: red }\n"; root.appendChild(document.createTextNode("\n")); root.appendChild(style); root.appendChild(document.createTextNode("\n")); var script = document.createElement("script"); script.textContent = "\n" + "onload = function() {\n" + "\tvar svgRect =\n" + "\t\tdocument.querySelector('#test').getBoundingClientRect();\n" + "\tpassed = (svgRect.width == " + expectedRect.width + " && " + "svgRect.height == " + expectedRect.height + ");\n" + "\tdocument.body.insertAdjacentHTML('beforeEnd',\n" + "\t\t'' + (passed ? 'Pass' : 'Fail') + '');\n" + "};\n"; root.appendChild(script); root.appendChild(document.createTextNode("\n")); var expectedElement = document.createElement("div"); expectedElement.id = "expected"; root.appendChild(expectedElement); root.appendChild(document.createTextNode("\n")); var testContainer = document.createElement("div"); testContainer.id = "testContainer"; testContainer.appendChild(document.createTextNode("\n\t")); testContainer.appendChild(container); testContainer.appendChild(document.createTextNode("\n")); root.appendChild(testContainer); root.appendChild(document.createTextNode("\n")); return "\n" + root.outerHTML; } function pad(n, width, z) { z = z || '0'; n = n + ''; return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; } function heightToDescription(height) { if (!height || height == "auto") return "auto"; if (parseLength(height).unit == '%') return "percentage"; return "fixed"; } var demoRoot = document.querySelector('#demo'); if (demoRoot) { var demo = buildDemoSerialization(); var iframe = document.createElement('iframe'); iframe.style.width = (Math.max(900, expectedRect.width)) + "px"; iframe.style.height = (Math.max(400, expectedRect.height)) + "px"; iframe.src = "data:text/html;charset=utf-8," + encodeURIComponent(demo); demoRoot.appendChild(iframe); demoRoot.insertAdjacentHTML( 'beforeEnd', '
'); } }; return { TestData: TestData, doCombinationTest: function(values, func, testSingleId) { function computeConfig(id) { id--; var multiplier = 1; var config = {}; for (var i=0; i