Class: Pdfrb::Document

Inherits:
Object
  • Object
show all
Defined in:
lib/pdfrb/document.rb,
lib/pdfrb/document/form.rb,
lib/pdfrb/document/info.rb,
lib/pdfrb/document/files.rb,
lib/pdfrb/document/fonts.rb,
lib/pdfrb/document/pages.rb,
lib/pdfrb/document/colors.rb,
lib/pdfrb/document/images.rb,
lib/pdfrb/document/layers.rb,
lib/pdfrb/document/stamps.rb,
lib/pdfrb/document/display.rb,
lib/pdfrb/document/outline.rb,
lib/pdfrb/document/metadata.rb,
lib/pdfrb/document/shadings.rb,
lib/pdfrb/document/portfolio.rb,
lib/pdfrb/document/structure.rb,
lib/pdfrb/document/annotations.rb,
lib/pdfrb/document/page_labels.rb,
lib/pdfrb/document/destinations.rb,
lib/pdfrb/document/form_xobject.rb,
lib/pdfrb/document/graphics_state.rb,
lib/pdfrb/document/output_intents.rb,
lib/pdfrb/document/associated_files.rb

Overview

Top-level PDF document facade. Owns:

* The IO it was read from (or nil for in-memory).
* An oid -> Object table for new/modified objects.
* A wrap() pipeline that upgrades raw Hashes to typed
Dictionary subclasses based on /Type or an explicit type.
* On read: an XrefSection + ObjectReader for lazy resolution.
* A revisions stack (TODO 30 — incremental updates).

Defined Under Namespace

Classes: Annotations, AssociatedFiles, Colors, Destinations, Display, Files, Fonts, Form, FormXObject, GraphicsState, Images, Info, Layers, Metadata, Outline, OutlineEntry, OutputIntents, PageLabels, Pages, Portfolio, Shadings, Stamps, Structure

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io: nil, config: {}) ⇒ Document

Returns a new instance of Document.



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/pdfrb/document.rb', line 35

def initialize(io: nil, config: {})
  Pdfrb::Model::Type.eager_load!
  @config = Configuration.new(config)
  @io = io
  @objects = {}        # oid -> Pdfrb::Model::Object (modified or new)
  @next_oid = 1
  @listeners = {}      # message_name -> [Proc]
  @xref = nil
  @object_reader = nil
  @version = "1.4"
  @empty_trailer = nil

  if io
    read_from_io(io)
  else
    seed_empty_structure
  end
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



54
55
56
# File 'lib/pdfrb/document.rb', line 54

def config
  @config
end

#ioObject (readonly)

Returns the value of attribute io.



54
55
56
# File 'lib/pdfrb/document.rb', line 54

def io
  @io
end

#next_oidObject (readonly)

Returns the value of attribute next_oid.



54
55
56
# File 'lib/pdfrb/document.rb', line 54

def next_oid
  @next_oid
end

#versionObject

Returns the value of attribute version.



54
55
56
# File 'lib/pdfrb/document.rb', line 54

def version
  @version
end

#xrefObject (readonly)

Returns the value of attribute xref.



54
55
56
# File 'lib/pdfrb/document.rb', line 54

def xref
  @xref
end

Class Method Details

.open(path, **opts) ⇒ Object



56
57
58
59
60
61
62
63
64
# File 'lib/pdfrb/document.rb', line 56

def self.open(path, **opts)
  if block_given?
    File.open(path, "rb") { |f| yield new(io: f, **opts) }
  else
    bytes = File.binread(path)
    require "stringio"
    new(io: StringIO.new(bytes), **opts)
  end
end

Instance Method Details

#add(value, type: nil) ⇒ Object



76
77
78
79
80
81
# File 'lib/pdfrb/document.rb', line 76

def add(value, type: nil)
  oid = allocate_oid
  obj = wrap(value, type: type, oid: oid, gen: 0)
  register(obj)
  obj
end

#annotationsObject



135
136
137
# File 'lib/pdfrb/document.rb', line 135

