Mudrit
Guides

Signature Appearance

Everything the visible signature can show — render modes, graphics, layout, colours, fonts, Unicode shaping, borders, fields, labels and templates

Part of mudrit-pdfsign

The Appearance struct controls what a visible signature draws inside its placement box: the render mode, any graphics, the layout, colours, fonts, and the viewer's validity icon. It is built fluently, and its defaults reproduce the classic iText-style text block, so you set only what you want to change.

use mudrit_pdfsign::prelude::*;

let ap = Appearance::new()
    .mode(RenderMode::GraphicAndText)
    .layout(Layout::GraphicLeft)
    .graphic(Image::Png(std::fs::read("logo.png")?));

let cfg = SignConfig::builder()
    .place("F", [350, 60, 560, 160])?
    .appearance(ap)                       // attach the whole Appearance
    .build();

Attach an appearance with .appearance(Appearance). The builder also exposes .trust_icon(bool) as a shortcut for the single most common tweak. The default appearance is DescriptionOnly with the validity icon on — so a bare SignConfig::builder() already gives you the familiar signer text block.

Render modes

RenderMode decides which of the text block and the graphics are drawn, and how.

The default. Only the signer text block — "Digitally Signed by: / <name> / <org> / Date: …". No graphic needed.

Appearance::new() // mode defaults to RenderMode::DescriptionOnly

The trust icon (Acro6 layered validity icon)

trust_icon (default true) builds the iText-style Acro6 layered appearance, so the viewer overlays its own dynamic validity icon on top of your content — a green check when the certificate is trusted, a "?" otherwise. Set it false for a flat, static appearance that never changes.

Appearance::new().trust_icon(false);   // flat appearance, no viewer overlay
// or, as a builder shortcut:
SignConfig::builder().trust_icon(true) /* … */;

The green check depends on the certificate being trusted by the viewer (Adobe AATL, the Windows store, a CCA root, …). The bundled test cert shows "validity unknown" with a "?" — that is expected. Mudrit builds the appearance; the viewer decides the icon. trust_icon is illegal with RenderMode::Invisible (nothing to overlay) and RenderMode::Watermark (flat-only).

Graphics

Up to four images can be drawn (logo, handwritten signature, seal, …); they are auto-arranged — a wide region becomes a row, a tall region becomes a column. RenderMode::Watermark uses only the first. An Image is PNG or JPEG bytes, or a path read at sign time:

Image::Png(bytes)                 // decoded to RGB/Gray (+ alpha /SMask), Flate-compressed
Image::Jpeg(bytes)                // embedded as-is via /DCTDecode (no re-encode): RGB, gray, or CMYK
Image::Path("logo.png".into())    // format auto-detected from the file's magic bytes, read at sign time

Add them with the builder methods:

MethodEffect
.graphic(Image)Append one graphic (up to 4 are drawn).
.graphic_path(path)Append a graphic loaded from a path at sign time (PNG/JPEG auto-detected).
.graphics(iter)Replace the graphics with an iterator of Image.
Appearance::new()
    .mode(RenderMode::GraphicAndText)
    .graphic(Image::Png(logo))
    .graphic_path("handwritten.png");   // a missing/invalid file fails loudly during sign_pdf, not here

Image::Path and .graphic_path (and .font_path) defer file reads to sign_pdf, so a bad path surfaces there rather than at build time. More than 4 graphics is rejected by validate().

Layout and graphic fraction

For RenderMode::GraphicAndText (and the name region of NameAndText), Layout places the graphic relative to the text, and graphic_fraction sets how much of the box the graphic region gets.

LayoutGraphic position
GraphicLeft (default)graphic left, text right — the common enterprise layout
GraphicRightgraphic right, text left
GraphicTopgraphic on top, text below
GraphicBottomgraphic on the bottom, text above
Appearance::new()
    .mode(RenderMode::GraphicAndText)
    .layout(Layout::GraphicRight)
    .graphic_fraction(0.35)             // graphic region = 35% of the box; must be 0.1..=0.9 (default 0.4)
    .graphic(Image::Png(logo));

Watermark opacity

watermark_opacity (default 0.2, range 0.0..=1.0) sets how faded the background graphic is in RenderMode::Watermark. Lower is subtler.

Appearance::new()
    .mode(RenderMode::Watermark)
    .trust_icon(false)
    .watermark_opacity(0.12)
    .graphic(Image::Png(seal));

Colours

OptionEffectDefault
.text_color([r, g, b])Text colour, RGB in 0.0..=1.0black [0, 0, 0]
.background([r, g, b])Optional background fill, RGB in 0.0..=1.0None (transparent)
Appearance::new()
    .text_color([0.10, 0.20, 0.45])   // deep blue text
    .background([0.96, 0.97, 1.0]);   // pale panel behind it

Font size and line spacing

OptionEffectDefault
.font_size(points)Explicit font size, disabling auto-fitNone → 9.94pt with auto-shrink to fit
.line_spacing(factor)Leading, as a multiple of the font size (must be > 0)1.2

Leaving font_size unset lets Mudrit auto-shrink the text to fit the box; setting it pins the size. line_spacing 1.0 packs lines tight (baselines one em apart); 1.2 leaves a little air. Both apply to the std-14 and embedded-font paths.

Appearance::new()
    .font_size(8.0)        // pin the size (no auto-shrink)
    .line_spacing(1.15);

