Universe

A utility package for typst package authors.

Tools for Typst (t4t in short) is a utility package for Typst package and template authors. It provides solutions to some recurring tasks in package development.

The package can be imported or any useful parts of it copied into a project. It is perfectly fine to treat t4t as a snippet collection and to pick and choose only some useful functions. For this reason, most functions are implemented without further dependencies.

Hopefully, this collection will grow over time with Typst to provide solutions for common problems.

Usage

Either import the package from the Typst preview repository:

#import "@preview/t4t:0.4.1": *

If only a few functions from t4t are needed, simply copy the necessary code to the beginning of the document.

Reference


Note: This reference might be out of date. Please refer to the manual for a complete overview of all functions.


The functions are categorized into different submodules that can be imported separately.

The modules are:

  • is
  • def
  • assert
  • alias
  • math
  • get

Any or all modules can be imported the usual way:

// Import as "t4t"
#import "@preview/t4t:0.4.1"
// Import all modules
#import "@preview/t4t:0.4.1": *
// Import specific modules
#import "@preview/t4t:0.4.1": is, def

In general, the main value is passed last to the utility functions. #def.if-none(), for example, takes the default value first and the value to test second. This is somewhat counterintuitive at first, but allows the use of .with() to generate derivative functions:

#let is-foo = eq.with("foo")

Test functions

#import "@preview/t4t:0.4.1": *
#import "@preview/t4t:0.4.1": test

These functions provide shortcuts to common tests like #is-eq(). Some of these are not shorter as writing pure Typst code (e.g. a == b), but can easily be used in .any() or .find() calls:

// check all values for none
if some-array.any(is-none) {
	...
}

// find first not none value
let x = (none, none, 5, none).find(not-none)

// find position of a value
let pos-bar = args.pos().position(test.is-eq.with("|"))

Some of the more frequently used tests are available as top-level imports from t4t. The rest can be found in the test module.

#import "@preview/t4t:0.4.1"

#if t4t.is-none(none) [
	it is none
]

The following tests are available in the base t4t module:

  • #is-none( ..values ): Tests if any of the passed values is none.

  • #not-none( ..values ): Tests if all of the passed values are not none.

  • #is-auto( ..values ): Tests if any of the passed values is auto.

  • #not-auto( ..values ): Tests if all of the passed values are not auto.

  • #is-empty( value ): Tests if value is empty. A value is considered empty if it is an empty array, dictionary or string or none otherwise.

  • #not-empty( value ): Tests if value is not empty.

  • #is-dict( value ): Tests if value is a dictionary.

  • #is-arr( value ): Tests if value is an array.

  • #is-content( value ): Tests if value is of type content.

  • #is-color( value ): Tests if value is a color.

  • #is-stroke( value ): Tests if value is a stroke.

  • #is-loc( value ): Tests if value is a location.

  • #is-bool( value ): Tests if value is a boolean.

  • #is-str( value ): Tests if value is a string.

  • #is-int( value ): Tests if value is an integer.

  • #is-float( value ): Tests if value is a float.

  • #is-num( value ): Tests if value is a numeric value (integer or float).

  • #is-frac( value ): Tests if value is a fraction.

  • #is-length( value ): Tests if value is a length.

  • #is-rlength( value ): Tests if value is a relative length.

  • #is-ratio( value ): Tests if value is a ratio.

  • #is-align( value ): Tests if value is an alignment.

  • #is-align2d( value ): Tests if value is a 2d alignment.

  • #is-func( value ): Tests if value is a function.

  • #test.neg( test ): Creates a new test function that is true when test is false. Can be used to create negations of tests like #let not-raw = is.neg(is.raw).

  • #test.eq( a, b ): Tests if values a and b are equal.

  • #test.neq( a, b ): Tests if values a and b are not equal.

  • #test.any( ..compare, value ): Tests if value is equal to any one of the other passed-in values.

  • #test.not-any( ..compare, value): Tests if value is not equal to any one of the other passed-in values.

  • #test.has( ..keys, value ): Tests if value contains all the passed keys. Either as keys in a dictionary or elements in an array. If value is neither of those types, false is returned.

  • #test.is-type( t, value ): Tests if value is of type t.

  • #test.is-elem( func, value ): Tests if value is a content element with value.func() == func. If func is a string, value will be compared to repr(value.func()) instead.

    Both of these effectively do the same:

    #test.is-elem(raw, some_content)
    #test.is-elem("raw", some_content)
    
  • #test.any-type( ..types, value ): Tests if value has any of the passed-in types.

  • #test.same-type( ..values ): Tests if all passed-in values have the same type.

  • #test.all-of-type( t, ..values ): Tests if all of the passed-in values have the type t.

  • #test.none-of-type( t, ..values ): Tests if none of the passed-in values has the type t.

  • #test.one-not-none( ..values ): Checks, if at least one value in values is not equal to none. Useful for checking multiple optional arguments for a valid value: #if is.one-not-none(..args.pos()) [ #args.pos().find(is.not-none) ]

  • #test.is-sequence( value ): Tests if value is a sequence of content.

  • #test.is-raw( value ): Tests if value is a raw element.

  • #test.is-table( value ): Tests if value is a table element.

  • #test.is-list( value ): Tests if value is a list element.

  • #test.is-enum( value ): Tests if value is an enum element.

  • #test.is-terms( value ): Tests if value is a terms element.

  • #test.is-cols( value ): Tests if value is a columns element.

  • #test.is-grid( value ): Tests if value is a grid element.

  • #test.is-stack( value ): Tests if value is a stack element.

  • #test.is-label( value ): Tests if value is of type label.

