pdf-core 0.8.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,27 +1,28 @@
1
-
2
1
  # frozen_string_literal: true
3
2
 
4
- #
5
- # Implements graphics state saving and restoring
6
- #
7
- # Copyright January 2010, Michael Witrant. All Rights Reserved.
8
- #
9
- # This is free software. Please see the LICENSE and COPYING files for details
10
- #
11
-
12
3
  module PDF
13
4
  module Core
5
+ # Graphics state saving and restoring
14
6
  class GraphicStateStack
7
+ # Graphic state stack
15
8
  attr_accessor :stack
16
9
 
10
+ # @param previous_state [GraphicState, nil]
17
11
  def initialize(previous_state = nil)
18
12
  self.stack = [GraphicState.new(previous_state)]
19
13
  end
20
14
 
15
+ # Pushes graphic state onto stack
16
+ #
17
+ # @param graphic_state [GraphicState, nil]
18
+ # @return [void]
21
19
  def save_graphic_state(graphic_state = nil)
22
20
  stack.push(GraphicState.new(graphic_state || current_state))
23
21
  end
24
22
 
23
+ # Restores previous graphic state
24
+ #
25
+ # @return [void]
25
26
  def restore_graphic_state
26
27
  if stack.empty?
27
28
  raise PDF::Core::Errors::EmptyGraphicStateStack,
@@ -30,64 +31,107 @@ module PDF
30
31
  stack.pop
31
32
  end
32
33
 
34
+ # Current graphic state
35
+ #
36
+ # @return [GraphicState]
33
37
  def current_state
34
38
  stack.last
35
39
  end
36
40
 
41
+ # Tells whether there are any saved graphic states
42
+ #
43
+ # @return [Boolean]
44
+ # @see #empty?
37
45
  def present?
38
46
  !stack.empty?
39
47
  end
40
48
 
49
+ # Tells whether there are no saved graphic states
50
+ #
51
+ # @return [Boolean]
52
+ # @see #present?
41
53
  def empty?
42
54
  stack.empty?
43
55
  end
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
87
+
88
+ # Stroke color
89
+ attr_accessor :stroke_color
50
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'
96
+ @color_space = {}
97
+ @fill_color = '000000'
57
98
  @stroke_color = '000000'
58
- @dash = { dash: nil, space: nil, phase: 0 }
59
- @cap_style = :butt
60
- @join_style = :miter
61
- @line_width = 1
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
110
  return '[] 0 d' unless @dash[:dash]
67
111
 
68
- array = if @dash[:dash].is_a?(Array)
69
- @dash[:dash]
70
- else
71
- [@dash[:dash], @dash[:space]]
72
- end
112
+ array =
113
+ if @dash[:dash].is_a?(Array)
114
+ @dash[:dash]
115
+ else
116
+ [@dash[:dash], @dash[:space]]
117
+ end
73
118
 
74
- "[#{PDF::Core.real_params(array)}] "\
75
- "#{PDF::Core.real(@dash[:phase])} d"
119
+ "[#{PDF::Core.real_params(array)}] #{PDF::Core.real(@dash[:phase])} d"
76
120
  end
77
121
 
78
122
  private
79
123
 
80
124
  def initialize_copy(other)
81
125
  # mutable state
82
- @color_space = other.color_space.dup
83
- @fill_color = other.fill_color.dup
126
+ @color_space = other.color_space.dup
127
+ @fill_color = other.fill_color.dup
84
128
  @stroke_color = other.stroke_color.dup
85
- @dash = other.dash.dup
129
+ @dash = other.dash.dup
86
130
 
87
131
  # immutable state that doesn't need to be duped
88
- @cap_style = other.cap_style
89
- @join_style = other.join_style
90
- @line_width = other.line_width
132
+ @cap_style = other.cap_style
133
+ @join_style = other.join_style
134
+ @line_width = other.line_width
91
135
  end
92
136
  end
93
137
  end
@@ -1,18 +1,17 @@
1
-
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module PDF
5
4
  module Core
6
- # This is used to differentiate strings that must be encoded as
7
- # a *literal* string, versus those that can be encoded in
8
- # 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
+ #
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).
9
12
  #