def annotations
  @annotations ||= Document::Annotations.new(self)
end

#associated_filesObject



155
# File 'lib/pdfrb/document.rb', line 155

def associated_files; @associated_files ||= Document::AssociatedFiles.new(self); end

#catalogObject



227
228
229
230
231
232
233
234
# File 'lib/pdfrb/document.rb', line 227

def catalog
  return @catalog if defined?(@catalog) && @catalog

  ref = trailer ? trailer[:Root] : nil
  return nil unless ref

  @catalog = object(ref)
end

#colorsObject



149
# File 'lib/pdfrb/document.rb', line 149

def colors; @colors ||= Document::Colors.new(self); end

#create_form_xobject(name: nil, bbox: nil, matrix: nil) ⇒ Object



190
191
192
# File 'lib/pdfrb/document.rb', line 190

def create_form_xobject(name: nil, bbox: nil, matrix: nil)
  FormXObject.new(self, name: name, bbox: bbox, matrix: matrix)
end

#destinationsObject



131
132
133
# File 'lib/pdfrb/document.rb', line 131

def destinations
  @destinations ||= Document::Destinations.new(self)
end

#dispatch_message(message, *args) ⇒ Object



101
102
103
104
105
# File 'lib/pdfrb/document.rb', line 101

def dispatch_message(message, *args)
  return unless @listeners.key?(message)

  @listeners[message].each { |blk| blk.call(*args) }
end

#displayObject



167
# File 'lib/pdfrb/document.rb', line 167

def display; @display ||= Document::Display.new(self); end

#each_indirect_objectObject

Yield every indirect object in this document: modified/new ones first (they shadow loaded ones), then every loaded entry the xref knows about.



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/pdfrb/document.rb', line 239

def each_indirect_object
  return enum_for(:each_indirect_object) unless block_given?

  seen = Set.new
  @objects.each_value do |obj|
    next unless obj.indirect?

    seen << obj.oid
    yield obj
  end
  return if @xref.nil?

  @xref.entries.each_key do |oid|
    next if seen.include?(oid)
    next if oid.zero?

    obj = object(Pdfrb::Model::Reference.new(oid, 0))
    next if obj.nil? || !obj.indirect?

    yield obj
  end
end

#filesObject



123
124
125
# File 'lib/pdfrb/document.rb', line 123

def files
  @files ||= Document::Files.new(self)
end

#fontsObject



115
116
117
# File 'lib/pdfrb/document.rb', line 115

def fonts
  @fonts ||= Document::Fonts.new(self)
end

#forget(oid) ⇒ Object

Drop a modified/new object from the in-memory table so it's no longer emitted by the writer. Used by Task::Optimize when a stream has been deduplicated against a canonical sibling. Returns the dropped object (or nil if not present).



205
206
207
# File 'lib/pdfrb/document.rb', line 205

def forget(oid)
  @objects.delete(oid)
end

#formObject



145
# File 'lib/pdfrb/document.rb', line 145

def form; @form ||= Document::Form.new(self); end

#graphics_stateObject



151
# File 'lib/pdfrb/document.rb', line 151

def graphics_state; @graphics_state ||= Document::GraphicsState.new(self); end

#imagesObject



119
120
121
# File 'lib/pdfrb/document.rb', line 119

def images
  @images ||= Document::Images.new(self)
end

#infoObject



165
# File 'lib/pdfrb/document.rb', line 165

def info; @info ||= Document::Info.new(self); end

#layersObject



153
# File 'lib/pdfrb/document.rb', line 153

def layers; @layers ||= Document::Layers.new(self); end

#load_xmp_packetObject



182
183
184
185
186
# File 'lib/pdfrb/document.rb', line 182

def load_xmp_packet
  Pdfrb::XMP::Packet.new
rescue LoadError
  nil
end

#metadataObject



127
128
129
# File 'lib/pdfrb/document.rb', line 127

def 
  @metadata ||= Document::Metadata.new(self)
end

