dojox/gfx/VectorText.js

  • Provides:

    • dojox.gfx.VectorText
  • Requires:

    • dojox.gfx in common
    • dojox.xml.DomParser in common
    • dojox.html.metrics in common
  • dojox.gfx.VectorFont

    • type
      Function
    • parameters:
      • url: (typeof String|dojo._Url)
    • source: [view]
         this._defaultLeading = 1.5;
         if(url!==undefined){
          this.load(url);
         }
    • summary
      Create this font object based on the SVG Font definition at url.
  • dojox.gfx.VectorFont._entityRe

    • summary
  • dojox.gfx.VectorFont._decodeEntitySequence

    • type
      Function
    • parameters:
      • str: (typeof )
    • source: [view]
      dojo.provide("dojox.gfx.VectorText");
      dojo.require("dojox.gfx");
      dojo.require("dojox.xml.DomParser");
      dojo.require("dojox.html.metrics");


      (function(){
       /*
        dojox.gfx.VectorText
        An implementation of the SVG Font 1.1 spec, using dojox.gfx.


        Basic interface:
        var f = new dojox.gfx.Font(url|string);
        surface||group.createVectorText(text)
         .setFill(fill)
         .setStroke(stroke)
         .setFont(fontStyleObject);


        The arguments passed to createVectorText are the same as you would
        pass to surface||group.createText; the difference is that this
        is entirely renderer-agnostic, and the return value is a subclass
        of dojox.gfx.Group.


        Note also that the "defaultText" object is slightly different:
        { type:"vectortext", x:0, y:0, width:null, height: null,
         text: "", align: "start", decoration: "none" }


        ...as well as the "defaultVectorFont" object:
        { type:"vectorfont", size:"10pt" }


        The reason for this should be obvious: most of the style for the font is defined
        by the font object itself.


        Note that this will only render IF and WHEN you set the font.
        */
       dojo.mixin(dojox.gfx, {
        vectorFontFitting: {
         NONE: 0, // render text according to passed size.
         FLOW: 1,  // render text based on the passed width and size
         FIT: 2   // render text based on a passed viewbox.
        },
        defaultVectorText: {
         type:"vectortext", x:0, y:0, width: null, height: null,
         text: "", align: "start", decoration: "none", fitting: 0, // vectorFontFitting.NONE
         leading: 1.5 // in ems.
        },
        defaultVectorFont: {
         type:"vectorfont", size: "10pt", family: null
        },
        _vectorFontCache: {},
        _svgFontCache: {},
        getVectorFont: function(/* String */url){
         if(dojox.gfx._vectorFontCache[url]){
          return dojox.gfx._vectorFontCache[url];
         }
         return new dojox.gfx.VectorFont(url);
        }
       });


       dojo.declare("dojox.gfx.VectorFont", null, {
        _entityRe: /&(quot|apos|lt|gt|amp|#x[^;]+|#\d+);/g,
        _decodeEntitySequence: function(str){
         // unescape the unicode sequences


         // nothing to decode
         if(!str.match(this._entityRe)){ return; } // undefined
         var xmlEntityMap = {
          amp:"&", apos:"'", quot:'"', lt:"<", gt:">"
         };


         // we have at least one encoded entity.
         var r, tmp="";
         while((r=this._entityRe.exec(str))!==null){
          if(r[1].charAt(1)=="x"){
           tmp += String.fromCharCode(parseInt(r[1].slice(2), 16));
          }
          else if(!isNaN(parseInt(r[1].slice(1),10))){
           tmp += String.fromCharCode(parseInt(r[1].slice(1), 10));
          }
          else {
           tmp += xmlEntityMap[r[1]] || "";
          }
         }
         return tmp; // String
    • returns
      undefined|String
    • summary
  • dojox.gfx.VectorFont._parse

    • type
      Function
    • parameters:
      • svg: (typeof String)
      • url: (typeof String)
    • source: [view]
         var doc = dojox.gfx._svgFontCache[url]||dojox.xml.DomParser.parse(svg);


         // font information
         var f = doc.documentElement.byName("font")[0], face = doc.documentElement.byName("font-face")[0];
         var unitsPerEm = parseFloat(face.getAttribute("units-per-em")||1000, 10);
         var advance = {
          x: parseFloat(f.getAttribute("horiz-adv-x"), 10),
          y: parseFloat(f.getAttribute("vert-adv-y")||0, 10)
         };
         if(!advance.y){
          advance.y = unitsPerEm;
         }


         var origin = {
          horiz: {
           x: parseFloat(f.getAttribute("horiz-origin-x")||0, 10),
           y: parseFloat(f.getAttribute("horiz-origin-y")||0, 10)
          },
          vert: {
           x: parseFloat(f.getAttribute("vert-origin-x")||0, 10),
           y: parseFloat(f.getAttribute("vert-origin-y")||0, 10)
          }
         };


         // face information
         var family = face.getAttribute("font-family"),
          style = face.getAttribute("font-style")||"all",
          variant = face.getAttribute("font-variant")||"normal",
          weight = face.getAttribute("font-weight")||"all",
          stretch = face.getAttribute("font-stretch")||"normal",


          // additional info, may not be needed
          range = face.getAttribute("unicode-range")||"U+0-10FFFF",
          panose = face.getAttribute("panose-1") || "0 0 0 0 0 0 0 0 0 0",
          capHeight = face.getAttribute("cap-height"),
          ascent = parseFloat(face.getAttribute("ascent")||(unitsPerEm-origin.vert.y), 10),
          descent = parseFloat(face.getAttribute("descent")||origin.vert.y, 10),
          baseline = {};


         // check for font-face-src/font-face-name
         var name = family;
         if(face.byName("font-face-name")[0]){
          name = face.byName("font-face-name")[0].getAttribute("name");
         }


         // see if this is cached already, and if so, forget the rest of the parsing.
         if(dojox.gfx._vectorFontCache[name]){ return; }


         // get any provided baseline alignment offsets.
         dojo.forEach(["alphabetic", "ideographic", "mathematical", "hanging" ], function(attr){
          var a = face.getAttribute(attr);
          if(a !== null /* be explicit, might be 0 */){
           baseline[attr] = parseFloat(a, 10);
          }
         });


        /*
         // TODO: decoration hinting.
         var decoration = { };
         dojo.forEach(["underline", "strikethrough", "overline"], function(type){
          if(face.getAttribute(type+"-position")!=null){
           decoration[type]={ };
          }
         });
        */


         // missing glyph info
         var missing = parseFloat(doc.documentElement.byName("missing-glyph")[0].getAttribute("horiz-adv-x")||advance.x, 10);


         // glyph information
         var glyphs = {}, glyphsByName={}, g=doc.documentElement.byName("glyph");
         dojo.forEach(g, function(node){
          // we are going to assume the following:
          //  1) we have the unicode attribute
          //  2) we have the name attribute
          //  3) we have the horiz-adv-x and d attributes.
          var code = node.getAttribute("unicode"),
           name = node.getAttribute("glyph-name"),
           xAdv = parseFloat(node.getAttribute("horiz-adv-x")||advance.x, 10),
           path = node.getAttribute("d");


          // unescape the unicode sequences
          if(code.match(this._entityRe)){
           code = this._decodeEntitySequence(code);
          }


          // build our glyph objects
          var o = { code: code, name: name, xAdvance: xAdv, path: path };
          glyphs[code]=o;
          glyphsByName[name]=o;
         }, this);


         // now the fun part: look for kerning pairs.
         var hkern=doc.documentElement.byName("hkern");
         dojo.forEach(hkern, function(node, i){
          var k = -parseInt(node.getAttribute("k"),10);
          // look for either a code or a name
          var u1=node.getAttribute("u1"),
           g1=node.getAttribute("g1"),
           u2=node.getAttribute("u2"),
           g2=node.getAttribute("g2"),
           gl;


          if(u1){
           // the first of the pair is a sequence of unicode characters.
           // TODO: deal with unicode ranges and mulitple characters.
           u1 = this._decodeEntitySequence(u1);
           if(glyphs[u1]){
            gl = glyphs[u1];
           }
          } else {
           // we are referring to a name.
           // TODO: deal with multiple names
           if(glyphsByName[g1]){
            gl = glyphsByName[g1];
           }
          }


          if(gl){
           if(!gl.kern){ gl.kern = {}; }
           if(u2){
            // see the notes above.
            u2 = this._decodeEntitySequence(u2);
            gl.kern[u2] = { x: k };
           } else {
            if(glyphsByName[g2]){
             gl.kern[glyphsByName[g2].code] = { x: k };
            }
           }
          }
         }, this);


         // pop the final definition in the font cache.
         dojo.mixin(this, {
          family: family,
          name: name,
          style: style,
          variant: variant,
          weight: weight,
          stretch: stretch,
          range: range,
          viewbox: { width: unitsPerEm, height: unitsPerEm },
          origin: origin,
          advance: dojo.mixin(advance, {
           missing:{ x: missing, y: missing }
          }),
          ascent: ascent,
          descent: descent,
          baseline: baseline,
          glyphs: glyphs
         });


         // cache the parsed font
         dojox.gfx._vectorFontCache[name] = this;
         dojox.gfx._vectorFontCache[url] = this;
         if(name!=family && !dojox.gfx._vectorFontCache[family]){
          dojox.gfx._vectorFontCache[family] = this;
         }


         // cache the doc
         if(!dojox.gfx._svgFontCache[url]){
          dojox.gfx._svgFontCache[url]=doc;
         }
    • summary
      Take the loaded SVG Font definition file and convert the info
      into things we can use. The SVG Font definition must follow
      the SVG 1.1 Font specification.
  • dojox.gfx.VectorFont._clean

    • type
      Function
    • source: [view]
         var name = this.name, family = this.family;
         dojo.forEach(["family","name","style","variant",
          "weight","stretch","range","viewbox",
          "origin","advance","ascent","descent",
          "baseline","glyphs"], function(prop){
           try{ delete this[prop]; } catch(e) { }
         }, this);


         // try to pull out of the font cache.
         if(dojox.gfx._vectorFontCache[name]){
          delete dojox.gfx._vectorFontCache[name];
         }
         if(dojox.gfx._vectorFontCache[family]){
          delete dojox.gfx._vectorFontCache[family];
         }
         return this;
    • summary
      Clean off all of the given mixin parameters.
  • dojox.gfx.VectorFont.load

    • type
      Function
    • parameters:
      • url: (typeof String|dojo._Url)
    • source: [view]
         this.onLoadBegin(url.toString());
         this._parse(
          dojox.gfx._svgFontCache[url.toString()]||dojo._getText(url.toString()),
          url.toString()
         );
         this.onLoad(this);
         return this; // dojox.gfx.VectorFont
    • summary
      Load the passed SVG and send it to the parser for parsing.
    • returns
      dojox.gfx.VectorFont
  • dojox.gfx.VectorFont.initialized

    • type
      Function
    • source: [view]
         return (this.glyphs!==null); // Boolean
    • summary
      Return if we've loaded a font def, and the parsing was successful.
    • returns
      Boolean
  • dojox.gfx.VectorFont._round

    • type
      Function
    • parameters:
      • n: (typeof )
    • source: [view]
        _round: function(n){ return Math.round(1000*n)/1000;
    • summary
  • dojox.gfx.VectorFont._leading

    • type
      Function
    • parameters:
      • unit: (typeof )
    • source: [view]
        _leading: function(unit){ return this.viewbox.height * (unit||this._defaultLeading);
    • summary
  • dojox.gfx.VectorFont._normalize

    • type
      Function
    • parameters:
      • str: (typeof )
    • source: [view]
         return str.replace(/\s+/g, String.fromCharCode(0x20));
    • summary
  • dojox.gfx.VectorFont._getWidth

    • type
      Function
    • parameters:
      • glyphs: (typeof )
    • source: [view]
         var w=0, last=0, lastGlyph=null;
         dojo.forEach(glyphs, function(glyph, i){
          last=glyph.xAdvance;
          if(glyphs[i] && glyph.kern && glyph.kern[glyphs[i].code]){
           last += glyph.kern[glyphs[i].code].x;
          }
          w += last;
          lastGlyph = glyph;
         });


         // if the last glyph was a space, pull it off.
         if(lastGlyph && lastGlyph.code == " "){
          w -= lastGlyph.xAdvance;
         }


         return this._round(w/*-last*/);
    • summary
  • dojox.gfx.VectorFont._getLongestLine

    • type
      Function
    • parameters:
      • lines: (typeof )
    • source: [view]
         var maxw=0, idx=0;
         dojo.forEach(lines, function(line, i){
          var max = Math.max(maxw, this._getWidth(line));
          if(max > maxw){
           maxw = max;
           idx=i;
          }
         }, this);
         return { width: maxw, index: idx, line: lines[idx] };
    • summary
  • dojox.gfx.VectorFont._trim

    • type
      Function
    • parameters:
      • lines: (typeof )
    • source: [view]
         var fn = function(arr){
          // check if the first or last character is a space and if so, remove it.
          if(!arr.length){ return; }
          if(arr[arr.length-1].code == " "){ arr.splice(arr.length-1, 1); }
          if(!arr.length){ return; }
          if(arr[0].code == " "){ arr.splice(0, 1); }
         };


         if(dojo.isArray(lines[0])){
          // more than one line.
          dojo.forEach(lines, fn);
         } else {
          fn(lines);
         }
         return lines;
    • summary
  • dojox.gfx.VectorFont._split

    • type
      Function
    • parameters:
      • chars: (typeof )
      • nLines: (typeof )
    • source: [view]
         var w = this._getWidth(chars),
          limit = Math.floor(w/nLines),
          lines = [],
          cw = 0,
          c = [],
          found = false;


         for(var i=0, l=chars.length; i    if(chars[i].code == " "){ found = true; }
          cw += chars[i].xAdvance;
          if(i+1     cw += chars[i].kern[chars[i+1].code].x;
          }


          if(cw>=limit){
           var chr=chars[i];
           while(found && chr.code != " " && i>=0){
            chr = c.pop(); i--;
           }
           lines.push(c);
           c=[];
           cw=0;
           found=false;
          }
          c.push(chars[i]);
         }
         if(c.length){ lines.push(c); }
         // "trim" it
         return this._trim(lines);
    • summary
      split passed chars into nLines by finding the closest whitespace.
  • dojox.gfx.VectorFont._getSizeFactor

    • type
      Function
    • parameters:
      • size: (typeof )
    • source: [view]
      dojo.provide("dojox.gfx.VectorText");
      dojo.require("dojox.gfx");
      dojo.require("dojox.xml.DomParser");
      dojo.require("dojox.html.metrics");


      (function(){
       /*
        dojox.gfx.VectorText
        An implementation of the SVG Font 1.1 spec, using dojox.gfx.


        Basic interface:
        var f = new dojox.gfx.Font(url|string);
        surface||group.createVectorText(text)
         .setFill(fill)
         .setStroke(stroke)
         .setFont(fontStyleObject);


        The arguments passed to createVectorText are the same as you would
        pass to surface||group.createText; the difference is that this
        is entirely renderer-agnostic, and the return value is a subclass
        of dojox.gfx.Group.


        Note also that the "defaultText" object is slightly different:
        { type:"vectortext", x:0, y:0, width:null, height: null,
         text: "", align: "start", decoration: "none" }


        ...as well as the "defaultVectorFont" object:
        { type:"vectorfont", size:"10pt" }


        The reason for this should be obvious: most of the style for the font is defined
        by the font object itself.


        Note that this will only render IF and WHEN you set the font.
        */
       dojo.mixin(dojox.gfx, {
        vectorFontFitting: {
         NONE: 0, // render text according to passed size.
         FLOW: 1,  // render text based on the passed width and size
         FIT: 2   // render text based on a passed viewbox.
        },
        defaultVectorText: {
         type:"vectortext", x:0, y:0, width: null, height: null,
         text: "", align: "start", decoration: "none", fitting: 0, // vectorFontFitting.NONE
         leading: 1.5 // in ems.
        },
        defaultVectorFont: {
         type:"vectorfont", size: "10pt", family: null
        },
        _vectorFontCache: {},
        _svgFontCache: {},
        getVectorFont: function(/* String */url){
         if(dojox.gfx._vectorFontCache[url]){
          return dojox.gfx._vectorFontCache[url];
         }
         return new dojox.gfx.VectorFont(url);
        }
       });


       dojo.declare("dojox.gfx.VectorFont", null, {
        _entityRe: /&(quot|apos|lt|gt|amp|#x[^;]+|#\d+);/g,
        _decodeEntitySequence: function(str){
         // unescape the unicode sequences


         // nothing to decode
         if(!str.match(this._entityRe)){ return; } // undefined
         var xmlEntityMap = {
          amp:"&", apos:"'", quot:'"', lt:"<", gt:">"
         };


         // we have at least one encoded entity.
         var r, tmp="";
         while((r=this._entityRe.exec(str))!==null){
          if(r[1].charAt(1)=="x"){
           tmp += String.fromCharCode(parseInt(r[1].slice(2), 16));
          }
          else if(!isNaN(parseInt(r[1].slice(1),10))){
           tmp += String.fromCharCode(parseInt(r[1].slice(1), 10));
          }
          else {
           tmp += xmlEntityMap[r[1]] || "";
          }
         }
         return tmp; // String
        },
        _parse: function(/* String */svg, /* String */url){
         // summary:
         //  Take the loaded SVG Font definition file and convert the info
         //  into things we can use. The SVG Font definition must follow
         //  the SVG 1.1 Font specification.
         var doc = dojox.gfx._svgFontCache[url]||dojox.xml.DomParser.parse(svg);


         // font information
         var f = doc.documentElement.byName("font")[0], face = doc.documentElement.byName("font-face")[0];
         var unitsPerEm = parseFloat(face.getAttribute("units-per-em")||1000, 10);
         var advance = {
          x: parseFloat(f.getAttribute("horiz-adv-x"), 10),
          y: parseFloat(f.getAttribute("vert-adv-y")||0, 10)
         };
         if(!advance.y){
          advance.y = unitsPerEm;
         }


         var origin = {
          horiz: {
           x: parseFloat(f.getAttribute("horiz-origin-x")||0, 10),
           y: parseFloat(f.getAttribute("horiz-origin-y")||0, 10)
          },
          vert: {
           x: parseFloat(f.getAttribute("vert-origin-x")||0, 10),
           y: parseFloat(f.getAttribute("vert-origin-y")||0, 10)
          }
         };


         // face information
         var family = face.getAttribute("font-family"),
          style = face.getAttribute("font-style")||"all",
          variant = face.getAttribute("font-variant")||"normal",
          weight = face.getAttribute("font-weight")||"all",
          stretch = face.getAttribute("font-stretch")||"normal",


          // additional info, may not be needed
          range = face.getAttribute("unicode-range")||"U+0-10FFFF",
          panose = face.getAttribute("panose-1") || "0 0 0 0 0 0 0 0 0 0",
          capHeight = face.getAttribute("cap-height"),
          ascent = parseFloat(face.getAttribute("ascent")||(unitsPerEm-origin.vert.y), 10),
          descent = parseFloat(face.getAttribute("descent")||origin.vert.y, 10),
          baseline = {};


         // check for font-face-src/font-face-name
         var name = family;
         if(face.byName("font-face-name")[0]){
          name = face.byName("font-face-name")[0].getAttribute("name");
         }


         // see if this is cached already, and if so, forget the rest of the parsing.
         if(dojox.gfx._vectorFontCache[name]){ return; }


         // get any provided baseline alignment offsets.
         dojo.forEach(["alphabetic", "ideographic", "mathematical", "hanging" ], function(attr){
          var a = face.getAttribute(attr);
          if(a !== null /* be explicit, might be 0 */){
           baseline[attr] = parseFloat(a, 10);
          }
         });


        /*
         // TODO: decoration hinting.
         var decoration = { };
         dojo.forEach(["underline", "strikethrough", "overline"], function(type){
          if(face.getAttribute(type+"-position")!=null){
           decoration[type]={ };
          }
         });
        */


         // missing glyph info
         var missing = parseFloat(doc.documentElement.byName("missing-glyph")[0].getAttribute("horiz-adv-x")||advance.x, 10);


         // glyph information
         var glyphs = {}, glyphsByName={}, g=doc.documentElement.byName("glyph");
         dojo.forEach(g, function(node){
          // we are going to assume the following:
          //  1) we have the unicode attribute
          //  2) we have the name attribute
          //  3) we have the horiz-adv-x and d attributes.
          var code = node.getAttribute("unicode"),
           name = node.getAttribute("glyph-name"),
           xAdv = parseFloat(node.getAttribute("horiz-adv-x")||advance.x, 10),
           path = node.getAttribute("d");


          // unescape the unicode sequences
          if(code.match(this._entityRe)){
           code = this._decodeEntitySequence(code);
          }


          // build our glyph objects
          var o = { code: code, name: name, xAdvance: xAdv, path: path };
          glyphs[code]=o;
          glyphsByName[name]=o;
         }, this);


         // now the fun part: look for kerning pairs.
         var hkern=doc.documentElement.byName("hkern");
         dojo.forEach(hkern, function(node, i){
          var k = -parseInt(node.getAttribute("k"),10);
          // look for either a code or a name
          var u1=node.getAttribute("u1"),
           g1=node.getAttribute("g1"),
           u2=node.getAttribute("u2"),
           g2=node.getAttribute("g2"),
           gl;


          if(u1){
           // the first of the pair is a sequence of unicode characters.
           // TODO: deal with unicode ranges and mulitple characters.
           u1 = this._decodeEntitySequence(u1);
           if(glyphs[u1]){
            gl = glyphs[u1];
           }
          } else {
           // we are referring to a name.
           // TODO: deal with multiple names
           if(glyphsByName[g1]){
            gl = glyphsByName[g1];
           }
          }


          if(gl){
           if(!gl.kern){ gl.kern = {}; }
           if(u2){
            // see the notes above.
            u2 = this._decodeEntitySequence(u2);
            gl.kern[u2] = { x: k };
           } else {
            if(glyphsByName[g2]){
             gl.kern[glyphsByName[g2].code] = { x: k };
            }
           }
          }
         }, this);


         // pop the final definition in the font cache.
         dojo.mixin(this, {
          family: family,
          name: name,
          style: style,
          variant: variant,
          weight: weight,
          stretch: stretch,
          range: range,
          viewbox: { width: unitsPerEm, height: unitsPerEm },
          origin: origin,
          advance: dojo.mixin(advance, {
           missing:{ x: missing, y: missing }
          }),
          ascent: ascent,
          descent: descent,
          baseline: baseline,
          glyphs: glyphs
         });


         // cache the parsed font
         dojox.gfx._vectorFontCache[name] = this;
         dojox.gfx._vectorFontCache[url] = this;
         if(name!=family && !dojox.gfx._vectorFontCache[family]){
          dojox.gfx._vectorFontCache[family] = this;
         }


         // cache the doc
         if(!dojox.gfx._svgFontCache[url]){
          dojox.gfx._svgFontCache[url]=doc;
         }
        },
        _clean: function(){
         // summary:
         //  Clean off all of the given mixin parameters.
         var name = this.name, family = this.family;
         dojo.forEach(["family","name","style","variant",
          "weight","stretch","range","viewbox",
          "origin","advance","ascent","descent",
          "baseline","glyphs"], function(prop){
           try{ delete this[prop]; } catch(e) { }
         }, this);


         // try to pull out of the font cache.
         if(dojox.gfx._vectorFontCache[name]){
          delete dojox.gfx._vectorFontCache[name];
         }
         if(dojox.gfx._vectorFontCache[family]){
          delete dojox.gfx._vectorFontCache[family];
         }
         return this;
        },


        constructor: function(/* String|dojo._Url */url){
         // summary::
         //  Create this font object based on the SVG Font definition at url.
         this._defaultLeading = 1.5;
         if(url!==undefined){
          this.load(url);
         }
        },
        load: function(/* String|dojo._Url */url){
         // summary::
         //  Load the passed SVG and send it to the parser for parsing.
         this.onLoadBegin(url.toString());
         this._parse(
          dojox.gfx._svgFontCache[url.toString()]||dojo._getText(url.toString()),
          url.toString()
         );
         this.onLoad(this);
         return this; // dojox.gfx.VectorFont
        },
        initialized: function(){
         // summary::
         //  Return if we've loaded a font def, and the parsing was successful.
         return (this.glyphs!==null); // Boolean
        },


        // preset round to 3 places.
        _round: function(n){ return Math.round(1000*n)/1000; },
        _leading: function(unit){ return this.viewbox.height * (unit||this._defaultLeading); },
        _normalize: function(str){
         return str.replace(/\s+/g, String.fromCharCode(0x20));
        },


        _getWidth: function(glyphs){
         var w=0, last=0, lastGlyph=null;
         dojo.forEach(glyphs, function(glyph, i){
          last=glyph.xAdvance;
          if(glyphs[i] && glyph.kern && glyph.kern[glyphs[i].code]){
           last += glyph.kern[glyphs[i].code].x;
          }
          w += last;
          lastGlyph = glyph;
         });


         // if the last glyph was a space, pull it off.
         if(lastGlyph && lastGlyph.code == " "){
          w -= lastGlyph.xAdvance;
         }


         return this._round(w/*-last*/);
        },


        _getLongestLine: function(lines){
         var maxw=0, idx=0;
         dojo.forEach(lines, function(line, i){
          var max = Math.max(maxw, this._getWidth(line));
          if(max > maxw){
           maxw = max;
           idx=i;
          }
         }, this);
         return { width: maxw, index: idx, line: lines[idx] };
        },


        _trim: function(lines){
         var fn = function(arr){
          // check if the first or last character is a space and if so, remove it.
          if(!arr.length){ return; }
          if(arr[arr.length-1].code == " "){ arr.splice(arr.length-1, 1); }
          if(!arr.length){ return; }
          if(arr[0].code == " "){ arr.splice(0, 1); }
         };


         if(dojo.isArray(lines[0])){
          // more than one line.
          dojo.forEach(lines, fn);
         } else {
          fn(lines);
         }
         return lines;
        },


        _split: function(chars, nLines){
         // summary:
         //  split passed chars into nLines by finding the closest whitespace.
         var w = this._getWidth(chars),
          limit = Math.floor(w/nLines),
          lines = [],
          cw = 0,
          c = [],
          found = false;


         for(var i=0, l=chars.length; i    if(chars[i].code == " "){ found = true; }
          cw += chars[i].xAdvance;
          if(i+1     cw += chars[i].kern[chars[i+1].code].x;
          }


          if(cw>=limit){
           var chr=chars[i];
           while(found && chr.code != " " && i>=0){
            chr = c.pop(); i--;
           }
           lines.push(c);
           c=[];
           cw=0;
           found=false;
          }
          c.push(chars[i]);
         }
         if(c.length){ lines.push(c); }
         // "trim" it
         return this._trim(lines);
        },


        _getSizeFactor: function(size){
         // given the size, return a scaling factor based on the height of the
         // font as defined in the font definition file.
         size += ""; // force the string cast.
         var metrics = dojox.html.metrics.getCachedFontMeasurements(),
          height=this.viewbox.height,
          f=metrics["1em"],
          unit=parseFloat(size, 10); // the default.
         if(size.indexOf("em")>-1){
          return this._round((metrics["1em"]*unit)/height);
         }
         else if(size.indexOf("ex")>-1){
          return this._round((metrics["1ex"]*unit)/height);
         }
         else if(size.indexOf("pt")>-1){
          return this._round(((metrics["12pt"] / 12)*unit) / height);
         }
         else if(size.indexOf("px")>-1){
          return this._round(((metrics["16px"] / 16)*unit) / height);
         }
         else if(size.indexOf("%")>-1){
          return this._round((metrics["1em"]*(unit / 100)) / height);
         }
         else {
          f=metrics[size]||metrics.medium;
          return this._round(f/height);
         }
    • returns
      undefined|String|dojox.gfx.VectorFont|Boolean
    • summary
  • dojox.gfx.VectorFont._getFitFactor

    • type
      Function
    • parameters:
      • lines: (typeof )
      • w: (typeof )
      • h: (typeof )
      • l: (typeof )
    • source: [view]
         if(!h){
          // if no height was passed, we assume an array of glyphs instead of lines.
          return this._round(w/this._getWidth(lines));
         } else {
          var maxw = this._getLongestLine(lines).width,
           maxh = (lines.length*(this.viewbox.height*l))-((this.viewbox.height*l)-this.viewbox.height);
          return this._round(Math.min(w/maxw, h/maxh));
         }
    • summary
      Find the scaling factor for the given phrase set.
  • dojox.gfx.VectorFont._getBestFit

    • type
      Function
    • parameters:
      • chars: (typeof )
      • w: (typeof )
      • h: (typeof )
      • ldng: (typeof )
    • source: [view]
         var limit=32,
          factor=0,
          lines=limit;
         while(limit>0){
          var f=this._getFitFactor(this._split(chars, limit), w, h, ldng);
          if(f>factor){
           factor = f;
           lines=limit;
          }
          limit--;
         }
         return { scale: factor, lines: this._split(chars, lines) };
    • summary
      Get the best number of lines to return given w and h.
  • dojox.gfx.VectorFont._getBestFlow

    • type
      Function
    • parameters:
      • chars: (typeof )
      • w: (typeof )
      • scale: (typeof )
    • source: [view]
         var lines = [],
          cw = 0,
          c = [],
          found = false;
         for(var i=0, l=chars.length; i    if(chars[i].code == " "){ found = true; }
          var tw = chars[i].xAdvance;
          if(i+1     tw += chars[i].kern[chars[i+1].code].x;
          }
          cw += scale*tw;


          if(cw>=w){
           var chr=chars[i];
           while(found && chr.code != " " && i>=0){
            chr = c.pop(); i--;
           }
           lines.push(c);
           c=[];
           cw=0;
           found=false;
          }
          c.push(chars[i]);
         }
         if(c.length){ lines.push(c); }
         return this._trim(lines);
    • summary
      Based on the given scale, do the best line splitting possible.
  • dojox.gfx.VectorFont.getWidth

    • type
      Function
    • parameters:
      • text: (typeof String)
      • scale: (typeof Float)
    • source: [view]
         return this._getWidth(dojo.map(this._normalize(text).split(""), function(chr){
          return this.glyphs[chr] || { xAdvance: this.advance.missing.x };
         }, this)) * (scale || 1); // Float
    • summary
      Get the width of the rendered text without actually rendering it.
  • dojox.gfx.VectorFont.getLineHeight

    • type
      Function
    • parameters:
      • scale: (typeof Float)
    • source: [view]
         return this.viewbox.height * (scale || 1); // Float
    • summary
      return the height of a single line, sans leading, based on scale.
    • returns
      Float
  • dojox.gfx.VectorFont.getCenterline

    • type
      Function
    • parameters:
      • scale: (typeof Float)
    • source: [view]
         return (scale||1) * (this.viewbox.height/2);
    • summary
      return the y coordinate that is the center of the viewbox.
  • dojox.gfx.VectorFont.getBaseline

    • type
      Function
    • parameters:
      • scale: (typeof Float)
    • source: [view]
         return (scale||1) * (this.viewbox.height+this.descent); // Float
    • summary
      Find the baseline coord for alignment; adjust for scale if passed.
    • returns
      Float
  • dojox.gfx.VectorFont.draw

    • type
      Function
    • parameters:
      • group: (typeof dojox.gfx.Container)
      • textArgs: (typeof dojox.gfx.__TextArgs)
      • fontArgs: (typeof dojox.gfx.__FontArgs)
      • fillArgs: (typeof dojox.gfx.__FillArgs)
      • strokeArgs: (typeof dojox.gfx.__StrokeArgs)
    • source: [view]
         if(!this.initialized()){
          throw new Error("dojox.gfx.VectorFont.draw(): we have not been initialized yet.");
         }
         // TODO: BIDI handling. Deal with layout/alignments based on font parameters.


         // start by creating the overall group. This is the INNER group (the caller
         // should be the outer).
         var g = group.createGroup();


         // do the x/y translation on the parent group
         // FIXME: this is probably not the best way of doing this.
         if(textArgs.x || textArgs.y){
          group.applyTransform({ dx: textArgs.x||0, dy: textArgs.y||0 });
         }


         // go get the glyph array.
         var text = dojo.map(this._normalize(textArgs.text).split(""), function(chr){
          return this.glyphs[chr] || { path:null, xAdvance: this.advance.missing.x };
         }, this);


         // determine the font style info, ignore decoration.
         var size = fontArgs.size,
          fitting = textArgs.fitting,
          width = textArgs.width,
          height = textArgs.height,
          align = textArgs.align,
          leading = textArgs.leading||this._defaultLeading;


         // figure out if we have to do fitting at all.
         if(fitting){
          // more than zero.
          if((fitting==dojox.gfx.vectorFontFitting.FLOW && !width) || (fitting==dojox.gfx.vectorFontFitting.FIT && (!width || !height))){
           // reset the fitting if we don't have everything we need.
           fitting = dojox.gfx.vectorFontFitting.NONE;
          }
         }


         // set up the lines array and the scaling factor.
         var lines, scale;
         switch(fitting){
          case dojox.gfx.vectorFontFitting.FIT:
           var o=this._getBestFit(text, width, height, leading);
           scale = o.scale;
           lines = o.lines;
           break;


          case dojox.gfx.vectorFontFitting.FLOW:
           scale = this._getSizeFactor(size);
           lines = this._getBestFlow(text, width, scale);
           break;


          default:
           scale = this._getSizeFactor(size);
           lines = [ text ];


         }


         // make sure lines doesn't have any empty lines.
         lines = dojo.filter(lines, function(item){
          return item.length>0;
         });


         // let's start drawing.
         var cy = 0,
          maxw = this._getLongestLine(lines).width;


         for(var i=0, l=lines.length; i    var cx = 0,
           line=lines[i],
           linew = this._getWidth(line),
           lg=g.createGroup();


          // loop through the glyphs and add them to the line group (lg)
          for (var j=0; j     var glyph=line[j];
           if(glyph.path!==null){
            var p = lg.createPath(glyph.path).setFill(fillArgs);
            if(strokeArgs){ p.setStroke(strokeArgs); }
            p.setTransform([
             dojox.gfx.matrix.flipY,
             dojox.gfx.matrix.translate(cx, -this.viewbox.height-this.descent)
            ]);
           }
           cx += glyph.xAdvance;
           if(j+1      cx += glyph.kern[line[j+1].code].x;
           }
          }


          // transform the line group.
          var dx = 0;
          if(align=="middle"){ dx = maxw/2 - linew/2; }
          else if(align=="end"){ dx = maxw - linew; }
          lg.setTransform({ dx: dx, dy: cy });
          cy += this.viewbox.height * leading;
         }


         // scale the group
         g.setTransform(dojox.gfx.matrix.scale(scale));


         // return the overall group
         return g; // dojox.gfx.Group
    • summary
      based on the passed parameters, draw the given text using paths
      defined by this font.
    • description
      The main method of a VectorFont, draw() will take a text fragment
      and render it in a set of groups and paths based on the parameters
      passed.
      
      The basics of drawing text are simple enough: pass it your text as
      part of the textArgs object, pass size and family info as part of
      the fontArgs object, pass at least a color as the fillArgs object,
      and if you are looking to create an outline, pass the strokeArgs
      object as well. fillArgs and strokeArgs are the same as any other
      gfx fill and stroke arguments; they are simply applied to any path
      object generated by this method.
      
      Resulting GFX structure
      -----------------------
      
      The result of this function is a set of gfx objects in the following
      structure:
      
      	dojox.gfx.Group 			//	the parent group generated by this function
      	+	dojox.gfx.Group[]		//	a group generated for each line of text
      		+	dojox.gfx.Path[]	//	each glyph/character in the text
      
      Scaling transformations (i.e. making the generated text the correct size)
      are always applied to the parent Group that is generated (i.e. the top
      node in the above example).  In theory, if you are looking to do any kind
      of other transformations (such as a translation), you should apply it to
      the group reference you pass to this method.  If you find that you need
      to apply transformations to the group that is returned by this method,
      you will need to reapply the scaling transformation as the *last* transform,
      like so:
      
      	textGroup.setTransform(new dojox.gfx.Matrix2D([
      		dojox.gfx.matrix.translate({ dx: dx, dy: dy }),
      		textGroup.getTransform()
      	]));
      
      In general, this should never be necessary unless you are doing advanced
      placement of your text.
      
      Advanced Layout Functionality
      -----------------------------
      
      In addition to straight text fragments, draw() supports a few advanced
      operations not normally available with vector graphics:
      
      * Flow operations (i.e. wrap to a given width)
      * Fitting operations (i.e. find a best fit to a given rectangle)
      
      To enable either, pass a `fitting` property along with the textArgs object.
      The possible values are contained in the dojox.gfx.vectorFontFitting enum
      (NONE, FLOW, FIT).
      
      `Flow fitting`
      Flow fitting requires both a passed size (in the fontArgs object) and a
      width (passed with the textArgs object).  draw() will attempt to split the
      passed text up into lines, at the closest whitespace according to the
      passed width.  If a width is missing, it will revert to NONE.
      
      `Best fit fitting`
      Doing a "best fit" means taking the passed text, and finding the largest
      size and line breaks so that it is the closest fit possible.  With best
      fit, any size arguments are ignored; if a height is missing, it will revert
      to NONE.
      
      Other notes
      -----------
      
      `a11y`
      Since the results of this method are rendering using pure paths (think
      "convert to outlines" in Adobe Illustrator), any text rendered by this
      code is NOT considered a11y-friendly.  If a11y is a requirement, we
      suggest using other, more a11y-friendly methods.
      
      `Font sources`
      Always make sure that you are legally allowed to use any fonts that you
      convert to SVG format; we claim no responsibility for any licensing
      infractions that may be caused by the use of this code.
    • returns
      dojox.gfx.Group
  • dojox.gfx.VectorFont.onLoadBegin

    • type
      Function
    • parameters:
      • url: (typeof String)
    • source: [view]
      }
    • summary
  • dojox.gfx.VectorFont.onLoad

    • type
      Function
    • parameters:
      • font: (typeof dojox.gfx.VectorFont)
    • source: [view]
      }
    • summary
  • dojox.gfx.VectorFont._defaultLeading

    • summary
  • baseline

    • type
      Object
    • summary
  • gl.kern

    • type
      Object
    • summary
  • dojox.gfx.vectorFontFitting

    • type
      Object
    • summary
  • dojox.gfx.vectorFontFitting.NONE

    • summary
  • dojox.gfx.vectorFontFitting.FLOW

    • summary
  • dojox.gfx.vectorFontFitting.FIT

    • summary
  • dojox.gfx.defaultVectorText

    • type
      Object
    • summary
  • dojox.gfx.defaultVectorText.type

    • summary
  • dojox.gfx.defaultVectorText.text

    • summary
  • dojox.gfx.defaultVectorText.leading

    • summary
  • dojox.gfx.defaultVectorFont

    • type
      Object
    • summary
  • dojox.gfx.defaultVectorFont.type

    • summary
  • dojox.gfx._vectorFontCache

    • type
      Object
    • summary
  • dojox.gfx._svgFontCache

    • type
      Object
    • summary
  • dojox.gfx.getVectorFont

    • type
      Function
    • parameters:
      • url: (typeof String)
    • source: [view]
         if(dojox.gfx._vectorFontCache[url]){
          return dojox.gfx._vectorFontCache[url];
         }
         return new dojox.gfx.VectorFont(url);
    • summary
  • dojox.gfx.VectorText

    • type
      Object
    • summary
  • dojox.gfx

    • type
      Object
    • summary
  • dojox

    • type
      Object
    • summary