2013年3月12日火曜日

svgフォントをfirefox/ieでも利用可能とする

svgのもつfont(svgフォント)機能はテキストエディタでフォントを作れるという非常に魅力的なものです.その一方でこのsvgフォントを直接サポートするブラウザはwebkit系のブラウザ(chrome,safari)及びopera(presto)のみであり,利用範囲が限られてしまいます.

その為,本来svgフォントを表示することの出来ないfirefoxやie(10)においてもsvgフォントを擬似的に描画させるスクリプトを書いて見ました.

2013/12/19 修正.文字列をコピペした際に[SVGSVGElement]って出力されてしまう部分を修正.





カラクリ


svgにおいて文字の形を表すglyph要素とpath要素との間には互換性があります.それぞれのd属性を他方に設定することで形状を引き継ぐことが可能です.(なお,その際に縦方向に反転してしまう点に注意します.)
従って,glyph要素をpath要素に変換すればインラインsvgから利用しやすい形状となります.

使い方


svgfontloader.jsを読み込んだ後,window.onload以降にFontLoader.loadメソッドを実行します.(詳しくはサンプルのhtmlを参照のこと)

制限事項


あくまでインラインsvgを使ってフォントを再現しているだけなので,cssによるテキスト装飾はできません.
(唯一color属性は親要素から引き継ぐことが可能です.)
ie9では動作を確認していません.(環境が無いので…)

ソースコード


