How to make Graphviz use ~/.config/fontconfig/fonts.conf by default?

I would like Graphviz to use settings from ~/.config/fontconfig/fonts.conf by default, i.e. without my need to set the fontname attribute.

~/.config/fontconfig/fonts.conf is

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
<fontconfig>
    <match target="pattern">
        <test name="family">
            <string>serif</string>
        </test>
        <edit name="family" mode="assign" binding="strong">
            <string>EB Garamond</string>
            <!-- <string>Gentium</string> -->
        </edit>
    </match>
    <match target="pattern">
        <test name="family">
            <string>monospace</string>
        </test>
        <edit name="family" mode="assign" binding="strong">
            <string>Cascadia Code</string>
        </edit>
    </match>
</fontconfig>

and

echo 'graph { a }' | dot -Tpng -v 2>&1 | grep fontname

gives

fontname: "Times-Roman" resolved to: (ps:pango  DejaVu Serif, ) (PangoCairoFcFont) "DejaVu Serif, Book" /usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf

As you can see, this is not what is set in fonts.conf. Only when I set the fontname attribute like

echo 'graph { a [ fontname = "serif" ] }' | dot -Tpng -v 2>&1 | grep fontname

I get

fontname: "serif" resolved to: (PangoCairoFcFont) "EB Garamond, 08 Regular" /usr/share/fonts/opentype/ebgaramond/EBGaramond08-Regular.otf

Please try: dot -Nserif -Tpng ...

  • Does it use a different font?
  • Does it use the desired font?
  • Is it an acceptable solution?

Steve, I guess you mean -Nfontname=serif?

echo 'graph { a }' | dot -Nfontname=serif -Tpng -v 2>&1 | grep fontname

gives

fontname: "serif" resolved to: (PangoCairoFcFont) "EB Garamond, 08 Regular" /usr/share/fonts/opentype/ebgaramond/EBGaramond08-Regular.otf

which is the same as what I get for the solution with fontname defined for a node directly. But if possible, I’d like a solution where I wouldn’t need to specify the font at all when using Graphviz (neither in the graph, nor in the command). It would be nice if Graphviz had a configuration file I could put in my home directory, but I couldn’t find any mention of it.

Yes, it should have been dot -Nfontname=serif -Tpng ...
(by the way, the most recent release of Graphviz accepts dot -Afontname=serif -Tpng ... to set the default for nodes, edges, and graphs)

Unfortunately, there is no config file for Graphviz.
But you could define a shell alias like: alias dot=ā€˜dot -Afontname=serif’

1 Like

Thank you for the alias suggestion. I didn’t think about it, I’m not used to aliases. Two impediments come to my mind (not of Graphviz’s nature). One, I should make an alias for every Graphviz command (I might use any of them). Two, aliases should make my commands always less portable across different shells. If I make an alias for Bash, it won’t be used in Dash. And even if I managed to make it work for both, one day I might use another shell in which it wouldn’t work. Both these impediments are to overcome, but still.

But I wonder, why shouldn’t Graphviz use settings in ~/.config/fontconfig/fonts.conf by default? I don’t know fontconfig, is there any technical reason?

PS. When I think about it, setting fontname to serif should tell Graphviz to use a serif font. But mind we, there is selected a serif DejaVu version anyway. So it’s like Graphviz understand setting this attribute as an indication not exactly to use a serif font, but to read the configuration in fonts.conf. Would you agree?

Long, long, long ago, people on the internet said to try using ~/.fonts.conf instead

Overall, this seems like a fontconfig question; graphviz is just above it in the stack, but doesn’t have any knowledge of how fontconfig customization works.

Stephen, if you mean Graphviz would work with ~/.fonts.conf, then no, it doesn’t. I did cp ~/.config/fontconfig/fonts.conf ~/.fonts.conf and the output still contains ā€œDejaVuā€, not ā€œGaramondā€. (And just to note that I’m aware of it, freedesktop.org says here that it’s deprecated.)

And I’m not sure whether this is a fontconfig question. As I said, from what I can see, it is fontname, Graphviz’s attribute, that causes it to use the configuration in ~/.config/fontconfig/fonts.conf.

I’ve looked into this , and learned a few things.

Until running ā€œfc-match serifā€ showed the binding you expect (EB Garamond?), this doesn’t seem like a graphviz issue. (pango uses fontconfig to find fonts.)

One problem is to figure out where to put user config files for fontconfig. On this computer, a MacBook, a lot of fontconfig customization files live in /opt/homebrew/etc/fonts/conf.d Sadly, 50-user.conf shows that loading $HOME/.config/fontconfig/conf.d is commented out, but <include ignore_missing="yes" deprecated="yes">~/.fonts.conf</include> lives on, which you can verify pretty easily. For example, drop an invalid XML tag into the user config file - fc-match will complain. (One an always edit 50-user.conf to try to get ~/.config/fontconfig working if that’s important.)

