pdf-core 0.5.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  # Describes PDF page geometries
4
4
  #
@@ -8,7 +8,6 @@
8
8
 
9
9
  module PDF
10
10
  module Core
11
-
12
11
  # Dimensions pulled from PDF::Writer, rubyforge.org/projects/ruby-pdf
13
12
  #
14
13
  # All of these dimensions are in PDF Points (1/72 inch)
@@ -68,59 +67,58 @@ module PDF
68
67
  # TABLOID:: => 792.00 x 1224.00
69
68
  #
70
69
  module PageGeometry
71
-
72
- SIZES = { "4A0" => [4767.87, 6740.79],
73
- "2A0" => [3370.39, 4767.87],
74
- "A0" => [2383.94, 3370.39],
75
- "A1" => [1683.78, 2383.94],
76
- "A2" => [1190.55, 1683.78],
77
- "A3" => [841.89, 1190.55],
78
- "A4" => [595.28, 841.89],
79
- "A5" => [419.53, 595.28],
80
- "A6" => [297.64, 419.53],
81
- "A7" => [209.76, 297.64],
82
- "A8" => [147.40, 209.76],
83
- "A9" => [104.88, 147.40],
84
- "A10" => [73.70, 104.88],
85
- "B0" => [2834.65, 4008.19],
86
- "B1" => [2004.09, 2834.65],
87
- "B2" => [1417.32, 2004.09],
88
- "B3" => [1000.63, 1417.32],
89
- "B4" => [708.66, 1000.63],
90
- "B5" => [498.90, 708.66],
91
- "B6" => [354.33, 498.90],
92
- "B7" => [249.45, 354.33],
93
- "B8" => [175.75, 249.45],
94
- "B9" => [124.72, 175.75],
95
- "B10" => [87.87, 124.72],
96
- "C0" => [2599.37, 3676.54],
97
- "C1" => [1836.85, 2599.37],
98
- "C2" => [1298.27, 1836.85],
99
- "C3" => [918.43, 1298.27],
100
- "C4" => [649.13, 918.43],
101
- "C5" => [459.21, 649.13],
102
- "C6" => [323.15, 459.21],
103
- "C7" => [229.61, 323.15],
104
- "C8" => [161.57, 229.61],
105
- "C9" => [113.39, 161.57],
106
- "C10" => [79.37, 113.39],
107
- "RA0" => [2437.80, 3458.27],
108
- "RA1" => [1729.13, 2437.80],
109
- "RA2" => [1218.90, 1729.13],
110
- "RA3" => [864.57, 1218.90],
111
- "RA4" => [609.45, 864.57],
112
- "SRA0" => [2551.18, 3628.35],
113
- "SRA1" => [1814.17, 2551.18],
114
- "SRA2" => [1275.59, 1814.17],
115
- "SRA3" => [907.09, 1275.59],
116
- "SRA4" => [637.80, 907.09],
117
- "EXECUTIVE" => [521.86, 756.00],
118
- "FOLIO" => [612.00, 936.00],
119
- "LEGAL" => [612.00, 1008.00],
120
- "LETTER" => [612.00, 792.00],
121
- "TABLOID" => [792.00, 1224.00] }
122
-
70
+ SIZES = {
71
+ '4A0' => [4767.87, 6740.79],
72
+ '2A0' => [3370.39, 4767.87],
73
+ 'A0' => [2383.94, 3370.39],
74
+ 'A1' => [1683.78, 2383.94],
75
+ 'A2' => [1190.55, 1683.78],
76
+ 'A3' => [841.89, 1190.55],
77
+ 'A4' => [595.28, 841.89],
78
+ 'A5' => [419.53, 595.28],
79
+ 'A6' => [297.64, 419.53],
80
+ 'A7' => [209.76, 297.64],
81
+ 'A8' => [147.40, 209.76],
82
+ 'A9' => [104.88, 147.40],
83
+ 'A10' => [73.70, 104.88],
84
+ 'B0' => [2834.65, 4008.19],
85
+ 'B1' => [2004.09, 2834.65],
86
+ 'B2' => [1417.32, 2004.09],
87
+ 'B3' => [1000.63, 1417.32],
88
+ 'B4' => [708.66, 1000.63],
89
+ 'B5' => [498.90, 708.66],
90
+ 'B6' => [354.33, 498.90],
91
+ 'B7' => [249.45, 354.33],
92
+ 'B8' => [175.75, 249.45],
93
+ 'B9' => [124.72, 175.75],
94
+ 'B10' => [87.87, 124.72],
95
+ 'C0' => [2599.37, 3676.54],
96
+ 'C1' => [1836.85, 2599.37],
97
+ 'C2' => [1298.27, 1836.85],
98
+ 'C3' => [918.43, 1298.27],
99
+ 'C4' => [649.13, 918.43],
100
+ 'C5' => [459.21, 649.13],
101
+ 'C6' => [323.15, 459.21],
102
+ 'C7' => [229.61, 323.15],
103
+ 'C8' => [161.57, 229.61],
104
+ 'C9' => [113.39, 161.57],
105
+ 'C10' => [79.37, 113.39],
106
+ 'RA0' => [2437.80, 3458.27],
107
+ 'RA1' => [1729.13, 2437.80],
108
+ 'RA2' => [1218.90, 1729.13],
109
+ 'RA3' => [864.57, 1218.90],
110
+ 'RA4' => [609.45, 864.57],
111
+ 'SRA0' => [2551.18, 3628.35],
112
+ 'SRA1' => [1814.17, 2551.18],
113
+ 'SRA2' => [1275.59, 1814.17],
114
+ 'SRA3' => [907.09, 1275.59],
115
+ 'SRA4' => [637.80, 907.09],
116
+ 'EXECUTIVE' => [521.86, 756.00],
117
+ 'FOLIO' => [612.00, 936.00],
118
+ 'LEGAL' => [612.00, 1008.00],
119
+ 'LETTER' => [612.00, 792.00],
120
+ 'TABLOID' => [792.00, 1224.00]
121
+ }.freeze
123
122
  end
