continued work

This commit is contained in:
Brandon Thomas 2026-09-30 11:38:47 -04:00
parent c9f38685b4
commit 05f5bed1ad
5 changed files with 23 additions and 15 deletions

View File

@ -292,6 +292,12 @@ fn view_toggles_redraw_without_dirtying() {
s.execute("view.overprintPreview", &json!({"on": false})).unwrap();
}
/// Whether the content has a DeviceCMYK fill/stroke operator (`c m y k k` / `K`).
fn has_cmyk_op(pdf: &str) -> bool {
let toks: Vec<&str> = pdf.split_ascii_whitespace().collect();
toks.windows(5).any(|w| (w[4] == "k" || w[4] == "K") && w[..4].iter().all(|t| t.parse::<f32>().is_ok()))
}
fn pdf_text(doc: &Document) -> String {
let bytes = drawcraft_pdf::export(doc, &drawcraft_pdf::PdfOptions { compress: false, created: Some(0), ..Default::default() }).unwrap();
String::from_utf8_lossy(&bytes).into_owned()
@ -302,17 +308,16 @@ fn pdf_cmyk_document_writes_device_cmyk() {
let mut s = session();
rect(&mut s, 0.0, 0.0, 10.0, 10.0, json!("#ff0000"));
let rgb_pdf = pdf_text(&s.doc().unwrap().doc);
assert!(!rgb_pdf.contains("DeviceCMYK"));
assert!(!has_cmyk_op(&rgb_pdf) && rgb_pdf.contains(" rg"));
s.execute("object.convertDocumentColorMode", &json!({"mode": "cmyk"})).unwrap();
let pdf = pdf_text(&s.doc().unwrap().doc);
std::fs::write("/tmp/claude-dbg.pdf", &pdf).ok();
assert!(pdf.contains("DeviceCMYK"), "CMYK colours are written as DeviceCMYK");
assert!(has_cmyk_op(&pdf), "CMYK colours are written with the DeviceCMYK `k` operator");
// RGB colours pasted into a CMYK document are separated on export.
let mut s2 = session();
rect(&mut s2, 0.0, 0.0, 10.0, 10.0, json!("#336699"));
let mut d = (*s2.doc().unwrap().doc).clone();
d.color_mode = ColorMode::Cmyk;
assert!(pdf_text(&d).contains("DeviceCMYK"));
assert!(has_cmyk_op(&pdf_text(&d)));
}
#[test]

View File

@ -82,15 +82,16 @@ impl BandMatrix {
let (n, bw) = (self.n, self.bw);
for i in 0..n {
let mut s = b[i];
for k in i.saturating_sub(bw)..i {
s -= self.a[self.idx(i, k)] * b[k];
let k0 = i.saturating_sub(bw);
for (k, bk) in b[k0..i].iter().enumerate() {
s -= self.a[self.idx(i, k0 + k)] * bk;
}
b[i] = s / self.a[self.idx(i, i)];
}
for i in (0..n).rev() {
let mut s = b[i];
for k in i + 1..(i + bw + 1).min(n) {
s -= self.a[self.idx(k, i)] * b[k];
for (k, bk) in b[i + 1..(i + bw + 1).min(n)].iter().enumerate() {
s -= self.a[self.idx(i + 1 + k, i)] * bk;
}
b[i] = s / self.a[self.idx(i, i)];
}

View File

@ -602,12 +602,10 @@ mod tests {
let mut pd = PathData::single(SubPath::polyline(&[Point::new(80.0, 100.0), Point::new(120.0, 100.0)], false));
let p = LiquifyParams { detail: 1.0, simplify: 0.0, ..prm(LiquifyKind::Twirl) };
apply_stroke(&mut pd, &[c], &p, 0);
// Rotation preserves the distance to the centre.
// Rotation preserves every anchor's distance to the centre (ends at 20, the midpoint at 0).
for (_, _, a) in pd.anchors() {
let d0 = if a.p.x < 100.0 { 20.0 } else { 20.0 };
if a.p.distance(c) > 1.0 {
assert!((a.p.distance(c) - d0).abs() < 1e-6 || a.p.distance(c) < 20.0);
}
let d = a.p.distance(c);
assert!((d - 20.0).abs() < 1e-9 || d < 1e-9, "{d}");
}
let first = pd.subpaths[0].anchors[0].p;
assert!(first.y != 100.0 && (first.distance(c) - 20.0).abs() < 1e-9);

View File

@ -68,6 +68,9 @@ impl Plane {
pub const ALL: [Plane; 3] = [Plane::Left, Plane::Right, Plane::Ground];
}
/// Plane Switching widget: (plane, face quad) list and the "no plane" circle (centre, radius).
pub type WidgetGeom = (Vec<(Plane, [Point; 4])>, (Point, f64));
/// A 3×3 projective transform (row-major).
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Homography(pub [[f64; 3]; 3]);
@ -350,7 +353,7 @@ impl PerspectiveGrid {
}
/// Plane Switching widget geometry: (plane, quad) faces and the "no plane" circle (centre, r).
pub fn widget(&self, doc: &Document, tol: f64) -> (Vec<(Plane, [Point; 4])>, (Point, f64)) {
pub fn widget(&self, doc: &Document, tol: f64) -> WidgetGeom {
let ab = doc.artboards.first().map(|a| a.rect).unwrap_or(Rect::new(0.0, 0.0, 612.0, 792.0));
let s = 40.0 * tol;
let (cx, cy) = (ab.x0 + 10.0 * tol + s / 2.0, ab.y0 + 10.0 * tol + s / 2.0);

View File

@ -235,6 +235,7 @@ pub fn menu_items() -> Vec<crate::menus::Item> {
/// The app-bar workspace switcher's menu.
pub fn popup(app: &mut DrawcraftApp, ui: &mut egui::Ui) {
ui.set_min_width(210.0);
ui.set_max_width(240.0);
let mut clicked: Option<(&'static str, Value)> = None;
for it in menu_items() {
match it {
@ -248,7 +249,7 @@ pub fn popup(app: &mut DrawcraftApp, ui: &mut egui::Ui) {
Some(false) => format!(" {label}"),
None => format!(" {label}"),
};
if ui.add(egui::Button::new(text).min_size(egui::vec2(ui.available_width(), 0.0))).clicked() {
if ui.add(egui::Button::new(text).min_size(egui::vec2(220.0, 0.0))).clicked() {
clicked = Some((id, if p.is_null() { json!({}) } else { p }));
ui.close();
}