Asciidoctor-pdf: Currently there can only be one location for fonts

Created on 4 Jan 2015  路  22Comments  路  Source: asciidoctor/asciidoctor-pdf

Currently there is a default location for fonts. If the attribute 'pdf-fontsdir' is set in a Gradle build, then the location searched is changed to the one specified and the default location is not used: there is a single fonts file location rather than being a search path of directories to look in. It would seem to be better for there to be a fonts search path and for there to be an attribute that can be set to add to the path. Leaving the current attribute pdf-fontsdir as a way of replacing would seem like a good thing, but perhaps it could be a sequence of directories to create a new path.

cf. http://discuss.asciidoctor.org/Font-file-location-td2638.html

enhancement

All 22 comments

I'll include the relevant part of my response I posted in the linked thread:

At the very least, the font directory should fall back to the directory of built-in fonts. In other words, the value of the pdf-fontsdir attribute should be appended to the built-in path instead of replacing it.

Even better world be to search the loadpath (which would include the classpath when running on the JVM). In that case, we may not even need pdf-fontsdir.

One thing is for sure. The current behavior makes little sense.

I don't know the way to search the loadpath for fonts.
We can use any font directories by absolute path in theme.yml as follows:

font:
  catalog:
     Mplus1mn:
       normal:  /Library/Fonts/mplus1mn-regular-ascii-conums.ttf
       bold:      /User/USER/Library/Fonts/mplus1mn-regular-ascii.ttf

But I have one question.
Why are variable references($value) in font_catalog never evaluated?
Although font_catalog values are Hash, variable references in them are never evaluated by
process_entry method and evaluate method in ThemeLoader.
If evaluate method is modified as follows, we can use absolute path in font_catalog more easily.

  def evaluate expr, vars
    case expr
    when ::String
      evaluate_math(expand_vars(expr, vars))
    when ::Array
      expr.map {|e| evaluate(e, vars) }
    when ::Hash # Added for Hash value
      expr.inject({}) do |h, (k,v) |
        h[k] = evaluate(v, vars)
        h
      end
    else
      expr
    end
  end

Then font_catalog in theme.yml:

font:
  dirs:
    system: /Library/Fonts
    user: /User/USER/Library/Fonts
  catalog:
     Mplus1mn:
       normal:  $font_dirs_system/mplus1mn-regular-ascii-conums.ttf
       bold:      $font_dirs_user/Fonts/mplus1mn-regular-ascii.ttf

If the implementation to search the loadpath takes a little time, can you modify the evaluete method?

I don't know the way to search the loadpath for fonts.

It's not possible yet, but we could consider using the hike gem for this requirement. Ruby Gems itself also provides APIs for finding stuff on the LOAD_PATH that we can consider using.

Why are variable references($value) in font_catalog never evaluated?

Just because this use case was never considered. I don't see any reason why we can't add it.

Actually, I really like the idea of a special magic variable that allows you to specify that the font path is installed on the system. Something like $SYSTEM_FONT_PATH. I'm open to other names.

Thanks your responses.

Actually, I really like the idea of a special magic variable that allows you to specify that the font path is > installed on the system. Something like $SYSTEM_FONT_PATH. I'm open to other names.

I like the idea too. I'm looking forward to the next improvement.

Aren't we reinventing the wheel here? fontconfig is specifically designed for font lookup in a system-defined directory. There is also seems to be a ruby binding as gem.

This seems much more promising to me than copying over your typefaces for every project again and again. Or maintain a system variable for every program using fonts.

Yes and no. I'm certainly open to allowing system-wide fonts to be used as an option to the catalog (Apache FOP has something similar). However, enabling that option makes Asciidoctor PDF non-portable. That will work for some people, but not others.

There's another major limitation of using system-wide fonts. Currently, fonts have to be prepared to work with Prawn. Specifically, they must be regenerated with the old-style kern table enabled. Otherwise, kerning will not work with Prawn (specifically ttfunk). (I still need to document this in the theming guide). I'd love to see this limitation lifted so that we can drop in any TTF font. Even better if Prawn could support OTF fonts.

I might consider using the fontconfig gem, if present, but I won't require it because it's a native gem and requires a system package. But what we might be able to do is either look in a known folder, like $HOME/.fonts or use fc-list to query for fonts based on a given pattern:

