How to access and SVG font glyphs.

1,293 views
Skip to first unread message

Thug

unread,
Jun 10, 2011, 1:05:58 PM6/10/11
to d3...@googlegroups.com
I'd like to display some open-source svg fonts, but quickly  found that - though nominally stored in svg files - these inevitably comprise one or more glyphs.

Addressing the svg file's font id is not enough - though I might have expected all the glyphs in the file to be shown, in fact nothing is displayed.  I appear to need to address the individual glyphs, which  form a sort of hash table.

I came across an old blog entry suggesting I may be able to address these using xpath, and wondered if anyone has experience or a D3 example in this context.

Alternatively, I thought I might be able to use the property selector, something along the lines of:

chart.selectAll("viper")
.data(viper).enter()
.append("svg:svg") 
.attr("class", "viper")
.append("svg:use")
.attr("xlink:href", "snakes.svg#snakes" )
                .property("glyph-name", "viper");

This has, however, no effect - presumably I'm not using property on the right selection.

Glad for any suggestions...
Thug

Mike Bostock

unread,
Jun 10, 2011, 1:14:26 PM6/10/11
to d3...@googlegroups.com
If I understand correctly, you're trying to display an individual
glyph from an SVG font file? Can you post a link to the SVG font file
that you're using?

Mike

Thug

unread,
Jun 10, 2011, 2:44:41 PM6/10/11
to d3...@googlegroups.com
Hi, Mike

Yes, it's the individual glyphs I'm trying to access.

I'm still looking for suitable fonts, but for the sake of argument, most seem to follow the pattern used in this example:


This is a good case in hand in as far as -following the link- the entire svg file content displays directly in the browser, but called from D3 as follows (with or without the property line) does not display:

chart = d3.select(".content")
    .append("svg:svg")
   .attr("class", "chart")
   .attr("width", "100%")
   .attr("height", 1000)
   .append("svg:g");
   
chart.selectAll("LED14")
.data("LED14").enter()
.append("svg:svg")
.attr("class", "LED14")
.append("svg:use")
.attr("xlink:href", "14SegmentFont.svg#LED14")
                                .property("unicode", "L")
.attr("x", 10 )
.attr("y", 10 );

Incidentally, another possible approach may be using Paul Irish's CSS @font-face technique, though descriptions I've found so far again don't say how to address the glyphs..

thanks
Thug

Rick Otten

unread,
Jun 10, 2011, 2:54:48 PM6/10/11
to d3...@googlegroups.com
Glyphs look interesting.

Can you transition between them?

(Unlike text, which you can only 'replace'.)


--
Rick Otten
rot...@windfish.net
O=='=+


Thug

unread,
Jun 13, 2011, 7:37:47 AM6/13/11
to d3...@googlegroups.com
It may be helpful if I expand a little on why I need access to individual SVG font glyphs.

First off, though, this is not just a cosmetics exercise - the glyphs would deliver concrete meaning to the application/user.

I have in mind a decidedly lightweight, reusable approach, such that
  • SVG fonts can be loaded analogously to any widely-used browser resource - think js or css libraries. (a must)
  • where desired, glyphs can be reused (a must). As an analogy, think of chinese or japanese glyphs, each of which can contribute meaning to several characters/words.
  • in as far as existing in-browser font selection mechanisms are not in conflict with the above, they could be exploited.
Though I continue to experiment here, hints on the following would be of help:
  • based on the example already posted, how to access individual SVG characters or (better) their component glyphs through D3, and/or
  • links to (prefereably graphical) overviews of existing in-browser font access architectures which might lend themselves to reuse with D3.
Thanks
Thug

Thug

unread,
Jun 14, 2011, 6:29:08 AM6/14/11
to d3...@googlegroups.com
A glyph is an SVG path, such as :

<glyph unicode="1"  d="M 568.96875 19.3125 L 485.5625 93.03125 L 485.90625 459.59375 L 572.6875 497.9375 L 572.25 19.3125 L 568.96875 19.3125 z M 572.6875 505.59375 L 485.90625 543.96875 L 485.5625 910.53125 L 568.96875 984.25 L 572.25 984.25 L 572.6875 505.59375 z "/>

I am able to glyph attributes by parsing the source SVG file as XML and using an XML DOM call, such as:

console.log( "unicode = " + this.getAttributeNode("unicode").nodeValue ); 

Nevertheless, I prefer to use a D3 if possible. From https://github.com/mbostock/d3/wiki/Selections#attr states that D3 uses CSS3 to select elements. For example, you can select by tag ("div"), class (".awesome"), unique identifier ("#foo"), attribute ("[color=red]"), or containment ("parent child").

As mentioned earlier, I had hoped I might use the same syntax used elsewhere with normal (non-font) SVG files:

chart.selectAll("LED14")
.data("LED14").enter()
.append("svg:svg")
.attr("class", "LED14")
.append("svg:use")
.attr("xlink:href", "14SegmentFont.svg#LED14");

Though class "LED14" is added to the HTML DOM, there is -as yet- neither character nor path (glyph) display. The syntax above suggests, though, that CSS selectors can also be used here, so I had hoped to directly access and display a glyph's path:

.attr("xlink:href", "14SegmentFont.svg[d]");

Barking up the wrong tree? Neither this, nor many other variations have worked. Admittedly I don't know my way around D3 well yet, but maybe XML parsing approach is the only way forward?

