mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-30 13:16:56 -06:00
Merge pull request #1919 from Silvertree2010/fix/1881-form-fields-export-with-a-stroke
Export form field borders from the item stroke
This commit is contained in:
+19
-6
@@ -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
|
||||
@@ -96,6 +98,16 @@ fn pdf_string(s: &str) -> String {
|
||||
o
|
||||
}
|
||||
|
||||
/// A checkbox appearance's border: the item's stroke weight, inset by half of it so it stays
|
||||
/// inside the box; nothing for an unstroked item.
|
||||
fn checkbox_border(bw: f64, w: f64, h: f64) -> String {
|
||||
if bw > 0.0 {
|
||||
format!("0.45 G {bw:.2} w {i:.2} {i:.2} {:.2} {:.2} re S", (w - bw).max(0.0), (h - bw).max(0.0), i = bw / 2.0)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Add `fields` and `media` to `pdf` (page indices in output order). `None` when the file's
|
||||
/// structure isn't the simple kind this understands.
|
||||
pub(crate) fn add_fields(pdf: &[u8], fields: &[Placed], media: &[PlacedMedia]) -> Option<Vec<u8>> {
|
||||
@@ -177,13 +189,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 };
|
||||
@@ -194,7 +207,7 @@ 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 = checkbox_border(*bw, w, h);
|
||||
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),
|
||||
|
||||
@@ -798,6 +798,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");
|
||||
|
||||
Reference in New Issue
Block a user