├── .gitignore ├── .github └── FUNDING.yml ├── units.pdf ├── src ├── dependencies.typ ├── impl │ ├── impl.typ │ ├── qty.typ │ ├── num │ │ ├── parse.typ │ │ ├── num.typ │ │ └── process.typ │ ├── angle.typ │ ├── array.typ │ ├── complex.typ │ └── unit.typ ├── lib.typ ├── utils.typ ├── defs │ ├── prefixes.typ │ └── units.typ └── metro.typ ├── tests ├── .gitignore ├── angle │ ├── self │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── angle-symbol │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── angle-separator │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ └── number-angle-product │ │ ├── ref │ │ └── 1.png │ │ └── test.typ ├── num │ ├── self │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── uncertainty │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── exponent │ │ ├── self │ │ │ ├── ref │ │ │ │ └── 1.png │ │ │ └── test.typ │ │ ├── exponent-base │ │ │ ├── ref │ │ │ │ └── 1.png │ │ │ └── test.typ │ │ ├── exponent-mode │ │ │ ├── ref │ │ │ │ └── 1.png │ │ │ └── test.typ │ │ ├── exponent-product │ │ │ ├── ref │ │ │ │ └── 1.png │ │ │ └── test.typ │ │ └── exponent-thresholds │ │ │ ├── ref │ │ │ └── 1.png │ │ │ └── test.typ │ ├── group-digits │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── parse-numbers │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── tight-spacing │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── digit-group-size │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── group-separator │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── drop-zero-decimal │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── print-zero-integer │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── rounding │ │ ├── round-pad │ │ │ ├── ref │ │ │ │ └── 1.png │ │ │ └── test.typ │ │ ├── round-half │ │ │ ├── ref │ │ │ │ └── 1.png │ │ │ └── test.typ │ │ ├── round-mode │ │ │ ├── ref │ │ │ │ └── 1.png │ │ │ └── test.typ │ │ ├── round-minimum │ │ │ ├── ref │ │ │ │ └── 1.png │ │ │ └── test.typ │ │ ├── round-direction │ │ │ ├── ref │ │ │ │ └── 1.png │ │ │ └── test.typ │ │ └── round-zero-positive │ │ │ ├── ref │ │ │ └── 1.png │ │ │ └── test.typ │ ├── group-minimum-digits │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── input-decimal-markers │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── output-decimal-marker │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── print-implicit-plus │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── print-unity-mantissa │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── print-zero-exponent │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── retain-explicit-plus │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── retain-negative-zero │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── minimum-decimal-digits │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── minimum-integer-digits │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── output-exponent-marker │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── zero-decimal-as-symbol │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── bracket-ambiguous-numbers │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── bracket-negative-numbers │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ └── retain-explicit-decimal-marker │ │ ├── ref │ │ └── 1.png │ │ └── test.typ ├── qty │ ├── self │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── quantity-product │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── allow-quantity-breaks │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ └── separate-uncertainty │ │ ├── ref │ │ └── 1.png │ │ └── test.typ ├── unit │ ├── self │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── sqrt │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── per-mode │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── per-symbol │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── sticky-per │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── qualifier-mode │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── inter-unit-product │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── power-half-as-sqrt │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ └── bracket-unit-denominator │ │ ├── ref │ │ └── 1.png │ │ └── test.typ ├── array │ ├── units │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── exponents │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── num-list │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── num-range │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ └── num-product │ │ ├── ref │ │ └── 1.png │ │ └── test.typ ├── complex │ ├── num │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── qty │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── complex-angle-unit │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── output-complex-root │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── print-complex-unity │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── complex-root-position │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ ├── complex-symbol-angle │ │ ├── ref │ │ │ └── 1.png │ │ └── test.typ │ └── complex-symbol-degree │ │ ├── ref │ │ └── 1.png │ │ └── test.typ └── template.typ ├── typst.toml ├── README.md ├── LICENSE └── manual.typ /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/settings.json 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: fenjalien 2 | -------------------------------------------------------------------------------- /units.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/units.pdf -------------------------------------------------------------------------------- /src/dependencies.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/oxifmt:0.2.0": strfmt 2 | #import "@preview/t4t:0.4.1": test -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | # added by typst-test, do not edit this line 2 | **/out/ 3 | **/diff/ 4 | **.pdf -------------------------------------------------------------------------------- /tests/angle/self/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/angle/self/ref/1.png -------------------------------------------------------------------------------- /tests/num/self/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/self/ref/1.png -------------------------------------------------------------------------------- /tests/qty/self/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/qty/self/ref/1.png -------------------------------------------------------------------------------- /tests/unit/self/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/unit/self/ref/1.png -------------------------------------------------------------------------------- /tests/unit/sqrt/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/unit/sqrt/ref/1.png -------------------------------------------------------------------------------- /tests/array/units/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/array/units/ref/1.png -------------------------------------------------------------------------------- /tests/complex/num/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/complex/num/ref/1.png -------------------------------------------------------------------------------- /tests/complex/qty/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/complex/qty/ref/1.png -------------------------------------------------------------------------------- /tests/template.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | -------------------------------------------------------------------------------- /tests/array/exponents/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/array/exponents/ref/1.png -------------------------------------------------------------------------------- /tests/array/num-list/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/array/num-list/ref/1.png -------------------------------------------------------------------------------- /tests/array/num-range/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/array/num-range/ref/1.png -------------------------------------------------------------------------------- /tests/num/uncertainty/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/uncertainty/ref/1.png -------------------------------------------------------------------------------- /tests/unit/per-mode/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/unit/per-mode/ref/1.png -------------------------------------------------------------------------------- /tests/unit/per-symbol/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/unit/per-symbol/ref/1.png -------------------------------------------------------------------------------- /tests/unit/sticky-per/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/unit/sticky-per/ref/1.png -------------------------------------------------------------------------------- /tests/array/num-product/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/array/num-product/ref/1.png -------------------------------------------------------------------------------- /tests/num/exponent/self/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/exponent/self/ref/1.png -------------------------------------------------------------------------------- /tests/num/group-digits/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/group-digits/ref/1.png -------------------------------------------------------------------------------- /tests/num/parse-numbers/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/parse-numbers/ref/1.png -------------------------------------------------------------------------------- /tests/num/tight-spacing/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/tight-spacing/ref/1.png -------------------------------------------------------------------------------- /tests/angle/angle-symbol/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/angle/angle-symbol/ref/1.png -------------------------------------------------------------------------------- /tests/num/digit-group-size/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/digit-group-size/ref/1.png -------------------------------------------------------------------------------- /tests/num/group-separator/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/group-separator/ref/1.png -------------------------------------------------------------------------------- /tests/qty/quantity-product/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/qty/quantity-product/ref/1.png -------------------------------------------------------------------------------- /tests/unit/qualifier-mode/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/unit/qualifier-mode/ref/1.png -------------------------------------------------------------------------------- /tests/angle/angle-separator/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/angle/angle-separator/ref/1.png -------------------------------------------------------------------------------- /tests/num/drop-zero-decimal/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/drop-zero-decimal/ref/1.png -------------------------------------------------------------------------------- /tests/num/print-zero-integer/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/print-zero-integer/ref/1.png -------------------------------------------------------------------------------- /tests/num/rounding/round-pad/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/rounding/round-pad/ref/1.png -------------------------------------------------------------------------------- /tests/num/group-minimum-digits/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/group-minimum-digits/ref/1.png -------------------------------------------------------------------------------- /tests/num/input-decimal-markers/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/input-decimal-markers/ref/1.png -------------------------------------------------------------------------------- /tests/num/output-decimal-marker/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/output-decimal-marker/ref/1.png -------------------------------------------------------------------------------- /tests/num/print-implicit-plus/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/print-implicit-plus/ref/1.png -------------------------------------------------------------------------------- /tests/num/print-unity-mantissa/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/print-unity-mantissa/ref/1.png -------------------------------------------------------------------------------- /tests/num/print-zero-exponent/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/print-zero-exponent/ref/1.png -------------------------------------------------------------------------------- /tests/num/retain-explicit-plus/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/retain-explicit-plus/ref/1.png -------------------------------------------------------------------------------- /tests/num/retain-negative-zero/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/retain-negative-zero/ref/1.png -------------------------------------------------------------------------------- /tests/num/rounding/round-half/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/rounding/round-half/ref/1.png -------------------------------------------------------------------------------- /tests/num/rounding/round-mode/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/rounding/round-mode/ref/1.png -------------------------------------------------------------------------------- /tests/qty/allow-quantity-breaks/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/qty/allow-quantity-breaks/ref/1.png -------------------------------------------------------------------------------- /tests/qty/separate-uncertainty/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/qty/separate-uncertainty/ref/1.png -------------------------------------------------------------------------------- /tests/unit/inter-unit-product/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/unit/inter-unit-product/ref/1.png -------------------------------------------------------------------------------- /tests/unit/power-half-as-sqrt/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/unit/power-half-as-sqrt/ref/1.png -------------------------------------------------------------------------------- /tests/angle/number-angle-product/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/angle/number-angle-product/ref/1.png -------------------------------------------------------------------------------- /tests/complex/complex-angle-unit/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/complex/complex-angle-unit/ref/1.png -------------------------------------------------------------------------------- /tests/complex/output-complex-root/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/complex/output-complex-root/ref/1.png -------------------------------------------------------------------------------- /tests/complex/print-complex-unity/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/complex/print-complex-unity/ref/1.png -------------------------------------------------------------------------------- /tests/num/exponent/exponent-base/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/exponent/exponent-base/ref/1.png -------------------------------------------------------------------------------- /tests/num/exponent/exponent-mode/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/exponent/exponent-mode/ref/1.png -------------------------------------------------------------------------------- /tests/num/minimum-decimal-digits/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/minimum-decimal-digits/ref/1.png -------------------------------------------------------------------------------- /tests/num/minimum-integer-digits/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/minimum-integer-digits/ref/1.png -------------------------------------------------------------------------------- /tests/num/output-exponent-marker/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/output-exponent-marker/ref/1.png -------------------------------------------------------------------------------- /tests/num/rounding/round-minimum/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/rounding/round-minimum/ref/1.png -------------------------------------------------------------------------------- /tests/num/uncertainty/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num(1, pm: 2) -------------------------------------------------------------------------------- /tests/num/zero-decimal-as-symbol/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/zero-decimal-as-symbol/ref/1.png -------------------------------------------------------------------------------- /tests/complex/complex-root-position/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/complex/complex-root-position/ref/1.png -------------------------------------------------------------------------------- /tests/complex/complex-symbol-angle/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/complex/complex-symbol-angle/ref/1.png -------------------------------------------------------------------------------- /tests/complex/complex-symbol-degree/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/complex/complex-symbol-degree/ref/1.png -------------------------------------------------------------------------------- /tests/num/bracket-ambiguous-numbers/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/bracket-ambiguous-numbers/ref/1.png -------------------------------------------------------------------------------- /tests/num/bracket-negative-numbers/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/bracket-negative-numbers/ref/1.png -------------------------------------------------------------------------------- /tests/num/exponent/exponent-product/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/exponent/exponent-product/ref/1.png -------------------------------------------------------------------------------- /tests/num/rounding/round-direction/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/rounding/round-direction/ref/1.png -------------------------------------------------------------------------------- /tests/num/self/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": unit, metro-setup, num 2 | #set page(width: auto, height: auto) 3 | 4 | #num("-123.456e12^2") -------------------------------------------------------------------------------- /tests/unit/bracket-unit-denominator/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/unit/bracket-unit-denominator/ref/1.png -------------------------------------------------------------------------------- /tests/num/exponent/exponent-thresholds/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/exponent/exponent-thresholds/ref/1.png -------------------------------------------------------------------------------- /tests/num/rounding/round-zero-positive/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/rounding/round-zero-positive/ref/1.png -------------------------------------------------------------------------------- /tests/num/retain-explicit-decimal-marker/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fenjalien/metro/HEAD/tests/num/retain-explicit-decimal-marker/ref/1.png -------------------------------------------------------------------------------- /tests/num/exponent/exponent-base/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num(exponent-base: "2", e: 2)[1] -------------------------------------------------------------------------------- /tests/num/tight-spacing/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num(2, e: 3) 5 | 6 | #num(2, e: 3, tight-spacing: true) -------------------------------------------------------------------------------- /tests/angle/angle-separator/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | #ang(6, 7, 6.5) 5 | 6 | #ang(6, 7, 6.5, angle-separator: " ") -------------------------------------------------------------------------------- /tests/angle/number-angle-product/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | #ang(2.67) 5 | 6 | #ang(2.67, number-angle-product: " ") -------------------------------------------------------------------------------- /tests/num/print-implicit-plus/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num(345) 5 | 6 | #num(345, print-implicit-plus: true) -------------------------------------------------------------------------------- /tests/num/print-zero-integer/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num(0.123) 5 | 6 | #num(0.123, print-zero-integer: false) -------------------------------------------------------------------------------- /tests/num/retain-explicit-plus/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num[+345] 5 | 6 | #num(retain-explicit-plus: true)[+345] -------------------------------------------------------------------------------- /tests/num/retain-negative-zero/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num[-0] 5 | 6 | #num(retain-negative-zero: true)[-0] -------------------------------------------------------------------------------- /tests/complex/qty/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #import units: * 3 | #set page(width: auto, height: auto, margin: 1cm) 4 | 5 | 6 | #complex(1, 1, ohm) 7 | 8 | #complex(1, 1deg, watt) -------------------------------------------------------------------------------- /tests/num/output-decimal-marker/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num[1.23] 5 | 6 | #num(output-decimal-marker: ",")[1.23] -------------------------------------------------------------------------------- /tests/complex/complex-symbol-angle/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | #complex(1, 1deg, complex-symbol-angle: math.upright("A")) 5 | -------------------------------------------------------------------------------- /tests/complex/complex-symbol-degree/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | #complex(1, 1deg, complex-symbol-degree: math.upright("d")) 5 | -------------------------------------------------------------------------------- /tests/num/exponent/exponent-product/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num(e: 2)[1] 5 | 6 | #num(e: 2, exponent-product: sym.dot)[1] -------------------------------------------------------------------------------- /tests/num/print-unity-mantissa/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num(1, e: 4) 5 | 6 | #num(1, e: 4, print-unity-mantissa: false) -------------------------------------------------------------------------------- /tests/num/print-zero-exponent/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num(444, e: 0) 5 | 6 | #num(444, e: 0, print-zero-exponent: true) -------------------------------------------------------------------------------- /tests/unit/per-symbol/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": unit, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #unit("joule per mole per kelvin", per-mode: "symbol", per-symbol: [ div ]) -------------------------------------------------------------------------------- /tests/num/bracket-negative-numbers/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num(-15673) 5 | 6 | #num(-15673, bracket-negative-numbers: true) -------------------------------------------------------------------------------- /tests/complex/print-complex-unity/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | 5 | #complex(0, 1) 6 | 7 | #complex(0, 1, print-complex-unity: true) 8 | -------------------------------------------------------------------------------- /tests/complex/complex-angle-unit/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | 5 | #complex(1, 1deg) 6 | 7 | #complex(1, 1rad, complex-angle-unit: "radians") -------------------------------------------------------------------------------- /tests/num/retain-explicit-decimal-marker/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num[10.] 5 | 6 | #num(retain-explicit-decimal-marker: true)[10.] 7 | -------------------------------------------------------------------------------- /tests/num/group-separator/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num[12345] 5 | 6 | #num(group-separator: ",")[12345] 7 | 8 | #num(group-separator: " ")[12345] -------------------------------------------------------------------------------- /tests/unit/bracket-unit-denominator/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": unit, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #unit("joule per mole per kelvin", per-mode: "symbol", bracket-unit-denominator: false) -------------------------------------------------------------------------------- /tests/unit/sqrt/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": unit, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #metro-setup(power-half-as-sqrt: true, per-mode: "symbol") 5 | 6 | #unit("hertz per sqrt(kilo watt hour)") -------------------------------------------------------------------------------- /tests/complex/output-complex-root/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | 5 | #complex(1, 2, output-complex-root: [i]) 6 | 7 | #complex(1, 2, output-complex-root: [j]) -------------------------------------------------------------------------------- /tests/num/bracket-ambiguous-numbers/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num(1.2, e: 4, pm: 0.3) 5 | 6 | #num(1.2, e: 4, pm: 0.3, bracket-ambiguous-numbers: false) -------------------------------------------------------------------------------- /tests/num/output-exponent-marker/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num(output-exponent-marker: "e", e: 2)[1] 5 | 6 | #num(output-exponent-marker: "E", e: 2)[1] -------------------------------------------------------------------------------- /tests/unit/inter-unit-product/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": unit 2 | #set page(width: auto, height: auto) 3 | 4 | #unit("farad squared lumen candela") 5 | 6 | #unit("farad squared lumen candela", inter-unit-product: sym.dot.c) -------------------------------------------------------------------------------- /tests/num/digit-group-size/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num[1234567890] 5 | 6 | #num(digit-group-size: 5)[1234567890] 7 | 8 | #num(digit-group-other-size: 2)[1234567890] -------------------------------------------------------------------------------- /tests/num/minimum-decimal-digits/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num[12.34] 5 | 6 | #num(minimum-decimal-digits: 2)[12.34] 7 | 8 | #num(minimum-decimal-digits: 4)[12.34] -------------------------------------------------------------------------------- /tests/num/minimum-integer-digits/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num[12.34] 5 | 6 | #num(minimum-integer-digits: 2)[12.34] 7 | 8 | #num(minimum-integer-digits: 4)[12.34] -------------------------------------------------------------------------------- /tests/num/rounding/round-pad/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | #metro-setup(round-mode: "figures", round-precision: 4) 5 | 6 | #num(12.3) 7 | 8 | #num(12.3, round-pad: false) 9 | -------------------------------------------------------------------------------- /tests/num/exponent/self/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num(e: [10])[1] 5 | 6 | #num(e: 10)[1] 7 | 8 | #num(1, e: 10) 9 | 10 | #num(1, e: [10]) 11 | 12 | $num(1, e: 10)$ -------------------------------------------------------------------------------- /tests/unit/sticky-per/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": unit, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #metro-setup(per-mode: "power") 5 | 6 | #unit("pascal per gray henry") 7 | 8 | #unit("pascal per gray henry", sticky-per: true) 9 | -------------------------------------------------------------------------------- /tests/unit/power-half-as-sqrt/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": unit, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #unit("Hz tothe(0.5)") 5 | 6 | #unit("Hz tothe(0.5)", power-half-as-sqrt: true) 7 | 8 | #unit("Hz tothe(2)", power-half-as-sqrt: true) -------------------------------------------------------------------------------- /tests/num/drop-zero-decimal/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": unit, metro-setup, num, qty 2 | #set page(width: auto, height: auto) 3 | 4 | #num("2.0")\ 5 | #num("2.1")\ 6 | 7 | #metro-setup(drop-zero-decimal: true) 8 | 9 | #num("2.0")\ 10 | #num("2.1")\ 11 | -------------------------------------------------------------------------------- /tests/num/rounding/round-zero-positive/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | #metro-setup(round-mode: "places") 5 | 6 | #num(-0.001) 7 | 8 | #metro-setup(round-zero-positive: false) 9 | 10 | #num(-0.001) -------------------------------------------------------------------------------- /tests/num/zero-decimal-as-symbol/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #num[123.00] 5 | 6 | #metro-setup(zero-decimal-as-symbol: true) 7 | 8 | #num[123.00] 9 | 10 | #num(zero-symbol: sym.dash.wave)[123.00] -------------------------------------------------------------------------------- /src/impl/impl.typ: -------------------------------------------------------------------------------- 1 | #import "unit.typ": unit, tothe, raiseto, qualifier 2 | #import "num/num.typ": num 3 | #import "qty.typ": qty 4 | #import "array.typ": num-list, num-product, num-range, qty-list, qty-product, qty-range 5 | #import "complex.typ": complex 6 | #import "angle.typ": ang -------------------------------------------------------------------------------- /tests/qty/allow-quantity-breaks/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": qty, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | 5 | #box(width: 3cm)[ 6 | Some filler text #qty(10, "m")\ 7 | #metro-setup(allow-quantity-breaks: true) 8 | Some filler text #qty(10, "m") 9 | ] -------------------------------------------------------------------------------- /tests/complex/complex-root-position/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | 5 | #complex(67, -0.9) 6 | 7 | #complex(67, -0.9, complex-root-position: "before-number") 8 | 9 | #complex(67, -0.9, complex-root-position: "after-number") -------------------------------------------------------------------------------- /tests/num/parse-numbers/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": unit, metro-setup, num, qty 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | $num(sqrt(2))$ 5 | 6 | $num(sqrt(2), e: sqrt(2), pw: sqrt(2), pm: sqrt(2))$ 7 | 8 | #num("1e n ^n") 9 | 10 | $qty(sqrt(2), m)$ 11 | 12 | -------------------------------------------------------------------------------- /tests/qty/self/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": qty, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #qty(1.23, "J/mol/kelvin") 5 | 6 | $qty(.23, "candela", e: 7)$ 7 | 8 | #qty(1.99, "per kilogram", per-mode: "symbol") 9 | 10 | #qty(1.345, "C/mol", per-mode: "fraction") -------------------------------------------------------------------------------- /tests/qty/separate-uncertainty/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": qty, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #qty(12.3, "kg", pm: 0.4) 5 | 6 | #qty(12.3, "kg", pm: 0.4, separate-uncertainty: "repeat") 7 | 8 | #qty(12.3, "kg", pm: 0.4, separate-uncertainty: "single") 9 | -------------------------------------------------------------------------------- /tests/num/group-digits/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | 5 | #num[12345.67890] 6 | 7 | #num(group-digits: "none")[12345.67890] 8 | 9 | #num(group-digits: "decimal")[12345.67890] 10 | 11 | #num(group-digits: "integer")[12345.67890] -------------------------------------------------------------------------------- /tests/num/rounding/round-minimum/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | #metro-setup(round-mode: "places") 5 | 6 | #num(0.0055) 7 | 8 | #num(0.0045) 9 | 10 | #metro-setup(round-minimum: 0.01) 11 | 12 | #num(0.0055) 13 | 14 | #num(0.0045) 15 | -------------------------------------------------------------------------------- /tests/angle/angle-symbol/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | #ang(6, 7, 6.5) 5 | 6 | #metro-setup( 7 | angle-symbol-degree: math.upright("d"), 8 | angle-symbol-minute: math.upright("m"), 9 | angle-symbol-second: math.upright("s"), 10 | ) 11 | 12 | #ang(6, 7, 6.5) -------------------------------------------------------------------------------- /tests/num/rounding/round-half/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | #metro-setup(round-mode: "figures", round-precision: 1, round-half: "up") 5 | 6 | #num(0.055) 7 | 8 | #num(0.045) 9 | 10 | #metro-setup(round-half: "even") 11 | 12 | #num(0.055) 13 | 14 | #num(0.045) -------------------------------------------------------------------------------- /tests/angle/self/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | #ang(2.67) 5 | 6 | #ang(2, 3, 4) 7 | 8 | #ang(2.67, angle-mode: "arc") 9 | 10 | #ang(2, 3, 4, angle-mode: "arc") 11 | 12 | #ang(2.67, angle-mode: "decimal") 13 | 14 | #ang(2, 3, 4, angle-mode: "decimal") 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/array/num-range/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | #num-range(10, 30) 5 | 6 | $numrange(10, 30)$ 7 | 8 | #num-range(5, 100) 9 | 10 | #num-range(5, 100, range-phrase: sym.dash) 11 | 12 | #num-range(10, 12) 13 | 14 | $#num-range(10, 12, range-open-phrase: [from ])$ 15 | -------------------------------------------------------------------------------- /tests/array/num-product/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | #num-product(10, 30) 5 | 6 | #num-product(5, 100, 2) 7 | 8 | #num-product(5, 100, 2, product-symbol: sym.dot.c) 9 | 10 | #metro-setup(product-mode: "phrase") 11 | 12 | #num-product(5, 100, 2) 13 | 14 | #num-product(5, 100, 2, product-phrase: " BY ") 15 | -------------------------------------------------------------------------------- /tests/num/rounding/round-direction/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | #metro-setup(round-mode: "places") 5 | 6 | #num(0.054) 7 | 8 | #num(0.046) 9 | 10 | #metro-setup(round-direction: "down") 11 | 12 | #num(0.054) 13 | 14 | #num(0.046) 15 | 16 | #metro-setup(round-direction: "up") 17 | 18 | #num(0.054) 19 | 20 | #num(0.046) -------------------------------------------------------------------------------- /typst.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "metro" 3 | version = "0.3.0" 4 | entrypoint = "src/lib.typ" 5 | authors = ["fenjalien ", "Mc-Zen "] 6 | license = "Apache-2.0" 7 | description = "Typset units and numbers with options." 8 | repository = "https://github.com/fenjalien/metro" 9 | exclude = ["manual.typ", "manual.pdf"] 10 | compiler = "0.12.0" -------------------------------------------------------------------------------- /tests/array/units/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #import units: * 3 | #set page(width: auto, height: auto, margin: 1cm) 4 | 5 | #for units in ("bracket", "repeat", "single") [ 6 | #metro-setup(list-units: units, product-units: units, range-units: units) 7 | 8 | #qty-list(2, 4, 6, 8, "T") 9 | 10 | #qty-product(2, 4, 6, 8, "T") 11 | 12 | #qty-range(2, 4, degreeCelsius) 13 | ] 14 | -------------------------------------------------------------------------------- /tests/num/group-minimum-digits/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | 5 | #num[1234] 6 | 7 | #num[12345] 8 | 9 | #num(group-minimum-digits: 4)[1234] 10 | 11 | #num(group-minimum-digits: 4)[12345] 12 | 13 | #num[1234.5678] 14 | 15 | #num[12345.67890] 16 | 17 | #num(group-minimum-digits: 4)[1234.5678] 18 | 19 | #num(group-minimum-digits: 4)[12345.67890] -------------------------------------------------------------------------------- /tests/num/input-decimal-markers/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": num, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | // Decimal Point 5 | #num[1.2] 6 | 7 | #num("1.2") 8 | 9 | $num(1.2)$ 10 | 11 | // Comma 12 | #num[1,2] 13 | 14 | #num("1,2") 15 | 16 | $num(1\,2)$ 17 | 18 | // Custom 19 | #metro-setup(input-decimal-markers: (sym.minus,)) 20 | 21 | #num[1-2] 22 | 23 | #num("1-2") 24 | 25 | $num(1-2)$ -------------------------------------------------------------------------------- /tests/complex/num/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | 5 | #complex(1, 1) 6 | 7 | #complex(1, 45deg) 8 | 9 | #complex(1, 1, complex-mode: "cartesian") 10 | 11 | #complex(1, 45deg, complex-mode: "cartesian", round-mode: "places") 12 | 13 | #complex(1, 1, complex-mode: "polar", round-mode: "places", round-pad: false) 14 | 15 | #complex(1, 45deg, complex-mode: "polar") 16 | 17 | -------------------------------------------------------------------------------- /tests/array/exponents/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | 5 | #for exponents in ("individual", "combine-bracket", "combine") [ 6 | #metro-setup(list-exponents: exponents, product-exponents: exponents, range-exponents: exponents) 7 | 8 | #num-list("5e3", "7e3", "9e3", "1e4") 9 | 10 | #num-product("5e3", "7e3", "9e3", "1e4") 11 | 12 | #num-range("5e3", "7e3") 13 | 14 | ] 15 | -------------------------------------------------------------------------------- /tests/num/rounding/round-mode/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": * 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | = None 5 | 6 | #num(1.23456) 7 | 8 | #num(14.23) 9 | 10 | = Places 11 | 12 | #metro-setup(round-mode: "places", round-precision: 3) 13 | 14 | #num(1.23456) 15 | 16 | #num(14.23) 17 | 18 | = Figures 19 | 20 | #metro-setup(round-mode: "figures", round-precision: 3) 21 | 22 | #num(1.23456) 23 | 24 | #num(14.23) 25 | -------------------------------------------------------------------------------- /tests/unit/per-mode/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": unit, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #unit("joule per mole per kelvin") 5 | 6 | #unit("metre per second squared") 7 | 8 | #metro-setup(per-mode: "fraction") 9 | 10 | #unit("joule per mole per kelvin") 11 | 12 | #unit("metre per second squared") 13 | 14 | #metro-setup(per-mode: "symbol") 15 | 16 | #unit("joule per mole per kelvin") 17 | 18 | #unit("metre per second squared") 19 | -------------------------------------------------------------------------------- /src/lib.typ: -------------------------------------------------------------------------------- 1 | #import "defs/units.typ" 2 | #import "defs/prefixes.typ" 3 | 4 | #import "metro.typ": num, unit, qty, metro-setup, declare-unit, declare-prefix, create-prefix, declare-power, declare-qualifier, metro-reset, num-list, num-product, num-range, qty-list, qty-product, qty-range, complex, ang 5 | 6 | #let numlist = num-list 7 | #let numproduct = num-product 8 | #let numrange = num-range 9 | #let qtylist = qty-list 10 | #let qtyproduct = qty-product 11 | #let qtyrange = qty-range -------------------------------------------------------------------------------- /tests/array/num-list/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": unit, metro-setup, num, qty, num-list 2 | #set page(width: auto, height: auto, margin: 1cm) 3 | 4 | #num-list(10, 30, 50, 70) 5 | 6 | #num-list(0.1, 0.2, 0.3) 7 | 8 | #num-list(0.1, 0.2, 0.3, list-separator: "; ") 9 | 10 | #num-list(0.1, 0.2, 0.3, list-final-separator: ", ") 11 | 12 | #num-list(0.1, 0.2, 0.3, list-separator: " and ", list-final-separator: " and finally ") 13 | 14 | #num-list(0.1, 0.2) 15 | 16 | #num-list(0.1, 0.2, list-pair-separator: ", and ") -------------------------------------------------------------------------------- /tests/num/exponent/exponent-thresholds/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": unit, metro-setup, num, qty 2 | #set page(width: auto, height: auto) 3 | 4 | #let inputs = ( 5 | "0.001", 6 | "0.012", 7 | "0.123", 8 | "1", 9 | "12", 10 | "123", 11 | "1234" 12 | ) 13 | 14 | 15 | #table( 16 | columns: (auto,)*3, 17 | [Input], [Threshold $-3:3$], [Threshold $-2:2$], 18 | ..for i in inputs {( 19 | num(i), 20 | num(i, exponent-mode: "threshold"), 21 | num(i, exponent-mode: "threshold", exponent-thresholds: (-2, 2)), 22 | )} 23 | ) -------------------------------------------------------------------------------- /tests/qty/quantity-product/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": qty, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | 5 | #qty(2.67, "farad")\ 6 | #qty(2.67, "farad", quantity-product: sym.space)\ 7 | #qty(2.67, "farad", quantity-product: none) 8 | 9 | #metro-setup(quantity-product: sym.times) 10 | 11 | #qty(1.23, "mm", per-mode: "symbol")\ 12 | #qty(1.23, "m/m", per-mode: "fraction")\ 13 | #qty(1.23, "m", per-mode: "symbol")\ 14 | #qty(1.23, "m")\ 15 | #qty(1.23, "/s", per-mode: "symbol")\ 16 | #qty(1.23, "m/s", per-mode: "symbol")\ 17 | #qty(1.23, "m/s") 18 | -------------------------------------------------------------------------------- /tests/unit/qualifier-mode/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": unit, metro-setup 2 | #set page(width: auto, height: auto) 3 | 4 | #unit("kilogram of(pol) squared per mole of(cat) per hour") 5 | 6 | #unit("kilogram of(pol) squared per mole of(cat) per hour", qualifier-mode: "bracket") 7 | 8 | #unit("deci bel of(i)", qualifier-mode: "combine") 9 | 10 | #metro-setup(qualifier-mode: "phrase", qualifier-phrase: sym.space) 11 | 12 | #unit("kilogram of(pol) squared per mole of(cat) per hour") 13 | 14 | #unit("kilogram of(pol) squared per mole of(cat) per hour", qualifier-phrase: [ of ]) 15 | -------------------------------------------------------------------------------- /tests/num/exponent/exponent-mode/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": unit, metro-setup, num, qty 2 | #set page(width: auto, height: auto) 3 | 4 | #let test = [ 5 | #num[0.012] \ 6 | #num[0.00123] \ 7 | #num[0.0001234] \ 8 | #num[0.000012345] \ 9 | #num[0.00000123456] \ 10 | #num[123] \ 11 | #num[1234] \ 12 | #num[12345] \ 13 | #num[123456] \ 14 | #num[1234567] \ 15 | ] 16 | 17 | #test 18 | 19 | #metro-setup(exponent-mode: "scientific") 20 | #test 21 | 22 | #metro-setup(exponent-mode: "engineering") 23 | #test 24 | 25 | #metro-setup(exponent-mode: "fixed", fixed-exponent: 2) 26 | #test 27 | #num(e: 4, "123") 28 | 29 | #metro-setup(exponent-mode: "fixed", fixed-exponent: 0) 30 | #num(e: 4, "1.23") -------------------------------------------------------------------------------- /tests/unit/self/test.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": unit, units, prefixes 2 | #import units: * 3 | #import prefixes: * 4 | #set page(width: auto, height: auto) 5 | 6 | $unit(kg m/s^2)$ 7 | 8 | #unit($kilo gram metre / second^2$) 9 | 10 | $unit(joule / mole / kelvin)$ 11 | 12 | #unit("kilo gram metre per square second") 13 | 14 | #unit("kilo gram metre second^2") 15 | 16 | #unit("per square becquerel") 17 | 18 | #unit("/becquerel^2") 19 | 20 | #unit("square becquerel") 21 | 22 | #unit("joule squared per lumen") 23 | 24 | #unit("cubic lux volt tesla cubed") 25 | 26 | #unit("henry tothe(5)") 27 | 28 | #unit("henry^5") 29 | 30 | #unit("raiseto(4.5) radian") 31 | 32 | #unit("kilogram of(metal)") 33 | 34 | #unit("kilogram_metal") -------------------------------------------------------------------------------- /src/utils.typ: -------------------------------------------------------------------------------- 1 | // Joins two dictionaries together by inserting `new` values into `old`. 2 | // When only-update is true, values will only be inserted if they keys exist in old. 3 | #let combine-dict(new, old, only-update: false) = { 4 | if only-update { 5 | for (k, v) in new { 6 | if k in old { 7 | old.insert(k, v) 8 | } 9 | } 10 | return old 11 | } else { 12 | return old + new 13 | } 14 | } 15 | 16 | #let content-to-string(it) = { 17 | return if type(it) == str { 18 | it 19 | } else if it == [ ] { 20 | " " 21 | } else if it.has("children") { 22 | it.children.map(content-to-string).join() 23 | } else if it.has("body") { 24 | content-to-string(it.body) 25 | } else if it.has("text") { 26 | it.text 27 | } else if it.has("base") { // attach 28 | content-to-string(it.base) + "^" + content-to-string(it.t) 29 | } 30 | } -------------------------------------------------------------------------------- /src/impl/qty.typ: -------------------------------------------------------------------------------- 1 | #import "num/num.typ": num 2 | #import "unit.typ": unit as unit_ 3 | #import "/src/utils.typ": combine-dict 4 | 5 | 6 | #let default-options = ( 7 | allow-quantity-breaks: false, 8 | quantity-product: sym.space.thin, 9 | separate-uncertainty: "bracket", 10 | ) 11 | 12 | #let get-options(options) = combine-dict(options, default-options) 13 | 14 | #let qty( 15 | number, 16 | unit, 17 | e: none, 18 | pm: none, 19 | pw: none, 20 | options 21 | ) = { 22 | options = get-options(options) 23 | 24 | let result = { 25 | let u = unit_( 26 | unit, 27 | options 28 | ) 29 | num( 30 | number, 31 | exponent: e, 32 | uncertainty: pm, 33 | power: pw, 34 | options + ( 35 | separate-uncertainty-unit: if options.separate-uncertainty == "repeat" { u } 36 | ) 37 | ) 38 | u 39 | } 40 | return if options.allow-quantity-breaks { 41 | result 42 | } else { 43 | box(result) 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Metro](https://github.com/fenjalien/metro) 2 | The Metro package aims to be a port of the Latex package siunitx. It allows easy typesetting of numbers and units with options. This package is very early in development and many features are missing, so any feature requests or bug reports are welcome! 3 | 4 | Metro’s name comes from Metrology, the study scientific study of measurement. 5 | 6 | **Bug reports, feature requests, and PRs are welcome!** 7 | 8 | ## Usage 9 | Requires Typst v0.11.0+. 10 | Use Typst's package manager: 11 | ``` 12 | #import "@preview/metro:0.3.0": * 13 | ``` 14 | You can also download the `src` folder and import `lib.typ` and import: 15 | ``` 16 | #import "src/lib.typ": * 17 | ``` 18 | 19 | See the manual for more detailed information: [manual.pdf](https://github.com/fenjalien/metro/releases/latest/download/manual.pdf) 20 | 21 | ## Future Features (in no particular order) 22 | 23 | - [x] Angles 24 | - [x] Complex numbers 25 | - [x] Ranges, lists and products 26 | - [ ] table extensions? 27 | - [ ] Number parsing 28 | - [ ] Uncertainties 29 | - [x] Exponents 30 | - [x] Number post-processing 31 | - [x] rounding 32 | - [x] exponent modes 33 | -------------------------------------------------------------------------------- /src/impl/num/parse.typ: -------------------------------------------------------------------------------- 1 | #import "/src/utils.typ": content-to-string 2 | 3 | // full: (sign, integer, decimal, exponent, power) 4 | // (sign, integer, decimal) 5 | #let parse(options, number, full: false) = { 6 | let typ = type(number) 7 | let result = if typ == content { 8 | content-to-string(number) 9 | } else if typ in (int, float) { 10 | str(number) 11 | } else if typ == str { 12 | number 13 | } 14 | 15 | if result == none { 16 | if options.parse-numbers == true { 17 | panic("Unknown number format: ", number) 18 | } 19 | return (auto,) * if full { 5 } else { 3 } 20 | } 21 | 22 | let input-decimal-markers = str(options.input-decimal-markers.join("|")).replace(sym.minus, "-").replace(sym.plus, "+") 23 | let basic-float = "([-+]?\d*(?:(?:" + input-decimal-markers + ")\d*)?)" 24 | result = result.replace(sym.minus, "-").replace(sym.plus, "+").replace(" ", "").match(regex({ 25 | "^" 26 | // Sign 27 | "([-+])?" 28 | // Integer 29 | "(\d+)?" 30 | // Decimal 31 | "(?:" 32 | "(?:" 33 | input-decimal-markers 34 | ")" 35 | "(\d*)" 36 | ")?" 37 | if full { 38 | // Exponent 39 | "(?:[eE](.*?))?" 40 | // Power 41 | "(?:\^(.*?))?" 42 | } 43 | "$" 44 | })) 45 | 46 | return if result == none { 47 | if options.parse-numbers == true { 48 | panic("Cannot match number: " + repr(number)) 49 | } 50 | (auto,) * if full { 5 } else { 3 } 51 | } else { 52 | result.captures 53 | } 54 | } 55 | 56 | #let to-float(options, number) = { 57 | let (sign, integer, decimal) = parse(options, number) 58 | if auto in (sign, integer, decimal) { 59 | panic("Cannot create a float from ", number, " as parsing failed.") 60 | } 61 | return float(sign + integer + "." + decimal) 62 | } -------------------------------------------------------------------------------- /src/impl/angle.typ: -------------------------------------------------------------------------------- 1 | #import "/src/utils.typ": content-to-string, combine-dict 2 | #import "/src/defs/units.typ": rad, arcminute, arcsecond 3 | #import "num/num.typ" 4 | 5 | #let default-options = ( 6 | angle-mode: "input", 7 | angle-symbol-degree: sym.degree, 8 | angle-symbol-minute: arcminute, 9 | angle-symbol-second: arcsecond, 10 | angle-separator: none, 11 | number-angle-product: none 12 | ) 13 | 14 | // The following is used for complex numbers 15 | 16 | #let is-angle(ang) = { 17 | let typ = type(ang) 18 | return typ == angle or (typ == str and ang.ends-with(regex("deg|rad"))) or (typ == content and repr(ang.func()) == "sequence" and ang.children.last() in (math.deg, rad)) 19 | } 20 | 21 | #let to-number(ang) = { 22 | let typ = type(ang) 23 | return if typ == angle { 24 | ang 25 | } else if typ == str { 26 | float(ang.slice(0, ang.len() - 3)) * if ang.ends-with("deg") { 1deg } else { 1rad } 27 | } else { 28 | let children = ang.children 29 | let mult = if children.pop() == math.deg { 1deg } else { 1rad } 30 | float(content-to-string(children.join())) * mult 31 | } 32 | } 33 | 34 | // Note the options should hold num options 35 | #let parse(options, ang) = { 36 | let typ = type(ang) 37 | return num.parse( 38 | options, 39 | to-number(ang) / if options.at("complex-angle-unit", default: "degrees") == "degrees" { 1deg } else { 1rad } 40 | ) 41 | } 42 | 43 | // The following is used for the `ang` function 44 | 45 | #let get-options(options) = combine-dict(options, default-options + num.default-options, only-update: true) 46 | 47 | #let ang(ang, options) = { 48 | options = get-options(options) 49 | 50 | let input-mode = if ang.len() > 1 { "arc" } else { "decimal" } 51 | 52 | if options.angle-mode != "input" and input-mode != options.angle-mode { 53 | let to-float = num.to-float.with(options) 54 | ang = if input-mode == "arc" { 55 | (to-float(ang.first()) + to-float(ang.at(1)) / 60 + if ang.len() > 2 { to-float(ang.at(2)) / 3600},) 56 | } else { 57 | let a = to-float(ang.first()) 58 | (calc.trunc(a),) 59 | a = calc.fract(a) * 60 60 | (calc.trunc(a),) 61 | a = calc.fract(a) * 60 62 | (calc.round(a),) 63 | } 64 | } 65 | 66 | let symbols = ( 67 | options.angle-symbol-degree, 68 | options.angle-symbol-minute, 69 | options.angle-symbol-second 70 | ) 71 | return math.equation({ 72 | ang.zip(symbols).map( 73 | ((a, s)) => num.num(a, options) + options.number-angle-product + s 74 | ).join(options.angle-separator) 75 | }) 76 | 77 | } -------------------------------------------------------------------------------- /src/impl/array.typ: -------------------------------------------------------------------------------- 1 | #import "num/num.typ" 2 | #import "qty.typ" 3 | #import "unit.typ" as unit_ 4 | #import "/src/utils.typ": combine-dict 5 | 6 | 7 | #let default-options = ( 8 | list-final-separator: [ and ], 9 | list-pair-separator: [ and ], 10 | list-separator: [, ], 11 | 12 | product-mode: "symbol", 13 | product-phrase: [ by ], 14 | product-symbol: sym.times, 15 | 16 | range-open-phrase: none, 17 | range-phrase: [ to ], 18 | 19 | list-close-bracket: sym.paren.r, 20 | list-open-bracket: sym.paren.l, 21 | product-close-bracket: sym.paren.r, 22 | product-open-bracket: sym.paren.l, 23 | range-close-bracket: sym.paren.r, 24 | range-open-bracket: sym.paren.l, 25 | // list-independent-prefix: false, 26 | // product-independent-prefix: false, 27 | // range-independent-prefix: false, 28 | list-exponents: "individual", 29 | product-exponents: "individual", 30 | range-exponents: "individual", 31 | list-units: "repeat", 32 | product-units: "repeat", 33 | range-units: "repeat", 34 | ) 35 | 36 | #let process-numbers(typ, numbers, joiner, options, unit: none) = { 37 | let exponents = options.at(typ + "-exponents") 38 | let units = options.at(typ + "-units") 39 | 40 | if unit != none { 41 | unit = unit_.unit(unit, options + qty.get-options(options)) 42 | } 43 | 44 | let exponent = if exponents != "individual" { 45 | let first = num.parse(num.get-options(options), numbers.first(), full: true) 46 | if first.at(3) != none { 47 | num.process(num.get-options(options), ..first, none).at(3) 48 | 49 | options.fixed-exponent = int(first.at(3)) 50 | options.exponent-mode = "fixed" 51 | options.drop-exponent = true 52 | } 53 | } 54 | 55 | let repeated-unit = if units == "repeat" { unit } 56 | let result = joiner(numbers.map(n => num.num(n, options) + repeated-unit)) 57 | 58 | if exponents == "combine-bracket" or (unit != none and units == "bracket") { 59 | result = math.lr(options.at(typ + "-open-bracket") + result + options.at(typ + "-close-bracket")) 60 | } 61 | 62 | return result + exponent + if repeated-unit == none { unit } 63 | } 64 | 65 | 66 | #let qty-list(numbers, unit: none, options) = { 67 | options = combine-dict(options, default-options) 68 | return process-numbers( 69 | "list", 70 | numbers, 71 | numbers => if numbers.len() == 2 { 72 | numbers.join(options.list-pair-separator) 73 | } else { 74 | let last = numbers.pop() 75 | numbers.join(options.list-separator) 76 | options.list-final-separator 77 | last 78 | }, 79 | options, 80 | unit: unit 81 | ) 82 | } 83 | 84 | #let num-list = qty-list.with(unit: none) 85 | 86 | #let qty-product(numbers, options, unit: none) = { 87 | options = combine-dict(options, default-options) 88 | return process-numbers( 89 | "product", 90 | numbers, 91 | numbers => if options.product-mode == "symbol" { 92 | math.equation(numbers.join(options.product-symbol)) 93 | } else { 94 | numbers.join(options.product-phrase) 95 | }, 96 | options, 97 | unit: unit 98 | ) 99 | } 100 | 101 | #let num-product = qty-product.with(unit: none) 102 | 103 | #let qty-range(n1, n2, options, unit: none) = { 104 | options = combine-dict(options, default-options) 105 | 106 | return process-numbers( 107 | "range", 108 | (n1, n2), 109 | numbers => { 110 | options.range-open-phrase 111 | numbers.join(options.range-phrase) 112 | }, 113 | options, 114 | unit: unit 115 | ) 116 | } 117 | 118 | #let num-range = qty-range.with(unit: none) -------------------------------------------------------------------------------- /src/impl/complex.typ: -------------------------------------------------------------------------------- 1 | #import "/src/utils.typ": combine-dict 2 | #import "num/num.typ" 3 | #import "angle.typ": is-angle, parse as parse-angle, to-number as angle-to-number 4 | #import "unit.typ" as unit_ 5 | #import "qty.typ" 6 | 7 | #let default-options = ( 8 | complex-angle-unit: "degrees", 9 | complex-mode: "input", 10 | complex-root-position: "after-number", 11 | complex-symbol-angle: sym.angle, 12 | complex-symbol-degree: sym.degree, 13 | output-complex-root: math.upright("i"), 14 | print-complex-unity: false 15 | ) 16 | 17 | // I've kind of given up with the combine-dict rules, I need to sort this out another time. 18 | #let get-options(options) = combine-dict(options, default-options + num.default-options + unit_.default-options + qty.default-options, only-update: true) 19 | 20 | 21 | #let parse(options, real, imag) = { 22 | if options.parse-numbers == false { 23 | if options.complex-mode == "input" { 24 | panic("Cannot identify the complex mode without parsing the number!") 25 | } 26 | return (options.complex-mode, real, imag) 27 | } 28 | 29 | let imag-type = type(imag) 30 | 31 | let input-mode = if is-angle(imag) { 32 | "polar" 33 | } else { 34 | "cartesian" 35 | } 36 | 37 | if options.complex-mode != "input" and input-mode != options.complex-mode { 38 | real = num.to-float(options, real) 39 | (real, imag) = if input-mode == "cartesian" { 40 | // output mode is polar 41 | imag = num.to-float(options, imag) 42 | ( 43 | calc.sqrt( 44 | calc.pow(real, 2) + calc.pow(imag, 2) 45 | ), 46 | calc.atan2(real, imag) 47 | ) 48 | } else { 49 | imag = angle-to-number(imag) 50 | // output mode is cartesian 51 | ( 52 | real * calc.cos(imag), 53 | real * calc.sin(imag) 54 | ) 55 | } 56 | } 57 | 58 | let mode = if options.complex-mode == "input" { input-mode } else { options.complex-mode } 59 | 60 | return ( 61 | mode, 62 | num.parse(options, real), 63 | if options.complex-mode == "input" and input-mode == "cartesian" or options.complex-mode == "cartesian" { 64 | num.parse(options, imag) 65 | } else { 66 | parse-angle(options, imag) 67 | } 68 | ) 69 | } 70 | 71 | #let complex(real, imag, unit, options) = { 72 | options = get-options(options) 73 | let (mode, real, imag) = parse(options, real, imag) 74 | let is-polar = mode == "polar" 75 | 76 | let real-mantissa = { 77 | let (options, sign, mantissa, ..rest) = num.process(options, ..real, none, none, none) 78 | mantissa 79 | real = num.build(options, sign, mantissa, ..rest) 80 | } 81 | 82 | return math.equation({ 83 | if is-polar or real-mantissa != "0" { 84 | real 85 | } 86 | 87 | if is-polar { 88 | options.complex-symbol-angle 89 | num.build(..num.process(options, ..imag, none, none, none)) 90 | if options.complex-angle-unit == "degrees" { 91 | options.complex-symbol-degree 92 | } 93 | } else { 94 | let (options, sign, mantissa, ..rest) = num.process(options, ..imag, none, none, none) 95 | 96 | options.print-implicit-plus = real-mantissa != "0" 97 | 98 | mantissa = if not options.print-complex-unity and mantissa in ("1", "") { 99 | options.output-complex-root 100 | } else if options.complex-root-position == "after-number" { 101 | mantissa + options.output-complex-root 102 | } else { 103 | options.output-complex-root + mantissa 104 | } 105 | 106 | num.build(options, sign, mantissa, ..rest) 107 | } 108 | 109 | if unit != none { 110 | unit_.unit(unit, options) 111 | } 112 | }) 113 | } 114 | -------------------------------------------------------------------------------- /src/impl/num/num.typ: -------------------------------------------------------------------------------- 1 | #import "/src/utils.typ": combine-dict, content-to-string 2 | 3 | #import "process.typ": process 4 | #import "parse.typ": parse, to-float 5 | 6 | #let default-options = ( 7 | // parsing 8 | input-decimal-markers: ("\.", ","), 9 | retain-explicit-decimal-marker: false, 10 | retain-explicit-plus: false, 11 | retain-negative-zero: false, 12 | retain-zero-uncertainty: false, 13 | parse-numbers: auto, 14 | 15 | // post-processing 16 | drop-exponent: false, 17 | drop-uncertainty: false, 18 | drop-zero-decimal: false, 19 | exponent-mode: "input", 20 | exponent-thresholds: (-3, 3), 21 | fixed-exponent: 0, 22 | minimum-integer-digits: 0, 23 | minimum-decimal-digits: 0, 24 | round-direction: "nearest", 25 | round-half: "up", 26 | round-minimum: 0, 27 | round-mode: "none", 28 | round-pad: true, 29 | round-precision: 2, 30 | round-zero-positive: true, 31 | // uncertainty-round-direction: "nearest", 32 | 33 | // Printing 34 | bracket-negative-numbers: false, 35 | digit-group-size: 3, 36 | digit-group-first-size: 3, 37 | digit-group-other-size: 3, 38 | exponent-base: "10", 39 | exponent-product: sym.times, 40 | group-digits: "all", 41 | group-minimum-digits: 5, 42 | group-separator: sym.space.thin, 43 | output-close-uncertainty: sym.paren.r, 44 | output-decimal-marker: ".", 45 | output-exponent-marker: none, 46 | output-open-uncertainty: sym.paren.l, 47 | print-implicit-plus: false, 48 | print-exponent-implicit-plus: false, 49 | print-mantissa-implicit-plus: false, 50 | print-unity-mantissa: true, 51 | print-zero-exponent: false, 52 | print-zero-integer: true, 53 | tight-spacing: false, 54 | bracket-ambiguous-numbers: true, 55 | zero-decimal-as-symbol: false, 56 | zero-symbol: sym.bar.h, 57 | 58 | // qty 59 | separate-uncertainty: "", 60 | separate-uncertainty-unit: none, 61 | ) 62 | 63 | 64 | 65 | 66 | 67 | 68 | #let build(options, sign, mantissa, exponent, power, uncertainty) = { 69 | sign += "" 70 | let is-negative = "-" in sign and (mantissa != "0" or options.retain-negative-zero) 71 | 72 | let bracket-ambiguous-numbers = options.bracket-ambiguous-numbers and exponent != none and uncertainty != none 73 | let bracket-negative-numbers = options.bracket-negative-numbers and is-negative 74 | 75 | // Return 76 | return math.equation({ 77 | let output = if options.print-mantissa { 78 | math.attach(mantissa, t: power) 79 | } 80 | 81 | if bracket-negative-numbers { 82 | output = math.lr("(" + output + ")") 83 | } else if is-negative { 84 | output = sym.minus + output 85 | } else if options.print-implicit-plus or options.print-mantissa-implicit-plus or ("+" in sign and options.retain-explicit-plus) { 86 | output = sym.plus + output 87 | } 88 | 89 | if "<" in sign { 90 | output = math.equation(sym.lt + output) 91 | } 92 | 93 | if options.separate-uncertainty == "repeat" and uncertainty != none { 94 | output += options.separate-uncertainty-unit 95 | } 96 | 97 | output += math.equation(uncertainty) 98 | 99 | if bracket-ambiguous-numbers { 100 | output = math.lr("(" + output + ")") 101 | } 102 | 103 | output += exponent 104 | 105 | if options.separate-uncertainty == "bracket" and uncertainty != none { 106 | output = math.lr("(" + output + ")") 107 | } 108 | 109 | return output 110 | }) 111 | } 112 | 113 | #let get-options(options) = combine-dict(options, default-options, only-update: true) 114 | 115 | #let num( 116 | number, 117 | exponent: none, 118 | uncertainty: none, 119 | power: none, 120 | options 121 | ) = { 122 | 123 | options = get-options(options) 124 | 125 | let (sign, integer, decimal, exp, pwr) = if options.parse-numbers != false { 126 | parse(options, number, full: true) 127 | } else { 128 | (auto,) * 5 129 | } 130 | options.number = number 131 | 132 | if exp not in (none, auto) { 133 | exponent = exp 134 | } 135 | if pwr not in (none, auto) { 136 | power = pwr 137 | } 138 | 139 | let (options, sign, mantissa, exponent, power, uncertainty) = process(options, sign, integer, decimal, exponent, power, uncertainty) 140 | 141 | return build(options, sign, mantissa, exponent, power, uncertainty) 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/defs/prefixes.typ: -------------------------------------------------------------------------------- 1 | // SI prefixes 2 | #let quecto = $q$ 3 | #let ronto = $r$ 4 | #let yocto = $y$ 5 | #let zepto = $z$ 6 | #let atto = $a$ 7 | #let femto = $f$ 8 | #let pico = $p$ 9 | #let nano = $n$ 10 | #let micro = $mu$ 11 | #let milli = $m$ 12 | #let centi = $c$ 13 | #let deci = $d$ 14 | #let deca = $d a$ 15 | #let deka = deca 16 | #let hecto = $h$ 17 | #let kilo = $k$ 18 | #let mega = $M$ 19 | #let giga = $G$ 20 | #let tera = $T$ 21 | #let peta = $P$ 22 | #let exa = $E$ 23 | #let zetta = $Z$ 24 | #let yotta = $Y$ 25 | #let ronna = $R$ 26 | #let quetta = $Q$ 27 | 28 | // Binary prefixes 29 | #let kibi = $K i$ 30 | #let mebi = $M i$ 31 | #let gibi = $G i$ 32 | #let tebi = $T i$ 33 | #let pebi = $P i$ 34 | #let exbi = $E i$ 35 | #let zebi = $Z i$ 36 | #let yobi = $Y i$ 37 | 38 | #{ 39 | quecto = math.class("unary", quecto) 40 | ronto = math.class("unary", ronto) 41 | yocto = math.class("unary", yocto) 42 | zepto = math.class("unary", zepto) 43 | atto = math.class("unary", atto) 44 | femto = math.class("unary", femto) 45 | pico = math.class("unary", pico) 46 | nano = math.class("unary", nano) 47 | micro = math.class("unary", micro) 48 | milli = math.class("unary", milli) 49 | centi = math.class("unary", centi) 50 | deci = math.class("unary", deci) 51 | deca = math.class("unary", deca) 52 | deka = math.class("unary", deka) 53 | hecto = math.class("unary", hecto) 54 | kilo = math.class("unary", kilo) 55 | mega = math.class("unary", mega) 56 | giga = math.class("unary", giga) 57 | tera = math.class("unary", tera) 58 | peta = math.class("unary", peta) 59 | exa = math.class("unary", exa) 60 | zetta = math.class("unary", zetta) 61 | yotta = math.class("unary", yotta) 62 | ronna = math.class("unary", ronna) 63 | quetta = math.class("unary", quetta) 64 | 65 | kibi = math.class("unary", kibi) 66 | mebi = math.class("unary", mebi) 67 | gibi = math.class("unary", gibi) 68 | tebi = math.class("unary", tebi) 69 | pebi = math.class("unary", pebi) 70 | exbi = math.class("unary", exbi) 71 | zebi = math.class("unary", zebi) 72 | yobi = math.class("unary", yobi) 73 | } 74 | #let _dict = ( 75 | quecto: quecto, 76 | ronto: ronto, 77 | yocto: yocto, 78 | zepto: zepto, 79 | atto: atto, 80 | femto: femto, 81 | pico: pico, 82 | nano: nano, 83 | micro: micro, 84 | milli: milli, 85 | centi: centi, 86 | deci: deci, 87 | deca: deca, 88 | deka: deka, 89 | hecto: hecto, 90 | kilo: kilo, 91 | mega: mega, 92 | giga: giga, 93 | tera: tera, 94 | peta: peta, 95 | exa: exa, 96 | zetta: zetta, 97 | yotta: yotta, 98 | ronna: ronna, 99 | quetta: quetta, 100 | 101 | kibi: kibi, 102 | mebi: mebi, 103 | gibi: gibi, 104 | tebi: tebi, 105 | pebi: pebi, 106 | exbi: exbi, 107 | zebi: zebi, 108 | yobi: yobi, 109 | ) 110 | 111 | #let _power-tens = ( 112 | quecto: -30, 113 | ronto: -27, 114 | yocto: -24, 115 | zepto: -21, 116 | atto: -18, 117 | femto: -15, 118 | pico: -12, 119 | nano: -9, 120 | micro: -6, 121 | milli: -3, 122 | centi: -2, 123 | deci: -1, 124 | deca: 1, 125 | deka: 1, 126 | hecto: 2, 127 | kilo: 3, 128 | mega: 6, 129 | giga: 9, 130 | tera: 12, 131 | peta: 15, 132 | exa: 18, 133 | zetta: 21, 134 | yotta: 24, 135 | ronna: 27, 136 | quetta: 30 137 | ) 138 | 139 | // #let _dict = ( 140 | // quecto: (quecto, -30), 141 | // ronto: (ronto, -27), 142 | // yocto: (yocto, -24), 143 | // zepto: (zepto, -21), 144 | // atto: (atto, -18), 145 | // femto: (femto, -15), 146 | // pico: (pico, -12), 147 | // nano: (nano, -9), 148 | // micro: (micro, -6), 149 | // milli: (milli, -3), 150 | // centi: (centi, -2), 151 | // deci: (deci, -1), 152 | // deca: (deca, 1), 153 | // deka: (deka, 1), 154 | // hecto: (hecto, 2), 155 | // kilo: (kilo, 3), 156 | // mega: (mega, 6), 157 | // giga: (giga, 9), 158 | // tera: (tera, 12), 159 | // peta: (peta, 15), 160 | // exa: (exa, 18), 161 | // zetta: (zetta, 21), 162 | // yotta: (yotta, 24), 163 | // ronna: (ronna, 27), 164 | // quetta: (quetta, 30) 165 | // ) 166 | // #{ 167 | // for (k, v) in _dict { 168 | // _dict.insert(k, (symbol: v.first(), power: v.last())) 169 | // } 170 | // } 171 | 172 | 173 | // #{ 174 | // quecto += sym.zws 175 | // ronto += sym.zws 176 | // yocto += sym.zws 177 | // zepto += sym.zws 178 | // atto += sym.zws 179 | // femto += sym.zws 180 | // pico += sym.zws 181 | // nano += sym.zws 182 | // micro += sym.zws 183 | // milli += sym.zws 184 | // centi += sym.zws 185 | // deci += sym.zws 186 | // deca += sym.zws 187 | // deka = deca 188 | // hecto += sym.zws 189 | // kilo += sym.zws 190 | // // kilo = $k#sym.zws$ 191 | // mega += sym.zws 192 | // giga += sym.zws 193 | // tera += sym.zws 194 | // peta += sym.zws 195 | // exa += sym.zws 196 | // zetta += sym.zws 197 | // yotta += sym.zws 198 | // ronna += sym.zws 199 | // quetta += sym.zws 200 | // } 201 | 202 | -------------------------------------------------------------------------------- /src/metro.typ: -------------------------------------------------------------------------------- 1 | #import "defs/units.typ" 2 | #import "defs/prefixes.typ" 3 | #import "impl/impl.typ" 4 | #import "utils.typ": combine-dict 5 | #import "dependencies.typ": strfmt 6 | 7 | #let _state-default = ( 8 | units: units._dict, 9 | prefixes: prefixes._dict, 10 | prefix-power-tens: prefixes._power-tens, 11 | powers: ( 12 | square: impl.raiseto([2]), 13 | cubic: impl.raiseto([3]), 14 | squared: impl.tothe([2]), 15 | cubed: impl.tothe([3]) 16 | ), 17 | qualifiers: (:), 18 | ) 19 | 20 | #let _state = state("metro-setup", _state-default) 21 | 22 | #let metro-reset() = _state.update(_ => return _state-default) 23 | 24 | #let metro-setup(..options) = _state.update(s => { 25 | return combine-dict(options.named(), s) 26 | }) 27 | 28 | #let declare-unit(unt, symbol) = _state.update(s => { 29 | s.units.insert(unt, symbol) 30 | return s 31 | }) 32 | 33 | #let create-prefix = math.class.with("unary") 34 | 35 | #let declare-prefix(prefix, symbol, power-tens) = _state.update(s => { 36 | s.prefixes.insert(prefix, symbol) 37 | s.prefix-power-tens.insert(prefix, power-tens) 38 | return s 39 | }) 40 | 41 | #let declare-power(before, after, power) = _state.update(s => { 42 | s.powers.insert(before, impl.raiseto([#power])) 43 | s.powers.insert(after, impl.tothe([#power])) 44 | return s 45 | }) 46 | 47 | #let declare-qualifier(quali, symbol) = _state.update(s => { 48 | s.qualifiers.insert(quali, impl.qualifier(symbol)) 49 | return s 50 | }) 51 | 52 | 53 | #let unit(input, ..options) = context { 54 | return impl.unit( 55 | input, 56 | combine-dict( 57 | options.named(), 58 | _state.get() 59 | ) 60 | ) 61 | } 62 | 63 | #let num(number, e: none, pm: none, pw: none, ..options) = context { 64 | return impl.num( 65 | number, 66 | exponent: e, 67 | uncertainty: pm, 68 | power: pw, 69 | combine-dict( 70 | options.named(), 71 | _state.get() 72 | ) 73 | ) 74 | } 75 | 76 | 77 | #let qty( 78 | number, 79 | units, 80 | e: none, 81 | pm: none, 82 | pw: none, 83 | ..options 84 | ) = context { 85 | return impl.qty( 86 | number, 87 | units, 88 | e: e, 89 | pm: pm, 90 | pw: pw, 91 | combine-dict(options.named(), _state.get()) 92 | ) 93 | } 94 | 95 | #let num-list( 96 | ..numbers-options, 97 | ) = context { 98 | assert( 99 | numbers-options.pos().len() > 1, 100 | message: strfmt("Expected at least two numbers, got {} instead!", numbers-options.pos().len()) 101 | ) 102 | return impl.num-list( 103 | numbers-options.pos(), 104 | combine-dict(numbers-options.named(), _state.get()) 105 | ) 106 | } 107 | 108 | #let qty-list( 109 | ..numbers-options, 110 | ) = context { 111 | assert( 112 | numbers-options.pos().len() > 2, 113 | message: strfmt("Expected at least two numbers and a unit, got {} instead!", numbers-options.pos().len()) 114 | ) 115 | let numbers = numbers-options.pos() 116 | return impl.qty-list( 117 | unit: numbers.pop(), 118 | numbers, 119 | combine-dict(numbers-options.named(), _state.get()), 120 | ) 121 | } 122 | 123 | #let num-product( 124 | ..numbers-options, 125 | ) = context { 126 | assert( 127 | numbers-options.pos().len() > 1, 128 | message: strfmt("Expected at least two numbers, got {} instead!", numbers-options.pos().len()) 129 | ) 130 | return impl.num-product( 131 | numbers-options.pos(), 132 | combine-dict(numbers-options.named(), _state.get()) 133 | ) 134 | } 135 | 136 | #let qty-product( 137 | ..numbers-options, 138 | ) = context { 139 | assert( 140 | numbers-options.pos().len() > 1, 141 | message: strfmt("Expected at least two numbers and a unit, got {} instead!", numbers-options.pos().len()) 142 | ) 143 | let numbers = numbers-options.pos() 144 | return impl.qty-product( 145 | unit: numbers.pop(), 146 | numbers, 147 | combine-dict(numbers-options.named(), _state.get()) 148 | ) 149 | } 150 | 151 | #let num-range( 152 | n1, 153 | n2, 154 | ..options, 155 | ) = context { 156 | return impl.num-range( 157 | n1, n2, 158 | combine-dict(options.named(), _state.get()) 159 | ) 160 | } 161 | 162 | #let qty-range( 163 | n1, 164 | n2, 165 | unit, 166 | ..options, 167 | ) = context { 168 | return impl.qty-range( 169 | n1, n2, unit: unit, 170 | combine-dict(options.named(), _state.get()) 171 | ) 172 | } 173 | 174 | #let complex( 175 | real, 176 | imag, 177 | ..unit-options, 178 | ) = context { 179 | let unit = unit-options.pos() 180 | if unit.len() == 1 { 181 | unit = unit.first() 182 | } else if unit == () { 183 | unit = none 184 | } else { 185 | panic(strfmt("Expected only one or none positional argument, got {}", unit.len())) 186 | } 187 | 188 | return impl.complex( 189 | real, 190 | imag, 191 | unit, 192 | combine-dict(unit-options.named(), _state.get()) 193 | ) 194 | } 195 | 196 | #let ang( 197 | ..ang-options, 198 | ) = context { 199 | return impl.ang( 200 | ang-options.pos(), 201 | combine-dict(ang-options.named(), _state.get()) 202 | ) 203 | } -------------------------------------------------------------------------------- /src/defs/units.typ: -------------------------------------------------------------------------------- 1 | // Used to define kilogram 2 | #let gram = $g$ 3 | 4 | // SI units 5 | #let ampere = $A$ 6 | #let candela = $c d$ 7 | #let kelvin = $kelvin$ 8 | #let kilogram = $k#gram$ 9 | #let metre = $m$ 10 | #let meter = metre 11 | #let mole = $m o l$ 12 | #let second = $s$ 13 | 14 | 15 | // Derived units 16 | #let becquerel = $B q$ 17 | #let degreeCelsius = $degree.c$ 18 | #let coulomb = $C$ 19 | #let farad = $F$ 20 | #let gray = $G y$ 21 | #let hertz = $H z$ 22 | #let henry = $H$ 23 | #let joule = $J$ 24 | #let lumen = $l m$ 25 | #let katal = $k a t$ 26 | #let lux = $l x$ 27 | #let newton = $N$ 28 | #let ohm = $ohm$ 29 | #let pascal = $P a$ 30 | #let radian = $r a d$ 31 | #let siemens = $S$ 32 | #let sievert = $S v$ 33 | #let steradian = $s r$ 34 | #let tesla = $T$ 35 | #let volt = $V$ 36 | #let watt = $W$ 37 | #let weber = $W b$ 38 | 39 | // Non-SI units 40 | #let astronomicalunit = $a u$ 41 | #let bel = $B$ 42 | #let dalton = $D a$ 43 | #let day = $d$ 44 | #let decibel = $d#bel$ 45 | #let electronvolt = $e V$ 46 | #let hectare = $h a$ 47 | #let hour = $h$ 48 | #let litre = $L$ 49 | #let liter = litre 50 | #let arcminute = $prime$ 51 | #let minute = $m i n$ 52 | #let arcsecond = $prime.double$ 53 | #let neper = $N p$ 54 | #let tonne = $t$ 55 | #let byte = $B$ 56 | 57 | // Unit abbreviations 58 | #let fg = $f#gram$ 59 | #let pg = $p#gram$ 60 | #let ng = $n#gram$ 61 | #let ug = $mu#gram$ 62 | #let mg = $m#gram$ 63 | #let kg = kilogram 64 | 65 | #let pm = $p#metre$ 66 | #let nm = $n#metre$ 67 | #let um = $mu#metre$ 68 | #let mm = $m#metre$ 69 | #let cm = $c#metre$ 70 | #let dm = $d#metre$ 71 | #let km = $k#metre$ 72 | 73 | // #let as = $a#second$ 74 | #let fs = $f#second$ 75 | #let ps = $p#second$ 76 | #let ns = $n#second$ 77 | #let us = $mu#second$ 78 | #let ms = $m#second$ 79 | 80 | #let fmol = $f#mole$ 81 | #let pmol = $p#mole$ 82 | #let nmol = $n#mole$ 83 | #let umol = $mu#mole$ 84 | #let mmol = $m#mole$ 85 | #let mol = mole 86 | #let kmol = $k#mole$ 87 | 88 | #let pA = $p#ampere$ 89 | #let nA = $n#ampere$ 90 | #let uA = $mu#ampere$ 91 | #let mA = $m#ampere$ 92 | #let kA = $k#ampere$ 93 | 94 | #let L = $#litre$ 95 | #let uL = $mu#litre$ 96 | #let mL = $m#litre$ 97 | #let hL = $h#litre$ 98 | 99 | #let mHz = $m#hertz$ 100 | #let Hz = hertz 101 | #let kHz = $k#hertz$ 102 | #let MHz = $M#hertz$ 103 | #let GHz = $G#hertz$ 104 | #let THz = $T#hertz$ 105 | 106 | #let mN = $m#newton$ 107 | #let kN = $k#newton$ 108 | #let MN = $M#newton$ 109 | 110 | #let Pa = pascal 111 | #let kPa = $k#pascal$ 112 | #let MPa = $M#pascal$ 113 | #let GPa = $G#pascal$ 114 | 115 | #let mohm = $m#ohm$ 116 | #let kohm = $k#ohm$ 117 | #let Mohm = $M#ohm$ 118 | 119 | #let pV = $p#volt$ 120 | #let nV = $n#volt$ 121 | #let uV = $mu#volt$ 122 | #let mV = $m#volt$ 123 | #let kV = $k#volt$ 124 | 125 | #let nW = $n#watt$ 126 | #let uW = $mu#watt$ 127 | #let mW = $m#watt$ 128 | #let kW = $k#watt$ 129 | #let MW = $M#watt$ 130 | #let GW = $G#watt$ 131 | 132 | #let uJ = $u#joule$ 133 | #let mJ = $m#joule$ 134 | #let kJ = $k#joule$ 135 | 136 | #let eV = electronvolt 137 | #let meV = $m#electronvolt$ 138 | #let keV = $k#electronvolt$ 139 | #let MeV = $M#electronvolt$ 140 | #let GeV = $G#electronvolt$ 141 | #let TeV = $T#electronvolt$ 142 | 143 | #let kWh = $kW h$ 144 | 145 | #let fF = $f#farad$ 146 | #let pF = $p#farad$ 147 | #let nF = $n#farad$ 148 | #let uF = $mu#farad$ 149 | #let mF = $m#farad$ 150 | 151 | #let fH = $f#henry$ 152 | #let pH = $p#henry$ 153 | #let nH = $n#henry$ 154 | #let mH = $m#henry$ 155 | #let uH = $mu#henry$ 156 | 157 | #let nC = $n#coulomb$ 158 | #let uC = $mu#coulomb$ 159 | #let mC = $m#coulomb$ 160 | 161 | #let dB = decibel 162 | 163 | #let cd = candela 164 | #let Bq = becquerel 165 | #let Gy = gray 166 | #let lm = lumen 167 | #let kat = katal 168 | #let lx = lux 169 | #let rad = radian 170 | #let Sv = sievert 171 | #let sr = steradian 172 | #let Wb = weber 173 | #let au = astronomicalunit 174 | #let Da = dalton 175 | #let ha = hectare 176 | #let Np = neper 177 | 178 | #let kB = $k#byte$ 179 | #let MB = $M#byte$ 180 | #let GB = $G#byte$ 181 | #let TB = $T#byte$ 182 | #let PB = $P#byte$ 183 | #let EB = $E#byte$ 184 | 185 | #let KiB = $K i#byte$ 186 | #let MiB = $M i#byte$ 187 | #let GiB = $G i#byte$ 188 | #let TiB = $T i#byte$ 189 | #let PiB = $P i#byte$ 190 | #let EiB = $E i#byte$ 191 | 192 | 193 | #let _dict = ( 194 | ampere: ampere, 195 | pA: pA, 196 | nA: nA, 197 | uA: uA, 198 | mA: mA, 199 | A: ampere, 200 | kA: kA, 201 | 202 | astronomicalunit: astronomicalunit, 203 | au: au, 204 | 205 | arcminute: arcminute, 206 | 207 | arcsecond: arcsecond, 208 | 209 | becquerel: becquerel, 210 | Bq: Bq, 211 | 212 | bel: bel, 213 | decibel: decibel, 214 | dB: dB, 215 | 216 | candela: candela, 217 | cd: cd, 218 | 219 | dalton: dalton, 220 | Da: Da, 221 | 222 | day: day, 223 | 224 | degree: sym.degree, 225 | 226 | degreeCelsius: sym.degree.c, 227 | 228 | coulomb: coulomb, 229 | C: coulomb, 230 | nC: nC, 231 | mC: mC, 232 | uC: uC, 233 | 234 | electronvolt: electronvolt, 235 | meV: meV, 236 | eV: eV, 237 | keV: keV, 238 | MeV: MeV, 239 | GeV: GeV, 240 | TeV: TeV, 241 | 242 | kWh: kWh, 243 | 244 | farad: farad, 245 | F: farad, 246 | fF: fF, 247 | pF: pF, 248 | nF: nF, 249 | uF: uF, 250 | mF: mF, 251 | 252 | gray: gray, 253 | Gy: Gy, 254 | 255 | hectare: hectare, 256 | ha: ha, 257 | 258 | henry: henry, 259 | H: henry, 260 | fH: fH, 261 | pH: pH, 262 | nH: nH, 263 | mH: mH, 264 | uH: uH, 265 | 266 | hertz: hertz, 267 | mHz: mHz, 268 | Hz: Hz, 269 | kHz: kHz, 270 | MHz: MHz, 271 | GHz: GHz, 272 | THz: THz, 273 | 274 | hour: hour, 275 | 276 | joule: joule, 277 | uJ: uJ, 278 | mJ: mJ, 279 | J: joule, 280 | kJ: kJ, 281 | 282 | katal: katal, 283 | kat: kat, 284 | 285 | kelvin: kelvin, 286 | K: kelvin, 287 | 288 | kilogram: kilogram, 289 | gram: gram, 290 | fg: fg, 291 | pg: pg, 292 | ng: ng, 293 | ug: ug, 294 | mg: mg, 295 | g: gram, 296 | kg: kg, 297 | 298 | litre: litre, 299 | liter: liter, 300 | uL: uL, 301 | mL: mL, 302 | L: litre, 303 | hL: hL, 304 | 305 | lumen: lumen, 306 | lm: lm, 307 | 308 | lux: lux, 309 | lx: lx, 310 | 311 | metre: metre, 312 | meter: meter, 313 | pm: pm, 314 | nm: nm, 315 | um: um, 316 | mm: mm, 317 | cm: cm, 318 | dm: dm, 319 | m: metre, 320 | km: km, 321 | 322 | minute: minute, 323 | 324 | mole: mole, 325 | fmol: fmol, 326 | pmol: pmol, 327 | nmol: nmol, 328 | umol: umol, 329 | mmol: mmol, 330 | mol: mol, 331 | kmol: kmol, 332 | 333 | neper: neper, 334 | Np: Np, 335 | 336 | newton: newton, 337 | mN: mN, 338 | N: newton, 339 | kN: kN, 340 | MN: MN, 341 | 342 | ohm: ohm, 343 | mohm: mohm, 344 | kohm: kohm, 345 | Mohm: Mohm, 346 | 347 | pascal: pascal, 348 | Pa: Pa, 349 | kPa: kPa, 350 | MPa: MPa, 351 | GPa: GPa, 352 | 353 | radian: radian, 354 | rad: rad, 355 | 356 | second: second, 357 | "as": $a#second$, 358 | fs: fs, 359 | ps: ps, 360 | ns: ns, 361 | us: us, 362 | ms: ms, 363 | s: second, 364 | 365 | siemens: siemens, 366 | 367 | sievert: sievert, 368 | Sv: Sv, 369 | 370 | steradian: steradian, 371 | sr: sr, 372 | 373 | tesla: tesla, 374 | T: tesla, 375 | 376 | tonne: tonne, 377 | 378 | volt: volt, 379 | pV: pV, 380 | nV: nV, 381 | uV: uV, 382 | mV: mV, 383 | V: volt, 384 | kV: kV, 385 | 386 | watt: watt, 387 | nW: nW, 388 | uW: uW, 389 | mW: mW, 390 | W: watt, 391 | kW: kW, 392 | MW: MW, 393 | GW: GW, 394 | 395 | weber: weber, 396 | Wb: Wb, 397 | 398 | byte: byte, 399 | kB: kB, 400 | MB: MB, 401 | GB: GB, 402 | TB: TB, 403 | PB: PB, 404 | EB: EB, 405 | KiB: KiB, 406 | MiB: MiB, 407 | GiB: GiB, 408 | TiB: TiB, 409 | PiB: PiB, 410 | EiB: EiB, 411 | 412 | ) 413 | -------------------------------------------------------------------------------- /src/impl/unit.typ: -------------------------------------------------------------------------------- 1 | #import "/src/dependencies.typ": test 2 | #import "/src/utils.typ": combine-dict, content-to-string 3 | 4 | // NULL unicode character as a marker 5 | #let NULL-after = [\u{FFFF} ] 6 | #let NULL-before = [ \u{FFFF}] 7 | 8 | 9 | #let tothe = (x) => math.attach(NULL-after, t: x) 10 | #let raiseto = (x) => math.attach(NULL-before, t: x) 11 | #let qualifier = (x) => math.attach(NULL-after, b: x) 12 | 13 | #let parse(options, input) = { 14 | parse = parse.with(options) 15 | let func = repr(input.func()) 16 | let out = (:) 17 | 18 | return if func == "attach" { 19 | if "t" in input.fields() { 20 | let power = float(content-to-string(input.t)) 21 | if power < 0 { 22 | power *= -1 23 | (is-per: true) 24 | } 25 | (power: power) 26 | } 27 | if "b" in input.fields() { 28 | (qualifier: input.b) 29 | } 30 | ( 31 | body: if input.base not in (NULL-after, NULL-before) { 32 | parse(input.base) 33 | } else { 34 | input.base 35 | } 36 | ) 37 | } else if func == "frac" { 38 | ( 39 | body: parse(input.num), 40 | per: (parse(input.denom),), 41 | ) 42 | } else if func == "class" { 43 | if input.class == "unary" { 44 | (prefix: input.body) 45 | } else if input.class == "binary" and input.body == [per] { 46 | (is-per: true, body: NULL-before) 47 | } 48 | } else if func in ("text", "equation", "display") { 49 | ( 50 | body: if func == "text" { 51 | input.text 52 | } else { 53 | input.body 54 | } 55 | ) 56 | } else if func == "lr" { 57 | parse(input.body.children.slice(1, -1).join()) 58 | } else if func == "root" { 59 | (power: 0.5, body: parse(input.radicand)) 60 | } else if func == "sequence" { 61 | let result = ((:),) 62 | let out = (:) 63 | let per-stuck = false 64 | for child in input.children { 65 | child = parse(child) 66 | if child == none { 67 | continue 68 | } 69 | 70 | if "body" in child { 71 | let body = child.remove("body") 72 | if body != NULL-after and "body" in out { 73 | if per-stuck or out.at("is-per", default: false) { 74 | result.last().per = result.last().at("per", default: ()) + (out,) 75 | } else { 76 | result.push(out) 77 | } 78 | if options.sticky-per and child.at("is-per", default: false) and body == NULL-before { 79 | per-stuck = true 80 | } 81 | out = (:) 82 | } 83 | if body not in (NULL-before, NULL-after) { 84 | out.body = body 85 | } 86 | } 87 | if "power" in child { 88 | out.power = out.at("power", default: 1) * child.remove("power") 89 | } 90 | for (k, v) in child { 91 | if v != none { 92 | out.insert(k, v) 93 | } 94 | } 95 | } 96 | if "body" in out { 97 | if per-stuck or out.at("is-per", default: false) { 98 | result.last().per = result.last().at("per", default: ()) + (out,) 99 | } else { 100 | result.push(out) 101 | } 102 | } 103 | if result.len() > 1 { 104 | (body: result) 105 | } else { 106 | result.first() 107 | } 108 | } 109 | 110 | } 111 | 112 | #let display(options, input) = { 113 | let quantity-product = options.quantity-product 114 | options.quantity-product = none 115 | display = display.with(options) 116 | let out = if "body" in input { 117 | if type(input.body) == array { 118 | input.body.map(display).filter(x => x != none).join(options.inter-unit-product) 119 | } else if type(input.body) == dictionary { 120 | display(input.body) 121 | } else { 122 | input.body 123 | } 124 | } 125 | 126 | if "prefix" in input { 127 | out = input.prefix + out 128 | } 129 | 130 | if "power" in input or "qualifier" in input { 131 | if options.power-half-as-sqrt and "power" in input and calc.abs(calc.fract(input.power)) == 0.5 { 132 | input.power -= 0.5 133 | out = math.sqrt(out) 134 | } 135 | let has-qualifier = "qualifier" in input 136 | 137 | if has-qualifier { 138 | if options.qualifier-mode == "bracket" { 139 | out += "(" + input.qualifier + ")" 140 | } else if options.qualifier-mode == "phrase"{ 141 | out += options.qualifier-phrase + input.qualifier 142 | } 143 | } 144 | 145 | out = math.attach( 146 | out, 147 | t: if "power" in input and input.power != 0 { 148 | str(input.power) 149 | }, 150 | b: if has-qualifier and options.qualifier-mode == "subscript" { 151 | input.qualifier 152 | } 153 | ) 154 | if has-qualifier and options.qualifier-mode == "combine" { 155 | out += if out.t != none { 156 | "(" + input.qualifier + ")" 157 | } else { 158 | input.qualifier 159 | } 160 | } 161 | } 162 | 163 | if "per" in input { 164 | if options.per-mode == "power" { 165 | out += if "body" in input { 166 | options.inter-unit-product 167 | } + input.per.map(p => { 168 | p.power = p.at("power", default: 1) * -1 169 | display(p) 170 | }).join(options.inter-unit-product) 171 | } else if options.per-mode == "fraction" { 172 | out = math.frac( 173 | out, 174 | input.per.map(display).join(options.inter-unit-product) 175 | ) 176 | } else if options.per-mode == "symbol" { 177 | let denom = input.per.map(display).join(options.inter-unit-product) 178 | if options.bracket-unit-denominator and input.per.len() > 1 { 179 | denom = "(" + denom + ")" 180 | } 181 | out += [#options.per-symbol] + [#denom] 182 | } 183 | } 184 | 185 | // Don't add a quantiy-prdouct if its in symbol mode and has a per 1/kg not 1 /kg 186 | return if quantity-product != none and (options.per-mode != "symbol" or "body" in input) { quantity-product + h(0pt)} + out 187 | } 188 | 189 | #let default-options = ( 190 | inter-unit-product: sym.space.thin, 191 | per-symbol: sym.slash, 192 | bracket-unit-denominator: true, 193 | per-mode: "power", 194 | power-half-as-sqrt: false, 195 | qualifier-mode: "subscript", 196 | qualifier-phrase: "", 197 | sticky-per: false, 198 | units: none, 199 | prefixes: none, 200 | prefix-power-tens: none, 201 | powers: none, 202 | qualifiers: none, 203 | 204 | quantity-product: none 205 | ) 206 | 207 | #let get-options(options) = combine-dict(options, default-options, only-update: true) 208 | 209 | #let unit(unit, options) = { 210 | let input = unit 211 | assert(type(input) in (str, content), message: "Expected string or content input type, got " + type(input) + " instead.") 212 | options = get-options(options) 213 | 214 | if type(input) == str { 215 | // Converts the string input into math content 216 | // The first replace adds quote marks around words attached to underscores otherwise math doesn't capture the qualifier correctly. 217 | // Second replace removes slashes with pers as they allow no numerator to be present. 218 | input = eval( 219 | input.replace(regex("_(\w+)|(?:(?:_|of)\((.+?)\))"), (m) => { 220 | let c = m.captures 221 | "_\"" + if c.first() == none { 222 | c.last() 223 | } else { 224 | c.first() 225 | } + "\"" 226 | }).replace("/", " per ").trim(), 227 | mode: "math", 228 | scope: options.units + options.prefixes + options.powers + options.qualifiers + ( 229 | per: math.class("binary", "per"), 230 | tothe: tothe, 231 | raiseto: raiseto, 232 | ) 233 | ) 234 | } 235 | 236 | // When math content is passed directly it comes as an equation which we normally don't want to step into. If the equation is not exactly a known unit or prefix step into it. 237 | if test.is-elem(math.equation, input) and not input in options.units.values() and not input in options.prefixes.values() { 238 | input = input.body 239 | } 240 | 241 | math.upright(math.equation( 242 | display(options, parse(options, input)) 243 | )) 244 | 245 | } -------------------------------------------------------------------------------- /src/impl/num/process.typ: -------------------------------------------------------------------------------- 1 | #import "parse.typ": parse 2 | 3 | #let group-digits(options, input, rev: true) = { 4 | let (first, other) = if options.digit-group-size != 3 { 5 | (options.digit-group-size,) * 2 6 | } else { 7 | (options.digit-group-first-size, options.digit-group-other-size) 8 | } 9 | 10 | let input = if rev { 11 | input.split("").slice(1, -1).rev() 12 | } else { 13 | input 14 | } 15 | 16 | let len = input.len() 17 | let start = calc.rem(first, input.len()) 18 | let result = ( 19 | input.slice(0, start), 20 | ..for i in range(start, len, step: other) { 21 | (input.slice(i, calc.min(i + other, len)),) 22 | } 23 | ) 24 | 25 | return if rev { 26 | result.map(x => x.rev().join()).rev() 27 | } else { 28 | result 29 | }.join(options.group-separator) 30 | } 31 | 32 | #let check-exponent-thresholds(options, exp) = (options.exponent-mode == "scientific" or exp - 1 < options.exponent-thresholds.first() or exp+1 > options.exponent-thresholds.last()) 33 | 34 | 35 | #let process-exponent(options, exp) = { 36 | let exponent = parse(options, exp) 37 | if exponent.all(x => x == auto) { 38 | exponent = ( 39 | none, // sign 40 | exp, // The not parsed exponent 41 | none // Decimal 42 | ) 43 | } 44 | 45 | if exponent.at(2) != none { 46 | exponent.insert(2, options.output-decimal-marker) 47 | } 48 | let sign = exponent.first() 49 | exponent = exponent.slice(1).join() 50 | if exponent != "0" or options.print-zero-exponent { 51 | if sign == "-" or options.print-implicit-plus or options.print-exponent-implicit-plus { 52 | exponent = if sign == "-" { sym.minus } else {sym.plus} + exponent 53 | } 54 | exponent = if options.output-exponent-marker != none { 55 | options.output-exponent-marker + exponent 56 | } else { 57 | math.attach(if options.print-mantissa {options.spacing + options.exponent-product + options.spacing } + options.exponent-base, t: exponent) 58 | } 59 | } else { 60 | exponent = none 61 | } 62 | return exponent 63 | } 64 | 65 | #let process-power(options, pwr) = { 66 | let power = parse(options, pwr) 67 | if power.all(x => x == auto) { 68 | return pwr 69 | } 70 | 71 | if power.at(2) != none { 72 | power.insert(2, options.output-decimal-marker) 73 | } 74 | return power.join() 75 | } 76 | 77 | #let process-uncertainty(options, pm) = { 78 | let uncertainty = parse(options, pm) 79 | if uncertainty.all(x => x == auto) { 80 | uncertainty = ( 81 | none, 82 | pm, 83 | none 84 | ) 85 | } 86 | if uncertainty.at(2) != none { 87 | uncertainty.insert(2, options.output-decimal-marker) 88 | } 89 | uncertainty = options.spacing + if uncertainty.first() == "-" { sym.minus.plus } else { sym.plus.minus } + options.spacing + uncertainty.slice(1).join() 90 | return uncertainty 91 | } 92 | 93 | #let non-zero-integer-regex = regex("[^0]") 94 | 95 | #let exponent-mode(options, integer, decimal, exponent) = { 96 | exponent = if exponent == none { 0 } else { int(exponent) } 97 | if integer.position(non-zero-integer-regex) == none and decimal.position(non-zero-integer-regex) == none { 98 | return (integer, decimal, exponent) 99 | } 100 | if options.exponent-mode in ("scientific", "threshold") { 101 | let i = integer.position(non-zero-integer-regex) 102 | if i != none and i < integer.len() { 103 | let exp = integer.len() - i - 1 + exponent 104 | if check-exponent-thresholds(options, exp) { 105 | exponent = exp 106 | decimal = integer.slice(i+1) + decimal 107 | integer = integer.slice(i, i+1) 108 | } 109 | } else if integer.len() > 1 or (integer == "0" and decimal.len() > 0) { 110 | let i = decimal.position(non-zero-integer-regex) 111 | let exp = exponent - i - 1 112 | if check-exponent-thresholds(options, exp) { 113 | integer = decimal.slice(i, i+1) 114 | decimal = decimal.slice(i+1) 115 | exponent = exp 116 | } 117 | } 118 | } else if options.exponent-mode == "engineering" { 119 | if integer.len() > 1 { 120 | let l = calc.rem(integer.len(), 3) 121 | if l == 0 { l = 3 } 122 | exponent += integer.slice(l).len() 123 | decimal = integer.slice(l) + decimal 124 | integer = integer.slice(0, l) 125 | } else if integer == "0" { 126 | let i = decimal.position(non-zero-integer-regex) 127 | let l = 3 - calc.rem(i, 3) 128 | if decimal.len() < i+l { 129 | decimal += "0" * ((i + l) - decimal.len()) 130 | } 131 | integer = decimal.slice(i, i+l) 132 | decimal = decimal.slice(i+l) 133 | exponent -= i+l 134 | } 135 | } else if options.exponent-mode == "fixed" { 136 | let n = options.fixed-exponent 137 | let i = exponent - n 138 | exponent = n 139 | if i < 0 { 140 | if integer.len() < -i { 141 | integer = "0" * -(i - integer.len()) + integer 142 | } 143 | decimal = integer.slice(i) + decimal 144 | integer = integer.slice(0, i) 145 | } else if i > 0 { 146 | if decimal.len() < i { 147 | decimal += "0" * (i - decimal.len()) 148 | } 149 | integer += decimal.slice(0, i) 150 | decimal = decimal.slice(i) 151 | } 152 | } 153 | return (integer, decimal, exponent) 154 | } 155 | 156 | // Rounds the digits of a number based on the last digit sliced off of it. 157 | // `decimal` should be `""` if there is no decimal 158 | #let round-digits(options, integer, decimal, index) = { 159 | let digit 160 | if integer.len() > index { 161 | decimal = "" 162 | digit = integer.at(index) 163 | integer = integer.slice(0, index) + "0" * (integer.len() - index) 164 | } else if integer.len() == index { 165 | digit = decimal.first() 166 | decimal = "" 167 | } else if integer.len() + decimal.len() > index { 168 | index -= integer.len() 169 | digit = decimal.at(index) 170 | decimal = decimal.slice(0, index) 171 | } 172 | 173 | if digit != none and (options.round-direction == "up" or ( 174 | options.round-direction == "nearest" and ( 175 | options.round-half == "even" and calc.odd(int((integer + decimal).last())) 176 | or options.round-half == "up" and int(digit) >= 5 177 | ) 178 | )) { 179 | if decimal != "" { 180 | let len = decimal.len() 181 | decimal = str(int(decimal) + 1) 182 | if len < decimal.len() { 183 | decimal = if options.round-pad { decimal.slice(1) } else { "" } 184 | integer = str(int(integer) + 1) 185 | } else { 186 | decimal = "0" * (len - decimal.len()) + decimal 187 | } 188 | } else { 189 | let i = integer.len() - index 190 | integer = str(int(integer) + calc.pow(10, i)) 191 | } 192 | } 193 | return (integer, decimal) 194 | } 195 | 196 | #let round-mode(options, sign, integer, decimal) = { 197 | let round-digits = round-digits.with(options) 198 | if options.round-mode == "places" { 199 | if decimal.len() > options.round-precision { 200 | (integer, decimal) = round-digits(integer, decimal, options.round-precision + integer.len()) 201 | if float(integer + "." + decimal) < options.round-minimum { 202 | (integer, decimal) = str(options.round-minimum).split(".") 203 | sign = "<" + sign 204 | } else if int(integer) + int(decimal) == 0 and options.round-zero-positive { 205 | sign = "+" 206 | } 207 | } else if decimal.len() < options.round-precision and options.round-pad { 208 | decimal += "0" * (options.round-precision - decimal.len()) 209 | } 210 | } else if options.round-mode == "figures" { 211 | if int(integer) == 0 { 212 | integer = "" 213 | } 214 | 215 | let len = integer.len() + decimal.len() 216 | if options.round-precision < len { 217 | (integer, decimal) = round-digits(integer, decimal, options.round-precision + if integer.len() < options.round-precision { decimal.position(non-zero-integer-regex) }) 218 | } else if len < options.round-precision and options.round-pad { 219 | decimal += "0" * (options.round-precision - len) 220 | } 221 | } 222 | // else if options.round-mode == "uncertainty" { 223 | 224 | // } 225 | 226 | return (sign, integer, decimal) 227 | } 228 | 229 | #let process(options, sign, integer, decimal, exponent, power, uncertainty) = { 230 | 231 | let parse-numbers = not (integer == auto and decimal == auto) 232 | if not parse-numbers { 233 | (sign, integer, decimal, exponent, power) = (sign, integer, decimal, exponent, power).map(x => if x != auto { x }) 234 | } 235 | if integer == none { 236 | integer = "" 237 | } 238 | if decimal == none { 239 | decimal = "" 240 | } 241 | 242 | // Exponent options 243 | if options.exponent-mode != "input" { 244 | (integer, decimal, exponent) = exponent-mode(options, integer, decimal, exponent) 245 | } 246 | 247 | // Rounding options 248 | if options.round-mode != none and (options.round-mode != "uncertainty" and uncertainty == none) { // or (options.round-mode == "uncertainty" and uncertainty != none)) { 249 | (sign, integer, decimal) = round-mode(options, sign, integer, decimal) 250 | } 251 | 252 | if options.drop-zero-decimal and decimal.match(non-zero-integer-regex) == none { 253 | decimal = "" 254 | } 255 | 256 | // Minimum digits 257 | if integer.len() < options.minimum-integer-digits { 258 | integer = "0" * (options.minimum-integer-digits - integer.len()) + integer 259 | } 260 | if decimal.len() < options.minimum-decimal-digits { 261 | decimal += "0" * (options.minimum-decimal-digits - decimal.len()) 262 | } 263 | 264 | if options.group-digits in ("all", "decimal", "integer") { 265 | let group-digits = group-digits.with(options) 266 | if options.group-digits in ("all", "integer") and integer.len() >= options.group-minimum-digits { 267 | integer = group-digits(integer) 268 | } 269 | if options.group-digits in ("all", "decimal") and decimal.len() >= options.group-minimum-digits { 270 | decimal = group-digits(decimal, rev: false) 271 | } 272 | } 273 | 274 | 275 | let mantissa = if parse-numbers { 276 | "" 277 | if (integer.len() == 0 or integer != "0") or options.print-zero-integer { 278 | if integer.len() == 0 { 279 | "0" 280 | } else { 281 | integer 282 | } 283 | } 284 | if decimal != "" or options.retain-explicit-decimal-marker { 285 | options.output-decimal-marker 286 | if options.zero-decimal-as-symbol and int(decimal) == 0 { 287 | options.zero-symbol 288 | } else { 289 | decimal 290 | } 291 | } 292 | } else { 293 | options.number 294 | } 295 | 296 | options.print-mantissa = options.print-unity-mantissa or mantissa not in ("1", "") 297 | 298 | options.spacing = if not options.tight-spacing { 299 | sym.space.thin 300 | } 301 | 302 | if options.drop-uncertainty { 303 | uncertainty = none 304 | } else if uncertainty != none { 305 | uncertainty = process-uncertainty(options, uncertainty) 306 | } 307 | 308 | if options.drop-exponent { 309 | exponent = none 310 | } else if exponent != none { 311 | exponent = process-exponent(options, exponent) 312 | } 313 | 314 | if power != none { 315 | power = process-power(options, power) 316 | } 317 | 318 | return (options, sign, mantissa, exponent, power, uncertainty) 319 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /manual.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ" 2 | #import lib: * 3 | #import units: * 4 | #import prefixes: * 5 | 6 | #let example(it, dir) = { 7 | set text(size: 1.25em) 8 | let (a, b) = ( 9 | eval( 10 | "#set text(font: \"Linux Libertine\")\n" + it.text, 11 | mode: "markup", 12 | scope: dictionary(units) + dictionary(prefixes) + dictionary(lib) 13 | ), 14 | raw(it.text.replace("\\\n", "\\\n"), lang: "typ") 15 | ) 16 | block( 17 | breakable: false, 18 | spacing: 0em, 19 | pad( 20 | left: 1em, 21 | stack( 22 | dir: dir, 23 | ..if dir == ltr { 24 | (a, 1fr, par(leading: 0.9em, b), 1fr) 25 | } else { 26 | (b, linebreak(), a) 27 | } 28 | ) 29 | ) 30 | ) 31 | metro-reset() 32 | } 33 | 34 | #show raw.where(lang: "example"): it => { 35 | example(it, ltr) 36 | } 37 | 38 | #show raw.where(lang: "example-stack"): it => { 39 | example(it, ttb) 40 | } 41 | 42 | #show link: set text(blue) 43 | 44 | #let param(term, t, default: none, description) = { 45 | if type(term) != array { 46 | term = (term,) 47 | } 48 | let types = ( 49 | ch: "Choice", 50 | nu: "Number", 51 | li: "Literal", 52 | sw: "Switch", 53 | "in": "Integer" 54 | ) 55 | 56 | 57 | if default != none { 58 | if t == "ch" { 59 | default = repr(default) 60 | } 61 | default = [(default: #raw(default))] 62 | // default = align(top + right, [(default: #raw(default))]) 63 | } 64 | 65 | t = text(types.at(t, default: t), font: "Source Code Pro") 66 | block(breakable: false, { 67 | align(horizon, 68 | stack( 69 | dir: ltr, 70 | term.map(t => strong(t + "\n")).join(), 71 | h(0.6em), 72 | t, 73 | 1fr, 74 | default 75 | ) 76 | ) 77 | block(pad(description, left: 2em), above: 0.65em) 78 | }) 79 | } 80 | 81 | #align(center)[ 82 | #text(16pt)[Metro] 83 | 84 | #link("https://github.com/fenjalien")[fenjalien] and #link("https://github.com/Mc-Zen")[Mc-Zen] \ 85 | https://github.com/fenjalien/metro \ 86 | Version 0.3.0 \ 87 | Requires Typst 0.11+ 88 | ] 89 | 90 | #outline(indent: auto) 91 | 92 | #pagebreak() 93 | 94 | #set heading(numbering: "1.1") 95 | 96 | = Introduction 97 | 98 | The Metro package aims to be a port of the Latex package siunitx. It allows easy typesetting of numbers and units with options. This package is very early in development and many features are missing, so any feature requests or bug reports are welcome! 99 | 100 | Metro's name comes from Metrology, the study scientific study of measurement. 101 | 102 | = Usage 103 | #set pad(left: 1em) 104 | 105 | Typst 0.11.0+ is required. You can import the package using the package manager: 106 | ```typ 107 | #import "@preview/metro:0.3.0": * 108 | ``` 109 | Or download the `src` folder and import `lib.typ`: 110 | ```typ 111 | #import "/src/lib.typ": * 112 | ``` 113 | 114 | // The package provdides the functions: 115 | // - `#ang(angle, ..options)` 116 | // - `#num(number, ..options)` 117 | // - `#unit(unit, ..options)` 118 | // - `#qty(number, unit, ..options)` 119 | // - `#num-list(numbers, ..options)` 120 | // - `#num-product(numbers, ..options)` 121 | // - `#num-range(number1, number2, ..options)` 122 | // - `#qty-list(numbers, unit, ..options)` 123 | // - `#qty-product(numbers, unit, ..options)` 124 | // - `#qty-range(number1, number2, unit, ..options)` 125 | // - `#complex(number, ..options)` 126 | // - `#metro-setup(..options)` 127 | 128 | == Options 129 | ```typ 130 | #metro-setup(..options) 131 | ``` 132 | 133 | All provided functions in this package have options that can control how they parse, process and print items. They can normally be given as keyword arguments directly to the function, but this can get tedious if you want the same options to apply throughout the document. You can instead use the `metro-setup` function. Any options given as keyword arguments will then be applied to the relevant subsequent functions in the document. 134 | 135 | All options and function arguments will use the following types: 136 | / Literal: Takes the given value directly. Input type is a string, content and sometimes a number. 137 | / Switch: On-off switches. Input type is a boolean. 138 | / Choice: Takes a limited number of choices, which are described separately for each option. Input type is a string. 139 | / Number: A float or integer. 140 | / Integer: An integer. 141 | 142 | 143 | #pagebreak() 144 | == Numbers 145 | ```typ 146 | #num(number, e: none, pm: none, pwr: none, ..options) 147 | ``` 148 | 149 | Parses, processes then prints a number. The number can be given as an integer, a float, a string, as some plain content or math content! The different forms of input should extend to all other functions with arguments that take a number, they will be parsed all the same. However it should be noted that: 150 | - When giving a number as an integer or float with an exponent in the number, it will not be seen by Metro (e.g. `3.4e3` will be seen as `3400` and not "3.4 with an exponent of 3"). 151 | - When using one of Metro's function within math mode, Typst considers dashes as subtraction symbols which breaks identifier names. So any options with dashes will not be able to be used when in math mode. 152 | 153 | ```example 154 | #num(123)\ 155 | #num("1234")\ 156 | #num[12345]\ 157 | $num(0.123)$\ 158 | #num("0,1234")\ 159 | #num[.12345]\ 160 | #num(e: -4)[3.45]\ 161 | #num("-1", e: 10, print-unity-mantissa: false) 162 | ``` 163 | 164 | #param("number", "li")[ 165 | The number to format. 166 | ] 167 | #param("pm", "li", default: "none", [ 168 | The uncertainty of the number. 169 | ]) 170 | #param("e", "li", default: "none", [ 171 | The exponent of the number. It can also be given as an integer in the number argument when it is of type string or content. It should be prefixed with an "e" or "E". 172 | ```example 173 | #num("1e10")\ 174 | #num[1E10] 175 | ``` 176 | ]) 177 | #param("pwr", "li", default: "none", [ 178 | The power of the number, it will be attached to the top. No processing is currently done to the power. It can also be passed as an integer in the number parameter when it is of type string or content. It should be prefixed after the exponent with an "^". 179 | ```example 180 | #num("1^2")\ 181 | $num(1^2)$ 182 | ``` 183 | ]) 184 | 185 | 186 | === Options 187 | ==== Parsing 188 | 189 | #param("input-decimal-markers", "Array", default: "('\.', ',')")[ 190 | An array of characters that indicate the sepration between the integer and decimal parts of a number. More than one inupt decimal marker can be used, it will be converted by the package to the appropriate output marker. 191 | ] 192 | 193 | #param("retain-explicit-decimal-marker", "sw", default: "false")[ 194 | Allows a trailing decimal marker with no decimal part present to be printed. 195 | ```example 196 | #num[10.]\ 197 | #num(retain-explicit-decimal-marker: true)[10.] 198 | ``` 199 | ] 200 | 201 | #param("retain-explicit-plus", "sw", default: "false")[ 202 | Allows a leading plus sign to be printed. 203 | ```example 204 | #num[+345]\ 205 | #num(retain-explicit-plus: true)[+345] 206 | ``` 207 | ] 208 | 209 | #param("retain-negative-zero", "sw", default: "false")[ 210 | Allows a negative sign on an entirely zero value. 211 | ```example 212 | #num[-0]\ 213 | #num(retain-negative-zero: true)[-0] 214 | ``` 215 | ] 216 | 217 | #param("parse-numbers", "sw", default: "auto")[ 218 | Turns the entire parsing system on and off. It allows the use of arbitrary values in numbers. When the option is `auto`, numbers will be attempt to be parsed but will quietly stop if it fails to do so. The number will then be printed as given. If the option is `false`, no parsing will even be attempted. If `true`, Metro will panic if the number cannot be parsed. 219 | 220 | ```example 221 | $num(sqrt(3))$\ 222 | #metro-setup(parse-numbers: false) 223 | $num(sqrt(4))$\ 224 | #metro-setup(parse-numbers: true) 225 | // Will panic: 226 | // $num(sqrt(5))$\ 227 | ``` 228 | ] 229 | 230 | ==== Post Processing 231 | 232 | #param("drop-exponent", "sw", default: "false", [ 233 | When `true` the exponent will be dropped (_after_ the processing of exponent) 234 | 235 | ```example 236 | #num("0.01e3")\ 237 | #num("0.01e3", drop-exponent: true) 238 | ``` 239 | ]) 240 | 241 | #param("drop-uncertainty", "sw", default: "false")[ 242 | When `true` the uncertainty will be dropped. 243 | ```example 244 | #num("0.01", pm: 0.02)\ 245 | #num("0.01", pm: 0.02, drop-uncertainty: true)\ 246 | ``` 247 | ] 248 | 249 | #param("drop-zero-decimal", "sw", default: "false")[ 250 | When `true`, if the decimal is zero it will be dropped before setting the minimum numbers of digits. 251 | 252 | ```example 253 | #num[2.1]\ 254 | #num[2.0]\ 255 | #metro-setup(drop-zero-decimal: true) 256 | #num[2.1]\ 257 | #num[2.0]\ 258 | ``` 259 | ] 260 | 261 | #param("exponent-mode", "ch", default: "input")[ 262 | How to convert the number to scientific notation. Note that the calculated exponent will be added to the given exponent for all options. 263 | 264 | / input: Does not perform any conversions, the exponent will be displayed as given. 265 | / scientific: Converts the number such that the integer will always be a single digit. 266 | / fixed: Convert the number to use the exponent value given by the `fixed-exponent` option. 267 | / engineering: Converts the number such that the exponent will be a multiple of three. 268 | / threshold: Like the `scientific` option except it will only convert the number when the exponent would be outside the range given by the `exponent-thresholds` option. 269 | 270 | ```example 271 | #let nums = [ 272 | #num[0.001]\ 273 | #num[0.0100]\ 274 | #num[1200]\ 275 | ] 276 | #nums 277 | #metro-setup(exponent-mode: "scientific") 278 | #nums 279 | #metro-setup(exponent-mode: "engineering") 280 | #nums 281 | #metro-setup(exponent-mode: "fixed", fixed-exponent: 2) 282 | #nums 283 | ``` 284 | #metro-reset() 285 | ] 286 | 287 | #param("exponent-thresholds", "Array", default: "(-3, 3)")[ 288 | Used to control the range of exponents that won't trigger when the `exponent-mode` is "threshold". The first value is the minimum inclusive, and the last value is the maximum inclusive. 289 | 290 | ```example-stack 291 | #let inputs = ( 292 | "0.001", 293 | "0.012", 294 | "0.123", 295 | "1", 296 | "12", 297 | "123", 298 | "1234" 299 | ) 300 | 301 | #table( 302 | columns: (auto,)*3, 303 | [Input], [Threshold $-3:3$], [Threshold $-2:2$], 304 | ..for i in inputs {( 305 | num(i), 306 | num(i, exponent-mode: "threshold"), 307 | num(i, exponent-mode: "threshold", exponent-thresholds: (-2, 2)), 308 | )} 309 | ) 310 | ``` 311 | ] 312 | 313 | #param("fixed-exponent", "Integer", default: "0")[ 314 | The exponent value to use when `exponent-mode` is "fixed". When zero, this may be used to remove scientific notation from the input. 315 | 316 | ```example 317 | #num("1.23e4")\ 318 | #num("1.23e4", exponent-mode: "fixed", fixed-exponent: 0) 319 | ``` 320 | ] 321 | 322 | #param("round-mode", "ch", default: "none")[ 323 | How the package should round numerical input. 324 | 325 | / none: No rounding is performed. 326 | ```example 327 | #num(1.23456)\ 328 | #num(14.23) 329 | ``` 330 | / figures: Round to a number of significant figures. 331 | ```example 332 | #metro-setup(round-mode: "figures") 333 | #num(1.23456)\ 334 | #num(14.23) 335 | ``` 336 | / places: Round to a number of decimal places. 337 | ```example 338 | #metro-setup(round-mode: "places") 339 | #num(1.23456)\ 340 | #num(14.23) 341 | ``` 342 | ] 343 | 344 | #param("round-precision", "Integer", default: "2")[ 345 | Controls the number of significant figures or decimal places to round to. 346 | ```example 347 | #metro-setup(round-mode: "places", round-precision: 3) 348 | #num(1.23456)\ 349 | #num(14.23)\ 350 | #metro-setup(round-mode: "figures", round-precision: 3) 351 | #num(1.23456)\ 352 | #num(14.23)\ 353 | ``` 354 | ] 355 | 356 | #param("round-pad", "sw", default: "true")[ 357 | Controls when rounding may "extend" a short number to more digits (or figures). 358 | ```example 359 | #metro-setup(round-mode: "figures", round-precision: 4) 360 | #num(12.3)\ 361 | #num(12.3, round-pad: false)\ 362 | ``` 363 | ] 364 | 365 | #param("round-direction", "ch", default: "nearest")[ 366 | Determines which direction a value is rounded toward. 367 | 368 | / nearest: Gives the common outcome that values round depending on whether the preceding digit is greater or less than 5. 369 | ```example 370 | #metro-setup(round-mode: "places") 371 | #num(0.054)\ 372 | #num(0.046) 373 | ``` 374 | / down: Values are always rounded down. It may be thought of as "truncation". 375 | ```example 376 | #metro-setup(round-mode: "places", round-direction: "down") 377 | #num(0.054)\ 378 | #num(0.046) 379 | ``` 380 | / up: Values are always rounded up. 381 | ```example 382 | #metro-setup(round-mode: "places", round-direction: "up") 383 | #num(0.054)\ 384 | #num(0.046) 385 | ``` 386 | ] 387 | 388 | #param("round-half", "ch", default: "up")[ 389 | Determines how numbers that are exactly half are rounded to the the `"nearest"`. 390 | 391 | / up: The number is rounded up. 392 | ```example 393 | #metro-setup(round-mode: "figures", round-precision: 1) 394 | #num(0.055)\ 395 | #num(0.045)\ 396 | ``` 397 | / even: The number is rounded to the nearest even part. 398 | ```example 399 | #metro-setup( 400 | round-mode: "figures", 401 | round-precision: 1, 402 | round-half: "even" 403 | ) 404 | #num(0.055)\ 405 | #num(0.045)\ 406 | ``` 407 | ] 408 | 409 | #param("round-minimum", "nu", default: "0")[ 410 | There are cases in which rounding will result in the number reaching zero. It may be desirable to show results as below a threshold value. This can be achieved by setting this option to the threshold value. There will be no effect when rounding to a number of significant figures as it is not possible to obtain the value zero in these cases. 411 | 412 | ```example 413 | #metro-setup(round-mode: "places") 414 | #num(0.0055)\ 415 | #num(0.0045)\ 416 | #metro-setup(round-minimum: 0.01) 417 | #num(0.0055)\ 418 | #num(0.0045)\ 419 | ``` 420 | ] 421 | 422 | #param("round-zero-positive", "sw", default: "true")[ 423 | When rounding negative numbers to a fixed number of places, a zero value may result. Usually this is expressed as an unsigned value, but in some cases retaining the negative sign may be desirable. This behaviour can be controlled using this option. 424 | 425 | ```example 426 | #metro-setup(round-mode: "places") 427 | #num(-0.001)\ 428 | #metro-setup(round-zero-positive: false) 429 | #num(-0.001) 430 | ``` 431 | ] 432 | 433 | #param("minimum-decimal-digits", "Integer", default: "0")[ 434 | May be used to pad the decimal component of a number to a given size. 435 | ```example 436 | #num(0.123)\ 437 | #num(0.123, minimum-decimal-digits: 2)\ 438 | #num(0.123, minimum-decimal-digits: 4) 439 | ``` 440 | ] 441 | 442 | #param("minimum-integer-digits", "Integer", default: "0")[ 443 | May be used to pad the integer component of a number to a given size. 444 | ```example 445 | #num(123)\ 446 | #num(123, minimum-integer-digits: 2)\ 447 | #num(123, minimum-integer-digits: 4) 448 | ``` 449 | ] 450 | 451 | ==== Printing 452 | 453 | #param("group-digits", "ch", default: "all")[ 454 | Whether to group digits into blocks to increase the ease of reading of numbers. Takes the values `all`, `none`, `decimal` and `integer`. Grouping can be acitivated separately for the integer and decimal parts of a number using the appropriately named values. 455 | 456 | ```example 457 | #num[12345.67890]\ 458 | #num(group-digits: "none")[12345.67890]\ 459 | #num(group-digits: "decimal")[12345.67890]\ 460 | #num(group-digits: "integer")[12345.67890] 461 | ``` 462 | ] 463 | 464 | #param("group-separator", "li", default: "sym.space.thin")[ 465 | The separator to use between groups of digits. 466 | ```example 467 | #num[12345]\ 468 | #num(group-separator: ",")[12345]\ 469 | #num(group-separator: " ")[12345] 470 | ``` 471 | ] 472 | 473 | #param("group-minimum-digits", "in", default: "5")[ 474 | Controls how many digits must be present before grouping is applied. The number of digits is considered separately for the integer and decimal parts of the number: grouping does not "cross the boundary". 475 | 476 | ```example 477 | #num[1234]\ 478 | #num[12345]\ 479 | #num(group-minimum-digits: 4)[1234]\ 480 | #num(group-minimum-digits: 4)[12345]\ 481 | #num[1234.5678]\ 482 | #num[12345.67890]\ 483 | #num(group-minimum-digits: 4)[1234.5678]\ 484 | #num(group-minimum-digits: 4)[12345.67890] 485 | ``` 486 | ] 487 | 488 | #param("digit-group-size", "in", default: "3")[ 489 | Controls the number of digits in each group. Finer control can be achieved using `digit-group-first-size` and `digit-group-other-size`: the first group is that immediately by the decimal point, the other value applies to the second and subsequent groupings. 490 | 491 | ```example 492 | #num[1234567890]\ 493 | #num(digit-group-size: 5)[1234567890]\ 494 | #num(digit-group-other-size: 2)[1234567890] 495 | ``` 496 | ] 497 | 498 | #param("output-decimal-marker", "li", default: ".")[ 499 | The decimal marker used in the output. This can differ from the input marker. 500 | 501 | ```example 502 | #num(1.23)\ 503 | #num(output-decimal-marker: ",")[1.23] 504 | ``` 505 | ] 506 | 507 | #param("exponent-base", "li", default: "10")[ 508 | The base of an exponent. 509 | ```example 510 | #num(exponent-base: "2", e: 2)[1] 511 | ``` 512 | ] 513 | 514 | #param("exponent-product", "li", default: "sym.times")[ 515 | The symbol to use as the product between the number and its exponent. 516 | ```example 517 | #num(e: 2, exponent-product: sym.times)[1]\ 518 | #num(e: 2, exponent-product: sym.dot)[1] 519 | ``` 520 | ] 521 | 522 | #param("output-exponent-marker", "li", default: "none")[ 523 | When not `none`, the value stored will be used in place of the normal product and base combination. 524 | ```example 525 | #num(output-exponent-marker: "e", e: 2)[1]\ 526 | #num(output-exponent-marker: "E", e: 2)[1] 527 | ``` 528 | ] 529 | 530 | #param("bracket-ambiguous-numbers", "sw", default: "true")[ 531 | There are certain combinations of numerical input which can be ambiguous. This can be corrected by adding brackets in the appropriate place. 532 | 533 | ```example 534 | #num(e: 4, pm: 0.3)[1.2]\ 535 | #num(bracket-ambiguous-numbers: false, e: 4, pm: 0.3)[1.2] 536 | ``` 537 | ] 538 | 539 | #param("bracket-negative-numbers", "sw", default: "false")[ 540 | Whether or not to display negative numbers in brackets. 541 | 542 | ```example 543 | #num[-15673]\ 544 | #num(bracket-negative-numbers: true)[-15673] 545 | ``` 546 | ] 547 | 548 | #param("tight-spacing", "sw", default: "false")[ 549 | Compresses spacing where possible. 550 | 551 | ```example 552 | #num(e: 3)[2]\ 553 | #num(e: 3, tight-spacing: true)[2] 554 | ``` 555 | ] 556 | 557 | #param("print-implicit-plus", "sw", default: "false")[ 558 | Force the number to have a sign. This is used if given and if no sign was present in the input. 559 | ```example 560 | #num(345)\ 561 | #num(345, print-implicit-plus: true) 562 | ``` 563 | It is possible to set this behaviour for the exponent and mantissa independently using `print-mantissa-implicit-plus` and `print-exponent-implicit-plus` respectively. 564 | ] 565 | 566 | #param("print-unity-mantissa", "sw", default: "true")[ 567 | Controls the printing of a mantissa of 1. 568 | ```example 569 | #num(e: 4)[1]\ 570 | #num(e: 4, print-unity-mantissa: false)[1] 571 | ``` 572 | ] 573 | 574 | #param("print-zero-exponent", "sw", default: "false")[ 575 | Controls the printing of an exponent of 0. 576 | ```example 577 | #num(e: 0)[444]\ 578 | #num(e: 0, print-zero-exponent: true)[444] 579 | ``` 580 | ] 581 | 582 | #param("print-zero-integer", "sw", default: "true")[ 583 | Controls the printing of an integer component of 0. 584 | ```example 585 | #num(0.123)\ 586 | #num(0.123, print-zero-integer: false) 587 | ``` 588 | ] 589 | 590 | #param("zero-decimal-as-symbol", "sw", default: "false")[ 591 | Whether to show entirely zero decimal parts as a symbol. Uses the symbol stroed using `zero-symbol` as the replacement. 592 | 593 | ```example 594 | #num[123.00]\ 595 | #metro-setup(zero-decimal-as-symbol: true) 596 | #num[123.00]\ 597 | #num(zero-symbol: [[#sym.bar.h]])[123.00] 598 | ``` 599 | ] 600 | 601 | #param("zero-symbol", "li", default: "sym.bar.h")[ 602 | The symbol to use when `zero-decimal-as-symbol` is `true`. 603 | ] 604 | 605 | #pagebreak() 606 | == Units 607 | 608 | ```typ 609 | #unit(unit, ..options) 610 | ``` 611 | 612 | Typsets a unit and provides full control over output format for the unit. The type passed to the function can be either a string or some math content. 613 | 614 | When using the function in math mode, Typst accepts single characters but multiple characters together are expected to be variables. So Metro defines units and prefixes which be can imported to be used. #pad[ 615 | ```typ 616 | #import "@preview/metro:0.2.0": unit, units, prefixes 617 | #unit($units.kg m/s^2$) 618 | // because `units` and `prefixes` here are modules you can import what you need 619 | #import units: gram, metre, second 620 | #import prefixes: kilo 621 | $unit(kilo gram metre / second^2)$ 622 | // You can also just import everything instead 623 | #import units: * 624 | #import prefixes: * 625 | $unit(joule / mole / kelvin)$ 626 | ``` 627 | 628 | #unit($units.kg m/s^2$)\ 629 | $unit(kilo gram metre / second^2)$\ 630 | $unit(joule / mole / kelvin)$ 631 | ] 632 | 633 | When using strings there is no need to import any units or prefixes as the string is parsed. Additionally several variables have been defined to allow the string to be more human readable. You can also use the same syntax as with math mode. 634 | ```example-stack 635 | // String 636 | #unit("kilo gram metre per square second")\ 637 | // Math equivalent 638 | #unit($kilo gram metre / second^2$)\ 639 | // String using math syntax 640 | #unit("kilo gram metre / second^2") 641 | ``` 642 | 643 | `per` used as in "metres _per_ second" is equivalent to a slash `/`. When using this in a string you don't need to specify a numerator. 644 | ```example-stack 645 | #unit("metre per second")\ 646 | $unit(metre/second)$\ 647 | 648 | #unit("per square becquerel")\ 649 | #unit("/becquerel^2") 650 | ``` 651 | 652 | `square` and `cubic` apply their respective powers to the units after them, while `squared` and `cubed` apply to units before them. 653 | ```example-stack 654 | #unit("square becquerel")\ 655 | #unit("joule squared per lumen")\ 656 | #unit("cubic lux volt tesla cubed") 657 | ``` 658 | 659 | Generic powers can be inserted using the `tothe` and `raiseto` functions. `tothe` specifically is equivalent to using caret `^`. 660 | ```example-stack 661 | #unit("henry tothe(5)")\ 662 | #unit($henry^5$)\ 663 | #unit("henry^5") 664 | 665 | #unit("raiseto(4.5) radian")\ 666 | #unit($radian^4.5$)\ 667 | #unit("radian^4.5") 668 | ``` 669 | 670 | You can also use the `sqrt` function for half powers. If you want to maintain the square root, you must set the `power-half-as-sqrt` option. 671 | 672 | ```example 673 | $unit(sqrt(H))$\ 674 | #unit("sqrt(H)", power-half-as-sqrt: true)\ 675 | ``` 676 | 677 | Generic qualifiers are available using the `of` function which is equivalent to using an underscore `_`. Note that when using an underscore for qualifiers in a string with a space, to capture the whole qualifier use brackets `()`. 678 | ```example-stack 679 | #unit("kilogram of(metal)")\ 680 | #unit($kilogram_"metal"$)\ 681 | #unit("kilogram_metal") 682 | 683 | #metro-setup(qualifier-mode: "bracket") 684 | #unit("milli mole of(cat) per kilogram of(prod)")\ 685 | #unit($milli mole_"cat" / kilogram_"prod"$)\ 686 | #unit("milli mole_(cat) / kilogram_(prod)") 687 | ``` 688 | 689 | === Options 690 | 691 | #param("inter-unit-product", "li", default: "sym.space.thin", [ 692 | The separator between each unit. The default setting is a thin space: another common choice is a centred dot. 693 | ```example 694 | #unit("farad squared lumen candela")\ 695 | #unit("farad squared lumen candela", inter-unit-product: $dot.c$) 696 | ``` 697 | ]) 698 | 699 | 700 | #param("per-mode", "ch", default: "power", [ 701 | Use to alter the handling of `per`. 702 | 703 | / power: Reciprocal powers 704 | ```example 705 | #unit("joule per mole per kelvin")\ 706 | #unit("metre per second squared") 707 | ``` 708 | 709 | / fraction: Uses the `math.frac` function (also known as `$ / $`) to typeset positive and negative powers of a unit separately. 710 | ```example 711 | #unit("joule per mole per kelvin", per-mode: "fraction")\ 712 | #unit("metre per second squared", per-mode: "fraction") 713 | ``` 714 | 715 | / symbol: Separates the two parts of a unit using the symbol in `per-symbol`. This method for displaying units can be ambiguous, and so brackets are added unless `bracket-unit-denominator` is set to `false`. Notice that `bracket-unit-denominator` only applies when `per-mode` is set to symbol. 716 | ```example 717 | #metro-setup(per-mode: "symbol") 718 | #unit("joule per mole per kelvin")\ 719 | #unit("metre per second squared") 720 | ``` 721 | ]) 722 | 723 | #param("per-symbol", "li", default: "sym.slash")[ 724 | The symbol to use to separate the two parts of a unit when `per-symbol` is `"symbol"`. 725 | ```example-stack 726 | #unit("joule per mole per kelvin", per-mode: "symbol", per-symbol: [ div ]) 727 | ``` 728 | ] 729 | 730 | #param("bracket-unit-denominator", "sw", default: "true")[ 731 | Whether or not to add brackets to unit denominators when `per-symbol` is `"symbol"`. 732 | ```example-stack 733 | #unit("joule per mole per kelvin", per-mode: "symbol", bracket-unit-denominator: false) 734 | ``` 735 | ] 736 | 737 | #metro-setup(per-mode: "power") 738 | 739 | #param("sticky-per", "sw", default: "false")[ 740 | Normally, `per` applies only to the next unit given. When `sticky-per` is `true`, this behaviour is changed so that `per` applies to all subsequent units. 741 | ```example 742 | #unit("pascal per gray henry")\ 743 | #unit("pascal per gray henry", sticky-per: true) 744 | ``` 745 | ] 746 | 747 | #param("qualifier-mode", "ch", default: "subscript")[ 748 | Sets how unit qualifiers can be printed. 749 | / subscript: 750 | ```example-stack 751 | #unit("kilogram of(pol) squared per mole of(cat) per hour") 752 | ``` 753 | 754 | / bracket: 755 | ```example-stack 756 | #unit("kilogram of(pol) squared per mole of(cat) per hour", qualifier-mode: "bracket") 757 | ``` 758 | 759 | / combine: Powers can lead to ambiguity and are automatically detected and brackets added as appropriate. 760 | ```example 761 | #unit("deci bel of(i)", qualifier-mode: "combine") 762 | ``` 763 | 764 | / phrase: Used with `qualifier-phrase`, which allows for example a space or other linking text to be inserted. 765 | ```example-stack 766 | #metro-setup(qualifier-mode: "phrase", qualifier-phrase: sym.space) 767 | #unit("kilogram of(pol) squared per mole of(cat) per hour")\ 768 | #metro-setup(qualifier-phrase: [ of ]) 769 | #unit("kilogram of(pol) squared per mole of(cat) per hour") 770 | ``` 771 | ] 772 | 773 | #param("power-half-as-sqrt", "sw", default: "false")[ 774 | When `true` the power of $0.5$ is shown by giving the unit sumbol as a square root. This 775 | ```example 776 | #unit("Hz tothe(0.5)")\ 777 | #unit("Hz tothe(0.5)", power-half-as-sqrt: true) 778 | ``` 779 | ] 780 | 781 | #metro-reset() 782 | 783 | #pagebreak() 784 | == Quantities 785 | 786 | ```typ 787 | #qty(number, unit, ..options) 788 | ``` 789 | 790 | This function combines the functionality of `num` and `unit` and formats the number and unit together. The `number` and `unit` arguments work exactly like those for the `num` and `unit` functions respectively. 791 | 792 | ```example 793 | #qty(1.23, "J / mol / kelvin")\ 794 | $qty(.23, candela, e: 7)$\ 795 | #qty(1.99, "per kilogram", per-mode: "symbol")\ 796 | #qty(1.345, "C/mol", per-mode: "fraction") 797 | ``` 798 | 799 | === Options 800 | 801 | #param("allow-quantity-breaks", "sw", default: "false")[ 802 | Controls whether the combination of the number and unit can be split across lines. 803 | ```example-stack 804 | #box(width: 3.25cm)[ 805 | Some filler text #qty(10, "m")\ 806 | #metro-setup(allow-quantity-breaks: true) 807 | Some filler text #qty(10, "m") 808 | ] 809 | ``` 810 | ] 811 | 812 | #param("quantity-product", "li", default: "sym.space.thin")[ 813 | The product symbol between the number and unit. 814 | ```example-stack 815 | #qty(2.67, "farad")\ 816 | #qty(2.67, "farad", quantity-product: sym.space)\ 817 | #qty(2.67, "farad", quantity-product: none) 818 | ``` 819 | ] 820 | 821 | #param("separate-uncertainty", "ch", default: "bracket")[ 822 | When a number has multiple parts, then the unit must apply to all parts of the number. 823 | 824 | / bracket: Places the entire numerical part in brackets and use a single unit symbol. 825 | ```example 826 | #qty(12.3, "kg", pm: 0.4) 827 | ``` 828 | 829 | / repeat: Prints the unit for each part of the number. 830 | ```example 831 | #qty(12.3, "kg", pm: 0.4, separate-uncertainty: "repeat") 832 | ``` 833 | 834 | / single: Prints only one unit symbol: mathematically incorrect. 835 | ```example 836 | #qty(12.3, "kg", pm: 0.4, separate-uncertainty: "single") 837 | ``` 838 | ] 839 | 840 | #pagebreak() 841 | == List, Products and Ranges 842 | 843 | ```typ 844 | #num-list(..numbers-options) 845 | ``` 846 | 847 | Lists of numbers may be processed using the `num-list` function. Each number should be given as a positional argument. The numbers are formatted using `num`. 848 | 849 | ```example 850 | #num-list(10, 30, 50, 70) 851 | ``` 852 | 853 | ```typ 854 | #num-product(..numbers-options) 855 | ``` 856 | 857 | Runs of products can be created using the `num-product` function. It acts in the same way `num-list` does. 858 | 859 | ```example 860 | #num-product(10, 30) 861 | ``` 862 | 863 | ```typ 864 | #num-range(number1, number2, ..options) 865 | ``` 866 | 867 | Simple ranges of numbers can be handled using the `num-range` function. It inserts a phrase or other text between the two numbers. 868 | 869 | ```example 870 | #num-range(10, 30) 871 | ``` 872 | 873 | The above list, product and range functions also have a `qty` variant where the last positional argument will be considered as a unit. 874 | 875 | ```example 876 | #qty-list(10, 30, 45, metre)\ 877 | #qty-product(10, 30, 45, metre)\ 878 | #qty-range(10, 30, metre)\ 879 | ``` 880 | 881 | The above function names cannot be used in math mode, instead equivalently named functions are provided that have the dash removed (e.g. `num-list` and `numlist`). 882 | 883 | === Options 884 | 885 | #param("list-separator", "li", default: "[, ]")[ 886 | The separator to place between each item in the a list of numbers. 887 | 888 | ```example 889 | #num-list(0.1, 0.2, 0.3) \ 890 | #num-list( 891 | list-separator: [; ], 892 | 0.1, 0.2, 0.3, 893 | ) 894 | ``` 895 | ] 896 | 897 | #param("list-final-separator", "li", default: "[ and ]")[ 898 | The separator before the last item of a list. 899 | 900 | ```example 901 | #num-list( 902 | list-final-separator: [, ], 903 | 0.1, 0.2, 0.3 904 | ) \ 905 | #num-list( 906 | list-separator: [ and ], 907 | list-final-separator: [ and ], 908 | 0.1, 0.2, 0.3 909 | ) 910 | ``` 911 | ] 912 | 913 | #param("list-pair-separator", "li", default: "[ and ]")[ 914 | The to use for exactly two items of a list. 915 | 916 | ```example 917 | #num-list(0.1, 0.2) \ 918 | #num-list( 919 | list-pair-separator: [, and ], 920 | 0.1, 0.2 921 | ) 922 | ``` 923 | ] 924 | 925 | #param("product-mode", "ch", default: "symbol")[ 926 | Products of numbers can be output using either a product symbol or a phrase. 927 | 928 | / symbol: The symbol in `product-symbol` is used. 929 | ```example 930 | #num-product(5, 100, 2) 931 | ``` 932 | / phrase: The phrase in `product-phrase` is used. 933 | ```example 934 | #num-product(5, 100, 2, product-mode: "phrase") 935 | ``` 936 | ] 937 | #param("product-symbol", "li", default: "sym.times")[ 938 | The symbol to use when `product-mode` is `"symbol"`. 939 | 940 | ```example 941 | #num-product(5, 100, 2, product-symbol: sym.dot.c) 942 | ``` 943 | ] 944 | 945 | #param("product-phrase", "li", default: "[ by ]")[ 946 | The phrase to use when `product-mode` is `"phrase"`. 947 | 948 | ```example 949 | #num-product(5, 100, 2, product-symbol: [ BY ]) 950 | ``` 951 | ] 952 | 953 | #param("range-open-phrase", "li", default: "none")[ 954 | The phrase to open ranges with. 955 | 956 | ```example 957 | #num-range(10, 12)\ 958 | #num-range(5, 100, range-open-phrase: "from ") 959 | ``` 960 | ] 961 | 962 | #param("range-phrase", "li", default: "[ to ]")[ 963 | The word or symbol to be inserted between the two entries of the range. 964 | 965 | ```example 966 | #num-range(5, 100)\ 967 | #num-range(5, 100, range-phrase: sym.dash)\ 968 | ``` 969 | ] 970 | 971 | #param(("list-exponents", "product-exponents", "range-exponents"), "ch", default: "individual")[ 972 | Controls how lists, products and ranges can be "compressed" by combining the exponent parts. 973 | 974 | / individual: Leaves the exponent with the matching value. 975 | ```example 976 | #num-list("5e3", "7e3", "9e3", "1e4")\ 977 | #num-product("5e3", "7e3", "9e3", "1e4")\ 978 | #num-range("5e3", "7e3") 979 | ``` 980 | / combine: The first exponent entry is taken and applied to all other entries, with the exponent itself placed at the end. 981 | ```example 982 | #metro-setup( 983 | list-exponents: "combine", 984 | product-exponents: "combine", 985 | range-exponents: "combine", 986 | ) 987 | #num-list("5e3", "7e3", "9e3", "1e4")\ 988 | #num-product("5e3", "7e3", "9e3", "1e4")\ 989 | #num-range("5e3", "7e3") 990 | ``` 991 | / combine-bracket: Like `"combine"` but the list, product or range is wrapped in brackets, with the exponent outside. 992 | ```example 993 | #metro-setup( 994 | list-exponents: "combine-bracket", 995 | product-exponents: "combine-bracket", 996 | range-exponents: "combine-bracket", 997 | ) 998 | #num-list("5e3", "7e3", "9e3", "1e4")\ 999 | #num-product("5e3", "7e3", "9e3", "1e4")\ 1000 | #num-range("5e3", "7e3") 1001 | ``` 1002 | ] 1003 | #param(("list-units", "product-units", "range-units"), "ch", default: "repeat")[ 1004 | Determines how `qty-list`, `qty-product` and `qty-range` functions print units. 1005 | 1006 | / repeat: Each number will be printed with a unit. 1007 | ```example 1008 | #qty-list(2, 4, 6, 8, tesla)\ 1009 | #qty-product(2, 4, metre)\ 1010 | #qty-range(2, 4, degreeCelsius) 1011 | ``` 1012 | 1013 | / single: The unit will only be placed at the end of the collection. 1014 | ```example 1015 | #metro-setup( 1016 | list-units: "single", 1017 | product-units: "single", 1018 | range-units: "single", 1019 | ) 1020 | #qty-list(2, 4, 6, 8, tesla)\ 1021 | #qty-product(2, 4, metre)\ 1022 | #qty-range(2, 4, degreeCelsius) 1023 | ``` 1024 | 1025 | / bracket: Like `"single"` except brackets are placed around the collection. 1026 | ```example 1027 | #metro-setup( 1028 | list-units: "bracket", 1029 | product-units: "bracket", 1030 | range-units: "bracket", 1031 | ) 1032 | #qty-list(2, 4, 6, 8, tesla)\ 1033 | #qty-product(2, 4, metre)\ 1034 | #qty-range(2, 4, degreeCelsius) 1035 | ``` 1036 | // no it doesn't, at least not yet 1037 | // The option `product-units` also offers the settings *bracket-power* and *power*. 1038 | // ```example 1039 | // #qty-product(2, 4, metre, product-units: "bracket-power")\ 1040 | // #qty-product(2, 4, metre, product-units: "power")\ 1041 | // ``` 1042 | ] 1043 | 1044 | #param(("list-open-bracket", "product-open-bracket", "range-open-bracket"), "li", default: "sym.paren.l")[ 1045 | The opening bracket to be used when the collection is placed in brackets. 1046 | ] 1047 | 1048 | #param(("list-close-bracket", "product-close-bracket", "range-close-bracket"), "li", default: "sym.paren.r")[ 1049 | The closing bracket to be used when the collection is placed in brackets. 1050 | ] 1051 | 1052 | #pagebreak() 1053 | 1054 | == Complex Numbers 1055 | ```typ 1056 | #complex(real, imag, ..unit-options) 1057 | ``` 1058 | 1059 | Typesets the complex number, the first positional argument will be the real component and the second will be the coefficient of the imaginary component. If the second argument is either of the #link("https://typst.app/docs/reference/layout/angle/")[angle type] or ends in "deg" or "rad", the complex number will be considered to be in polar form and the first argument will be the radius. A unit can be optionally given as the third positional argument. 1060 | 1061 | Note that when giving the angle as an angle type in radains, it will be output in degrees by default. This is due to angle types being unit agnostic. This behaviour can be changed with the `complex-angle-unit` option. 1062 | 1063 | === Options 1064 | 1065 | #param("complex-mode", "ch", default: "input")[ 1066 | The format in which complex values are printed. 1067 | 1068 | / input: The complex value is printed as-given. 1069 | ```example 1070 | #complex(1, 1)\ 1071 | #complex(1, 45deg)\ 1072 | ``` 1073 | / cartesian: The output will be formatted in Cartesian form. 1074 | ```example 1075 | #metro-setup(complex-mode: "cartesian") 1076 | #complex(1, 1)\ 1077 | #complex(1, 45deg, round-mode: "places")\ 1078 | ``` 1079 | / polar: The output will be formatted in polar form. 1080 | ```example 1081 | #metro-setup(complex-mode: "polar") 1082 | #complex(1, 1, round-mode: "places", round-pad: false)\ 1083 | #complex(1, 45deg)\ 1084 | ``` 1085 | ] 1086 | 1087 | #param("output-complex-root", "li", default: "math.upright(\"i\")")[ 1088 | The output complex root symbol. 1089 | 1090 | ```example 1091 | #complex(1, 2, output-complex-root: "i")\ 1092 | #complex(1, 2, output-complex-root: "j")\ 1093 | ``` 1094 | ] 1095 | 1096 | #param("complex-root-position", "ch", default: "after-number")[ 1097 | The position of the complex root can be adjusted to place it either before or after the associated numeral in a complex number by using this option. 1098 | 1099 | ```example 1100 | #complex(67, -0.9)\ 1101 | #complex(67, -0.9, complex-root-position: "before-number")\ 1102 | ``` 1103 | ] 1104 | 1105 | #param("complex-angle-unit", "ch", default: "degrees")[ 1106 | The output unit of the angle component of a complex number in polar form. 1107 | ```example 1108 | #complex(1, 1rad, ohm)\ 1109 | #complex(1, 1rad, complex-angle-unit: "radians", ohm) 1110 | ``` 1111 | ] 1112 | 1113 | #param("complex-symbol-angle", "li", default: "sym.angle")[ 1114 | The symbol used to denote the angle of a complex number in polar form. 1115 | ```example 1116 | #complex(1, 1deg, ohm, complex-symbol-angle: math.upright("A")) 1117 | ``` 1118 | ] 1119 | 1120 | #param("complex-symbol-degree", "li", default: "sym.degree")[ 1121 | The symbol use for the units of degrees of a complex number in polar form. 1122 | ```example 1123 | #complex(1, 1deg, ohm, complex-symbol-degree: math.upright("d")) 1124 | ``` 1125 | ] 1126 | 1127 | #param("print-complex-unity", "sw", default: "false")[ 1128 | When the complex part of a number is exactly 1, it is possible to either print or suppress the value. 1129 | 1130 | ```example 1131 | #complex(0, 1, ohm)\ 1132 | #complex(0, 1, ohm, print-complex-unity: true)\ 1133 | ``` 1134 | ] 1135 | 1136 | #pagebreak() 1137 | 1138 | == Angles 1139 | ```typ 1140 | #ang(..ang-options) 1141 | ``` 1142 | 1143 | Typsets angles. The angle can be given as a single decimal number or 2 to 3 positional arguments of degrees, minutes and second, which is called the "arc format" in this document. 1144 | 1145 | ```example 1146 | #ang(10)\ 1147 | #ang(12.3)\ 1148 | #ang("4,5")\ 1149 | #ang(1, 2, 3)\ 1150 | #ang(0, 0, 1)\ 1151 | #ang(10, 0, 0)\ 1152 | #ang(0, 1)\ 1153 | ``` 1154 | 1155 | === Options 1156 | 1157 | #param("angle-mode", "ch", default: "input")[ 1158 | The format in which angles are printed. 1159 | 1160 | / input: The angle is printed as given. 1161 | ```example 1162 | #ang(2.67)\ 1163 | #ang(2, 3, 4)\ 1164 | ``` 1165 | / arc: The output will be formatted as an arc (degrees/minutes/seconds). 1166 | ```example 1167 | #metro-setup(angle-mode: "arc") 1168 | #ang(2.67)\ 1169 | #ang(2,3,4) 1170 | ``` 1171 | / decimal: The output will be formatted as a decimal value. 1172 | ```example 1173 | #metro-setup(angle-mode: "decimal") 1174 | #ang(2.67)\ 1175 | #ang(2,3,4) 1176 | ``` 1177 | ] 1178 | 1179 | #param("number-angle-product", "li", default: "none")[ 1180 | The separator between the number and angle symbol. This is independent of the related `quantity-product` option used by the `qty` function. 1181 | 1182 | ```example 1183 | #ang(2.67)\ 1184 | #ang(2.67, number-angle-product: sym.space) 1185 | ``` 1186 | ] 1187 | 1188 | #param("angle-separator", "li", default: "none")[ 1189 | The separation of the different parts of an angle when printed in arc format. 1190 | ```example 1191 | #ang(6, 7, 6.5)\ 1192 | #ang(6, 7, 6.5, angle-separator: sym.space) 1193 | ``` 1194 | ] 1195 | 1196 | #param("angle-symbol-degree", "li", default: "sym.degree")[ 1197 | The symbol to use for the degree unit of an arc angle. 1198 | ] 1199 | 1200 | #param("angle-symbol-minute", "li", default: "units.arcminute")[ 1201 | The symbol to use for the minute unit of an arc angle. 1202 | ] 1203 | 1204 | #param("angle-symbol-second", "li", default: "sym.arcsecond")[ 1205 | The symbol to use for the second unit of an arc angle. 1206 | 1207 | ```example 1208 | #metro-setup( 1209 | angle-symbol-degree: math.upright("d"), 1210 | angle-symbol-minute: math.upright("m"), 1211 | angle-symbol-second: math.upright("s"), 1212 | ) 1213 | #ang(6, 7, 6.5) 1214 | ``` 1215 | ] 1216 | 1217 | #pagebreak() 1218 | = Meet the Units 1219 | 1220 | The following tables show the currently supported prefixes, units and their abbreviations. Note that unit abbreviations that have single letter commands are not available for import for use in math. This is because math mode already accepts single letter variables. 1221 | 1222 | #{ 1223 | set figure(kind: "Table", supplement: "Table") 1224 | 1225 | let generate(..units) = { 1226 | units.pos().map(x => { 1227 | let (name, command) = if type(x) == "array" { 1228 | x 1229 | } else { 1230 | (x, x) 1231 | } 1232 | (name, raw(command), unit(command)) 1233 | }).join() 1234 | } 1235 | 1236 | let headers = ([Unit], [Command], [Symbol]) 1237 | 1238 | figure( 1239 | table( 1240 | columns: 3, 1241 | stroke: none, 1242 | table.hline(), 1243 | ..headers, 1244 | table.hline(), 1245 | ..generate( 1246 | "ampere", 1247 | "candela", 1248 | "kelvin", 1249 | "kilogram", 1250 | "metre", 1251 | "mole", 1252 | "second" 1253 | ), 1254 | table.hline(), 1255 | ), 1256 | caption: [SI base units.] 1257 | ) 1258 | 1259 | figure( 1260 | table( 1261 | columns: 6, 1262 | stroke: none, 1263 | table.hline(), 1264 | ..headers * 2, 1265 | table.hline(), 1266 | ..generate( 1267 | "becquerel", "newton", 1268 | ("degree Celsius", "degreeCelsius"), "ohm", 1269 | "coulomb", "pascal", 1270 | "farad", "radian", 1271 | "gray", "siemens", 1272 | "hertz", "sievert", 1273 | "henry", "steradian", 1274 | "joule", "tesla", 1275 | "lumen", "volt", 1276 | "katal", "watt", 1277 | "lux", "weber" 1278 | ), 1279 | table.hline() 1280 | ), 1281 | caption: [Coherent derived units in the SI with special names and symbols.] 1282 | ) 1283 | 1284 | figure( 1285 | table( 1286 | columns: 3, 1287 | stroke: none, 1288 | table.hline(), 1289 | ..headers, 1290 | table.hline(), 1291 | ..generate( 1292 | "astronomicalunit", 1293 | "bel", 1294 | "dalton", 1295 | "day", 1296 | "decibel", 1297 | "degree", 1298 | "electronvolt", 1299 | "hectare", 1300 | "hour", 1301 | "litre", 1302 | ("", "liter"), 1303 | ("minute (plane angle)", "arcminute"), 1304 | ("minute (time)", "minute"), 1305 | ("second (plane angle)", "arcsecond"), 1306 | "neper", 1307 | "tonne" 1308 | ), 1309 | table.hline(), 1310 | ), 1311 | caption: [Non-SI units accepted for use with the International System of Units.] 1312 | ) 1313 | 1314 | figure( 1315 | table( 1316 | columns: 3, 1317 | stroke: none, 1318 | table.hline(), 1319 | ..headers, 1320 | table.hline(), 1321 | ..generate( 1322 | "byte", 1323 | ), 1324 | table.hline(), 1325 | ), 1326 | caption: [Non-SI units.] 1327 | ) 1328 | 1329 | figure( 1330 | table( 1331 | columns: 8, 1332 | stroke: none, 1333 | table.hline(), 1334 | ..([Prefix], [Command], [Symbol], [$10^x$]) * 2, 1335 | table.hline(), 1336 | ..(( 1337 | ("quecto", -30), ("deca", 1), 1338 | ("ronto", -27), ("hecto", 2), 1339 | ("yocto", -24), ("kilo", 3), 1340 | ("atto", -21), ("mega", 6), 1341 | ("zepto", -18), ("giga", 9), 1342 | ("femto", -15), ("tera", 12), 1343 | ("pico", -12), ("peta", 15), 1344 | ("nano", -9), ("exa", 18), 1345 | ("micro", -6), ("zetta", 21), 1346 | ("milli", -3), ("yotta", 24), 1347 | ("centi", -2), ("ronna", 27), 1348 | ("deci", -1), ("quetta", 30) 1349 | ).map(x => (x.first(), raw(x.first()), unit(x.first()), num(x.last()))).join()), 1350 | table.hline(), 1351 | ), 1352 | caption: [SI prefixes] 1353 | ) 1354 | figure( 1355 | table( 1356 | columns: 4, 1357 | stroke: none, 1358 | table.hline(), 1359 | [Prefix], [Command], [Symbol], [$2^x$], 1360 | table.hline(), 1361 | ..(( 1362 | ("kibi", 10), 1363 | ("mebi", 20), 1364 | ("gibi", 30), 1365 | ("tebi", 40), 1366 | ("pebi", 50), 1367 | ("exbi", 60), 1368 | ("zebi", 70), 1369 | ("yobi", 80), 1370 | ).map(x => (x.first(), raw(x.first()), unit(x.first()), num(x.last()))).join()), 1371 | table.hline(), 1372 | ), 1373 | caption: [Binary prefixes] 1374 | ) 1375 | 1376 | let ge(..xs) = { 1377 | let xs = xs.pos() 1378 | for i in range(0, xs.len()-1, step: 2) { 1379 | let name = xs.at(i) 1380 | let abbr = xs.at(i+1) 1381 | (name, raw(abbr), unit(abbr)) 1382 | } 1383 | } 1384 | 1385 | page( 1386 | margin: 1cm, 1387 | figure( 1388 | caption: [Unit abbreviations], 1389 | stack( 1390 | dir: ltr, 1391 | table( 1392 | columns: 3, 1393 | stroke: none, 1394 | table.hline(), 1395 | [Unit], [Abbreviation], [Symbol], 1396 | table.hline(), 1397 | ..ge( 1398 | "femtogram", "fg", 1399 | "picogram", "pg", 1400 | "nanogram", "ng", 1401 | "microgram", "ug", 1402 | "milligram", "mg", 1403 | "gram", "g", 1404 | "kilogram", "kg" 1405 | ), 1406 | table.hline(), 1407 | ..ge( 1408 | "picometre", "pm", 1409 | "nanometre", "nm", 1410 | "micrometre", "um", 1411 | "millimetre", "mm", 1412 | "centimetre", "cm", 1413 | "decimetre", "dm", 1414 | "metre", "m", 1415 | "kilometre", "km", 1416 | ), 1417 | table.hline(), 1418 | ..ge( 1419 | "attosecond", "as", 1420 | "femtosecond", "fs", 1421 | "picosecond", "ps", 1422 | "nanosecond", "ns", 1423 | "microsecond", "us", 1424 | "millisecond", "ms", 1425 | "second", "s", 1426 | ), 1427 | table.hline(), 1428 | ..ge( 1429 | "femtomole", "fmol", 1430 | "picomole", "pmol", 1431 | "nanomole", "nmol", 1432 | "micromole", "umol", 1433 | "millimole", "mmol", 1434 | "mole", "mol", 1435 | "kilomole", "kmol", 1436 | ), 1437 | table.hline(), 1438 | ..ge( 1439 | "picoampere", "pA", 1440 | "nanoampere", "nA", 1441 | "microampere", "uA", 1442 | "milliampere", "mA", 1443 | "ampere", "A", 1444 | "kiloampere", "kA", 1445 | ), 1446 | table.hline(), 1447 | ..ge( 1448 | "microlitre", "uL", 1449 | "millilitre", "mL", 1450 | "litre", "L", 1451 | "hectolitre", "hL", 1452 | ) 1453 | ), 1454 | table( 1455 | columns: 3, 1456 | stroke: none, 1457 | table.vline(), 1458 | table.hline(), 1459 | [Unit], [Abbreviation], [Symbol], 1460 | table.hline(), 1461 | ..ge( 1462 | "millihertz", "mHz", 1463 | "hertz", "Hz", 1464 | "kilohertz", "kHz", 1465 | "megahertz", "MHz", 1466 | "gigahertz", "GHz", 1467 | "terahertz", "THz", 1468 | ), 1469 | table.hline(), 1470 | ..ge( 1471 | "millinewton", "mN", 1472 | "newton", "N", 1473 | "kilonewton", "kN", 1474 | "meganewton", "MN", 1475 | ), 1476 | table.hline(), 1477 | ..ge( 1478 | "pascal", "Pa", 1479 | "kilopascal", "kPa", 1480 | "megapascal", "MPa", 1481 | "gigapascal", "GPa", 1482 | ), 1483 | table.hline(), 1484 | ..ge( 1485 | "milliohm", "mohm", 1486 | "kilohm", "kohm", 1487 | "megohm", "Mohm", 1488 | ), 1489 | table.hline(), 1490 | ..ge( 1491 | "picovolt", "pV", 1492 | "nanovolt", "nV", 1493 | "microvolt", "uV", 1494 | "millivolt", "mV", 1495 | "volt", "V", 1496 | "kilovolt", "kV", 1497 | ), 1498 | table.hline(), 1499 | ..ge( 1500 | "watt", "W", 1501 | "nanowatt", "nW", 1502 | "microwatt", "uW", 1503 | "milliwatt", "mW", 1504 | "kilowatt", "kW", 1505 | "megawatt", "MW", 1506 | "gigawatt", "GW", 1507 | ), 1508 | table.hline(), 1509 | ..ge( 1510 | "joule", "J", 1511 | "microjoule", "uJ", 1512 | "millijoule", "mJ", 1513 | "kilojoule", "kJ", 1514 | ), 1515 | table.hline(), 1516 | ..ge( 1517 | "electronvolt", "eV", 1518 | "millielectronvolt", "meV", 1519 | "kiloelectronvolt", "keV", 1520 | "megaelectronvolt", "MeV", 1521 | "gigaelectronvolt", "GeV", 1522 | "teraelectronvolt", "TeV", 1523 | ), 1524 | table.hline(), 1525 | ..ge( 1526 | "kilowatt hour", "kWh" 1527 | ) 1528 | ), 1529 | table( 1530 | columns: 3, 1531 | stroke: none, 1532 | table.vline(), 1533 | table.hline(), 1534 | [Unit], [Abbreviation], [Symbol], 1535 | table.hline(), 1536 | ..ge( 1537 | "farad", "F", 1538 | "femtofarad", "fF", 1539 | "picofarad", "pF", 1540 | "nanofarad", "nF", 1541 | "microfarad", "uF", 1542 | "millifarad", "mF", 1543 | ), 1544 | table.hline(), 1545 | ..ge( 1546 | "henry", "H", 1547 | "femtohenry", "fH", 1548 | "picohenry", "pH", 1549 | "nanohenry", "nH", 1550 | "millihenry", "mH", 1551 | "microhenry", "uH", 1552 | ), 1553 | table.hline(), 1554 | ..ge( 1555 | "coulomb", "C", 1556 | "nanocoulomb", "nC", 1557 | "millicoulomb", "mC", 1558 | "microcoulomb", "uC", 1559 | ), 1560 | table.hline(), 1561 | ..ge( 1562 | "kelvin", "K", 1563 | "decibel", "dB", 1564 | "astrnomicalunit", "au", 1565 | "becquerel", "Bq", 1566 | "candela", "cd", 1567 | "dalton", "Da", 1568 | "gray", "Gy", 1569 | "hectare", "ha", 1570 | "katal", "kat", 1571 | "lumen", "lm", 1572 | "neper", "Np", 1573 | "radian", "rad", 1574 | "sievert", "Sv", 1575 | "steradian", "sr", 1576 | "weber", "Wb" 1577 | ), 1578 | table.hline(), 1579 | ..ge( 1580 | "kilobyte", "kB", 1581 | "megabyte", "MB", 1582 | "gigabyte", "GB", 1583 | "terabyte", "TB", 1584 | "petabyte", "PB", 1585 | "exabyte", "EB", 1586 | "kibibyte", "KiB", 1587 | "mebibyte", "MiB", 1588 | "gibibyte", "GiB", 1589 | "tebibyte", "TiB", 1590 | "pebibyte", "PiB", 1591 | "exbibyte", "EiB", 1592 | ), 1593 | ) 1594 | ) 1595 | ) 1596 | ) 1597 | } 1598 | = Creating 1599 | 1600 | The following functions can be used to define custom units, prefixes, powers and qualifiers that can be used with the `unit` function. 1601 | 1602 | == Units 1603 | ```typ 1604 | #declare-unit(unit, symbol, ..options) 1605 | ``` 1606 | 1607 | Declare's a custom unit to be used with the `unit` and `qty` functions. 1608 | 1609 | #param("unit", "string")[The string to use to identify the unit for string input.] 1610 | #param("symbol", "li")[The unit's symbol. A string or math content can be used. When using math content it is recommended to pass it through `unit` first.] 1611 | 1612 | ```example-stack 1613 | #let inch = "in" 1614 | #declare-unit("inch", inch) 1615 | #unit("inch / s")\ 1616 | #unit($inch / s$) 1617 | ``` 1618 | 1619 | == Prefixes 1620 | ```typ 1621 | #create-prefix(symbol) 1622 | ``` 1623 | Use this function to correctly create the symbol for a prefix. Metro uses Typst's #link("https://typst.app/docs/reference/math/class/", `math.class`) function with the `class` parameter `"unary"` to designate a prefix. This function does it for you. 1624 | 1625 | #param("symbol", "li")[The prefix's symbol. A string or math content can be used. When using math content it is recommended to pass it through `unit` first.] 1626 | 1627 | ```typ 1628 | #declare-prefix(prefix, symbol, power-tens) 1629 | ``` 1630 | 1631 | Declare's a custom prefix to be used with the `unit` and `qty` functions. 1632 | 1633 | #param("prefix", "string")[The string to use to identify the prefix for string input.] 1634 | #param("symbol", "li")[The prefix's symbol. This should be the output of the `create-prefix` function specified above.] 1635 | #param("power-tens", "nu")[The power ten of the prefix.] 1636 | 1637 | ```example-stack 1638 | #let myria = create-prefix("my") 1639 | #declare-prefix("myria", myria, 4) 1640 | #unit("myria meter")\ 1641 | #unit($myria meter$) 1642 | ``` 1643 | 1644 | == Powers 1645 | ```typ 1646 | #declare-power(before, after, power) 1647 | ``` 1648 | 1649 | This function adds two symbols for string input, one for use before a unit, the second for use after a unit, both of which are equivalent to the `power`. 1650 | 1651 | #param("before", "string")[The string that specifies this power before a unit.] 1652 | #param("after", "string")[The string that specifies this power after a unit.] 1653 | #param("power", "nu")[The power.] 1654 | 1655 | ```example-stack 1656 | #declare-power("quartic", "tothefourth", 4) 1657 | #unit("kilogram tothefourth")\ 1658 | #unit("quartic metre") 1659 | ``` 1660 | 1661 | == Qualifiers 1662 | ```typ 1663 | #declare-qualifier(qualifier, symbol) 1664 | ``` 1665 | 1666 | This function defines a custom qualifier for string input. 1667 | 1668 | #param("qualifier", "string")[The string that specifies this qualifier.] 1669 | #param("symbol", "li")[The qualifier's symbol. Can be string or content.] 1670 | 1671 | ```example-stack 1672 | #declare-qualifier("polymer", "pol") 1673 | #declare-qualifier("catalyst", "cat") 1674 | #unit("gram polymer per mole catalyst per hour") 1675 | ``` --------------------------------------------------------------------------------