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,26 +1,28 @@
1
- # encoding: utf-8
2
- #
3
- # Implements graphics state saving and restoring
4
- #
5
- # Copyright January 2010, Michael Witrant. All Rights Reserved.
6
- #
7
- # This is free software. Please see the LICENSE and COPYING files for details
8
- #
9
-
1
+ # frozen_string_literal: true
10
2
 
11
3
  module PDF
12
4
  module Core
5
+ # Graphics state saving and restoring
13
6
  class GraphicStateStack
7
+ # Graphic state stack
14
8
  attr_accessor :stack
15
9
 
10
+ # @param previous_state [GraphicState, nil]
16
11
  def initialize(previous_state = nil)
17
12
  self.stack = [GraphicState.new(previous_state)]
18
13
  end
19
14
 
15
+ # Pushes graphic state onto stack
16
+ #
17
+ # @param graphic_state [GraphicState, nil]
18
+ # @return [void]
20
19
  def save_graphic_state(graphic_state = nil)
21
20
  stack.push(GraphicState.new(graphic_state || current_state))
22
21
  end
23
22
 
23
+ # Restores previous graphic state
24
+ #
25
+ # @return [void]
24
26
  def restore_graphic_state
25
27
  if stack.empty?
26
28
  raise PDF::Core::Errors::EmptyGraphicStateStack,
@@ -29,62 +31,108 @@ module PDF
29
31
  stack.pop
30
32
  end
31
33
 
34
+ # Current graphic state
35
+ #
36
+ # @return [GraphicState]
32
37
  def current_state
33
38
  stack.last
34
39
  end
35
40
 
41
+ # Tells whether there are any saved graphic states
42
+ #
43
+ # @return [Boolean]
44
+ # @see #empty?
36
45
  def present?
37
- stack.size > 0
46
+ !stack.empty?
38
47
  end
39
48
 
49
+ # Tells whether there are no saved graphic states
50
+ #
51
+ # @return [Boolean]
52
+ # @see #present?
40
53
  def empty?
41
54
  stack.empty?
42
55
  end
43
-
44
56
  end
45
57
 
58
+ # Graphics state.
59
+ # It's a *partial* represenation of PDF graphics state. Only the parts
60
+ # implemented in Prawn are present here.
61
+ #
46
62
  # NOTE: This class may be a good candidate for a copy-on-write hash.
47
63
  class GraphicState
48
- attr_accessor :color_space, :dash, :cap_style, :join_style, :line_width,
49
- :fill_color, :stroke_color
64
+ # Color space
65
+ # @return [Hash]
66
+ attr_accessor :color_space
67
+
68
+ # Dash
69
+ # @return [Hash<[:dash, :space, :phase], [nil, Numeric]>]
70
+ attr_accessor :dash
71
+
72
+ # Line cap
73
+ # @return [Symbol]
74
+ attr_accessor :cap_style
75
+
76
+ # Line Join
77
+ # @return [Symbol]
78
+ attr_accessor :join_style
79
+
80
+ # Line width
81
+ # @return [Numberic]
82
+ attr_accessor :line_width
83
+
84
+ # Fill color
85
+ # @return [String]
86
+ attr_accessor :fill_color
50
87
 
88
+ # Stroke color
89
+ attr_accessor :stroke_color
90
+
91
+ # @param previous_state [GraphicState, nil]
51
92
  def initialize(previous_state = nil)
52
93
  if previous_state
53
94
  initialize_copy(previous_state)
54
95
  else
55
- @color_space = {}
56
- @fill_color = "000000"
57
- @stroke_color = "000000"
58
- @dash = { :dash => nil, :space => nil, :phase => 0 }
59
- @cap_style = :butt
60
- @join_style = :miter
61
- @line_width = 1
96
+ @color_space = {}
97
+ @fill_color = '000000'
98
+ @stroke_color = '000000'
99
+ @dash = { dash: nil, space: nil, phase: 0 }
100
+ @cap_style = :butt
101
+ @join_style = :miter
102
+ @line_width = 1
62
103
  end
63
104
  end
64
105
 
106
+ # PDF representation of dash settings
107
+ #
108
+ # @return [String]
65
109
  def dash_setting
