collective

Browse / github.com/storytold/designcraft / patch

Export form field borders from the item stroke

Form fields export with a stroke Designcraft 0.10.0; windows. Creating a fillable .pdf - even though the field boxes are 0-width stroke with transparent fill and stroke <img width="263" height="318" alt="Image" src="https://github.com/user-attachments/assets/23a93e73-0d6e-475d-afbb-a92dc9c5519c" /> ... the created .pdf has a black box around every fillable field. <img width="184" height="17" alt="Image" src="https://github.com/user-attachments/assets/e3f1cf26-d483-42bf-92e2-25d567a22fc0" />

Status
open
Score
0.00
Adopters
0 other operators
Author
Silvertree2010 (imported from pull request #1919, open)
Base
50650f74d7328745cfcdd16098313a9fc31b23d5
Size
+40 −6 in 2 files
Review
pending
Shared
2026-10-28
Id
0e957b7fcbdf8ff8b512bf92

How it went

No other operator has reported using it yet.

Diff

raw · crates/pdf/src/forms.rs crates/pdf/src/tests.rs

diff --git a/crates/pdf/src/forms.rs b/crates/pdf/src/forms.rs--- a/crates/pdf/src/forms.rs+++ b/crates/pdf/src/forms.rs@@ -10,8 +10,9 @@ use designcraft_doc::{Document, FieldKind, FormField, Item, MediaOptions}; use crate::export::Sheet; use crate::transitions::{bracketed, int_after, object, rfind}; -/// A field placed on an output page: (page index, PDF rectangle, field).-pub(crate) type Placed = (usize, [f64; 4], FormField);+/// A field placed on an output page: (page index, PDF rectangle, field, border width, 0 when the+/// item has no stroke).+pub(crate) type Placed = (usize, [f64; 4], FormField, f64);  /// A placed video or sound on an output page: (page index, PDF rectangle, file name, MIME type, /// bytes, options).@@ -37,7 +38,8 @@ pub(crate) fn collect(doc: &Document, sheets: &[Sheet]) -> Vec<Placed> {     let mut out = Vec::new();     visit(doc, sheets, |pi, r, it| {         if let Some(f) = &it.form_field {-            out.push((pi, r, f.clone()));+            let bw = if it.stroke.is_none() || !it.stroke.weight.is_finite() { 0.0 } else { it.stroke.weight };+            out.push((pi, r, f.clone(), bw));         }     });     out@@ -158,13 +160,14 @@ pub(crate) fn add_fields(pdf: &[u8], fields: &[Placed], media: &[PlacedMedia]) -         put(&mut out, font, b"<</Type/Font/Subtype/Type1/BaseFont/Helvetica/Encoding/WinAnsiEncoding>>");     }     let mut all = Vec::new();-    for (page, r, f) in fields {+    for (page, r, f, bw) in fields {         let Some(&page_ref) = kids.get(*page) else { continue };         let rect = format!("[{:.2} {:.2} {:.2} {:.2}]", r[0], r[1], r[2], r[3]);         let (w, h) = (r[2] - r[0], r[3] - r[1]);         let fs = if f.font_size > 0.0 { f.font_size } else { 0.0 };+        let (bc, bs) = if *bw > 0.0 { ("/BC[0.45 0.45 0.45]", format!("/BS<</W {bw:.2}>>")) } else { ("", "/BS<</W 0>>".to_string()) };         let common = format!(-            "/Type/Annot/Subtype/Widget/T{}/Rect{rect}/F 4/P {page_ref} 0 R/MK<</BC[0.45 0.45 0.45]/BG[1 1 1]>>/DA(/Helv {fs:.1} Tf 0 g)",+            "/Type/Annot/Subtype/Widget/T{}/Rect{rect}/F 4/P {page_ref} 0 R/MK<<{bc}/BG[1 1 1]>>{bs}/DA(/Helv {fs:.1} Tf 0 g)",             pdf_string(&f.name)         );         let req = if f.required { 2 } else { 0 };@@ -175,7 +178,8 @@ pub(crate) fn add_fields(pdf: &[u8], fields: &[Placed], media: &[PlacedMedia]) -             }             FieldKind::CheckBox => {                 // Appearances: an empty box, and a box with a check mark.-                let border = format!("0.45 G 1 w 0.5 0.5 {:.2} {:.2} re S", (w - 1.0).max(0.0), (h - 1.0).max(0.0));+                let border =+                    if *bw > 0.0 { format!("0.45 G 1 w 0.5 0.5 {:.2} {:.2} re S", (w - 1.0).max(0.0), (h - 1.0).max(0.0)) } else { String::new() };                 let check = format!(                     "{border} 0 g {:.2} w 1 J 1 j {:.2} {:.2} m {:.2} {:.2} l {:.2} {:.2} l S",                     (w.min(h) * 0.12).max(0.8),diff --git a/crates/pdf/src/tests.rs b/crates/pdf/src/tests.rs--- a/crates/pdf/src/tests.rs+++ b/crates/pdf/src/tests.rs@@ -791,6 +791,36 @@ fn only_printed_fields_and_buttons_export() {     assert_eq!(all.matches("/Link").count(), shown.matches("/Link").count(), "only the shown button"); } +#[test]+fn form_field_border_follows_the_item_stroke() {+    use designcraft_doc::{FieldKind, FormField, Shape, Stroke};+    use designcraft_geom::shapes::rectangle;+    let export = |stroke: Stroke| {+        let mut d = Document::new(&NewDocument::default());+        let lid = d.default_layer();+        let mut it = Item::new(ItemId(d.alloc()), lid, Shape::Rectangle, rectangle(Rect::new(72.0, 72.0, 250.0, 96.0)));+        it.stroke = stroke;+        it.form_field = Some(FormField {+            kind: FieldKind::TextField,+            name: "name".into(),+            value: String::new(),+            options: Vec::new(),+            multiline: false,+            required: false,+            font_size: 0.0,+        });+        d.insert_item(SpreadRef::Doc(0), it, None).unwrap();+        uncompressed(&d, PdfOptions::default())+    };+    let none = export(Stroke { weight: 0.0, swatch: designcraft_color::swatch::NONE.into(), ..Stroke::default() });+    assert!(none.contains("/Subtype/Widget"));+    assert!(none.contains("/BS<</W 0>>"), "no border width");+    assert!(!none.contains("/BC"), "no border colour");+    let stroked = export(Stroke { weight: 1.0, ..Stroke::default() });+    assert!(stroked.contains("/BS<</W 1"), "border width");+    assert!(stroked.contains("/BC"), "border colour");+}+ #[test] fn extreme_page_selection_is_a_page_error_without_overflow() {     let doc = doc_with_text("Bounded page selection");