tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

svg-sizing.js (16378B)


      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.placeholderWidthAttr);
    136            mapPresentationalHintLength(this, "height", config.placeholderHeightAttr);
    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, outerHeight) {
    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.placeholderWidthAttr);
    254            if (this.config.placeholderHeightAttr)
    255                placeholder.setAttribute("height", this.config.placeholderHeightAttr);
    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/svg", "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), options);
    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.5em;\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(demo);
    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 slowness
    402                    if (id % 20 === 0) {
    403                        step_timeout(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 })();