How to provide assets (image files) with a template so that they could eventually be overwritten?

Hello. I have question regarding templates and images.

I made this template. The code sits in this repo.

The template is a cover page and some parts of it are made up of images. A set of images is provided with the template (located in ./template/logos).

The package structure is:

.
├── lib.typ
├── template
│   ├── logos
│   │   ├── archi.png
│   │   ├── FNR.png
│   │   ├── FNRS-en.png
│   │   └── FNRS-fr.png
│   └── main.typ
└── typst.toml

When creating a new project from the template in the Typst app, the #show function in main.typ does not allow making use of images outside the template folder.

This is the working default:

#import "@preview/lumen:0.1.0": cover
#show: cover(
  logo: "template/logos/archi.png",
  [...]
  fund-logo: "template/logos/FNRS-fr.png",
)

But what I try to make possible is that the user can upload own images and use them in the #show function:

#import "@preview/lumen:0.1.0": cover
#show: cover(
  logo: "science.png",
  [...]
  fund-logo: "new-fund.png",
)

Basically I need some help to understand how assets and link to images work for templates.
Thanks !

Hi there,

I believe this PR contains the answer to your question. It’s not released yet but you can access it: packages/docs/resources.md at 07b827f8915838224b2ff9aee162cf0e0c86d092 · typst/packages · GitHub

The proper way to let the people using your template overwrite the file to use is to take content as an argument directly, not a string. For example, you should replace this:

#let cover-page(logo-path: "logo.png", title) = {
  image(logo-path)
  heading(title)
}

With something like:

#let cover-page(logo: image("logo.png"), title) = {
  logo
  heading(title)
}

EDIT: Also this:

This is true for basic usage, but for something more complex a closure hack by @ensko can be used:

#let cover(logo: none, doc) = {
  [Logo: #image(logo(), width: 69%)]
  doc
}

#import "@local/name:0.0.0": cover
#show: cover.with(logo: () => read("tiger.jpg", encoding: none))

You either have to mandate that users provide the closure or check the type and do stuff accordingly to it.

To make it slightly better, define an additional read function:

#let read-image = read.with(encoding: none)

#let cover(logo: none, doc) = {
  [Logo: #image(logo(), width: 69%)]
  doc
}

#import "@local/name:0.0.0": cover, read-image
#show: cover.with(logo: () => read-image("tiger.jpg"))

This is still not very complex, so here, if you just need to edit image settings, you can pass image.with("file"):

#show: cover.with(logo: image.with("tiger.jpg"))

#let cover(logo: none, doc) = {
  [Logo: #logo(width: 69%)]
  doc
}

If you want, for example (package structure does have 4 predefined logo file names), to override a directory full of assets, then I’m pretty sure logo: image => read("localdir/" + image) should work and is complex enough for the hack to be actually useful.

Thanks, that is exactly the explanation I was looking for !

I changed the image path strings with an image object each in this commit. I’ll stil have to make tests to see if it works as expected.

In Typst 0.15.0 the path type was added. It is resolved relative to where it is called (e.g. user code) and can also be passed to packages, allowing them to read the file.

You could also support values of different types and then use type(...) to determine their type and decide how to handle them. For example:

  • type str: treat it as one of your built-in logos, e.g. "template/logos/archi.png"

  • type path: load the image from the user-provided path

  • type content: assume it represents the result of an image(...) call and include it as is

But on the other hand that makes your template more complex and possibly more confusing to use.