pdf-core 0.4.0 → 0.10.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,119 +8,63 @@
8
8
 
9
9
  module PDF
10
10
  module Core
11
-
12
11
  # Dimensions pulled from PDF::Writer, rubyforge.org/projects/ruby-pdf
13
- #
14
- # All of these dimensions are in PDF Points (1/72 inch)
15
- #
16
- # ===Inbuilt Sizes:
17
- #
18
- #
19
- # 4A0:: => 4767.87 x 6740.79
20
- # 2A0:: => 3370.39 x 4767.87
21
- # A0:: => 2383.94 x 3370.39
22
- # A1:: => 1683.78 x 2383.94
23
- # A2:: => 1190.55 x 1683.78
24
- # A3:: => 841.89 x 1190.55
25
- # A4:: => 595.28 x 841.89
26
- # A5:: => 419.53 x 595.28
27
- # A6:: => 297.64 x 419.53
28
- # A7:: => 209.76 x 297.64
29
- # A8:: => 147.40 x 209.76
30
- # A9:: => 104.88 x 147.40
31
- # A10:: => 73.70 x 104.88
32
- # B0:: => 2834.65 x 4008.19
33
- # B1:: => 2004.09 x 2834.65
34
- # B2:: => 1417.32 x 2004.09
35
- # B3:: => 1000.63 x 1417.32
36
- # B4:: => 708.66 x 1000.63
37
- # B5:: => 498.90 x 708.66
38
- # B6:: => 354.33 x 498.90
39
- # B7:: => 249.45 x 354.33
40
- # B8:: => 175.75 x 249.45
41
- # B9:: => 124.72 x 175.75
42
- # B10:: => 87.87 x 124.72
43
- # C0:: => 2599.37 x 3676.54
44
- # C1:: => 1836.85 x 2599.37
45
- # C2:: => 1298.27 x 1836.85
46
- # C3:: => 918.43 x 1298.27
47
- # C4:: => 649.13 x 918.43
48
- # C5:: => 459.21 x 649.13
49
- # C6:: => 323.15 x 459.21
50
- # C7:: => 229.61 x 323.15
51
- # C8:: => 161.57 x 229.61
52
- # C9:: => 113.39 x 161.57
53
- # C10:: => 79.37 x 113.39
54
- # RA0:: => 2437.80 x 3458.27
55
- # RA1:: => 1729.13 x 2437.80
56
- # RA2:: => 1218.90 x 1729.13
57
- # RA3:: => 864.57 x 1218.90
58
- # RA4:: => 609.45 x 864.57
59
- # SRA0:: => 2551.18 x 3628.35
60
- # SRA1:: => 1814.17 x 2551.18
61
- # SRA2:: => 1275.59 x 1814.17
62
- # SRA3:: => 907.09 x 1275.59
63
- # SRA4:: => 637.80 x 907.09
64
- # EXECUTIVE:: => 521.86 x 756.00
65
- # FOLIO:: => 612.00 x 936.00
66
- # LEGAL:: => 612.00 x 1008.00
67
- # LETTER:: => 612.00 x 792.00
68
- # TABLOID:: => 792.00 x 1224.00
69
- #
70
12
  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
