Class: Acrofill::Appearance

Inherits:
Object
  • Object
show all
Defined in:
lib/acrofill/appearance.rb

Overview

Builds the /AP /N appearance stream (a Form XObject) for a filled text-field widget, honouring the field's /DA string and /Q alignment.

Constant Summary collapse

PADDING =
2.0
ASCENT =

Helvetica cap-height-ish ascent, em fractions

0.718
DESCENT =
0.207
COLOR_OP_ARITY =

Colour-setting operators allowed in a /DA string, and their operand counts.

{ 'g' => 1, 'rg' => 3, 'k' => 4 }.freeze

Instance Method Summary collapse

Constructor Details

#initialize(doc, acroform) ⇒ Appearance

Returns a new instance of Appearance.



13
14
15
16
# File 'lib/acrofill/appearance.rb', line 13

def initialize(doc, acroform)
  @doc = doc
  @acroform = acroform
end

Instance Method Details

#build(field_node, widget, value, multiline: false) ⇒ Object

Returns a Reference to the new appearance XObject, or nil when the widget geometry is unusable.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/acrofill/appearance.rb', line 20

def build(field_node, widget, value, multiline: false)
  rect = normalized_rect(widget[:Rect] || @doc.inherited_value(field_node, :Rect))
  return nil unless rect

  width = rect[2] - rect[0]
  height = rect[3] - rect[1]
  return nil if width <= 0 || height <= 0

  font_name, size, color_ops = parse_da(field_node)
  base_font = base_font_for(font_name)
  align = alignment(field_node)

  body =
    if multiline
      size = 12.0 if size <= 0
      size = size.clamp(2.0, 144.0)
      multiline_body(value, base_font, size, width, height, align)
    else
      text = printable_text(value)
      size = [height * 0.66, 12.0].min if size.zero?
      size = shrink_to_fit(text, base_font, size, width)
      # Vertically center the ascent box, matching pdftk's baseline
      # placement exactly: ty = (h - ascent*size) / 2.
      ty = [(height - (size * ASCENT)) / 2.0, size * DESCENT].max
      "#{fmt(line_x(text, base_font, size, width, align))} #{fmt(ty)} Td\n" \
        "(#{escape_literal(text)}) Tj\n"
    end

  content = +"/Tx BMC\nq\nBT\n"
  content << "#{color_ops}\n" unless color_ops.empty?
  content << "/#{font_name} #{fmt(size)} Tf\n"
  content << body
  content << "ET\nQ\nEMC\n"

  dict = {
    Type: :XObject,
    Subtype: :Form,
    FormType: 1,
    BBox: [0, 0, width, height],
    Resources: { Font: { font_name.to_sym => font_ref(font_name) } }
  }
  @doc.add(StreamObject.new(dict, content.b))
end