Bullseye -- Hit the target (HTML or paged/PDF) when styling your Typst document

My new package Bullseye (Github) has just been merged! This package helps you write target-dependent Typst code, particularly target-dependent content and show rules. Here’s a quick example from the readme:

#import "@preview/bullseye:0.1.0": *

// apply styling only for non-HTML output
#show: show-target(paged: doc => {
  set page(height: auto)

  import "@preview/codly:1.3.0"
  show: codly.codly-init
  doc
})

// apply image styling only for html output
#show image: show-target(html: it => {
  html.elem("img", attrs: ("src": it.source, "alt": it.alt))
})

// insert content only for HTML output
#context on-target(html: {
  html.elem("a", attrs: (href: "#"))[(back to top)]
})

For comparison, this is what the image show rule would look like without Bullseye:

#show image: it => context {
  if target() != "html" { return it }
  html.elem("img", attrs: ("src": it.source, "alt": it.alt))
}

… except that this code doesn’t work out-of-the-box if the unstable HTML feature isn’t enabled, even though it shouldn’t (and wouldn’t) affect PDF output at all.

Bullseye gives you

  • convenience functions for producing target-specific styles and content
  • wrappers for target() and html that work without the feature flag – which means, you can write code using these features, but unless the target is HTML (and therefore the feature flag is enabled), no HTML elements may be actually displayed in the document.

Further details can be found in the manual.

As Bullseye uses unstable features, I naturally can’t guarantee that it will continue to work. In fact, I hope most of its functionality will eventually become redundant! But for now, I think it fills a need that I have felt a few times now when either targeting both HTML and PDF, or targeting HTML while previewing in the web app or using Tinymist’s default settings.

13 Likes

You mentioned this to me in some forum post or other—I’m finding myself needing to use it for your reasons as well! I’m compiling my blog with Typst, but since everything is wrapped in #html, nothing shows! So I needed to comment out my template to get things to show up, but bullseye does that for me conditionally :3

Oh whoops, just read the manual and realized a simpler approach is enough for me for now:

#let conf(
  page-title: "",
  date: "",
  doc,
) = context {
  if target() == "paged" {
    doc
    return
  }
  
  everything_else()
}

This looks like exactly what I want to use to help clean up this, thanks!

1 Like