///|
fn pdf_writer_exponent_marker(text : BytesView) -> Int {
for i in 0.. Int raise @core.PdfError {
let digit_start = match text.get(start) {
Some(b'+') | Some(b'-') => start + 1
_ => start
}
guard digit_start < text.length() else { raise NumberExpected }
let mut value = 0
for index in digit_start.. "123456789012"); this is a deliberate fix, pinned by
/// the wide-real regression test.
fn pdf_writer_push_significant_digits(
output : Array[Byte],
digits : Array[Int],
decimal_position : Int,
) -> Unit {
let significant_start = for i in 0..= 0 else {
pdf_writer_push_byte(output, '0')
return
}
let keep_end = significant_start + PDF_WRITER_SIGNIFICANT_DIGITS
let copy_end = if keep_end < digits.length() {
keep_end
} else {
digits.length()
}
let rounded = digits[significant_start:copy_end].to_owned()
let mut adjusted_decimal_position = decimal_position - significant_start
if keep_end < digits.length() && digits[keep_end] >= 5 {
let carried = for carry_index = rounded.length() - 1; carry_index >= 0; {
if rounded[carry_index] == 9 {
rounded[carry_index] = 0
continue carry_index - 1
}
rounded[carry_index] += 1
break true
} nobreak {
false
}
if !carried {
rounded.insert(0, 1)
adjusted_decimal_position += 1
}
}
let end = for end = rounded.length(); end > adjusted_decimal_position &&
end > 0 &&
rounded[end - 1] == 0; {
continue end - 1
} nobreak {
end
}
if adjusted_decimal_position <= 0 {
pdf_writer_push_ascii(output, "0.")
for _ in 0..<-adjusted_decimal_position {
pdf_writer_push_byte(output, '0')
}
pdf_writer_push_digit_values(output, rounded, 0, end)
} else if adjusted_decimal_position >= end {
pdf_writer_push_digit_values(output, rounded, 0, end)
for _ in 0..<(adjusted_decimal_position - end) {
pdf_writer_push_byte(output, '0')
}
} else {
pdf_writer_push_digit_values(output, rounded, 0, adjusted_decimal_position)
pdf_writer_push_byte(output, '.')
pdf_writer_push_digit_values(
output, rounded, adjusted_decimal_position, end,
)
}
}
///|
fn pdf_writer_push_digit_values(
output : Array[Byte],
digits : ArrayView[Int],
start : Int,
end : Int,
) -> Unit {
for i in start.. Unit raise @core.PdfError {
let mut mantissa_start = 0
if exponent_at > 0 {
match text[0] {
b'+' => mantissa_start = 1
b'-' => {
pdf_writer_push_byte(output, '-')
mantissa_start = 1
}
_ => ()
}
}
let exponent = pdf_writer_parse_signed_int(text, exponent_at + 1)
let digits : Array[Int] = []
let mut fractional_digits = 0
let mut seen_decimal = false
for i in mantissa_start.. seen_decimal = true
b'0'..=b'9' => {
digits.push(text[i].to_int() - '0')
if seen_decimal {
fractional_digits += 1
}
}
_ => raise NumberExpected
}
}
if digits is [] {
pdf_writer_push_byte(output, '0')
} else {
let decimal_position = digits.length() + exponent - fractional_digits
pdf_writer_push_significant_digits(output, digits, decimal_position)
}
}
///|
fn pdf_writer_push_plain_significant_real(
output : Array[Byte],
text : BytesView,
) -> Unit raise @core.PdfError {
let (start, negative) = match text.get(0) {
Some(b'+') => (1, false)
Some(b'-') => (1, true)
_ => (0, false)
}
let digits : Array[Int] = []
let mut decimal_position = -1
for index in start.. {
guard decimal_position < 0 else { raise NumberExpected }
decimal_position = digits.length()
}
b'0'..=b'9' => digits.push(text[index].to_int() - '0')
_ => raise NumberExpected
}
}
guard decimal_position >= 0 else {
pdf_writer_push_view(output, text)
return
}
// the sign is emitted only when a significant digit exists, matching the
// previous behavior where -0.000 rendered as a bare 0
if negative && digits.iter().any(digit => digit != 0) {
pdf_writer_push_byte(output, '-')
}
pdf_writer_push_significant_digits(output, digits, decimal_position)
}
///|
fn pdf_writer_push_fixed_six_real(output : Array[Byte], value : Double) -> Unit {
if value < 0.0 {
pdf_writer_push_byte(output, '-')
}
pdf_writer_push_ascii(output, "0.")
let magnitude = if value < 0.0 { -value } else { value }
let scaled = (magnitude * 1000000.0).round().to_int()
for divisor = 100000; divisor > 0; {
pdf_writer_push_byte(output, '0' + scaled / divisor % 10)
continue divisor / 10
}
}
///|
fn pdf_writer_push_real(
output : Array[Byte],
value : Double,
) -> Unit raise @core.PdfError {
if value.trunc() != value && value < 0.0001 && value > -0.0001 {
pdf_writer_push_fixed_six_real(output, value)
} else {
let text = @ascii.encode(value.to_string())
match pdf_writer_exponent_marker(text) {
-1 => pdf_writer_push_plain_significant_real(output, text)
exponent_at => pdf_writer_push_scientific_real(output, text, exponent_at)
}
}
}
///|
/// Format a PDF real number as bytes.
///
/// Scientific notation is expanded to PDF-compatible decimal form where
/// possible. Non-finite or unparsable numeric text raises `@core.PdfError`.
pub fn pdf_write_real(value : Double) -> @core.PdfBytes raise @core.PdfError {
let output : Array[Byte] = []
pdf_writer_push_real(output, value)
Bytes::from_array(output)
}
///|
/// Compatibility alias for `pdf_write_real`.
pub fn pdf_format_real(value : Double) -> @core.PdfBytes raise @core.PdfError {
pdf_write_real(value)
}
///|
/// The largest magnitude a PDF integer is sure to have (PDF 32000-1,
/// Annex C): an integral real beyond it is written with a decimal point,
/// so that a reader does not take it for an integer that overflows.
const PDF_WRITER_INTEGER_LIMIT : Double = 2147483647.0
///|
fn pdf_writer_push_exact_real(
output : Array[Byte],
value : Double,
) -> Unit raise @core.PdfError {
// NaN and the infinities have no PDF form
guard value - value == 0.0 else { raise NumberExpected }
if value == 0.0 {
pdf_writer_push_byte(output, '0')
return
}
// the shortest decimal that reads back as `value`, perhaps with an
// exponent, which PDF's number syntax does not have
let text = @ascii.encode(value.to_string())
let negative = text[0] is b'-'
let start = if negative { 1 } else { 0 }
let exponent_at = pdf_writer_exponent_marker(text)
let mantissa_end = if exponent_at < 0 { text.length() } else { exponent_at }
let exponent = if exponent_at < 0 {
0
} else {
pdf_writer_parse_signed_int(text, exponent_at + 1)
}
// the significant digits, and where the point falls among them
let digits : Array[Byte] = []
let mut point = -1
for i in start.. point = digits.length()
b'0'..=b'9' => digits.push(text[i])
_ => raise NumberExpected
}
}
if point < 0 {
point = digits.length()
}
point += exponent
if negative {
pdf_writer_push_byte(output, '-')
}
if point <= 0 {
pdf_writer_push_ascii(output, "0.")
for _ in 0..<-point {
pdf_writer_push_byte(output, '0')
}
for digit in digits {
output.push(digit)
}
} else if point >= digits.length() {
for digit in digits {
output.push(digit)
}
for _ in 0..<(point - digits.length()) {
pdf_writer_push_byte(output, '0')
}
if value.abs() > PDF_WRITER_INTEGER_LIMIT {
pdf_writer_push_ascii(output, ".0")
}
} else {
for digit in digits[:point] {
output.push(digit)
}
pdf_writer_push_byte(output, '.')
for digit in digits[point:] {
output.push(digit)
}
}
}
///|
/// Format a PDF real number exactly: the shortest decimal that reads back
/// as `value`, spelt out without an exponent (PDF 32000-1, 7.3.3), as
/// `PdfExactReal` is written. An integral value is written as an integer
/// where a PDF integer is sure to hold it (up to 2147483647 in magnitude,
/// Annex C) and with a decimal point beyond. `-0` is written as `0`.
/// Non-finite values raise `@core.PdfError`.
///
/// Annex C also bounds what a reader is sure to hold as a real (about
/// ±3.403e38, and values nearer 0 than about 1.175e-38 read as 0); values
/// beyond are written, but are the caller's to avoid.
pub fn pdf_write_exact_real(
value : Double,
) -> @core.PdfBytes raise @core.PdfError {
let output : Array[Byte] = []
pdf_writer_push_exact_real(output, value)
Bytes::from_array(output)
}