-
13
+ # Named page sizes.
14
+ #
15
+ # All of these dimensions are in PDF Points (1/72 inch).
16
+ SIZES = {
17
+ '4A0' => [4767.87, 6740.79],
18
+ '2A0' => [3370.39, 4767.87],
19
+ 'A0' => [2383.94, 3370.39],
20
+ 'A1' => [1683.78, 2383.94],
21
+ 'A2' => [1190.55, 1683.78],
22
+ 'A3' => [841.89, 1190.55],
23
+ 'A4' => [595.28, 841.89],
24
+ 'A5' => [419.53, 595.28],
25
+ 'A6' => [297.64, 419.53],
26
+ 'A7' => [209.76, 297.64],
27
+ 'A8' => [147.40, 209.76],
28
+ 'A9' => [104.88, 147.40],
29
+ 'A10' => [73.70, 104.88],
30
+ 'B0' => [2834.65, 4008.19],
31
+ 'B1' => [2004.09, 2834.65],
32
+ 'B2' => [1417.32, 2004.09],
33
+ 'B3' => [1000.63, 1417.32],
34
+ 'B4' => [708.66, 1000.63],
35
+ 'B5' => [498.90, 708.66],
36
+ 'B6' => [354.33, 498.90],
37
+ 'B7' => [249.45, 354.33],
38
+ 'B8' => [175.75, 249.45],
39
+ 'B9' => [124.72, 175.75],
40
+ 'B10' => [87.87, 124.72],
41
+ 'C0' => [2599.37, 3676.54],
42
+ 'C1' => [1836.85, 2599.37],
43
+ 'C2' => [1298.27, 1836.85],
44
+ 'C3' => [918.43, 1298.27],
45
+ 'C4' => [649.13, 918.43],
46
+ 'C5' => [459.21, 649.13],
47
+ 'C6' => [323.15, 459.21],
48
+ 'C7' => [229.61, 323.15],
49
+ 'C8' => [161.57, 229.61],
50
+ 'C9' => [113.39, 161.57],
51
+ 'C10' => [79.37, 113.39],
52
+ 'RA0' => [2437.80, 3458.27],
53
+ 'RA1' => [1729.13, 2437.80],
54
+ 'RA2' => [1218.90, 1729.13],
55
+ 'RA3' => [864.57, 1218.90],
56
+ 'RA4' => [609.45, 864.57],
57
+ 'SRA0' => [2551.18, 3628.35],
58
+ 'SRA1' => [1814.17, 2551.18],
59
+ 'SRA2' => [1275.59, 1814.17],
60
+ 'SRA3' => [907.09, 1275.59],
61
+ 'SRA4' => [637.80, 907.09],
62
+ 'EXECUTIVE' => [521.86, 756.00],
63
+ 'FOLIO' => [612.00, 936.00],
64
+ 'LEGAL' => [612.00, 1008.00],
65
+ 'LETTER' => [612.00, 792.00],
66
+ 'TABLOID' => [792.00, 1224.00],
67
+ }.freeze
123
68
  end
124
69
  end
125
70
  end
126
-
@@ -1,95 +1,131 @@
1
- # encoding: utf-8
2
- #
3
- # pdf_object.rb : Handles Ruby to PDF object serialization
4
- #
5
- # Copyright April 2008, Gregory Brown. All Rights Reserved.
6
- #
7
- # This is free software. Please see the LICENSE and COPYING files for details.
1
+ # frozen_string_literal: true
8
2
 
9
- # Top level Module
10
- #
11
3
  module PDF
12
4
  module Core
13
5
  module_function
14
6
 
7
+ # Serializes floating number into a string
8
+ #
9
+ # @param num [Numeric]
10
+ # @return [String]
11
+ def real(num)
12
+ result = format('%.5f', num)
13
+ result.sub!(/((?<!\.)0)+\z/, '')
14
+ result
15
+ end
16
+
17
+ # Serializes a n array of numbers. This is specifically for use in PDF
18
+ # content streams.
19
+ #
20
+ # @param array [Array<Numeric>]
21
+ # @return [String]
22
+ def real_params(array)
23
+ array.map { |e| real(e) }.join(' ')
24
+ end
25
+
26
+ # Converts string to UTF-16BE encoding as expected by PDF.
27
+ #
28
+ # @param str [String]
29
+ # @return [String]
30
+ # @api private
15
31
  def utf8_to_utf16(str)
16
- "\xFE\xFF".force_encoding(::Encoding::UTF_16BE) + str.encode(::Encoding::UTF_16BE)
32
+ (+"\xFE\xFF").force_encoding(::Encoding::UTF_16BE) <<
33
+ str.encode(::Encoding::UTF_16BE)
17
34
  end
18
35
 
19
- # encodes any string into a hex representation. The result is a string
36
+ # Encodes any string into a hex representation. The result is a string
20
37
  # with only 0-9 and a-f characters. That result is valid ASCII so tag
21
- # it as such to account for behaviour of different ruby VMs
38
+ # it as such to account for behaviour of different ruby VMs.
39
+ #
40
+ # @param str [String]
41
+ # @return [String]
22
42
  def string_to_hex(str)
