【问题标题】:Detecting individual Unicode character support with JavaScript使用 JavaScript 检测单个 Unicode 字符支持
【发布时间】:2010-12-27 00:41:48
【问题描述】:

是否可以检测客户端是否支持特定的 Unicode 字符,或者是否将其呈现为丢失的字形框?

重要提示:支持尽可能多的浏览器

不重要:效率、速度或优雅

我能想到的唯一方法是使用画布,所以我想在我开始走这条路之前先问问。

这不适用于公共网站;我只是想编译每个浏览器支持的字符列表。

【问题讨论】:

  • 为什么这个问题是社区维基?
  • 我没有意识到标记问题社区 wiki 有一个缺点。我的错。
  • 浏览器显示的字符集更多地取决于用户安装的字体,而不是浏览器。几乎所有浏览器都支持 Unicode,而且大多数字符不需要任何特殊处理。
  • 相关:“Unicode 符号和操作系统/浏览器字体支持”stackoverflow.com/questions/51042771/…

标签: javascript unicode cross-browser


【解决方案1】:

如果您想最大限度地支持浏览器,您可能不想在任何事情上依赖 javascript。许多移动浏览器甚至不支持它。

如果浏览器不支持字符集,什么是回退?以另一种语言显示内容?也许链接一个可以根据需要切换语言的网站会更健壮。

【讨论】:

  • 我正在尝试编译每个浏览器支持的字符列表,而不是最大化对公共页面的支持。
  • All major browsers support Javascript,包括所有主要的移动浏览器。我意识到这个答案很旧,但根据我链接到的页面,即使写了这个答案也是如此。
【解决方案2】:

这更像是一个疯狂的想法,而不是一个真正的答案:

如果您能找到一个您知道总是会呈现为丢失字形框的字符,您可以使用与javascript font detector 相同的技术——将字符和丢失的字形框渲染到屏幕外并比较它们的宽度。如果它们不同,那么您就知道该字符没有呈现为丢失的字形框。当然,这对于固定宽度的字体根本不起作用,对于很多字符宽度相同的其他字体,它可能有很多固定的负数。

【讨论】:

  • 谢谢!这很有帮助。当然,它不适用于与缺少字形框的宽度和高度相同的任何字符,但这是朝着正确方向迈出的一步。
  • 这并不适用于每个字符,但如果你提高字体大小,你应该会得到很好的结果。我仍然喜欢这个答案......有点奇怪但它可以工作:-)
  • @Hippo - 这是一个很好的观点:因为字体是在屏幕外渲染的,所以你可以让它们变得非常大。
  • "如果你能找到一个你知道总是会呈现为缺失字形框的字符" U+FFFEU+FFFF 就是这样做的,they're guaranteed not to be valid unicode characters
【解决方案3】:

您始终可以使用 charCodeAt() 方法评估每个字符。这将返回 unicode 字符值。根据您的操作,您可以限制要接受为“有效”字符的范围......如果您复制“框”中的字符,您可以使用网络上的字符翻译器查看对应的unicode值是。

这是我用谷歌搜索发现的:enter link description here

