summaryrefslogtreecommitdiff
path: root/js/dojo/dojox/gfx/VectorText.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/dojo/dojox/gfx/VectorText.js')
-rw-r--r--js/dojo/dojox/gfx/VectorText.js787
1 files changed, 787 insertions, 0 deletions
diff --git a/js/dojo/dojox/gfx/VectorText.js b/js/dojo/dojox/gfx/VectorText.js
new file mode 100644
index 0000000..230b936
--- /dev/null
+++ b/js/dojo/dojox/gfx/VectorText.js
@@ -0,0 +1,787 @@
+//>>built
+define("dojox/gfx/VectorText", ["dojo/_base/lang","dojo/_base/declare","dojo/_base/array", "dojo/_base/loader" /* dojo._getText */,
+ "dojo/_base/xhr","./_base", "dojox/xml/DomParser", "dojox/html/metrics","./matrix"],
+ function (lang,declare,arr,loader,xhr,gfx,xmlDomParser,HtmlMetrics,Matrix){
+/*=====
+ gfx = dojox.gfx;
+ dojox.gfx.VectorText = {
+ // summary:
+ // 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.
+ };
+ =====*/
+ var _getText = function(url){
+ var result;
+ xhr.get({url:url, sync:true, load:function(text){ // Note synchronous!
+ result = text;
+ }});
+ return result;
+ };
+
+ lang.getObject("dojox.gfx.VectorText", true);
+ lang.mixin(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(gfx._vectorFontCache[url]){
+ return gfx._vectorFontCache[url];
+ }
+ return new gfx.VectorFont(url);
+ }
+ });
+
+ return declare("dojox.gfx.VectorFont", null, { // EARLY RETURN
+ _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 = gfx._svgFontCache[url]||xmlDomParser.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(gfx._vectorFontCache[name]){ return; }
+
+ // get any provided baseline alignment offsets.
+ arr.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 = { };
+ arr.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");
+ arr.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");
+ arr.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.
+ lang.mixin(this, {
+ family: family,
+ name: name,
+ style: style,
+ variant: variant,
+ weight: weight,
+ stretch: stretch,
+ range: range,
+ viewbox: { width: unitsPerEm, height: unitsPerEm },
+ origin: origin,
+ advance: lang.mixin(advance, {
+ missing:{ x: missing, y: missing }
+ }),
+ ascent: ascent,
+ descent: descent,
+ baseline: baseline,
+ glyphs: glyphs
+ });
+
+ // cache the parsed font
+ gfx._vectorFontCache[name] = this;
+ gfx._vectorFontCache[url] = this;
+ if(name!=family && !gfx._vectorFontCache[family]){
+ gfx._vectorFontCache[family] = this;
+ }
+
+ // cache the doc
+ if(!gfx._svgFontCache[url]){
+ gfx._svgFontCache[url]=doc;
+ }
+ },
+ _clean: function(){
+ // summary:
+ // Clean off all of the given mixin parameters.
+ var name = this.name, family = this.family;
+ arr.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(gfx._vectorFontCache[name]){
+ delete gfx._vectorFontCache[name];
+ }
+ if(gfx._vectorFontCache[family]){
+ delete 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(
+ gfx._svgFontCache[url.toString()]||_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;
+ arr.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;
+ arr.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(lang.isArray(lines[0])){
+ // more than one line.
+ arr.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<l; i++){
+ if(chars[i].code == " "){ found = true; }
+ cw += chars[i].xAdvance;
+ if(i+1<l && chars[i].kern && chars[i].kern[chars[i+1].code]){
+ 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 = HtmlMetrics.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);
+ }
+ },
+
+ _getFitFactor: function(lines, w, h, l){
+ // summary:
+ // Find the scaling factor for the given phrase set.
+ 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));
+ }
+ },
+ _getBestFit: function(chars, w, h, ldng){
+ // summary:
+ // Get the best number of lines to return given w and h.
+ 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) };
+ },
+
+ _getBestFlow: function(chars, w, scale){
+ // summary:
+ // Based on the given scale, do the best line splitting possible.
+ var lines = [],
+ cw = 0,
+ c = [],
+ found = false;
+ for(var i=0, l=chars.length; i<l; i++){
+ if(chars[i].code == " "){ found = true; }
+ var tw = chars[i].xAdvance;
+ if(i+1<l && chars[i].kern && chars[i].kern[chars[i+1].code]){
+ 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);
+ },
+
+ // public functions
+ getWidth: function(/* String */text, /* Float? */scale){
+ // summary:
+ // Get the width of the rendered text without actually rendering it.
+ return this._getWidth(arr.map(this._normalize(text).split(""), function(chr){
+ return this.glyphs[chr] || { xAdvance: this.advance.missing.x };
+ }, this)) * (scale || 1); // Float
+ },
+ getLineHeight: function(/* Float? */scale){
+ // summary:
+ // return the height of a single line, sans leading, based on scale.
+ return this.viewbox.height * (scale || 1); // Float
+ },
+
+ // A note:
+ // Many SVG exports do not include information such as x-height, caps-height
+ // and other coords that may help alignment. We can calc the baseline and
+ // we can get a mean line (i.e. center alignment) but that's about all, reliably.
+ getCenterline: function(/* Float? */scale){
+ // summary:
+ // return the y coordinate that is the center of the viewbox.
+ return (scale||1) * (this.viewbox.height/2);
+ },
+ getBaseline: function(/* Float? */scale){
+ // summary:
+ // Find the baseline coord for alignment; adjust for scale if passed.
+ return (scale||1) * (this.viewbox.height+this.descent); // Float
+ },
+
+ draw: function(/* dojox.gfx.Container */group, /* dojox.gfx.__TextArgs */textArgs, /* dojox.gfx.__FontArgs */fontArgs, /* dojox.gfx.__FillArgs */fillArgs, /* dojox.gfx.__StrokeArgs? */strokeArgs){
+ // 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.
+ 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 = arr.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==gfx.vectorFontFitting.FLOW && !width) || (fitting==gfx.vectorFontFitting.FIT && (!width || !height))){
+ // reset the fitting if we don't have everything we need.
+ fitting = gfx.vectorFontFitting.NONE;
+ }
+ }
+
+ // set up the lines array and the scaling factor.
+ var lines, scale;
+ switch(fitting){
+ case gfx.vectorFontFitting.FIT:
+ var o=this._getBestFit(text, width, height, leading);
+ scale = o.scale;
+ lines = o.lines;
+ break;
+
+ case 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 = arr.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<l; 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<line.length; j++){
+ var glyph=line[j];
+ if(glyph.path!==null){
+ var p = lg.createPath(glyph.path).setFill(fillArgs);
+ if(strokeArgs){ p.setStroke(strokeArgs); }
+ p.setTransform([
+ Matrix.flipY,
+ Matrix.translate(cx, -this.viewbox.height-this.descent)
+ ]);
+ }
+ cx += glyph.xAdvance;
+ if(j+1<line.length && glyph.kern && glyph.kern[line[j+1].code]){
+ 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(Matrix.scale(scale));
+
+ // return the overall group
+ return g; // dojox.gfx.Group
+ },
+
+ // events
+ onLoadBegin: function(/* String */url){ },
+ onLoad: function(/* dojox.gfx.VectorFont */font){ }
+ });
+
+ // TODO: dojox.gfx integration
+/*
+
+ // Inherit from Group but attach Text properties to it.
+ dojo.declare("dojox.gfx.VectorText", dojox.gfx.Group, {
+ constructor: function(rawNode){
+ dojox.gfx.Group._init.call(this);
+ this.fontStyle = null;
+ },
+
+ // private methods.
+ _setFont: function(){
+ // render this using the font code.
+ var f = this.fontStyle;
+ var font = dojox.gfx._vectorFontCache[f.family];
+ if(!font){
+ throw new Error("dojox.gfx.VectorText._setFont: the passed font family '" + f.family + "' was not found.");
+ }
+
+ // the actual rendering belongs to the font itself.
+ font.draw(this, this.shape, this.fontStyle, this.fillStyle, this.strokeStyle);
+ },
+
+ getFont: function(){ return this.fontStyle; },
+
+ // overridden public methods.
+ setShape: function(newShape){
+ dojox.gfx.Group.setShape.call(this);
+ this.shape = dojox.gfx.makeParameters(this.shape, newShape);
+ this.bbox = null;
+ this._setFont();
+ return this;
+ },
+
+ // if we've been drawing, we should have exactly one child, and that
+ // child will contain the real children.
+ setFill: function(fill){
+ this.fillStyle = fill;
+ if(this.children[0]){
+ dojo.forEach(this.children[0].children, function(group){
+ dojo.forEach(group.children, function(path){
+ path.setFill(fill);
+ });
+ }, this);
+ }
+ return this;
+ },
+ setStroke: function(stroke){
+ this.strokeStyle = stroke;
+ if(this.children[0]){
+ dojo.forEach(this.children[0].children, function(group){
+ dojo.forEach(group.children, function(path){
+ path.setStroke(stroke);
+ });
+ }, this);
+ }
+ return this;
+ },
+
+ setFont: function(newFont){
+ // this will do the real rendering.
+ this.fontStyle = typeof newFont == "string" ? dojox.gfx.splitFontString(newFont)
+ : dojox.gfx.makeParameters(dojox.gfx.defaultFont, newFont);
+ this._setFont();
+ return this;
+ },
+
+ getBoundingBox: function(){
+ return this.bbox;
+ }
+ });
+
+ // TODO: figure out how to add this to container objects!
+ dojox.gfx.shape.Creator.createVectorText = function(text){
+ return this.createObject(dojox.gfx.VectorText, text);
+ }
+*/
+});