mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-26 20:06:50 -06:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ae80124cb |
@@ -358,6 +358,56 @@ fn components<'a>(cons: &[&'a Constraint]) -> Vec<Vec<&'a Constraint>> {
|
|||||||
groups.into_values().collect()
|
groups.into_values().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The solver weight of each unknown of `model`: kept handles weigh more, preferred-to-move ones
|
||||||
|
/// less, angles and ratios by size², and contact parameters move freely.
|
||||||
|
fn solver_weights(model: &Model, opts: &SolveOptions) -> Vec<f64> {
|
||||||
|
let mut weights = vec![1.0; model.x0.len()];
|
||||||
|
for s in &model.slots {
|
||||||
|
let w = if opts.keep.contains(&s.handle) {
|
||||||
|
1e4
|
||||||
|
} else if opts.prefer_move.contains(&s.handle) {
|
||||||
|
1e-2
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
for j in s.off..s.off + s.len {
|
||||||
|
if let Some(slot) = weights.get_mut(j) {
|
||||||
|
*slot = w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (j, f) in slot_scales(model, s) {
|
||||||
|
if let Some(slot) = weights.get_mut(j) {
|
||||||
|
*slot *= f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Contact parameters move freely.
|
||||||
|
let geo = model.slots.iter().map(|s| s.off + s.len).max().unwrap_or(0);
|
||||||
|
for w in weights.iter_mut().skip(geo) {
|
||||||
|
*w = 1e-3;
|
||||||
|
}
|
||||||
|
weights
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Per-unknown factors for the angles and ratios of one slot: size², so that the movement of
|
||||||
|
/// points is what is minimised.
|
||||||
|
fn slot_scales(model: &Model, s: &model::Slot) -> Vec<(usize, f64)> {
|
||||||
|
let sq = |j: usize| model.x0.get(j).map_or(1.0, |r| (r * r).max(1e-6));
|
||||||
|
match s.shape {
|
||||||
|
model::Shape::Arc => vec![(s.off + 3, sq(s.off + 2)), (s.off + 4, sq(s.off + 2))],
|
||||||
|
model::Shape::Ellipse { arc } => {
|
||||||
|
let a2 = (sq(s.off + 2) + sq(s.off + 3)).max(1e-6);
|
||||||
|
let mut v = vec![(s.off + 4, a2)];
|
||||||
|
if arc {
|
||||||
|
v.extend([(s.off + 5, a2), (s.off + 6, a2)]);
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
||||||
|
model::Shape::Insert { .. } => vec![(s.off + 2, model.rigid_extent2(s.handle).max(1.0))],
|
||||||
|
_ => Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Build the component systems. Constraints that cannot be interpreted are returned as skipped.
|
/// Build the component systems. Constraints that cannot be interpreted are returned as skipped.
|
||||||
pub(crate) fn build(d: &Drawing, cons: &[&Constraint], opts: &SolveOptions) -> Result<(Vec<Comp>, Vec<(u32, String)>), SolveError> {
|
pub(crate) fn build(d: &Drawing, cons: &[&Constraint], opts: &SolveOptions) -> Result<(Vec<Comp>, Vec<(u32, String)>), SolveError> {
|
||||||
let mut members_left = model::MAX_RIGID_WORK;
|
let mut members_left = model::MAX_RIGID_WORK;
|
||||||
@@ -396,46 +446,7 @@ pub(crate) fn build_budgeted(
|
|||||||
if built.is_empty() {
|
if built.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut weights = vec![1.0; model.x0.len()];
|
let weights = solver_weights(&model, opts);
|
||||||
for s in &model.slots {
|
|
||||||
let w = if opts.keep.contains(&s.handle) {
|
|
||||||
1e4
|
|
||||||
} else if opts.prefer_move.contains(&s.handle) {
|
|
||||||
1e-2
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
};
|
|
||||||
for j in s.off..s.off + s.len {
|
|
||||||
if let Some(slot) = weights.get_mut(j) {
|
|
||||||
*slot = w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Angles and ratios: weight by size² so the movement of points is what is minimised.
|
|
||||||
let sq = |j: usize| model.x0.get(j).map_or(1.0, |r| (r * r).max(1e-6));
|
|
||||||
let scaled: Vec<(usize, f64)> = match s.shape {
|
|
||||||
model::Shape::Arc => vec![(s.off + 3, sq(s.off + 2)), (s.off + 4, sq(s.off + 2))],
|
|
||||||
model::Shape::Ellipse { arc } => {
|
|
||||||
let a2 = (sq(s.off + 2) + sq(s.off + 3)).max(1e-6);
|
|
||||||
let mut v = vec![(s.off + 4, a2)];
|
|
||||||
if arc {
|
|
||||||
v.extend([(s.off + 5, a2), (s.off + 6, a2)]);
|
|
||||||
}
|
|
||||||
v
|
|
||||||
}
|
|
||||||
model::Shape::Insert { .. } => vec![(s.off + 2, model.rigid_extent2(s.handle).max(1.0))],
|
|
||||||
_ => Vec::new(),
|
|
||||||
};
|
|
||||||
for (j, f) in scaled {
|
|
||||||
if let Some(slot) = weights.get_mut(j) {
|
|
||||||
*slot *= f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Contact parameters move freely.
|
|
||||||
let geo = model.slots.iter().map(|s| s.off + s.len).max().unwrap_or(0);
|
|
||||||
for w in weights.iter_mut().skip(geo) {
|
|
||||||
*w = 1e-3;
|
|
||||||
}
|
|
||||||
let held = opts.hold.iter().filter_map(|r| model.lock_indices(r)).flatten().collect();
|
let held = opts.hold.iter().filter_map(|r| model.lock_indices(r)).flatten().collect();
|
||||||
let tol = scale_tol(&model.x0);
|
let tol = scale_tol(&model.x0);
|
||||||
comps.push(Comp { model, built, weights, held, tol });
|
comps.push(Comp { model, built, weights, held, tol });
|
||||||
|
|||||||
@@ -117,21 +117,7 @@ pub fn run(s: &mut Session, p: &Value) -> Result<Value> {
|
|||||||
|
|
||||||
// Endpoints closer than the tolerance meet. Transitive components of three or more ends are
|
// Endpoints closer than the tolerance meet. Transitive components of three or more ends are
|
||||||
// branch junctions; a component of exactly two is a simple joint.
|
// branch junctions; a component of exactly two is a simple joint.
|
||||||
let mut parent: Vec<usize> = (0..n).collect();
|
let mut parent = merge_ends(&ends, tol);
|
||||||
for i in 0..n {
|
|
||||||
let Some(a) = ends.get(i) else { continue };
|
|
||||||
for j in (i + 1)..n {
|
|
||||||
let Some(b) = ends.get(j) else { continue };
|
|
||||||
if a.at.dist(b.at) <= tol {
|
|
||||||
let (ra, rb) = (find(&mut parent, i), find(&mut parent, j));
|
|
||||||
if ra != rb
|
|
||||||
&& let Some(slot) = parent.get_mut(ra)
|
|
||||||
{
|
|
||||||
*slot = rb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut groups: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
|
let mut groups: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
let root = find(&mut parent, i);
|
let root = find(&mut parent, i);
|
||||||
@@ -140,54 +126,8 @@ pub fn run(s: &mut Session, p: &Value) -> Result<Value> {
|
|||||||
|
|
||||||
// Junctions are reported and their pieces left out of the chains: not resolved.
|
// Junctions are reported and their pieces left out of the chains: not resolved.
|
||||||
let mut excluded = vec![false; pieces.len()];
|
let mut excluded = vec![false; pieces.len()];
|
||||||
let mut junctions: Vec<Value> = Vec::new();
|
let (joints, junctions) = sort_groups(&groups, &ends, &pieces, &mut excluded);
|
||||||
let mut joints: Vec<(usize, usize)> = Vec::new();
|
let link = joint_links(joints, &ends, &pieces, &excluded);
|
||||||
for group in groups.values() {
|
|
||||||
match group.as_slice() {
|
|
||||||
&[a, b] => joints.push((a, b)),
|
|
||||||
[_, _, ..] => {
|
|
||||||
let scale = group.len() as f64;
|
|
||||||
let mut at = Vec2::ZERO;
|
|
||||||
let mut members: Vec<Handle> = Vec::new();
|
|
||||||
for &e in group {
|
|
||||||
let Some(end) = ends.get(e) else { continue };
|
|
||||||
at += end.at / scale;
|
|
||||||
let Some(piece) = pieces.get(end.piece) else { continue };
|
|
||||||
if !members.contains(&piece.handle) {
|
|
||||||
members.push(piece.handle);
|
|
||||||
}
|
|
||||||
if let Some(slot) = excluded.get_mut(end.piece) {
|
|
||||||
*slot = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
junctions.push(json!({
|
|
||||||
"at": [at.x, at.y],
|
|
||||||
"ends": group.len(),
|
|
||||||
"members": members.iter().map(|h| h.hex()).collect::<Vec<_>>(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A joint links the two ends for the walk. A joint into an excluded piece is not a link, and
|
|
||||||
// a line whose own ends meet within a huge tolerance is a spur, not a zero-area loop.
|
|
||||||
let mut link: Vec<Option<usize>> = vec![None; n];
|
|
||||||
for (a, b) in joints {
|
|
||||||
let (Some(ea), Some(eb)) = (ends.get(a), ends.get(b)) else { continue };
|
|
||||||
if ea.piece == eb.piece && pieces.get(ea.piece).is_some_and(|p| p.arc.is_none()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if excluded.get(ea.piece).copied().unwrap_or(true) || excluded.get(eb.piece).copied().unwrap_or(true) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(slot) = link.get_mut(a) {
|
|
||||||
*slot = Some(b);
|
|
||||||
}
|
|
||||||
if let Some(slot) = link.get_mut(b) {
|
|
||||||
*slot = Some(a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut visited = vec![false; pieces.len()];
|
let mut visited = vec![false; pieces.len()];
|
||||||
let mut loops: Vec<Value> = Vec::new();
|
let mut loops: Vec<Value> = Vec::new();
|
||||||
@@ -225,6 +165,84 @@ pub fn run(s: &mut Session, p: &Value) -> Result<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A report with nothing analyzed: an empty drawing, or one past the pair cap.
|
/// A report with nothing analyzed: an empty drawing, or one past the pair cap.
|
||||||
|
/// Union-find over the ends: ends closer than `tol` share a root. Returns each end's parent.
|
||||||
|
fn merge_ends(ends: &[End], tol: f64) -> Vec<usize> {
|
||||||
|
let n = ends.len();
|
||||||
|
let mut parent: Vec<usize> = (0..n).collect();
|
||||||
|
for i in 0..n {
|
||||||
|
let Some(a) = ends.get(i) else { continue };
|
||||||
|
for j in (i + 1)..n {
|
||||||
|
let Some(b) = ends.get(j) else { continue };
|
||||||
|
if a.at.dist(b.at) <= tol {
|
||||||
|
let (ra, rb) = (find(&mut parent, i), find(&mut parent, j));
|
||||||
|
if ra != rb
|
||||||
|
&& let Some(slot) = parent.get_mut(ra)
|
||||||
|
{
|
||||||
|
*slot = rb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parent
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Each group of two ends is a joint (end indices); each of three or more is a junction, reported
|
||||||
|
/// with its pieces marked `excluded`.
|
||||||
|
fn sort_groups(groups: &BTreeMap<usize, Vec<usize>>, ends: &[End], pieces: &[Piece], excluded: &mut [bool]) -> (Vec<(usize, usize)>, Vec<Value>) {
|
||||||
|
let mut joints: Vec<(usize, usize)> = Vec::new();
|
||||||
|
let mut junctions: Vec<Value> = Vec::new();
|
||||||
|
for group in groups.values() {
|
||||||
|
match group.as_slice() {
|
||||||
|
&[a, b] => joints.push((a, b)),
|
||||||
|
[_, _, ..] => {
|
||||||
|
let scale = group.len() as f64;
|
||||||
|
let mut at = Vec2::ZERO;
|
||||||
|
let mut members: Vec<Handle> = Vec::new();
|
||||||
|
for &e in group {
|
||||||
|
let Some(end) = ends.get(e) else { continue };
|
||||||
|
at += end.at / scale;
|
||||||
|
let Some(piece) = pieces.get(end.piece) else { continue };
|
||||||
|
if !members.contains(&piece.handle) {
|
||||||
|
members.push(piece.handle);
|
||||||
|
}
|
||||||
|
if let Some(slot) = excluded.get_mut(end.piece) {
|
||||||
|
*slot = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
junctions.push(json!({
|
||||||
|
"at": [at.x, at.y],
|
||||||
|
"ends": group.len(),
|
||||||
|
"members": members.iter().map(|h| h.hex()).collect::<Vec<_>>(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(joints, junctions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The link each end takes from the joints. A joint into an excluded piece is not a link, and
|
||||||
|
/// a line whose own ends meet within a huge tolerance is a spur, not a zero-area loop.
|
||||||
|
fn joint_links(joints: Vec<(usize, usize)>, ends: &[End], pieces: &[Piece], excluded: &[bool]) -> Vec<Option<usize>> {
|
||||||
|
let mut link: Vec<Option<usize>> = vec![None; ends.len()];
|
||||||
|
for (a, b) in joints {
|
||||||
|
let (Some(ea), Some(eb)) = (ends.get(a), ends.get(b)) else { continue };
|
||||||
|
if ea.piece == eb.piece && pieces.get(ea.piece).is_some_and(|p| p.arc.is_none()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if excluded.get(ea.piece).copied().unwrap_or(true) || excluded.get(eb.piece).copied().unwrap_or(true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(slot) = link.get_mut(a) {
|
||||||
|
*slot = Some(b);
|
||||||
|
}
|
||||||
|
if let Some(slot) = link.get_mut(b) {
|
||||||
|
*slot = Some(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
link
|
||||||
|
}
|
||||||
|
|
||||||
fn empty_report(offset: usize, tol: f64, pair_tests: usize, pair_tests_needed: usize, pair_limit: usize, capped: bool, skipped: Skipped) -> Value {
|
fn empty_report(offset: usize, tol: f64, pair_tests: usize, pair_tests_needed: usize, pair_limit: usize, capped: bool, skipped: Skipped) -> Value {
|
||||||
json!({
|
json!({
|
||||||
"schema": 1,
|
"schema": 1,
|
||||||
|
|||||||
@@ -785,6 +785,48 @@ fn glob_cached(
|
|||||||
Ok(matched)
|
Ok(matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// For the bracket class opening at `pat[p]`, whether `text[t]` is in it: the index of its closing
|
||||||
|
/// `]` when it is, `None` when it is not or the class is never closed.
|
||||||
|
fn class_end(text: &[char], pat: &[char], p: usize, t: usize, budget: &mut u64) -> Result<Option<usize>, ()> {
|
||||||
|
let mut close = p;
|
||||||
|
while let Some(c) = pat.get(close) {
|
||||||
|
charge_match(budget)?;
|
||||||
|
if *c == ']' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
close += 1;
|
||||||
|
}
|
||||||
|
if close == pat.len() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let Some(&c) = text.get(t) else { return Ok(None) };
|
||||||
|
let set = &pat[p + 1..close];
|
||||||
|
let (neg, set) = match set.split_first() {
|
||||||
|
Some(('~', r)) if !r.is_empty() => (true, r),
|
||||||
|
_ => (false, set),
|
||||||
|
};
|
||||||
|
let mut hit = false;
|
||||||
|
let mut i = 0;
|
||||||
|
while i < set.len() {
|
||||||
|
charge_match(budget)?;
|
||||||
|
if set.get(i + 1) == Some(&'-') && i + 2 < set.len() {
|
||||||
|
if set[i] <= c && c <= set[i + 2] {
|
||||||
|
hit = true;
|
||||||
|
}
|
||||||
|
i += 3;
|
||||||
|
} else {
|
||||||
|
if set[i] == c {
|
||||||
|
hit = true;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hit == neg {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Ok(Some(close))
|
||||||
|
}
|
||||||
|
|
||||||
/// Evaluate wildcard tokens and backtracking branches under shared work and recursion limits.
|
/// Evaluate wildcard tokens and backtracking branches under shared work and recursion limits.
|
||||||
fn glob_inner(
|
fn glob_inner(
|
||||||
text: &[char],
|
text: &[char],
|
||||||
@@ -822,42 +864,7 @@ fn glob_inner(
|
|||||||
p += 2;
|
p += 2;
|
||||||
}
|
}
|
||||||
'[' => {
|
'[' => {
|
||||||
let mut close = p;
|
let Some(close) = class_end(text, pat, p, t, budget)? else { return Ok(false) };
|
||||||
while let Some(c) = pat.get(close) {
|
|
||||||
charge_match(budget)?;
|
|
||||||
if *c == ']' {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
close += 1;
|
|
||||||
}
|
|
||||||
if close == pat.len() {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
let Some(&c) = text.get(t) else { return Ok(false) };
|
|
||||||
let set = &pat[p + 1..close];
|
|
||||||
let (neg, set) = match set.split_first() {
|
|
||||||
Some(('~', r)) if !r.is_empty() => (true, r),
|
|
||||||
_ => (false, set),
|
|
||||||
};
|
|
||||||
let mut hit = false;
|
|
||||||
let mut i = 0;
|
|
||||||
while i < set.len() {
|
|
||||||
charge_match(budget)?;
|
|
||||||
if set.get(i + 1) == Some(&'-') && i + 2 < set.len() {
|
|
||||||
if set[i] <= c && c <= set[i + 2] {
|
|
||||||
hit = true;
|
|
||||||
}
|
|
||||||
i += 3;
|
|
||||||
} else {
|
|
||||||
if set[i] == c {
|
|
||||||
hit = true;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hit == neg {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
t += 1;
|
t += 1;
|
||||||
p = close + 1;
|
p = close + 1;
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-32
@@ -447,6 +447,44 @@ fn islands(g: &Graph, outer: &[usize], p: Vec2) -> Vec<Island> {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The point of the nearest other curve within `snap` of the end `e` of curve `i`, as (curve,
|
||||||
|
/// segment, parameter, point). `None` when the end touches a curve, or none is near. `Err` when
|
||||||
|
/// the budget of segment checks runs out.
|
||||||
|
fn nearest_curve(polys: &[Vec<Vec2>], i: usize, e: Vec2, snap: f64, tol: f64, budget: &mut usize) -> Result<Option<(usize, usize, f64, Vec2)>, ()> {
|
||||||
|
let mut best: Option<(f64, usize, usize, f64, Vec2)> = None;
|
||||||
|
let mut touching = false;
|
||||||
|
for (j, other) in polys.iter().enumerate() {
|
||||||
|
if j == i {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (k, w) in other.windows(2).enumerate() {
|
||||||
|
if *budget == 0 {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
*budget -= 1;
|
||||||
|
let [a, b] = w else { continue };
|
||||||
|
if !(a.is_finite() && b.is_finite())
|
||||||
|
|| e.x < a.x.min(b.x) - snap
|
||||||
|
|| e.x > a.x.max(b.x) + snap
|
||||||
|
|| e.y < a.y.min(b.y) - snap
|
||||||
|
|| e.y > a.y.max(b.y) + snap
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let line = Line::new(*a, *b);
|
||||||
|
let t = line.param_of(e).clamp(0.0, 1.0);
|
||||||
|
let q = line.at(t);
|
||||||
|
let d = q.dist(e);
|
||||||
|
if d <= tol {
|
||||||
|
touching = true;
|
||||||
|
} else if d <= snap && best.is_none_or(|(bd, ..)| d < bd) {
|
||||||
|
best = Some((d, j, k, t, q));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(best.filter(|_| !touching).map(|(_, j, k, t, q)| (j, k, t, q)))
|
||||||
|
}
|
||||||
|
|
||||||
/// The curves with each open end that stops within `snap` of another curve moved onto that
|
/// The curves with each open end that stops within `snap` of another curve moved onto that
|
||||||
/// curve (which gains a vertex there), so the two meet exactly. A curve that is flattened
|
/// curve (which gains a vertex there), so the two meet exactly. A curve that is flattened
|
||||||
/// into chords (an ellipse, a spline) lies up to the flattening tolerance off the true curve,
|
/// into chords (an ellipse, a spline) lies up to the flattening tolerance off the true curve,
|
||||||
@@ -474,38 +512,8 @@ pub fn snap_ends(polys: &[Vec<Vec2>], snap: f64, tol: f64) -> Vec<Vec<Vec2>> {
|
|||||||
if !e.is_finite() {
|
if !e.is_finite() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut best: Option<(f64, usize, usize, f64, Vec2)> = None;
|
let Ok(near) = nearest_curve(polys, i, e, snap, tol, &mut budget) else { break 'ends };
|
||||||
let mut touching = false;
|
let Some((j, k, t, q)) = near else { continue };
|
||||||
for (j, other) in polys.iter().enumerate() {
|
|
||||||
if j == i {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (k, w) in other.windows(2).enumerate() {
|
|
||||||
if budget == 0 {
|
|
||||||
break 'ends;
|
|
||||||
}
|
|
||||||
budget -= 1;
|
|
||||||
let [a, b] = w else { continue };
|
|
||||||
if !(a.is_finite() && b.is_finite())
|
|
||||||
|| e.x < a.x.min(b.x) - snap
|
|
||||||
|| e.x > a.x.max(b.x) + snap
|
|
||||||
|| e.y < a.y.min(b.y) - snap
|
|
||||||
|| e.y > a.y.max(b.y) + snap
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let line = Line::new(*a, *b);
|
|
||||||
let t = line.param_of(e).clamp(0.0, 1.0);
|
|
||||||
let q = line.at(t);
|
|
||||||
let d = q.dist(e);
|
|
||||||
if d <= tol {
|
|
||||||
touching = true;
|
|
||||||
} else if d <= snap && best.is_none_or(|(bd, ..)| d < bd) {
|
|
||||||
best = Some((d, j, k, t, q));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let Some((_, j, k, t, q)) = best.filter(|_| !touching) else { continue };
|
|
||||||
let Some(slot) = out.get_mut(i).and_then(|v| if at_end { v.last_mut() } else { v.first_mut() }) else { continue };
|
let Some(slot) = out.get_mut(i).and_then(|v| if at_end { v.last_mut() } else { v.first_mut() }) else { continue };
|
||||||
*slot = q;
|
*slot = q;
|
||||||
let ends = polys.get(j).and_then(|o| o.get(k).zip(o.get(k + 1)));
|
let ends = polys.get(j).and_then(|o| o.get(k).zip(o.get(k + 1)));
|
||||||
|
|||||||
@@ -235,6 +235,46 @@ pub struct Op {
|
|||||||
pub names: Vec<String>,
|
pub names: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The index just past the string literal opening at `b[i]`, honouring escapes and nesting.
|
||||||
|
fn skip_string(b: &[char], mut i: usize) -> usize {
|
||||||
|
let mut depth = 0;
|
||||||
|
while i < b.len() {
|
||||||
|
match b[i] {
|
||||||
|
'\\' => i += 1,
|
||||||
|
'(' => depth += 1,
|
||||||
|
')' => {
|
||||||
|
depth -= 1;
|
||||||
|
if depth == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
i + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The index just past the inline dictionary opening at `b[i]` (`<<`).
|
||||||
|
fn skip_dict(b: &[char], mut i: usize) -> usize {
|
||||||
|
let mut depth = 0;
|
||||||
|
while i < b.len() {
|
||||||
|
if b[i] == '<' && b.get(i + 1) == Some(&'<') {
|
||||||
|
depth += 1;
|
||||||
|
i += 1;
|
||||||
|
} else if b[i] == '>' && b.get(i + 1) == Some(&'>') {
|
||||||
|
depth -= 1;
|
||||||
|
i += 1;
|
||||||
|
if depth == 0 {
|
||||||
|
i += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
i
|
||||||
|
}
|
||||||
|
|
||||||
/// The operators of content text in order, each with its numeric operands (other operands
|
/// The operators of content text in order, each with its numeric operands (other operands
|
||||||
/// are skipped).
|
/// are skipped).
|
||||||
pub fn ops(content: &str) -> Vec<Op> {
|
pub fn ops(content: &str) -> Vec<Op> {
|
||||||
@@ -253,39 +293,10 @@ pub fn ops(content: &str) -> Vec<Op> {
|
|||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
} else if c == '(' {
|
} else if c == '(' {
|
||||||
let mut depth = 0;
|
i = skip_string(&b, i);
|
||||||
while i < b.len() {
|
|
||||||
match b[i] {
|
|
||||||
'\\' => i += 1,
|
|
||||||
'(' => depth += 1,
|
|
||||||
')' => {
|
|
||||||
depth -= 1;
|
|
||||||
if depth == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
} else if c == '<' && b.get(i + 1) == Some(&'<') {
|
} else if c == '<' && b.get(i + 1) == Some(&'<') {
|
||||||
// An inline dictionary (marked-content properties): skip to its end.
|
// An inline dictionary (marked-content properties): skip to its end.
|
||||||
let mut depth = 0;
|
i = skip_dict(&b, i);
|
||||||
while i < b.len() {
|
|
||||||
if b[i] == '<' && b.get(i + 1) == Some(&'<') {
|
|
||||||
depth += 1;
|
|
||||||
i += 1;
|
|
||||||
} else if b[i] == '>' && b.get(i + 1) == Some(&'>') {
|
|
||||||
depth -= 1;
|
|
||||||
i += 1;
|
|
||||||
if depth == 0 {
|
|
||||||
i += 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
} else if c == '<' {
|
} else if c == '<' {
|
||||||
while i < b.len() && b[i] != '>' {
|
while i < b.len() && b[i] != '>' {
|
||||||
i += 1;
|
i += 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user