Second problem is to figure out how to write an override for <famlly>serif</family>. I’m stumped. My initial attempts failed. No matter what I wrote in ~/.fonts,config on this computer all I can get is:

% fc-match serif
ćƒ’ćƒ©ć‚®ćƒŽäøøć‚“ ProN W4.ttc: "Hiragino Maru Gothic Pro" "W4"
%

At first that seems weird, but this definition is found in /opt/homebrew/etc/fonts/conf.d/65-nonlatin.conf.default so may be legit?

According to the README in /opt/homebrew/etc/fonts/conf.d my definitions should be read earlier and override the 65 prefix config, but I’m out of ideas here. I’ve tried about half a dozen ways of writing an override in ~/.fonts.config and none of them work.

I can override ā€œTimesā€ but not ā€œserifā€, Crazy.

fc-match serif

gives

EBGaramond08-Regular.otf: "EB Garamond" "08 Regular"

which is indeed what I expect. So does it suggest a problem on the Graphviz’s part or not, I’m not sure?

Just so we can compare, my /etc/fonts/conf.d/50-user.conf is

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
  <its:rules xmlns:its="http://www.w3.org/2005/11/its" version="1.0">
    <its:translateRule translate="no" selector="/fontconfig/*[not(self::description)]"/>
  </its:rules>

  <description>Load per-user customization files</description>
	<!--
	    Load per-user customization files where stored on XDG Base Directory
	    specification compliant places. it should be usually:
	      $HOME/.config/fontconfig/conf.d
	      $HOME/.config/fontconfig/fonts.conf
	-->
	<include ignore_missing="yes" prefix="xdg">fontconfig/conf.d</include>
	<include ignore_missing="yes" prefix="xdg">fontconfig/fonts.conf</include>
	<!-- the following elements will be removed in the future -->
	<include ignore_missing="yes" deprecated="yes">~/.fonts.conf.d</include>
	<include ignore_missing="yes" deprecated="yes">~/.fonts.conf</include>
</fontconfig>

Have you used the correct file? It should be ~/.fonts.conf, not ~/.fonts.config.

PS.

What file are you talking about?

Well, I’m really stumped.

The only other thing I can contribute is that graphviz might specify Times-Roman or Times, not Serif unless some graphviz driver rewrites/overrides. The code is the most reliable documentation for details at that level :frowning:

So did you succeed in overwriting the selection of the Hiragino font, or not?

Then, what does Graphviz specify, and where? I’m not accustomed to the code… (I might try to get! But it would certainly take time. The closest I can find is DEFAULT_FONTNAME Ā· Search Ā· GitLab)

One thing came to my mind to clear the picture, to do (in Bash)

for string in Times-Roman TimesRoman 'Times Roman'
do fc-match "$string"
done

It gives

NimbusRoman-Regular.otf: "Nimbus Roman" "Regular"
DejaVuSans.ttf: "DejaVu Sans" "Book"
DejaVuSans.ttf: "DejaVu Sans" "Book"

which is different both from fc-match serif (DejaVu), Graphviz without fontname (DejaVu), and Graphviz with fontname (EB Garamond). But really, I don’t know which one I should expect to be the closest match.

It’s not a good situation, because there’s been a lot of work by different people, some long retired or moved away, on font binding and rendering over the years. Also because graphviz is intended to work with multiple conflicting or overlapping back end graphics systems with the goal of 100% compatibility. In primordial times graphviz was Postscript fonts with built in font tables. Huge step forward with freetype, fontconfig, libgd. Patches to smooth over differences with Windows. Another huge step with cairo pango, probably with some extensions . Later, native MacOS quartz drivers. With some driver selections, font sizing is done in one system (e.g. cairo pango) and rendering in another system (e.g. native SVG renderer). Some of the relevant code, e.g. hardwiring of default fonts, is in graphviz/lib/common/const.h and thereabouts. Other code found in graphviz/plugin. It is dismaying to see how often font names and translations are wired into arbitrary code.

Here’s a useful piece of information: on this computer, when I run dot on digraph G { a } the API call to pango_font_description_from_string in libpango.dylib passes the string argument

ā€œTimes New Roman, REGULARā€

This probably comes from line 111 of pango/gvgetfontlist_pango.c

% git blame -L111,111 pango/gvgetfontlist_pango.c
a166e6ee3e (erg 2011-02-26 21:33:07 +0000 111) #define SER_2 ā€œTimes New Romanā€
%

That’s @erg, who still comments here once in a while.

It’s ~/.fonts.conf

Also I learned you can set FC_DEBUG to get more output, e.g. FC_DEBUG=1024 fc-match Serif shows what config files as being processed; FC_DEBUG=65535 seemed like it might have the potential to turn on a lot of messages. Well, er, we should have read this first: Ubuntu Manpage: fonts.conf - Font configuration files

1 Like

