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 passedvalues
isnone
. -
#not-none( ..values )
: Tests if all of the passedvalues
are notnone
. -
#is-auto( ..values )
: Tests if any of the passedvalues
isauto
. -
#not-auto( ..values )
: Tests if all of the passedvalues
are notauto
. -
#is-empty( value )
: Tests ifvalue
is empty. A value is considered empty if it is an empty array, dictionary or string ornone
otherwise. -
#not-empty( value )
: Tests ifvalue
is not empty. -
#is-dict( value )
: Tests ifvalue
is a dictionary. -
#is-arr( value )
: Tests ifvalue
is an array. -
#is-content( value )
: Tests ifvalue
is of type content. -
#is-color( value )
: Tests ifvalue
is a color. -
#is-stroke( value )
: Tests ifvalue
is a stroke. -
#is-loc( value )
: Tests ifvalue
is a location. -
#is-bool( value )
: Tests ifvalue
is a boolean. -
#is-str( value )
: Tests ifvalue
is a string. -
#is-int( value )
: Tests ifvalue
is an integer. -
#is-float( value )
: Tests ifvalue
is a float. -
#is-num( value )
: Tests ifvalue
is a numeric value (integer
orfloat
). -
#is-frac( value )
: Tests ifvalue
is a fraction. -
#is-length( value )
: Tests ifvalue
is a length. -
#is-rlength( value )
: Tests ifvalue
is a relative length. -
#is-ratio( value )
: Tests ifvalue
is a ratio. -
#is-align( value )
: Tests ifvalue
is an alignment. -
#is-align2d( value )
: Tests ifvalue
is a 2d alignment. -
#is-func( value )
: Tests ifvalue
is a function. -
#test.neg( test )
: Creates a new test function that istrue
whentest
isfalse
. Can be used to create negations of tests like#let not-raw = is.neg(is.raw)
. -
#test.eq( a, b )
: Tests if valuesa
andb
are equal. -
#test.neq( a, b )
: Tests if valuesa
andb
are not equal. -
#test.any( ..compare, value )
: Tests ifvalue
is equal to any one of the other passed-in values. -
#test.not-any( ..compare, value)
: Tests ifvalue
is not equal to any one of the other passed-in values. -
#test.has( ..keys, value )
: Tests ifvalue
contains all the passedkeys
. Either as keys in a dictionary or elements in an array. Ifvalue
is neither of those types,false
is returned. -
#test.is-type( t, value )
: Tests ifvalue
is of typet
. -
#test.is-elem( func, value )
: Tests ifvalue
is a content element withvalue.func() == func
. Iffunc
is a string,value
will 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 ifvalue
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 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 invalues
is 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 ifvalue
is a sequence of content. -
#test.is-raw( value )
: Tests ifvalue
is a raw element. -
#test.is-table( value )
: Tests ifvalue
is a table element. -
#test.is-list( value )
: Tests ifvalue
is a list element. -
#test.is-enum( value )
: Tests ifvalue
is an enum element. -
#test.is-terms( value )
: Tests ifvalue
is a terms element. -
#test.is-cols( value )
: Tests ifvalue
is a columns element. -
#test.is-grid( value )
: Tests ifvalue
is a grid element. -
#test.is-stack( value )
: Tests ifvalue
is a stack element. -
#test.is-label( value )
: Tests ifvalue
is of typelabel
.
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 )
: Returnsdef
iftest
istrue
,value
otherwise.#def.if-false( value, test, def:none, do:none )
: Returnsdef
iftest
isfalse
,value
otherwise.#def.if-none( value, def:none, do:none )
: Returnsdef
ifvalue
isnone
,value
otherwise.#def.if-auto( value, def:none, do:none )
: Returnsdef
ifvalue
isauto
,value
otherwise.#def.if-any( value, ..compare, def:none, do:none )
: Returnsdef
ifvalue
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 )
: Returnsdef
ifvalue
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 )
: Returnsdef
ifvalue
is empty,value
otherwise.#def.as-arr( ..values )
: Always returns an array containing allvalues
. Any arrays invalues
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 passedtest
istrue
. -
#assert.that-not( test )
: Asserts that the passedtest
isfalse
. -
#assert.eq( a, b )
: Asserts thata
is equal tob
. -
#assert.ne( a, b )
: Asserts thata
is not equal tob
. -
#assert.neq( a, b )
: Alias forassert.ne
. -
#assert.not-none( value )
: Asserts thatvalue
is not equal tonone
. -
#assert.any( ..values, value )
: Asserts thatvalue
is one of the passedvalues
. -
#assert.not-any( ..values, value )
: Asserts thatvalue
is not one of the passedvalues
. -
#assert.any-type( ..types, value )
: Asserts that the type ofvalue
is one of the passedtypes
. -
#assert.not-any-type( ..types, value )
: Asserts that the type ofvalue
is not one of the passedtypes
. -
#assert.all-of-type( t, ..values )
: Asserts that the type of all passedvalues
is equal tot
. -
#assert.none-of-type( t, ..values )
: Asserts that the type of all passedvalues
is not equal tot
. -
#assert.not-empty( value )
: Asserts thatvalue
is not empty. -
#assert.new( test )
: Creates a new assert function that uses the passedtest
.test
is a function with signature(any) => boolean
. This is a quick way to create an assertion from any of theis
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 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
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 withsep
. -
#get.stroke-paint( stroke, default: black )
: Returns the color ofstroke
. If no color information is available,def
is used. (Deprecated, usestroke.paint
instead.) -
#get.stroke-thickness( stroke, default: 1pt )
: Returns the thickness ofstroke
. If no thickness information is available,def
is used. (Deprecated, usestroke.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 keyspaint
,thickness
,dash
,cap
andjoin
.If
stroke
is a dictionary itself, all key-value pairs are copied to the resulting stroke. Any named arguments inoverrides
will 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
,bottom
andright
.If
inset
is a dictionary itself, all key-value pairs are copied to the resulting stroke. Any named arguments inoverrides
will override the previous value. -
#get.x-align( align, default:left )
: Returns the alignment along the x-axis from the passed-inalign
value. If none is present,def
is returned. (Deprecated, usealign.x
instead.)#get.x-align(top + center) // center
-
#get.y-align( align, default:top )
: Returns the alignment along the y-axis from the passed-inalign
value. If none is present,def
is returned. (Deprecated, usealign.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 ofa
andb
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 betweenmin
andmax
. 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 oft
betweenmin
andmax
.#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, usemath.clamp
:#let width = math.lerp(0%, 100%, math.clamp(0, 1, x))
-
#map( min, max, range-min, range-max, value )
: Maps avalue
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.12std
module. - :warning: Changed argument order in
def
module functions.- Old behaviour is still available in the
def.compat
module for now.
- Old behaviour is still available in the
- :warning: Renamed
is
module totest
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
andget.stroke-thickness
to use the nativestroke
type. - Refactored
get.x-align
andget.y-align
to use the nativealign.x
andalign.y
values.
Version 0.3.3
- Fixed language tag in README for all typst examples.
- Deprecated
alias
module in favor ofstd
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
andassert.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 indef
. - Allowed strings in
is.elem
(see #1).- Added
is.sequence
test.
- Added
- Deprecated
get.stroke-paint
,get.stroke-thickness
,get.x-align
andget.y-align
in favor of new Typst 0.7.0 features.
Version 0.1.0
- Initial release