#object(reference) ⇒ Object Also known as: dereference

Resolve a Reference to its Object. New/modified objects take precedence over xref-loaded ones; otherwise consult the ObjectReader (which caches per oid).



86
87
88
89
90
91
92
93
94
# File 'lib/pdfrb/document.rb', line 86

def object(reference)
  return reference unless reference.is_a?(Pdfrb::Model::Reference)

  modified = @objects[reference.oid]
  return modified if modified
  return nil if @object_reader.nil?

  @object_reader.load_oid(reference.oid)
end

#outlineObject



139
140
141
# File 'lib/pdfrb/document.rb', line 139

def outline
  @outline ||= Document::Outline.new(self)
end

#output_intentsObject



159
# File 'lib/pdfrb/document.rb', line 159

def output_intents; @output_intents ||= Document::OutputIntents.new(self); end

#page_labelsObject



161
# File 'lib/pdfrb/document.rb', line 161

def page_labels; @page_labels ||= Document::PageLabels.new(self); end

#pagesObject

---- Facade accessors (Phase 13) ---- Each returns a memoised helper object that knows how to mutate this document. Defined under Pdfrb::Document::*.



111
112
113
# File 'lib/pdfrb/document.rb', line 111

def pages
  @pages ||= Document::Pages.new(self)
end

#portfolioObject



157
# File 'lib/pdfrb/document.rb', line 157

def portfolio; @portfolio ||= Document::Portfolio.new(self); end

#register_listener(message, &block) ⇒ Object



97
98
99
# File 'lib/pdfrb/document.rb', line 97

def register_listener(message, &block)
  (@listeners[message] ||= []) << block
end

#register_override(obj) ⇒ Object

Replace an indirect object in the @objects table. Used by the Importer when promoting a Dictionary stub to a Stream (so cycles resolve correctly). Idempotent.



197
198
199
# File 'lib/pdfrb/document.rb', line 197

def register_override(obj)
  @objects[obj.oid] = obj
end

#shadingsObject



147
# File 'lib/pdfrb/document.rb', line 147

def shadings; @shadings ||= Document::Shadings.new(self); end

#stampsObject



163
# File 'lib/pdfrb/document.rb', line 163

def stamps; @stamps ||= Document::Stamps.new(self); end

#structureObject



143
# File 'lib/pdfrb/document.rb', line 143

def structure; @structure ||= Document::Structure.new(self); end

#trailerObject

Convenience accessor for the document Catalog dict. Loads from trailer's /Root, which itself is loaded lazily from the xref.



221
222
223
224
225
# File 'lib/pdfrb/document.rb', line 221

def trailer
  return @trailer if defined?(@trailer) && @trailer

  @trailer = read_trailer
end

#wrap(data, type: nil, oid: 0, gen: 0) ⇒ Object



66
67
68
69
70
71
72
73
74
# File 'lib/pdfrb/document.rb', line 66

def wrap(data, type: nil, oid: 0, gen: 0)
  target_class = resolve_target_class(data, type)
  value = unwrap_value(data)
  if target_class <= Pdfrb::Model::Object
    target_class.new(value, oid: oid, gen: gen, document: self)
  else
    target_class.new(value)
  end
end

#write(path = nil, io: nil) ⇒ Object

Write this document to path (or any IO via +io:).

Raises:

  • (ArgumentError)


210
211
212
213
214
215
216
217
# File 'lib/pdfrb/document.rb', line 210

def write(path = nil, io: nil)
  target = io || (path && File.open(path, "wb"))
  raise ArgumentError, "write needs a path or io:" unless target

  Pdfrb::Writer.write(self, target)
  target.close if path && io.nil? && target.is_a?(IO)
  self
end

#xmpObject



169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/pdfrb/document.rb', line 169

def xmp
  return @xmp if @xmp

  packet = load_xmp_packet
  return nil unless packet

  title = [:Title]
  packet.title = title if title
  author = [:Author]
  packet.author = author if author
  @xmp = packet
end