forked from 1b-agent/opencadstudio-upstream
feat(shape): render SHAPE entities from their compiled SHX shape file
SHAPE entities only drew a diamond placeholder — .SHX files were never parsed. A new reader (scene/text/shx.rs) decodes the classic `AutoCAD-86 shapes 1.0/1.1` container and interprets the full shape bytecode — pen up/down, scale divide/multiply, position push/pop, subshapes, signed XY runs, octant/fractional/bulge arcs, the 16 direction vectors and the vertical-only skip — into unit-space polylines, cached per file and per shape number. The entity resolves its STYLE by handle (name fallback), the style's shape file with the shared path fallbacks (as stored → relative to the drawing → basename next to it; traded files carry foreign absolute paths), then places the glyph with size × relative X scale, oblique shear and rotation through the OCS mapping. load_file now records doc.source_path so render-time resolution knows the drawing's folder. A missing or unreadable shape file keeps the diamond placeholder. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
f4f8c0687c
commit
1de4344280
4 files changed
+517
-1
No files matched your search
@@ -55,6 +55,42 @@ fn shape_marker(
|
||||
|
||||
// ── TruckConvertible ──────────────────────────────────────────────────────────
|
||||
|
||||
/// The shape's SHX polylines, resolved through its STYLE's shape file: the
|
||||
/// style by handle (name fallback), the file with the shared path fallbacks
|
||||
/// (as stored → relative to the drawing → basename next to it — foreign
|
||||
/// absolute paths are the norm in traded files).
|
||||
fn shx_polylines(
|
||||
shape: &Shape,
|
||||
document: &acadrust::CadDocument,
|
||||
) -> Option<crate::scene::text::shx::ShapePolylines> {
|
||||
let style = shape
|
||||
.style_handle
|
||||
.and_then(|h| document.text_styles.iter().find(|s| s.handle == h))
|
||||
.or_else(|| {
|
||||
let name = shape.style_name.trim();
|
||||
(!name.is_empty())
|
||||
.then(|| {
|
||||
document
|
||||
.text_styles
|
||||
.iter()
|
||||
.find(|s| s.name.eq_ignore_ascii_case(name))
|
||||
})
|
||||
.flatten()
|
||||
})?;
|
||||
let font = style.font_file.trim();
|
||||
if font.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let base = document
|
||||
.source_path
|
||||
.as_deref()
|
||||
.map(std::path::Path::new)
|
||||
.and_then(|p| p.parent());
|
||||
let resolved = crate::io::resolve_image_file(font, base)?;
|
||||
let num = u16::try_from(shape.shape_number).ok()?;
|
||||
crate::scene::text::shx::shape_polylines(&resolved, num)
|
||||
}
|
||||
|
||||
impl TruckConvertible for Shape {
|
||||
fn to_truck(&self, _document: &acadrust::CadDocument) -> Option<TruckEntity> {
|
||||
// SHAPE insertion point is OCS (planar entity) — map through the
|
||||
@@ -70,6 +106,55 @@ impl TruckConvertible for Shape {
|
||||
let size = self.size.abs().max(0.5);
|
||||
|
||||
let snap_pt = glam::DVec3::new(ox, oy, oz);
|
||||
|
||||
// Real SHX glyph when the style's shape file resolves; the diamond
|
||||
// marker stays as the missing-file placeholder.
|
||||
if let Some(polys) = shx_polylines(self, _document) {
|
||||
let (sin_r, cos_r) = (self.rotation.sin(), self.rotation.cos());
|
||||
let ob = self.oblique_angle.tan();
|
||||
let xs = if self.relative_x_scale.abs() < 1e-9 {
|
||||
1.0
|
||||
} else {
|
||||
self.relative_x_scale
|
||||
};
|
||||
let scale = self.size.abs().max(1e-9);
|
||||
let mut pts: Vec<[f64; 3]> = Vec::new();
|
||||
for poly in polys.iter() {
|
||||
if poly.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
if !pts.is_empty() {
|
||||
pts.push([f64::NAN; 3]);
|
||||
}
|
||||
for &[px, py] in poly {
|
||||
// unit → entity local: size, X-scale, oblique shear, rotation.
|
||||
let lx = px * scale * xs + py * scale * ob;
|
||||
let ly = py * scale;
|
||||
let wx = lx * cos_r - ly * sin_r;
|
||||
let wy = lx * sin_r + ly * cos_r;
|
||||
let (gx, gy, gz) = crate::scene::view::transform::ocs_point_to_wcs(
|
||||
(
|
||||
self.insertion_point.x + wx,
|
||||
self.insertion_point.y + wy,
|
||||
self.insertion_point.z,
|
||||
),
|
||||
(self.normal.x, self.normal.y, self.normal.z),
|
||||
);
|
||||
pts.push([gx, gy, gz]);
|
||||
}
|
||||
}
|
||||
if !pts.is_empty() {
|
||||
return Some(TruckEntity {
|
||||
pick_tris: Vec::new(),
|
||||
object: TruckObject::Lines(pts),
|
||||
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
|
||||
tangent_geoms: vec![],
|
||||
key_vertices: vec![[ox, oy, oz]],
|
||||
fill_tris: vec![],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let pts = shape_marker(
|
||||
ox,
|
||||
oy,
|
||||
|
||||
+3
-1
@@ -190,6 +190,7 @@ pub fn load_file(path: &Path) -> Result<CadDocument, String> {
|
||||
fix_viewport_status_flags(&mut doc);
|
||||
fix_current_style_names(&mut doc);
|
||||
resolve_raster_image_paths(&mut doc, path.parent());
|
||||
doc.source_path = Some(path.to_string_lossy().into_owned());
|
||||
Ok(doc)
|
||||
}
|
||||
"dxf" => {
|
||||
@@ -201,6 +202,7 @@ pub fn load_file(path: &Path) -> Result<CadDocument, String> {
|
||||
fix_viewport_status_flags(&mut doc);
|
||||
fix_current_style_names(&mut doc);
|
||||
resolve_raster_image_paths(&mut doc, path.parent());
|
||||
doc.source_path = Some(path.to_string_lossy().into_owned());
|
||||
Ok(doc)
|
||||
}
|
||||
_ => Err(format!("Unsupported file format: .{ext}")),
|
||||
@@ -265,7 +267,7 @@ fn resolve_raster_image_paths(doc: &mut CadDocument, base_dir: Option<&Path>) {
|
||||
/// Resolve a (possibly foreign / absolute) image path to an existing file:
|
||||
/// as stored, then relative to the drawing folder, then just the file name
|
||||
/// next to the drawing.
|
||||
fn resolve_image_file(raw: &str, base_dir: Option<&Path>) -> Option<String> {
|
||||
pub(crate) fn resolve_image_file(raw: &str, base_dir: Option<&Path>) -> Option<String> {
|
||||
if Path::new(raw).is_file() {
|
||||
return Some(raw.to_string());
|
||||
}
|
||||
|
||||
@@ -6,3 +6,4 @@ pub mod sysfont;
|
||||
pub mod ttf_glyph;
|
||||
pub mod web_font;
|
||||
pub mod complex_lt;
|
||||
pub mod shx;
|
||||
@@ -0,0 +1,428 @@
|
||||
// Compiled AutoCAD shape-file (.SHX) reader + shape tessellator.
|
||||
//
|
||||
// Parses the classic `AutoCAD-86 shapes 1.0/1.1` container (the format SHAPE
|
||||
// entities and complex-linetype shapes reference) and interprets a shape's
|
||||
// bytecode into unit-space polylines. Fonts (`AutoCAD-86 unifont` etc.) are
|
||||
// out of scope here — text keeps going through the LFF/TTF substitutes.
|
||||
//
|
||||
// Bytecode reference (each item, pen starts DOWN at the origin):
|
||||
// 0 end of shape
|
||||
// 1 / 2 pen down / pen up
|
||||
// 3 n / 4 n divide / multiply the vector scale by n
|
||||
// 5 / 6 push / pop the current position (4-deep stack)
|
||||
// 7 n draw subshape n
|
||||
// 8 dx dy signed byte displacement
|
||||
// 9 … repeated signed (dx,dy) pairs until (0,0)
|
||||
// 0xA r f octant arc: radius, flags (sign|start-octant|octant-count)
|
||||
// 0xB s e hr r f fractional arc
|
||||
// 0xC dx dy b bulge segment; 0xD … repeated until (0,0)
|
||||
// 0xE vertical-text-only flag: skip the NEXT item horizontally
|
||||
// else vector: high nibble = length, low nibble = one of 16
|
||||
// predefined directions
|
||||
//
|
||||
// Results are cached per (file, shape-number); parse tables per file.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
/// One shape's tessellation in unit space: polylines of XY points.
|
||||
pub type ShapePolylines = Arc<Vec<Vec<[f64; 2]>>>;
|
||||
|
||||
/// The 16 SHX vector directions. Diagonal-ish entries deliberately use the
|
||||
/// classic half-steps (not normalized) — lengths scale per direction exactly
|
||||
/// as AutoCAD draws them.
|
||||
const DIRS: [[f64; 2]; 16] = [
|
||||
[1.0, 0.0],
|
||||
[1.0, 0.5],
|
||||
[1.0, 1.0],
|
||||
[0.5, 1.0],
|
||||
[0.0, 1.0],
|
||||
[-0.5, 1.0],
|
||||
[-1.0, 1.0],
|
||||
[-1.0, 0.5],
|
||||
[-1.0, 0.0],
|
||||
[-1.0, -0.5],
|
||||
[-1.0, -1.0],
|
||||
[-0.5, -1.0],
|
||||
[0.0, -1.0],
|
||||
[0.5, -1.0],
|
||||
[1.0, -1.0],
|
||||
[1.0, -0.5],
|
||||
];
|
||||
|
||||
struct ShxFile {
|
||||
/// shape number → (name, spec bytes)
|
||||
shapes: HashMap<u16, (String, Vec<u8>)>,
|
||||
}
|
||||
|
||||
fn file_cache() -> &'static Mutex<HashMap<String, Option<Arc<ShxFile>>>> {
|
||||
static C: OnceLock<Mutex<HashMap<String, Option<Arc<ShxFile>>>>> = OnceLock::new();
|
||||
C.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
fn shape_cache() -> &'static Mutex<HashMap<(String, u16), Option<ShapePolylines>>> {
|
||||
static C: OnceLock<Mutex<HashMap<(String, u16), Option<ShapePolylines>>>> = OnceLock::new();
|
||||
C.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
fn load_file(path: &str) -> Option<Arc<ShxFile>> {
|
||||
if let Ok(c) = file_cache().lock() {
|
||||
if let Some(hit) = c.get(path) {
|
||||
return hit.clone();
|
||||
}
|
||||
}
|
||||
let parsed = parse_file(path).map(Arc::new);
|
||||
if let Ok(mut c) = file_cache().lock() {
|
||||
c.insert(path.to_string(), parsed.clone());
|
||||
}
|
||||
parsed
|
||||
}
|
||||
|
||||
fn parse_file(path: &str) -> Option<ShxFile> {
|
||||
let bytes = std::fs::read(path).ok()?;
|
||||
// Header line: `AutoCAD-86 shapes 1.0`/`1.1` + CR LF SUB. Reject fonts.
|
||||
let head_end = bytes.iter().position(|&b| b == 0x1A)?;
|
||||
let header = String::from_utf8_lossy(&bytes[..head_end]);
|
||||
if !header.contains("shapes") {
|
||||
return None;
|
||||
}
|
||||
let mut p = head_end + 1;
|
||||
let rd_u16 = |p: &mut usize| -> Option<u16> {
|
||||
let v = u16::from_le_bytes([*bytes.get(*p)?, *bytes.get(*p + 1)?]);
|
||||
*p += 2;
|
||||
Some(v)
|
||||
};
|
||||
let _first = rd_u16(&mut p)?;
|
||||
let _last = rd_u16(&mut p)?;
|
||||
let count = rd_u16(&mut p)? as usize;
|
||||
let mut dir: Vec<(u16, usize)> = Vec::with_capacity(count);
|
||||
for _ in 0..count {
|
||||
let num = rd_u16(&mut p)?;
|
||||
let len = rd_u16(&mut p)? as usize;
|
||||
dir.push((num, len));
|
||||
}
|
||||
let mut shapes = HashMap::new();
|
||||
for (num, len) in dir {
|
||||
let end = p.checked_add(len)?.min(bytes.len());
|
||||
let blob = &bytes[p..end];
|
||||
p = end;
|
||||
// Name = leading NUL-terminated ASCII, spec follows.
|
||||
let name_end = blob.iter().position(|&b| b == 0).unwrap_or(0);
|
||||
let name = String::from_utf8_lossy(&blob[..name_end]).into_owned();
|
||||
let spec = blob.get(name_end + 1..).unwrap_or(&[]).to_vec();
|
||||
shapes.insert(num, (name, spec));
|
||||
}
|
||||
Some(ShxFile { shapes })
|
||||
}
|
||||
|
||||
/// Tessellate `shape_number` from the SHX at `path` into unit-space
|
||||
/// polylines. `None` when the file/shape is missing or not a shapes file.
|
||||
pub fn shape_polylines(path: &str, shape_number: u16) -> Option<ShapePolylines> {
|
||||
let key = (path.to_string(), shape_number);
|
||||
if let Ok(c) = shape_cache().lock() {
|
||||
if let Some(hit) = c.get(&key) {
|
||||
return hit.clone();
|
||||
}
|
||||
}
|
||||
let built = build_shape(path, shape_number);
|
||||
if let Ok(mut c) = shape_cache().lock() {
|
||||
c.insert(key, built.clone());
|
||||
}
|
||||
built
|
||||
}
|
||||
|
||||
fn build_shape(path: &str, shape_number: u16) -> Option<ShapePolylines> {
|
||||
let file = load_file(path)?;
|
||||
let mut out: Vec<Vec<[f64; 2]>> = Vec::new();
|
||||
let mut cur: Vec<[f64; 2]> = vec![[0.0, 0.0]];
|
||||
let mut st = InterpState {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
scale: 1.0,
|
||||
pen_down: true,
|
||||
stack: Vec::new(),
|
||||
};
|
||||
interpret(&file, shape_number, &mut st, &mut cur, &mut out, 0);
|
||||
if cur.len() > 1 {
|
||||
out.push(cur);
|
||||
}
|
||||
if out.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(Arc::new(out))
|
||||
}
|
||||
|
||||
struct InterpState {
|
||||
x: f64,
|
||||
y: f64,
|
||||
scale: f64,
|
||||
pen_down: bool,
|
||||
stack: Vec<(f64, f64)>,
|
||||
}
|
||||
|
||||
/// Append a straight segment / move in interpreter space.
|
||||
fn advance(
|
||||
st: &mut InterpState,
|
||||
cur: &mut Vec<[f64; 2]>,
|
||||
out: &mut Vec<Vec<[f64; 2]>>,
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
) {
|
||||
st.x += dx * st.scale;
|
||||
st.y += dy * st.scale;
|
||||
if st.pen_down {
|
||||
cur.push([st.x, st.y]);
|
||||
} else {
|
||||
if cur.len() > 1 {
|
||||
out.push(std::mem::take(cur));
|
||||
}
|
||||
cur.clear();
|
||||
cur.push([st.x, st.y]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Arc from the current point: `start`/`sweep` in radians over `r` (already
|
||||
/// scale-applied). Sampled into short segments.
|
||||
fn emit_arc(
|
||||
st: &mut InterpState,
|
||||
cur: &mut Vec<[f64; 2]>,
|
||||
out: &mut Vec<Vec<[f64; 2]>>,
|
||||
r: f64,
|
||||
start: f64,
|
||||
sweep: f64,
|
||||
) {
|
||||
if r <= 0.0 || sweep == 0.0 {
|
||||
return;
|
||||
}
|
||||
let cx = st.x - r * start.cos();
|
||||
let cy = st.y - r * start.sin();
|
||||
let steps = ((sweep.abs() / std::f64::consts::TAU * 32.0).ceil() as usize).max(2);
|
||||
for i in 1..=steps {
|
||||
let a = start + sweep * i as f64 / steps as f64;
|
||||
let nx = cx + r * a.cos();
|
||||
let ny = cy + r * a.sin();
|
||||
if st.pen_down {
|
||||
cur.push([nx, ny]);
|
||||
} else if i == steps {
|
||||
if cur.len() > 1 {
|
||||
out.push(std::mem::take(cur));
|
||||
}
|
||||
cur.clear();
|
||||
cur.push([nx, ny]);
|
||||
}
|
||||
st.x = nx;
|
||||
st.y = ny;
|
||||
}
|
||||
}
|
||||
|
||||
/// Bulge segment (like LWPOLYLINE bulge, in ±127 units).
|
||||
fn emit_bulge(
|
||||
st: &mut InterpState,
|
||||
cur: &mut Vec<[f64; 2]>,
|
||||
out: &mut Vec<Vec<[f64; 2]>>,
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
bulge: f64,
|
||||
) {
|
||||
let (sx, sy) = (st.x, st.y);
|
||||
let (ex, ey) = (sx + dx * st.scale, sy + dy * st.scale);
|
||||
let b = bulge / 127.0;
|
||||
if b.abs() < 1e-6 || !st.pen_down {
|
||||
advance(st, cur, out, dx, dy);
|
||||
return;
|
||||
}
|
||||
// Bulge = tan(theta/4); sample the arc through the chord.
|
||||
let theta = 4.0 * b.atan();
|
||||
let chord = ((ex - sx).powi(2) + (ey - sy).powi(2)).sqrt();
|
||||
if chord < 1e-12 {
|
||||
return;
|
||||
}
|
||||
let r = chord / (2.0 * (theta / 2.0).sin().abs());
|
||||
let mid_x = (sx + ex) * 0.5;
|
||||
let mid_y = (sy + ey) * 0.5;
|
||||
let d = (r * r - chord * chord / 4.0).max(0.0).sqrt() * if theta > 0.0 { 1.0 } else { -1.0 };
|
||||
let (nx, ny) = (-(ey - sy) / chord, (ex - sx) / chord);
|
||||
let (cx, cy) = (mid_x + nx * d, mid_y + ny * d);
|
||||
let a0 = (sy - cy).atan2(sx - cx);
|
||||
let steps = ((theta.abs() / std::f64::consts::TAU * 32.0).ceil() as usize).max(2);
|
||||
for i in 1..=steps {
|
||||
let a = a0 + theta * i as f64 / steps as f64;
|
||||
let px = cx + r * a.cos();
|
||||
let py = cy + r * a.sin();
|
||||
cur.push([px, py]);
|
||||
st.x = px;
|
||||
st.y = py;
|
||||
}
|
||||
st.x = ex;
|
||||
st.y = ey;
|
||||
if let Some(last) = cur.last_mut() {
|
||||
*last = [ex, ey];
|
||||
}
|
||||
}
|
||||
|
||||
fn interpret(
|
||||
file: &ShxFile,
|
||||
shape_number: u16,
|
||||
st: &mut InterpState,
|
||||
cur: &mut Vec<[f64; 2]>,
|
||||
out: &mut Vec<Vec<[f64; 2]>>,
|
||||
depth: usize,
|
||||
) {
|
||||
if depth > 4 {
|
||||
return;
|
||||
}
|
||||
let Some((_, spec)) = file.shapes.get(&shape_number) else {
|
||||
return;
|
||||
};
|
||||
let b = spec.as_slice();
|
||||
let mut i = 0usize;
|
||||
let mut skip_next = false;
|
||||
while i < b.len() {
|
||||
let code = b[i];
|
||||
i += 1;
|
||||
// 0xE: the next item applies to vertical text only — decode it to
|
||||
// know its length, but discard its effect.
|
||||
let skipping = skip_next;
|
||||
skip_next = false;
|
||||
match code {
|
||||
0 => return,
|
||||
1 => {
|
||||
if !skipping {
|
||||
st.pen_down = true;
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
if !skipping {
|
||||
st.pen_down = false;
|
||||
}
|
||||
}
|
||||
3 | 4 => {
|
||||
let n = *b.get(i).unwrap_or(&1) as f64;
|
||||
i += 1;
|
||||
if !skipping && n > 0.0 {
|
||||
if code == 3 {
|
||||
st.scale /= n;
|
||||
} else {
|
||||
st.scale *= n;
|
||||
}
|
||||
}
|
||||
}
|
||||
5 => {
|
||||
if !skipping && st.stack.len() < 4 {
|
||||
st.stack.push((st.x, st.y));
|
||||
}
|
||||
}
|
||||
6 => {
|
||||
if !skipping {
|
||||
if let Some((px, py)) = st.stack.pop() {
|
||||
st.x = px;
|
||||
st.y = py;
|
||||
if cur.len() > 1 {
|
||||
out.push(std::mem::take(cur));
|
||||
}
|
||||
cur.clear();
|
||||
cur.push([px, py]);
|
||||
}
|
||||
}
|
||||
}
|
||||
7 => {
|
||||
let sub = *b.get(i).unwrap_or(&0) as u16;
|
||||
i += 1;
|
||||
if !skipping && sub != 0 && sub != shape_number {
|
||||
interpret(file, sub, st, cur, out, depth + 1);
|
||||
}
|
||||
}
|
||||
8 => {
|
||||
let dx = *b.get(i).unwrap_or(&0) as i8 as f64;
|
||||
let dy = *b.get(i + 1).unwrap_or(&0) as i8 as f64;
|
||||
i += 2;
|
||||
if !skipping {
|
||||
advance(st, cur, out, dx, dy);
|
||||
}
|
||||
}
|
||||
9 => loop {
|
||||
let dx = *b.get(i).unwrap_or(&0) as i8 as f64;
|
||||
let dy = *b.get(i + 1).unwrap_or(&0) as i8 as f64;
|
||||
i += 2;
|
||||
if dx == 0.0 && dy == 0.0 {
|
||||
break;
|
||||
}
|
||||
if !skipping {
|
||||
advance(st, cur, out, dx, dy);
|
||||
}
|
||||
},
|
||||
0xA => {
|
||||
let r = *b.get(i).unwrap_or(&0) as f64;
|
||||
let f = *b.get(i + 1).unwrap_or(&0);
|
||||
i += 2;
|
||||
if !skipping {
|
||||
let cw = f & 0x80 != 0;
|
||||
let start_oct = ((f >> 4) & 7) as f64;
|
||||
let mut n_oct = (f & 7) as f64;
|
||||
if n_oct == 0.0 {
|
||||
n_oct = 8.0;
|
||||
}
|
||||
let start = start_oct * std::f64::consts::FRAC_PI_4;
|
||||
let sweep = n_oct * std::f64::consts::FRAC_PI_4 * if cw { -1.0 } else { 1.0 };
|
||||
emit_arc(st, cur, out, r * st.scale, start, sweep);
|
||||
}
|
||||
}
|
||||
0xB => {
|
||||
let so = *b.get(i).unwrap_or(&0) as f64;
|
||||
let eo = *b.get(i + 1).unwrap_or(&0) as f64;
|
||||
let hr = *b.get(i + 2).unwrap_or(&0) as f64;
|
||||
let r = *b.get(i + 3).unwrap_or(&0) as f64;
|
||||
let f = *b.get(i + 4).unwrap_or(&0);
|
||||
i += 5;
|
||||
if !skipping {
|
||||
let radius = (hr * 256.0 + r) * st.scale;
|
||||
let cw = f & 0x80 != 0;
|
||||
let start_oct = ((f >> 4) & 7) as f64;
|
||||
let mut n_oct = (f & 7) as f64;
|
||||
if n_oct == 0.0 {
|
||||
n_oct = 8.0;
|
||||
}
|
||||
let q = std::f64::consts::FRAC_PI_4;
|
||||
let start = start_oct * q + so * q / 256.0 * if cw { -1.0 } else { 1.0 };
|
||||
let total = n_oct * q;
|
||||
let sweep_mag = total - so * q / 256.0 - (255.0 - eo) * q / 256.0
|
||||
+ if eo == 0.0 { 0.0 } else { 0.0 };
|
||||
let sweep = sweep_mag.max(0.0) * if cw { -1.0 } else { 1.0 };
|
||||
emit_arc(st, cur, out, radius, start, sweep);
|
||||
}
|
||||
}
|
||||
0xC => {
|
||||
let dx = *b.get(i).unwrap_or(&0) as i8 as f64;
|
||||
let dy = *b.get(i + 1).unwrap_or(&0) as i8 as f64;
|
||||
let bu = *b.get(i + 2).unwrap_or(&0) as i8 as f64;
|
||||
i += 3;
|
||||
if !skipping {
|
||||
emit_bulge(st, cur, out, dx, dy, bu);
|
||||
}
|
||||
}
|
||||
0xD => loop {
|
||||
let dx = *b.get(i).unwrap_or(&0) as i8 as f64;
|
||||
let dy = *b.get(i + 1).unwrap_or(&0) as i8 as f64;
|
||||
i += 2;
|
||||
if dx == 0.0 && dy == 0.0 {
|
||||
break;
|
||||
}
|
||||
let bu = *b.get(i).unwrap_or(&0) as i8 as f64;
|
||||
i += 1;
|
||||
if !skipping {
|
||||
emit_bulge(st, cur, out, dx, dy, bu);
|
||||
}
|
||||
},
|
||||
0xE => {
|
||||
skip_next = true;
|
||||
}
|
||||
v => {
|
||||
let len = (v >> 4) as f64;
|
||||
let dir = DIRS[(v & 0xF) as usize];
|
||||
if !skipping {
|
||||
advance(st, cur, out, dir[0] * len, dir[1] * len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user