Text: stories made in a right-to-left-bound document fill their columns from the right (#100)

This commit is contained in:
aiongg
2026-10-08 01:57:30 -04:00
parent 3c83f4f477
commit 0f52f3ba89
5 changed files with 39 additions and 0 deletions
+7
View File
@@ -171,11 +171,18 @@ impl Document {
Ok(removed)
}
/// The column direction a new story starts with: right to left in a document whose binding is
/// right to left (Document Setup), else left to right. Stories already made keep theirs.
pub fn new_story_direction(&self) -> crate::TextDirection {
if self.settings.right_to_left_binding { crate::TextDirection::RightToLeft } else { crate::TextDirection::LeftToRight }
}
/// Create a text frame with a new (or given) story at `rect` on spread `r`.
pub fn add_text_frame(&mut self, r: SpreadRef, rect: Rect, layer: LayerId, text: &str, para: ParaFormat) -> Result<(ItemId, StoryId)> {
let id = ItemId(self.alloc());
let sid = StoryId(self.alloc());
let mut story = Story::with_text(sid, text, para);
story.direction = self.new_story_direction();
story.frames.push(id);
self.stories.insert(sid, Arc::new(story));
let mut item = Item::new(id, layer, Shape::Rectangle, shapes::rectangle(rect));
+29
View File
@@ -759,6 +759,35 @@ mod setup_tests {
let d = &s.doc().unwrap().doc;
assert_eq!(d.spreads[1].pages.iter().map(|p| (p.side, p.x)).collect::<Vec<_>>(), vec![(Left, 0.0), (Right, w)]);
}
/// Multi-column frames filled the left column first in a right-to-left document (#100):
/// stories made in a right-to-left-bound document start their columns on the right.
#[test]
fn new_stories_follow_the_binding_direction() {
use designcraft_doc::TextDirection::{LeftToRight, RightToLeft};
let text = (1..=12).map(|i| format!("الفقرة {i}")).collect::<Vec<_>>().join("\n");
for (binding, expected) in [("rightToLeft", RightToLeft), ("leftToRight", LeftToRight)] {
let mut s = crate::Session::new();
s.execute("file.new", &json!({"preset": "A4", "pages": 1})).unwrap();
s.execute("layout.documentSetup", &json!({"binding": binding})).unwrap();
let f = s.execute("frame.create", &json!({"rect": [40, 170, 555, 260], "content": "text", "text": text})).unwrap();
s.execute("object.textFrameOptions", &json!({"ids": [f["id"]], "columns": 2, "gutter": 20})).unwrap();
let sid = designcraft_doc::StoryId(f["story"].as_u64().unwrap());
let st = s.doc().unwrap();
assert_eq!(st.doc.story(sid).unwrap().direction, expected, "{binding}");
let cs = s.cache.get(&st.doc, sid, None);
let fr = &cs.frames[0];
let (first, last) = (fr.lines.first().unwrap(), fr.lines.last().unwrap());
assert!(fr.columns.len() == 2 && first.x0 != last.x0, "the text fills both columns: {:?}", fr.columns);
assert_eq!(first.x0 > last.x0, expected == RightToLeft, "{binding}: the first line is in the first column");
// An empty frame given text content starts a story too.
let g = s.execute("frame.create", &json!({"rect": [40, 400, 555, 500]})).unwrap();
s.execute("object.content", &json!({"ids": [g["id"]], "type": "text"})).unwrap();
let d = &s.doc().unwrap().doc;
let tf = d.item(designcraft_doc::ItemId(g["id"].as_u64().unwrap())).unwrap().text_frame().unwrap().story;
assert_eq!(d.story(tf).unwrap().direction, expected, "{binding}: object.content");
}
}
}
#[cfg(test)]
+1
View File
@@ -1379,6 +1379,7 @@ fn content_type(s: &mut Session, p: &Value) -> Result<Value> {
("text", Some(Content::Unassigned | Content::Graphic(_))) => {
let sid = StoryId(d.alloc());
let mut st = Story::new(sid);
st.direction = d.new_story_direction();
st.frames.push(*id);
d.stories.insert(sid, Arc::new(st));
if let Some(it) = d.item_mut(*id) {
+1
View File
@@ -54,6 +54,7 @@ fn on_path(s: &mut Session, p: &Value) -> Result<Value> {
}
let sid = StoryId(d.alloc());
let mut st = Story::with_text(sid, &text, Default::default());
st.direction = d.new_story_direction();
st.frames = vec![id];
d.stories.insert(sid, std::sync::Arc::new(st));
let it = d.item_mut(id).ok_or(designcraft_doc::DocError::NoItem(id))?;
+1
View File
@@ -200,6 +200,7 @@ pub(super) fn place_text(s: &mut Session, p: &Value, name: &str, bytes: &[u8]) -
Some(_) => {
let sid = StoryId(d.alloc());
let mut ns = Story::new(sid);
ns.direction = d.new_story_direction();
ns.frames = vec![fid];
d.stories.insert(sid, std::sync::Arc::new(ns));
if let Some(it) = d.item_mut(fid) {