Last active
August 9, 2021 07:51
-
-
Save sounisi5011/069402006a1294e22a44 to your computer and use it in GitHub Desktop.
文字の表示に対応しているか判定する
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 文字の表示に対応しているか判定します。 | |
* 判定を行うには、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