///|
/// Split one byte-encoded text run so at least one word is emitted.
///
/// This follows CamlPDF's `cpdftype.ml` behavior: words are split only on ASCII
/// space, the first word is kept even when it exceeds `space_left`, and a space
/// separator is retained after every non-final word in the emitted prefix.
pub fn pdf_type_split_text(
space_left : Double,
widths : ArrayView[Double],
text : BytesView,
) -> (@core.PdfBytes, Bool, @core.PdfBytes) raise @core.PdfError {
let output = Array::make(text.length(), b'\x00')
let mut output_position = 0
let mut position = 0
let mut remaining_space = space_left
while position < text.length() {
let word_start = position
while position < text.length() && text[position] != b' ' {
position += 1
}
let word_end = position
let word = text[word_start:word_end]
let word_width = pdf_type_width_of_bytes(widths, word)
if output_position == 0 || word_width < remaining_space {
for byte in word {
output[output_position] = byte
output_position += 1
}
if word_end < text.length() {
output[output_position] = b' '
output_position += 1
let space_width = pdf_type_width_of_bytes(widths, pdf_type_ascii(" "))
remaining_space = remaining_space - word_width - space_width
position = word_end + 1
} else {
remaining_space = remaining_space - word_width
position = word_end
}
} else {
return (
Bytes::from_array(output[:output_position]),
true,
text[word_start:].to_owned(),
)
}
}
(
Bytes::from_array(output[:output_position]),
false,
text[position:].to_owned(),
)
}
///|
/// Lay out cpdftype elements into wrapped lines for the requested paper width.
///
/// The `rmargin` parameter is accepted for parity with CamlPDF but intentionally
/// ignored: the source computes the maximum x position as `page_width - lmargin`.
pub fn pdf_type_layout(
lmargin : Double,
rmargin : Double,
paper : @geometry.PaperSize,
elements : ArrayView[PdfTypeElement],
) -> Array[PdfTypeElement] raise @core.PdfError {
ignore(rmargin)
let xpos_max = @geometry.unit_points(paper.width(), paper.unit()) - lmargin
let mut xpos = lmargin
let mut widths : Array[Double] = []
let output : Array[PdfTypeElement] = []
for element in elements {
match element {
PdfTypeFont(name, font, font_size) => {
widths = pdf_type_font_widths(font, font_size)
output.push(PdfTypeFont(name, font, font_size))
}
PdfTypeText(text) =>
if text.length() > 0 {
let mut current_text = text
while current_text.length() > 0 {
let (this_line, needs_newline, rest) = pdf_type_split_text(
xpos_max - xpos,
widths,
current_text,
)
output.push(PdfTypeText(this_line))
xpos = xpos + pdf_type_width_of_bytes(widths, this_line)
if needs_newline {
output.push(PdfTypeNewLine)
xpos = lmargin
}
current_text = rest
}
}
PdfTypeHGlue(length) => {
xpos = xpos + length
output.push(PdfTypeHGlue(length))
if xpos >= xpos_max {
output.push(PdfTypeNewLine)
xpos = lmargin
}
}
PdfTypeNewLine => {
xpos = lmargin
output.push(PdfTypeNewLine)
}
other => output.push(other)
}
}
output
}
///|
fn pdf_type_push_page_break(
output : Array[PdfTypeElement],
current_tag : (String, Int)?,
) -> Unit {
match current_tag {
Some(_) => output.push(PdfTypeEndTag)
None => ()
}
output.push(PdfTypeNewPage)
match current_tag {
Some((tag, number)) => output.push(PdfTypeTag(tag, number))
None => ()
}
}
///|
/// Insert page breaks into a laid-out cpdftype stream.
///
/// Active structure tags are closed before an inserted `PdfTypeNewPage` and
/// reopened immediately after it, matching CamlPDF's pagination helper.
pub fn pdf_type_paginate(
tmargin : Double,
bmargin : Double,
paper : @geometry.PaperSize,
elements : ArrayView[PdfTypeElement],
) -> Array[PdfTypeElement] {
let max_ypos = @geometry.unit_points(paper.height(), paper.unit()) - bmargin
let output : Array[PdfTypeElement] = []
let mut ypos = tmargin
let mut font_size = 0.0
let mut current_tag : (String, Int)? = None
for element in elements {
match element {
PdfTypeVGlue(length) => {
ypos = ypos + length
output.push(PdfTypeVGlue(length))
if ypos > max_ypos {
ypos = tmargin + font_size
pdf_type_push_page_break(output, current_tag)
}
}
PdfTypeNewLine => {
ypos = ypos + font_size * 1.3
output.push(PdfTypeNewLine)
if ypos > max_ypos {
ypos = tmargin + font_size
pdf_type_push_page_break(output, current_tag)
}
}
PdfTypeFont(name, font, size) => {
font_size = size
output.push(PdfTypeFont(name, font, size))
}
PdfTypeNewPage => {
ypos = tmargin + font_size
pdf_type_push_page_break(output, current_tag)
}
PdfTypeBeginDocument => {
ypos = tmargin + font_size
output.push(PdfTypeBeginDocument)
}
PdfTypeTag(tag, number) => {
current_tag = Some((tag, number))
output.push(PdfTypeTag(tag, number))
}
PdfTypeEndTag => {
current_tag = None
output.push(PdfTypeEndTag)
}
other => output.push(other)
}
}
output
}
///|
fn pdf_type_number_tags(
elements : ArrayView[PdfTypeElement],
) -> Array[PdfTypeElement] {
let output : Array[PdfTypeElement] = []
let mut tag_number = 0
for element in elements {
match element {
PdfTypeTag(name, _) => {
output.push(PdfTypeTag(name, tag_number))
tag_number += 1
}
other => output.push(other)
}
}
output
}