Обнаружение индивидуальной поддержки символов в Юникоде с помощью JavaScript

Можно ли определить, поддерживает ли клиент определенный символ Unicode или будет ли он отображаться как отсутствующий символ глифа?

Важно: поддержка как можно большего количества браузеров

Не важно: эффективность, скорость или элегантность

Единственный метод, который я могу придумать, - это использовать холст, поэтому я решил, что задаю вопрос, прежде чем начинать идти по этой дороге.

Спасибо!

Изменить: это не предназначено для использования на общедоступном веб-сайте; Я просто пытаюсь скомпилировать список символов, поддерживаемых каждым браузером.

Ответы

Ответ 1

Это скорее дикая идея, чем реальный ответ:

Если бы вы могли найти персонажа, который, как вы знали, всегда отображался как отсутствующий символ глифа, вы могли бы использовать тот же метод, что и этот детектор шрифта javascript - визуализируйте символ и отсутствующий глифный блок вне экрана и сравните их ширину. Если они разные, то вы знаете, что символ не отображается как отсутствующий символ. Конечно, это не будет работать для шрифтов с фиксированной шириной, и у него может быть много фиксированных негативов для других шрифтов, где много символов одинаковой ширины.

Ответ 2

Не уверен, можно ли полагаться на продвижение (браузеры могут изменить то, что показано для неподдерживаемых символов), и я не уверен, что это оптимизировано (поскольку у меня нет хорошего понимания идеальных границ для измерения здесь), но следующий подход (рисования текста в холсте и проверки результата в виде изображения) может, если его рассмотреть, обеспечить более надежную и точную проверку, чем проверка ширины. Весь код в начале - это просто обнаружение браузера, которое мы должны использовать, поскольку обнаружение функции невозможно.

(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" ). Per https://bugzilla.mozilla.org/show_bug.cgi?id=442133#c9, однако measureText в настоящее время ошибочно реагирует на увеличение только для неизвестных символов. Теперь, если бы только один мог имитировать увеличение в холсте JavaScript, чтобы повлиять на эти измерения (и только те измерения)...

Код доступен для справки по адресу https://gist.github.com/brettz9/1f061bb2ce06368db3e5

Ответ 3

Поскольку эффективность и скорость не имеют значения, сохраните проблему и используйте образ символа вместо HTML-объекта.

Ответ 4

Вы всегда можете оценить каждый символ, используя метод charCodeAt(). Это вернет значение символа юникода. В зависимости от того, что вы делаете, вы можете ограничить диапазон, который вы хотите принять, как "Действительные" символы... Если вы скопируете символ, который находится в поле "Ящик", вы можете использовать переводчик символов в Интернете, чтобы узнать, что соответствующее значение юникода.

Здесь я нашел googled и нашел: введите ссылку здесь

Ответ 5

Если вы хотите максимизировать поддержку браузера, вы, вероятно, не хотите полагаться на javascript для чего-либо. Многие мобильные браузеры даже не поддерживают его.

Если браузер не поддерживает набор char, что такое откат? Отображение содержимого на другом языке? Возможно, ссылки на сайт, который переключит языки по запросу, будут более надежными.