mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-30 13:16:56 -06:00
C: Attributes panel — overprint fill/stroke/gap, nonprinting
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2cd4102469
commit
3609862c2e
@@ -38,6 +38,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
|
||||
- Control panel scale X/Y, rotation and shear fields (`transform.info`, absolute `transform.set {scaleX, scaleY, rotation, shear}` about the reference point) and Preferences › Transformations are Totals for nested objects and placed graphics.
|
||||
- Variable fonts: each named instance is a style (Font menu, shaping with feature variations, metrics, outlines) and embeds at its axis settings in PDF; free axis sliders still to come.
|
||||
- Object ▸ Select (first/next above, next/last below, container, content, previous/next in group), full Object ▸ Convert Shape (`object.convertShape`), Table ▸ Split Cell Horizontally/Vertically.
|
||||
- Attributes panel (`object.attributes`: Overprint Fill/Stroke/Gap, Nonprinting), with Overprint Gap in Overprint Preview and IDML.
|
||||
- Drag and drop text editing: drag selected text to move it (Alt copies) with its formatting (`text.release`).
|
||||
- Image Import Options: place any page of a multi-page PDF (`file.place {pdfPage}`; the app asks which page), shown on screen and embedded as that page in PDF export.
|
||||
- Define Lists: named numbered lists that continue across stories in page order (`list.define`, paragraph `listName` / `startAt`), round-tripped through IDML; `null` in `type.para` / `type.char` now removes an override.
|
||||
|
||||
@@ -178,6 +178,9 @@ pub struct Stroke {
|
||||
pub gap_swatch: String,
|
||||
pub gap_tint: f32,
|
||||
pub overprint: bool,
|
||||
/// Overprint Gap (Attributes panel).
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub gap_overprint: bool,
|
||||
}
|
||||
|
||||
impl Default for Stroke {
|
||||
@@ -197,6 +200,7 @@ impl Default for Stroke {
|
||||
gap_swatch: designcraft_color::swatch::NONE.into(),
|
||||
gap_tint: 1.0,
|
||||
overprint: false,
|
||||
gap_overprint: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Stroke",
|
||||
[],
|
||||
None,
|
||||
"{swatch?, tint?, weight?, align?: center|inside|outside, type?: {kind:…}, cap?: butt|round|projecting, join?: miter|round|bevel, miterLimit?, start?, end?: none|simple|simpleWide|triangle|triangleWide|barbed|curved|circle|circleSolid|square|squareSolid|bar, gapSwatch?, gapTint?, ids?}",
|
||||
"{swatch?, tint?, weight?, align?: center|inside|outside, type?: {kind:…}, cap?: butt|round|projecting, join?: miter|round|bevel, miterLimit?, start?, end?: none|simple|simpleWide|triangle|triangleWide|barbed|curved|circle|circleSolid|square|squareSolid|bar, gapSwatch?, gapTint?, gapOverprint?: bool, ids?}",
|
||||
has_selection,
|
||||
|s, p| {
|
||||
let p2 = p.clone();
|
||||
@@ -188,6 +188,9 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
if let Some(v) = p2.get("gapTint").and_then(Value::as_f64) {
|
||||
st.gap_tint = v as f32;
|
||||
}
|
||||
if let Some(v) = p2.get("gapOverprint").and_then(Value::as_bool) {
|
||||
st.gap_overprint = v;
|
||||
}
|
||||
for (k, apply) in [("align", 0), ("type", 1), ("cap", 2), ("join", 3), ("start", 4), ("end", 5)] {
|
||||
if let Some(v) = p2.get(k).cloned() {
|
||||
let _ = match apply {
|
||||
@@ -425,6 +428,37 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
Ok(json!({"sides": s.prefs.polygon_sides, "starInset": (s.prefs.star_inset * 100.0).round()}))
|
||||
}
|
||||
),
|
||||
cmd!(
|
||||
"object.attributes",
|
||||
"Attributes",
|
||||
["Window", "Output", "Attributes"],
|
||||
None,
|
||||
"{overprintFill?, overprintStroke?, overprintGap?, nonprinting?: bool, ids?} — Attributes panel",
|
||||
has_selection,
|
||||
|s, p| {
|
||||
let b = |k: &str| p.get(k).and_then(Value::as_bool);
|
||||
let (of, os, og, np) = (b("overprintFill"), b("overprintStroke"), b("overprintGap"), b("nonprinting"));
|
||||
set_flag(
|
||||
s,
|
||||
p,
|
||||
move |i| {
|
||||
if let Some(v) = of {
|
||||
i.fill.overprint = v;
|
||||
}
|
||||
if let Some(v) = os {
|
||||
i.stroke.overprint = v;
|
||||
}
|
||||
if let Some(v) = og {
|
||||
i.stroke.gap_overprint = v;
|
||||
}
|
||||
if let Some(v) = np {
|
||||
i.nonprinting = v;
|
||||
}
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
),
|
||||
cmd!(
|
||||
"object.globalLight",
|
||||
"Global Light…",
|
||||
@@ -1655,3 +1689,23 @@ mod transform_values_tests {
|
||||
assert_eq!(info(&mut s, b)["scaleX"], 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod attributes_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn attributes_panel_flags() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({})).unwrap();
|
||||
let a = s.execute("frame.create", &json!({"rect": [10, 10, 50, 50]})).unwrap()["id"].as_u64().unwrap();
|
||||
s.execute("object.attributes", &json!({"overprintFill": true, "overprintGap": true, "nonprinting": true})).unwrap();
|
||||
let it = s.doc().unwrap().doc.item(ItemId(a)).unwrap().clone();
|
||||
assert!(it.fill.overprint && it.stroke.gap_overprint && it.nonprinting && !it.stroke.overprint);
|
||||
// IDML keeps them.
|
||||
let bytes = designcraft_idml::export_idml(&s.doc().unwrap().doc);
|
||||
let back = designcraft_idml::import_idml(&bytes).unwrap();
|
||||
let it = back.spreads.iter().flat_map(|sp| sp.items.iter()).find(|i| i.fill.overprint).expect("overprint fill survives");
|
||||
assert!(it.stroke.gap_overprint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1189,6 +1189,7 @@ impl<'a> Ex<'a> {
|
||||
el.set("GapColor", self.sw(&s.gap_swatch));
|
||||
el.set("GapTint", pct(s.gap_tint as f64));
|
||||
el.set("OverprintStroke", bool_s(s.overprint));
|
||||
el.set("OverprintGap", bool_s(s.gap_overprint));
|
||||
}
|
||||
|
||||
// ---------- spreads ----------
|
||||
|
||||
@@ -1619,6 +1619,7 @@ impl<'r> Importer<'r> {
|
||||
s.gap_tint = tint(Some(t)).unwrap_or(1.0);
|
||||
}
|
||||
s.overprint = g(self, "OverprintStroke").as_deref() == Some("true");
|
||||
s.gap_overprint = g(self, "OverprintGap").as_deref() == Some("true");
|
||||
s
|
||||
}
|
||||
|
||||
|
||||
@@ -549,6 +549,12 @@ impl Renderer {
|
||||
if let Some(g) = gap
|
||||
&& !matches!(st.align, StrokeAlign::Inside | StrokeAlign::Outside if closed)
|
||||
{
|
||||
let op = overprints(f, &st.gap_swatch, st.gap_tint, st.gap_overprint);
|
||||
if op {
|
||||
ctx.set_transform(Affine::IDENTITY);
|
||||
ctx.push_layer(None, Some(BlendMode::new(Mix::Multiply, Compose::SrcOver)), None, None, None);
|
||||
ctx.set_transform(f.view * xf);
|
||||
}
|
||||
ctx.set_paint(color_of(&g, 1.0));
|
||||
ctx.set_stroke(kurbo::Stroke {
|
||||
dash_pattern: Default::default(),
|
||||
@@ -557,6 +563,9 @@ impl Renderer {
|
||||
..stroke.clone()
|
||||
});
|
||||
ctx.stroke_path(bp);
|
||||
if op {
|
||||
ctx.pop_layer();
|
||||
}
|
||||
ctx.set_paint(color_of(&c, 1.0));
|
||||
}
|
||||
match st.align {
|
||||
|
||||
@@ -29,6 +29,7 @@ pub const ICON_PANELS: &[(&str, &str, &str)] = &[
|
||||
("links", "Links", "panel-links"),
|
||||
("info", "Info", "panel-info"),
|
||||
("preflight", "Preflight", "panel-preflight"),
|
||||
("attributes", "Attributes", "panel-attributes"),
|
||||
];
|
||||
|
||||
pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
@@ -163,6 +164,7 @@ pub fn panel_body(app: &mut DesignApp, ui: &mut egui::Ui, id: &str) {
|
||||
"effects" => panels::properties::effects_panel(app, ui),
|
||||
"links" => panels::properties::links_panel(app, ui),
|
||||
"preflight" => panels::properties::preflight_panel(app, ui),
|
||||
"attributes" => panels::properties::attributes_panel(app, ui),
|
||||
"table" => panels::table::show(app, ui),
|
||||
_ => panels::properties::info_panel(app, ui),
|
||||
}
|
||||
|
||||
@@ -310,6 +310,11 @@ pub fn paint(p: &Painter, r: Rect, name: &str, c: Color32) {
|
||||
pen.circle(10.0, 10.0, 7.0);
|
||||
pen.line(&[(6.5, 10.0), (9.0, 12.5), (13.5, 7.5)]);
|
||||
}
|
||||
"panel-attributes" => {
|
||||
// Two overlapping inks: overprint.
|
||||
pen.rect(3.0, 3.0, 13.0, 13.0);
|
||||
pen.rect(7.0, 7.0, 17.0, 17.0);
|
||||
}
|
||||
"panel-text-frame" => {
|
||||
pen.rect(3.0, 4.0, 17.0, 16.0);
|
||||
pen.text(10.0, 10.0, "T", 9.0);
|
||||
|
||||
@@ -462,6 +462,7 @@ pub const MENUS: &[(&str, &[&str])] = &[
|
||||
"cmd:data.merge",
|
||||
"<",
|
||||
">Output",
|
||||
"ui:window.panel|Attributes|{\"panel\": \"attributes\"}",
|
||||
"cmd:preflight.run",
|
||||
"<",
|
||||
"-",
|
||||
|
||||
@@ -1774,3 +1774,28 @@ pub fn preflight_panel(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Window › Output › Attributes: overprint fill, stroke and gap; nonprinting.
|
||||
pub fn attributes_panel(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
let Some(it) = app.session.active().and_then(|d| d.selection.items.first().and_then(|id| d.doc.item(*id)).cloned()) else {
|
||||
ui.label("Select an object.");
|
||||
return;
|
||||
};
|
||||
let rows = [
|
||||
("overprintFill", "Overprint Fill", it.fill.overprint),
|
||||
("overprintStroke", "Overprint Stroke", it.stroke.overprint),
|
||||
("nonprinting", "Nonprinting", it.nonprinting),
|
||||
("overprintGap", "Overprint Gap", it.stroke.gap_overprint),
|
||||
];
|
||||
egui::Grid::new("attributes_panel").num_columns(2).spacing(vec2(16.0, 6.0)).show(ui, |ui| {
|
||||
for (i, (key, label, on)) in rows.into_iter().enumerate() {
|
||||
let mut v = on;
|
||||
if ui.checkbox(&mut v, label).changed() {
|
||||
let _ = app.run("object.attributes", json!({ key: v }));
|
||||
}
|
||||
if i % 2 == 1 {
|
||||
ui.end_row();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+4
-4
@@ -5,12 +5,12 @@ The summary is generated by `cargo xtask parity` from the table at the bottom of
|
||||
Each row of the InDesign feature catalogue is scored **D** (done), **P** (partial: works but lacks options or UI) or **M** (missing), weighted by priority (**P0** core = 3, **P1** = 2, **P2** = 1). Out-of-scope rows (Adobe cloud services, plug-ins, Typekit, Flash) are excluded.
|
||||
|
||||
<!-- SUMMARY -->
|
||||
**Weighted breadth parity: 84%** over 275 features.
|
||||
**Weighted breadth parity: 85%** over 275 features.
|
||||
|
||||
| Priority | Features | Done | Partial | Missing | Parity |
|
||||
|---|---|---|---|---|---|
|
||||
| P0 | 102 | 99 | 3 | 0 | 99% |
|
||||
| P1 | 97 | 71 | 24 | 2 | 86% |
|
||||
| P1 | 97 | 72 | 23 | 2 | 86% |
|
||||
| P2 | 76 | 12 | 13 | 51 | 24% |
|
||||
|
||||
| Area | Parity |
|
||||
@@ -18,7 +18,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
||||
| Application shell & workspace | 80% |
|
||||
| Documents, pages, spreads | 84% |
|
||||
| Layers | 95% |
|
||||
| Frames, shapes & paths | 90% |
|
||||
| Frames, shapes & paths | 91% |
|
||||
| Transform | 100% |
|
||||
| Fill, stroke, colour | 86% |
|
||||
| Effects & transparency | 62% |
|
||||
@@ -128,7 +128,7 @@ Open P0 items: Preferences, Vector/page formats (PDF/AI/SVG/EPS place), Export t
|
||||
| Frames, shapes & paths | Paste Into / Paste in Place | P0 | D |
|
||||
| Frames, shapes & paths | Paste Without Formatting | P0 | D |
|
||||
| Frames, shapes & paths | Non-printing attribute | P0 | D |
|
||||
| Frames, shapes & paths | Overprint fill/stroke/gap | P1 | P |
|
||||
| Frames, shapes & paths | Overprint fill/stroke/gap | P1 | D |
|
||||
| Transform | Transform panel / Control | P0 | D |
|
||||
| Transform | Free Transform tool | P0 | D |
|
||||
| Transform | Rotate, Scale, Shear tools + dialogs | P0 | D |
|
||||
|
||||
Reference in New Issue
Block a user