【讨论】:

    【解决方案4】:

    不确定是否可以依赖它(浏览器可能会更改不支持字符的显示内容),我也不确定这是否经过优化(因为我对此处测量的理想边界没有很好的理解),但是如果经过审查,以下方法(在画布中绘制文本并将结果作为图像检查)可能会提供比检查宽度更可靠和准确的检查。开头的所有代码只是浏览器检测,我们必须使用它,因为无法进行特征检测。

    (function () {
    
    // http://www.quirksmode.org/js/detect.html
    var BrowserDetect = {
        init: function () {
            this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
            this.version = this.searchVersion(navigator.userAgent)
                || this.searchVersion(navigator.appVersion)
                || "an unknown version";
            this.OS = this.searchString(this.dataOS) || "an unknown OS";
        },
        searchString: function (data) {
            for (var i=0;i<data.length;i++) {
                var dataString = data[i].string;
                var dataProp = data[i].prop;
                this.versionSearchString = data[i].versionSearch || data[i].identity;
                if (dataString) {
                    if (dataString.indexOf(data[i].subString) != -1)
                        return data[i].identity;
                }
                else if (dataProp)
                    return data[i].identity;
            }
        },
        searchVersion: function (dataString) {
            var index = dataString.indexOf(this.versionSearchString);
            if (index == -1) return;
            return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
        },
        dataBrowser: [
            {
                string: navigator.userAgent,
                subString: "Chrome",
                identity: "Chrome"
            },
            {   string: navigator.userAgent,
                subString: "OmniWeb",
                versionSearch: "OmniWeb/",
                identity: "OmniWeb"
            },
            {
                string: navigator.vendor,
                subString: "Apple",
                identity: "Safari",
                versionSearch: "Version"
            },
            {
                prop: window.opera,
                identity: "Opera",
                versionSearch: "Version"
            },
            {
                string: navigator.vendor,
                subString: "iCab",
                identity: "iCab"
            },
            {
                string: navigator.vendor,
                subString: "KDE",
                identity: "Konqueror"
            },
            {
                string: navigator.userAgent,
                subString: "Firefox",
                identity: "Firefox"
            },
            {
                string: navigator.vendor,
                subString: "Camino",
                identity: "Camino"
            },
            {       // for newer Netscapes (6+)
                string: navigator.userAgent,
                subString: "Netscape",
                identity: "Netscape"
            },
            {
                string: navigator.userAgent,
                subString: "MSIE",
                identity: "Explorer",
                versionSearch: "MSIE"
            },
            {
                string: navigator.userAgent,
                subString: "Gecko",
                identity: "Mozilla",
                versionSearch: "rv"
            },
            {       // for older Netscapes (4-)
                string: navigator.userAgent,
                subString: "Mozilla",
                identity: "Netscape",
                versionSearch: "Mozilla"
            }
        ],
        dataOS : [
            {
                string: navigator.platform,
                subString: "Win",
                identity: "Windows"
            },
            {
                string: navigator.platform,
                subString: "Mac",
                identity: "Mac"
            },
            {
                   string: navigator.userAgent,
                   subString: "iPhone",
                   identity: "iPhone/iPod"
            },
            {
                string: navigator.platform,
                subString: "Linux",
                identity: "Linux"
            }
        ]
    
    };
    BrowserDetect.init();
    
    
    /**
    * Checks whether a given character is supported in the specified font. If the
    *   font argument is not provided, it will default to sans-serif, the default
    *   of the canvas element
    * @param {String} chr Character to check for support
    * @param {String} [font] Font Defaults to sans-serif
    * @returns {Boolean} Whether or not the character is visually distinct from characters that are not supported
    */
    function characterInFont (chr, font) {
        var data,
            size = 10, // We use 10 to confine results (could do further?) and minimum required for 10px
            x = 0, 
            y = size,
            canvas = document.createElement('canvas'),
            ctx = canvas.getContext('2d');
        // Necessary?
        canvas.width = size;
        canvas.height = size;
    
        if (font) { // Default of canvas is 10px sans-serif
            font = size + 'px ' + font; // Fix size so we can test consistently
            /**
            // Is there use to confining by this height?
            var d = document.createElement("span");
            d.font = font;
            d.textContent = chr;
            document.body.appendChild(d);
            var emHeight = d.offsetHeight;
            document.body.removeChild(d);
            alert(emHeight); // 19 after page load on Firefox and Chrome regardless of canvas height
            //*/
        }
    
        ctx.fillText(chr, x, y);
        data = ctx.getImageData(0, 0, ctx.measureText(chr).width, canvas.height).data; // canvas.width
        data = Array.prototype.slice.apply(data);
    
        function compareDataToBox (data, box, filter) {
            if (filter) { // We can stop making this conditional if we confirm the exact arrays will continue to work, or otherwise remove and rely on safer full arrays
                data = data.filter(function (item) {
                    return item != 0;
                });
            }
            return data.toString() !== box;
        }
    
        var missingCharBox;
        switch (BrowserDetect.browser) {
            case 'Firefox': // Draws nothing
                missingCharBox = '';
                break;
            case 'Opera':
                //missingCharBox = '0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,197,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,73,0,0,0,0,0,0,0,0,0,0,0,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,0,0,0,0,0,0,0,0,0,0,0,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,0,0,0,0,0,0,0,0,0,0,0,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,0,0,0,0,0,0,0,0,0,0,0,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,0,0,0,0,0,0,0,0,0,0,0,197,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,73,0,0,0,0';
                missingCharBox = '197,255,255,255,255,73,36,36,36,36,36,36,36,36,197,255,255,255,255,73';
                break;
            case 'Chrome':
                missingCharBox = '2,151,255,255,255,255,67,2,26,2,26,2,26,2,26,2,26,2,26,2,26,2,26,2,151,255,255,255,255,67';
                break;
            case 'Safari':
                missingCharBox = '17,23,23,23,23,5,52,21,21,21,21,41,39,39,39,39,39,39,39,39,63,40,40,40,40,43';
                break;
            default:
                throw 'characterInFont() not tested successfully for this browser';
        }
        return compareDataToBox(data, missingCharBox, true);
    }
    
    // EXPORTS
    ((typeof exports !== 'undefined') ? exports : this).characterInFont = characterInFont;
    
    }());
    
    var r1 = characterInFont('a', 'Arial'); // true
    var r2 = characterInFont('\uFAAA', 'Arial'); // false
    alert(r1);
    alert(r2);
    

    更新 1

    我尝试更新现代 Firefox(尝试检查画布中预期的十六进制数字),并检查以确保与我上面的代码不同,画布(以及与之匹配的模式)足够大容纳每个context.measureText() 的最宽字符(我的测试中的U+0BCC,虽然可能取决于字体,在我的情况下为“Arial Unicode MS”)。但是,根据https://bugzilla.mozilla.org/show_bug.cgi?id=442133#c9measureText 目前错误地仅响应未知字符的缩放。现在,如果只有一个人可以模拟 JavaScript 画布中的缩放以影响这些测量(并且只有那些测量)......

    代码可在https://gist.github.com/brettz9/1f061bb2ce06368db3e5参考

    【讨论】:

    • Brett 的解决方案不再适用于 Firefox,因为它现在在找不到字符时会在框中显示十六进制 Unicode 代码点。
    【解决方案5】:

    您可以使用画布检查该字符的渲染是否与您知道不支持的字符相同。 U+FFFF 是一个比较好的角色选择,因为it's guaranteed not to be a valid unicode character

    因此,您创建一个画布来渲染U+FFFF 字符,并创建另一个画布来渲染要测试的字符。然后,您可以通过使用toDataURL 方法比较它们的数据URL 来比较这两个画布。如果画布相同,则测试字符与不受支持的U+FFFF 字符相同,这意味着它不受支持,如果画布不相同,则测试字符的呈现方式与不受支持的字符不同,因此受支持.

    下面的代码就是这样做的:

    //The first argument is the character you want to test, and the second argument is the font you want to test it in.
    //If the second argument is left out, it defaults to the font of the <body> element.
    //The third argument isn't used under normal circumstances, it's just used internally to avoid infinite recursion.
    function characterIsSupported(character, font = getComputedStyle(document.body).fontFamily, recursion = false){
        //Create the canvases
        let testCanvas = document.createElement("canvas");
        let referenceCanvas = document.createElement("canvas");
        testCanvas.width = referenceCanvas.width = testCanvas.height = referenceCanvas.height = 150;
    
        //Render the characters
        let testContext = testCanvas.getContext("2d");
        let referenceContext = referenceCanvas.getContext("2d");
        testContext.font = referenceContext.font = "100px " + font;
        testContext.fillStyle = referenceContext.fillStyle = "black";
        testContext.fillText(character, 0, 100);
        referenceContext.fillText('\uffff', 0, 100);
        
        //Firefox renders unsupported characters by placing their character code inside the rectangle making each unsupported character look different.
        //As a workaround, in Firefox, we hide the inside of the character by placing a black rectangle on top of it.
        //The rectangle we use to hide the inside has an offset of 10px so it can still see part of the character, reducing the risk of false positives.
        //We check for Firefox and browers that behave similarly by checking if U+FFFE is supported, since U+FFFE is, just like U+FFFF, guaranteed not to be supported.
        if(!recursion && characterIsSupported('\ufffe', font, true)){
            testContext.fillStyle = referenceContext.fillStyle = "black";
            testContext.fillRect(10, 10, 80, 80);
            referenceContext.fillRect(10, 10, 80, 80);
        }
    
        //Check if the canvases are identical
        return testCanvas.toDataURL() != referenceCanvas.toDataURL();
    }
    
    //Examples
    console.log("a is supported: " + characterIsSupported('a'));    //Returns true, 'a' should be supported in all browsers
    console.log("\ufffe is supported: " + characterIsSupported('\ufffe'));    //Returns false, U+FFFE is guaranteed to be unsupported just like U+FFFF
    console.log("\u2b61 is supported: " + characterIsSupported('\u2b61'));    //Results vary depending on the browser. At the time of writing this, this returns true in Chrome on Windows and false in Safari on iOS.
    console.log("\uf8ff is supported: " + characterIsSupported('\uf8ff'));    //The unicode Apple logo is only supported on Apple devices, so this should return true on Apple devices and false on non-Apple devices.

    【讨论】:

      猜你喜欢
      • 2016-11-04
      • 2019-09-07
      • 2016-02-11
      • 1970-01-01
      • 1970-01-01
      • 2011-06-05
      • 2020-02-02
      • 1970-01-01
      相关资源
      最近更新 更多