Thank you, I will address your posts later. For now I just wanted to correct a statement from my last post. Writing

(…) which is different both from fc-match serif (DejaVu), Graphviz without fontname (DejaVu), and Graphviz with fontname (EB Garamond)

I meant that only the first line (i.e. NimbusRoman-Regular.otf: "Nimbus Roman" "Regular") mentions a different font than in the quote. The two other lines of course mention the same font as in the quote (i.e. DejaVu).

OK, so it’s correct.

I’ll try it.

Good observation! I’ll read it.

In fontname | Graphviz we read,

If Graphviz was built using the fontconfig library, the latter library will be used to search for the font.

(By the way, why ā€œthe latterā€? There is no ā€œformerā€.)

How do I know whether I have built Graphviz using fontconfig?

Update. I ran

./configure 2>&1 | grep -i fontconfig

and

make 2>&1 | grep -i fontconfig

and

sudo make install 2>&1 | grep -i fontconfig

and only the last pipeline outputs anything, which is

libtool: install: ((…) gcc (…) -lfontconfig (…))

Can this be a proof?

OK, to be able to proceed with what I have found out, from now on I’m going to assume that it’s true that I have built Graphviz with fontconfig support. If you can, please tell me whether my test above can prove it, or if it can’t, how I should test it properly.

I read fonts-conf (to be fair, I did read it the past, but only partially). I don’t understand everything yet, so should there arise a need to make use of information it contains, I’ll probably need to read again relevant parts.

I think I’m starting to imagine how this could be a ā€œfontconfig questionā€ (that is, regardless of whether it’s true, that’s yet to verify). I still don’t know how Graphviz works in this regard, though, so please correct me if I’ll be wrong.

I haven’t found any direct information how Graphviz should behave having no fontname defined (if there’s none indeed, maybe it could be added somewhere?). I’ve found only two related passages. One is in Font FAQ | Graphviz, and reads

[u]nder fontconfig, fontnames are family names, which fontconfig matches to the closest font it finds.

I understand it to suggest that fontconfig receives fontname’s value as is (otherwise, I expect there a description of how it be changed).

The other passage is in fontname | Graphviz, and reads

(…) default: "Times-Roman"

I understand it to suggest that if fontname isn’t given a value by the user, it’s given a value of Times-Roman. That in turn should suggest that this attribute is used for something, and this ā€œsomethingā€ might be passing its value to fontconfig. Of course, under the hood there may be more happening to this value.

So assuming

  1. that

    echo 'graph { a }' | dot -Tpng -v 2>&1 | grep fontname
    

    should involve Graphviz’s passing the string Times-Roman to fontconfig,

  2. and that fontconfig should match it in the same way as with fc-match,

then

fc-match Times-Roman

should match DejaVu, that is, the same font as the pipeline in point 1 matches. That’s not the case, though. Instead (what I have already showed in an earlier post) it gives

NimbusRoman-Regular.otf: "Nimbus Roman" "Regular"

I’ll take a look into the code now, maybe I’ll find some clues (but rather not).

How do you get the API calls?

To summarize, the goal is now to find what exact pattern fontconfig receives from Graphviz if there is no fontname specified. (Maybe also what it receives when fontname is specified, just to verify.) Then, assuming this be expected from the Graphviz’s point of view, and the way it be matched be expected from the fontconfig’s point of view, I’ll just make a rule in my fonts.conf so that this pattern be treated the same as the pattern serif, and my problem will be solved (and I can try to help you, Stephen, if you are still confused about fontconfig). If either assumption turned out to be false, probably I’d want to delve into this.

Update. I’m looking at the code and starting to suspect Graphviz doesn’t call fontconfig directly. In Font FAQ | Graphviz we read that

[t]ext layout is performed by pango, which accepts text and computes a layout with metrics that determine node sizes

so maybe it’s Pango that does this instead? Graphviz give it text, Pango call fontconfig, then return mentioned ā€œlayoutā€. It would match the fact that you, Stephen, has pointed me to the function pango_font_description_from_string (and mean I have taken the longer route to get that)…

Right, looks like that’s proof. Of course, pango requires fontconfig.

It’s unfortunate some of the build documentation is from an earlier era when the core drivers (like graphviz native PostScript and SVG) were written so they could be self-contained, but probably for a long time we have not considered that or tested if that works; pango’s needed for internationalization, anyway.

The -v command line option shows some runtime messages related to font binding, too.

I tried just now, and agree that ~/.fonts.conf (or whatever is the path where fontconfig is running) seems to have no effect on the binding that fontconfig makes in graphviz. The following verbose message confirms this

fontname: ā€œTimes-Romanā€ resolved to: (ps:pango Times New Roman, REGULAR) (PangoCairoCoreTextFont) ā€œTimes New Roman, 14ā€

though fc-match Times-Roman on my computer is easily overridden to any other local font.

Sorry I haven’t been able to shed any light on this.