Skip to content

Instantly share code, notes, and snippets.

@sounisi5011
Last active August 9, 2021 07:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sounisi5011/069402006a1294e22a44 to your computer and use it in GitHub Desktop.
Save sounisi5011/069402006a1294e22a44 to your computer and use it in GitHub Desktop.
文字の表示に対応しているか判定する
html {
font-family: 'Hiragino Kaku Gothic ProN', '\30d2\30e9\30ae\30ce\89d2\30b4 ProN W3', \6e38\30b4\30b7\30c3\30af\4f53, 'Yu Gothic', YuGothic, \30e1\30a4\30ea\30aa, Meiryo, sans-serif;
}
function appendChildlen(targetNode, ChildNodeList){
var l = ChildNodeList.length;
for (var i = 0; i < l; i++) {
targetNode.appendChild(ChildNodeList[i]);
}
}
function str2textNode(str){
return document.createTextNode(str);
}
function textNode2liElem(textNode){
var liElem = document.createElement('li');
liElem.appendChild(textNode);
return liElem;
}
var charList = [
'\u00B6', // U+00B6 - PILCROW SIGN
'\u25A1', // U+25A1 - WHITE SQUARE
'\u25BA', // U+25BA - BLACK RIGHT-POINTING POINTER
'\uA4E8', // U+A4E8 - LISU LETTER HHA
'\uFE45', // U+FE45 - SESAME DOT
'\uD83D\uDCA9', // U+1F4A9 - PILE OF POO
'\uD83D\uDD49', // U+1F549 - OM SYMBOL
'\uD83D\uDEE1' // U+1F6E1 - SHIELD
];
window.addEventListener('load', function(){
var body = document.body;
var ulElem;
var pElem;
var supportChar = new SupportCharacter(body);
if (supportChar.enabled) {
ulElem = document.createElement('ul');
appendChildlen(
ulElem,
charList
.map(function(char){
return (
(supportChar.check(char, body) ? '表示可能' : '表示不可能') +
': ' + char
);
})
.map(str2textNode)
.map(textNode2liElem)
);
body.appendChild(ulElem);
} else {
pElem = document.createElement('p');
pElem.appendChild(document.createTextNode('判定非対応'));
body.appendChild(pElem);
}
}, false);
<!DOCTYPE html>
<html lang="ja">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="format-detection" content="telephone=no">
<title>文字が表示できるか判定</title>
<link rel="dns-prefetch" href="//cdnjs.cloudflare.com/">
<link href="app.css" rel="stylesheet" type="text/css">
<h1>文字が表示できるか判定</h1>
<script src="//cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.4/es5-shim.min.js"></script>
<script src="SupportCharacter.js"></script>
<script src="app.js"></script>
/**
* 文字の表示に対応しているか判定します。
* 判定を行うには、canvas要素とcanvasへのテキストの描画に対応している必要があります。
*
* @param {Element} targetElem 文字の対応判定を行う基準となる要素。
* この要素の算出スタイル、及び所属するdocumentオブジェクトを元に、判定を行います。
* @constructor
*/
function SupportCharacter(targetElem){
/**
* 基準となる要素のdocumentオブジェクト。
* 判定用のcanvas要素を生成する際に利用されます。
* @type {Document}
*/
var document = targetElem.ownerDocument || document;
/**
* 文字が表示されるか比較する基準となるcanvas要素。
* 「表示できない文字」が描画される。
* @type {Element}
*/
var sampleElem;
/**
* 検証する文字を描画するcanvas要素。
* @type {Element}
*/
var testElem;
/**
* 変数sampleElemのcanvasから取得したコンテキスト。
* @type {CanvasRenderingContext2D}
*/
var sampleCtx;
/**
* 変数testElemのcanvasから取得したコンテキスト。
* @type {CanvasRenderingContext2D}
*/
var testCtx;
/**
* canvasの大きさ。canvasの縦・横の大きさとして利用されます。
* @type {number}
*/
var canvasSize = (0 < this.CANVAS_SIZE) ? this.CANVAS_SIZE : 100;
/**
* canvasのフォント指定用文字列。
* @type {string}
*/
var fontStyle;
/**
* toDataURLメソッドの出力結果を一時的に保持する変数。
* @type {string|null}
*/
var oldDataURL = null;
/**
* getImageDataメソッドの出力結果を一時的に保持する変数。
* @type {ImageData|null}
*/
var oldImageData = null;
/**
* 判定可能フラグをfalseに設定しておく。
*/
this.enabled = false;
/**
* 基準となるcanvas要素を生成する。
*/
sampleElem = document.createElement('canvas');
sampleElem.width = canvasSize;
sampleElem.height = canvasSize;
/**
* canvasに対応していない場合、処理を終了する。
*/
if (!sampleElem.getContext) {
return;
}
/**
* 基準となるcanvas要素のコンテキストを生成。
*/
sampleCtx = sampleElem.getContext('2d');
/**
* コンテキストの生成に失敗した場合や、
* clearRectメソッド及びfillTextメソッドに対応していない場合、
* 処理を終了する。
*/
if (
!sampleCtx ||
!this.isFunction(sampleCtx.clearRect) ||
!this.isFunction(sampleCtx.fillText)
) {
return;
}
/**
* toDataURLメソッドが利用できる場合、
* toDataURLメソッドの結果を保持する。
*/
if (this.isFunction(sampleElem.toDataURL)) {
oldDataURL = sampleElem.toDataURL();
/**
* 結果が"data:,"(canvasのサイズが0の時の出力結果)の場合、
* toDataURLメソッドが信頼できないため結果を抹消。
*/
if (oldDataURL === 'data:,') {
oldDataURL = null;
}
}
/**
* getImageDataメソッドが利用できる場合、
* getImageDataメソッドで生成したImageDataオブジェクトを保持する。
*/
if (this.isFunction(sampleCtx.getImageData)) {
oldImageData = this.getImageData(sampleCtx);
}
/**
* 文字を描画し、canvasの描画内容を変更する。
*/
fontStyle = this.getFontValue(targetElem);
this.drowText(sampleCtx, 'A', fontStyle);
if (
!!oldDataURL &&
sampleElem.toDataURL() !== oldDataURL
) {
/**
* toDataURLメソッドが判定に利用できる(描画内容の異なるcanvasを判定できた)場合、
* toDataURLメソッドによる判定を利用する。
*/
this.isMatchCanvas = this.isMatchDataURL;
} else if (
!!oldImageData && !!oldImageData.data &&
!this.isMatchUint8ClampedArray(
this.getImageData(sampleCtx).data,
oldImageData.data
)
){
/**
* ImageDataオブジェクトが判定に利用できる(描画内容の異なるcanvasを判定できた)場合、
* ImageDataオブジェクトによる判定を利用する。
*/
this.isMatchCanvas = this.isMatchImageData;
} else {
/**
* それ以外の場合、利用できる方法がないため終了。
*/
return;
}
/**
* 検証する文字を描画するcanvas要素と、
* そのコンテキストを生成。
*/
testElem = sampleElem.cloneNode(false);
testCtx = testElem.getContext('2d');
/**
* プロパティを設定する。
*/
/**
* 文字が表示されるか比較する基準となるcanvasのコンテキスト。
* @type {CanvasRenderingContext2D}
*/
this.sampleCtx = sampleCtx;
/**
* 判定したい文字を描画するcanvasのコンテキスト。
* @type {CanvasRenderingContext2D}
*/
this.testCtx = testCtx;
/**
* フォントの初期値。
* @type {string}
*/
this.fontStyle = fontStyle;
/**
* 文字の対応判定が可能な場合にtrue、そうでない場合にfalseになります。
* @type {boolean}
*/
this.enabled = true;
}
/**
* 「表示できない文字」の基準となる文字。
* 外字の「U+10FFFF」を指定。
* @const {string}
*/
SupportCharacter.prototype.NON_SUPPORT_CHAR = '\uDBFF\uDFFF';
/**
* canvasのサイズ。縦・横の大きさとして利用されます。
* @const {number}
*/
SupportCharacter.prototype.CANVAS_SIZE = 100;
/**
* 指定された文字が、正常に描画できるか検証します。
* 「正常に描画できる」とは、「文字化け」と異なる表示結果となったため、
* 現在のフォントがその文字に対応しており、表示できると「推測される」事を意味します。
*
* @param {string} char 判定対象の文字。
* 文字列が指定された場合、先頭の文字のみが判定に利用されます。
* @param {(string|Element)=} opt_fontStyle フォントの種類を指定する文字列、
* あるいは文字を描画したいDOM要素。
* 文字列を指定する場合、CSSのfont-familyプロパティと同じ値を指定します。
* 要素が指定された場合、getComputedStyleにより取得される要素の算出スタイルの
* font-familyプロパティが使用されます。
* @return {boolean} 文字に対応している場合true、それ以外の場合にfalseが返されます。
* 文字に対応していない場合だけでなく、文字が未指定の場合や
* 文字の判定が行えない場合もfalseが返されます。
* このため、判定を行う前に、enabledプロパティやgetFirstCharメソッドで
* 文字の判定が可能か検証してください。
*/
SupportCharacter.prototype.check = function(char, opt_fontStyle){
/**
* 文字が表示されるか比較する基準となるcanvasのコンテキスト。
* @type {CanvasRenderingContext2D}
*/
var sampleCtx = this.sampleCtx;
/**
* 判定したい文字を描画するcanvasのコンテキスト。
* @type {CanvasRenderingContext2D}
*/
var testCtx = this.testCtx;
/**
* フォント名
* @type {string}
*/
var fontStyle;
if (this.enabled && (char = this.getFirstChar(char)) !== '') {
fontStyle = (1 < arguments.length) ? this.getFontValue(arguments[1]) : this.fontStyle;
this.drowText(sampleCtx, this.NON_SUPPORT_CHAR, fontStyle);
this.drowText(testCtx, char, fontStyle);
return !this.isMatchCanvas(sampleCtx, testCtx);
}
return false;
};
/**
* canvasに文字を描画します。
* 描画する際、canvasの内容はリセットされます。
*
* @param {CanvasRenderingContext2D} ctx 描画するcanvasのコンテキスト
* @param {string} text 描画する文字列
* @param {string} font フォントの指定
* @protected
*/
SupportCharacter.prototype.drowText = function(ctx, text, font){
/**
* 引数に指定されたコンテキストに対応するcanvas要素。
* @type {Element}
*/
var canvasElem = ctx.canvas;
/**
* フォントの大きさ。canvasの縦・横のうち、小さい方の半分。
* @type {number}
*/
var fontSize = Math.min(canvasElem.width, canvasElem.height) / 2;
/**
* 描画前に、canvasの内容をリセット。
*/
this.clearCanvas(ctx);
/**
* 文字を描画。
*/
ctx.font = fontSize + 'px ' + font;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillText(text, 0, 0);
};
/**
* 指定されたcanvasの描画内容が等しいか比較します。
* このメソッドは、コンストラクタ内で{@see SupportCharacter#isMatchDataURL}、
* または{@see SupportCharacter#isMatchImageData}へと上書きされます。
*
* @param {CanvasRenderingContext2D} ctx1 比較する1つめのcanvasのコンテキスト
* @param {CanvasRenderingContext2D} ctx2 比較する2つめのcanvasのコンテキスト
* @return {boolean} 等しい場合にtrue、それ以外の場合にfalse
* @protected
*/
SupportCharacter.prototype.isMatchCanvas = function(ctx1, ctx2){};
/**
* 指定されたcanvasの描画内容が等しいか、
* toDataURLメソッドで生成したDataURLを利用して比較します。
* このメソッドは、{@see SupportCharacter#isMatchCanvas}を上書きするために存在します。
*
* @param {CanvasRenderingContext2D} ctx1 比較する1つめのcanvasのコンテキスト
* @param {CanvasRenderingContext2D} ctx2 比較する2つめのcanvasのコンテキスト
* @return {boolean} 等しい場合にtrue、それ以外の場合にfalse
* @protected
*/
SupportCharacter.prototype.isMatchDataURL = function(ctx1, ctx2){
/**
* 検証する文字を描画するcanvas要素。
* @type {Element}
*/
var canvasElem1 = ctx1.canvas;
/**
* ctx2に対応するcanvas要素。
* @type {Element}
*/
var canvasElem2 = ctx2.canvas;
return canvasElem1.toDataURL() === canvasElem2.toDataURL();
};
/**
* 指定されたcanvasの描画内容が等しいか、
* canvasのImageDataオブジェクトを利用して比較します。
* このメソッドは、{@see SupportCharacter#isMatchCanvas}を
* 上書きするために存在します。
*
* @param {CanvasRenderingContext2D} ctx1 比較する1つめのcanvasのコンテキスト
* @param {CanvasRenderingContext2D} ctx2 比較する2つめのcanvasのコンテキスト
* @return {boolean} 等しい場合にtrue、それ以外の場合にfalse
* @protected
*/
SupportCharacter.prototype.isMatchImageData = function(ctx1, ctx2){
return this.isMatchUint8ClampedArray(
this.getImageData(ctx1).data,
this.getImageData(ctx2).data
);
};
/**
* Uint8ClampedArrayオブジェクトまたは
* CanvasPixelArrayオブジェクトの持つデータが
* 完全に一致するか比較します。
*
* @param {Uint8ClampedArray} array1 比較する1つめのUint8ClampedArrayオブジェクト
* @param {Uint8ClampedArray} array2 比較する2つめのUint8ClampedArrayオブジェクト
* @return {boolean} 等しい場合にtrue、それ以外の場合にfalse
* @protected
*/
SupportCharacter.prototype.isMatchUint8ClampedArray = function(array1, array2){
/**
* array1の要素数。
* @type {number}
*/
var arr1len = array1.length;
/**
* array2の要素数。
* @type {number}
*/
var arr2len = array2.length;
/**
* for文で用いるインデックス番号。
* @type {number}
*/
var i;
/**
* 要素数が異なる場合、
* 即座にfalseとする。
*/
if (arr1len !== arr2len) {
return false;
}
if (
this.isFunction(array1.toString) &&
this.isFunction(array2.toString)
) {
/**
* toStringメソッドが定義されている場合、それを利用して比較する。
*/
return array1.toString() === array2.toString();
} else {
/**
* forループで比較する。
* 1つでも異なる値があった場合、即座にfalseとする。
*/
for (i = arr1len; i--; ) {
if (array1[i] !== array2[i]) {
return false;
}
}
return true;
}
};
/**
* canvasの描画内容をリセットします。
*
* @param {CanvasRenderingContext2D} ctx リセットするcanvasのコンテキスト
* @protected
*/
SupportCharacter.prototype.clearCanvas = function(ctx){
/**
* 引数に指定されたコンテキストに対応するcanvas要素。
* @type {Element}
*/
var canvasElem = ctx.canvas;
ctx.clearRect(0, 0, canvasElem.width, canvasElem.height);
};
/**
* canvasのImageDataオブジェクトを取得します。
*
* @param {CanvasRenderingContext2D} ctx 取得するcanvasのコンテキスト
* @return {ImageData} 取得したImageDataオブジェクト
* @protected
*/
SupportCharacter.prototype.getImageData = function(ctx){
/**
* 引数に指定されたコンテキストに対応するcanvas要素。
* @type {Element}
*/
var canvasElem = ctx.canvas;
return ctx.getImageData(0, 0, canvasElem.width, canvasElem.height);
};
/**
* 文字列、あるいは要素から、フォント名の指定値として扱う文字列を取得します。
* 取得に失敗した場合、"sans-serif"が出力されます。
*
* @param {string|Element} fontStyle フォントの種類を指定する文字列、
* あるいは文字を描画したいDOM要素。
* 文字列を指定する場合、CSSのfont-familyプロパティと同じ値を指定します。
* 要素が指定された場合、getComputedStyleにより取得される要素の算出スタイルの
* font-familyプロパティが使用されます。
* @return {string} 取得したフォント名
* @protected
*/
SupportCharacter.prototype.getFontValue = function(fontStyle){
/**
* フォント名の文字列
* @type {string}
*/
var fontStyleStr;
/**
* フォントの指定を取得する要素
* @type {Element}
*/
var fontStyleElem;
/**
* getComputedStyleにより取得したスタイルオブジェクト
* @type {CSSStyleDeclaration}
*/
var styleObject;
/**
* 引数が要素の場合、要素の属するwindowオブジェクトが格納されます。
* @type {Window}
*/
var defaultView;
if (this.isString(fontStyle)) {
/**
* 文字列の場合、font-familyプロパティと同じ指定値と解釈します。
*/
fontStyleStr = String(fontStyle);
} else if(
!!fontStyle &&
typeof fontStyle === 'object' &&
fontStyle.nodeType === 1 &&
!!fontStyle.ownerDocument
) {
/**
* 要素の場合、getComputedStyleで要素の算出スタイルを取得し、
* font-familyプロパティの値を利用する。
*/
fontStyleElem = fontStyle;
defaultView = fontStyleElem.ownerDocument.defaultView || window;
styleObject = (
!!defaultView.getComputedStyle &&
defaultView.getComputedStyle(fontStyleElem, '')
) || fontStyleElem.style;
fontStyleStr = styleObject.fontFamily;
}
return fontStyleStr || this.fontStyle || 'sans-serif';
};
/**
* 指定された文字列から、先頭の1文字を取得します。
* 文字列が空文字列、あるいはUnicodeとして不正なバイナリ文字列の場合、
* 空文字列が返されます。
* この関数はサロゲートペアに対応しています。
*
* @param {string} str 取得する文字列
* @return {string} 取得した文字、あるいは空文字列
* @protected
*/
SupportCharacter.prototype.getFirstChar = function(str){
/**
* RegExp.prototype.execメソッドの返り値を保持する変数。
* @type {Array<string>|null}
*/
var result;
return (
(
str !== '' &&
!!(result = /^(?:[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF])/.exec(str))
) ?
result[0] :
''
);
};
/**
* 引数の値が文字列か判定します。
*
* @param {*} value
* @return {boolean} 文字列の場合はtrue、それ以外の場合はfalse
* @protected
*/
SupportCharacter.prototype.isString = function(value){
return (
typeof value === 'string' ||
(
!!value &&
typeof value === 'object' &&
Object.prototype.toString.call(value) === '[object String]'
)
);
};
/**
* 引数の値が関数か判定します。
*
* @param {*} value
* @return {boolean} 関数の場合はtrue、それ以外の場合はfalse
* @protected
*/
SupportCharacter.prototype.isFunction = function(value){
return typeof value === 'function';
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment