Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to pass flags to dvisvgm when using tikz engine? #2038

Closed
3 tasks done
andrewheiss opened this issue Aug 26, 2021 · 6 comments
Closed
3 tasks done

Add ability to pass flags to dvisvgm when using tikz engine? #2038

andrewheiss opened this issue Aug 26, 2021 · 6 comments

Comments

@andrewheiss
Copy link
Contributor

Currently , if the tikz engine is used and fig.ext = "svg", dvisvgm is called to convert a DVI version of the TikZ chunk to SVG. The shell command for dvisvgm is hard-coded in the engine function:

if (system2('dvisvgm', c('-o', shQuote(fig2), fig)) != 0)

This makes it impossible to add any additional flags to the command. One situation where this might be useful is when embedding fonts. According to the first FAQ at dvisvgm's list, most web browsers and vector editors struggle with embedded fonts from TikZ output. This causes issues with tikz chunks too. For instance, here's a simple reprex:

tikz-example.Rmd:

---
title: "tikz font fun"
output:
  html_document: default
---

```{r setup, include=FALSE}
# Specify location of libgs.dylib on macOS with MacTeX-2021
Sys.setenv(LIBGS = "/usr/local/share/ghostscript/9.53.3/lib/libgs.dylib.9.53")
```

```{tikz, test-dag, echo=FALSE, cache=TRUE, fig.ext="svg"}
\usetikzlibrary{positioning}
\begin{tikzpicture}[every node/.append style={draw, minimum size=0.5cm}]
\node[draw=none] (X) at (0,0) {$X_{it}$};
\node[draw=none] (Y) at (2,0) {$Y_{it}$};
\node[rectangle] (Z) at (1,1) {$Z$};
\path [-latex] (X) edge (Y);
\draw [-latex] (Z) edge (Y);
\draw [-latex] (Z) edge (X);
\end{tikzpicture}
```

That will knit to HTML and include an SVG file, but the resulting SVG will use placeholder fonts:

image

If you open the resulting SVG file in Illustrator or Inkscape, it will complain about missing fonts.

If you run dvisvgm on the cached DVI file from the terminal and include --no-fonts, text will be converted to outlines instead of embedded as text, and the resulting SVG will look 99% correct (the conversion to outlines isn't perfect, but it's close enough):

dvisvgm --no-fonts tikz-example_files/figure-html/test-dag-1.dvi

image

It might be helpful to allow users to add additional options to dvisvgm when using the tikz engine, or perhaps just have a logical flag for embedding fonts or not. Within the eng_tikz() function, converting the tikz chunk to PNG allows for additional options from options$engine.opts$convert.opts:

knitr/R/engine.R

