From 38213ed534d8a7cd520c0265b99a345bc2966b39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20S=C3=A1nchez=20Mu=C3=B1oz?= Date: Mon, 24 Mar 2025 19:16:33 +0100 Subject: [PATCH] Use `u64` instead of `usize` to store counter and enumeration item numbers, so behavior does not vary from 64-bit to 32-bit platforms (#6026) --- crates/typst-layout/src/lists.rs | 10 ++-- .../src/introspection/counter.rs | 24 ++++----- crates/typst-library/src/layout/page.rs | 2 +- crates/typst-library/src/model/enum.rs | 6 +-- crates/typst-library/src/model/numbering.rs | 49 ++++++++---------- crates/typst-pdf/src/page.rs | 14 ++--- crates/typst-syntax/src/ast.rs | 2 +- crates/typst-syntax/src/lexer.rs | 2 +- tests/ref/enum-numbering-huge.png | Bin 0 -> 900 bytes tests/ref/page-numbering-huge.png | Bin 0 -> 913 bytes tests/src/world.rs | 2 +- tests/suite/introspection/counter.typ | 10 ++++ tests/suite/layout/page.typ | 10 ++++ tests/suite/model/enum.typ | 5 ++ tests/suite/model/numbering.typ | 1 + 15 files changed, 82 insertions(+), 55 deletions(-) create mode 100644 tests/ref/enum-numbering-huge.png create mode 100644 tests/ref/page-numbering-huge.png diff --git a/crates/typst-layout/src/lists.rs b/crates/typst-layout/src/lists.rs index f8d910abf..974788a70 100644 --- a/crates/typst-layout/src/lists.rs +++ b/crates/typst-layout/src/lists.rs @@ -96,9 +96,13 @@ pub fn layout_enum( let mut cells = vec![]; let mut locator = locator.split(); - let mut number = - elem.start(styles) - .unwrap_or_else(|| if reversed { elem.children.len() } else { 1 }); + let mut number = elem.start(styles).unwrap_or_else(|| { + if reversed { + elem.children.len() as u64 + } else { + 1 + } + }); let mut parents = EnumElem::parents_in(styles); let full = elem.full(styles); diff --git a/crates/typst-library/src/introspection/counter.rs b/crates/typst-library/src/introspection/counter.rs index 5432df238..772bea963 100644 --- a/crates/typst-library/src/introspection/counter.rs +++ b/crates/typst-library/src/introspection/counter.rs @@ -229,10 +229,10 @@ impl Counter { if self.is_page() { let at_delta = engine.introspector.page(location).get().saturating_sub(at_page.get()); - at_state.step(NonZeroUsize::ONE, at_delta); + at_state.step(NonZeroUsize::ONE, at_delta as u64); let final_delta = engine.introspector.pages().get().saturating_sub(final_page.get()); - final_state.step(NonZeroUsize::ONE, final_delta); + final_state.step(NonZeroUsize::ONE, final_delta as u64); } Ok(CounterState(smallvec![at_state.first(), final_state.first()])) } @@ -250,7 +250,7 @@ impl Counter { if self.is_page() { let delta = engine.introspector.page(location).get().saturating_sub(page.get()); - state.step(NonZeroUsize::ONE, delta); + state.step(NonZeroUsize::ONE, delta as u64); } Ok(state) } @@ -319,7 +319,7 @@ impl Counter { let delta = page.get() - prev.get(); if delta > 0 { - state.step(NonZeroUsize::ONE, delta); + state.step(NonZeroUsize::ONE, delta as u64); } } @@ -500,7 +500,7 @@ impl Counter { let (mut state, page) = sequence.last().unwrap().clone(); if self.is_page() { let delta = engine.introspector.pages().get().saturating_sub(page.get()); - state.step(NonZeroUsize::ONE, delta); + state.step(NonZeroUsize::ONE, delta as u64); } Ok(state) } @@ -616,13 +616,13 @@ pub trait Count { /// Counts through elements with different levels. #[derive(Debug, Clone, PartialEq, Hash)] -pub struct CounterState(pub SmallVec<[usize; 3]>); +pub struct CounterState(pub SmallVec<[u64; 3]>); impl CounterState { /// Get the initial counter state for the key. pub fn init(page: bool) -> Self { // Special case, because pages always start at one. - Self(smallvec![usize::from(page)]) + Self(smallvec![u64::from(page)]) } /// Advance the counter and return the numbers for the given heading. @@ -645,7 +645,7 @@ impl CounterState { } /// Advance the number of the given level by the specified amount. - pub fn step(&mut self, level: NonZeroUsize, by: usize) { + pub fn step(&mut self, level: NonZeroUsize, by: u64) { let level = level.get(); while self.0.len() < level { @@ -657,7 +657,7 @@ impl CounterState { } /// Get the first number of the state. - pub fn first(&self) -> usize { + pub fn first(&self) -> u64 { self.0.first().copied().unwrap_or(1) } @@ -675,7 +675,7 @@ impl CounterState { cast! { CounterState, self => Value::Array(self.0.into_iter().map(IntoValue::into_value).collect()), - num: usize => Self(smallvec![num]), + num: u64 => Self(smallvec![num]), array: Array => Self(array .into_iter() .map(Value::cast) @@ -758,7 +758,7 @@ impl Show for Packed { #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct ManualPageCounter { physical: NonZeroUsize, - logical: usize, + logical: u64, } impl ManualPageCounter { @@ -773,7 +773,7 @@ impl ManualPageCounter { } /// Get the current logical page counter state. - pub fn logical(&self) -> usize { + pub fn logical(&self) -> u64 { self.logical } diff --git a/crates/typst-library/src/layout/page.rs b/crates/typst-library/src/layout/page.rs index af6ad642d..62e25278a 100644 --- a/crates/typst-library/src/layout/page.rs +++ b/crates/typst-library/src/layout/page.rs @@ -484,7 +484,7 @@ pub struct Page { pub supplement: Content, /// The logical page number (controlled by `counter(page)` and may thus not /// match the physical number). - pub number: usize, + pub number: u64, } impl Page { diff --git a/crates/typst-library/src/model/enum.rs b/crates/typst-library/src/model/enum.rs index a4126e72c..2d95996ab 100644 --- a/crates/typst-library/src/model/enum.rs +++ b/crates/typst-library/src/model/enum.rs @@ -129,7 +129,7 @@ pub struct EnumElem { /// [Ahead], /// ) /// ``` - pub start: Smart, + pub start: Smart, /// Whether to display the full numbering, including the numbers of /// all parent enumerations. @@ -217,7 +217,7 @@ pub struct EnumElem { #[internal] #[fold] #[ghost] - pub parents: SmallVec<[usize; 4]>, + pub parents: SmallVec<[u64; 4]>, } #[scope] @@ -274,7 +274,7 @@ impl Show for Packed { pub struct EnumItem { /// The item's number. #[positional] - pub number: Option, + pub number: Option, /// The item's body. #[required] diff --git a/crates/typst-library/src/model/numbering.rs b/crates/typst-library/src/model/numbering.rs index ada8a3965..d82c3e4cd 100644 --- a/crates/typst-library/src/model/numbering.rs +++ b/crates/typst-library/src/model/numbering.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use chinese_number::{ - from_usize_to_chinese_ten_thousand as usize_to_chinese, ChineseCase, ChineseVariant, + from_u64_to_chinese_ten_thousand as u64_to_chinese, ChineseCase, ChineseVariant, }; use comemo::Tracked; use ecow::{eco_format, EcoString, EcoVec}; @@ -85,7 +85,7 @@ pub fn numbering( /// If `numbering` is a pattern and more numbers than counting symbols are /// given, the last counting symbol with its prefix is repeated. #[variadic] - numbers: Vec, + numbers: Vec, ) -> SourceResult { numbering.apply(engine, context, &numbers) } @@ -105,7 +105,7 @@ impl Numbering { &self, engine: &mut Engine, context: Tracked, - numbers: &[usize], + numbers: &[u64], ) -> SourceResult { Ok(match self { Self::Pattern(pattern) => Value::Str(pattern.apply(numbers).into()), @@ -156,7 +156,7 @@ pub struct NumberingPattern { impl NumberingPattern { /// Apply the pattern to the given number. - pub fn apply(&self, numbers: &[usize]) -> EcoString { + pub fn apply(&self, numbers: &[u64]) -> EcoString { let mut fmt = EcoString::new(); let mut numbers = numbers.iter(); @@ -185,7 +185,7 @@ impl NumberingPattern { } /// Apply only the k-th segment of the pattern to a number. - pub fn apply_kth(&self, k: usize, number: usize) -> EcoString { + pub fn apply_kth(&self, k: usize, number: u64) -> EcoString { let mut fmt = EcoString::new(); if let Some((prefix, _)) = self.pieces.first() { fmt.push_str(prefix); @@ -379,7 +379,7 @@ impl NumberingKind { } /// Apply the numbering to the given number. - pub fn apply(self, n: usize) -> EcoString { + pub fn apply(self, n: u64) -> EcoString { match self { Self::Arabic => eco_format!("{n}"), Self::LowerRoman => roman_numeral(n, Case::Lower), @@ -392,9 +392,10 @@ impl NumberingKind { } const SYMBOLS: &[char] = &['*', '†', '‡', '§', '¶', '‖']; - let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()]; - let amount = ((n - 1) / SYMBOLS.len()) + 1; - std::iter::repeat_n(symbol, amount).collect() + let n_symbols = SYMBOLS.len() as u64; + let symbol = SYMBOLS[((n - 1) % n_symbols) as usize]; + let amount = ((n - 1) / n_symbols) + 1; + std::iter::repeat_n(symbol, amount.try_into().unwrap()).collect() } Self::Hebrew => hebrew_numeral(n), @@ -489,18 +490,16 @@ impl NumberingKind { } Self::LowerSimplifiedChinese => { - usize_to_chinese(ChineseVariant::Simple, ChineseCase::Lower, n).into() + u64_to_chinese(ChineseVariant::Simple, ChineseCase::Lower, n).into() } Self::UpperSimplifiedChinese => { - usize_to_chinese(ChineseVariant::Simple, ChineseCase::Upper, n).into() + u64_to_chinese(ChineseVariant::Simple, ChineseCase::Upper, n).into() } Self::LowerTraditionalChinese => { - usize_to_chinese(ChineseVariant::Traditional, ChineseCase::Lower, n) - .into() + u64_to_chinese(ChineseVariant::Traditional, ChineseCase::Lower, n).into() } Self::UpperTraditionalChinese => { - usize_to_chinese(ChineseVariant::Traditional, ChineseCase::Upper, n) - .into() + u64_to_chinese(ChineseVariant::Traditional, ChineseCase::Upper, n).into() } Self::EasternArabic => decimal('\u{0660}', n), @@ -512,7 +511,7 @@ impl NumberingKind { } /// Stringify an integer to a Hebrew number. -fn hebrew_numeral(mut n: usize) -> EcoString { +fn hebrew_numeral(mut n: u64) -> EcoString { if n == 0 { return '-'.into(); } @@ -566,7 +565,7 @@ fn hebrew_numeral(mut n: usize) -> EcoString { } /// Stringify an integer to a Roman numeral. -fn roman_numeral(mut n: usize, case: Case) -> EcoString { +fn roman_numeral(mut n: u64, case: Case) -> EcoString { if n == 0 { return match case { Case::Lower => 'n'.into(), @@ -622,7 +621,7 @@ fn roman_numeral(mut n: usize, case: Case) -> EcoString { /// /// [converter]: https://www.russellcottrell.com/greek/utilities/GreekNumberConverter.htm /// [numbers]: https://mathshistory.st-andrews.ac.uk/HistTopics/Greek_numbers/ -fn greek_numeral(n: usize, case: Case) -> EcoString { +fn greek_numeral(n: u64, case: Case) -> EcoString { let thousands = [ ["͵α", "͵Α"], ["͵β", "͵Β"], @@ -683,7 +682,7 @@ fn greek_numeral(n: usize, case: Case) -> EcoString { let mut decimal_digits: Vec = Vec::new(); let mut n = n; while n > 0 { - decimal_digits.push(n % 10); + decimal_digits.push((n % 10) as usize); n /= 10; } @@ -778,18 +777,16 @@ fn greek_numeral(n: usize, case: Case) -> EcoString { /// /// You might be familiar with this scheme from the way spreadsheet software /// tends to label its columns. -fn zeroless( - alphabet: [char; N_DIGITS], - mut n: usize, -) -> EcoString { +fn zeroless(alphabet: [char; N_DIGITS], mut n: u64) -> EcoString { if n == 0 { return '-'.into(); } + let n_digits = N_DIGITS as u64; let mut cs = EcoString::new(); while n > 0 { n -= 1; - cs.push(alphabet[n % N_DIGITS]); - n /= N_DIGITS; + cs.push(alphabet[(n % n_digits) as usize]); + n /= n_digits; } cs.chars().rev().collect() } @@ -797,7 +794,7 @@ fn zeroless( /// Stringify a number using a base-10 counting system with a zero digit. /// /// This function assumes that the digits occupy contiguous codepoints. -fn decimal(start: char, mut n: usize) -> EcoString { +fn decimal(start: char, mut n: u64) -> EcoString { if n == 0 { return start.into(); } diff --git a/crates/typst-pdf/src/page.rs b/crates/typst-pdf/src/page.rs index 4e95f3c70..68125d29a 100644 --- a/crates/typst-pdf/src/page.rs +++ b/crates/typst-pdf/src/page.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::num::NonZeroUsize; +use std::num::NonZeroU64; use ecow::EcoString; use pdf_writer::types::{ActionType, AnnotationFlags, AnnotationType, NumberingStyle}; @@ -48,7 +48,7 @@ pub fn traverse_pages( // the real (not logical) page numbers. Here, the final PDF page number // will differ, but we can at least use labels to indicate what was // the corresponding real page number in the Typst document. - (skipped_pages > 0).then(|| PdfPageLabel::arabic(i + 1)) + (skipped_pages > 0).then(|| PdfPageLabel::arabic((i + 1) as u64)) }); pages.push(Some(encoded)); } @@ -219,7 +219,7 @@ pub(crate) struct PdfPageLabel { /// /// Describes where to start counting from when setting a style. /// (Has to be greater or equal than 1) - pub offset: Option, + pub offset: Option, } /// A PDF page label number style. @@ -242,7 +242,7 @@ pub enum PdfPageLabelStyle { impl PdfPageLabel { /// Create a new `PdfNumbering` from a `Numbering` applied to a page /// number. - fn generate(numbering: &Numbering, number: usize) -> Option { + fn generate(numbering: &Numbering, number: u64) -> Option { let Numbering::Pattern(pat) = numbering else { return None; }; @@ -275,18 +275,18 @@ impl PdfPageLabel { (!prefix.is_empty()).then(|| prefix.clone()) }; - let offset = style.and(NonZeroUsize::new(number)); + let offset = style.and(NonZeroU64::new(number)); Some(PdfPageLabel { prefix, style, offset }) } /// Creates an arabic page label with the specified page number. /// For example, this will display page label `11` when given the page /// number 11. - fn arabic(number: usize) -> PdfPageLabel { + fn arabic(number: u64) -> PdfPageLabel { PdfPageLabel { prefix: None, style: Some(PdfPageLabelStyle::Arabic), - offset: NonZeroUsize::new(number), + offset: NonZeroU64::new(number), } } } diff --git a/crates/typst-syntax/src/ast.rs b/crates/typst-syntax/src/ast.rs index f79e65982..7b211bfc1 100644 --- a/crates/typst-syntax/src/ast.rs +++ b/crates/typst-syntax/src/ast.rs @@ -778,7 +778,7 @@ node! { impl<'a> EnumItem<'a> { /// The explicit numbering, if any: `23.`. - pub fn number(self) -> Option { + pub fn number(self) -> Option { self.0.children().find_map(|node| match node.kind() { SyntaxKind::EnumMarker => node.text().trim_end_matches('.').parse().ok(), _ => Option::None, diff --git a/crates/typst-syntax/src/lexer.rs b/crates/typst-syntax/src/lexer.rs index b8f2bf25f..ac69eb616 100644 --- a/crates/typst-syntax/src/lexer.rs +++ b/crates/typst-syntax/src/lexer.rs @@ -480,7 +480,7 @@ impl Lexer<'_> { self.s.eat_while(char::is_ascii_digit); let read = self.s.from(start); - if self.s.eat_if('.') && self.space_or_end() && read.parse::().is_ok() { + if self.s.eat_if('.') && self.space_or_end() && read.parse::().is_ok() { return SyntaxKind::EnumMarker; } diff --git a/tests/ref/enum-numbering-huge.png b/tests/ref/enum-numbering-huge.png new file mode 100644 index 0000000000000000000000000000000000000000..b8117e0f490d8616cdf9e6a3982223c95aebb23e GIT binary patch literal 900 zcmV-~1AF|5P)pITPE1iPJQQ38U(OOa$cY)FYC4wYFIzY*^fRHR2x{XGK;e-uL z8g4s1V7W~Q7+7NhfjIdZ9?T)a1P1&1?5CaAub;g0&ig!nf8FD^_j?16sSP45!Xhlf zj|8>=W+NCFPmZZ!Wdj{4B$7}fX)C9+wBnqhrfRY>e}^M zQbV6CP9I&!MA@S;vX75eM@wc8adN2I5>6#K(EsgtA)im4>O=F?Iz;<3MLhNPFQmwh&9F3|{v`G`V-Da5#qW3)UR8 z6`7q#r(*CX7>lS;r8wE?-prj{+A=CN(%WpC-@(l!c;g{Ff3RbROEA2h)erwpKtx!C0dNA{ zHt=1VgbBSLI{tv=mKkuhguv1ZOTW#*h#^AQ?120F@#t6=f3C_d>`7E^gl0p%f&FXg zEAE0>8Fd!*Rnh~g{;4m93x5KI8L;Uy2A(jf?)}3AKb{`m*%7G z>c6VqBFFZ4=}+3xl7B0*C#_z#jh*x2+_Q zN!Bg1ZuPxT-jYe~%OQPQZ(K}@99(Ps*_%ho+RM2_G1ZR~0F!zlbepj?XrNYhhsal5jrY1i)*9|~Z@(+zx@qmc12#fHO a4F3yJa>=#fj;+`L00004WE64bZO`02)+5xV|<5-3lAvYN9%=6;3_iP?-S3 z$2kPbDgfIjE1*+&;rp%z29>=)v(gWDaUIaB7=o`+;aV^@9en~W6{&7h&d?P+v^=j3 zq#@a_%eUUb&4FCK=MJ$eyO4D!lg zDpI`0M}1du&-%2au8lk}x=sg^aKrxYW%!Lt@?buR`^DMi*o_3}&S);- znmV%qaWt#Gs?Ne$ZMqS0oERn9#kokapyLeHy#^|C(36Zus@!742NKYhW=zMOvaBDx zwCQ3)CNkmlAhu_@HPJ;U}Te##P?%u_Q&BA2yzv zCGmZ}Ua>?ywUs1vY7_UFgCn~nYWvQJ*nVB&ffhKoO4R)cQ870C;X};`tRvI2ymo%v zW%F3~NUCQylRYNw5pkD)AYP)L+(veHYZLdkarPY&wJmP(mOLeyH70%zW z?f#v9w8&`tzLh&LA^iaSlF_a$luu(+ssd_ddT_4_To{yo0jgwea6P`>T2HUa{vfLd zMj372_wrFVU%aCYEvpBL20DD}W!YK&sJvVC33j%v~bR8E2_C(}Wo-nZY?A#VXU{AGPlrISEy~)TZ zVz9@InK6SscE`+^!5$BL-Ug*d5ZKF{EG!ZP_5$PQIs}2e$npuZFtC@F_e>Dj>mX*a nAh1_j@W#(zj~O%L|HSw=-pe=f9rb$n00000NkvXXu0mjf5Bjt~ literal 0 HcmV?d00001 diff --git a/tests/src/world.rs b/tests/src/world.rs index 5c2678328..9e0e91ad7 100644 --- a/tests/src/world.rs +++ b/tests/src/world.rs @@ -244,7 +244,7 @@ fn lines( engine: &mut Engine, context: Tracked, span: Span, - count: usize, + count: u64, #[default(Numbering::Pattern(NumberingPattern::from_str("A").unwrap()))] numbering: Numbering, ) -> SourceResult { diff --git a/tests/suite/introspection/counter.typ b/tests/suite/introspection/counter.typ index 2f095f2fb..b0657a2ad 100644 --- a/tests/suite/introspection/counter.typ +++ b/tests/suite/introspection/counter.typ @@ -164,3 +164,13 @@ B #context test(c.get(), (1,)) #c.step(level: 3) #context test(c.get(), (1, 0, 1)) + +--- counter-huge --- +// Test values greater than 32-bits +#let c = counter("c") +#c.update(100000000001) +#context test(c.get(), (100000000001,)) +#c.step() +#context test(c.get(), (100000000002,)) +#c.update(n => n + 2) +#context test(c.get(), (100000000004,)) diff --git a/tests/suite/layout/page.typ b/tests/suite/layout/page.typ index a35f19bb3..4df9f9cac 100644 --- a/tests/suite/layout/page.typ +++ b/tests/suite/layout/page.typ @@ -246,6 +246,16 @@ Look, ma, no page numbers! #set page(header: auto, footer: auto) Default page numbers now. +--- page-numbering-huge --- +#set page(margin: (bottom: 20pt, rest: 0pt)) +#let filler = lines(1) + +// Test values greater than 32-bits +#set page(numbering: "1/1") +#counter(page).update(100000000001) +#pagebreak() +#pagebreak() + --- page-marginal-style-text-set --- #set page(numbering: "1", margin: (bottom: 20pt)) #set text(red) diff --git a/tests/suite/model/enum.typ b/tests/suite/model/enum.typ index 7176b04e2..7ee4dc20c 100644 --- a/tests/suite/model/enum.typ +++ b/tests/suite/model/enum.typ @@ -134,6 +134,11 @@ a + 0. // Error: 22-28 invalid numbering pattern #set enum(numbering: "(())") +--- enum-numbering-huge --- +// Test values greater than 32-bits +100000000001. A ++ B + --- enum-number-align-unaffected --- // Alignment shouldn't affect number #set align(horizon) diff --git a/tests/suite/model/numbering.typ b/tests/suite/model/numbering.typ index ccd7cfc18..6af989ff1 100644 --- a/tests/suite/model/numbering.typ +++ b/tests/suite/model/numbering.typ @@ -49,6 +49,7 @@ 2000000001, "βΜκʹ, αʹ", 2000010001, "βΜκʹ, αΜαʹ, αʹ", 2056839184, "βΜκʹ, αΜ͵εχπγ, ͵θρπδ", + 12312398676, "βΜρκγʹ, αΜ͵ασλθ, ͵ηχοϛ", ) #t( pat: sym.Alpha,