VectorText.js 24 KB

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