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
@@ -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),@@ -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");