"use strict";
/*
firefoxでsvgフォントを擬似的に表示させるスクリプト.
対応するブラウザ:firefox,ie10(ie9は?)

テキストの部分をインラインsvgに置き換えることでsvgフォントに含まれるglyph情報を表示する.
※理想的にはotfやwoff等の形式に変換してしまいたいところだが,ファイル仕様が…

NOTE:
横書きのみに対応
生成した文字形は厳密にはテキストではないため,cssによるスタイリングは無効となる.
(一部文字の色のみが引き継がれる)
※本文に対してのみ有効(擬似要素内のcontentでは無効)
※svg要素内部のテキストには無効
*/
(function(){
 var SVG_NS = "http://www.w3.org/2000/svg";
 //svgフォントをロードする
 function load(path, cssClass){
  var req = new XMLHttpRequest();
  req.open("GET", path, true);
  req.onreadystatechange = (function(cssClass){
   return function(e){
    if(req.readyState != 4){return;}
    if(req.status == 200 || req.status == 0){
     var svg = req.responseXML.documentElement;
     var font = svg.getElementsByTagName("font")[0];
     if(svg.namespaceURI == SVG_NS || font != null){
      displayFont(font, cssClass);
     }
    }
   };
  })(cssClass);
  req.send(null);
 }

 //フォントを表示する
 function displayFont(font, cssClass){
  var map = setupFont(font, cssClass);
  displayGlyph(map, cssClass);
 }
 
 //初期設定を行う
 //glyph→path要素に変換し,use要素から参照可能とする.
 //unicode文字⇔svg-useフォントのマップを返す.
 //文字を挿入する場合はこのマップからsvg要素をクローンしhtmlに挿入する
 function setupFont(font, cssClass){
  //font-face要素
  var fontFace = font.getElementsByTagName("font-face")[0];
  //文字のテンプレートを格納するsvg要素
  var svg = createSVGElement("svg");
  svg.style.display = "none";
  var defs = createSVGElement("defs");
  var pathTmp = createSVGElement("path");
  svg.appendChild(defs);
  
  //文字の入れ替えに用いるsvg要素(cloneして用いる)
  var charTmp = createSVGElement("svg");
  charTmp.setAttribute("preserveAspectRatio", "xMinYMin slice");
  charTmp.appendChild(createSVGElement("use"));

  //文字列に対するsvg要素のマップ
  var map = {};
  //フォントを格納しているsvg要素にmapを挿入.(再利用するためのもの)
  svg.unicodeSvgMap = map;
  svg.appendChild(font);
  
  var glyphs = font.querySelectorAll("glyph");
  for(var i = 0, len = glyphs.length; i<len; i++){
   var glyph = glyphs[i];
   var unicode = glyph.getAttribute("unicode");
   var id = cssClass + "_" + unicode;
   //文字のテンプレート側   
   var path = pathTmp.cloneNode(false);
   path.setAttribute("d", glyph.getAttribute("d"));
   path.id = id;
   defs.appendChild(path);
   //文字を利用する側
   var char = charTmp.cloneNode(true);
   var use = char.querySelector("use");
   use.href.baseVal = "#" + id;
   setStyle(char, use, font, fontFace, glyph);
   map[unicode] = char;
  }
  document.body.appendChild(svg);
  return map;
 }
 
 //表示用のスタイルを設定する.
 //グリフ描画の為の設定を施す(大きさ等)
 function setStyle(char, use, font, fontFace, glyph){
  var upem = fontFace.getAttribute("units-per-em");
  var width = glyph.getAttribute("horiz-adv-x");
  if(!width){
   width = font.getAttribute("horiz-adv-x");
  }
  var cs = char.style;
  cs.width = "1em";
  cs.height = "1em";
  cs.marginRight = (width/upem - 1) + "em";
  cs.display = "inline";

  char.setAttribute("viewBox", [0, 0, upem, upem].join(" "));
  use.setAttribute("transform", "translate(0," + upem + "),scale(1,-1)");
  var us = use.style;
  us.fill = "currentColor";
 }
 
 //指定したクラスをもつ要素にグリフを適用する
 function displayGlyph(map, cssClass){
  //既にsvgフォント適用済みの部分は処理対象外
  var elems = document.querySelectorAll(
   "." + cssClass + ",." + cssClass + " *:not(.svgfont_replaced)");
  //テキスト分割の為の正規表現は使いまわせるのでここで生成してしまう.
  var regEx = toRegExp(map);
  for(var i = 0, len = elems.length; i<len; i++){
   var elem = elems[i];
   if(ignore(elem)){
    continue;
   }
   applyGlyphToElement(map, elem, regEx);
  }
 }
 
 //文字列マップから正規表現を生成する
 var regEx4esc = new RegExp("([.*+?^=!:${}()|[\]\/\\])","g");
 function toRegExp(map){
  var patterns = [];
  for(var i in map){
   patterns.push(i.replace(regEx4esc, "\\$1"));
  }
  patterns.push("\.");
  return new RegExp(patterns.join("|"), "g");
 }
 
 //処理対象外の要素を除外する
 var ignores = {
  input: true, 
  select: true, 
  option: true, 
  meter: true, 
  textArea: true,
  button: true};
 function ignore(elem){
  //svg要素内部のテキストは除外(面倒なので)
  if(elem.namespaceURI == SVG_NS){return true;}
  return ignores[elem.nodeName.toLowerCase()];
 }

 //単一ノードにグリフを適用する
 function applyGlyphToElement(map, elem, regEx){
  var children = toArray(elem.childNodes);
  for(var i = 0, len = children.length; i<len; i++){
   var child = children[i];
   if(child.nodeType == 3){
    var text = child.textContent;
    var frg = toNodeFragment(map, text, regEx);
    elem.insertBefore(frg, child);
    elem.removeChild(child);
   }
  }
 }
 
 //テキストをノードのリスト変換する
 var wrapperTmp = document.createElement("span");
 wrapperTmp.className = "svgfont_replaced";
 var spanTmp = document.createElement("span");
 spanTmp.style.fontSize = 0;
 function toNodeFragment(map, text, regEx){
  var frg = document.createDocumentFragment();
  //正規表現で文字列を分解する
  var arr = text.match(regEx);
  if(arr === null){return frg;}
  for(var i = 0, len = arr.length; i<len; i++){
   var str = arr[i];
   var strSvg = map[str];
   if(strSvg){
    var wrapper = wrapperTmp.cloneNode(false);
    var span = spanTmp.cloneNode(false);
    span.innerHTML = str;
    wrapper.appendChild(span);
    wrapper.appendChild(strSvg.cloneNode(true));
    frg.appendChild(wrapper);
   }else{
    frg.appendChild(document.createTextNode(str));
   }
  }
  return frg;
 }

 //svg関連の要素を生成する
 function createSVGElement(nodeName){
  return document.createElementNS(SVG_NS, nodeName);
 }

 //NodeList等の配列様オブジェクトを配列に変換する
 function toArray(arrayLike){
  var array = [];
  for(var i = 0, len = arrayLike.length; i<len; i++){
   array.push(arrayLike[i]);
  }
  return array;
 }

 //外部に公開するapi
 //svgフォントをサポートしている
 //or svgをサポートしない環境では何もしない
 window.FontLoader = {
  load: document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Font", "1.1") 
   || document.createElementNS(SVG_NS, "svg").viewBox === undefined ? function(){}: load
 };
})();

0 件のコメント:

コメントを投稿