const HELV_BOLD_ASCII: [u16; 95] = [
278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556,
556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667,
611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667,
667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556,
278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584,
];
fn extra_glyph_width(c: char) -> Option<u16> {
Some(match c {
'\u{2014}' => 1000, '\u{2013}' => 556, '\u{2018}' | '\u{2019}' => 278, '\u{201C}' | '\u{201D}' => 500, '\u{00A7}' => 556, '\u{2022}' => 350, '\u{2026}' => 1000, _ => return None,
})
}
const UNKNOWN_GLYPH_WIDTH: u16 = 1000;
fn glyph_width(c: char) -> u16 {
if c.is_ascii() {
let code = c as u32;
if (0x20..=0x7E).contains(&code) {
return HELV_BOLD_ASCII[(code - 0x20) as usize];
}
}
extra_glyph_width(c).unwrap_or(UNKNOWN_GLYPH_WIDTH)
}
pub(crate) fn text_width_pts(s: &str, size_pt: f32) -> f32 {
let units: u32 = s.chars().map(|c| glyph_width(c) as u32).sum();
units as f32 * size_pt / 1000.0
}
const EPS: f32 = 0.01;
fn split_paragraphs(text: &str) -> Vec<String> {
let mut paragraphs = Vec::new();
let mut current: Vec<&str> = Vec::new();
for line in text.lines() {
if line.trim().is_empty() {
if !current.is_empty() {
paragraphs.push(current.join(" "));
current.clear();
}
} else {
current.push(line);
}
}
if !current.is_empty() {
paragraphs.push(current.join(" "));
}
paragraphs
}
fn wrap_paragraphs(paragraphs: &[String], line_widths: &[f32], size_pt: f32) -> Vec<String> {
debug_assert!(!line_widths.is_empty());
let space_w = text_width_pts(" ", size_pt);
let mut lines: Vec<String> = Vec::new();
for paragraph in paragraphs {
let mut current = String::new();
let mut current_w = 0.0_f32;
for word in paragraph.split_whitespace() {
let word_w = text_width_pts(word, size_pt);
let idx = lines.len();
let max_w = line_widths
.get(idx)
.copied()
.unwrap_or_else(|| *line_widths.last().unwrap());
if current.is_empty() {
current = word.to_string();
current_w = word_w;
} else if current_w + space_w + word_w <= max_w + EPS {
current.push(' ');
current.push_str(word);
current_w += space_w + word_w;
} else {
lines.push(std::mem::take(&mut current));
current = word.to_string();
current_w = word_w;
}
}
if !current.is_empty() {
lines.push(current);
}
}
lines
}
pub const PART_IV_CROSS_REFERENCE_PREFIX: &str = "Part II, line 1 (continued): ";
#[derive(Debug, Clone, Default)]
pub(crate) struct PartIiWrap {
pub part_ii_line1: String,
pub part_iv_lines: Vec<String>,
}
#[derive(Debug, Clone, Copy)]
pub struct PartIiOverflow {
pub rows_needed: usize,
pub capacity: usize,
pub chars_fit: usize,
}
pub(crate) fn wrap_part_ii(
narrative: &str,
part_ii_width_pts: f32,
part_iv_width_pts: f32,
part_iv_capacity: usize,
size_pt: f32,
) -> Result<PartIiWrap, PartIiOverflow> {
let paragraphs = split_paragraphs(narrative);
if paragraphs.is_empty() {
return Ok(PartIiWrap::default());
}
let capacity = 1 + part_iv_capacity;
let prefix_w = text_width_pts(PART_IV_CROSS_REFERENCE_PREFIX, size_pt);
let line_widths = [
part_ii_width_pts,
(part_iv_width_pts - prefix_w).max(0.0),
part_iv_width_pts,
];
let lines = wrap_paragraphs(¶graphs, &line_widths, size_pt);
let too_wide = lines.iter().enumerate().any(|(i, l)| {
let budget = line_widths
.get(i)
.copied()
.unwrap_or_else(|| *line_widths.last().unwrap());
text_width_pts(l, size_pt) > budget + EPS
});
if too_wide || lines.len() > capacity {
let rows_needed = if too_wide {
lines.len().max(capacity + 1)
} else {
lines.len()
};
let fit_count = capacity.min(lines.len());
let mut fit: Vec<String> = lines[..fit_count].to_vec();
if let Some(first_iv) = fit.get_mut(1) {
if let Some(stripped) = first_iv.strip_prefix(PART_IV_CROSS_REFERENCE_PREFIX) {
*first_iv = stripped.to_string();
}
}
let chars_fit = fit.join(" ").chars().count();
return Err(PartIiOverflow {
rows_needed,
capacity,
chars_fit,
});
}
let part_ii_line1 = lines[0].clone();
let mut part_iv_lines: Vec<String> = lines.get(1..).unwrap_or(&[]).to_vec();
if let Some(first) = part_iv_lines.first_mut() {
*first = format!("{PART_IV_CROSS_REFERENCE_PREFIX}{first}");
}
Ok(PartIiWrap {
part_ii_line1,
part_iv_lines,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn measures_known_widths() {
assert!((text_width_pts("MM", 8.0) - 13.328).abs() < 0.01);
assert!(text_width_pts("WWWW", 8.0) > text_width_pts("iiii", 8.0));
assert_eq!(text_width_pts("", 8.0), 0.0);
}
#[test]
fn backtick_and_apostrophe_use_the_assets_own_widths_not_the_generic_afm() {
assert_eq!(text_width_pts("`", 8.0), 333.0 * 8.0 / 1000.0);
assert_eq!(text_width_pts("'", 8.0), 238.0 * 8.0 / 1000.0);
}
#[test]
fn unknown_glyphs_use_the_widest_known_width_never_narrower() {
let widest_known = *HELV_BOLD_ASCII.iter().max().unwrap();
assert!(UNKNOWN_GLYPH_WIDTH as u32 >= widest_known as u32);
assert_eq!(glyph_width('\u{E000}'), UNKNOWN_GLYPH_WIDTH);
}
#[test]
fn split_paragraphs_treats_a_blank_line_as_a_boundary() {
let paras = split_paragraphs("first account of events\n\nsecond, unrelated account");
assert_eq!(
paras,
vec![
"first account of events".to_string(),
"second, unrelated account".to_string(),
]
);
}
#[test]
fn split_paragraphs_collapses_multiple_blank_lines_to_one_boundary() {
let paras = split_paragraphs("a\n\n\n\nb");
assert_eq!(paras, vec!["a".to_string(), "b".to_string()]);
}
#[test]
fn split_paragraphs_of_empty_text_yields_no_paragraphs() {
assert_eq!(split_paragraphs(""), Vec::<String>::new());
assert_eq!(split_paragraphs(" \n \n"), Vec::<String>::new());
}
#[test]
fn wrap_paragraphs_never_splits_a_word_and_preserves_every_word_in_order() {
let paras = split_paragraphs("one two three four five six seven eight nine ten");
let lines = wrap_paragraphs(¶s, &[60.0], 8.0); assert!(lines.len() > 1, "fixture premise: must actually wrap");
let rejoined: Vec<&str> = lines.iter().flat_map(|l| l.split_whitespace()).collect();
let original: Vec<&str> = "one two three four five six seven eight nine ten"
.split_whitespace()
.collect();
assert_eq!(
rejoined, original,
"no word may be dropped, split, or reordered"
);
for line in &lines {
assert!(
text_width_pts(line, 8.0) <= 60.0 + EPS,
"line {line:?} exceeds the 60pt budget"
);
}
}
#[test]
fn wrap_paragraphs_hard_breaks_between_paragraphs_never_shares_a_line() {
let paras = split_paragraphs("short one\n\nshort two");
let lines = wrap_paragraphs(¶s, &[1000.0], 8.0);
assert_eq!(
lines,
vec!["short one".to_string(), "short two".to_string()],
"a paragraph boundary must start a new line even when there is room to share one"
);
}
#[test]
fn wrap_paragraphs_of_no_paragraphs_yields_no_lines() {
assert_eq!(wrap_paragraphs(&[], &[518.4], 8.0), Vec::<String>::new());
}
#[test]
fn wrap_part_ii_fits_entirely_on_part_ii_line_1_when_short() {
let wrapped = wrap_part_ii("a short narrative", 514.4, 536.0, 27, 8.0).unwrap();
assert_eq!(wrapped.part_ii_line1, "a short narrative");
assert!(wrapped.part_iv_lines.is_empty());
}
#[test]
fn wrap_part_ii_of_empty_narrative_writes_nothing() {
let wrapped = wrap_part_ii("", 514.4, 536.0, 27, 8.0).unwrap();
assert_eq!(wrapped.part_ii_line1, "");
assert!(wrapped.part_iv_lines.is_empty());
}
#[test]
fn wrap_part_ii_spills_to_part_iv_with_the_cross_reference_prefix() {
let wrapped = wrap_part_ii(
"one two three four five six seven eight",
60.0,
1000.0,
27,
8.0,
)
.unwrap();
assert!(!wrapped.part_iv_lines.is_empty(), "must have spilled");
assert!(
wrapped.part_iv_lines[0].starts_with(PART_IV_CROSS_REFERENCE_PREFIX),
"the FIRST Part IV line must carry the IRS-required cross-reference: {:?}",
wrapped.part_iv_lines[0]
);
for later in &wrapped.part_iv_lines[1..] {
assert!(
!later.starts_with(PART_IV_CROSS_REFERENCE_PREFIX),
"only the FIRST Part IV line carries the cross-reference: {later:?}"
);
}
let mut got: Vec<&str> = wrapped.part_ii_line1.split_whitespace().collect();
for (i, l) in wrapped.part_iv_lines.iter().enumerate() {
let text = if i == 0 {
l.strip_prefix(PART_IV_CROSS_REFERENCE_PREFIX).unwrap()
} else {
l.as_str()
};
got.extend(text.split_whitespace());
}
assert_eq!(
got,
"one two three four five six seven eight"
.split_whitespace()
.collect::<Vec<_>>()
);
}
#[test]
fn wrap_part_ii_treats_a_paragraph_break_as_a_hard_break_across_the_wrap() {
let wrapped =
wrap_part_ii("first account\n\nsecond account", 200.0, 1000.0, 27, 8.0).unwrap();
assert_eq!(wrapped.part_ii_line1, "first account");
assert_eq!(wrapped.part_iv_lines.len(), 1);
assert_eq!(
wrapped.part_iv_lines[0],
format!("{PART_IV_CROSS_REFERENCE_PREFIX}second account")
);
}
#[test]
fn wrap_part_ii_fails_closed_when_more_lines_are_needed_than_available() {
let text = "word ".repeat(900);
let err = wrap_part_ii(&text, 514.4, 536.0, 27, 8.0)
.expect_err("900 words must not fit in 1 + 27 = 28 lines");
assert_eq!(err.capacity, 28);
assert!(
err.rows_needed > err.capacity,
"rows_needed {} must exceed capacity {}",
err.rows_needed,
err.capacity
);
assert!(
err.chars_fit > 0 && err.chars_fit < text.chars().count(),
"chars_fit {} must be a real partial count, not 0 or the whole narrative ({})",
err.chars_fit,
text.chars().count()
);
}
#[test]
fn wrap_part_ii_fails_closed_on_a_single_word_wider_than_any_line() {
let token = "a".repeat(50);
let err = wrap_part_ii(&token, 20.0, 20.0, 27, 8.0)
.expect_err("an unbreakable token wider than every line can never fit");
assert!(err.rows_needed > err.capacity);
}
}