10
- # Some features of the PDF format appear to require that literal
11
- # strings be used. One such feature is the /Dest key of a link
12
- # annotation; if a hex encoded string is used there, the links
13
- # do not work (as tested in Mac OS X Preview, and Adobe Acrobat
14
- # Reader).
15
- class LiteralString < String #:nodoc:
13
+ # @api private
14
+ class LiteralString < String
16
15
  end
17
16
  end
18
17
  end
@@ -2,22 +2,37 @@
2
2
 
3
3
  require 'pdf/core/utils'
4
4
 
5
- # name_tree.rb : Implements NameTree for PDF
6
- #
7
- # Copyright November 2008, Jamis Buck. All Rights Reserved.
8
- #
9
- # This is free software. Please see the LICENSE and COPYING files for details.
10
- #
11
5
  module PDF
12
6
  module Core
13
- module NameTree #:nodoc:
14
- 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>]
15
17
  attr_reader :children
18
+
19
+ # Children number limit
20
+ # @return [Integer]
16
21
  attr_reader :limit
22
+
23
+ # @return [Prawn::Document]
17
24
  attr_reader :document
25
+
26
+ # Parent node
27
+ # @return [Node]
18
28
  attr_accessor :parent
29
+
30
+ # @return [Reference]
19
31
  attr_accessor :ref
20
32
 
33
+ # @param document [Prawn::Document] owning document
34
+ # @param limit [Integer] Children limit
35
+ # @param parent [Node] Parent node
21
36
  def initialize(document, limit, parent = nil)
22
37
  @document = document
23
38
  @children = []
@@ -26,22 +41,37 @@ module PDF
26
41
  @ref = nil
27
42
  end
28
43
 
44
+ # Tells whether there are any children nodes
45
+ #
46
+ # @return [Boolean]
29
47
  def empty?
30
48
  children.empty?
31
49
  end
32
50
 
51
+ # Number of all (including nested) children nodes
52
+ #
53
+ # @return [Integer]
33
54
  def size
34
- leaf? ? children.size : children.map(&:size).reduce(:+)
55
+ leaf? ? children.size : children.sum(&:size)
35
56
  end
36
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]
37
62
  def leaf?
38
63
  children.empty? || children.first.is_a?(Value)
39
64
  end
40
65
 
66
+ # Adds a value
67
+ #
68
+ # @param name [String]
69
+ # @param value [any]
41
70
  def add(name, value)
42
71
  self << Value.new(name, value)
43
72
  end
44
73
 
74
+ # @return [Hash] a hash representation of this node
45
75
  def to_hash
46
76
  hash = {}
47
77
 
@@ -55,6 +85,7 @@ module PDF
55
85
  hash
56
86
  end
57
87
 
88
+ # @return [String] the least (in lexicographic order) value name
58
89
  def least
59
90
  if leaf?
60
91
  children.first.name
@@ -63,6 +94,7 @@ module PDF
63
94
  end
64
95
  end
65
96
 
97
+ # @return [String] the greatest (in lexicographic order) value name
66
98
  def greatest
67
99
  if leaf?
68
100
  children.last.name
@@ -71,6 +103,10 @@ module PDF
71
103
  end
72
104
  end
73
105
 
106
+ # Insert value maintaining order and rebalancing tree if needed.
107
+ #
108
+ # @param value [Value]
109
+ # @return [value]
74
110
  def <<(value)
75
111
  if children.empty?
76
112
  children << value
@@ -78,7 +114,7 @@ module PDF
78
114
  children.insert(insertion_point(value), value)
79
115
  split! if children.length > limit
80
116
  else
81
- fit = children.detect { |child| child >= value }
117
+ fit = children.find { |child| child >= value }
82
118
  fit ||= children.last
83
119
  fit << value
84
120
  end
@@ -86,10 +122,19 @@ module PDF
86
122
  value
87
123
  end
88
124
 
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#<=>
89
131
  def >=(other)
90
132
  children.empty? || children.last >= other
91
133
  end
92
134
 
135
+ # Split the tree at the node.
136
+ #
137
+ # @return [void]
93
138
  def split!
94
139
  if parent
95
140
  parent.split(self)
@@ -102,13 +147,13 @@ module PDF
102
147
  end
103
148
 
104
149
  # Returns a deep copy of this node, without copying expensive things
105
- # like the ref to @document.
150
+ # like the `ref` to `document`.
106
151
  #