Fonts — std-14 vs. embedded Unicode

By default the text is drawn with a PDF standard-14 font: no font program is embedded, it renders in every viewer, but it covers only WinAnsi (Latin-1). Choose the family with .std_font(Std14):

Appearance::new().std_font(Std14::TimesBold);

The Std14 families are the three Adobe base faces in their four styles: Helvetica (default) / HelveticaBold / HelveticaOblique / HelveticaBoldOblique; Times / TimesBold / TimesItalic / TimesBoldItalic; Courier / CourierBold / CourierOblique / CourierBoldOblique.

For Unicode text — Devanagari, CJK, Arabic, and other complex scripts — embed a TrueType/OpenType font. Mudrit renders it as a Type0 / Identity-H font with full HarfBuzz shaping (conjuncts, joining forms, marks). An embedded font overrides std_font.

Appearance::new().font(std::fs::read("NotoSansDevanagari.ttf")?);   // bytes in memory
Appearance::new().font_path("fonts/NotoSansDevanagari.ttf");        // read at sign time

Complex scripts require an embedded font

The std-14 fallback (WinAnsi, no GSUB/GPOS) cannot map or shape Hebrew, Arabic, the Indic block (Devanagari … Malayalam, Sinhala), Thai/Lao, Tibetan, Myanmar, or Khmer. When such characters are actually present, an embedded font is mandatory — otherwise the sign fails loudly. (A plain-Latin string in any script direction is fine on std-14.)

Text direction (bidi / RTL)

Direction sets the base paragraph direction (Ltr default). Direction::Rtl (or the .rtl() shorthand) bidi-reorders the text right-to-left and right-aligns it, per the Unicode bidi algorithm — inherently RTL runs (Arabic, Hebrew) flow RTL, while embedded LTR runs (Latin, digits, Devanagari) keep their natural order. Pair RTL with an embedded font so the non-Latin glyphs are shaped.

Appearance::new()
    .font(std::fs::read("NotoNaskhArabic.ttf")?)
    .rtl();                     // == .direction(Direction::Rtl)

Word wrapping

wrap (default true) wraps the signer text to the box width — long values break onto multiple lines (word-wrap; an over-wide token character-wraps). Set false for the classic one-line-per-field auto-shrink behaviour.

Appearance::new().wrap(false);   // one line per field, auto-shrunk to fit

Border

Stroke a Border around the signature box (drawn inside the signed appearance, so it renders in every viewer). Build it solid or dashed; the width must be > 0.

Appearance::new().border(Border::solid([0.0, 0.0, 0.0], 1.0));   // 1pt black solid
Appearance::new().border(Border::dashed([0.2, 0.2, 0.2], 0.75)); // 0.75pt grey dashed
ConstructorBorder
Border::solid([r, g, b], width)Solid stroke, RGB 0.0..=1.0, width in points
Border::dashed([r, g, b], width)Dashed stroke, same parameters

Fields, labels, and templates

Three layers control which text appears and in what order, from most structured to most free-form:

fields — pick and order the signer fields

SigField chooses which fields show and in what order; empty values are skipped (e.g. an absent organisation). None (the default) draws the classic block; Some(list) drives it explicitly.

Appearance::new().fields([
    SigField::Name, SigField::Org, SigField::Reason, SigField::Location, SigField::Date,
]);

The variants are Name, Org, Date, Reason, Location, Contact. Name uses the certificate CN (or the .name(...) override); the others come from the signing metadata.

labels — localize the field prefixes

Labels sets the prefix for each field, so the block translates. Override any; the rest stay English.

Appearance::new()
    .fields([SigField::Name, SigField::Date])
    .labels(Labels::default()
        .signed_by("Hastakshar:")     // was "Digitally Signed by:"
        .date("Dinank:"));            // was "Date:"

Defaults: signed_by = "Digitally Signed by:", org = "Org:", date = "Date:", reason = "Reason:", location = "Location:", contact = "Contact:". Set org to "" for the bare organization value with no prefix.

template — a free-form layout

template overrides fields entirely: newlines split lines, and the placeholders {name}, {org}, {date}, {reason}, {location}, {contact} interpolate.

Appearance::new().template("Signed by {name}\n{org}\n{date} · {location}");

Precedence: template (if set) wins over fields, which wins over the default block. A template that uses a complex/RTL script is validated at build time — it errors unless you also set an embedded font.

Invisible signatures

For a legally-signed document with no visible mark, use the Appearance::invisible() constructor — it sets RenderMode::Invisible and turns the trust icon off for you.

let cfg = SignConfig::builder()
    .appearance(Appearance::invisible())
    .build();

An invisible signature still carries the full CMS, timestamp, LTV, etc. — only the on-page widget is omitted. Placements are irrelevant to it.

Validation

Appearance::validate() rejects impossible combinations early (they are re-checked at render time too), returning Error::InvalidInput:

RuleRequirement
Graphics countat most 4 graphics
graphic_fractionwithin 0.1..=0.9
watermark_opacitywithin 0.0..=1.0
font_sizea positive, finite number of points (when set)
line_spacinga positive, finite multiple
border.widthpositive and finite
template on std-14no complex/RTL script chars unless an embedded font is set
Invisible modetrust_icon must be off and no graphics
Watermark modetrust_icon must be off and exactly one (the first) graphic present
GraphicOnly / GraphicAndTextat least one graphic

Full option reference

Prop

Type

Next

On this page