66
- if @dash[:dash].kind_of?(Array)
67
- "[#{@dash[:dash].join(' ')}] #{@dash[:phase]} d"
68
- else
69
- "[#{@dash[:dash]} #{@dash[:space]}] #{@dash[:phase]} d"
70
- end
110
+ return '[] 0 d' unless @dash[:dash]
111
+
112
+ array =
113
+ if @dash[:dash].is_a?(Array)
114
+ @dash[:dash]
115
+ else
116
+ [@dash[:dash], @dash[:space]]
117
+ end
118
+
119
+ "[#{PDF::Core.real_params(array)}] #{PDF::Core.real(@dash[:phase])} d"
71
120
  end
72
121
 
73
122
  private
74
123
 
75
124
  def initialize_copy(other)
76
125
  # mutable state
77
- @color_space = other.color_space.dup
78
- @fill_color = other.fill_color.dup
126
+ @color_space = other.color_space.dup
127
+ @fill_color = other.fill_color.dup
79
128
  @stroke_color = other.stroke_color.dup
80
- @dash = other.dash.dup
129
+ @dash = other.dash.dup
81
130
 
82
131
  # immutable state that doesn't need to be duped
83
- @cap_style = other.cap_style
84
- @join_style = other.join_style
85
- @line_width = other.line_width
132
+ @cap_style = other.cap_style
133
+ @join_style = other.join_style
134
+ @line_width = other.line_width
86
135
  end
87
136
  end
88
137
  end
89
138
  end
90
-
@@ -1,16 +1,17 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  module PDF
3
4
  module Core
4
- # This is used to differentiate strings that must be encoded as
5
- # a *literal* string, versus those that can be encoded in
6
- # the PDF hexadecimal format.
5
+ # This is used to differentiate strings that must be encoded as a *literal*
6
+ # string, versus those that can be encoded in the PDF hexadecimal format.
7
7
  #
8
- # Some features of the PDF format appear to require that literal
9
- # strings be used. One such feature is the /Dest key of a link
10
- # annotation; if a hex encoded string is used there, the links
11
- # do not work (as tested in Mac OS X Preview, and Adobe Acrobat
12
- # Reader).
13
- class LiteralString < String #:nodoc:
8
+ # Some features of the PDF format appear to require that literal strings be
9
+ # used. One such feature is the `Dest` key of a link annotation; if a hex
10
+ # encoded string is used there, the links do not work (as tested in Mac OS
11
+ # X Preview, and Adobe Acrobat Reader).
12
+ #
13
+ # @api private
14
+ class LiteralString < String
14
15
  end
15
16
  end
16
17
  end
@@ -1,22 +1,39 @@
1
- # encoding: utf-8
2
-
3
- # name_tree.rb : Implements NameTree for PDF
4
- #
5
- # Copyright November 2008, Jamis Buck. All Rights Reserved.
6
- #
7
- # This is free software. Please see the LICENSE and COPYING files for details.
8
- #
1
+ # frozen_string_literal: true
2
+
3
+ require 'pdf/core/utils'
4
+
9
5
  module PDF
10
6
  module Core
11
- module NameTree #:nodoc:
12
- class Node #:nodoc:
7
+ # Name Tree for PDF
8
+ #
9
+ # @api private
10
+ module NameTree
11
+ # Name Tree node
12
+ #
13
+ # @api private
14
+ class Node
15
+ # Child nodes
16
+ # @return [Array<Node>]
13
17
  attr_reader :children
18
+
19
+ # Children number limit
20
+ # @return [Integer]
14
21
  attr_reader :limit
22
+
23
+ # @return [Prawn::Document]
15
24
  attr_reader :document
25
+
26
+ # Parent node
27
+ # @return [Node]
16
28
  attr_accessor :parent
29
+
30
+ # @return [Reference]
17
31
  attr_accessor :ref
18
32
 
19
- def initialize(document, limit, parent=nil)
33
+ # @param document [Prawn::Document] owning document
34
+ # @param limit [Integer] Children limit
35
+ # @param parent [Node] Parent node
36
+ def initialize(document, limit, parent = nil)
20
37
  @document = document
21
38
  @children = []
22
39
  @limit = limit
@@ -24,22 +41,37 @@ module PDF
24
41
  @ref = nil
25
42
  end
26
43
 
44
+ # Tells whether there are any children nodes
45
+ #
46
+ # @return [Boolean]
27
47
  def empty?
28
48
  children.empty?
29
49
  end
30
50
 
51
+ # Number of all (including nested) children nodes
52
+ #
53
+ # @return [Integer]
31
54
  def size
