///|
fn pdf_page_label_copy_bytes(
output : Array[Byte],
position : Int,
bytes : PdfBytes,
) -> Int {
let mut position = position
for byte in bytes {
output[position] = byte
position += 1
}
position
}
///|
fn pdf_page_label_ascii_bytes(text : String) -> PdfBytes {
@ascii.encode(text)
}
///|
fn pdf_page_label_roman_pair(pair_index : Int) -> (Int, Byte, Byte?) {
match pair_index {
0 => (900, b'C', Some(b'M'))
1 => (500, b'D', None)
2 => (400, b'C', Some(b'D'))
3 | 4 | 5 => (100, b'C', None)
6 => (90, b'X', Some(b'C'))
7 => (50, b'L', None)
8 => (40, b'X', Some(b'L'))
9 | 10 | 11 => (10, b'X', None)
12 => (9, b'I', Some(b'X'))
13 => (5, b'V', None)
14 => (4, b'I', Some(b'V'))
_ => (1, b'I', None)
}
}
///|
fn pdf_page_label_roman_pair_length(pair_index : Int) -> Int {
let (_, _, suffix) = pdf_page_label_roman_pair(pair_index)
match suffix {
Some(_) => 2
None => 1
}
}
///|
fn pdf_page_label_copy_roman_pair(
output : Array[Byte],
position : Int,
pair_index : Int,
) -> Int {
let (_, prefix, suffix) = pdf_page_label_roman_pair(pair_index)
output[position] = prefix
match suffix {
Some(byte) => {
output[position + 1] = byte
position + 2
}
None => position + 1
}
}
///|
fn pdf_page_label_roman_upper_length(number : Int) -> Int {
let mut length = 0
let mut remaining = number
if remaining >= 1000 {
length += remaining / 1000
remaining = remaining % 1000
}
if remaining > 0 {
let mut pair_index = 0
while pair_index < 18 {
let (value, _, _) = pdf_page_label_roman_pair(pair_index)
if remaining >= value {
length += pdf_page_label_roman_pair_length(pair_index)
remaining -= value
} else {
pair_index += 1
}
}
}
length
}
///|
/// Format a positive integer as uppercase Roman numeral bytes.
///
/// Values less than or equal to zero produce an empty byte string, matching
/// cpdf's page-label formatter behavior.
pub fn pdf_page_label_roman_upper(number : Int) -> PdfBytes {
let output = Array::make(pdf_page_label_roman_upper_length(number), b'\x00')
let mut position = 0
let mut remaining = number
if remaining >= 1000 {
for _ in 0..<(remaining / 1000) {
output[position] = b'M'
position += 1
}
remaining = remaining % 1000
}
if remaining > 0 {
let mut pair_index = 0
while pair_index < 18 {
let (value, _, _) = pdf_page_label_roman_pair(pair_index)
if remaining >= value {
position = pdf_page_label_copy_roman_pair(output, position, pair_index)
remaining -= value
} else {
pair_index += 1
}
}
}
Bytes::from_array(output)
}
///|
/// Lowercase ASCII A-Z bytes and preserve all other bytes unchanged.
pub fn pdf_page_label_lowercase(bytes : PdfBytes) -> PdfBytes {
let output = Array::make(bytes.length(), b'\x00')
for i in 0..= 65 && value <= 90 {
output[i] = (value + 32).to_byte()
} else {
output[i] = byte
}
}
Bytes::from_array(output)
}
///|
fn pdf_page_label_letters(number : Int, lowercase : Bool) -> PdfBytes {
if number > 0 {
let letter = (number - 1) % 26
let count = (number - 1) / 26 + 1
let base = if lowercase { 97 } else { 65 }
let output = Array::make(count, (base + letter).to_byte())
Bytes::from_array(output)
} else {
[]
}
}
///|
/// Format the numeric suffix for a page label style.
///
/// Prefix-only labels return an empty byte string.
pub fn pdf_page_label_number_bytes(
number : Int,
style : PdfPageLabelStyle,
) -> PdfBytes {
match style {
PageLabelNoLabelPrefixOnly => []
PageLabelDecimalArabic => pdf_page_label_ascii_bytes(number.to_string())
PageLabelUppercaseRoman => pdf_page_label_roman_upper(number)
PageLabelLowercaseRoman =>
pdf_page_label_lowercase(pdf_page_label_roman_upper(number))
PageLabelUppercaseLetters => pdf_page_label_letters(number, false)
PageLabelLowercaseLetters => pdf_page_label_letters(number, true)
}
}
///|
/// Render the visible label text for one page within a label range.
///
/// The optional prefix bytes are copied first. The numeric suffix is calculated
/// from the range's 1-based `start_page` and `start_value`, then formatted with
/// the range style.
pub fn pdf_page_label_text_of_single(
page_number : Int,
label : PdfPageLabel,
) -> PdfBytes {
let real_number = page_number - (label.start_page - label.start_value)
let number_bytes = pdf_page_label_number_bytes(real_number, label.label_style)
let prefix_length = match label.label_prefix {
Some(prefix) => prefix.length()
None => 0
}
let output = Array::make(prefix_length + number_bytes.length(), b'\x00')
let mut position = 0
match label.label_prefix {
Some(prefix) =>
for byte in prefix {
output[position] = byte
position += 1
}
None => ()
}
for byte in number_bytes {
output[position] = byte
position += 1
}
Bytes::from_array(output)
}
///|
/// Return the effective page-label range for one concrete page.
///
/// The returned range keeps the style and prefix, but normalizes `start_page`
/// and `start_value` to the numeric value displayed on `page_number`.
pub fn pdf_page_label_of_single(
page_number : Int,
label : PdfPageLabel,
) -> PdfPageLabel {
let real_number = page_number - (label.start_page - label.start_value)
{ ..label, start_page: real_number, start_value: real_number, }
}
///|
/// Find the active page-label range for a 1-based page number.
///
/// The input ranges are expected in document order. Returns `None` when the
/// document has no page-label ranges.
pub fn pdf_page_label_of_page_number(
page_number : Int,
labels : ArrayView[PdfPageLabel],
) -> PdfPageLabel? {
if labels.length() == 0 {
None
} else {
let mut label = labels[0]
for index in 1.. PdfBytes? {
if labels.length() == 0 {
None
} else {
let mut label = labels[0]
for index in 1..