778 lines
24 KiB
JavaScript
778 lines
24 KiB
JavaScript
|
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(r[1].slice(2), 16);
|
||
|
}
|
||
|
else if(!isNaN(parseInt(r[1].slice(1),10))){
|
||
|
tmp+=String.fromCharCode(r[1].slice(1));
|
||
|
}
|
||
|
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<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 = 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);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_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(dojo.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 = 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<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([
|
||
|
dojox.gfx.matrix.flipY,
|
||
|
dojox.gfx.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(dojox.gfx.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);
|
||
|
}
|
||
|
*/
|
||
|
})();
|