32
- leaf? ? children.size : children.inject(0) { |sum, child| sum + child.size }
55
+ leaf? ? children.size : children.sum(&:size)
33
56
  end
34
57
 
58
+ # Tells whether this is a leaf node. A leaf node is the one that has no
59
+ # children or only {Value} children.
60
+ #
61
+ # @return [Boolean]
35
62
  def leaf?
36
63
  children.empty? || children.first.is_a?(Value)
37
64
  end
38
65
 
66
+ # Adds a value
67
+ #
68
+ # @param name [String]
69
+ # @param value [any]
39
70
  def add(name, value)
40
71
  self << Value.new(name, value)
41
72
  end
42
73
 
74
+ # @return [Hash] a hash representation of this node
43
75
  def to_hash
44
76
  hash = {}
45
77
 
@@ -47,12 +79,13 @@ module PDF
47
79
  if leaf?
48
80
  hash[:Names] = children if leaf?
49
81
  else
50
- hash[:Kids] = children.map { |child| child.ref }
82
+ hash[:Kids] = children.map(&:ref)
51
83
  end
52
84
 
53
- return hash
85
+ hash
54
86
  end
55
87
 
88
+ # @return [String] the least (in lexicographic order) value name
56
89
  def least
57
90
  if leaf?
58
91
  children.first.name
@@ -61,6 +94,7 @@ module PDF
61
94
  end
62
95
  end
63
96
 
97
+ # @return [String] the greatest (in lexicographic order) value name
64
98
  def greatest
65
99
  if leaf?
66
100
  children.last.name
@@ -69,6 +103,10 @@ module PDF
69
103
  end
70
104
  end
71
105
 
106
+ # Insert value maintaining order and rebalancing tree if needed.
107
+ #
108
+ # @param value [Value]
109
+ # @return [value]
72
110
  def <<(value)
73
111
  if children.empty?
74
112
  children << value
@@ -76,98 +114,124 @@ module PDF
76
114
  children.insert(insertion_point(value), value)
77
115
  split! if children.length > limit
78
116
  else
79
- fit = children.detect { |child| child >= value }
80
- fit = children.last unless fit
117
+ fit = children.find { |child| child >= value }
118
+ fit ||= children.last
81
119
  fit << value
82
120
  end
83
121
 
84
122
  value
85
123
  end
86
124
 
87
- def >=(value)
88
- children.empty? || children.last >= value
125
+ # This is a compatibility method to allow uniform comparison between
126
+ # nodes and values.
127
+ #
128
+ # @api private
129
+ # @return [Boolean]
130
+ # @see Value#<=>
131
+ def >=(other)
132
+ children.empty? || children.last >= other
89
133
  end
90
134
 
135
+ # Split the tree at the node.
136
+ #
137
+ # @return [void]
91
138
  def split!
92
139
  if parent
93
140
  parent.split(self)
94
141
  else
95
- left, right = new_node(self), new_node(self)
142
+ left = new_node(self)
143
+ right = new_node(self)
96
144
  split_children(self, left, right)
97
145
  children.replace([left, right])
98
146
  end
99
147
  end
100
148
 
101
149
  # Returns a deep copy of this node, without copying expensive things
102
- # like the ref to @document.
150
+ # like the `ref` to `document`.
103
151
  #
152
+ # @return [Node]
104
153
  def deep_copy
105
154
  node = dup
106
- node.instance_variable_set("@children",
107
- Marshal.load(Marshal.dump(children)))
108
- node.instance_variable_set("@ref",
109
- node.ref ? node.ref.deep_copy : nil)
155
+ node.instance_variable_set(:@children, Utils.deep_clone(children))
156
+ node.instance_variable_set(:@ref, node.ref ? node.ref.deep_copy : nil)
110
157
  node
111
158
  end
112
159
 
113
160
  protected
114
161
 
115
- def split(node)
116
- new_child = new_node(self)
117
- split_children(node, node, new_child)
118
- index = children.index(node)
119
- children.insert(index+1, new_child)
120
- split! if children.length > limit
121
- end
162
+ def split(node)
163
+ new_child = new_node(self)
164
+ split_children(node, node, new_child)
165
+ index = children.index(node)
166
+ children.insert(index + 1, new_child)
167
+ split! if children.length > limit
168
+ end
122
169
 
123
170
  private
124
171
 