Default values

#import "@preview/t4t:0.4.1": def

These functions perform a test to decide if a given value is invalid. If the test passes, the default def is returned, value otherwise.

Almost all functions support an optional do argument to be set to a function of one argument that will be applied to the value if the test fails. For example:

// Sets date to a datetime from an optional
// string argument in the format "YYYY-MM-DD"
#let date = def.if-none(
	passed_date, 		// passed-in argument
	def: datetime.today(), 	// default
	do: (d) >= {		// post-processor
		d = d.split("-")
		datetime(year=d[0], month=d[1], day=d[2])
	}
)

Note that previous versions of these functions got the default passed in as the first positional argument. Since Version 0.4.0 the default is now a named argument in favor of more readable code. To restore the old behaviour, you can import the def.compat module as def:

#import "@preview/t4t:0.4.1": def
#import def.compat as def
  • #def.if-true( value, test, def:none, do:none ): Returns def if test is true, value otherwise.
  • #def.if-false( value, test, def:none, do:none ): Returns def if test is false, value otherwise.
  • #def.if-none( value, def:none, do:none ): Returns def if value is none, value otherwise.
  • #def.if-auto( value, def:none, do:none ): Returns def if value is auto, value otherwise.
  • #def.if-any( value, ..compare, def:none, do:none ): Returns def if value is equal to any of the passed-in values, value otherwise. (#def.if-any(none, auto, 1pt, width))
  • #def.if-not-any( value, ..compare, def:none, do:none ): Returns def if value is not equal to any of the passed-in values, value otherwise. (#def.if-not-any(left, right, top, bottom, position))
  • #def.if-empty( def:none, do:none, value ): Returns def if value is empty, value otherwise.
  • #def.as-arr( ..values ): Always returns an array containing all values. Any arrays in values will be flattened into the result. This is useful for arguments, that can have one element or an array of elements: #def.as-arr(author).join(", ").

Assertions

#import "@preview/t4t:0.4.1": assert

This submodule overloads the default assert function and provides more asserts to quickly check if given values are valid. All functions use assert in the background.

Since a module in Typst is not callable, the assert function is now available as assert.that(). assert.eq and assert.ne work as expected.

All assert functions take an optional argument message to set the error message shown if the assert fails.

  • #assert.that( test ): Asserts that the passed test is true.

  • #assert.that-not( test ): Asserts that the passed test is false.

  • #assert.eq( a, b ): Asserts that a is equal to b.

  • #assert.ne( a, b ): Asserts that a is not equal to b.

  • #assert.neq( a, b ): Alias for assert.ne.

  • #assert.not-none( value ): Asserts that value is not equal to none.

  • #assert.any( ..values, value ): Asserts that value is one of the passed values.

  • #assert.not-any( ..values, value ): Asserts that value is not one of the passed values.

  • #assert.any-type( ..types, value ): Asserts that the type of value is one of the passed types.

  • #assert.not-any-type( ..types, value ): Asserts that the type of value is not one of the passed types.

  • #assert.all-of-type( t, ..values ): Asserts that the type of all passed values is equal to t.

  • #assert.none-of-type( t, ..values ): Asserts that the type of all passed values is not equal to t.

  • #assert.not-empty( value ): Asserts that value is not empty.

  • #assert.new( test ): Creates a new assert function that uses the passed test. test is a function with signature (any) => boolean. This is a quick way to create an assertion from any of the is functions:

    #let assert-foo = assert.new(is.eq.with("foo"))
    
    #let assert-length = assert.new(is.length)
    

Element helpers

#import "@preview/t4t:0.4.1": get

