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)

This commit is contained in:
Eduardo Sánchez Muñoz 2025-03-24 19:16:33 +01:00 committed by GitHub
parent 636eea18bc
commit 38213ed534
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 82 additions and 55 deletions

View File

@ -96,9 +96,13 @@ pub fn layout_enum(
let mut cells = vec![]; let mut cells = vec![];
let mut locator = locator.split(); let mut locator = locator.split();
let mut number = let mut number = elem.start(styles).unwrap_or_else(|| {
elem.start(styles) if reversed {
.unwrap_or_else(|| if reversed { elem.children.len() } else { 1 }); elem.children.len() as u64
} else {
1
}
});
let mut parents = EnumElem::parents_in(styles); let mut parents = EnumElem::parents_in(styles);
let full = elem.full(styles); let full = elem.full(styles);

View File

@ -229,10 +229,10 @@ impl Counter {
if self.is_page() { if self.is_page() {
let at_delta = let at_delta =
engine.introspector.page(location).get().saturating_sub(at_page.get()); 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 = let final_delta =
engine.introspector.pages().get().saturating_sub(final_page.get()); 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()])) Ok(CounterState(smallvec![at_state.first(), final_state.first()]))
} }
@ -250,7 +250,7 @@ impl Counter {
if self.is_page() { if self.is_page() {
let delta = let delta =
engine.introspector.page(location).get().saturating_sub(page.get()); engine.introspector.page(location).get().saturating_sub(page.get());
state.step(NonZeroUsize::ONE, delta); state.step(NonZeroUsize::ONE, delta as u64);
} }
Ok(state) Ok(state)
} }
@ -319,7 +319,7 @@ impl Counter {
let delta = page.get() - prev.get(); let delta = page.get() - prev.get();
if delta > 0 { 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(); let (mut state, page) = sequence.last().unwrap().clone();
if self.is_page() { if self.is_page() {
let delta = engine.introspector.pages().get().saturating_sub(page.get()); let delta = engine.introspector.pages().get().saturating_sub(page.get());
state.step(NonZeroUsize::ONE, delta); state.step(NonZeroUsize::ONE, delta as u64);
} }
Ok(state) Ok(state)
} }
@ -616,13 +616,13 @@ pub trait Count {
/// Counts through elements with different levels. /// Counts through elements with different levels.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq, Hash)]
pub struct CounterState(pub SmallVec<[usize; 3]>); pub struct CounterState(pub SmallVec<[u64; 3]>);
impl CounterState { impl CounterState {
/// Get the initial counter state for the key. /// Get the initial counter state for the key.
pub fn init(page: bool) -> Self { pub fn init(page: bool) -> Self {
// Special case, because pages always start at one. // 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. /// 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. /// 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(); let level = level.get();
while self.0.len() < level { while self.0.len() < level {
@ -657,7 +657,7 @@ impl CounterState {
} }
/// Get the first number of the state. /// Get the first number of the state.
pub fn first(&self) -> usize { pub fn first(&self) -> u64 {
self.0.first().copied().unwrap_or(1) self.0.first().copied().unwrap_or(1)
} }
@ -675,7 +675,7 @@ impl CounterState {
cast! { cast! {
CounterState, CounterState,
self => Value::Array(self.0.into_iter().map(IntoValue::into_value).collect()), 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 array: Array => Self(array
.into_iter() .into_iter()
.map(Value::cast) .map(Value::cast)
@ -758,7 +758,7 @@ impl Show for Packed<CounterDisplayElem> {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct ManualPageCounter { pub struct ManualPageCounter {
physical: NonZeroUsize, physical: NonZeroUsize,
logical: usize, logical: u64,
} }
impl ManualPageCounter { impl ManualPageCounter {
@ -773,7 +773,7 @@ impl ManualPageCounter {
} }
/// Get the current logical page counter state. /// Get the current logical page counter state.
pub fn logical(&self) -> usize { pub fn logical(&self) -> u64 {
self.logical self.logical
} }

View File

@ -484,7 +484,7 @@ pub struct Page {
pub supplement: Content, pub supplement: Content,
/// The logical page number (controlled by `counter(page)` and may thus not /// The logical page number (controlled by `counter(page)` and may thus not
/// match the physical number). /// match the physical number).
pub number: usize, pub number: u64,
} }
impl Page { impl Page {

View File

@ -129,7 +129,7 @@ pub struct EnumElem {
/// [Ahead], /// [Ahead],
/// ) /// )
/// ``` /// ```
pub start: Smart<usize>, pub start: Smart<u64>,
/// Whether to display the full numbering, including the numbers of /// Whether to display the full numbering, including the numbers of
/// all parent enumerations. /// all parent enumerations.
@ -217,7 +217,7 @@ pub struct EnumElem {
#[internal] #[internal]
#[fold] #[fold]
#[ghost] #[ghost]
pub parents: SmallVec<[usize; 4]>, pub parents: SmallVec<[u64; 4]>,
} }
#[scope] #[scope]
@ -274,7 +274,7 @@ impl Show for Packed<EnumElem> {
pub struct EnumItem { pub struct EnumItem {
/// The item's number. /// The item's number.
#[positional] #[positional]
pub number: Option<usize>, pub number: Option<u64>,
/// The item's body. /// The item's body.
#[required] #[required]

View File

@ -1,7 +1,7 @@
use std::str::FromStr; use std::str::FromStr;
use chinese_number::{ 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 comemo::Tracked;
use ecow::{eco_format, EcoString, EcoVec}; 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 /// If `numbering` is a pattern and more numbers than counting symbols are
/// given, the last counting symbol with its prefix is repeated. /// given, the last counting symbol with its prefix is repeated.
#[variadic] #[variadic]
numbers: Vec<usize>, numbers: Vec<u64>,
) -> SourceResult<Value> { ) -> SourceResult<Value> {
numbering.apply(engine, context, &numbers) numbering.apply(engine, context, &numbers)
} }
@ -105,7 +105,7 @@ impl Numbering {
&self, &self,
engine: &mut Engine, engine: &mut Engine,
context: Tracked<Context>, context: Tracked<Context>,
numbers: &[usize], numbers: &[u64],
) -> SourceResult<Value> { ) -> SourceResult<Value> {
Ok(match self { Ok(match self {
Self::Pattern(pattern) => Value::Str(pattern.apply(numbers).into()), Self::Pattern(pattern) => Value::Str(pattern.apply(numbers).into()),
@ -156,7 +156,7 @@ pub struct NumberingPattern {
impl NumberingPattern { impl NumberingPattern {
/// Apply the pattern to the given number. /// 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 fmt = EcoString::new();
let mut numbers = numbers.iter(); let mut numbers = numbers.iter();
@ -185,7 +185,7 @@ impl NumberingPattern {
} }
/// Apply only the k-th segment of the pattern to a number. /// 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(); let mut fmt = EcoString::new();
if let Some((prefix, _)) = self.pieces.first() { if let Some((prefix, _)) = self.pieces.first() {
fmt.push_str(prefix); fmt.push_str(prefix);
@ -379,7 +379,7 @@ impl NumberingKind {
} }
/// Apply the numbering to the given number. /// Apply the numbering to the given number.
pub fn apply(self, n: usize) -> EcoString { pub fn apply(self, n: u64) -> EcoString {
match self { match self {
Self::Arabic => eco_format!("{n}"), Self::Arabic => eco_format!("{n}"),
Self::LowerRoman => roman_numeral(n, Case::Lower), Self::LowerRoman => roman_numeral(n, Case::Lower),
@ -392,9 +392,10 @@ impl NumberingKind {
} }
const SYMBOLS: &[char] = &['*', '†', '‡', '§', '¶', '‖']; const SYMBOLS: &[char] = &['*', '†', '‡', '§', '¶', '‖'];
let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()]; let n_symbols = SYMBOLS.len() as u64;
let amount = ((n - 1) / SYMBOLS.len()) + 1; let symbol = SYMBOLS[((n - 1) % n_symbols) as usize];
std::iter::repeat_n(symbol, amount).collect() let amount = ((n - 1) / n_symbols) + 1;
std::iter::repeat_n(symbol, amount.try_into().unwrap()).collect()
} }
Self::Hebrew => hebrew_numeral(n), Self::Hebrew => hebrew_numeral(n),
@ -489,18 +490,16 @@ impl NumberingKind {
} }
Self::LowerSimplifiedChinese => { Self::LowerSimplifiedChinese => {
usize_to_chinese(ChineseVariant::Simple, ChineseCase::Lower, n).into() u64_to_chinese(ChineseVariant::Simple, ChineseCase::Lower, n).into()
} }
Self::UpperSimplifiedChinese => { Self::UpperSimplifiedChinese => {
usize_to_chinese(ChineseVariant::Simple, ChineseCase::Upper, n).into() u64_to_chinese(ChineseVariant::Simple, ChineseCase::Upper, n).into()
} }
Self::LowerTraditionalChinese => { Self::LowerTraditionalChinese => {
usize_to_chinese(ChineseVariant::Traditional, ChineseCase::Lower, n) u64_to_chinese(ChineseVariant::Traditional, ChineseCase::Lower, n).into()
.into()
} }
Self::UpperTraditionalChinese => { Self::UpperTraditionalChinese => {
usize_to_chinese(ChineseVariant::Traditional, ChineseCase::Upper, n) u64_to_chinese(ChineseVariant::Traditional, ChineseCase::Upper, n).into()
.into()
} }
Self::EasternArabic => decimal('\u{0660}', n), Self::EasternArabic => decimal('\u{0660}', n),
@ -512,7 +511,7 @@ impl NumberingKind {
} }
/// Stringify an integer to a Hebrew number. /// Stringify an integer to a Hebrew number.
fn hebrew_numeral(mut n: usize) -> EcoString { fn hebrew_numeral(mut n: u64) -> EcoString {
if n == 0 { if n == 0 {
return '-'.into(); return '-'.into();
} }
@ -566,7 +565,7 @@ fn hebrew_numeral(mut n: usize) -> EcoString {
} }
/// Stringify an integer to a Roman numeral. /// 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 { if n == 0 {
return match case { return match case {
Case::Lower => 'n'.into(), 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 /// [converter]: https://www.russellcottrell.com/greek/utilities/GreekNumberConverter.htm
/// [numbers]: https://mathshistory.st-andrews.ac.uk/HistTopics/Greek_numbers/ /// [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 = [ let thousands = [
["͵α", "͵Α"], ["͵α", "͵Α"],
["͵β", "͵Β"], ["͵β", "͵Β"],
@ -683,7 +682,7 @@ fn greek_numeral(n: usize, case: Case) -> EcoString {
let mut decimal_digits: Vec<usize> = Vec::new(); let mut decimal_digits: Vec<usize> = Vec::new();
let mut n = n; let mut n = n;
while n > 0 { while n > 0 {
decimal_digits.push(n % 10); decimal_digits.push((n % 10) as usize);
n /= 10; 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 /// You might be familiar with this scheme from the way spreadsheet software
/// tends to label its columns. /// tends to label its columns.
fn zeroless<const N_DIGITS: usize>( fn zeroless<const N_DIGITS: usize>(alphabet: [char; N_DIGITS], mut n: u64) -> EcoString {
alphabet: [char; N_DIGITS],
mut n: usize,
) -> EcoString {
if n == 0 { if n == 0 {
return '-'.into(); return '-'.into();
} }
let n_digits = N_DIGITS as u64;
let mut cs = EcoString::new(); let mut cs = EcoString::new();
while n > 0 { while n > 0 {
n -= 1; n -= 1;
cs.push(alphabet[n % N_DIGITS]); cs.push(alphabet[(n % n_digits) as usize]);
n /= N_DIGITS; n /= n_digits;
} }
cs.chars().rev().collect() cs.chars().rev().collect()
} }
@ -797,7 +794,7 @@ fn zeroless<const N_DIGITS: usize>(
/// Stringify a number using a base-10 counting system with a zero digit. /// Stringify a number using a base-10 counting system with a zero digit.
/// ///
/// This function assumes that the digits occupy contiguous codepoints. /// 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 { if n == 0 {
return start.into(); return start.into();
} }

View File

@ -1,5 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::num::NonZeroUsize; use std::num::NonZeroU64;
use ecow::EcoString; use ecow::EcoString;
use pdf_writer::types::{ActionType, AnnotationFlags, AnnotationType, NumberingStyle}; 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 // 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 // will differ, but we can at least use labels to indicate what was
// the corresponding real page number in the Typst document. // 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)); pages.push(Some(encoded));
} }
@ -219,7 +219,7 @@ pub(crate) struct PdfPageLabel {
/// ///
/// Describes where to start counting from when setting a style. /// Describes where to start counting from when setting a style.
/// (Has to be greater or equal than 1) /// (Has to be greater or equal than 1)
pub offset: Option<NonZeroUsize>, pub offset: Option<NonZeroU64>,
} }
/// A PDF page label number style. /// A PDF page label number style.
@ -242,7 +242,7 @@ pub enum PdfPageLabelStyle {
impl PdfPageLabel { impl PdfPageLabel {
/// Create a new `PdfNumbering` from a `Numbering` applied to a page /// Create a new `PdfNumbering` from a `Numbering` applied to a page
/// number. /// number.
fn generate(numbering: &Numbering, number: usize) -> Option<PdfPageLabel> { fn generate(numbering: &Numbering, number: u64) -> Option<PdfPageLabel> {
let Numbering::Pattern(pat) = numbering else { let Numbering::Pattern(pat) = numbering else {
return None; return None;
}; };
@ -275,18 +275,18 @@ impl PdfPageLabel {
(!prefix.is_empty()).then(|| prefix.clone()) (!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 }) Some(PdfPageLabel { prefix, style, offset })
} }
/// Creates an arabic page label with the specified page number. /// Creates an arabic page label with the specified page number.
/// For example, this will display page label `11` when given the page /// For example, this will display page label `11` when given the page
/// number 11. /// number 11.
fn arabic(number: usize) -> PdfPageLabel { fn arabic(number: u64) -> PdfPageLabel {
PdfPageLabel { PdfPageLabel {
prefix: None, prefix: None,
style: Some(PdfPageLabelStyle::Arabic), style: Some(PdfPageLabelStyle::Arabic),
offset: NonZeroUsize::new(number), offset: NonZeroU64::new(number),
} }
} }
} }

View File

@ -778,7 +778,7 @@ node! {
impl<'a> EnumItem<'a> { impl<'a> EnumItem<'a> {
/// The explicit numbering, if any: `23.`. /// The explicit numbering, if any: `23.`.
pub fn number(self) -> Option<usize> { pub fn number(self) -> Option<u64> {
self.0.children().find_map(|node| match node.kind() { self.0.children().find_map(|node| match node.kind() {
SyntaxKind::EnumMarker => node.text().trim_end_matches('.').parse().ok(), SyntaxKind::EnumMarker => node.text().trim_end_matches('.').parse().ok(),
_ => Option::None, _ => Option::None,

View File

@ -480,7 +480,7 @@ impl Lexer<'_> {
self.s.eat_while(char::is_ascii_digit); self.s.eat_while(char::is_ascii_digit);
let read = self.s.from(start); let read = self.s.from(start);
if self.s.eat_if('.') && self.space_or_end() && read.parse::<usize>().is_ok() { if self.s.eat_if('.') && self.space_or_end() && read.parse::<u64>().is_ok() {
return SyntaxKind::EnumMarker; return SyntaxKind::EnumMarker;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

View File

@ -244,7 +244,7 @@ fn lines(
engine: &mut Engine, engine: &mut Engine,
context: Tracked<Context>, context: Tracked<Context>,
span: Span, span: Span,
count: usize, count: u64,
#[default(Numbering::Pattern(NumberingPattern::from_str("A").unwrap()))] #[default(Numbering::Pattern(NumberingPattern::from_str("A").unwrap()))]
numbering: Numbering, numbering: Numbering,
) -> SourceResult<Value> { ) -> SourceResult<Value> {

View File

@ -164,3 +164,13 @@ B
#context test(c.get(), (1,)) #context test(c.get(), (1,))
#c.step(level: 3) #c.step(level: 3)
#context test(c.get(), (1, 0, 1)) #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,))

View File

@ -246,6 +246,16 @@ Look, ma, no page numbers!
#set page(header: auto, footer: auto) #set page(header: auto, footer: auto)
Default page numbers now. 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 --- --- page-marginal-style-text-set ---
#set page(numbering: "1", margin: (bottom: 20pt)) #set page(numbering: "1", margin: (bottom: 20pt))
#set text(red) #set text(red)

View File

@ -134,6 +134,11 @@ a + 0.
// Error: 22-28 invalid numbering pattern // Error: 22-28 invalid numbering pattern
#set enum(numbering: "(())") #set enum(numbering: "(())")
--- enum-numbering-huge ---
// Test values greater than 32-bits
100000000001. A
+ B
--- enum-number-align-unaffected --- --- enum-number-align-unaffected ---
// Alignment shouldn't affect number // Alignment shouldn't affect number
#set align(horizon) #set align(horizon)

View File

@ -49,6 +49,7 @@
2000000001, "βΜκʹ, αʹ", 2000000001, "βΜκʹ, αʹ",
2000010001, "βΜκʹ, αΜαʹ, αʹ", 2000010001, "βΜκʹ, αΜαʹ, αʹ",
2056839184, "βΜκʹ, αΜ͵εχπγ, ͵θρπδ", 2056839184, "βΜκʹ, αΜ͵εχπγ, ͵θρπδ",
12312398676, "βΜρκγʹ, αΜ͵ασλθ, ͵ηχοϛ",
) )
#t( #t(
pat: sym.Alpha, pat: sym.Alpha,