mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-31 13:57:18 -06:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8a88080d0 | ||
|
|
57957ef917 | ||
|
|
277f352d06 | ||
|
|
fbea1b2f8f |
+38
-37
@@ -362,6 +362,43 @@ mod tests {
|
|||||||
assert!(near(corner_radius(Some(10_000), -5.0, 100.0), 0.0));
|
assert!(near(corner_radius(Some(10_000), -5.0, 100.0), 0.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One rerouting case: the free end settles and the attached end keeps its site.
|
||||||
|
fn assert_transformed_reroute(kind: ShapeKind, rot: f32, flip_h: bool, flip_v: bool, free: Option<ConnEnd>, free_start: bool) {
|
||||||
|
let attached = Some(ConnEnd { id: 7, site: 3 });
|
||||||
|
let (start, end) = if free_start { (free, attached) } else { (attached, free) };
|
||||||
|
let mut obj = shape(kind, 40.0, 20.0, ShapeExtra { start, end, ..Default::default() });
|
||||||
|
let InlineObject::Shape { float, .. } = &mut obj else { panic!() };
|
||||||
|
float.rot = rot;
|
||||||
|
float.flip_h = flip_h;
|
||||||
|
float.flip_v = flip_v;
|
||||||
|
// Frame (10, 30, 40, 20), centre (30, 40): mirror, then rotate
|
||||||
|
// clockwise in y-down group space. Do not use the routing transform
|
||||||
|
// to compute the expectation.
|
||||||
|
let sign = if free_start { -1.0 } else { 1.0 };
|
||||||
|
let dx = sign * if flip_h { -20.0 } else { 20.0 };
|
||||||
|
let dy = sign * if flip_v { -10.0 } else { 10.0 };
|
||||||
|
let (sin, cos) = rot.to_radians().sin_cos();
|
||||||
|
let expected = (30.0 + dx * cos - dy * sin, 40.0 + dx * sin + dy * cos);
|
||||||
|
let kids = vec![
|
||||||
|
GroupChild { x: 100.0, y: 200.0, obj: shape(ShapeKind::Rectangle, 40.0, 20.0, ShapeExtra { id: 7, ..Default::default() }) },
|
||||||
|
GroupChild { x: 10.0, y: 30.0, obj },
|
||||||
|
];
|
||||||
|
let mut group = group(kids);
|
||||||
|
assert!(reroute_group(&mut group));
|
||||||
|
let InlineObject::Group { children, .. } = &group else { panic!() };
|
||||||
|
let c = &children[1];
|
||||||
|
let InlineObject::Shape { w, h, float, .. } = &c.obj else { panic!() };
|
||||||
|
let (start, end) = ends(c.x, c.y, *w, *h, float.flip_h, float.flip_v);
|
||||||
|
let (free, attached) = if free_start { (start, end) } else { (end, start) };
|
||||||
|
assert!(
|
||||||
|
(free.0 - expected.0).abs() < 0.001 && (free.1 - expected.1).abs() < 0.001,
|
||||||
|
"{kind:?}, {rot}, {flip_h}, {flip_v}, {free_start}: {free:?} != {expected:?}"
|
||||||
|
);
|
||||||
|
assert_eq!(attached, (140.0, 210.0));
|
||||||
|
assert_eq!(float.rot, 0.0);
|
||||||
|
assert!(!reroute_group(&mut group), "rerouting must settle");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rerouting_preserves_transformed_free_and_unresolved_ends() {
|
fn rerouting_preserves_transformed_free_and_unresolved_ends() {
|
||||||
for kind in [ShapeKind::StraightConnector, ShapeKind::ElbowConnector, ShapeKind::CurvedConnector] {
|
for kind in [ShapeKind::StraightConnector, ShapeKind::ElbowConnector, ShapeKind::CurvedConnector] {
|
||||||
@@ -369,43 +406,7 @@ mod tests {
|
|||||||
for (flip_h, flip_v) in [(false, false), (true, false), (false, true), (true, true)] {
|
for (flip_h, flip_v) in [(false, false), (true, false), (false, true), (true, true)] {
|
||||||
for free in [None, Some(ConnEnd { id: 404, site: 0 }), Some(ConnEnd { id: 7, site: 99 })] {
|
for free in [None, Some(ConnEnd { id: 404, site: 0 }), Some(ConnEnd { id: 7, site: 99 })] {
|
||||||
for free_start in [false, true] {
|
for free_start in [false, true] {
|
||||||
let attached = Some(ConnEnd { id: 7, site: 3 });
|
assert_transformed_reroute(kind, rot, flip_h, flip_v, free, free_start);
|
||||||
let (start, end) = if free_start { (free, attached) } else { (attached, free) };
|
|
||||||
let mut obj = shape(kind, 40.0, 20.0, ShapeExtra { start, end, ..Default::default() });
|
|
||||||
let InlineObject::Shape { float, .. } = &mut obj else { panic!() };
|
|
||||||
float.rot = rot;
|
|
||||||
float.flip_h = flip_h;
|
|
||||||
float.flip_v = flip_v;
|
|
||||||
// Frame (10, 30, 40, 20), centre (30, 40): mirror, then rotate
|
|
||||||
// clockwise in y-down group space. Do not use the routing transform
|
|
||||||
// to compute the expectation.
|
|
||||||
let sign = if free_start { -1.0 } else { 1.0 };
|
|
||||||
let dx = sign * if flip_h { -20.0 } else { 20.0 };
|
|
||||||
let dy = sign * if flip_v { -10.0 } else { 10.0 };
|
|
||||||
let (sin, cos) = rot.to_radians().sin_cos();
|
|
||||||
let expected = (30.0 + dx * cos - dy * sin, 40.0 + dx * sin + dy * cos);
|
|
||||||
let kids = vec![
|
|
||||||
GroupChild {
|
|
||||||
x: 100.0,
|
|
||||||
y: 200.0,
|
|
||||||
obj: shape(ShapeKind::Rectangle, 40.0, 20.0, ShapeExtra { id: 7, ..Default::default() }),
|
|
||||||
},
|
|
||||||
GroupChild { x: 10.0, y: 30.0, obj },
|
|
||||||
];
|
|
||||||
let mut group = group(kids);
|
|
||||||
assert!(reroute_group(&mut group));
|
|
||||||
let InlineObject::Group { children, .. } = &group else { panic!() };
|
|
||||||
let c = &children[1];
|
|
||||||
let InlineObject::Shape { w, h, float, .. } = &c.obj else { panic!() };
|
|
||||||
let (start, end) = ends(c.x, c.y, *w, *h, float.flip_h, float.flip_v);
|
|
||||||
let (free, attached) = if free_start { (start, end) } else { (end, start) };
|
|
||||||
assert!(
|
|
||||||
(free.0 - expected.0).abs() < 0.001 && (free.1 - expected.1).abs() < 0.001,
|
|
||||||
"{kind:?}, {rot}, {flip_h}, {flip_v}, {free_start}: {free:?} != {expected:?}"
|
|
||||||
);
|
|
||||||
assert_eq!(attached, (140.0, 210.0));
|
|
||||||
assert_eq!(float.rot, 0.0);
|
|
||||||
assert!(!reroute_group(&mut group), "rerouting must settle");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -356,6 +356,31 @@ pub enum Piece {
|
|||||||
|
|
||||||
/// Read Replace with text: `^` codes (unless `literal`), `\1`–`\9` with wildcards, and with a
|
/// Read Replace with text: `^` codes (unless `literal`), `\1`–`\9` with wildcards, and with a
|
||||||
/// regex also `$n`, `${n}`, `${name}`, `$name`, `$$`, `\n` (paragraph mark), `\t` and `\\`.
|
/// regex also `$n`, `${n}`, `${name}`, `$name`, `$$`, `\n` (paragraph mark), `\t` and `\\`.
|
||||||
|
/// Pushes the pending literal text, if any.
|
||||||
|
fn flush_literal(cur: &mut String, out: &mut Vec<Piece>) {
|
||||||
|
if !cur.is_empty() {
|
||||||
|
out.push(Piece::Lit(std::mem::take(cur)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `^` code whose letter is `chars[i]` (`^&`, `^c`, a replace code or a numeric one): pushes
|
||||||
|
/// its piece and returns the characters it used, or `None` when it isn't a code.
|
||||||
|
fn caret_code(chars: &[char], i: usize, cur: &mut String, out: &mut Vec<Piece>) -> Option<usize> {
|
||||||
|
let &n = chars.get(i)?;
|
||||||
|
if n == '&' || n == 'c' {
|
||||||
|
flush_literal(cur, out);
|
||||||
|
out.push(if n == '&' { Piece::Found } else { Piece::Clip });
|
||||||
|
return Some(1);
|
||||||
|
}
|
||||||
|
if let Some(ch) = replace_code(n) {
|
||||||
|
cur.push(ch);
|
||||||
|
return Some(1);
|
||||||
|
}
|
||||||
|
let (ch, used) = numeric_code(chars, i)?;
|
||||||
|
cur.push(ch);
|
||||||
|
Some(used)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_replace(with: &str, o: &Opts) -> Vec<Piece> {
|
pub fn parse_replace(with: &str, o: &Opts) -> Vec<Piece> {
|
||||||
// With a regex, `special: false` turns off only the `^` codes; group references still expand.
|
// With a regex, `special: false` turns off only the `^` codes; group references still expand.
|
||||||
if o.literal && !o.regex {
|
if o.literal && !o.regex {
|
||||||
@@ -364,11 +389,6 @@ pub fn parse_replace(with: &str, o: &Opts) -> Vec<Piece> {
|
|||||||
let chars: Vec<char> = with.chars().collect();
|
let chars: Vec<char> = with.chars().collect();
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
let mut cur = String::new();
|
let mut cur = String::new();
|
||||||
let flush = |cur: &mut String, out: &mut Vec<Piece>| {
|
|
||||||
if !cur.is_empty() {
|
|
||||||
out.push(Piece::Lit(std::mem::take(cur)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while let Some(&c) = chars.get(i) {
|
while let Some(&c) = chars.get(i) {
|
||||||
i += 1;
|
i += 1;
|
||||||
@@ -379,7 +399,7 @@ pub fn parse_replace(with: &str, o: &Opts) -> Vec<Piece> {
|
|||||||
i += used;
|
i += used;
|
||||||
}
|
}
|
||||||
Some((Ref::Group(pc), used)) => {
|
Some((Ref::Group(pc), used)) => {
|
||||||
flush(&mut cur, &mut out);
|
flush_literal(&mut cur, &mut out);
|
||||||
out.extend(pc);
|
out.extend(pc);
|
||||||
i += used;
|
i += used;
|
||||||
}
|
}
|
||||||
@@ -391,7 +411,7 @@ pub fn parse_replace(with: &str, o: &Opts) -> Vec<Piece> {
|
|||||||
if c == '\\' && o.wildcards {
|
if c == '\\' && o.wildcards {
|
||||||
match chars.get(i) {
|
match chars.get(i) {
|
||||||
Some(d) if d.is_ascii_digit() && *d != '0' => {
|
Some(d) if d.is_ascii_digit() && *d != '0' => {
|
||||||
flush(&mut cur, &mut out);
|
flush_literal(&mut cur, &mut out);
|
||||||
out.push(Piece::Group(d.to_digit(10).unwrap_or(0) as usize));
|
out.push(Piece::Group(d.to_digit(10).unwrap_or(0) as usize));
|
||||||
i += 1;
|
i += 1;
|
||||||
continue;
|
continue;
|
||||||
@@ -406,28 +426,14 @@ pub fn parse_replace(with: &str, o: &Opts) -> Vec<Piece> {
|
|||||||
}
|
}
|
||||||
if c == '^'
|
if c == '^'
|
||||||
&& !o.literal
|
&& !o.literal
|
||||||
&& let Some(&n) = chars.get(i)
|
&& let Some(used) = caret_code(&chars, i, &mut cur, &mut out)
|
||||||
{
|
{
|
||||||
if n == '&' || n == 'c' {
|
i += used;
|
||||||
flush(&mut cur, &mut out);
|
continue;
|
||||||
out.push(if n == '&' { Piece::Found } else { Piece::Clip });
|
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(ch) = replace_code(n) {
|
|
||||||
cur.push(ch);
|
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some((ch, used)) = numeric_code(&chars, i) {
|
|
||||||
cur.push(ch);
|
|
||||||
i += used;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
cur.push(c);
|
cur.push(c);
|
||||||
}
|
}
|
||||||
flush(&mut cur, &mut out);
|
flush_literal(&mut cur, &mut out);
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user