23
- str.unpack("H*").first.force_encoding(::Encoding::US_ASCII)
43
+ str.unpack1('H*').force_encoding(::Encoding::US_ASCII)
24
44
  end
25
45
 
46
+ # Characters to escape in name objects
47
+ # @api private
48
+ ESCAPED_NAME_CHARACTERS = (1..32).to_a + [35, 40, 41, 47, 60, 62] + (127..255).to_a
49
+
50
+ # How to escape special characters in literal strings
51
+ # @api private
52
+ STRING_ESCAPE_MAP = { '(' => '\(', ')' => '\)', '\\' => '\\\\', "\r" => '\r' }.freeze
53
+
26
54
  # Serializes Ruby objects to their PDF equivalents. Most primitive objects
27
55
  # will work as expected, but please note that Name objects are represented
28
- # by Ruby Symbol objects and Dictionary objects are represented by Ruby hashes
29
- # (keyed by symbols)
56
+ # by Ruby Symbol objects and Dictionary objects are represented by Ruby
57
+ # hashes (keyed by symbols)
30
58
  #
31
- # Examples:
59
+ # Examples:
32
60
  #
33
- # PdfObject(true) #=> "true"
34
- # PdfObject(false) #=> "false"
35
- # PdfObject(1.2124) #=> "1.2124"
36
- # PdfObject("foo bar") #=> "(foo bar)"
37
- # PdfObject(:Symbol) #=> "/Symbol"
38
- # PdfObject(["foo",:bar, [1,2]]) #=> "[foo /bar [1 2]]"
61
+ # pdf_object(true) #=> "true"
62
+ # pdf_object(false) #=> "false"
63
+ # pdf_object(1.2124) #=> "1.2124"
64
+ # pdf_object('foo bar') #=> "(foo bar)"
65
+ # pdf_object(:Symbol) #=> "/Symbol"
66
+ # pdf_object(['foo',:bar, [1,2]]) #=> "[foo /bar [1 2]]"
39
67
  #
40
- def PdfObject(obj, in_content_stream = false)
41
- case(obj)
42
- when NilClass then "null"
43
- when TrueClass then "true"
44
- when FalseClass then "false"
68
+ # @param obj [nil, Boolean, Numeric, Array, Hash, Time, Symbol, String,
69
+ # PDF::Core::ByteString, PDF::Core::LiteralString,
70
+ # PDF::Core::NameTree::Node, PDF::Core::NameTree::Value,
71
+ # PDF::Core::OutlineRoot, PDF::Core::OutlineItem, PDF::Core::Reference]
72
+ # Object to serialise
73
+ # @param in_content_stream [Boolean] Specifies whther to use content stream
74
+ # format or object format
75
+ # @return [String]
76
+ # @raise [PDF::Core::Errors::FailedObjectConversion]
77
+ def pdf_object(obj, in_content_stream = false)
78
+ case obj
79
+ when NilClass then 'null'
80
+ when TrueClass then 'true'
81
+ when FalseClass then 'false'
45
82
  when Numeric
46
- if (str = String(obj)) =~ /e/i
47
- # scientific notation is not supported in PDF
48
- sprintf("%.16f", obj).gsub(/\.?0+\z/, "")
49
- else
50
- str
51
- end
83
+ num_string = obj.is_a?(Integer) ? String(obj) : real(obj)
84
+
85
+ # Truncate trailing fraction zeroes
86
+ num_string.sub!(/(\d*)((\.0*$)|(\.0*[1-9]*)0*$)/, '\1\4')
87
+ num_string
52
88
  when Array
53
- "[" << obj.map { |e| PdfObject(e, in_content_stream) }.join(' ') << "]"
89
+ "[#{obj.map { |e| pdf_object(e, in_content_stream) }.join(' ')}]"
54
90
  when PDF::Core::LiteralString
55
- obj = obj.gsub(/[\\\n\r\t\b\f\(\)]/n) { |m| "\\#{m}" }
91
+ obj = obj.gsub(/[\\\r()]/, STRING_ESCAPE_MAP)
56
92
  "(#{obj})"
57
93
  when Time
