VectorText.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  1. define("dojox/gfx/VectorText", ["dojo/_base/lang","dojo/_base/declare","dojo/_base/array", "dojo/_base/loader" /* dojo._getText */,
  2. "dojo/_base/xhr","./_base", "dojox/xml/DomParser", "dojox/html/metrics","./matrix"],
  3. function (lang,declare,arr,loader,xhr,gfx,xmlDomParser,HtmlMetrics,Matrix){
  4. /*=====
  5. gfx = dojox.gfx;
  6. dojox.gfx.VectorText = {
  7. // summary:
  8. // An implementation of the SVG Font 1.1 spec, using dojox.gfx.
  9. //
  10. // Basic interface:
  11. // var f = new dojox.gfx.Font(url|string);
  12. // surface||group.createVectorText(text)
  13. // .setFill(fill)
  14. // .setStroke(stroke)
  15. // .setFont(fontStyleObject);
  16. //
  17. // The arguments passed to createVectorText are the same as you would
  18. // pass to surface||group.createText; the difference is that this
  19. // is entirely renderer-agnostic, and the return value is a subclass
  20. // of dojox.gfx.Group.
  21. //
  22. // Note also that the "defaultText" object is slightly different:
  23. // { type:"vectortext", x:0, y:0, width:null, height: null,
  24. // text: "", align: "start", decoration: "none" }
  25. //
  26. // ...as well as the "defaultVectorFont" object:
  27. // { type:"vectorfont", size:"10pt" }
  28. //
  29. // The reason for this should be obvious: most of the style for the font is defined
  30. // by the font object itself.
  31. //
  32. // Note that this will only render IF and WHEN you set the font.
  33. };
  34. =====*/
  35. var _getText = function(url){
  36. var result;
  37. xhr.get({url:url, sync:true, load:function(text){ // Note synchronous!
  38. result = text;
  39. }});
  40. return result;
  41. };
  42. lang.getObject("dojox.gfx.VectorText", true);
  43. lang.mixin(gfx, {
  44. vectorFontFitting: {
  45. NONE: 0, // render text according to passed size.
  46. FLOW: 1, // render text based on the passed width and size
  47. FIT: 2 // render text based on a passed viewbox.
  48. },
  49. defaultVectorText: {
  50. type:"vectortext", x:0, y:0, width: null, height: null,
  51. text: "", align: "start", decoration: "none", fitting: 0, // vectorFontFitting.NONE
  52. leading: 1.5 // in ems.
  53. },
  54. defaultVectorFont: {
  55. type:"vectorfont", size: "10pt", family: null
  56. },
  57. _vectorFontCache: {},
  58. _svgFontCache: {},
  59. getVectorFont: function(/* String */url){
  60. if(gfx._vectorFontCache[url]){
  61. return gfx._vectorFontCache[url];
  62. }
  63. return new gfx.VectorFont(url);
  64. }
  65. });
  66. return declare("dojox.gfx.VectorFont", null, { // EARLY RETURN
  67. _entityRe: /&(quot|apos|lt|gt|amp|#x[^;]+|#\d+);/g,
  68. _decodeEntitySequence: function(str){
  69. // unescape the unicode sequences
  70. // nothing to decode
  71. if(!str.match(this._entityRe)){ return; } // undefined
  72. var xmlEntityMap = {
  73. amp:"&", apos:"'", quot:'"', lt:"<", gt:">"
  74. };
  75. // we have at least one encoded entity.
  76. var r, tmp="";
  77. while((r=this._entityRe.exec(str))!==null){
  78. if(r[1].charAt(1)=="x"){
  79. tmp += String.fromCharCode(parseInt(r[1].slice(2), 16));
  80. }
  81. else if(!isNaN(parseInt(r[1].slice(1),10))){
  82. tmp += String.fromCharCode(parseInt(r[1].slice(1), 10));
  83. }
  84. else {
  85. tmp += xmlEntityMap[r[1]] || "";
  86. }
  87. }
  88. return tmp; // String
  89. },
  90. _parse: function(/* String */svg, /* String */url){
  91. // summary:
  92. // Take the loaded SVG Font definition file and convert the info
  93. // into things we can use. The SVG Font definition must follow
  94. // the SVG 1.1 Font specification.
  95. var doc = gfx._svgFontCache[url]||xmlDomParser.parse(svg);
  96. // font information
  97. var f = doc.documentElement.byName("font")[0], face = doc.documentElement.byName("font-face")[0];
  98. var unitsPerEm = parseFloat(face.getAttribute("units-per-em")||1000, 10);
  99. var advance = {
  100. x: parseFloat(f.getAttribute("horiz-adv-x"), 10),
  101. y: parseFloat(f.getAttribute("vert-adv-y")||0, 10)
  102. };
  103. if(!advance.y){
  104. advance.y = unitsPerEm;
  105. }
  106. var origin = {
  107. horiz: {
  108. x: parseFloat(f.getAttribute("horiz-origin-x")||0, 10),
  109. y: parseFloat(f.getAttribute("horiz-origin-y")||0, 10)
  110. },
  111. vert: {
  112. x: parseFloat(f.getAttribute("vert-origin-x")||0, 10),
  113. y: parseFloat(f.getAttribute("vert-origin-y")||0, 10)
  114. }
  115. };
  116. // face information
  117. var family = face.getAttribute("font-family"),
  118. style = face.getAttribute("font-style")||"all",
  119. variant = face.getAttribute("font-variant")||"normal",
  120. weight = face.getAttribute("font-weight")||"all",
  121. stretch = face.getAttribute("font-stretch")||"normal",
  122. // additional info, may not be needed
  123. range = face.getAttribute("unicode-range")||"U+0-10FFFF",
  124. panose = face.getAttribute("panose-1") || "0 0 0 0 0 0 0 0 0 0",
  125. capHeight = face.getAttribute("cap-height"),
  126. ascent = parseFloat(face.getAttribute("ascent")||(unitsPerEm-origin.vert.y), 10),
  127. descent = parseFloat(face.getAttribute("descent")||origin.vert.y, 10),
  128. baseline = {};
  129. // check for font-face-src/font-face-name
  130. var name = family;
  131. if(face.byName("font-face-name")[0]){
  132. name = face.byName("font-face-name")[0].getAttribute("name");
  133. }
  134. // see if this is cached already, and if so, forget the rest of the parsing.
  135. if(gfx._vectorFontCache[name]){ return; }
  136. // get any provided baseline alignment offsets.
  137. arr.forEach(["alphabetic", "ideographic", "mathematical", "hanging" ], function(attr){
  138. var a = face.getAttribute(attr);
  139. if(a !== null /* be explicit, might be 0 */){
  140. baseline[attr] = parseFloat(a, 10);
  141. }
  142. });
  143. /*
  144. // TODO: decoration hinting.
  145. var decoration = { };
  146. arr.forEach(["underline", "strikethrough", "overline"], function(type){
  147. if(face.getAttribute(type+"-position")!=null){
  148. decoration[type]={ };
  149. }
  150. });
  151. */
  152. // missing glyph info
  153. var missing = parseFloat(doc.documentElement.byName("missing-glyph")[0].getAttribute("horiz-adv-x")||advance.x, 10);
  154. // glyph information
  155. var glyphs = {}, glyphsByName={}, g=doc.documentElement.byName("glyph");
  156. arr.forEach(g, function(node){
  157. // we are going to assume the following:
  158. // 1) we have the unicode attribute
  159. // 2) we have the name attribute
  160. // 3) we have the horiz-adv-x and d attributes.
  161. var code = node.getAttribute("unicode"),
  162. name = node.getAttribute("glyph-name"),
  163. xAdv = parseFloat(node.getAttribute("horiz-adv-x")||advance.x, 10),
  164. path = node.getAttribute("d");
  165. // unescape the unicode sequences
  166. if(code.match(this._entityRe)){
  167. code = this._decodeEntitySequence(code);
  168. }
  169. // build our glyph objects
  170. var o = { code: code, name: name, xAdvance: xAdv, path: path };
  171. glyphs[code]=o;
  172. glyphsByName[name]=o;
  173. }, this);
  174. // now the fun part: look for kerning pairs.
  175. var hkern=doc.documentElement.byName("hkern");
  176. arr.forEach(hkern, function(node, i){
  177. var k = -parseInt(node.getAttribute("k"),10);
  178. // look for either a code or a name
  179. var u1=node.getAttribute("u1"),
  180. g1=node.getAttribute("g1"),
  181. u2=node.getAttribute("u2"),
  182. g2=node.getAttribute("g2"),
  183. gl;
  184. if(u1){
  185. // the first of the pair is a sequence of unicode characters.
  186. // TODO: deal with unicode ranges and mulitple characters.
  187. u1 = this._decodeEntitySequence(u1);
  188. if(glyphs[u1]){
  189. gl = glyphs[u1];
  190. }
  191. } else {
  192. // we are referring to a name.
  193. // TODO: deal with multiple names
  194. if(glyphsByName[g1]){
  195. gl = glyphsByName[g1];
  196. }
  197. }
  198. if(gl){
  199. if(!gl.kern){ gl.kern = {}; }
  200. if(u2){
  201. // see the notes above.
  202. u2 = this._decodeEntitySequence(u2);
  203. gl.kern[u2] = { x: k };
  204. } else {
  205. if(glyphsByName[g2]){
  206. gl.kern[glyphsByName[g2].code] = { x: k };
  207. }
  208. }
  209. }
  210. }, this);
  211. // pop the final definition in the font cache.
  212. lang.mixin(this, {
  213. family: family,
  214. name: name,
  215. style: style,
  216. variant: variant,
  217. weight: weight,
  218. stretch: stretch,
  219. range: range,
  220. viewbox: { width: unitsPerEm, height: unitsPerEm },
  221. origin: origin,
  222. advance: lang.mixin(advance, {
  223. missing:{ x: missing, y: missing }
  224. }),
  225. ascent: ascent,
  226. descent: descent,
  227. baseline: baseline,
  228. glyphs: glyphs
  229. });
  230. // cache the parsed font
  231. gfx._vectorFontCache[name] = this;
  232. gfx._vectorFontCache[url] = this;
  233. if(name!=family && !gfx._vectorFontCache[family]){
  234. gfx._vectorFontCache[family] = this;
  235. }
  236. // cache the doc
  237. if(!gfx._svgFontCache[url]){
  238. gfx._svgFontCache[url]=doc;
  239. }
  240. },
  241. _clean: function(){
  242. // summary:
  243. // Clean off all of the given mixin parameters.
  244. var name = this.name, family = this.family;
  245. arr.forEach(["family","name","style","variant",
  246. "weight","stretch","range","viewbox",
  247. "origin","advance","ascent","descent",
  248. "baseline","glyphs"], function(prop){
  249. try{ delete this[prop]; } catch(e) { }
  250. }, this);
  251. // try to pull out of the font cache.
  252. if(gfx._vectorFontCache[name]){
  253. delete gfx._vectorFontCache[name];
  254. }
  255. if(gfx._vectorFontCache[family]){
  256. delete gfx._vectorFontCache[family];
  257. }
  258. return this;
  259. },
  260. constructor: function(/* String|dojo._Url */url){
  261. // summary::
  262. // Create this font object based on the SVG Font definition at url.
  263. this._defaultLeading = 1.5;
  264. if(url!==undefined){
  265. this.load(url);
  266. }
  267. },
  268. load: function(/* String|dojo._Url */url){
  269. // summary::
  270. // Load the passed SVG and send it to the parser for parsing.
  271. this.onLoadBegin(url.toString());
  272. this._parse(
  273. gfx._svgFontCache[url.toString()]||_getText(url.toString()),
  274. url.toString()
  275. );
  276. this.onLoad(this);
  277. return this; // dojox.gfx.VectorFont
  278. },
  279. initialized: function(){
  280. // summary::
  281. // Return if we've loaded a font def, and the parsing was successful.
  282. return (this.glyphs!==null); // Boolean
  283. },
  284. // preset round to 3 places.
  285. _round: function(n){ return Math.round(1000*n)/1000; },
  286. _leading: function(unit){ return this.viewbox.height * (unit||this._defaultLeading); },
  287. _normalize: function(str){
  288. return str.replace(/\s+/g, String.fromCharCode(0x20));
  289. },
  290. _getWidth: function(glyphs){
  291. var w=0, last=0, lastGlyph=null;
  292. arr.forEach(glyphs, function(glyph, i){
  293. last=glyph.xAdvance;
  294. if(glyphs[i] && glyph.kern && glyph.kern[glyphs[i].code]){
  295. last += glyph.kern[glyphs[i].code].x;
  296. }
  297. w += last;
  298. lastGlyph = glyph;
  299. });
  300. // if the last glyph was a space, pull it off.
  301. if(lastGlyph && lastGlyph.code == " "){
  302. w -= lastGlyph.xAdvance;
  303. }
  304. return this._round(w/*-last*/);
  305. },
  306. _getLongestLine: function(lines){
  307. var maxw=0, idx=0;
  308. arr.forEach(lines, function(line, i){
  309. var max = Math.max(maxw, this._getWidth(line));
  310. if(max > maxw){
  311. maxw = max;
  312. idx=i;
  313. }
  314. }, this);
  315. return { width: maxw, index: idx, line: lines[idx] };
  316. },
  317. _trim: function(lines){
  318. var fn = function(arr){
  319. // check if the first or last character is a space and if so, remove it.
  320. if(!arr.length){ return; }
  321. if(arr[arr.length-1].code == " "){ arr.splice(arr.length-1, 1); }
  322. if(!arr.length){ return; }
  323. if(arr[0].code == " "){ arr.splice(0, 1); }
  324. };
  325. if(lang.isArray(lines[0])){
  326. // more than one line.
  327. arr.forEach(lines, fn);
  328. } else {
  329. fn(lines);
  330. }
  331. return lines;
  332. },
  333. _split: function(chars, nLines){
  334. // summary:
  335. // split passed chars into nLines by finding the closest whitespace.
  336. var w = this._getWidth(chars),
  337. limit = Math.floor(w/nLines),
  338. lines = [],
  339. cw = 0,
  340. c = [],
  341. found = false;
  342. for(var i=0, l=chars.length; i<l; i++){
  343. if(chars[i].code == " "){ found = true; }
  344. cw += chars[i].xAdvance;
  345. if(i+1<l && chars[i].kern && chars[i].kern[chars[i+1].code]){
  346. cw += chars[i].kern[chars[i+1].code].x;
  347. }
  348. if(cw>=limit){
  349. var chr=chars[i];
  350. while(found && chr.code != " " && i>=0){
  351. chr = c.pop(); i--;
  352. }
  353. lines.push(c);
  354. c=[];
  355. cw=0;
  356. found=false;
  357. }
  358. c.push(chars[i]);
  359. }
  360. if(c.length){ lines.push(c); }
  361. // "trim" it
  362. return this._trim(lines);
  363. },
  364. _getSizeFactor: function(size){
  365. // given the size, return a scaling factor based on the height of the
  366. // font as defined in the font definition file.
  367. size += ""; // force the string cast.
  368. var metrics = HtmlMetrics.getCachedFontMeasurements(),
  369. height=this.viewbox.height,
  370. f=metrics["1em"],
  371. unit=parseFloat(size, 10); // the default.
  372. if(size.indexOf("em")>-1){
  373. return this._round((metrics["1em"]*unit)/height);
  374. }
  375. else if(size.indexOf("ex")>-1){
  376. return this._round((metrics["1ex"]*unit)/height);
  377. }
  378. else if(size.indexOf("pt")>-1){
  379. return this._round(((metrics["12pt"] / 12)*unit) / height);
  380. }
  381. else if(size.indexOf("px")>-1){
  382. return this._round(((metrics["16px"] / 16)*unit) / height);
  383. }
  384. else if(size.indexOf("%")>-1){
  385. return this._round((metrics["1em"]*(unit / 100)) / height);
  386. }
  387. else {
  388. f=metrics[size]||metrics.medium;
  389. return this._round(f/height);
  390. }
  391. },
  392. _getFitFactor: function(lines, w, h, l){
  393. // summary:
  394. // Find the scaling factor for the given phrase set.
  395. if(!h){
  396. // if no height was passed, we assume an array of glyphs instead of lines.
  397. return this._round(w/this._getWidth(lines));
  398. } else {
  399. var maxw = this._getLongestLine(lines).width,
  400. maxh = (lines.length*(this.viewbox.height*l))-((this.viewbox.height*l)-this.viewbox.height);
  401. return this._round(Math.min(w/maxw, h/maxh));
  402. }
  403. },
  404. _getBestFit: function(chars, w, h, ldng){
  405. // summary:
  406. // Get the best number of lines to return given w and h.
  407. var limit=32,
  408. factor=0,
  409. lines=limit;
  410. while(limit>0){
  411. var f=this._getFitFactor(this._split(chars, limit), w, h, ldng);
  412. if(f>factor){
  413. factor = f;
  414. lines=limit;
  415. }
  416. limit--;
  417. }
  418. return { scale: factor, lines: this._split(chars, lines) };
  419. },
  420. _getBestFlow: function(chars, w, scale){
  421. // summary:
  422. // Based on the given scale, do the best line splitting possible.
  423. var lines = [],
  424. cw = 0,
  425. c = [],
  426. found = false;
  427. for(var i=0, l=chars.length; i<l; i++){
  428. if(chars[i].code == " "){ found = true; }
  429. var tw = chars[i].xAdvance;
  430. if(i+1<l && chars[i].kern && chars[i].kern[chars[i+1].code]){
  431. tw += chars[i].kern[chars[i+1].code].x;
  432. }
  433. cw += scale*tw;
  434. if(cw>=w){
  435. var chr=chars[i];
  436. while(found && chr.code != " " && i>=0){
  437. chr = c.pop(); i--;
  438. }
  439. lines.push(c);
  440. c=[];
  441. cw=0;
  442. found=false;
  443. }
  444. c.push(chars[i]);
  445. }
  446. if(c.length){ lines.push(c); }
  447. return this._trim(lines);
  448. },
  449. // public functions
  450. getWidth: function(/* String */text, /* Float? */scale){
  451. // summary:
  452. // Get the width of the rendered text without actually rendering it.
  453. return this._getWidth(arr.map(this._normalize(text).split(""), function(chr){
  454. return this.glyphs[chr] || { xAdvance: this.advance.missing.x };
  455. }, this)) * (scale || 1); // Float
  456. },
  457. getLineHeight: function(/* Float? */scale){
  458. // summary:
  459. // return the height of a single line, sans leading, based on scale.
  460. return this.viewbox.height * (scale || 1); // Float
  461. },
  462. // A note:
  463. // Many SVG exports do not include information such as x-height, caps-height
  464. // and other coords that may help alignment. We can calc the baseline and
  465. // we can get a mean line (i.e. center alignment) but that's about all, reliably.
  466. getCenterline: function(/* Float? */scale){
  467. // summary:
  468. // return the y coordinate that is the center of the viewbox.
  469. return (scale||1) * (this.viewbox.height/2);
  470. },
  471. getBaseline: function(/* Float? */scale){
  472. // summary:
  473. // Find the baseline coord for alignment; adjust for scale if passed.
  474. return (scale||1) * (this.viewbox.height+this.descent); // Float
  475. },
  476. draw: function(/* dojox.gfx.Container */group, /* dojox.gfx.__TextArgs */textArgs, /* dojox.gfx.__FontArgs */fontArgs, /* dojox.gfx.__FillArgs */fillArgs, /* dojox.gfx.__StrokeArgs? */strokeArgs){
  477. // summary:
  478. // based on the passed parameters, draw the given text using paths
  479. // defined by this font.
  480. //
  481. // description:
  482. // The main method of a VectorFont, draw() will take a text fragment
  483. // and render it in a set of groups and paths based on the parameters
  484. // passed.
  485. //
  486. // The basics of drawing text are simple enough: pass it your text as
  487. // part of the textArgs object, pass size and family info as part of
  488. // the fontArgs object, pass at least a color as the fillArgs object,
  489. // and if you are looking to create an outline, pass the strokeArgs
  490. // object as well. fillArgs and strokeArgs are the same as any other
  491. // gfx fill and stroke arguments; they are simply applied to any path
  492. // object generated by this method.
  493. //
  494. // Resulting GFX structure
  495. // -----------------------
  496. //
  497. // The result of this function is a set of gfx objects in the following
  498. // structure:
  499. //
  500. // | dojox.gfx.Group // the parent group generated by this function
  501. // | + dojox.gfx.Group[] // a group generated for each line of text
  502. // | + dojox.gfx.Path[] // each glyph/character in the text
  503. //
  504. // Scaling transformations (i.e. making the generated text the correct size)
  505. // are always applied to the parent Group that is generated (i.e. the top
  506. // node in the above example). In theory, if you are looking to do any kind
  507. // of other transformations (such as a translation), you should apply it to
  508. // the group reference you pass to this method. If you find that you need
  509. // to apply transformations to the group that is returned by this method,
  510. // you will need to reapply the scaling transformation as the *last* transform,
  511. // like so:
  512. //
  513. // | textGroup.setTransform(new dojox.gfx.Matrix2D([
  514. // | dojox.gfx.matrix.translate({ dx: dx, dy: dy }),
  515. // | textGroup.getTransform()
  516. // | ]));
  517. //
  518. // In general, this should never be necessary unless you are doing advanced
  519. // placement of your text.
  520. //
  521. // Advanced Layout Functionality
  522. // -----------------------------
  523. //
  524. // In addition to straight text fragments, draw() supports a few advanced
  525. // operations not normally available with vector graphics:
  526. //
  527. // * Flow operations (i.e. wrap to a given width)
  528. // * Fitting operations (i.e. find a best fit to a given rectangle)
  529. //
  530. // To enable either, pass a `fitting` property along with the textArgs object.
  531. // The possible values are contained in the dojox.gfx.vectorFontFitting enum
  532. // (NONE, FLOW, FIT).
  533. //
  534. // `Flow fitting`
  535. // Flow fitting requires both a passed size (in the fontArgs object) and a
  536. // width (passed with the textArgs object). draw() will attempt to split the
  537. // passed text up into lines, at the closest whitespace according to the
  538. // passed width. If a width is missing, it will revert to NONE.
  539. //
  540. // `Best fit fitting`
  541. // Doing a "best fit" means taking the passed text, and finding the largest
  542. // size and line breaks so that it is the closest fit possible. With best
  543. // fit, any size arguments are ignored; if a height is missing, it will revert
  544. // to NONE.
  545. //
  546. // Other notes
  547. // -----------
  548. //
  549. // `a11y`
  550. // Since the results of this method are rendering using pure paths (think
  551. // "convert to outlines" in Adobe Illustrator), any text rendered by this
  552. // code is NOT considered a11y-friendly. If a11y is a requirement, we
  553. // suggest using other, more a11y-friendly methods.
  554. //
  555. // `Font sources`
  556. // Always make sure that you are legally allowed to use any fonts that you
  557. // convert to SVG format; we claim no responsibility for any licensing
  558. // infractions that may be caused by the use of this code.
  559. if(!this.initialized()){
  560. throw new Error("dojox.gfx.VectorFont.draw(): we have not been initialized yet.");
  561. }
  562. // TODO: BIDI handling. Deal with layout/alignments based on font parameters.
  563. // start by creating the overall group. This is the INNER group (the caller
  564. // should be the outer).
  565. var g = group.createGroup();
  566. // do the x/y translation on the parent group
  567. // FIXME: this is probably not the best way of doing this.
  568. if(textArgs.x || textArgs.y){
  569. group.applyTransform({ dx: textArgs.x||0, dy: textArgs.y||0 });
  570. }
  571. // go get the glyph array.
  572. var text = arr.map(this._normalize(textArgs.text).split(""), function(chr){
  573. return this.glyphs[chr] || { path:null, xAdvance: this.advance.missing.x };
  574. }, this);
  575. // determine the font style info, ignore decoration.
  576. var size = fontArgs.size,
  577. fitting = textArgs.fitting,
  578. width = textArgs.width,
  579. height = textArgs.height,
  580. align = textArgs.align,
  581. leading = textArgs.leading||this._defaultLeading;
  582. // figure out if we have to do fitting at all.
  583. if(fitting){
  584. // more than zero.
  585. if((fitting==gfx.vectorFontFitting.FLOW && !width) || (fitting==gfx.vectorFontFitting.FIT && (!width || !height))){
  586. // reset the fitting if we don't have everything we need.
  587. fitting = gfx.vectorFontFitting.NONE;
  588. }
  589. }
  590. // set up the lines array and the scaling factor.
  591. var lines, scale;
  592. switch(fitting){
  593. case gfx.vectorFontFitting.FIT:
  594. var o=this._getBestFit(text, width, height, leading);
  595. scale = o.scale;
  596. lines = o.lines;
  597. break;
  598. case gfx.vectorFontFitting.FLOW:
  599. scale = this._getSizeFactor(size);
  600. lines = this._getBestFlow(text, width, scale);
  601. break;
  602. default:
  603. scale = this._getSizeFactor(size);
  604. lines = [ text ];
  605. }
  606. // make sure lines doesn't have any empty lines.
  607. lines = arr.filter(lines, function(item){
  608. return item.length>0;
  609. });
  610. // let's start drawing.
  611. var cy = 0,
  612. maxw = this._getLongestLine(lines).width;
  613. for(var i=0, l=lines.length; i<l; i++){
  614. var cx = 0,
  615. line=lines[i],
  616. linew = this._getWidth(line),
  617. lg=g.createGroup();
  618. // loop through the glyphs and add them to the line group (lg)
  619. for (var j=0; j<line.length; j++){
  620. var glyph=line[j];
  621. if(glyph.path!==null){
  622. var p = lg.createPath(glyph.path).setFill(fillArgs);
  623. if(strokeArgs){ p.setStroke(strokeArgs); }
  624. p.setTransform([
  625. Matrix.flipY,
  626. Matrix.translate(cx, -this.viewbox.height-this.descent)
  627. ]);
  628. }
  629. cx += glyph.xAdvance;
  630. if(j+1<line.length && glyph.kern && glyph.kern[line[j+1].code]){
  631. cx += glyph.kern[line[j+1].code].x;
  632. }
  633. }
  634. // transform the line group.
  635. var dx = 0;
  636. if(align=="middle"){ dx = maxw/2 - linew/2; }
  637. else if(align=="end"){ dx = maxw - linew; }
  638. lg.setTransform({ dx: dx, dy: cy });
  639. cy += this.viewbox.height * leading;
  640. }
  641. // scale the group
  642. g.setTransform(Matrix.scale(scale));
  643. // return the overall group
  644. return g; // dojox.gfx.Group
  645. },
  646. // events
  647. onLoadBegin: function(/* String */url){ },
  648. onLoad: function(/* dojox.gfx.VectorFont */font){ }
  649. });
  650. // TODO: dojox.gfx integration
  651. /*
  652. // Inherit from Group but attach Text properties to it.
  653. dojo.declare("dojox.gfx.VectorText", dojox.gfx.Group, {
  654. constructor: function(rawNode){
  655. dojox.gfx.Group._init.call(this);
  656. this.fontStyle = null;
  657. },
  658. // private methods.
  659. _setFont: function(){
  660. // render this using the font code.
  661. var f = this.fontStyle;
  662. var font = dojox.gfx._vectorFontCache[f.family];
  663. if(!font){
  664. throw new Error("dojox.gfx.VectorText._setFont: the passed font family '" + f.family + "' was not found.");
  665. }
  666. // the actual rendering belongs to the font itself.
  667. font.draw(this, this.shape, this.fontStyle, this.fillStyle, this.strokeStyle);
  668. },
  669. getFont: function(){ return this.fontStyle; },
  670. // overridden public methods.
  671. setShape: function(newShape){
  672. dojox.gfx.Group.setShape.call(this);
  673. this.shape = dojox.gfx.makeParameters(this.shape, newShape);
  674. this.bbox = null;
  675. this._setFont();
  676. return this;
  677. },
  678. // if we've been drawing, we should have exactly one child, and that
  679. // child will contain the real children.
  680. setFill: function(fill){
  681. this.fillStyle = fill;
  682. if(this.children[0]){
  683. dojo.forEach(this.children[0].children, function(group){
  684. dojo.forEach(group.children, function(path){
  685. path.setFill(fill);
  686. });
  687. }, this);
  688. }
  689. return this;
  690. },
  691. setStroke: function(stroke){
  692. this.strokeStyle = stroke;
  693. if(this.children[0]){
  694. dojo.forEach(this.children[0].children, function(group){
  695. dojo.forEach(group.children, function(path){
  696. path.setStroke(stroke);
  697. });
  698. }, this);
  699. }
  700. return this;
  701. },
  702. setFont: function(newFont){
  703. // this will do the real rendering.
  704. this.fontStyle = typeof newFont == "string" ? dojox.gfx.splitFontString(newFont)
  705. : dojox.gfx.makeParameters(dojox.gfx.defaultFont, newFont);
  706. this._setFont();
  707. return this;
  708. },
  709. getBoundingBox: function(){
  710. return this.bbox;
  711. }
  712. });
  713. // TODO: figure out how to add this to container objects!
  714. dojox.gfx.shape.Creator.createVectorText = function(text){
  715. return this.createObject(dojox.gfx.VectorText, text);
  716. }
  717. */
  718. });