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.1.0": *
If only a few functions from t4t are needed, simply copy the necessary code to the beginning of the document.
Reference
The functions are categorized into different submodules that can be imported separately.
The modules are:
isdefassertaliasmathget
Any or all modules cann be imported the usual way:
// Import as "t4t"
#import "@preview/t4t:0.1.0"
// Import all modules
#import "@preview/t4t:0.1.0": *
// Import specific modules
#import "@preview/t4t:0.1.0": 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.1.0": is
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(is.not-none)
// find position of a value
let pos-bar = args.pos().position(is.eq.with("|"))
There are two exceptions: is-none and is-auto. Since keywords can’t be used as function names, the is module can’t define a function to do is.none(). Therefore the methods is-none and is-auto are provided in the base module of t4t:
#import "@preview/t4t:0.1.0": is-none, is-auto
The is submodule still has these tests, but under different names (is.n and is.non for none and is.a and is.aut for auto).
#is.eq( a, b ): Tests if valuesaandbare equal.#is.neq( a, b ): Tests if valuesaandbare not equal.#is.n( ..values ): Tests if any of the passedvaluesisnone.#is.non( ..values ): Alias foris.n.#is.not-none( ..values ): Tests if all of the passedvaluesare notnone.#is.not-n( ..values ): Alias foris.not-none.#is.a( ..values ): Tests if any of the passedvaluesisauto.#is.aut( ..values ): Alias foris-a.#is.not-auto( ..values ): Tests if all of the passedvaluesare notauto.#is.not-a( ..values ): Alias foris.not-auto.#is.empty( value ): Tests ifvalueis empty. A value is considered empty if it is an empty array, dictionary or string ornoneotherwise.#is.not-empty( value ): Tests ifvalueis not empty.#is.any( ..compare, value ): Tests ifvalueis equal to any one of the other passed in values.#is.not-any( ..compare, value): Tests ifvalueis not equals to any one of the other passed in values.#is.has( ..keys, value ): Tests ifvaluecontains all the passedkeys. Either as keys in a dictionary or elements in an array. Ifvalueis neither of those types,falseis returned.#is.type( t, value ): Tests ifvalueis of typet.#is.dict( value ): Tests ifvalueis a dictionary.#is.arr( value ): Tests ifvalueis an array.#is.content( value ): Tests ifvalueis of typecontent.#is.color( value ): Tests ifvalueis a color.#is.stroke( value ): Tests ifvalueis a stroke.#is.loc( value ): Tests ifvalueis a location.#is.bool( value ): Tests ifvalueis a boolean.#is.str( value ): Tests ifvalueis a string.#is.int( value ): Tests ifvalueis an integer.#is.float( value ): Tests ifvalueis a float.#is.num( value ): Tests ifvalueis a numeric value (integerorfloat).#is.frac( value ): Tests ifvalueis a fraction.#is.length( value ): Tests ifvalueis a length.#is.rlength( value ): Tests ifvalueis a relative length.#is.ratio( value ): Tests ifvalueis a ratio.#is.align( value ): Tests ifvalueis an alignment.#is.align2d( value ): Tests ifvalueis a 2d alignment.#is.func( value ): Tests ifvalueis a function.#is.any-type( ..types, value ): Tests ifvaluehas any of the passe in types.#is.same-type( ..values ): Tests if all passed in values have the same type.#is.all-of-type( t, ..values ): Tests if all of the passed in values have the typet.#is.none-of-type( t, ..values ): Tests if none of the passed in values has the typet.#is.elem( func, value ): Tests ifvalueis a content element withvalue.func() == func.#is.raw( value ): Tests ifvalueis a raw element.#is.table( value ): Tests ifvalueis a table element.#is.list( value ): Tests ifvalueis a list element.#is.enum( value ): Tests ifvalueis an enum element.#is.terms( value ): Tests ifvalueis a terms element.#is.cols( value ): Tests ifvalueis a columns element.#is.grid( value ): Tests ifvalueis a grid element.#is.stack( value ): Tests ifvalueis a stack element.
Default values
#import "@preview/t4t:0.1.0": def
These functions perform a test to decide, if a given value is invalid. If the test passes, the default is returned, the value otherwise.
#def.if-true( test, default, value ): Returnsdefaultiftestistrue,valueotherwise.#def.if-false( test, default, value ): Returnsdefaultiftestisfalse,valueotherwise.#def.if-none( default, value ): Returnsdefaultifvalueisnone,valueotherwise.#def.if-auto( default, value ): Returnsdefaultifvalueisauto,valueotherwise.#def.if-any( ..compare, default, value ): Returnsdefaultifvalueis equal to any of the passed in values,valueotherwise. (#def.if-any(none, auto, 1pt, width))#def.if-not-any( ..compare, default, value ): Returnsdefaultifvalueis not equal to any of the passed in values,valueotherwise. (#def.if-not-any(left, right, top, bottom, left, position))#def.if-empty( default, value ): Returnsdefaultifvalueis empty,valueotherwise.
Assertions
#import "@preview/t4t:0.1.0": 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 functino 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 passedtestistrue. -
#assert.eq( a, b ): Asserts thatais equal tob. -
#assert.ne( a, b ): Asserts thatais not equal tob. -
#assert.neq( a, b ): Alias forassert.ne. -
#assert.not-none( value ): Asserts thatvalueis not equal tonone. -
#assert.any( ..values, value ): Asserts thatvalueis one of the passedvalues. -
#assert.not-any( ..values, value ): Asserts thatvalueis not one of the passedvalues. -
#assert.any-type( ..types, value ): Asserts that the type ofvalueis one of the passedtypes. -
#assert.not-any-type( ..types, value ): Asserts that the type ofvalueis not one of the passedtypes. -
#assert.all-of-type( t, ..values ): Asserts that the type of all passedvaluesis equal tot. -
#assert.none-of-type( t, ..values ): Asserts that the type of all passedvaluesis not equal tot. -
#assert.not-empty( value ): Asserts thatvalueis not empty. -
#assert.new( test ): Creates a new assert function that uses the passedtest.testis a function with signature(any) => boolean. This is a quick way to create an assertion from any of theisfunctions:#let assert-foo = assert.new(is.eq.with("foo")) #let assert-length = assert.new(is.length)
Element helpers
#import "@preview/t4t:0.1.0": 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 ): Creat a new dictionary from the passedvalues. 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 ): Recursivley 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: "" ): Creats a function to extract values from an argument sinkargs.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 inargs.named(). Named arguments are always present, either with their value fromargs.named()or with the provided value.A
prefixcan 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. -
#stroke-paint( stroke, default: black ): Returns the color ofstroke. If no color information is available,defaultis used. -
#stroke-thickness( stroke, default: 1pt ): Returns the thickness ofstroke. If no thickness information is available,defaultis used. -
#stroke-dict( stroke, ..overrides ): Creates a dictionary with the keys necessary for a stroke. The returned dictionary is guaranteed to have the keyspaint,thickness,dash,capandjoin.If
strokeis a dictionary itself, all key/value-pairs are copied to the resulting stroke. Any named arguments inoverrideswill override the previous value. -
#inset-at( direction, inset, default: 0pt ): Returns the inset (or outset) in a givendirection, ascertained frominset. -
#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,bottomandright.If
insetis a dictionary itself, all key/value-pairs are copied to the resulting stroke. Any named arguments inoverrideswill override the previous value. -
#x-align( align, default:left ): Returns the alignment along the x-axis from the passed inalignvalue. If none is present,defaultis returned.#get.x-align(top + center) // center -
#y-align( align, default:top ): Returns the alignment along the y-axis from the passed inalignvalue. If none is present,defaultis returned.
== Math functions
#import "@preview/t4t:0.1.0": math
Some functions to complement the native calc module.
-
#math.minmax( a, b ): Returns an array with the minimum ofaandbas the first element and the maximum as the second:#let (min, max) = math.minmax(a, b) -
#math.clamp( min, max, value ): Clamps a value betweenminandmax. In contrast tocalc.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 oftbetweenminandmax.#let width = math.lerp(0%, 100%, x)tshould be a value between 0 and 1, but the interpolation works with other values, too. To constrain the result into the given interval, usemath.clamp:#let width = math.lerp(0%, 100%, math.clamp(0, 1, x)) -
#map( min, max, range-min, range-max, value ): Maps avaluefrom the interval[min, max]into the interval[range-min, range-max]:#let text-weight = int(math.map(8pt, 16pt, 400, 800, text-size))
Alias functions
#import "@preview/t4t:0.1.0": alias
Some of the native Typst function as aliases, to prevent collisions with some common argument namens.
For example using numbering as an argument is not possible, if the value is supposed to be passed to the numbering() function. To still allow argument names, that are in line with the common typst names (like numbering, align …), these alias functions can be used:
#let excercise( no, numbering: "1)" ) = [
Exercise #alias.numbering(numbering, no)
]
The following functions have aliases right now:
numberingaligntypetextrawtablelistenumtermsgridstackcolumns