125
- def new_node(parent=nil)
126
- node = Node.new(document, limit, parent)
127
- node.ref = document.ref!(node)
128
- return node
129
- end
172
+ def new_node(parent = nil)
173
+ node = Node.new(document, limit, parent)
174
+ node.ref = document.ref!(node)
175
+ node
176
+ end
130
177
 
131
- def split_children(node, left, right)
132
- half = (node.limit+1)/2
178
+ def split_children(node, left, right)
179
+ half = (node.limit + 1) / 2
133
180
 
134
- left_children, right_children = node.children[0...half], node.children[half..-1]
181
+ left_children = node.children[0...half]
182
+ right_children = node.children[half..]
135
183
 
136
- left.children.replace(left_children)
137
- right.children.replace(right_children)
184
+ left.children.replace(left_children)
185
+ right.children.replace(right_children)
138
186
 
139
- unless node.leaf?
140
- left_children.each { |child| child.parent = left }
141
- right_children.each { |child| child.parent = right }
142
- end
187
+ unless node.leaf?
188
+ left_children.each { |child| child.parent = left }
189
+ right_children.each { |child| child.parent = right }
143
190
  end
191
+ end
144
192
 
145
- def insertion_point(value)
146
- children.each_with_index do |child, index|
147
- return index if child >= value
148
- end
149
- return children.length
193
+ def insertion_point(value)
194
+ children.each_with_index do |child, index|
195
+ return index if child >= value
150
196
  end
197
+ children.length
198
+ end
151
199
  end
152
200
 
153
- class Value #:nodoc:
201
+ # # Name Tree value
202
+ #
203
+ # @api private
204
+ class Value
154
205
  include Comparable
155
206
 
207
+ # @return [String]
156
208
  attr_reader :name
209
+
210
+ # @return [any]
157
211
  attr_reader :value
158
212
 
213
+ # @param name [String]
214
+ # @param value [any]
159
215
  def initialize(name, value)
160
- @name, @value = PDF::Core::LiteralString.new(name), value
216
+ @name = PDF::Core::LiteralString.new(name)
217
+ @value = value
161
218
  end
162
219
 
163
- def <=>(leaf)
164
- name <=> leaf.name
220
+ # @param other [Value]
221
+ # @return [-1, 0, 1]
222
+ # @see Object#<=>
223
+ # @see Enumerable
224
+ def <=>(other)
225
+ name <=> other.name
165
226
  end
166
227
 
228
+ # @return [String] a string containing a human-readable representation
229
+ # of this value object
167
230
  def inspect
168
231
  "#<Value: #{name.inspect} : #{value.inspect}>"
169
232
  end
170
233
 
234
+ # @return [String] a string representation of this value
171
235
  def to_s
172
236
  "#{name} : #{value}"
173
237
  end
@@ -1,106 +1,151 @@
1
- # encoding: utf-8
2
-
3
- # Implements PDF object repository
4
- #
5
- # Copyright August 2009, Brad Ediger. 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
3
  module PDF
10
4
  module Core
11
- class ObjectStore #:nodoc:
5
+ # PDF object repository
6
+ #
7
+ # @api private
8
+ class ObjectStore
12
9
  include Enumerable
13
10
 
11
+ # Minimum PDF version
12
+ # @return [Float]
14
13
  attr_reader :min_version
15
14
 
15
+ # @param opts [Hash]
16
+ # @option opts :info [Hash] Documnt info dict
17
+ # @option opts :print_scaling [:none, nil] (nil) Print scaling viewer
18
+ # option
16
19
  def initialize(opts = {})
17
20
  @objects = {}
18
21
  @identifiers = []
19
22
 
20
- @info ||= ref(opts[:info] || {}).identifier
21
- @root ||= ref(:Type => :Catalog).identifier
23
+ @info ||= ref(opts[:info] || {}).identifier
24
+ @root ||= ref(Type: :Catalog).identifier
22
25
  if opts[:print_scaling] == :none
23
- root.data[:ViewerPreferences] = {:PrintScaling => :None}
26
+ root.data[:ViewerPreferences] = { PrintScaling: :None }
24
27
  end
25
28
  if pages.nil?
26
- root.data[:Pages] = ref(:Type => :Pages, :Count => 0, :Kids => [])
29
+ root.data[:Pages] = ref(Type: :Pages, Count: 0, Kids: [])
27
30
  end
28
31
  end
29
32
 
