Skip to content

Commit

Permalink
inline SVG images instead of base64 encoding them, to make it possibl…
Browse files Browse the repository at this point in the history
…e to manipulate them via JS, and the file size will also be smaller (base64 encoding increases the file size by 1/3)
  • Loading branch information
yihui committed Jan 5, 2025
1 parent 5aa923b commit 9dc0b10
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 50 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: litedown
Type: Package
Title: A Lightweight Version of R Markdown
Version: 0.4.13
Version: 0.4.14
Authors@R: c(
person("Yihui", "Xie", role = c("aut", "cre"), email = "[email protected]", comment = c(ORCID = "0000-0003-0645-5666", URL = "https://yihui.org")),
person("Tim", "Taylor", role = "ctb", comment = c(ORCID = "0000-0002-8587-7113")),
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

- Added options `dollar`, `signif`, and `power` to format numbers from inline code. See https://yihui.org/litedown/#sec:inline-code for details.

- When embedding SVG images in HTML output, embed their raw XML content instead of base64 encoding them.

- Empty table headers are removed in HTML output (they may be generated from data frames or matrices without column names).

- Added support for the chunk option `collapse = TRUE` (thanks, @J-Moravec, #40).
Expand Down
43 changes: 33 additions & 10 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -949,23 +949,33 @@ embed_resources = function(x, options) {
if (!any(embed)) return(x)
clean = isTRUE(options[['embed_cleanup']])

r = '(<img[^>]* src="|<!--#[^>]*? style="background-image: url\\("?)([^"]+?)("|"?\\);)'
x = match_replace(x, r, function(z) {
# find images in <img> and (for slides only) comments
rs = c(
'(<img src=")([^"]+)("[^>]*?/>)',
'(<!--#[^>]*? style="background-image: url\\("?)([^"]+?)("?\\);)'
)
for (r in rs) x = match_replace(x, r, function(z) {
z1 = sub(r, '\\1', z)
z2 = sub(r, '\\2', z)
z3 = sub(r, '\\3', z)
# skip images already base64 encoded
for (i in grep('^data:.+;base64,.+', z2, invert = TRUE)) {
if (is_https(f <- z2[i])) {
if (embed[1]) z2[i] = download_cache$get(f, 'base64')
} else if (file_exists(f <- URLdecode(f))) {
z2[i] = base64_uri(f)
if (clean && normalize_path(f) %in% .env$plot_files) file.remove(f)
} else {
warning("File '", f, "' not found (hence cannot be embedded).")
is_svg = grepl('[.]svg$', f <- z2[i]) && grepl('^<img', z1[i])
a = if (is_svg) str_trim(gsub('^"|/>$', '', z3[i])) else ''
if (is_https(f)) {
if (embed[1]) z2[i] = if (!is_svg) download_cache$get(f, 'base64') else {
download_cache$get(f, 'text', function(xml) process_svg(xml, a))
}
} else if (embed[2]) {
if (file_exists(f <- URLdecode(f))) {
z2[i] = if (is_svg) process_svg(read_utf8(f), a) else base64_uri(f)
if (clean && normalize_path(f) %in% .env$plot_files) file.remove(f)
} else {
warning("File '", f, "' not found (hence cannot be embedded).")
}
}
}
paste0(z1, z2, z3)
ifelse(grepl('<svg', z2), z2, paste0(z1, z2, z3))
})

# CSS and JS
Expand Down Expand Up @@ -1001,6 +1011,19 @@ embed_resources = function(x, options) {
x
}

# remove the xml/doctype declaration in svg, and add attributes
process_svg = function(x, attr) {
while (length(x) > 0 && !grepl('^\\s*<svg .+', x[1])) x = x[-1]
if (length(x) > 0 && !attr %in% c('', 'alt=""')) {
x[1] = if (grepl(r <- '\\s*>\\s*$', x[1])) {
paste0(gsub(r, ' ', x[1]), attr, '>')
} else {
paste(x[1], attr)
}
}
one_string(x)
}

normalize_options = function(x, format = 'html') {
g = get_option('options', format)
x = option2list(x)
Expand Down
58 changes: 35 additions & 23 deletions docs/04-mark.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ below.

### `auto_identifiers`

Add automatic IDs to headings, e.g.,
Whether to add automatic IDs to headings (`true` by default), e.g., convert

``` markdown
# Hello world!

## Introduction
```

will be converted to
to

``` html
<h1 id="chp:hello-world">Hello world!</h1>
Expand All @@ -76,20 +76,22 @@ any ID is duplicated, a numeric suffix will be added to the ID, e.g.,

Whether to use the LaTeX package [**cleveref**](https://ctan.org/pkg/cleveref)
for "clever" cross-references (@sec:cross-references). This option is for LaTeX
output only. If enabled, **cleveref** will be loaded and references will use the
command `\cref{}` instead of `\ref{}`, which will automatically add the type of
reference before the reference number, e.g., `\cref{sec:intro}` may generate
`Section 1`, so you do not have to write `Section \ref{sec:intro}`.
output only and `false` by default. If enabled, **cleveref** will be loaded and
references will use the command `\cref{}` instead of `\ref{}`, which will
automatically add the type of reference before the reference number, e.g.,
`\cref{sec:intro}` may generate `Section 1`, so you do not have to write
`Section \ref{sec:intro}`.

### `embed_cleanup`

Whether to clean up plot files after they have been embedded in HTML output (see
@sec:embed-resources).
@sec:embed-resources). This option is `true` by default.

### `embed_resources`

Embed resources (images, CSS, and JS) in the HTML output using their
base64-encoded data (images) or raw content (CSS/JS). Possible values are:
base64-encoded data (all images except for SVG) or raw content (SVG/CSS/JS).
Possible values are:

- `null` or `false`: Do not embed any resources.

Expand All @@ -108,6 +110,13 @@ For `https` resources, after you have embedded them successfully once, they will
be cached locally (via `xfun::download_cache`) and will not require an Internet
connection again.

::: callout-note
All images are base64 encoded, except for SVG images, of which the raw XML
content is embedded, which makes it possible to manipulate SVG images via JS.
Without embedding the raw content, SVG images are included via the `<img>` tag,
and cannot be manipulated by JS.
:::

### `js_highlight`

Specify the JavaScript library to syntax highlight code blocks. Possible values
Expand Down Expand Up @@ -206,23 +215,24 @@ other's features.

### `keep_yaml`

Whether to keep the YAML metadata in the output. When `TRUE`, the original YAML
in the Markdown input (if exists) will be written to the output. Note that when
this option is enabled, templates (@sec:templates) will be disabled and `mark()`
will only generate a document fragment. This option was introduced mainly for
Hugo websites to use **litedown** instead of Hugo's Markdown engines to render
pages.
Whether to keep the YAML metadata in the output (`false` by default). When
`true`, the original YAML in the Markdown input (if exists) will be written to
the output. Note that when this option is enabled, templates (@sec:templates)
will be disabled and `mark()` will only generate a document fragment. This
option was introduced mainly for Hugo websites to use **litedown** instead of
Hugo's Markdown engines to render pages.

### `latex_math`

Whether to identify LaTeX math expressions in pairs of single (`$ $`) or double
dollar signs (`$$ $$`), and transform them so that they could be correctly
rendered by MathJax (HTML output) or LaTeX.
rendered by MathJax (HTML output) or LaTeX. This option is `true` by default.

### `number_sections`

Whether to number section headings. To skip numbering a specific heading, add a
class attribute `.unnumbered` (or use the shorthand `-`) to it. For example:
Whether to number section headings (`false` by default). To skip numbering a
specific heading, add a class attribute `.unnumbered` (or use the shorthand `-`)
to it. For example:

``` md
## Preface {.unnumbered}
Expand All @@ -233,23 +243,23 @@ class attribute `.unnumbered` (or use the shorthand `-`) to it. For example:
### `smartypants`

Whether to translate certain ASCII strings into smart typographic characters
(see `?litedown::smartypants`).
(see `?litedown::smartypants`). This option is `false` by default.

### `superscript`

Whether to translate strings between two carets into superscripts, e.g.,
`text^foo^` to `text<sup>foo</sup>`.
`text^foo^` to `text<sup>foo</sup>`. This option is `true` by default.

### `subscript`

Whether to translate strings between two tildes into subscripts, e.g.,
`text~foo~` to `text<sub>foo</sub>`.
`text~foo~` to `text<sub>foo</sub>`. This option is `true` by default.

### `toc`

Whether to generate a table of contents (TOC) from section headings. If a
heading has an `id` attribute, the corresponding TOC item will be a link to this
heading. You can also set a sub-option:
Whether to generate a table of contents (TOC) from section headings (`false` by
default). If a heading has an `id` attribute, the corresponding TOC item will be
a link to this heading. You can also set a sub-option:

- `depth`: The number of section levels to include in the TOC (`3` by
default). Setting `toc` to `true` is equivalent to:
Expand All @@ -273,6 +283,8 @@ The desired type of the top-level headings in LaTeX output. Possible values are
will be rendered to `\chapter{heading}` instead of the default
`\section{heading}`.

### Other options

Options not described above can be found on the help pages of **commonmark**,
e.g., the `hardbreaks` option is for the `hardbreaks` argument of
`commonmark::markdown_*()` functions, and the `table` option is for the `table`
Expand Down
38 changes: 22 additions & 16 deletions docs/05-assets.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -119,31 +119,37 @@ js: ["@copy-button"]
```

By default, this will add copy buttons to all code blocks (`<code>` in `<pre>`)
and all elements with the class name `copy-this`. For example, below is a quote
inside a fenced Div `::: copy-this` and you will see a copy button if you hover
over it with the cursor:

::: {.side .side-right}
Love like you've never been hurt\
Dance like nobody's watching\
Sing like nobody's listening\
Work like you don't need the money\
Live like it's heaven on earth

---Alfred D'Souza
:::
and all elements with the class name `copy-this`. For example, below are two
quotes, and the right quote is inside a fenced Div `::: copy-this`, so you will
see a copy button in the right quote if you hover over it with the cursor:

:::: flex-col
> Love like you've never been hurt\
> Dance like nobody's watching\
> Sing like nobody's listening\
> Work like you don't need the money\
> Live like it's heaven on earth
>
> ---Alfred D'Souza

::: copy-this
> Copy like you understand the code...
> Copy like you've never been hurt\
> Copy like nobody's watching\
> Copy like nobody's listening\
> Copy like you don't need the money\
> Copy like it's heaven on earth
>
> ---Yihui Xie
> ---Yihui (Ode to the [Copy
> Ninja](https://en.wikipedia.org/wiki/Kakashi_Hatake))
:::
::::

All code blocks in this book also have copy buttons in them.

In fact, you can add the copy button to any element on an HTML page. You may
read [this post](https://yihui.org/en/2023/09/copy-button/) if you are
interested in generalizing the copy button to other elements.
interested in generalizing the copy button to other elements (without using the
`copy-this` class).

## Tabsets {#sec:tabsets}

Expand Down

0 comments on commit 9dc0b10

Please sign in to comment.