Class: Jpdfer::Pdf

Inherits:
Object
  • Object
show all
Defined in:
lib/jpdfer/pdf.rb

Overview

PDF Document with a form that can be read, filled, and saved.

Defined Under Namespace

Classes: NonexistentFieldError, ReadOnlyError

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file_or_path, options = {}) ⇒ Pdf

Currently the only option is :keystore



91
92
93
94
95
96
97
98
# File 'lib/jpdfer/pdf.rb', line 91

def initialize(file_or_path, options = {})
  data = file_or_path.read if file_or_path.respond_to?(:read)
  data ||= File.read file_or_path

  @keystore = options[:keystore]

  init(data)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Object

Instances of Pdf forward any possible unknown method calls to the underlying iText PdfReader instance



102
103
104
105
# File 'lib/jpdfer/pdf.rb', line 102

def method_missing(method, *args)
  return @reader.send(method, *args) if @reader.respond_to? method
  super
end

Class Method Details

.concatenate(pdfs, save_path = nil, options = {}) ⇒ Object

A convenience method which concatenates the pages of several pdfs into one pdf. If a block is given, the new pdf is yielded and saved to save_path after the block has been called.

The available options are :keystore and :flatten.

Returns the created pdf. If no block is given, save_as must be called to save the pdf.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/jpdfer/pdf.rb', line 48

def self.concatenate(pdfs, save_path=nil, options={})
  output_buffer = StringIO.new
  flatten = options.delete(:flatten)
  concatenator = PdfCopyFields.new output_buffer.to_outputstream

  pdfs.each do |pdf, page_range|
    if page_range
      PageRangeUtilities::normalize_page_range(page_range).each do |pages|
        # We need to help jruby convert Fixnum to java.lang.Integer. It defaults to java.lang.Long
        pages = pages.map {|page| Java::JavaLang::Integer.new page}
        concatenator.addDocument pdf.reader, pages
      end
    else
      concatenator.addDocument pdf.reader
    end
  end
  concatenator.close

  pdf = new(StringIO.new(output_buffer.string), options)
  if block_given?
    yield pdf
    pdf.save_as(save_path, flatten)
  end
  pdf
end

.create_flatten_fields_xml(fields) ⇒ Object



20
21
22
23
24
25
26
27
28
# File 'lib/jpdfer/pdf.rb', line 20

def self.create_flatten_fields_xml(fields)
  schema = DublinCoreSchema.new
  schema.addDescription(JSON({'jpdfer_flattened_fields' => fields}))
  metaout = StringIO.new
  xmpwriter = XmpWriter.new(metaout.to_outputstream)
  xmpwriter.addRdfDescription(schema)
  xmpwriter.close
  metaout.string
end

.description_from_metadata_xml(metadata_string) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
# File 'lib/jpdfer/pdf.rb', line 30

def self.()
  .gsub!(/<\?.*?\?>/, '')
  namespaces = {
    "xmlns:x"   => "adobe:ns:meta/",
    "xmlns:rdf" => "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
    "xmlns:dc"  => "http://purl.org/dc/elements/1.1/"
  }
  root_node = Nokogiri::XML.parse()
  descriptions = root_node.xpath('.//dc:description//rdf:li/text()', namespaces)
  descriptions.count > 0 ? descriptions.first.text : ""
end

.open(pdf_path, save_path = nil, options = {}) ⇒ Object

A convenience method which initializes a new pdf. If a block is given, the new pdf is yielded and saved to save_path after the block has been called.

The options accept :flatten and :keystore.

Returns the created pdf. If no block is given, save_as must be called to save the pdf.



80
81
82
83
84
85
86
87
88
# File 'lib/jpdfer/pdf.rb', line 80

def self.open(pdf_path, save_path=nil, options={})
  flatten = options.delete(:flatten)
  pdf = new(pdf_path, options)
  if block_given?
    yield pdf
    pdf.save_as(save_path, flatten)
  end
  pdf
end

Instance Method Details

#add_image(image_path, page, x, y, scale = 1.0) ⇒ Object

Adds the image at image_path to the given page, at coordinates x and y

Raises:



286
287
288
289
290
291
292
293
# File 'lib/jpdfer/pdf.rb', line 286

def add_image(image_path, page, x, y, scale=1.0)
  raise ReadOnlyError.new('Previously saved pdfs are read-only') if @saved
  canvas = @stamper.getOverContent(page)
  image = Image.getInstance(image_path)
  image.setAbsolutePosition(x, y)
  image.scalePercent(scale * 100)
  canvas.addImage(image, false)
end

#add_viewer_preference(key, value) ⇒ Object