124
123
  end
125
124
  end
126
-
@@ -1,5 +1,5 @@
1
- # encoding: utf-8
2
- #
1
+ # frozen_string_literal: true
2
+
3
3
  # pdf_object.rb : Handles Ruby to PDF object serialization
4
4
  #
5
5
  # Copyright April 2008, Gregory Brown. All Rights Reserved.
@@ -13,89 +13,98 @@ module PDF
13
13
  module_function
14
14
 
15
15
  def real(num)
16
- num.to_f.round(4)
16
+ format('%<number>.5f', number: num).sub(/((?<!\.)0)+\z/, '')
17
17
  end
18
18
 
19
19
  def real_params(array)
20
- array.map { |e| real(e) }.join(" ")
20
+ array.map { |e| real(e) }.join(' ')
21
21
  end
22
22
 
23
23
  def utf8_to_utf16(str)
24
- "\xFE\xFF".force_encoding(::Encoding::UTF_16BE) + str.encode(::Encoding::UTF_16BE)
24
+ (+"\xFE\xFF").force_encoding(::Encoding::UTF_16BE) +
25
+ str.encode(::Encoding::UTF_16BE)
25
26
  end
26
27
 
27
28
  # encodes any string into a hex representation. The result is a string
28
29
  # with only 0-9 and a-f characters. That result is valid ASCII so tag
29
30
  # it as such to account for behaviour of different ruby VMs
30
31
  def string_to_hex(str)
31
- str.unpack("H*").first.force_encoding(::Encoding::US_ASCII)
32
+ str.unpack1('H*').force_encoding(::Encoding::US_ASCII)
32
33
  end
33
34
 
35
+ ESCAPED_NAME_CHARACTERS = (1..32).to_a + [35, 40, 41, 47, 60, 62] + (127..255).to_a
36
+
34
37
  # Serializes Ruby objects to their PDF equivalents. Most primitive objects
35
38
  # will work as expected, but please note that Name objects are represented