58
- obj = obj.strftime("D:%Y%m%d%H%M%S%z").chop.chop + "'00'"
59
- obj = obj.gsub(/[\\\n\r\t\b\f\(\)]/n) { |m| "\\#{m}" }
94
+ obj = "#{obj.strftime('D:%Y%m%d%H%M%S%z').chop.chop}'00'"
95
+ obj = obj.gsub(/[\\\r()]/, STRING_ESCAPE_MAP)
60
96
  "(#{obj})"
61
97
  when PDF::Core::ByteString
62
- "<" << obj.unpack("H*").first << ">"
98
+ "<#{obj.unpack1('H*')}>"
63
99
  when String
64
100
  obj = utf8_to_utf16(obj) unless in_content_stream
65
- "<" << string_to_hex(obj) << ">"
66
- when Symbol
67
- "/" + obj.to_s.unpack("C*").map { |n|
68
- if n < 33 || n > 126 || [35,40,41,47,60,62].include?(n)
69
- "#" + n.to_s(16).upcase
101
+ "<#{string_to_hex(obj)}>"
102
+ when Symbol
103
+ (@symbol_str_cache ||= {})[obj] ||= (+'/') << obj.to_s.unpack('C*').map { |n|
104
+ if ESCAPED_NAME_CHARACTERS.include?(n)
105
+ "##{n.to_s(16).upcase}"
70
106
  else
71
- [n].pack("C*")
107
+ n.chr
72
108
  end
73
- }.join
109
+ }.join
74
110
  when ::Hash
75
- output = "<< "
76
- obj.each do |k,v|
77
- unless String === k || Symbol === k
111
+ output = +'<< '
112
+ obj
113
+ .sort_by { |k, _v| k.to_s }
114
+ .each do |(k, v)|
115
+ unless k.is_a?(String) || k.is_a?(Symbol)
78
116
  raise PDF::Core::Errors::FailedObjectConversion,
79
- "A PDF Dictionary must be keyed by names"
117
+ 'A PDF Dictionary must be keyed by names'
80
118
  end
81
- output << PdfObject(k.to_sym, in_content_stream) << " " <<
82
- PdfObject(v, in_content_stream) << "\n"
119
+ output << pdf_object(k.to_sym, in_content_stream) << ' ' <<
120
+ pdf_object(v, in_content_stream) << "\n"
83
121
  end
84
- output << ">>"
122
+ output << '>>'
85
123
  when PDF::Core::Reference
86
124
  obj.to_s
87
- when PDF::Core::NameTree::Node
88
- PdfObject(obj.to_hash)
125
+ when PDF::Core::NameTree::Node, PDF::Core::OutlineRoot, PDF::Core::OutlineItem
126
+ pdf_object(obj.to_hash)
89
127
  when PDF::Core::NameTree::Value
90
- PdfObject(obj.name) + " " + PdfObject(obj.value)
91
- when PDF::Core::OutlineRoot, PDF::Core::OutlineItem
92
- PdfObject(obj.to_hash)
128
+ "#{pdf_object(obj.name)} #{pdf_object(obj.value)}"
93
129
  else
94
130
  raise PDF::Core::Errors::FailedObjectConversion,
95
131
  "This object cannot be serialized to PDF (#{obj.inspect})"
@@ -1,78 +1,118 @@
1
- # encoding: utf-8
2
-
3
- # reference.rb : Implementation of PDF indirect objects
4
- #
5
- # Copyright April 2008, Gregory Brown. All Rights Reserved.
6
- #
7
- # This is free software. Please see the LICENSE and COPYING files for details.
1
+ # frozen_string_literal: true
8
2
 
3
+ require 'pdf/core/utils'
9
4
 
10
5
  module PDF
11
6
  module Core
12
- class Reference #:nodoc:
7
+ # PDF indirect objects
8
+ #
9
+ # @api private
10
+ class Reference
11
+ # Object identifier
12
+ # @return [Integer]
13
+ attr_accessor :identifier
14
+
15
+ # Object generation
16
+ # @return [Integer]
17
+ attr_accessor :gen
18
+
19
+ # Object data
20
+ # @return [any]
21
+ attr_accessor :data
22
+
23
+ # Offset of the serialized object in the document
24
+ # @return [Integer]
25
+ attr_accessor :offset
13
26
 