Adds viewer preferences to the pdf. For all possible key value pairs see: api.itextpdf.com/itext/com/itextpdf/text/pdf/interfaces/PdfViewerPreferences.html#addViewerPreference(com.itextpdf.text.pdf.PdfName, com.itextpdf.text.pdf.PdfObject)

keys and values can be passed in as lower or upper case symbols or strings



227
228
229
230
231
232
233
234
235
# File 'lib/jpdfer/pdf.rb', line 227

def add_viewer_preference(key, value)
  converted_key = PdfName.const_get(key.upcase) rescue nil
  converted_key ||= PdfBoolean.const_get(key.upcase)

  converted_value = PdfName.const_get(value.upcase) rescue nil
  converted_value ||= PdfBoolean.const_get(value.upcase)

  @stamper.add_viewer_preference(converted_key, converted_value)
end

#add_watermark(text, options = {}) ⇒ Object

Add watermark text to all pages at coordinates x and y

options:

:x The placement of the watermark on the x-axis
  Default: The center of the pdf

:y The placement of the watermark on the y-axis
  Default: The center of the pdf

:font should be an instance of com.itextpdf.text.Font to be used for the watermark.
   Default: Helvetica Bold 132pt 0.9 Gray

:rotation is an angle given in degrees that will rotate the watermark.
   Default: 45

:alignment one of the com.itextpdf.text.Element alignment values
  Default: ALIGN_CENTER

Raises:



314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/jpdfer/pdf.rb', line 314

def add_watermark(text, options={})
  raise ReadOnlyError.new('Previously saved pdfs are read-only') if @saved

  x = options[:x] || @reader.crop_box(1).width / 2
  y = options[:y] || @reader.crop_box(1).height / 2
  alignment = options[:alignment] || Element::ALIGN_CENTER
  phrase = Phrase.new(text, options[:font] || default_watermark_font)
  rotation = options[:rotation] || 45

  1.upto(@reader.getNumberOfPages).each do |page|
    canvas = @stamper.getUnderContent(page)
    ColumnText.showTextAligned(canvas, alignment, phrase, x, y, rotation)
  end
end

#certification_levelObject

Returns the certification level of the pdf



243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/jpdfer/pdf.rb', line 243

def certification_level
  case @reader.getCertificationLevel
  when PdfSignatureAppearance::CERTIFIED_FORM_FILLING
    level = :form_filling
  when PdfSignatureAppearance::CERTIFIED_FORM_FILLING_AND_ANNOTATIONS
    level = :form_filling_and_annotations
  when PdfSignatureAppearance::CERTIFIED_NO_CHANGES_ALLOWED
    level = :no_changes_allowed
  when PdfSignatureAppearance::NOT_CERTIFIED
    level = :not_certified
  end
  level
end

#fieldsObject

Returns fields defined in this PDF form and their values, if any. fields returns an empty hash if PDF document does not contain a form



156
157
158
159
160
161
# File 'lib/jpdfer/pdf.rb', line 156

def fields
  form = @stamper.getAcroFields
  form.getFields.each_with_object({}) do |(name, value), fields|
    fields[name.to_sym] = form.getField(name)
  end
end

#flattened_fieldsObject

Returns field names and values that were written to a form in this pdf before flattening. Returns an empty hash if there are not any.



208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/jpdfer/pdf.rb', line 208

def flattened_fields
   = String.from_java_bytes @reader.
  description_text = self.class.()
  begin
     = JSON(description_text)
    flattened_fields = .key?('jpdfer_flattened_fields') ? ['jpdfer_flattened_fields'] : {}
  rescue JSON::ParserError
    flattened_fields = {}
  end
  flattened_fields.each_with_object({}) do |(name, value), fields|
    fields[name.to_sym] = value
  end
end

#get_field(name) ⇒ Object

Returns value of named field.

Raises Pdf::NonexistentFieldError if field does not exist.

name

Symbol name of field to retrieve



167
168
169
170
# File 'lib/jpdfer/pdf.rb', line 167

def get_field(name)
  raise NonexistentFieldError.new("'#{name}' field does not exist in form") unless has_field?(name)
  @stamper.getAcroFields.getField(name.to_s)
end

#has_field?(name) ⇒ Boolean

true if field name exists in form

name

Field name as Symbol (or String)

Returns:

  • (Boolean)


196
197
198
# File 'lib/jpdfer/pdf.rb', line 196

def has_field?(name)
  fields.key?(name.to_sym)
end

#has_flattened_fields?Boolean

true if the receiving Pdf instance was previously flattened with jpdfer

Returns:

  • (Boolean)