font:
  catalog:
    Noto Serif:
      normal: Noto Serif:style=Regular

I want to mention that one thing I don't like about Apache FOP is how magical font discovery is. I really want this to be transparent because it's something that's been very confusing in the past. That's one of the reasons I really like having the fonts relative to the theme. It's very clear what's going on.

I can see your portability argument. Being "optional" is certainly a valid conclusion, though I have no clue how good this can be integrated in your buildchain (on a ruby-level rescuing a LoadError?!).

If I get this correct, Apache FOP would establish a dependency on JRuby, because it uses the JVM or am I wrong?!

I neither see a golden path, but requiring users to always carry around TTF files in their project repositories is a no-go in many usecases; for example if licensing issues come up, because the font is not licensed with OFL, but the content itself gets published.

So yes, I agree. Being "optional" seems to be the best approach here.

(on a ruby-level rescuing a LoadError?!).

That's exactly how optional libraries are loaded. RubyGems does offer APIs for checking if a gem is there, but usually it isn't worth the bother.

If I get this correct, Apache FOP would establish a dependency on JRuby, because it uses the JVM or am I wrong?!

I was only referring to Apache FOP as a reference. I'm not talking about using it.

I neither see a golden path, but requiring users to always carry around TTF files in their project repositories is a no-go in many usecases; for example if licensing issues come up, because the font is not licensed with OFL, but the content itself gets published.

Exactly. That's the strong/valid argument for adding the possibility of using fonts loaded from somewhere else. I still think it's pretty reasonably just to have a folder that's outside of project. Auto-detection isn't strictly necessary. We could even look at environment variables. I definitely understand that a separation is needed for certain use cases.

Regardless, we have lots of avenues to explore here.

So there is no way to switch only one font? For example I want to use a different monospaced font. How can I do this without copying it into the gem directory?

The font doesn't have to be in the gem directory. It's just that all the fonts need to be in the same directory. That directory is set using the pdf-fontsdir attribute. See https://github.com/asciidoctor/asciidoctor-pdf/blob/master/docs/theming-guide.adoc#custom-fonts

Yes. But as I said, I want only one different font. For now, I have to copy Noto font to the folder set by pdf-fontsdir.

That's correct.

I finally worked out the design for this feature. Supporting multiple directories turned out to be quite easy. Drawing on the PATH environment variable as inspiration, the multiple directories to search can be specified using the value of the pdf-fontsdir attribute, where the entries are separated by the path separator (: for Unix, ; for Windows).

The harder part was figuring out how to still be able to reference the bundled fonts when using a custom fonts dir. I decided to recognize the token GEM_FONTS_DIR. For example, to search a custom path and then the bundled fonts dir, use the value path/to/custom/fonts:GEM_FONTS_DIR. Alternately, the GEM_FONTS_DIR token can be used as a speical path prefix in the theme file to make a reference to the fonts dir in the gem. For example: GEM_FONTS_DIR/mplus1p-regular-fallback.ttf.

While I do use pdf-fontsdir now, it would be greatly appreciated if I could just use any font that fc-list returns (without having to specify paths).

Asciidoctor PDF generally does not call out to system commands. However, you're free to invoke fc-list, grab the unique set of directories, combine them on ,, and pass them to the pdf-fontsdir attribute when invoking Asciidoctor PDF.

Though, keep in mind, most fonts are not ready to be used with Asciidoctor PDF. So it's questionable whether the system-wide fonts would work anyway. See https://github.com/asciidoctor/asciidoctor-pdf/blob/master/docs/theming-guide.adoc#preparing-a-custom-font

I just try to get this to work with the asciidoctor gradle plugin.
It seems that it does not split the list of folders when I specify "myfolder:GEM_FONTS_DIR" it always complains that it can't find myfolder:GEM_FONTS_DIR/somefont.ttf.
Any idea?

The valid delimiters are comma and semi-colon. Choose whichever one works for your platform / scenario (as sometimes semi-colon has special meaning). For examples, see https://github.com/asciidoctor/asciidoctor-pdf/blob/master/docs/theming-guide.adoc#custom-fonts.

Was this page helpful?
0 / 5 - 0 ratings