mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-11 02:41:31 -06:00
M7: CR and CRLF break rows on the styled rewrite path too (#4199)
A multi-line or multi-style draft is laid out by rewrite_styled_block, which broke rows only at \n: a CRLF left a stray CR before the break and a lone CR broke nothing. The CR of a CRLF is now skipped and a lone CR breaks the row, keeping the draft's character positions, and a draft whose only break is a lone CR takes the same path as one with \n.
This commit is contained in:
@@ -6717,6 +6717,32 @@ fn a_lone_carriage_return_breaks_an_edited_paragraph() {
|
||||
assert_eq!(lines, ["one", "two"]);
|
||||
}
|
||||
|
||||
/// On the styled rewrite path too, `\r\n` is one row break and a lone `\r` another, and no
|
||||
/// carriage return reaches the page: with no other formatting, and with a part set in red.
|
||||
#[test]
|
||||
fn carriage_returns_break_rows_on_the_styled_path() {
|
||||
let draft = "one\r\ntwo\rthree";
|
||||
let red = text::RangeStyle {
|
||||
range: 0..3,
|
||||
family: None,
|
||||
resource: None,
|
||||
size: None,
|
||||
color: Some([1.0, 0.0, 0.0]),
|
||||
underline: None,
|
||||
char_spacing: None,
|
||||
scale: None,
|
||||
};
|
||||
for style in [text::BlockStyle::default(), text::BlockStyle { ranges: vec![red], ..Default::default() }] {
|
||||
let mut doc = text_page("BT /F1 12 Tf 72 700 Td (Hello world) Tj ET");
|
||||
text::rewrite_block(&mut doc, 0, 0, Some(draft), &style).unwrap();
|
||||
let doc = reopen(&doc);
|
||||
let content = page_content_bytes(&doc, 0);
|
||||
assert!(!content.contains(&b'\r') && !content.windows(2).any(|w| w == b"\\r"), "{}", String::from_utf8_lossy(&content));
|
||||
let lines: Vec<String> = text::text_lines(&doc, 0).unwrap().into_iter().map(|l| l.text).collect();
|
||||
assert_eq!(lines, ["one", "two", "three"], "{style:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A subset with no space glyph shows each space as a positioning gap one space wide, so a run
|
||||
/// of spaces still takes a run's width (#4199).
|
||||
#[test]
|
||||
|
||||
@@ -2726,7 +2726,8 @@ fn rewrite_block_from(
|
||||
&& (!style.ranges.is_empty()
|
||||
|| style.resource.is_some()
|
||||
|| (text.is_some_and(|draft| {
|
||||
draft.contains('\n')
|
||||
// A row break, `\n` or a carriage return, takes the path that keeps untouched rows.
|
||||
draft.contains(['\n', '\r'])
|
||||
|| pdfcraft_fonts::source_shaping::required(draft)
|
||||
|| !pdfcraft_fonts::embedding::matching_faces(&b.base_font).is_empty()
|
||||
}) || registered_face
|
||||
|
||||
@@ -547,11 +547,20 @@ pub(super) fn rewrite_styled_block(
|
||||
let mut programs: HashMap<String, Option<std::sync::Arc<Vec<u8>>>> = HashMap::new();
|
||||
for (range, index) in styled_spans {
|
||||
let source = lines.get(index).unwrap_or(first);
|
||||
for ch in draft.get(range).unwrap_or_default().chars() {
|
||||
let start = range.start;
|
||||
for (offset, ch) in draft.get(range).unwrap_or_default().char_indices() {
|
||||
if ch == '\n' {
|
||||
chars.push((ch, index, source.font.clone()));
|
||||
continue;
|
||||
}
|
||||
// A carriage return breaks the row too. Before `\n` the pair is one break, so the
|
||||
// CR stays a `\r` the rows skip. Either way it keeps its place among the draft's
|
||||
// characters, which rows are counted in.
|
||||
if ch == '\r' {
|
||||
let crlf = start.checked_add(offset).and_then(|at| draft.get(at.saturating_add(1)..)).is_some_and(|rest| rest.starts_with('\n'));
|
||||
chars.push((if crlf { '\r' } else { '\n' }, index, source.font.clone()));
|
||||
continue;
|
||||
}
|
||||
let s = ch.to_string();
|
||||
let name = if let Some(resource) = overrides.get(&index).and_then(|r| r.resource.as_ref()).or(style.resource.as_ref()) {
|
||||
if !metrics.get(resource).is_some_and(|m| source_encodes(m, &s)) {
|
||||
@@ -735,6 +744,11 @@ pub(super) fn rewrite_styled_block(
|
||||
let mut at = 0;
|
||||
while at < chars.len() {
|
||||
let Some(item) = chars.get(at) else { break };
|
||||
// The CR of a CRLF: the `\n` after it breaks the row.
|
||||
if item.0 == '\r' {
|
||||
at += 1;
|
||||
continue;
|
||||
}
|
||||
if item.0 == '\n' {
|
||||
row_ranges.push(row_start..row_start + row.len());
|
||||
rows.push(std::mem::take(&mut row));
|
||||
|
||||
Reference in New Issue
Block a user