36
- # by Ruby Symbol objects and Dictionary objects are represented by Ruby hashes
37
- # (keyed by symbols)
39
+ # by Ruby Symbol objects and Dictionary objects are represented by Ruby
40
+ # hashes (keyed by symbols)
38
41
  #
39
42
  # Examples:
40
43
  #
41
- # PdfObject(true) #=> "true"
42
- # PdfObject(false) #=> "false"
43
- # PdfObject(1.2124) #=> "1.2124"
44
- # PdfObject("foo bar") #=> "(foo bar)"
45
- # PdfObject(:Symbol) #=> "/Symbol"
46
- # PdfObject(["foo",:bar, [1,2]]) #=> "[foo /bar [1 2]]"
44
+ # pdf_object(true) #=> "true"
45
+ # pdf_object(false) #=> "false"
46
+ # pdf_object(1.2124) #=> "1.2124"
47
+ # pdf_object('foo bar') #=> "(foo bar)"
48
+ # pdf_object(:Symbol) #=> "/Symbol"
49
+ # pdf_object(['foo',:bar, [1,2]]) #=> "[foo /bar [1 2]]"
47
50
  #
48
- def PdfObject(obj, in_content_stream = false)
49
- case(obj)
50
- when NilClass then "null"
51
- when TrueClass then "true"
52
- when FalseClass then "false"
51
+ def pdf_object(obj, in_content_stream = false)
52
+ case obj
53
+ when NilClass then 'null'
54
+ when TrueClass then 'true'
55
+ when FalseClass then 'false'
53
56
  when Numeric
54
- obj = real(obj) unless obj.kind_of?(Integer)
57
+ obj = real(obj) unless obj.is_a?(Integer)
55
58
 
56
- String(obj) # NOTE: this can fail on huge floating point numbers,
57
- # but it seems unlikely to ever happen in practice.
59
+ # NOTE: this can fail on huge floating point numbers, but it seems
60
+ # unlikely to ever happen in practice.
61
+ num_string = String(obj)
62
+
63
+ # Truncate trailing fraction zeroes
64
+ num_string.sub(/(\d*)((\.0*$)|(\.0*[1-9]*)0*$)/, '\1\4')
58
65
  when Array
59
- "[" << obj.map { |e| PdfObject(e, in_content_stream) }.join(' ') << "]"
66
+ "[#{obj.map { |e| pdf_object(e, in_content_stream) }.join(' ')}]"
60
67
  when PDF::Core::LiteralString
61
- obj = obj.gsub(/[\\\n\r\t\b\f\(\)]/n) { |m| "\\#{m}" }
68
+ obj = obj.gsub(/[\\\n\r\t\b\f()]/) { |m| "\\#{m}" }
62
69
  "(#{obj})"
63
70
  when Time
64
- obj = obj.strftime("D:%Y%m%d%H%M%S%z").chop.chop + "'00'"
65
- obj = obj.gsub(/[\\\n\r\t\b\f\(\)]/n) { |m| "\\#{m}" }
71
+ obj = "#{obj.strftime('D:%Y%m%d%H%M%S%z').chop.chop}'00'"
72
+ obj = obj.gsub(/[\\\n\r\t\b\f()]/) { |m| "\\#{m}" }
66
73
  "(#{obj})"
67
74
  when PDF::Core::ByteString
68
- "<" << obj.unpack("H*").first << ">"
75
+ "<#{obj.unpack1('H*')}>"
69
76
  when String
70
77
  obj = utf8_to_utf16(obj) unless in_content_stream
71
- "<" << string_to_hex(obj) << ">"
72
- when Symbol
73
- "/" + obj.to_s.unpack("C*").map { |n|
74
- if n < 33 || n > 126 || [35,40,41,47,60,62].include?(n)
75
- "#" + n.to_s(16).upcase
76
- else
77
- [n].pack("C*")
78
- end
79
- }.join
78
+ "<#{string_to_hex(obj)}>"
79
+ when Symbol
80
+ name_string =
81
+ obj.to_s.unpack('C*').map do |n|
82
+ if ESCAPED_NAME_CHARACTERS.include?(n)
83
+ "##{n.to_s(16).upcase}"
84
+ else
85
+ [n].pack('C*')
86
+ end
87
+ end.join
88
+ "/#{name_string}"
80
89
  when ::Hash