152
+ # @return [Node]
107
153
  def deep_copy
108
154
  node = dup
109
- node.instance_variable_set('@children', Utils.deep_clone(children))
110
- node.instance_variable_set('@ref',
111
- 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)
112
157
  node
113
158
  end
114
159
 
@@ -134,7 +179,7 @@ module PDF
134
179
  half = (node.limit + 1) / 2
135
180
 
136
181
  left_children = node.children[0...half]
137
- right_children = node.children[half..-1]
182
+ right_children = node.children[half..]
138
183
 
139
184
  left.children.replace(left_children)
140
185
  right.children.replace(right_children)
@@ -153,25 +198,40 @@ module PDF
153
198
  end
154
199
  end
155
200
 
156
- class Value #:nodoc:
201
+ # # Name Tree value
202
+ #
203
+ # @api private
204
+ class Value
157
205
  include Comparable
158
206
 
207
+ # @return [String]
159
208
  attr_reader :name
209
+
210
+ # @return [any]
160
211
  attr_reader :value
161
212
 
213
+ # @param name [String]
214
+ # @param value [any]
162
215
  def initialize(name, value)
163
216
  @name = PDF::Core::LiteralString.new(name)
164
217
  @value = value
165
218
  end
166
219
 
220
+ # @param other [Value]
221
+ # @return [-1, 0, 1]
222
+ # @see Object#<=>
223
+ # @see Enumerable
167
224
  def <=>(other)
168
225
  name <=> other.name
169
226
  end
170
227
 
228
+ # @return [String] a string containing a human-readable representation
229
+ # of this value object
171
230
  def inspect
172
231
  "#<Value: #{name.inspect} : #{value.inspect}>"
173
232
  end
174
233
 
234
+ # @return [String] a string representation of this value
175
235
  def to_s
176
236
  "#{name} : #{value}"
177
237
  end
@@ -1,24 +1,27 @@
1
1
  # frozen_string_literal: true
2
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.
8
-
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
26
  root.data[:ViewerPreferences] = { PrintScaling: :None }
24
27
  end
@@ -27,22 +30,39 @@ module PDF
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
@@ -51,12 +71,20 @@ module PDF
51
71
  # If the object provided is not a PDF::Core::Reference, one is created
52
72
  # from the arguments provided.
53
73
  #
54
- def push(*args, &block)
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)
55
83
  reference =
56
84
  if args.first.is_a?(PDF::Core::Reference)
57
85
  args.first
58
86
  else
59
- PDF::Core::Reference.new(*args, &block)
87
+ PDF::Core::Reference.new(*args)
60
88
  end
61
89
 
62
90
  @objects[reference.identifier] = reference
@@ -66,36 +94,58 @@ module PDF
66
94
 
67
95
  alias << push
68
96
 
97
+ # Iterate over document object references.
98
+ #
99
+ # @yieldparam ref [Reference]
100
+ # @return [void]
69
101
  def each
70
102
  @identifiers.each do |id|
71
- yield @objects[id]
103
+ yield(@objects[id])
72
104
  end
73
105
  end
74
106
 
107
+ # Get object reference by its identifier.
108
+ #
109
+ # @param id [Integer] object identifier
110
+ # @return [Reference]
75
111
  def [](id)
76
112
  @objects[id]
77
113
  end
78
114
 
115
+ # Number of object references in the document.
116
+ #
117
+ # @return [Integer]
79
118
  def size
80
119
  @identifiers.size
81
120
  end
82
121
  alias length size
83
122
 
84
- # returns the object ID for a particular page in the document. Pages
85
- # are indexed starting at 1 (not 0!).
123
+ # Get page reference identifier by page number.Pages are indexed starting
124
+ # at 1 (**not 0**).
86
125
  #
126
+ # @example
127
+ # !!!ruby
87
128
  # object_id_for_page(1)
88
- # => 5
129
+ # #=> 5
89
130
  # object_id_for_page(10)
90
- # => 87
131
+ # #=> 87
91
132
  # object_id_for_page(-11)
92
- # => 17
133
+ # #=> 17
93
134
  #
135
+ # @param page [Integer] page number
136
+ # @return [Integer] page object identifier
94
137
  def object_id_for_page(page)
95
138
  page -= 1 if page.positive?