238
239
240
# File 'lib/jpdfer/pdf.rb', line 238

def has_flattened_fields?
  flattened_fields.size > 0 ? true : false
end

#has_form?Boolean

true if the receiving Pdf instance has a form

Returns:

  • (Boolean)


201
202
203
# File 'lib/jpdfer/pdf.rb', line 201

def has_form?
  @stamper.getAcroFields.getFields.size > 0
end

#inspectObject

Returns a string representation of the Pdf object



113
114
115
# File 'lib/jpdfer/pdf.rb', line 113

def inspect
  "<Jpdfer::Pdf @reader=#{@reader}, @stamper=#{@stamper}>"
end

#javascript=(script) ⇒ Object

Replaces any javascript currently attached to the page with the contents of script

Returns script



333
334
335
336
# File 'lib/jpdfer/pdf.rb', line 333

def javascript=(script)
  @stamper.addJavaScript(script)
  script
end

#page_sizeObject

Returns the page size of the pdf as [width (Float), height (Float)]



139
140
141
142
143
144
145
146
# File 'lib/jpdfer/pdf.rb', line 139

def page_size
  page_size = @reader.crop_box(1)
  if @reader.page_rotation(1) % 180 == 0
    [page_size.width, page_size.height]
  else
    [page_size.height, page_size.width]
  end
end

#page_typeObject

Returns the page type of the pdf or :unknown See Jpdfer::PAGES_SIZES



150
151
152
# File 'lib/jpdfer/pdf.rb', line 150

def page_type
  PAGE_SIZES.fetch(page_size, :unknown)
end

#readerObject

Returns an independent instance of the underlying iText PdfReader.



108
109
110
# File 'lib/jpdfer/pdf.rb', line 108

def reader
  PdfReader.new @data.to_java_bytes
end

#save_as(path, flatten = false) ⇒ Object

Writes PDF to path. If flatten is true, also flattens the form so that the form is printed on the PDF document but the form is no longer editable.

save_as returns UNTESTED if the PDF form is not valid



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/jpdfer/pdf.rb', line 122

def save_as(path, flatten=false)
  if flatten
     = self.class.create_flatten_fields_xml(fields)
    @stamper. .to_java_bytes
  end

  @stamper.setFormFlattening(flatten)
  @stamper.close

  File.open(path, 'wb') do |file|
    file.write(@output_buffer.string)
  end

  init(@output_buffer.string)
end

#set_certification_level(level) ⇒ Object

Set the certification level on a pdf initialized with an optional keystore

level must be one of :form_filling, :form_filling_and_annotations, :no_changes_allowed, :not_certified



261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/jpdfer/pdf.rb', line 261

def set_certification_level(level)
  case level
  when :form_filling
    certification_level = PdfSignatureAppearance::CERTIFIED_FORM_FILLING
  when :form_filling_and_annotations
    certification_level = PdfSignatureAppearance::CERTIFIED_FORM_FILLING_AND_ANNOTATIONS
  when :no_changes_allowed
    certification_level = PdfSignatureAppearance::CERTIFIED_NO_CHANGES_ALLOWED
  when :not_certified
    level = PdfSignatureAppearance::NOT_CERTIFIED
  end
  @stamper.getSignatureAppearance.setCertificationLevel(certification_level)
end

#set_field(name, value) ⇒ Object

Sets named field. set_field returns value set.

name: Symbol naming the field to write



175
176
177
178
179
180
# File 'lib/jpdfer/pdf.rb', line 175

def set_field(name, value)
  name = name.to_sym
  raise NonexistentFieldError.new("'#{name}' field does not exist in form") unless has_field?(name)
  @stamper.getAcroFields.setField(name.to_s, value.to_s)
  value
end

#set_fields(fields) ⇒ Object

Sets many fields at once. Returns the hash of fields set (should always be equal to given set of fields).

fields

A hash of :key => “value” pairs.



186
187
188
189
190
191
# File 'lib/jpdfer/pdf.rb', line 186

def set_fields(fields)
  fields.each_pair do |name, value|
    set_field(name, value)
  end
  fields
end

#set_signature_location(location) ⇒ Object

Sets the location of the signature on the pdf



281
282
283
# File 'lib/jpdfer/pdf.rb', line 281

def set_signature_location(location)
  @stamper.getSignatureAppearance.setLocation(location)
end

#set_signature_reason(reason) ⇒ Object

Sets the reason for the signature on the pdf



276
277
278
# File 'lib/jpdfer/pdf.rb', line 276

def set_signature_reason(reason)
  @stamper.getSignatureAppearance.setReason(reason)
end