mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
M5: a push button's caption uses the font, size and color of its /DA
Button appearances always drew the caption in regular Helvetica, sized from the button's height, whatever the field's /DA asked for: DA Form 31's 'Attachments Menu' (9 pt bold) came out at 4 pt. The caption now takes the /DA's font from the form's /DR, its size (an automatic size still fits the button) and its color, as text fields already do.
This commit is contained in:
@@ -220,6 +220,26 @@ fn dr_font(doc: &Document, name: &str) -> Option<(Object, FontText)> {
|
||||
Some((entry, FontText::Simple(Box::new(Metrics::from_dict(doc, fd)))))
|
||||
}
|
||||
|
||||
/// A push button's caption, centered in a `width` × `height` box, in the font, size and color its
|
||||
/// `/DA` asks for: the font from the form's `/DR`, else Helvetica, and the size `auto` when the
|
||||
/// `/DA` leaves it to the viewer (0). Returns the content operators, the font resource (name and
|
||||
/// object), and the first character drawn as `?` because the font can't show it.
|
||||
pub(crate) fn button_caption(doc: &Document, da: &str, caption: &str, width: f64, height: f64, auto: f64) -> (Vec<u8>, String, Object, Option<char>) {
|
||||
let da = parse_da(da);
|
||||
let (name, font, enc) = match dr_font(doc, &da.font).filter(|(_, enc)| enc.can_encode(caption)) {
|
||||
Some((o, enc)) => (da.font.clone(), o, enc),
|
||||
None => ("Helv".to_string(), helvetica(), FontText::WinAnsi),
|
||||
};
|
||||
let missing = if enc == FontText::WinAnsi { pdfcraft_fonts::first_undrawable(caption) } else { None };
|
||||
let size = if da.size > 0.0 { da.size } else { auto };
|
||||
let tw = enc.width(caption, size);
|
||||
let mut out =
|
||||
format!("BT /{name} {size:.2} Tf {} 1 0 0 1 {:.3} {:.3} Tm ", da.color, (width - tw) / 2.0, (height - size * 0.7) / 2.0).into_bytes();
|
||||
out.extend(literal(&enc.encode(caption)));
|
||||
out.extend_from_slice(b" Tj ET\n");
|
||||
(out, name, font, missing)
|
||||
}
|
||||
|
||||
fn helvetica() -> Object {
|
||||
let mut f = Dict::new();
|
||||
f.set(b"Type".to_vec(), Object::name("Font"));
|
||||
|
||||
+12
-17
@@ -7,7 +7,6 @@
|
||||
//! immediately so every viewer shows the empty field.
|
||||
|
||||
use pdfcraft_cos::{Dict, Document, ObjRef, Object, PdfString, Stream};
|
||||
use pdfcraft_fonts::{helvetica_width, literal, win_ansi};
|
||||
|
||||
use crate::{Field, FieldKind, FormError, Widget, appearance, fields, flags, page_refs};
|
||||
|
||||
@@ -706,7 +705,7 @@ fn redraw_widgets(doc: &mut Document, name: &str, only: Option<usize>) -> Result
|
||||
appearance::check_box_states(doc, w, f.kind, &on)
|
||||
}
|
||||
FieldKind::PushButton => {
|
||||
let (s, m) = button_appearance(doc, w);
|
||||
let (s, m) = button_appearance(doc, w, &f.da);
|
||||
missing = m;
|
||||
let r = doc.add(Object::Stream(s));
|
||||
let mut d = Dict::new();
|
||||
@@ -767,7 +766,9 @@ fn form_stream(place: crate::appearance::Placement, content: Vec<u8>, resources:
|
||||
/// A push button: background, border, its icon (`/MK /I`, scaled to fit and centred) and its
|
||||
/// centred caption (unless the layout is icon only, `/TP 1`). The caption is drawn in Helvetica,
|
||||
/// with the first character it can't draw (drawn as `?`), for the caller to mark and report.
|
||||
fn button_appearance(doc: &Document, w: &Widget) -> (Stream, Option<char>) {
|
||||
/// A push button's appearance: its icon and caption, the caption in the font its `/DA` asks for
|
||||
/// (the widget's, else `field_da`, the field's own or inherited one).
|
||||
fn button_appearance(doc: &Document, w: &Widget, field_da: &str) -> (Stream, Option<char>) {
|
||||
let (mut c, place) = frame_only(doc, w);
|
||||
let (width, height) = (place.layout_w, place.layout_h);
|
||||
let wobj = doc.get(w.obj);
|
||||
@@ -792,21 +793,15 @@ fn button_appearance(doc: &Document, w: &Widget) -> (Stream, Option<char>) {
|
||||
}
|
||||
}
|
||||
let mut missing = None;
|
||||
if !icon_only && !caption.is_empty() {
|
||||
missing = pdfcraft_fonts::first_undrawable(&caption);
|
||||
let size = ((height - 4.0) * 0.6).clamp(4.0, 14.0);
|
||||
let tw = helvetica_width(&caption, size);
|
||||
content.extend(format!("BT /Helv {size:.2} Tf 0 g 1 0 0 1 {:.3} {:.3} Tm ", (width - tw) / 2.0, (height - size * 0.7) / 2.0).bytes());
|
||||
content.extend(literal(&win_ansi(&caption)));
|
||||
content.extend_from_slice(b" Tj ET\n");
|
||||
}
|
||||
let mut font = Dict::new();
|
||||
font.set(b"Type".to_vec(), Object::name("Font"));
|
||||
font.set(b"Subtype".to_vec(), Object::name("Type1"));
|
||||
font.set(b"BaseFont".to_vec(), Object::name("Helvetica"));
|
||||
font.set(b"Encoding".to_vec(), Object::name("WinAnsiEncoding"));
|
||||
let mut fonts = Dict::new();
|
||||
fonts.set(b"Helv".to_vec(), Object::Dict(font));
|
||||
if !icon_only && !caption.is_empty() {
|
||||
let da = wobj.as_dict().and_then(|d| d.get(b"DA")).and_then(|o| doc.resolve(o).as_string().map(|s| s.to_text()));
|
||||
let auto = ((height - 4.0) * 0.6).clamp(4.0, 14.0);
|
||||
let (ops, name, font, m) = appearance::button_caption(doc, da.as_deref().unwrap_or(field_da), &caption, width, height, auto);
|
||||
content.extend(ops);
|
||||
fonts.set(name.into_bytes(), font);
|
||||
missing = m;
|
||||
}
|
||||
let mut res = Dict::new();
|
||||
res.set(b"Font".to_vec(), Object::Dict(fonts));
|
||||
if !xobjects.is_empty() {
|
||||
|
||||
@@ -87,6 +87,30 @@ fn ap(doc: &Document, w: &Widget) -> String {
|
||||
String::from_utf8_lossy(&s.decoded().unwrap()).into_owned()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_button_caption_is_drawn_in_its_fields_font_size_and_color() {
|
||||
let objs: Vec<String> = vec![
|
||||
"<< /Type /Catalog /Pages 2 0 R /AcroForm 4 0 R >>".into(),
|
||||
"<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 600 800] >>".into(),
|
||||
"<< /Type /Page /Parent 2 0 R /Annots [7 0 R 8 0 R] >>".into(),
|
||||
"<< /Fields [7 0 R 8 0 R] /DA (/Helv 0 Tf 0 g) /DR << /Font << /Helv 5 0 R /HeBo 6 0 R >> >> >>".into(),
|
||||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>".into(),
|
||||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>".into(),
|
||||
// Its /DA asks for 9pt Helvetica Bold in blue.
|
||||
"<< /Type /Annot /Subtype /Widget /FT /Btn /T (menu) /Ff 65536 /DA (/HeBo 9 Tf 0 0 1 rg) /MK << /CA (Menu) >> /Rect [50 700 140 711] /P 3 0 R >>".into(),
|
||||
// An automatic size (0): sized to the button, as before.
|
||||
"<< /Type /Annot /Subtype /Widget /FT /Btn /T (auto) /Ff 65536 /MK << /CA (Auto) >> /Rect [50 600 140 630] /P 3 0 R >>".into(),
|
||||
];
|
||||
let mut doc = document(&objs);
|
||||
redraw_field(&mut doc, "menu").unwrap();
|
||||
redraw_field(&mut doc, "auto").unwrap();
|
||||
let all = fields(&doc);
|
||||
let menu = ap(&doc, &field(&all, "menu").widgets[0]);
|
||||
assert!(menu.contains("/HeBo 9.00 Tf") && menu.contains("0 0 1 rg"), "{menu}");
|
||||
let auto = ap(&doc, &field(&all, "auto").widgets[0]);
|
||||
assert!(auto.contains("/Helv 14.00 Tf") && auto.contains(" 0 g"), "{auto}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn button_caption_preserves_other_widgets_and_shared_dictionaries() {
|
||||
for indirect_caption in [false, true] {
|
||||
|
||||
Reference in New Issue
Block a user