30
- def ref(data, &block)
31
- push(size + 1, data, &block)
33
+ # Wrap an object into a reference.
34
+ #
35
+ # @param data [Hash, Array, Numeric, String, Symbol, Date, Time, nil]
36
+ # object data
37
+ # @return [Reference]
38
+ def ref(data)
39
+ push(size + 1, data)
32
40
  end
33
41
 
42
+ # Document info dict reference
43
+ #
44
+ # @return [Reference]
34
45
  def info
35
46
  @objects[@info]
36
47
  end
37
48
 
49
+ # Document root dict reference
50
+ #
51
+ # @return [Reference]
38
52
  def root
39
53
  @objects[@root]
40
54
  end
41
55
 
56
+ # Document pages reference
57
+ #
58
+ # @return [Reference]
42
59
  def pages
43
60
  root.data[:Pages]
44
61
  end
45
62
 
63
+ # Number of pages in the document
64
+ #
65
+ # @return [Integer]
46
66
  def page_count
47
67
  pages.data[:Count]
48
68
  end
49
69
 
50
70
  # Adds the given reference to the store and returns the reference object.
51
- # If the object provided is not a PDF::Core::Reference, one is created from the
52
- # arguments provided.
71
+ # If the object provided is not a PDF::Core::Reference, one is created
72
+ # from the arguments provided.
53
73
  #
54
- def push(*args, &block)
55
- reference = if args.first.is_a?(PDF::Core::Reference)
56
- args.first
57
- else
58
- PDF::Core::Reference.new(*args, &block)
59
- end
74
+ # @overload push(reference)
75
+ # @param reference [Reference]
76
+ # @return [reference]
77
+ # @overload push(id, data)
78
+ # @param id [Integer] reference identifier
79
+ # @param data [Hash, Array, Numeric, String, Symbol, Date, Time, nil]
80
+ # object data
81
+ # @return [Reference] - the added reference
82
+ def push(*args)
83
+ reference =
84
+ if args.first.is_a?(PDF::Core::Reference)
85
+ args.first
86
+ else
87
+ PDF::Core::Reference.new(*args)
88
+ end
60
89
 
61
90
  @objects[reference.identifier] = reference
62
91
  @identifiers << reference.identifier
63
92
  reference
64
93
  end
65
94
 
66
- alias_method :<<, :push
95
+ alias << push
67
96
 
97
+ # Iterate over document object references.
98
+ #
99
+ # @yieldparam ref [Reference]
100
+ # @return [void]
68
101
  def each
69
102
  @identifiers.each do |id|
70
- yield @objects[id]
103
+ yield(@objects[id])
71
104
  end
72
105
  end
73
106
 
107
+ # Get object reference by its identifier.
108
+ #
109
+ # @param id [Integer] object identifier
110
+ # @return [Reference]
74
111
  def [](id)
75
112
  @objects[id]
76
113
  end
77
114
 
115
+ # Number of object references in the document.
116
+ #
117
+ # @return [Integer]
78
118
  def size
79
119
  @identifiers.size
80
120
  end
81
- alias_method :length, :size
121
+ alias length size
82
122
 
83
- # returns the object ID for a particular page in the document. Pages
84
- # are indexed starting at 1 (not 0!).
123
+ # Get page reference identifier by page number.Pages are indexed starting
124
+ # at 1 (**not 0**).
85
125
  #
126
+ # @example
127
+ # !!!ruby
86
128
  # object_id_for_page(1)
87
- # => 5
129
+ # #=> 5
88
130
  # object_id_for_page(10)
89
- # => 87
131
+ # #=> 87
90
132
  # object_id_for_page(-11)
91
- # => 17
133
+ # #=> 17
92
134
  #
93
- def object_id_for_page(k)
94
- k -= 1 if k > 0
135
+ # @param page [Integer] page number
136
+ # @return [Integer] page object identifier
137
+ def object_id_for_page(page)
138
+ page -= 1 if page.positive?
95
139
  flat_page_ids = get_page_objects(pages).flatten
96
- flat_page_ids[k]
140
+ flat_page_ids[page]
97
141
  end
98
142
 
99
- def is_utf8?(str)
100
- str.force_encoding(::Encoding::UTF_8)
101
- str.valid_encoding?
143
+ private
144
+
145
+ # returns an array with the object IDs for all pages
146
+ def get_page_objects(pages)
147
+ pages.data[:Kids].map(&:identifier)
102
148
  end
103
149
  end
104
150
  end
105
151
  end
106
-