14
- attr_accessor :gen, :data, :offset, :stream, :identifier
27
+ # Object stream
28
+ # @return [Stream]
29
+ attr_accessor :stream
15
30
 
31
+ # In PDF only dict object can have a stream attached. This exception
32
+ # indicates someone tried to add a stream to another kind of object.
33
+ class CannotAttachStream < StandardError
34
+ # @param message [String] Error message
35
+ def initialize(message = 'Cannot attach stream to a non-dictionary object')
36
+ super
37
+ end
38
+ end
39
+
40
+ # @param id [Integer] Object identifier
41
+ # @param data [any] Object data
16
42
  def initialize(id, data)
17
43
  @identifier = id
18
- @gen = 0
19
- @data = data
20
- @stream = Stream.new
44
+ @gen = 0
45
+ @data = data
46
+ @stream = Stream.new
21
47
  end
22
48
 
49
+ # Serialized PDF object
50
+ #
51
+ # @return [String]
23
52
  def object
24
- output = "#{@identifier} #{gen} obj\n"
25
- unless @stream.empty?
26
- output << PDF::Core::PdfObject(data.merge @stream.data) << "\n" << @stream.object
53
+ output = +"#{@identifier} #{gen} obj\n"
54
+ if @stream.empty?
55
+ output << PDF::Core.pdf_object(data) << "\n"
27
56
  else
28
- output << PDF::Core::PdfObject(data) << "\n"
57
+ output << PDF::Core.pdf_object(data.merge(@stream.data)) <<
58
+ "\n" << @stream.object
29
59
  end
30
60
 
31
61
  output << "endobj\n"
32
62
  end
33
63
 
64
+ # Appends data to object stream
65
+ #
66
+ # @param io [String] data
67
+ # @return [io]
68
+ # @raise [CannotAttachStream] if object is not a dict
34
69
  def <<(io)
35
- raise "Cannot attach stream to non-dictionary object" unless @data.is_a?(::Hash)
70
+ unless @data.is_a?(::Hash)
71
+ raise CannotAttachStream
72
+ end
73
+
36
74
  (@stream ||= Stream.new) << io
37
75
  end
38
76
 
77
+ # Object reference in PDF format
78
+ #
79
+ # @return [String]
39
80
  def to_s
40
81
  "#{@identifier} #{gen} R"
41
82
  end
42
83
 
43
- # Creates a deep copy of this ref. If +share+ is provided, shares the
44
- # given dictionary entries between the old ref and the new.
84
+ # Creates a deep copy of this ref.
45
85
  #
46
- def deep_copy(share=[])
86
+ # @param share [Array<Symbol>] a list of dictionary entries to share
87
+ # between the old ref and the new
88
+ # @return [Reference]
89
+ def deep_copy(share = [])
47
90
  r = dup
48
91
 
49
92
  case r.data
50
93
  when ::Hash
51
94
  # Copy each entry not in +share+.
52
95
  (r.data.keys - share).each do |k|
53
- r.data[k] = Marshal.load(Marshal.dump(r.data[k]))
96
+ r.data[k] = Utils.deep_clone(r.data[k])
54
97
  end
55
98
  when PDF::Core::NameTree::Node
56
99
  r.data = r.data.deep_copy
57
100
  else
58
- r.data = Marshal.load(Marshal.dump(r.data))
101
+ r.data = Utils.deep_clone(r.data)
59
102
  end
60
103
 
61
- r.stream = Marshal.load(Marshal.dump(r.stream))
104
+ r.stream = Utils.deep_clone(r.stream)
62
105
  r
63
106
  end
64
107
 
65
108
  # Replaces the data and stream with that of other_ref.
109
+ #
110
+ # @param other_ref [Reference]
111
+ # @return [void]
66
112
  def replace(other_ref)
67
- @data = other_ref.data
113
+ @data = other_ref.data
68
114
  @stream = other_ref.stream
69
115
  end
70
116
  end
71
-
72
- module_function
73
-
74
- def Reference(*args, &block) #:nodoc:
75
- Reference.new(*args, &block)
76
- end
77
117
  end
78
118
  end