Lines 311 to 315 in 55a2df9

} else {
# convert to the desired output-format using magick
if (ext != 'pdf') magick::image_write(do.call(magick::image_convert, c(
list(magick::image_read_pdf(fig), ext), options$engine.opts$convert.opts
)), fig2)

I wonder if a similar approach could be used for SVG output, so users could do something like engine.opts = list(embed_fonts = FALSE) or something similar.


By filing an issue to this repo, I promise that

  • I have fully read the issue guide at https://yihui.org/issue/.
  • I have provided the necessary information about my issue.
    • If I'm asking a question, I have already asked it on Stack Overflow or RStudio Community, waited for at least 24 hours, and included a link to my question there.
    • If I'm filing a bug report, I have included a minimal, self-contained, and reproducible example, and have also included xfun::session_info('knitr'). I have upgraded all my packages to their latest versions (e.g., R, RStudio, and R packages), and also tried the development version: remotes::install_github('yihui/knitr').
    • If I have posted the same issue elsewhere, I have also mentioned it in this issue.
  • I have learned the Github Markdown syntax, and formatted my issue correctly.

I understand that my issue may be closed if I don't fulfill my promises.

@andrewheiss
Copy link
Contributor Author

andrewheiss commented Aug 27, 2021

Or even better, the --font-format=woff option for dvisvgm will embed the fonts in the SVG file as base64-encoded web fonts, which makes the resulting SVG file work universally:

dvisvgm --font-format=woff tikz-example_files/figure-html/test-dag-1.dvi

First part of test-dag-1.svg:

<?xml version='1.0' encoding='UTF-8'?>
<!-- This file was generated by dvisvgm 2.11.1 -->
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='69.540777pt' height='39.78342pt' viewBox='-68.679588 -72.00005 69.540777 39.78342'>
<style type='text/css'>
<![CDATA[@font-face{font-family:cmmi7;src:url(data:application/x-font-woff;base64,d09GRgABAAAAAATMAA0AAAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAABMAAAABoAAAAcjlK29U9TLzIAAAFMAAAAQgAAAFZVmV5oY21hcAAAAZAAAABLAAABSgCUCCBjdnQgAAAB3AAAAAQAAAAEACECeWdhc3AAAAHgAAAACAAAAAj//
...

image

Somehow allowing for the --font-format option in if (system2('dvisvgm', c('-o', shQuote(fig2), fig)) != 0) would be fantastic and would make it so SVG-based tikz files would be fairly universally usable

@andrewheiss
Copy link
Contributor Author

As an attempted workaround to avoid making any changes to the core knitr code, I tried creating a function for use in fig.process that would take the DVI and rerun it through dvisvgm with the --font-format option, thus overwriting the original non-embedded SVG, but I can never get the dvi2svg() function to actually ever trigger when knitting. This could theoretically fix the issue:

```{r setup, include=FALSE}
# Specify location of libgs.dylib on macOS with MacTeX-2021
Sys.setenv(LIBGS = "/usr/local/share/ghostscript/9.53.3/lib/libgs.dylib.9.53")

dvi2svg <- function(path) {
  path_svg <- xfun::with_ext(path, "svg")
  path_dvi <- xfun::with_ext(path, "dvi")

  if (system2('dvisvgm', c('--font-format=woff', '-o', shQuote(path_svg), shQuote(path_dvi))) != 0)
      stop('Failed to convert ', path_dvi, ' to ', path_svg)
  path_svg
}
```

```{r, engine="tikz", test-dag, echo=FALSE, cache=TRUE, fig.ext="svg", dev="tikz", fig.process=dvi2svg}
\usetikzlibrary{positioning}
\begin{tikzpicture}[every node/.append style={draw, minimum size=0.5cm}]
\node[draw=none] (X) at (0,0) {$X_{it}$};
\node[draw=none] (Y) at (2,0) {$Y_{it}$};
\node[rectangle] (Z) at (1,1) {$Z$};
\path [-latex] (X) edge (Y);
\draw [-latex] (Z) edge (Y);
\draw [-latex] (Z) edge (X);
\end{tikzpicture}
```

@andrewheiss
Copy link
Contributor Author

Apologies for yet another comment! Here's a working, slightly hacky workaround/solution that uses a chunk hook to rerun dvisvgm on the DVI file that knitr creates. It's roundabout, but it works. It still might be worth adding the ability to include --font-format=woff or --no-fonts in the system2() command.

tikz-example.Rmd:

---
title: "tikz font fun"
output:
  html_document: default
---

```{r setup, include=FALSE}
# Specify location of libgs.dylib on macOS with MacTeX-2021
Sys.setenv(LIBGS = "/usr/local/share/ghostscript/9.53.3/lib/libgs.dylib.9.53")

embed_svg_fonts <- function(before, options, envir) {
  if (!before) {
    paths <- knitr:::get_plot_files()

    knitr:::in_base_dir(
      lapply(paths, function(x) {
        message("Embedding fonts in ", x)
        path_svg <- xfun::with_ext(x, "svg")
        path_dvi <- xfun::with_ext(x, "dvi")
        
        if (system2('dvisvgm', c('--font-format=woff', '-o', shQuote(path_svg), shQuote(path_dvi))) != 0)
          stop('Failed to convert ', path_dvi, ' to ', path_svg)
      })
    )
  }
}

knitr::knit_hooks$set(embed_svg_fonts = embed_svg_fonts)
```

```{tikz, test-dag, echo=FALSE, fig.ext="svg", embed_svg_fonts = TRUE}
\usetikzlibrary{positioning}
\begin{tikzpicture}[every node/.append style={draw, minimum size=0.5cm}]
\node[draw=none] (X) at (0,0) {$X_{it}$};
\node[draw=none] (Y) at (2,0) {$Y_{it}$};
\node[rectangle] (Z) at (1,1) {$Z$};
\path [-latex] (X) edge (Y);
\draw [-latex] (Z) edge (Y);
\draw [-latex] (Z) edge (X);
\end{tikzpicture}
```

@yihui
Copy link
Owner

yihui commented Aug 27, 2021

Please feel free to submit a PR. It appears simple enough to implement---perhaps add something like options$engine.opts$dvisvgm.opts here? Thanks!

if (system2('dvisvgm', c('-o', shQuote(fig2), fig)) != 0)

andrewheiss added a commit to andrewheiss/knitr that referenced this issue Aug 28, 2021
@andrewheiss
Copy link
Contributor Author

Whoa, that was a super easy fix. PR #2039 fixes this issue and now this chunk will automatically embed fonts without needing the knit hook workaround above:

```{tikz example, fig.ext="svg", engine.opts=list(dvisvgm.opts = "--font-format=woff")}
\usetikzlibrary{positioning}
\begin{tikzpicture}
\node (X) at (0,0) {$X_{it}$};
\end{tikzpicture}
```

@yihui yihui closed this as completed in fad3ead Aug 28, 2021
@github-actions
Copy link

github-actions bot commented Mar 2, 2022

This old thread has been automatically locked. If you think you have found something related to this, please open a new issue by following the issue guide (https://yihui.org/issue/), and link to this old issue if necessary.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 2, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants