source: Dev/branches/rest-dojo-ui/client/dojox/gfx/VectorText.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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