81
- output = "<< "
82
- obj.each do |k,v|
83
- unless String === k || Symbol === k
90
+ output = +'<< '
91
+ obj.each do |k, v|
92
+ unless k.is_a?(String) || k.is_a?(Symbol)
84
93
  raise PDF::Core::Errors::FailedObjectConversion,
85
- "A PDF Dictionary must be keyed by names"
94
+ 'A PDF Dictionary must be keyed by names'
86
95
  end
87
- output << PdfObject(k.to_sym, in_content_stream) << " " <<
88
- PdfObject(v, in_content_stream) << "\n"
96
+ output << pdf_object(k.to_sym, in_content_stream) << ' ' <<
97
+ pdf_object(v, in_content_stream) << "\n"
89
98
  end
90
- output << ">>"
99
+ output << '>>'
91
100
  when PDF::Core::Reference
92
101
  obj.to_s
93
102
  when PDF::Core::NameTree::Node
94
- PdfObject(obj.to_hash)
103
+ pdf_object(obj.to_hash)
95
104
  when PDF::Core::NameTree::Value
96
- PdfObject(obj.name) + " " + PdfObject(obj.value)
105
+ "#{pdf_object(obj.name)} #{pdf_object(obj.value)}"
97
106
  when PDF::Core::OutlineRoot, PDF::Core::OutlineItem
98
- PdfObject(obj.to_hash)
107
+ pdf_object(obj.to_hash)
99
108
  else
100
109
  raise PDF::Core::Errors::FailedObjectConversion,
101
110
  "This object cannot be serialized to PDF (#{obj.inspect})"
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  # reference.rb : Implementation of PDF indirect objects
4
4
  #
@@ -6,13 +6,19 @@
6
6
  #
7
7
  # This is free software. Please see the LICENSE and COPYING files for details.
8
8
 
9
+ require 'pdf/core/utils'
9
10
 
10
11
  module PDF
11
12
  module Core
12
13
  class Reference #:nodoc:
13
-
14
14
  attr_accessor :gen, :data, :offset, :stream, :identifier
15
15
 
16
+ class CannotAttachStream < StandardError
17
+ def initialize(message = 'Cannot attach stream to a non-dictionary object')
18
+ super
19
+ end
20
+ end
21
+
16
22
  def initialize(id, data)
17
23
  @identifier = id
18
24
  @gen = 0
@@ -21,18 +27,22 @@ module PDF
21
27
  end
22
28
 
23
29
  def object
24
- output = "#{@identifier} #{gen} obj\n"
25
- unless @stream.empty?
26
- output << PDF::Core::PdfObject(data.merge @stream.data) << "\n" << @stream.object
30
+ output = +"#{@identifier} #{gen} obj\n"
31
+ if @stream.empty?
32
+ output << PDF::Core.pdf_object(data) << "\n"
27
33
  else
28
- output << PDF::Core::PdfObject(data) << "\n"
34
+ output << PDF::Core.pdf_object(data.merge(@stream.data)) <<
35
+ "\n" << @stream.object
29
36
  end
30
37
 
31
38
  output << "endobj\n"
32
39
  end
33
40
 
34
41
  def <<(io)
35
- raise "Cannot attach stream to non-dictionary object" unless @data.is_a?(::Hash)
42
+ unless @data.is_a?(::Hash)
43
+ raise CannotAttachStream
44
+ end
45
+
36
46
  (@stream ||= Stream.new) << io
37
47
  end
38
48
 
@@ -43,22 +53,22 @@ module PDF
43
53
  # Creates a deep copy of this ref. If +share+ is provided, shares the
44
54
  # given dictionary entries between the old ref and the new.
45
55
  #
46
- def deep_copy(share=[])
56
+ def deep_copy(share = [])
47
57
  r = dup