This submodule is a collection of functions, that mostly deal with content elements and get some information from them. Though some handle other types like dictionaries.

  • #get.dict( ..values ): Create a new dictionary from the passed values. All named arguments are stored in the new dictionary as is. All positional arguments are grouped in key-value pairs and inserted into the dictionary:

    #get.dict("a", 1, "b", 2, "c", d:4, e:5)
    
    // (a:1, b:2, c:none, d:4, e:5)
    
  • #get.dict-merge( ..dicts ): Recursively merges the passed-in dictionaries:

    #get.dict-merge(
    	(a: 1),
    	(a: (one: 1, two:2)),
    	(a: (two: 4, three:3))
    )
    
    // (a:(one:1, two:4, three:3))
    
  • #get.args( args, prefix: "" ): Creates a function to extract values from an argument sink args.

    The resulting function takes any number of positional and named arguments and creates a dictionary with values from args.named(). Positional arguments are present in the result if they are present in args.named(). Named arguments are always present, either with their value from args.named() or with the provided value.

    A prefix can be specified, to extract only specific arguments. The resulting dictionary will have all keys with the prefix removed, though.

    #let my-func( ..options, title ) = block(
    	..get.args(options)(
    		"spacing", "above", "below",
    		width:100%
    	)
    )[
    	#text(..get.args(options, prefix:"text-")(
    		fill:black, size:0.8em
    	), title)
    ]
    
    #my-func(
    	width: 50%,
    	text-fill: red, text-size: 1.2em
    )[#lorem(5)]
    
  • #get.text( element, sep: "" ): Recursively extracts the text content of a content element. If present, all child elements are converted to text and joined with sep.

  • #get.stroke-paint( stroke, default: black ): Returns the color of stroke. If no color information is available, def is used. (Deprecated, use stroke.paint instead.)

  • #get.stroke-thickness( stroke, default: 1pt ): Returns the thickness of stroke. If no thickness information is available, def is used. (Deprecated, use stroke.thickness instead.)

  • #get.stroke-dict( stroke, ..overrides ): Creates a dictionary with the keys necessary for a stroke. The returned dictionary is guaranteed to have the keys paint, thickness, dash, cap and join.

    If stroke is a dictionary itself, all key-value pairs are copied to the resulting stroke. Any named arguments in overrides will override the previous value.

  • #get.inset-at( direction, inset, default: 0pt ): Returns the inset (or outset) in a given direction, ascertained from inset.

  • #get.inset-dict( inset, ..overrides ): Creates a dictionary usable as an inset (or outset) argument.

    The resulting dictionary is guaranteed to have the keys top, left, bottom and right.

    If inset is a dictionary itself, all key-value pairs are copied to the resulting stroke. Any named arguments in overrides will override the previous value.

  • #get.x-align( align, default:left ): Returns the alignment along the x-axis from the passed-in align value. If none is present, def is returned. (Deprecated, use align.x instead.)

    #get.x-align(top + center) // center
    
  • #get.y-align( align, default:top ): Returns the alignment along the y-axis from the passed-in align value. If none is present, def is returned. (Deprecated, use align.y instead.)

Math functions

#import "@preview/t4t:0.4.1": math

Some functions to complement the native calc module.

  • #math.minmax( a, b ): Returns an array with the minimum of a and b as the first element and the maximum as the second:

    #let (min, max) = math.minmax(a, b)
    
  • #math.clamp( min, max, value ): Clamps a value between min and max. In contrast to calc.clamp() this function works for other values than numbers, as long as they are comparable.

    text-size = math.clamp(0.8em, 1.2em, text-size)
    
  • #lerp( min, max, t ): Calculates the linear interpolation of t between min and max.

    #let width = math.lerp(0%, 100%, x)
    

    t should be a value between 0 and 1, but the interpolation works with other values, too. To constrain the result into the given interval, use math.clamp:

    #let width = math.lerp(0%, 100%, math.clamp(0, 1, x))
    
  • #map( min, max, range-min, range-max, value ): Maps a value from the interval [min, max] into the interval [range-min, range-max]:

    #let text-weight = int(math.map(8pt, 16pt, 400, 800, text-size))
    

Changelog

Version 0.4.1

  • Fixed some bugs after renaming modules.

Version 0.4.0

  • :warning: Removed alias module in favor of the Typst 0.12 std module.
  • :warning: Changed argument order in def module functions.
    • Old behaviour is still available in the def.compat module for now.
  • :warning: Renamed is module to test and moved some tests to the top-level module.
    • is will become a reserved word in future Typst versions (see typst/typst#5229)
  • Refactored get.stroke-paint and get.stroke-thickness to use the native stroke type.
  • Refactored get.x-align and get.y-align to use the native align.x and align.y values.

Version 0.3.3

  • Fixed language tag in README for all typst examples.
  • Deprecated alias module in favor of std in Typst 0.12.
  • Fixed missing whitespace in get.text (thanks to @).

Version 0.3.2

  • Fixed issue with the new #type function in Typst 0.8.0.

Version 0.3.1

  • Some fixes for message evaluation in assert module.

Version 0.3.0

  • Added a manual (build with tidy and Mantys).
  • Added simple tests for all functions.
  • Fixed bug in is.elem (see #2).
  • Added assert.has-pos, assert.no-pos, assert.has-named and assert.no-named.
  • Added meaningful messages to asserts.
    • Asserts now support functions as message arguments that can dynamically build an error message from the arguments.
  • Improved spelling. (Thanks to @cherryblossom000 for proofreading.)

Version 0.2.0

  • Added is.neg function to negate a test function.
  • Added alias.label.
  • Added is.label test.
  • Added def.as-arr to create an array of the supplied values. Useful if an argument can be both a single value or an array.
  • Added assert.that-not for negated assertions.
  • Added is.one-not-none test to check multiple values, if at least one is not none.
  • Added do argument to all functions in def.
  • Allowed strings in is.elem (see #1).
    • Added is.sequence test.
  • Deprecated get.stroke-paint, get.stroke-thickness, get.x-align and get.y-align in favor of new Typst 0.7.0 features.

Version 0.1.0

  • Initial release