Module: Pdfrb::Task::RegenerateAppearances

Defined in:
lib/pdfrb/task/regenerate_appearances.rb

Overview

Walks every widget annotation in the document and regenerates the /AP /N appearance stream via Appearance::Generator. Use after mutating field values directly (e.g. via Form#set_value) or to repair documents with /NeedAppearances set so viewers don't have to synthesise appearances on the fly.

Per ISO 32000-2 ยง12.5.5, an annotation's /AP /N is the normal appearance; PDF/A requires it for every widget. This task also clears /NeedAppearances on /AcroForm since by definition all fields now have a computed appearance.

Class Method Summary collapse

Class Method Details

.call(document, only: nil) ⇒ Integer

Returns count of appearances regenerated.

Parameters:

  • document (Pdfrb::Document)
  • only: (Array<Symbol>, nil) (defaults to: nil)

    limit to the given FT values (e.g. [:Tx, :Btn]). Default: all field types.

Returns:

  • (Integer)

    count of appearances regenerated.



22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/pdfrb/task/regenerate_appearances.rb', line 22

def call(document, only: nil)
  generator = Pdfrb::Appearance::Generator.new(document)
  widgets = each_widget(document).to_a
  count = 0
  widgets.each do |widget|
    next unless matching?(widget, only)

    regenerated = regenerate(generator, widget)
    count += 1 if regenerated
  end
  clear_need_appearances(document)
  count
end

.clear_need_appearances(document) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/pdfrb/task/regenerate_appearances.rb', line 83

def clear_need_appearances(document)
  catalog = document.catalog
  return unless catalog

  acroform = catalog.value[:AcroForm]
  return unless acroform

  if acroform.is_a?(Pdfrb::Model::Cos::Dictionary)
    acroform.value.delete(:NeedAppearances)
  elsif acroform.is_a?(::Hash)
    acroform.delete(:NeedAppearances)
  end
end

.each_widget(document) ⇒ Object



36
37
38
39
40
41
42
43
44
45
# File 'lib/pdfrb/task/regenerate_appearances.rb', line 36

def each_widget(document)
  return enum_for(:each_widget, document) unless block_given?

  document.each_indirect_object do |obj|
    next unless obj.value.is_a?(::Hash)

    subtype = obj.value[:Subtype]
    yield obj if subtype == :Widget
  end
end

.matching?(widget, only) ⇒ Boolean

Returns:

  • (Boolean)


47
48
49
50
51
52
# File 'lib/pdfrb/task/regenerate_appearances.rb', line 47

def matching?(widget, only)
  return true if only.nil?

  ft = widget.value[:FT]
  ft && only.include?(ft.to_sym)
end

.regenerate(generator, widget) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/pdfrb/task/regenerate_appearances.rb', line 54

def regenerate(generator, widget)
  ft = widget.value[:FT]&.to_sym
  value = widget.value[:V]
  case ft
  when :Tx
    generator.text_field(widget, value: value.to_s)
    true
  when :Btn
    if value.is_a?(::Hash)
      # Multi-state radio; regenerate each state's appearance.
      value.each_key do |state|
        generator.checkbox(widget, checked: state != :Off)
      end
    else
      generator.checkbox(widget, checked: [:Yes, true].include?(value))
    end
    true
  when :Ch
    generator.combo(widget, value: value.to_s)
    true
  else
    false
  end
rescue StandardError
  # Appearance generation is best-effort; if a single field
  # raises, skip it rather than aborting the whole sweep.
  false
end