mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 20:01:37 +00:00
.ai import: legacy overflow breaks keep the direction and paragraph attributes they were read with
The reader holds an overflow paragraph break (`(\r) TX`) until more type follows it. When that type came, the break took the state in force then, not the state where the break was read: - A story that started with the break took the `TV` direction of the characters after it, so `1 TV (\r) TX 0 TV (Body) Tx` opened horizontal and gave no warning. - A break read in the other direction inside a story did not count as a direction change. - The empty paragraph that the break ended took the attributes of the paragraph after it, while `(\r) Tx` keeps its own. The pending break now records `TV` and, when it ends an empty paragraph, that paragraph's attributes.
This commit is contained in:
@@ -78,6 +78,17 @@ struct ParaRun {
|
||||
tabs_version: u64,
|
||||
}
|
||||
|
||||
/// An overflow paragraph break (`(\r) TX`) read before any more type, with the state in force
|
||||
/// where it was read.
|
||||
#[derive(Debug)]
|
||||
struct PendingBreak {
|
||||
/// `TV` at the break.
|
||||
vertical: bool,
|
||||
/// The attributes and [`TypeState::tabs_version`] of the empty paragraph the break ends, when
|
||||
/// it ends one that no character started.
|
||||
para: Option<(ParaStyle, u64)>,
|
||||
}
|
||||
|
||||
/// A text object being read.
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct LegacyText {
|
||||
@@ -101,7 +112,7 @@ pub(super) struct LegacyText {
|
||||
/// The current paragraph's attributes are in `paras`.
|
||||
para_taken: bool,
|
||||
/// An overflow paragraph break (`(\r) TX`) not yet followed by type: the story's end.
|
||||
pending_break: bool,
|
||||
pending_break: Option<PendingBreak>,
|
||||
/// Characters were set in a mask rendering mode (`Tr` 4 to 7): the text object is a clipping
|
||||
/// path (§5.7).
|
||||
clips: bool,
|
||||
@@ -632,7 +643,10 @@ impl Reader<'_> {
|
||||
}
|
||||
// Overflow text ending the story with a paragraph break (`(\r) TX`) adds no paragraph.
|
||||
if w == "TX" && decoded == "\n" {
|
||||
t.pending_break = true;
|
||||
if t.pending_break.is_none() {
|
||||
let para = (!t.para_taken).then(|| (st.para.clone(), st.tabs_version));
|
||||
t.pending_break = Some(PendingBreak { vertical: st.vertical, para });
|
||||
}
|
||||
return;
|
||||
}
|
||||
// As many characters as the file's allowance has left.
|
||||
@@ -642,10 +656,13 @@ impl Reader<'_> {
|
||||
}
|
||||
let decoded: String = decoded.chars().take(room).collect();
|
||||
st.chars += decoded.chars().count();
|
||||
// A break read before these characters keeps the direction and paragraph attributes it was
|
||||
// read with, and is the story's first character when nothing came before it.
|
||||
let brk = t.pending_break.take();
|
||||
if t.runs.is_empty() {
|
||||
t.vertical = st.vertical;
|
||||
t.vertical = brk.as_ref().map_or(st.vertical, |b| b.vertical);
|
||||
}
|
||||
t.direction_changed |= st.vertical != t.vertical;
|
||||
t.direction_changed |= st.vertical != t.vertical || brk.as_ref().is_some_and(|b| b.vertical != t.vertical);
|
||||
let gs = &self.gs;
|
||||
let mut style = st.style.clone();
|
||||
style.tate_chu_yoko |= st.kumi_recorded && !t.vertical;
|
||||
@@ -664,7 +681,10 @@ impl Reader<'_> {
|
||||
(style.stroke_cap, style.stroke_join, style.stroke_miter_limit, style.stroke_dash) = (gs.cap, gs.join, gs.miter, gs.dash.clone());
|
||||
(style.overprint_fill, style.overprint_stroke) = (gs.overprint_fill, gs.overprint_stroke);
|
||||
let mut text = String::new();
|
||||
if std::mem::take(&mut t.pending_break) {
|
||||
if let Some(brk) = brk {
|
||||
if let Some((mut para, tabs_version)) = brk.para {
|
||||
t.begin_para(&mut para, tabs_version);
|
||||
}
|
||||
text.push('\n');
|
||||
t.para_taken = false;
|
||||
}
|
||||
|
||||
@@ -3554,6 +3554,31 @@ fn legacy_type_keeps_its_writing_direction() {
|
||||
let after = point_type("", "Hi").replace("TO\n", "1 TV\nTO\n");
|
||||
let after = legacy_texts(&format!("{after}{}", point_type("", "Next")));
|
||||
assert_eq!((after[0].vertical, after[1].vertical), (false, true));
|
||||
// An overflow break (`(\r) TX`) keeps the direction in force where it was read: at the start of
|
||||
// the story it sets the object's direction, and later it warns when that differs.
|
||||
let leading = legacy_read(&point_type("1 TV\n(\\r) TX\n0 TV", "Body"));
|
||||
let leading = (art_texts(&leading.doc), leading.warnings);
|
||||
assert_eq!((leading.0[0].vertical, leading.0[0].plain_text().as_str()), (true, "\nBody"));
|
||||
assert_eq!(leading.1, [DIRECTION_CHANGE_LEFT_OUT]);
|
||||
let inside = legacy_read(&point_type("", "a").replace("TO\n", "1 TV\n(\\r) TX\n0 TV\n(b) Tx\nTO\n"));
|
||||
let inside = (art_texts(&inside.doc), inside.warnings);
|
||||
assert_eq!((inside.0[0].vertical, inside.0[0].plain_text().as_str()), (false, "a\nb"));
|
||||
assert_eq!(inside.1, [DIRECTION_CHANGE_LEFT_OUT]);
|
||||
}
|
||||
|
||||
/// An overflow break (`(\r) TX`) that type follows ends a paragraph as `(\r) Tx` does: an empty
|
||||
/// paragraph it ends keeps the attributes in force where the break was read.
|
||||
#[test]
|
||||
fn an_overflow_break_keeps_the_paragraph_attributes_it_was_read_with() {
|
||||
use vectorcraft_doc::Justify::{Center, Right};
|
||||
for op in ["Tx", "TX"] {
|
||||
let leading = &legacy_texts(&point_type(&format!("1 Ta\n(\\r) {op}\n2 Ta"), "Body"))[0];
|
||||
assert_eq!((leading.para_at(0).justify, leading.para_at(1).justify), (Center, Right), "{op}");
|
||||
let empty = point_type("1 Ta", "a\\r").replace("TO\n", &format!("(\\r) {op}\n2 Ta\n(b) Tx\nTO\n"));
|
||||
let empty = &legacy_texts(&empty)[0];
|
||||
assert_eq!(empty.plain_text(), "a\n\nb", "{op}");
|
||||
assert_eq!((empty.para_at(1).justify, empty.para_at(2).justify), (Center, Right), "{op}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The glyph origins of a legacy-format text object.
|
||||
|
||||
Reference in New Issue
Block a user