I just noticed this, after posting another message here.
Soon after reading a graph file, Graphviz conditions the input by applying various default settings to attributes that weren’t already set. These values are hardwired.
The theory or philosophy behind some of this design was, for a given graph, graphviz using any driver on any platform should yield the same result (or as close to it as possible). I intended for this to work at least for standard Postscript fonts. Over the years, with multiple people at different places working on the code, this may not still be true, but that was the intent at first.
I was able to see the arg passed to pango_font_description_from_string by
lldb dot
run it once to load all the libraries so the following symbol can be resolved
b pango_font_description_from_string
run it again and inspect the stack
Maybe someday graphviz/plugin/pango/gvtextlayout_pango.c should emit useful verbose/debugging messages. As an aside, I noticed that it calls fprintf stderr (which we would probably avoid these days; there is a dedicated API in graphviz to deliver warnings) , and there doesn’t seem to be any other warning/error/console output.
a test to check that fontconfig is being used (you should see lots of fontconfig debug messages): export FC_DEBUG=4;dot -v myfile.gv -Tpng -omyfile.png 2>&1|less
On my system, pango (or something else?) seems to be replacing Times-Roman with DejaVu Serif - before handing that off to fontconfig. (No idea why).
Here is a version of ~/.config/fontconfig/fonts.conf that actually fires. It looks for familycontains (matches) DejaVu Serif and replaces it with family = Symbol. Far from optimal, but it works - at least on my system.
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
<fontconfig>
<!-- does not fire -->
<match target="pattern">
<test name="family" compare="contains">
<string>Times</string>
</test>
<edit name="family" mode="assign_replace" binding="strong">
<string>Symbol</string>
</edit>
</match>
<!-- does fire -->
<match target="pattern">
<test name="family" compare="contains">
<string>DejaVu Serif</string>
</test>
<edit name="family" mode="assign_replace" binding="strong">
<string>Symbol</string>
</edit>
</match>
</fontconfig>
*But even if someone can figure out how to stop pango from making a hash of things, a fonts.conf that translates every occurrance of Times Roman to XXX will probably bollux up many other applications.
I have it already planned to inspect the relationship of Pango and fontconfig, but good to know!
I’m not sure what exact documentation you mean, but I guess you’re talking of the way Graphviz could render fonts without Pango and fontconfig. Maybe one day, someday, I could delve into this topic. If you mean that that documentation lacks something, I believe it be good to update it.
Only one line, that starting with fontname:, right?
Like the default value for fontname, i.e. Times-Roman?
Seems reasonable, yes.
I didn’t know that tool, thank you!
Steve,
Yes, I tried various FC_DEBUG values, but for some reason I didn’t think of it as a proof. Maybe it would be good to have ./configure output under graphviz-14.0.1 will be compiled with the following: lines like
libraries:
fontconfig: Yes
(changing Yes to No if fontconfig be not used), and maybe consider other libraries that are also not being listed currently.
In general, to be fair, I’m not used to build applications myself. But when I think about it, it would be good to have ./configure return at least a list of all the things that may be not used under some circumstances (and fontconfig may be not used, according to the documentation). How does that sound?
Be it Pango or not, that’s a useful observation! All the more I see we have a “font supply chain” to inspect here, i.e. Graphviz → Pango → fontconfig (let’s suppose now it looks like that). And as for “why” you get such result, that’s surely a TODO.
What do you mean by “fires”? Is it the output of fc-match that is expected, or the output of Graphviz? For me, when I replace my fonts.conf with the one you showed, both
So it looks in all three cases it’s the same “Standard Symbols PS”. So which one is different than for you?
That’s true, that be a trade-off if one want only Graphviz to have its font translated. But fontconfig seems to be flexible. Maybe such translation be not that much of a concern if one know good enough what they can do with fontconfig?
fontconfig is effectively a rule-based Expert system (older AI technology that preceeded neural nets). fires is an old term from my days as a developer of Expert Systems.
The second “rule” - DejaVu Serif replaced by Symbol - “fires” when using Graphviz, even though the 1st “rule” - Times replaced by Symbol - does not.
The 1st rule does “fire” for fc-match.
It was a mistake to use Symbol for both rules. It would be more useful to have used different font families.
I agree that some kind of config file wouuld be a nice addition to Graphviz.
OK, thanks, I wasn’t clear. By “what ‘fires’ mean” I meant “how you check that it fires”. “Fires” is clear for me here. But it might be just because I’m not used to see this word in any other context, and this in turn becase I’m not a native English speaker.
But what is this behavior to show?
I thought that this thread could benefit from having a clearer picture of it all, so I made, naturally, a graph.
I didn’t now where to put double quotes, and where to omit them, so I decided to put them only when I get them in the output (I don’t think they matter, but still it’s good to be precise).
as for the default fontname case, if Graphviz indeed passes to Pango Times-Roman, then the font I get from Graphviz ("DejaVu Serif, Book") is different than that when I run fc-match Times-Roman ("Nimbus Roman" "Regular"),
as for the fontname = serif case, I get the same font as when I run fc-match serif ("EB Garamond, 08 Regular").
So it looks reasonable to assume, at least for now, that Graphviz is not involved in the problem, and to check Pango and fontconfig. If we assume fontconfig behave the same when called by Pango as when I run fc-match, then it’s likely that Pango converts Times-Roman to something. Then, fontconfig get this something and just return the font that it assume be best for it.
I’m especially curious why there is this additional string (ps:pango DejaVu Serif, ) in the output of Graphviz in the default fontname case. Compare once again that when I run
We could intercept calls from pango to fontconfig, if that helps.
It’s not likely the pango project will agree to change their semantics for us, so this may not be a good use of further time.
Once upon a time, we wanted fontconfig to indicate that a binding had essentially failed (though a fallback font was substituted) so we could inform the operator, “Hey, Silly-Handwriting is not available on this computer”, but we didn’t get that.
For me that’s okay. I’d like to check how things work it if only to know where the problem lies exactly. As for you (all), please don’t feel obligated to go the same direction I go in this debugging. I still don’t understand many things, so I may go astray at times. But I try. If you happen to have a better idea what to do, I’m always glad to know. But anyway I think it’s good to know all the details how Graphviz interacts with its plugins (maybe I’m the only one that doesn’t understand it here). Sometimes I think our discussion here can’t help me to find what I look for so much as it helps to gather my thoughts.
You “didn’t get”, meaning fontconfig’s maintainers didn’t agree to change the way it worked?
Some news, today I tried one thing. File
plugin/pango/gvgetfontlist_pango.c
contains line
#define SER_6 "DejaVu Serif"
and when I change it to
#define SER_6 "aaa"
then do
./configure && make && sudo make install
then the pipeline (I’ve simplified the earlier one)
In the first case (changed) there’s (ps:pango Liberation Serif, REGULAR), and in the second case (original) there’s (ps:pango DejaVu Serif, ) (by the way, notice the dangling comma, is there something missing?). I don’t know yet how to interpret it.
And also, would ./configure && make alone do, without sudo make install? I see there is the cmd/dot/.libs/dot binary in the Graphiviz source directory. Can I be sure it would always return the same results as when I just run dot? It’d make the process faster.
Update. I’ve updated the “default ‘fontname’ case” diagram from above with the information about SER_6:
I’m just making things up, but would look hard at pangofc-font.c or one of its friends to find the sweet spot where you could see this in a debugger. The goal would be to find actual values for the API calls marked ? in the diagram at the end of your message (where pango calls fontconfig).
For me that’s okay. I’d like to check how things work it if only to know where the problem lies exactly. As for you (all), please don’t feel obligated to go the same direction I go in this debugging. I still don’t understand many things, so I may go astray at times. But I try. If you happen to have a better idea what to do, I’m always glad to know. But anyway I think it’s good to know all the details how Graphviz interacts with its plugins (maybe I’m the only one that doesn’t understand it here). Sometimes I think our discussion here can’t help me to find what I look for so much as it helps to gather my thoughts.
You “didn’t get”, meaning fontconfig’s maintainers didn’t agree to change the way it worked?
That’s right.
Some news, today I tried one thing. File
plugin/pango/gvgetfontlist_pango.c
contains line
#define SER_6 "DejaVu Serif"
and when I change it to
#define SER_6 "aaa"
then do
./configure && make && sudo make install
then the pipeline (I’ve simplified the earlier one)
In the first case (changed) there’s (ps:pango Liberation Serif, REGULAR), and in the second case (original) there’s (ps:pango DejaVu Serif, ) (by the way, notice the dangling comma, is there something missing?). I don’t know yet how to interpret it.
And also, would ./configure && make alone do, without sudo make install? I see there is the cmd/dot/.libs/dot binary in the Graphiviz source directory. Can I be sure it would always return the same results as when I just run dot? It’d make the process faster.
Update. I’ve updated the “default ‘fontname’ case” diagram from above with the information about SER_6:
You might decompose the “graphviz” box into a left part (that is renderer-independent) and a right part that interfaces to a specific renderer, like cairopango or ligd/freetype or MacOS quartz. I think it’s the right part where Times-Roman gets converted to, er, something else like DejaVu Serif. (I wasn’t very involved in this code, and I’m a little surprised we are munging font names before they even get to a 3rd part font lookup module, but maybe this was done for the sake of better hoped-for device independence at the graphviz level?)
Thanks for this separation. I think I won’t show it on the diagram right away, but maybe later, and it anyway should help me understand the whole picture.
Now, let me go that “SER_6 route”. We’ll see what we’ll get.
# Not using "-w" for not to omit potentially useful occurences
# (I don't know C that good to be sure how exactly "-w"
# could influence the result).
grep -RIl SER_6
returns only 1 file, which is the aforementioned
plugin/pango/gvgetfontlist_pango.c
In this file, there are 6 occurences of SER_6. The first is its definition (line 115):
#define SER_6 "DejaVu Serif"
All other 5 occurences of it are its usages as array elements, in the definitions of 5 different arrays. I’m not copying the actual code here for brevity. The arrays are
returns again only 1 file, and the same as above. There, all of these variables are used as fields in initializations of the structure fontdef_t. These initializations are in turn used as array elements, to initialize the array gv_ps_fontdefs. The code is
There is returned again only 1 file, and the same as above. There, there are 6 occurences of the string gv_ps_fontdefs:
the first in its definition (starting from line 228),
the second in the definition of the size of the array (GV_FONT_LIST_SIZE, line 240),
the third in a comment (starting in line 248. It’s not related to the problem, but there are three slashes and \p, so I suppose this comment has something to do with Doxygen?),
the fourth in an argument to an fprintf call (line 302),
the fifth in an argument to another fprintf call (line 306),
the last, sixth, in line 350, which is
gv_ps_fontdef = gv_ps_fontdefs+j;
The only occurence for us to move further is the last. Let’s see what’s done by the part of the code that contains that line 350. I use the term “part” loosely here, here it is:
for (size_t j = 0; j < GV_FONT_LIST_SIZE; j++) {
/* get the Graphviz PS font information and create the
available font definition structs */
availfont_t *gv_afs = &gv_af_p.fonts[j];
gv_ps_fontdef = gv_ps_fontdefs+j;
(I notice that the body of this for loop isn’t indented. Is it a feature or a bug? I believe it would be more readable when indented. Is it a matter of mixing spaces and tabs to indent? I’ve a tab set to 4 spaces. Maybe if I had to 8, I’d see the indentation as intended.) Here, we iterate over said array, and, if I recall C correctly, copy the address of an element of gv_ps_fontdefs (this element, just to recall, is a structure) to gv_ps_fontdef.
gv_ps_fontdef is used in several places from there onward, but the only use that interests me is that involving the use of its element equiv (which holds a pointer to one of the arrays PS_BOOKMAN_E etc., each of which contains SER_6. Because, ultimately, we still want to track just the usage of SER_6). The use of this element is within this part of the code (the comment starts in line 365):
/* if a match is not found on the primary Graphviz font family,
search for a match on the equivalent font family names */
if (family_name.data == NULL) {
array_sz = gv_ps_fontdef->eq_sz;
for (k = 0; k < array_sz; k++) {
for (i = 0; i < n_families; i++) {
family = families[i];
name = pango_font_family_get_name(family);
if (strcasecmp(gv_ps_fontdef->equiv[k], name) == 0) {
family_name = strview(name, '\0');
availfaces = get_faces(family);
break;
}
}
if (family_name.data != NULL)
break;
}
}
Let me use gdb. I want to know if the if involving the use is entered to, so I make a breakpoint on the first line within it:
b plugin/pango/gvgetfontlist_pango.c:374
info locals shows (I’ve omitted uninteresting lines)
I can do c a couple of times more. I don’t know if the value of the variable gv_af_p is important, but just to have that information noted, the last time I do c, info locals shows that it is
As for strview, I see its definition in lib/util/strview.h. I don’t know what it’s doing, but I assume nothing that changes name significantly. As such, I conclude family_name be the same as name.
As for get_faces, it’s defined in our current file (starting in line 268). I’m not sure what it does either, but nothing to SER_6 (which is represented by name because they are equal in the if), so I think I might focus on the value that gets returned from strview, and which is assigned to family_name.
I guess that the only interesting use of family_name is in line 399, which is
gv_afs->fontname = strview_str(family_name);
As you can see, probably it’s here that gv_afs gets the values of two of its three fields (see the definition of its type in line 242). Then, there is the line
availfont_t *gv_afs = &gv_af_p.fonts[j];
which binds together gv_afs and gv_af_p. This is all chaotic for me so far (when I will exit this file?), but essentially it’s probably gv_af_p that I should be concerned of now. I think its name be read “[g]raph[v]iz [a]vailable [f]onts”… “[p]arameter”? It’s of type availfonts_t, which definition is (the comment starts in line 248)
/// list of available fonts
///
/// The i-th entry corresponds to the i-th entry from \p gv_ps_fontdefs. An
/// entry will have zeroed fields if the corresponding font is unavailable at
/// runtime.
typedef struct {
availfont_t fonts[GV_FONT_LIST_SIZE];
} availfonts_t;
The comment is not explicit about it, but it looks as if gv_af_p were a map either from AvantGarde etc. to DejaVu Sans etc., or the other way around. What may be significant for the rest of our journey, there is no Times-Roman value there, but there is similarly-looking Times.
gv_af_p has 20 occurences in our current file, but an interesting one is in line 410, where it’s returned from the function gv_get_ps_fontlist. I’ll go this direction.
grep -IRl gv_get_ps_fontlist
gives only one file, again the same. There, the function is called once, in line 502, which is
availfonts_t gv_af_p = gv_get_ps_fontlist(fontmap); // get the available installed fonts
and this same-named gv_af_p is then used as a function argument in line 507, which is
That dangling comma suggests to me that the value DejaVu Serif, here is what I see in the output of dot starting with fontname: (see my earlier post). But why is this value paired with the value Times-Roman? Well, probably it’s mapped, and now it is to find out where and why.
Let’s go back. In this function, gv_fmap’s fields are assigned values only in the aforementioned for loop. It looks like that:
/* add the Graphviz PS font name and available system font string to the array */
for (size_t j = 0; j < ps_fontnames_sz; j++) {
ps_alias = &postscript_alias[j];
gv_fmap[ps_alias->xfig_code].gv_ps_fontname = ps_alias->name;
gv_fmap[ps_alias->xfig_code].gv_font = gv_get_font(gv_af_p, ps_alias, &xb, &xb2);
}
This shows ps_alias->name’s value Times-Roman is mapped to gv_get_font(gv_af_p, ps_alias, &xb, &xb2) value DejaVu Serif, . ps_alias is &postscript_alias[j], and postscript_alias in turn is, line 257,
I’ve never seen such a use of the #include directive, but I think I can assume that it just causes the content of the file ps_font_equiv.h to be copied in this place directly.
I find that file doing
find -name ps_font_equiv.h
(Update November 2, 2025. To be precise, the path of this file is lib/common/ps_font_equiv.h.)
I open it, and what I see are likely font mappings. Remember, we are looking for the Times-Roman value. There seems to be one in line 44, which is
To be sure what is represents, I’d need to examine all the fields of this, supposedly, structure. I think this file might benefit from being added a comment at the beginning telling us what the file contains, and what are the fields to represent. Anyway, the value Times-Roman is paired with the constant TIMES, and that constant is defined starting from line 3 as
#if defined(_WIN32)
#define TIMES "Times New Roman"
#else
#define TIMES "Times"
#endif
Now, I guess on my system, Linux, _WIN32 shouldn’t be defined. To be sure, I tried to set breakpoints in that file, but they were not being reached (I yet need to learn how exactly debugging header files works in C). So I can’t be sure directly for now, but I can look at it from an indirect perspective. I can remember that gv_af_p contained a pairing between the value Times and the value DejaVu Serif.
So Graphviz is responsible for, I’m short of words… “dancing together” of the values Times, Times-Roman, DejaVu Serif (in my system. In another they may be different). For now I think I’ll just stop here. Later I’ll try to understand exactly what’s going on. One thing that’s good is that it’s mainly one file involved in the whole thing (plugin/pango/gvgetfontlist_pango.c).
PS. Just to note, it may be useful for future readers. Though at the start of this topic I was using whatever version of Graphviz I happened to have installed, I’m using version 14.0.1 in this post, and probably in most of my posts in this topic. This is to say, I’ve decided not to use GitLab directly, but to download the source and use its offline version, to be sure the code not change during my solving of the problem.
I’ve made another diagram, depicting the functions defined or declared in the file plugin/pango/gvgetfontlist_pango.c (I’d include also gvgetfontlist_pango.h if there were a file with this name somewhere, but there’s not).
that of all the functions declared or defined in the file plugin/pango/gvgetfontlist_pango.c, only the function get_font_mapping “interacts with” other files (thus tells the outside world what font has been chosen “in this file”),
that there’s only one file that function “interacts with”, i.e. plugin/pango/gvtextlayout_pango.c.
In plugin/pango/gvgetfontlist_pango.c:329 we see a comment to the gv_get_ps_fontlist function that reads
[t]his function creates an array of font definitions. Each entry corresponds to one of the Graphviz PS fonts. The font definitions contain the generic font name and a list of equivalent fonts that can be used in place of the PS font if the PS font is not available on the system
What are “Graphviz PS fonts”?
2
What do you mean by “Graphviz was Postscript fonts”? That Graphviz provided PostScript font files, or that it just used such files (if they were available)? What are “font tables”?
Graphviz recognized some (maybe all) of the 35 standard Type 1 fonts and had built in glyph width tables to estimate text label sizes. When John Ellson added graphics output through libgd and freetype, we started using them as an alternative.