mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-07 12:13:19 +00:00
Merge pull request #5897 from dotCooCoo/fix/restyled-empty-frame-keeps-style
Text: a restyled empty frame keeps its style before an unedited empty frame
This commit is contained in:
@@ -39,6 +39,27 @@ fn empty_later_frames_keep_the_first_frames_edited_paragraph_style() {
|
||||
assert_eq!([hyphenates(a), hyphenates(b), hyphenates(c)], [true, true, true]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_restyled_empty_frame_keeps_its_style_before_an_unedited_empty_frame() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 600, "height": 300})).unwrap();
|
||||
let a = s.execute("text.create", &json!({"x": 10, "y": 10, "text": "First\n", "area": {"width": 150, "height": 60}})).unwrap()["id"]
|
||||
.as_u64()
|
||||
.unwrap();
|
||||
let b = s.execute("shape.rectangle", &json!({"x": 200, "y": 10, "width": 150, "height": 60})).unwrap()["id"].as_u64().unwrap();
|
||||
let c = s.execute("shape.rectangle", &json!({"x": 400, "y": 10, "width": 150, "height": 60})).unwrap()["id"].as_u64().unwrap();
|
||||
s.execute("select.set", &json!({"ids": [a, b, c]})).unwrap();
|
||||
s.execute("text.thread.create", &json!({})).unwrap();
|
||||
assert_eq!([text(&s, a), text(&s, b), text(&s, c)], ["First\n", "", ""]);
|
||||
s.execute("text.setFormat", &json!({"ids": [b], "hyphenate": true})).unwrap();
|
||||
let hyphenates = |id, para| match &s.doc().unwrap().doc.node(NodeId(id)).unwrap().kind {
|
||||
NodeKind::Text(t) => t.para_at(para).hyphenate,
|
||||
_ => panic!("not text"),
|
||||
};
|
||||
// The empty paragraph after the break takes the style; the paragraph "First" keeps its own.
|
||||
assert_eq!([hyphenates(a, 0), hyphenates(a, 1), hyphenates(b, 0), hyphenates(c, 0)], [false, true, true, true]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_flows_reflows_on_edit_and_survives_delete() {
|
||||
let (mut s, a, b) = setup();
|
||||
|
||||
@@ -20,24 +20,30 @@ pub fn story(frames: &[&TextObject]) -> Vec<TextRun> {
|
||||
/// paragraph shares that paragraph with the next frame (its first one): the earlier frame's style
|
||||
/// wins unless `later_wins[k]` says frame `k`'s first paragraph should (it was just restyled), or
|
||||
/// the earlier frame's part of the paragraph is empty (its slice ends with a paragraph break).
|
||||
/// An empty frame that was just restyled keeps its style against unedited empty frames after it.
|
||||
pub fn story_paras(frames: &[&TextObject], later_wins: &[bool]) -> Vec<ParaStyle> {
|
||||
let mut out: Vec<ParaStyle> = vec![];
|
||||
let mut prev_empty_tail = true;
|
||||
let mut empty_tail_restyled = false;
|
||||
for (k, f) in frames.iter().enumerate() {
|
||||
let text = f.plain_text();
|
||||
let restyled = later_wins.get(k).copied().unwrap_or(false);
|
||||
let mut styles = f.paragraph_styles().into_iter();
|
||||
if k > 0
|
||||
&& let Some(first) = styles.next()
|
||||
&& (prev_empty_tail || later_wins.get(k).copied().unwrap_or(false))
|
||||
&& (restyled || (prev_empty_tail && !(text.is_empty() && empty_tail_restyled)))
|
||||
&& let Some(last) = out.last_mut()
|
||||
{
|
||||
*last = first;
|
||||
}
|
||||
out.extend(styles);
|
||||
let text = f.plain_text();
|
||||
// An empty frame has no paragraph boundary of its own. Keep the preceding frame's
|
||||
// boundary state so another empty frame cannot replace a paragraph's edited style.
|
||||
if !text.is_empty() {
|
||||
prev_empty_tail = text.ends_with('\n') || out.is_empty();
|
||||
empty_tail_restyled = false;
|
||||
} else if restyled {
|
||||
empty_tail_restyled = true;
|
||||
}
|
||||
}
|
||||
out
|
||||
|
||||
@@ -163,6 +163,39 @@ fn empty_frames_do_not_replace_an_earlier_paragraph_style() {
|
||||
assert_eq!(story_paras(&[&first, &restyled_middle, &last], &[false, true, false])[0].justify, Justify::Center);
|
||||
}
|
||||
|
||||
/// When the earlier frame ends with a paragraph break, a restyled empty frame keeps its style
|
||||
/// against an unedited empty frame after it. An unedited empty frame before it, a frame with
|
||||
/// text after it, or a restyled frame after it still gives the paragraph its own style.
|
||||
#[test]
|
||||
fn a_restyled_empty_frame_keeps_its_style_after_a_paragraph_break() {
|
||||
let style = |j| ParaStyle { justify: j, ..Default::default() };
|
||||
let mut first = frame(0.0);
|
||||
first.runs = vec![TextRun { text: "First\n".into(), style: CharStyle::default(), inline: None }];
|
||||
first.set_all_paras(style(Justify::Right));
|
||||
let mut restyled = frame(200.0);
|
||||
restyled.set_all_paras(style(Justify::Center));
|
||||
let empty = frame(400.0);
|
||||
let tail_style = |frames: &[&TextObject], wins: &[bool]| story_paras(frames, wins).last().map(|p| p.justify);
|
||||
|
||||
assert_eq!(tail_style(&[&first, &restyled, &empty], &[false, true, false]), Some(Justify::Center));
|
||||
assert_eq!(tail_style(&[&first, &restyled, &empty, &empty], &[false, true, false, false]), Some(Justify::Center));
|
||||
// An unedited empty frame before the restyled one gives way to it.
|
||||
assert_eq!(tail_style(&[&first, &empty, &restyled, &empty], &[false, false, true, false]), Some(Justify::Center));
|
||||
// A restyled first frame of an empty thread keeps its style as well.
|
||||
assert_eq!(tail_style(&[&restyled, &empty, &empty], &[true, false, false]), Some(Justify::Center));
|
||||
|
||||
// Without an edit, the last empty frame still gives the empty paragraph its style.
|
||||
assert_eq!(tail_style(&[&first, &restyled, &empty], &[false, false, false]), Some(Justify::Left));
|
||||
// A later frame with text still gives the paragraph it holds its style.
|
||||
let mut with_text = empty.clone();
|
||||
with_text.runs = vec![TextRun { text: "Tail".into(), style: CharStyle::default(), inline: None }];
|
||||
assert_eq!(tail_style(&[&first, &restyled, &with_text], &[false, true, false]), Some(Justify::Left));
|
||||
// A later restyled frame wins over the earlier one.
|
||||
let mut right = empty.clone();
|
||||
right.set_all_paras(style(Justify::Right));
|
||||
assert_eq!(tail_style(&[&first, &restyled, &right], &[false, true, true]), Some(Justify::Right));
|
||||
}
|
||||
|
||||
/// Numbered list paragraphs count on from one frame to the next: the second frame's first
|
||||
/// whole paragraph takes the next number. A paragraph split between the frames has its number
|
||||
/// in the first frame only, and the paragraph after it takes the next number.
|
||||
|
||||
Reference in New Issue
Block a user