48
58
 
49
59
  case r.data
50
60
  when ::Hash
51
61
  # Copy each entry not in +share+.
52
62
  (r.data.keys - share).each do |k|
53
- r.data[k] = Marshal.load(Marshal.dump(r.data[k]))
63
+ r.data[k] = Utils.deep_clone(r.data[k])
54
64
  end
55
65
  when PDF::Core::NameTree::Node
56
66
  r.data = r.data.deep_copy
57
67
  else
58
- r.data = Marshal.load(Marshal.dump(r.data))
68
+ r.data = Utils.deep_clone(r.data)
59
69
  end
60
70
 
61
- r.stream = Marshal.load(Marshal.dump(r.stream))
71
+ r.stream = Utils.deep_clone(r.stream)
62
72
  r
63
73
  end
64
74
 
@@ -68,11 +78,5 @@ module PDF
68
78
  @stream = other_ref.stream
69
79
  end
70
80
  end
71
-
72
- module_function
73
-
74
- def Reference(*args, &block) #:nodoc:
75
- Reference.new(*args, &block)
76
- end
77
81
  end
78
82
  end
@@ -1,4 +1,6 @@
1
- require "stringio"
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
2
4
 
3
5
  module PDF
4
6
  module Core
@@ -6,7 +8,7 @@ module PDF
6
8
  def initialize(state)
7
9
  @state = state
8
10
  @state.populate_pages_from_store(self)
9
-
11
+
10
12
  min_version(state.store.min_version) if state.store.min_version
11
13
 
12
14
  @page_number = 0
@@ -14,8 +16,8 @@ module PDF
14
16
 
15
17
  attr_reader :state
16
18
 
17
- # Creates a new Reference and adds it to the Document's object
18
- # list. The +data+ argument is anything that Prawn::PdfObject() can convert.
19
+ # Creates a new Reference and adds it to the Document's object list. The
20
+ # +data+ argument is anything that Prawn.pdf_object() can convert.
19
21
  #
20
22
  # Returns the identifier which points to the reference in the ObjectStore
21
23
  #
@@ -50,7 +52,7 @@ module PDF
50
52
  #
51
53
  # pdf.add_content("#{PDF::Core.real_params([x1, y1])} m") # move
52
54
  # pdf.add_content("#{PDF::Core.real_params([ x2, y2 ])} l") # draw path
53
- # pdf.add_content("S") # stroke
55
+ # pdf.add_content('S') # stroke
54
56
  #
55
57
  def add_content(str)
56
58
  save_graphics_state if graphic_state.nil?
@@ -62,7 +64,7 @@ module PDF
62
64
  # dictionary do not incur the additional overhead.
63
65
  #
64
66
  def names
65
- state.store.root.data[:Names] ||= ref!(:Type => :Names)
67
+ state.store.root.data[:Names] ||= ref!(Type: :Names)
66
68
  end
67
69
 
68
70
  # Returns true if the Names dictionary is in use for this document.
@@ -80,29 +82,36 @@ module PDF
80
82
  # Defines a block to be called just before a new page is started.
81
83
  #
82
84
  def on_page_create(&block)
83
- if block_given?
84
- state.on_page_create_callback = block
85
- else
86
- state.on_page_create_callback = nil
87
- end
85
+ state.on_page_create_callback =
86
+ if block_given?
87
+ block
88
+ end
88
89
  end
89
90
 
90
91
  def start_new_page(options = {})
91
- if last_page = state.page
92
+ last_page = state.page
93
+ if last_page
92
94
  last_page_size = last_page.size
93
95
  last_page_layout = last_page.layout
94
96
  last_page_margins = last_page.margins
95
97
  end
96
98
 
97
- page_options = {:size => options[:size] || last_page_size,
98
- :layout => options[:layout] || last_page_layout,
99
- :margins => last_page_margins}
99
+ page_options = {
100
+ size: options[:size] || last_page_size,
101
+ layout: options[:layout] || last_page_layout,
102
+ margins: last_page_margins
103
+ }
100
104
  if last_page