Thug


Mike Bostock

unread,
Jun 14, 2011, 12:04:24 PM6/14/11
to d3...@googlegroups.com
> .attr("xlink:href", "14SegmentFont.svg[d]");

You can't use selectors as the value of an attribute; selectors are
used in the select and selectAll methods. So, you can select the glyph
you want to inspect, and then use the attr method to retrieve the
value of the "d' attribute. For example:

d3.select("#foo").attr("d") // returns the string attribute value

It looks like you'd need to select the glyph based on the unicode
attribute. You could try doing something like this:

chart.selectAll("path")
.data("LED14".split(""))
.enter().append("svg:path")
.attr("d", function(d) { return d3.select("#font
glyph[unicode='" + d + "']").attr("d"); });

This assumes that you've embedded the font svg:svg element using the
id "font" elsewhere on the page.

(BTW, you were passing an invalid selector to selectAll. And, using a
string for the data is a bit magical, since it normally expects an
array.)

Mike

Thug

unread,
Jun 16, 2011, 4:24:44 PM6/16/11
to d3...@googlegroups.com
Hallo Mike

Thanks once more for your tips, which have brought a good step further. Using the approach below, I now have svg font glyphs (paths) displaying in my browser, but seem to have hit a confusing snag.

The following code cycles through a set of font files "sample_font_<xx>.svg" one at a time, outputting each contained set of glyph paths it finds to a dedicated, horizontal rectangular area (one per file). The rectangles are displayed correctly, but only the contents of the initial font file are displayed - and repeated verbatim in each subsequent rectangle.

With each source file containing a decidedly different font, by means of targetted console logging I've found that the innermost "this" (marked red below), though being updated by the containing ".each()" calls, is not advancing with the file accesses. I suspect a conflict between nested d3 function call "this" and the containing loop.

Does this behaviour ring any bells? If not, I'll try a different approach.

for (x = 0; x < file_nums.length; x++) {

SVG_from_file[x] = d3.xml("sample_font_" + file_nums[x] + ".svg", "image/svg+xml", function(svg){

chart[x] = d3.select(".content")
.append("svg:svg")
.attr("class", "chart")
.attr("width", "100%")
.attr("height", 100)
.append("svg:g");
   
var cumulative_horiz_adv_x = 50;

d3.selectAll(svg.getElementsByTagName("defs"))
.selectAll("font")
.each(function(d, i) {
 
d3.selectAll(this)
.selectAll("glyph")
.each(function(d, i) {
 
   // picking up each glyph - but ONLY from first font file
var glyph_name = this.getAttributeNode("glyph-name").nodeValue; 
var path = this.getAttributeNode("d").nodeValue;

console.log( "horiz-adv-x = " + this.getAttributeNode("horiz-adv-x").nodeValue );
chart[x].selectAll("path")
.data(glyph_name)
.enter().append("svg:path")
.attr("class", glyph_name)
.attr("d", path );
});
});
});
};

Thanks
Thug

Mike Bostock

unread,
Jun 16, 2011, 4:38:59 PM6/16/11
to d3...@googlegroups.com
Yep, this is a problem with closures and the asynchronous nature of
d3.xml. If we look at just the outer loop:

for (var i = 0; i < n; i++) {
array[i] = d3.xml("path/to/file.xml", function(svg) {
… some code that refers to `i` …
});
}

The problem is your callback function (the second argument to xml) is
called asynchronously. Typically, they'll be called after the loop
terminates. And so, the value of `i` (or `x` in your code) in the
callback function will usually be `n`, because that's the value of `i`
when the loop terminates. To fix this, you'll need to capture a
reference to the current value of `i` in your callback, rather than
referring to the global value. You can do that with closures:

for (var i = 0; i < n; i++) {
array[i] = d3.xml("path/to/file.xml", callback(i));
}

Where:

function callback(i) {
return function(svg) {
… some code that refers to `i` …
};
}

Now, `callback` is a function which generates your callback, capturing
a reference to the current value of `i`.

Mike

Thug

unread,
Jun 17, 2011, 6:15:48 AM6/17/11
to d3...@googlegroups.com
I came within a hairbreadth of a similar workaround yesterday, just didn't get it quite right. Excellent, thanks!
Thug

Thug

unread,
Jun 17, 2011, 7:50:25 AM6/17/11
to d3...@googlegroups.com
Mmm, spoke too soon. Even if I remove all reference to the index "i" from within callback() and function(svg), "this" is not advancing.

With file accesses incomplete at the time of callback() call and my outermost "this" dependent on mismatched "svg" objects, I tried combining your suggestion with storage of the svg object in a global array variable within function(svg):

svg_a[x]=svg;

font_a[x]=d3.selectAll(svg_a[x].getElementsByTagName("defs"))
.selectAll("font")
.each(function(d, i) {
....... 
}

No behavioural change, but in retrospect it makes sense. Tricky.

Thug 

Thug

unread,
Jun 17, 2011, 12:56:41 PM6/17/11
to d3...@googlegroups.com
Hi Mike

I read up on closures, but the svg object is also, as you say, being created after the loop terminates. However, I found some alternatives already posted for a similar context, under:


Perhaps I can get self-limiting recursive calls working. The serial approach is my last choice (clunky).
Thug.
Reply all
Reply to author
Forward
0 new messages