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.3": *
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:
isdefassertaliasmathget
Any or all modules can be imported the usual way:
// Import as "t4t"
#import "@preview/t4t:0.4.3"
// Import all modules
#import "@preview/t4t:0.4.3": *
// Import specific modules
#import "@preview/t4t:0.4.3": 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.3": *
#import "@preview/t4t:0.4.3": 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.3"
#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 passedvaluesisnone. -
#not-none( ..values ): Tests if all of the passedvaluesare notnone. -
#is-auto( ..values ): Tests if any of the passedvaluesisauto. -
#not-auto( ..values ): Tests if all of the passedvaluesare notauto. -
#is-empty( value ): Tests ifvalueis empty. A value is considered empty if it is an empty array, dictionary or string ornoneotherwise. -
#not-empty( value ): Tests ifvalueis not empty. -
#is-dict( value ): Tests ifvalueis a dictionary. -
#is-arr( value ): Tests ifvalueis an array. -
#is-content( value ): Tests ifvalueis of type content. -
#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-relative( 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. -
#test.neg( test ): Creates a new test function that istruewhentestisfalse. Can be used to create negations of tests like#let not-raw = is.neg(is.raw). -
#test.eq( a, b ): Tests if valuesaandbare equal. -
#test.neq( a, b ): Tests if valuesaandbare not equal. -
#test.any( ..compare, value ): Tests ifvalueis equal to any one of the other passed-in values. -
#test.not-any( ..compare, value): Tests ifvalueis not equal to any one of the other passed-in values. -
#test.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. -
#test.is-type( t, value ): Tests ifvalueis of typet. -
#test.is-elem( func, value ): Tests ifvalueis a content element withvalue.func() == func. Iffuncis a string,valuewill be compared torepr(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 ifvaluehas 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 typet. -
#test.none-of-type( t, ..values ): Tests if none of the passed-in values has the typet. -
#test.one-not-none( ..values ): Checks, if at least one value invaluesis not equal tonone. 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 ifvalueis a sequence of content. -
#test.is-raw( value ): Tests ifvalueis a raw element. -
#test.is-table( value ): Tests ifvalueis a table element. -
#test.is-list( value ): Tests ifvalueis a list element. -
#test.is-enum( value ): Tests ifvalueis an enum element. -
#test.is-terms( value ): Tests ifvalueis a terms element. -
#test.is-cols( value ): Tests ifvalueis a columns element. -
#test.is-grid( value ): Tests ifvalueis a grid element. -
#test.is-stack( value ): Tests ifvalueis a stack element. -
#test.is-label( value ): Tests ifvalueis of typelabel.
Default values
#import "@preview/t4t:0.4.3": 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.3": def
#import def.compat as def
#def.if-true( value, test, def:none, do:none ): Returnsdefiftestistrue,valueotherwise.#def.if-false( value, test, def:none, do:none ): Returnsdefiftestisfalse,valueotherwise.#def.if-none( value, def:none, do:none ): Returnsdefifvalueisnone,valueotherwise.#def.if-auto( value, def:none, do:none ): Returnsdefifvalueisauto,valueotherwise.#def.if-any( value, ..compare, def:none, do:none ): Returnsdefifvalueis equal to any of the passed-in values,valueotherwise. (#def.if-any(none, auto, 1pt, width))#def.if-not-any( value, ..compare, def:none, do:none ): Returnsdefifvalueis not equal to any of the passed-in values,valueotherwise. (#def.if-not-any(left, right, top, bottom, position))#def.if-empty( def:none, do:none, value ): Returnsdefifvalueis empty,valueotherwise.#def.as-arr( ..values ): Always returns an array containing allvalues. Any arrays invalueswill 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.3": 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 passedtestistrue. -
#assert.that-not( test ): Asserts that the passedtestisfalse. -
#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.4.3": 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 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 ): 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 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 withsep. -
#get.stroke-paint( stroke, default: black ): Returns the color ofstroke. If no color information is available,defis used. (Deprecated, usestroke.paintinstead.) -
#get.stroke-thickness( stroke, default: 1pt ): Returns the thickness ofstroke. If no thickness information is available,defis used. (Deprecated, usestroke.thicknessinstead.) -
#get.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. -
#get.inset-at( direction, inset, default: 0pt ): Returns the inset (or outset) in a givendirection, ascertained frominset. -
#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,bottomandright.If
insetis a dictionary itself, all key-value pairs are copied to the resulting stroke. Any named arguments inoverrideswill override the previous value. -
#get.x-align( align, default:left ): Returns the alignment along the x-axis from the passed-inalignvalue. If none is present,defis returned. (Deprecated, usealign.xinstead.)#get.x-align(top + center) // center -
#get.y-align( align, default:top ): Returns the alignment along the y-axis from the passed-inalignvalue. If none is present,defis returned. (Deprecated, usealign.yinstead.)
Math functions
#import "@preview/t4t:0.4.3": 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))