101
- new_graphic_state = last_page.graphic_state.dup if last_page.graphic_state
105
+ if last_page.graphic_state
106
+ new_graphic_state = last_page.graphic_state.dup
107
+ end
102
108
 
103
- #erase the color space so that it gets reset on new page for fussy pdf-readers
104
- new_graphic_state.color_space = {} if new_graphic_state
105
- page_options.merge!(:graphic_state => new_graphic_state)
109
+ # Erase the color space so that it gets reset on new page for fussy
110
+ # pdf-readers
111
+ if new_graphic_state
112
+ new_graphic_state.color_space = {}
113
+ end
114
+ page_options[:graphic_state] = new_graphic_state
106
115
  end
107
116
 
108
117
  state.page = PDF::Core::Page.new(self, page_options)
@@ -121,10 +130,10 @@ module PDF
121
130
  # draw on it.
122
131
  #
123
132
  # See Prawn::Document#number_pages for a sample usage of this capability.
124
-
125
- def go_to_page(k)
126
- @page_number = k
127
- state.page = state.pages[k-1]
133
+
134
+ def go_to_page(page_number)
135
+ @page_number = page_number
136
+ state.page = state.pages[page_number - 1]
128
137
  end
129
138
 
130
139
  def finalize_all_page_contents
@@ -161,18 +170,16 @@ module PDF
161
170
  if output.instance_of?(StringIO)
162
171
  str = output.string
163
172
  str.force_encoding(::Encoding::ASCII_8BIT)
164
- return str
165
- else
166
- return nil
173
+ str
167
174
  end
168
175
  end
169
176
 
170
177
  # Renders the PDF document to file.
171
178
  #
172
- # pdf.render_file "foo.pdf"
179
+ # pdf.render_file 'foo.pdf'
173
180
  #
174
181
  def render_file(filename)
175
- File.open(filename, "wb") { |f| render(f) }
182
+ File.open(filename, 'wb') { |f| render(f) }
176
183
  end
177
184
 
178
185
  # Write out the PDF Header, as per spec 3.4.1
@@ -201,7 +208,7 @@ module PDF
201
208
  output << "0 #{state.store.size + 1}\n"
202
209
  output << "0000000000 65535 f \n"
203
210
  state.store.each do |ref|
204
- output.printf("%010d", ref.offset)
211
+ output.printf('%<offset>010d', offset: ref.offset)
205
212
  output << " 00000 n \n"
206
213
  end
207
214
  end
@@ -209,24 +216,26 @@ module PDF
209
216
  # Write out the PDF Trailer, as per spec 3.4.4
210
217
  #
211
218
  def render_trailer(output)
212
- trailer_hash = {:Size => state.store.size + 1,
213
- :Root => state.store.root,
214
- :Info => state.store.info}
219
+ trailer_hash = {
220
+ Size: state.store.size + 1,
221
+ Root: state.store.root,
222
+ Info: state.store.info
223
+ }
215
224
  trailer_hash.merge!(state.trailer) if state.trailer
216
225
 
217
226
  output << "trailer\n"
218
- output << PDF::Core::PdfObject(trailer_hash) << "\n"
227
+ output << PDF::Core.pdf_object(trailer_hash) << "\n"
219
228
  output << "startxref\n"
220
229
  output << @xref_offset << "\n"
221
- output << "%%EOF" << "\n"
230
+ output << '%%EOF' << "\n"
222
231
  end
223
232
 
224
233
  def open_graphics_state
225
- add_content "q"
234
+ add_content 'q'
226
235
  end
227
236
 
228
237
  def close_graphics_state
229
- add_content "Q"
238
+ add_content 'Q'
230
239
  end
231
240
 
232
241
  def save_graphics_state(graphic_state = nil)
@@ -242,7 +251,7 @@ module PDF
242
251
  # false otherwise
243
252
  #
244
253
  def compression_enabled?
245
- !!state.compress
254
+ state.compress
246
255
  end
247
256
 
248
257
  # Pops the last saved graphics state off the graphics state stack and