96
139
  flat_page_ids = get_page_objects(pages).flatten
97
140
  flat_page_ids[page]
98
141
  end
142
+
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)
148
+ end
99
149
  end
100
150
  end
101
151
  end
@@ -2,10 +2,55 @@
2
2
 
3
3
  module PDF
4
4
  module Core
5
- class OutlineItem #:nodoc:
6
- attr_accessor :count, :first, :last, :next, :prev, :parent, :title, :dest,
7
- :closed
5
+ # Outline item.
6
+ #
7
+ # @api private
8
+ # @see # PDF 1.7 spec, section 8.2.2 Document Outline
9
+ class OutlineItem
10
+ # The total number of its open descendants at all lower levels of the
11
+ # outline hierarchy.
12
+ # @return [Integer]
13
+ attr_accessor :count
8
14
 
15
+ # The first of this item’s immediate children in the outline hierarchy.
16
+ # @return [Reference<PDF::Core::OutlineItem>]
17
+ attr_accessor :first
18
+
19
+ # The last of this item’s immediate children in the outline hierarchy.
20
+ # @return [Reference<PDF::Core::OutlineItem>]
21
+ attr_accessor :last
22
+
23
+ # The next item at this outline level.
24
+ # @return [Reference<PDF::Core::OutlineItem>]
25
+ attr_accessor :next
26
+
27
+ # The previous item at this outline level.
28
+ # @return [Reference<PDF::Core::OutlineItem>]
29
+ attr_accessor :prev
30
+
31
+ # The parent of this item in the outline hierarchy.
32
+ # @return [Reference<[PDF::Core::OutlineItem, PDF::Core::OutlineRoot]>]
33
+ attr_accessor :parent
34
+
35
+ # The text to be displayed on the screen for this item.
36
+ # @return [String]
37
+ attr_accessor :title
38
+
39
+ # The destination to be displayed when this item is activated.
40
+ # @return [String]
41
+ # @return [Symbol]
42
+ # @return [Array]
43
+ # @see Destinations
44
+ attr_accessor :dest
45
+
46
+ # Is this item open or closed.
47
+ # @return [Boolean]
48
+ attr_accessor :closed
49
+
50
+ # @param title [String]
51
+ # @param parent [PDF::Core::OutlineRoot, PDF::Core::OutlineItem]
52
+ # @param options [Hash]
53
+ # @option options :closed [Boolean]
9
54
  def initialize(title, parent, options)
10
55
  @closed = options[:closed]
11
56
  @title = title
@@ -13,15 +58,18 @@ module PDF
13
58
  @count = 0
14
59
  end
15
60
 
61
+ # A hash representation of this outline item.
62
+ #
63
+ # @return [Hash]
16
64
  def to_hash
17
65
  hash = {
18
66
  Title: title,
19
67
  Parent: parent,
20
- Count: closed ? -count : count
68
+ Count: closed ? -count : count,
21
69
  }
22
70
  [
23
71
  { First: first }, { Last: last }, { Next: defined?(@next) && @next },
24
- { Prev: prev }, { Dest: dest }
72
+ { Prev: prev }, { Dest: dest },
25
73
  ].each do |h|
26
74
  unless h.values.first.nil?
27
75
  hash.merge!(h)
@@ -2,13 +2,29 @@
2
2
 
3
3
  module PDF
4
4
  module Core
5
- class OutlineRoot #:nodoc:
6
- attr_accessor :count, :first, :last
5
+ # Document Outline root.
6
+ #
7
+ # @api private
8
+ # @see # PDF 1.7 spec, section 8.2.2 Document Outline
9
+ class OutlineRoot
10
+ # The total number of open items at all levels of the outline.
11
+ # @return [Integer]
12
+ attr_accessor :count
13
+
14
+ # The first top-level item in the outline.
15
+ # @return [Reference]
16
+ attr_accessor :first
17
+
18
+ # The last top-level item in the outline.
19
+ # @return [Reference]
20
+ attr_accessor :last
7
21
 
8
22
  def initialize
9
23
  @count = 0
10
24
  end
11
25
 
26
+ # Hash representation of the outline root
27
+ # @return [Hash]
12
28
  def to_hash
13
29
  { Type: :Outlines, Count: count, First: first, Last: last }
14
30
  end