From 84c9a581a27074b5481e2c169f355830fc1f303f Mon Sep 17 00:00:00 2001 From: Mc-Zen <52877387+Mc-Zen@users.noreply.github.com> Date: Sat, 28 Mar 2026 15:02:07 +0100 Subject: [PATCH 1/9] [test] fraction --- tests/zi/fraction/.gitignore | 5 +++++ tests/zi/fraction/ref.typ | 23 +++++++++++++++++++++++ tests/zi/fraction/test.typ | 26 ++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 tests/zi/fraction/.gitignore create mode 100644 tests/zi/fraction/ref.typ create mode 100644 tests/zi/fraction/test.typ diff --git a/tests/zi/fraction/.gitignore b/tests/zi/fraction/.gitignore new file mode 100644 index 0000000..2e4c5d8 --- /dev/null +++ b/tests/zi/fraction/.gitignore @@ -0,0 +1,5 @@ +# generated by tytanic, do not edit + +/diff/ +/out/ +/ref/ diff --git a/tests/zi/fraction/ref.typ b/tests/zi/fraction/ref.typ new file mode 100644 index 0000000..2d6adfd --- /dev/null +++ b/tests/zi/fraction/ref.typ @@ -0,0 +1,23 @@ +#set page(width: auto, height: auto, margin: .5em) + +#let th = sym.space.thin + +$"m"th "s"^(-1)$ \ +$"m"\/"s"$ \ +$"m"/"s"$ \ + +$"m"th "s"^(-1) th "N"^(-1)$ \ +$"m"\/("s"th"N")$ \ +$"m"/("s"th"N")$ \ + +#pagebreak() + +#set super(typographic: false) + +m#th;s#super[−1] \ +m/s \ +m/s \ + +m#th;s#super[−1]#th;N#super[−1] \ +m/(s#th;N) \ +m/(s#th;N) \ diff --git a/tests/zi/fraction/test.typ b/tests/zi/fraction/test.typ new file mode 100644 index 0000000..3cf7317 --- /dev/null +++ b/tests/zi/fraction/test.typ @@ -0,0 +1,26 @@ +#set page(width: auto, height: auto, margin: .5em) +#import "/src/zero.typ": set-unit, set-num, zi + + +#let m-s-N = zi.declare("m/s/N") + +#zi.m-s() \ +#zi.m-s(fraction: "inline") \ +#zi.m-s(fraction: "fraction") \ + +#m-s-N() \ +#m-s-N(fraction: "inline") \ +#m-s-N(fraction: "fraction") \ + + +#pagebreak() +#set-num(math: false) + +#zi.m-s() \ +#zi.m-s(fraction: "inline") \ +#zi.m-s(fraction: "fraction") \ + + +#m-s-N() \ +#m-s-N(fraction: "inline") \ +#m-s-N(fraction: "fraction") \ \ No newline at end of file From 065963a589a3c0c7adaf59db4601f43ad6a11739 Mon Sep 17 00:00:00 2001 From: Mc-Zen <52877387+Mc-Zen@users.noreply.github.com> Date: Sat, 28 Mar 2026 15:03:20 +0100 Subject: [PATCH 2/9] [add] utility for processing dict breakable arguments --- src/utility.typ | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/utility.typ b/src/utility.typ index 9dfb6e8..b1bb636 100644 --- a/src/utility.typ +++ b/src/utility.typ @@ -43,3 +43,27 @@ #assert.eq(shift-decimal-left("123", "456", digits: -5), ("12345600", "")) #assert.eq(shift-decimal-left("0", "0012", digits: -4), ("12", "")) #assert.eq(shift-decimal-left("0", "0012", digits: -2), ("0", "12")) + + +#let process-breakable(breakable) = { + let default = ( + uncertainty: false, + power: false, + unit: false, + ) + + if breakable == false { + return default + } + if breakable == true { + return ( + uncertainty: true, + power: true, + unit: true, + ) + } + if type(breakable) == dictionary { + return default + breakable + } + assert(false) +} From 2bf409d26300029f87f69061784fefaca285afd7 Mon Sep 17 00:00:00 2001 From: Mc-Zen <52877387+Mc-Zen@users.noreply.github.com> Date: Sat, 28 Mar 2026 15:12:42 +0100 Subject: [PATCH 3/9] [refactor] fractions --- src/units.typ | 54 +++++++++++++++--------------------- tests/zi/advanced/ref.typ | 16 +++++++++++ tests/zi/advanced/ref/1.png | Bin 2458 -> 0 bytes tests/zi/advanced/ref/2.png | Bin 594 -> 0 bytes 4 files changed, 38 insertions(+), 32 deletions(-) create mode 100644 tests/zi/advanced/ref.typ delete mode 100644 tests/zi/advanced/ref/1.png delete mode 100644 tests/zi/advanced/ref/2.png diff --git a/src/units.typ b/src/units.typ index c72c956..12277dd 100644 --- a/src/units.typ +++ b/src/units.typ @@ -184,6 +184,13 @@ /// Unprocessed arguments. ..args, ) = { + assert( + fraction in ("power", "fraction", "inline"), + message: "Invalid fraction: " + + fraction + + ". Expected \"power\", \"fraction\", or \"inline\"", + ) + let fold-units = fold-units.with( unit-separator: unit-separator, math: math, @@ -200,7 +207,7 @@ let denominator-content = fold-units(..denominator, denom-exp-multiplier) if fraction == "power" { - // Numerator may be empty! + // Numerator could be empty! let result = denominator-content if numerator.len() != 0 { result = numerator-content + unit-separator + result @@ -211,34 +218,21 @@ // For the two fractional modes, the numerator shall not be empty. if numerator.len() == 0 { numerator-content = $1$ } - if fraction == "fraction" { - if not math { - assert( - false, - "`math: false` cannot be used together with `fraction: \"fraction\"`", - ) + if math { + if denominator.len() > 1 and fraction == "inline" { + denominator-content = $(#denominator-content)$ } - return $#numerator-content/#denominator-content$ - } else if fraction == "inline" { + set std.math.frac(style: "horizontal") if fraction == "inline" + $#numerator-content/#denominator-content$ + } else { if denominator.len() > 1 { - denominator-content = "(" + denominator-content + ")" - } - - if math { - $#numerator-content#h(0pt)\/#h(0pt)#denominator-content$ - } else { - numerator-content + "/" + denominator-content + denominator-content = [(#denominator-content)] } - } else { - assert( - false, - message: "Invalid fraction: " - + fraction - + ". Expected \"power\", \"fraction\", or \"symbol\"", - ) + numerator-content + "/" + denominator-content } } + #let unit( unit, ..args, @@ -255,13 +249,6 @@ ..num-state.unit, math: num-state.math, ) - if not num-state.unit.breakable { - if num-state.math { - result = $result$ - } else { - result = box(result) - } - } result } @@ -319,10 +306,13 @@ unit.numerator.first().first() = prefix + unit.numerator.first().first() } } + let breakable = utility.process-breakable(num-state.unit.breakable) let result = { num(value, state: num-state, force-parentheses-around-uncertainty: true) + sym.wj separator + if not breakable.unit { sym.wj } show-unit( unit.numerator, unit.denominator, @@ -332,9 +322,9 @@ ) } - if not num-state.unit.breakable { + if not breakable.power { if num-state.math { - result = $result$ + result = box($result$) } else { result = box(result) } diff --git a/tests/zi/advanced/ref.typ b/tests/zi/advanced/ref.typ new file mode 100644 index 0000000..0c94381 --- /dev/null +++ b/tests/zi/advanced/ref.typ @@ -0,0 +1,16 @@ +#set page(width: auto, height: auto, margin: .5em) + +#let th = sym.space.thin + +$Pi^2$ \ +$"M"_dot.o\/("s"^2 th"β"^2)$ \ +$1\/("M"_dot.o th √"s"th"β"^"a")$ \ +$#sym.prime.double^2$ + + +#pagebreak() + +$"L"$ \ +$"mL"$ \ +$"l"$ \ +$"ml"$ \ diff --git a/tests/zi/advanced/ref/1.png b/tests/zi/advanced/ref/1.png deleted file mode 100644 index 598c07d521b1c5cd39f17d900222c5f2e7f894c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2458 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 diff --git a/tests/zi/advanced/ref/2.png b/tests/zi/advanced/ref/2.png deleted file mode 100644 index 5ef027d03237679fd60ae676468657cbf08bb3d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 594 zcmV-Y0vwyuyIZU3x# zNYVC)iRl@Tx*Ni@oOo@=w1k~qZutN2)TT$kqU+4Yd&FuxZO1Nn(`Q--$K3m# z6DM-?5bekq?uhUI|9grAeFCz1zY(h~ooNk_%ww7jBpaA+5wGnLkSt-k03_R(E}>}K z`xTG2_h4;zz}haLX!HI-mNwo0Cr{lB*~y8qYzgJsbj--y)qbh?t@ zyyn*rM532hJby0dL9`=NSPOt;G)p3o%w>8-thRKf13 Date: Sat, 28 Mar 2026 15:14:57 +0100 Subject: [PATCH 4/9] [tests] move out lowercase liter test --- tests/zi/declare-advanced/.gitignore | 1 + tests/zi/{advanced => declare-advanced}/ref.typ | 8 -------- tests/zi/{advanced => declare-advanced}/test.typ | 10 ---------- tests/zi/lowercase-liter/.gitignore | 1 + tests/zi/lowercase-liter/ref.typ | 6 ++++++ tests/zi/lowercase-liter/test.typ | 8 ++++++++ 6 files changed, 16 insertions(+), 18 deletions(-) create mode 100644 tests/zi/declare-advanced/.gitignore rename tests/zi/{advanced => declare-advanced}/ref.typ (78%) rename tests/zi/{advanced => declare-advanced}/test.typ (74%) create mode 100644 tests/zi/lowercase-liter/.gitignore create mode 100644 tests/zi/lowercase-liter/ref.typ create mode 100644 tests/zi/lowercase-liter/test.typ diff --git a/tests/zi/declare-advanced/.gitignore b/tests/zi/declare-advanced/.gitignore new file mode 100644 index 0000000..2e73a39 --- /dev/null +++ b/tests/zi/declare-advanced/.gitignore @@ -0,0 +1 @@ +ref/ \ No newline at end of file diff --git a/tests/zi/advanced/ref.typ b/tests/zi/declare-advanced/ref.typ similarity index 78% rename from tests/zi/advanced/ref.typ rename to tests/zi/declare-advanced/ref.typ index 0c94381..2a6e309 100644 --- a/tests/zi/advanced/ref.typ +++ b/tests/zi/declare-advanced/ref.typ @@ -6,11 +6,3 @@ $Pi^2$ \ $"M"_dot.o\/("s"^2 th"β"^2)$ \ $1\/("M"_dot.o th √"s"th"β"^"a")$ \ $#sym.prime.double^2$ - - -#pagebreak() - -$"L"$ \ -$"mL"$ \ -$"l"$ \ -$"ml"$ \ diff --git a/tests/zi/advanced/test.typ b/tests/zi/declare-advanced/test.typ similarity index 74% rename from tests/zi/advanced/test.typ rename to tests/zi/declare-advanced/test.typ index 863682f..dd8ece4 100644 --- a/tests/zi/advanced/test.typ +++ b/tests/zi/declare-advanced/test.typ @@ -7,13 +7,3 @@ #zi.declare($M_dot.o$, ("s", -2), ($β$, -2))() \ #zi.declare(($M_dot.o$, -1), ("s", -0.5), ($β$, "-a"))() \ #zi.declare((sym.prime.double, 2))() \ - - -#pagebreak() - - -#zi.liter() \ -#zi.mL() \ -#set-unit(lowercase-liter: true) -#zi.liter() \ -#zi.mL() \ diff --git a/tests/zi/lowercase-liter/.gitignore b/tests/zi/lowercase-liter/.gitignore new file mode 100644 index 0000000..2e73a39 --- /dev/null +++ b/tests/zi/lowercase-liter/.gitignore @@ -0,0 +1 @@ +ref/ \ No newline at end of file diff --git a/tests/zi/lowercase-liter/ref.typ b/tests/zi/lowercase-liter/ref.typ new file mode 100644 index 0000000..9fd46fe --- /dev/null +++ b/tests/zi/lowercase-liter/ref.typ @@ -0,0 +1,6 @@ +#set page(width: auto, height: auto, margin: .5em) + +$"L"$ \ +$"mL"$ \ +$"l"$ \ +$"ml"$ \ diff --git a/tests/zi/lowercase-liter/test.typ b/tests/zi/lowercase-liter/test.typ new file mode 100644 index 0000000..618e36f --- /dev/null +++ b/tests/zi/lowercase-liter/test.typ @@ -0,0 +1,8 @@ +#set page(width: auto, height: auto, margin: .5em) +#import "/src/zero.typ": set-unit, zi + +#zi.liter() \ +#zi.mL() \ +#set-unit(lowercase-liter: true) +#zi.liter() \ +#zi.mL() \ From e3345eea2be89a3967e021de77dbfe42f9d4b206 Mon Sep 17 00:00:00 2001 From: Mc-Zen <52877387+Mc-Zen@users.noreply.github.com> Date: Sat, 28 Mar 2026 15:19:52 +0100 Subject: [PATCH 5/9] [move] `breakable` from unit to num --- README.md | 4 ++-- src/state.typ | 2 +- src/units.typ | 9 +-------- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b848054..d4377bc 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ Zero's core is the `num()` function, which provides flexible number formatting. decimal-separator: str = ".", product: content = sym.times, tight: bool = false, + breakable: bool = false, math: bool = true, omit-unity-mantissa: bool = false, @@ -128,6 +129,7 @@ Zero's core is the `num()` function, which provides flexible number formatting. - `decimal-separator: str = "."` : Specifies the marker that is used for separating integer and decimal part. - `product: content = sym.times` : Specifies the multiplication symbol used for scientific notation. - `tight: bool = false` : If true, tight spacing is applied between operands (applies to $\times$ and $\pm$). +- `breakable: bool` : Whether numbers and quantities can be broken across paragraph lines. - `math: bool = true` : If set to `false`, the parts of the number won't be wrapped in a `math.equation`. This makes it possible to use `num()` with non-math fonts. - `omit-unity-mantissa: bool = false` : Determines whether a mantissa of 1 is omitted in scientific notation, e.g., $10^4$ instead of $1\cdot 10^4$. - `positive-sign: bool = false` : If set to `true`, positive coefficients are shown with a $+$ sign. @@ -409,7 +411,6 @@ The appearance of units can be configured via `set-unit`: #set-unit( unit-separator: content = sym.space.thin, fraction: str = "power", - breakable: bool = false, prefix: auto | none = auto ) ``` @@ -418,7 +419,6 @@ The appearance of units can be configured via `set-unit`: - `"power"` : Units with negative exponents are shown as powers. - `"fraction"` : When units with negative exponents are present, a fraction is created and the concerned units are put in the denominator. - `"inline"` : An inline fraction is created. -- `breakable: bool` : Whether units and quantities can be broken across paragraph lines. - `prefix: auto | none` : When set to `auto` and `num.exponent` is set to `"eng"`, a metric prefix is displayed along with the unit, replacing the exponent, e.g., `zi.m[2e4]` will render as 20km. These options are also available when instancing a quantity, e.g., `#zi.m(fraction: "inline")[2.5]`. diff --git a/src/state.typ b/src/state.typ index e0158e0..d3edcea 100644 --- a/src/state.typ +++ b/src/state.typ @@ -15,6 +15,7 @@ fixed: none, exponent: auto, trim-zeros: false, + breakable: false, group: ( size: 3, separator: sym.space.thin, @@ -30,7 +31,6 @@ unit: ( unit-separator: sym.space.thin, fraction: "power", - breakable: false, use-sqrt: true, prefix: auto, lowercase-liter: false, diff --git a/src/units.typ b/src/units.typ index 12277dd..1be2504 100644 --- a/src/units.typ +++ b/src/units.typ @@ -306,7 +306,7 @@ unit.numerator.first().first() = prefix + unit.numerator.first().first() } } - let breakable = utility.process-breakable(num-state.unit.breakable) + let breakable = utility.process-breakable(num-state.breakable) let result = { num(value, state: num-state, force-parentheses-around-uncertainty: true) @@ -322,13 +322,6 @@ ) } - if not breakable.power { - if num-state.math { - result = box($result$) - } else { - result = box(result) - } - } result } From d81fc795c951893c48245a1cffdcbe6b503107d1 Mon Sep 17 00:00:00 2001 From: Mc-Zen <52877387+Mc-Zen@users.noreply.github.com> Date: Sat, 28 Mar 2026 19:11:19 +0100 Subject: [PATCH 6/9] [add] fine-grained breaking features --- src/formatting.typ | 46 ++++++++++++++++++++++++++++++++++++++-------- src/units.typ | 12 ++++++------ 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/formatting.typ b/src/formatting.typ index 6a2dd3a..3f16b28 100644 --- a/src/formatting.typ +++ b/src/formatting.typ @@ -173,9 +173,19 @@ } +#let nonbreaking-binary-class(body, tight: false, breakable: true) = { + let space = if tight { 0pt } else { 2em/9 } + + if breakable { + math.class("binary", body) + } else { + // Recover spacing of binary relations but without breakability + h(space) + math.class("normal", body) + h(space) + } +} #let format-uncertainty = it => { - /// pm, digits, mode, concise, tight, math + /// pm, digits, mode, concise, tight, math, breakable let pm = it.pm if pm == none { return () } let is-symmetric = type(pm.first()) != array @@ -206,18 +216,26 @@ decimal-separator: it.decimal-separator, ))) if is-symmetric { - if it.concise { ("(", pm.first(), ")") } else if it.math { + if it.concise { + ("(", pm.first(), ")") + } else if it.math { ( math.class("normal", none), - math.class(if it.tight { "normal" } else { "binary" }, sym.plus.minus), + nonbreaking-binary-class( + sym.plus.minus, + tight: it.tight, + breakable: it.breakable.uncertainty + ), pm.first(), ) } else { - let space = if not it.tight { sym.space.thin } + let space = if it.tight { sym.space.hair } else { sym.space.thin } ( space, + sym.wj, sym.plus.minus, space, + if not it.breakable.uncertainty { sym.wj }, pm.first(), ) } @@ -231,6 +249,7 @@ ) } else { ( + sym.wj, non-math-attach( none, t: "+" + pm.at(0), @@ -243,7 +262,7 @@ #let format-power = it => { - /// x, base, product, positive-sign-exponent, tight, math + /// x, base, product, positive-sign-exponent, tight, math, breakable if it.exponent == none { return () } let (sign, integer, fractional) = decompose-signed-float-numeral(it.exponent) @@ -262,19 +281,24 @@ if it.product == none { (power,) } else { ( box(), - math.class(if it.tight { "normal" } else { "binary" }, it.product), + nonbreaking-binary-class( + it.product, + tight: it.tight, + breakable: it.breakable.power + ), power, ) } } else { let power = non-math-attach([#it.base], t: [#exponent]) if it.product == none { (power,) } else { - let space = if not it.tight { sym.space.thin } + let space = if it.tight { sym.space.hair } else { sym.space.thin } ( - box(), space, + sym.wj, it.product, space, + if not it.breakable.power { sym.wj }, power, ) } @@ -284,6 +308,7 @@ #let show-num-impl = it => { + let breakable = utility.process-breakable(it.breakable) /// sign, int, frac, e, pm, /// digits /// omit-unity-mantissa, uncertainty-mode, positive-sign @@ -321,6 +346,7 @@ math: it.math, mode: it.uncertainty-mode, decimal-separator: it.decimal-separator, + breakable: breakable ) @@ -332,6 +358,7 @@ tight: it.tight, math: it.math, decimal-separator: it.decimal-separator, + breakable: breakable ) let integer-part = ( @@ -349,6 +376,9 @@ ) let uncertainty-part = format-uncertainty(uncertainty) + if not breakable.uncertainty and uncertainty-part.len() != 0{ + // uncertainty-part = (std.box(equation-from-items(uncertainty-part)),) + } if concise-uncertainty { fractional-part += uncertainty-part diff --git a/src/units.typ b/src/units.typ index 1be2504..38ec908 100644 --- a/src/units.typ +++ b/src/units.typ @@ -192,7 +192,7 @@ ) let fold-units = fold-units.with( - unit-separator: unit-separator, + unit-separator: unit-separator + sym.wj, math: math, use-sqrt: use-sqrt, ) @@ -210,7 +210,7 @@ // Numerator could be empty! let result = denominator-content if numerator.len() != 0 { - result = numerator-content + unit-separator + result + result = numerator-content + unit-separator + sym.wj + result } return if math { $result$ } else { result } } @@ -243,12 +243,12 @@ } let num-state = update-num-state(num-state.get(), args) - let result = show-unit( + let result = (show-unit( unit.numerator, unit.denominator, ..num-state.unit, math: num-state.math, - ) + )) result } @@ -313,13 +313,13 @@ sym.wj separator if not breakable.unit { sym.wj } - show-unit( + box(show-unit( unit.numerator, unit.denominator, fraction: num-state.unit.fraction, unit-separator: num-state.unit.unit-separator, math: num-state.math, - ) + )) } result From 5e7716abc02294fe673c736be561944a9c556432 Mon Sep 17 00:00:00 2001 From: Mc-Zen <52877387+Mc-Zen@users.noreply.github.com> Date: Sun, 29 Mar 2026 21:10:32 +0200 Subject: [PATCH 7/9] [test] breaking behavior --- tests/breakable/.gitignore | 5 ++ tests/breakable/ref.typ | 113 +++++++++++++++++++++++++++++++++++++ tests/breakable/test.typ | 86 ++++++++++++++++++++++++++++ tests/num/self/test.typ | 4 +- 4 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 tests/breakable/.gitignore create mode 100644 tests/breakable/ref.typ create mode 100644 tests/breakable/test.typ diff --git a/tests/breakable/.gitignore b/tests/breakable/.gitignore new file mode 100644 index 0000000..2e4c5d8 --- /dev/null +++ b/tests/breakable/.gitignore @@ -0,0 +1,5 @@ +# generated by tytanic, do not edit + +/diff/ +/out/ +/ref/ diff --git a/tests/breakable/ref.typ b/tests/breakable/ref.typ new file mode 100644 index 0000000..8472607 --- /dev/null +++ b/tests/breakable/ref.typ @@ -0,0 +1,113 @@ +#set page(width: auto, height: auto, margin: .5em) + +#set super(typographic: false) +#let th = sym.space.thin +#let hair = sym.space.hair + + +== Math mode +// Non-breakable +#block(stroke: gray, width: 15em)[ + The length is $(1plus.minus 2)×10^2th"m"th"s"^(-1)$. +] +#block(stroke: gray, width: 10em)[ + The length is\ $(1plus.minus 2)×10^2th"m"th"s"^(-1)$. +] +// Single break points +#block(stroke: gray, width: 13em)[ + The length is $(1plus.minus 2)×10^2$\ $"m"th"s"^(-1)$. +] +#block(stroke: gray, width: 12em)[ + The length is $(1plus.minus 2)×$\ $10^2th"m"th"s"^(-1)$. +] +#block(stroke: gray, width: 10em)[ + The length is $(1plus.minus$\ $2)×10^2th"m"th"s"^(-1)$. +] + +#pagebreak() + + +== Text mode +// Non-breakable +#block(stroke: gray, width: 13em)[ + The length is (1#th±#th⁠2) ⁠× ⁠10#super[2]#th;m#th;s#super[−1]. +] +#block(stroke: gray, width: 10em)[ + The length is\ (1#th±#th⁠2) ⁠× ⁠10#super[2]#th;m#th;s#super[−1]. +] +// Single break points +#block(stroke: gray, width: 11em)[ + The length is (1#th±#th⁠2) ⁠× ⁠10#super[2]#th;\ m#th;s#super[−1]. +] +#block(stroke: gray, width: 10.5em)[ + The length is (1#th±#th⁠2) ⁠× \ ⁠10#super[2]#th;m#th;s#super[−1]. +] +#block(stroke: gray, width: 10em)[ + The length is (1#th±#th\ 2) ⁠× ⁠10#super[2]#th;m#th;s#super[−1]. +] + +#pagebreak() + + +== Tight mode +#block(stroke: gray, width: 14em)[ + The length is $(1#h(0pt)plus.minus 2)#h(0pt)×#h(0pt)10^2th"m"th"s"^(-1)$. +] +#block(stroke: gray, width: 10em)[ + The length is\ $(1#h(0pt)plus.minus 2)#h(0pt)×#h(0pt)10^2th"m"th"s"^(-1)$. +] +// Single break points +#block(stroke: gray, width: 11em)[ + The length is $(1#h(0pt)plus.minus 2)#h(0pt)×#h(0pt)10^2$\ $"m"th"s"^(-1)$. +] +#block(stroke: gray, width: 10.5em)[ + The length is $(1#h(0pt)plus.minus 2)×$\ $10^2th"m"th"s"^(-1)$. +] +#block(stroke: gray, width: 10em)[ + The length is $(1plus.minus$\ $2)#h(0pt)×#h(0pt)10^2th"m"th"s"^(-1)$. +] + + +#pagebreak() + + +== Tight text mode +#block(stroke: gray, width: 13em)[ + The length is (1#hair±#hair⁠2)#hair;×#hair;⁠10#super[2]#th;m#th;s#super[−1]. +] +#block(stroke: gray, width: 10em)[ + The length is\ (1#hair±#hair⁠2)#hair;×#hair;⁠10#super[2]#th;m#th;s#super[−1]. +] +// Single break points +#block(stroke: gray, width: 11em)[ + The length is (1#hair±#hair⁠2)#hair;×#hair;⁠10#super[2]#hair;\ m#th;s#super[−1]. +] +#block(stroke: gray, width: 10.5em)[ + The length is (1#hair±#hair⁠2)#hair;×#hair;\ ⁠10#super[2]#th;m#th;s#super[−1]. +] +#block(stroke: gray, width: 10em)[ + The length is (1#hair±#hair\ 2)#hair;⁠×#hair;⁠10#super[2]#th;m#th;s#super[−1]. +] + + +#pagebreak() + +== Don't break asym. uncertainties +#block(stroke: gray, width: 10em)[ + The length is\ + $\(1#none^(+2)_(-2))×10^2th"m"$. +] +#block(stroke: gray, width: 10em)[ + The length is \ + #import "/src/zero.typ": zi + #zi.meter("1+2-2e2", math: false). +] + +#pagebreak() + +#set page(width: 9em, height: auto, margin: .5em) + + +Unit of force is $"kg"th"m"th"stel"th"A"th"A"th"s"^(-2)th"mol"^(-1)$ + +Unit of force is \ #box(width: 10cm)[kg#th;m#th;stel#th;A#th;A#th;s#super[−2]#th;mol#super[−1]] \ \ No newline at end of file diff --git a/tests/breakable/test.typ b/tests/breakable/test.typ new file mode 100644 index 0000000..f25933c --- /dev/null +++ b/tests/breakable/test.typ @@ -0,0 +1,86 @@ +#import "/src/zero.typ": * + +// #set box(fill: red) +#set page(width: auto, height: auto, margin: .5em) + +#let test-clause(..args) = [ + #[The length is] #(zi.m-s("1+-2e2", ..args)). \ + // #hide[The length is] #(zi.meter("1+2-2e2", ..args)). +] + +== Math mode +// Non-breakable +#block(stroke: gray, width: 15em, test-clause()) +#block(stroke: gray, width: 10em, test-clause()) +// Single break points +#block(stroke: gray, width: 13em, test-clause(breakable: (unit: true))) +#block(stroke: gray, width: 12em, test-clause(breakable: (power: true))) +#block(stroke: gray, width: 10em, test-clause(breakable: (uncertainty: true))) + +#pagebreak() + + +== Text mode +#set-num(math: false) +#block(stroke: gray, width: 13em, test-clause()) +#block(stroke: gray, width: 10em, test-clause()) +// Single break points +#block(stroke: gray, width: 11em, test-clause(breakable: (unit: true))) +#block(stroke: gray, width: 10.5em, test-clause(breakable: (power: true))) +#block(stroke: gray, width: 10em, test-clause(breakable: (uncertainty: true))) + +#pagebreak() + + +== Tight mode +#set-num(tight: true, math: true) +#block(stroke: gray, width: 14em, test-clause()) +#block(stroke: gray, width: 10em, test-clause()) +// Single break points +#block(stroke: gray, width: 11em, test-clause(breakable: (unit: true))) +#block(stroke: gray, width: 10.5em, test-clause(breakable: (power: true))) +#block(stroke: gray, width: 10em, test-clause(breakable: (uncertainty: true))) + + +#pagebreak() + + +== Tight text mode +#set-num(tight: true, math: false) +#block(stroke: gray, width: 13em, test-clause()) +#block(stroke: gray, width: 10em, test-clause()) +// Single break points +#block(stroke: gray, width: 11em, test-clause(breakable: (unit: true))) +#block(stroke: gray, width: 10.5em, test-clause(breakable: (power: true))) +#block(stroke: gray, width: 10em, test-clause(breakable: (uncertainty: true))) + + +#pagebreak() + +== Don't break asym. uncertainties +#set-num(tight: false, math: true) +#block(stroke: gray, width: 10em)[ + The length is #(zi.meter("1+2-2e2", breakable: (uncertainty: true))). +] +#set-num(tight: false, math: false) +#block(stroke: gray, width: 10em)[ + The length is #(zi.meter("1+2-2e2", breakable: (uncertainty: true))). +] + +#pagebreak() + + + +#set page(width: 9em, height: auto, margin: .5em) + +#set-num(tight: false, math: true) + + +#let long-unit = zi.declare("kg m/s^2/mol/N stel A A") + + +#set-num(breakable: false) +Unit of force is #long-unit(). + +#set-num(math: false) +Unit of force is #long-unit(). diff --git a/tests/num/self/test.typ b/tests/num/self/test.typ index 905443f..6ea50c8 100644 --- a/tests/num/self/test.typ +++ b/tests/num/self/test.typ @@ -290,7 +290,7 @@ // num with manual state #[ - #let state = default-state + #let state = impl.default-state #{ state.decimal-separator = "," } #let num = num.with(state: state) #num[2.34] @@ -309,7 +309,7 @@ // num with state input #context { - let state = num-state.get() + let state = impl.num-state.get() num("1", state: state) [ ] num("2", state: state) From 90563ddaea134bb178281e765a8dd82e5eca04a1 Mon Sep 17 00:00:00 2001 From: Mc-Zen <52877387+Mc-Zen@users.noreply.github.com> Date: Sun, 29 Mar 2026 21:12:23 +0200 Subject: [PATCH 8/9] [revert] move of state to impl --- tests/num/self/test.typ | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/num/self/test.typ b/tests/num/self/test.typ index 6ea50c8..905443f 100644 --- a/tests/num/self/test.typ +++ b/tests/num/self/test.typ @@ -290,7 +290,7 @@ // num with manual state #[ - #let state = impl.default-state + #let state = default-state #{ state.decimal-separator = "," } #let num = num.with(state: state) #num[2.34] @@ -309,7 +309,7 @@ // num with state input #context { - let state = impl.num-state.get() + let state = num-state.get() num("1", state: state) [ ] num("2", state: state) From 24e2e40acac89801ad5a14d8fc97927cbfbee148 Mon Sep 17 00:00:00 2001 From: Mc-Zen <52877387+Mc-Zen@users.noreply.github.com> Date: Sun, 29 Mar 2026 21:17:11 +0200 Subject: [PATCH 9/9] [docs] for `breakable` --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d4377bc..de3d1fb 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ Zero's core is the `num()` function, which provides flexible number formatting. decimal-separator: str = ".", product: content = sym.times, tight: bool = false, - breakable: bool = false, + breakable: bool | dict = false, math: bool = true, omit-unity-mantissa: bool = false, @@ -129,7 +129,7 @@ Zero's core is the `num()` function, which provides flexible number formatting. - `decimal-separator: str = "."` : Specifies the marker that is used for separating integer and decimal part. - `product: content = sym.times` : Specifies the multiplication symbol used for scientific notation. - `tight: bool = false` : If true, tight spacing is applied between operands (applies to $\times$ and $\pm$). -- `breakable: bool` : Whether numbers and quantities can be broken across paragraph lines. +- `breakable: bool | dict` : Whether numbers and quantities can be broken across paragraph lines. Setting this to `true`/`false` entirely enables/disables breaking. For more fine-grained control, a dictionary with the keys `uncertainty`, `power`, and `unit` (all booleans) can be passed for specifying whether breaks are allowed after the ± symbol, the × symbol, or before the unit, respectively. - `math: bool = true` : If set to `false`, the parts of the number won't be wrapped in a `math.equation`. This makes it possible to use `num()` with non-math fonts. - `omit-unity-mantissa: bool = false` : Determines whether a mantissa of 1 is omitted in scientific notation, e.g., $10^4$ instead of $1\cdot 10^4$. - `positive-sign: bool = false` : If set to `true`, positive coefficients are shown with a $+$ sign.