File: name_tree.rb

package info (click to toggle)
ruby-pdf-core 0.10.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 408 kB
  • sloc: ruby: 2,270; makefile: 4
file content (241 lines) | stat: -rw-r--r-- 6,022 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# frozen_string_literal: true

require 'pdf/core/utils'

module PDF
  module Core
    # Name Tree for PDF
    #
    # @api private
    module NameTree
      # Name Tree node
      #
      # @api private
      class Node
        # Child nodes
        # @return [Array<Node>]
        attr_reader :children

        # Children number limit
        # @return [Integer]
        attr_reader :limit

        # @return [Prawn::Document]
        attr_reader :document

        # Parent node
        # @return [Node]
        attr_accessor :parent

        # @return [Reference]
        attr_accessor :ref

        # @param document [Prawn::Document] owning document
        # @param limit [Integer] Children limit
        # @param parent [Node] Parent node
        def initialize(document, limit, parent = nil)
          @document = document
          @children = []
          @limit = limit
          @parent = parent
          @ref = nil
        end

        # Tells whether there are any children nodes
        #
        # @return [Boolean]
        def empty?
          children.empty?
        end

        # Number of all (including nested) children nodes
        #
        # @return [Integer]
        def size
          leaf? ? children.size : children.sum(&:size)
        end

        # Tells whether this is a leaf node. A leaf node is the one that has no
        # children or only {Value} children.
        #
        # @return [Boolean]
        def leaf?
          children.empty? || children.first.is_a?(Value)
        end

        # Adds a value
        #
        # @param name [String]
        # @param value [any]
        def add(name, value)
          self << Value.new(name, value)
        end

        # @return [Hash] a hash representation of this node
        def to_hash
          hash = {}

          hash[:Limits] = [least, greatest] if parent
          if leaf?
            hash[:Names] = children if leaf?
          else
            hash[:Kids] = children.map(&:ref)
          end

          hash
        end

        # @return [String] the least (in lexicographic order) value name
        def least
          if leaf?
            children.first.name
          else
            children.first.least
          end
        end

        # @return [String] the greatest (in lexicographic order) value name
        def greatest
          if leaf?
            children.last.name
          else
            children.last.greatest
          end
        end

        # Insert value maintaining order and rebalancing tree if needed.
        #
        # @param value [Value]
        # @return [value]
        def <<(value)
          if children.empty?
            children << value
          elsif leaf?
            children.insert(insertion_point(value), value)
            split! if children.length > limit
          else
            fit = children.find { |child| child >= value }
            fit ||= children.last
            fit << value
          end

          value
        end

        # This is a compatibility method to allow uniform comparison between
        # nodes and values.
        #
        # @api private
        # @return [Boolean]
        # @see Value#<=>
        def >=(other)
          children.empty? || children.last >= other
        end

        # Split the tree at the node.
        #
        # @return [void]
        def split!
          if parent
            parent.split(self)
          else
            left = new_node(self)
            right = new_node(self)
            split_children(self, left, right)
            children.replace([left, right])
          end
        end

        # Returns a deep copy of this node, without copying expensive things
        # like the `ref` to `document`.
        #
        # @return [Node]
        def deep_copy
          node = dup
          node.instance_variable_set(:@children, Utils.deep_clone(children))
          node.instance_variable_set(:@ref, node.ref ? node.ref.deep_copy : nil)
          node
        end

        protected

        def split(node)
          new_child = new_node(self)
          split_children(node, node, new_child)
          index = children.index(node)
          children.insert(index + 1, new_child)
          split! if children.length > limit
        end

        private

        def new_node(parent = nil)
          node = Node.new(document, limit, parent)
          node.ref = document.ref!(node)
          node
        end

        def split_children(node, left, right)
          half = (node.limit + 1) / 2

          left_children = node.children[0...half]
          right_children = node.children[half..]

          left.children.replace(left_children)
          right.children.replace(right_children)

          unless node.leaf?
            left_children.each { |child| child.parent = left }
            right_children.each { |child| child.parent = right }
          end
        end

        def insertion_point(value)
          children.each_with_index do |child, index|
            return index if child >= value
          end
          children.length
        end
      end

      # # Name Tree value
      #
      # @api private
      class Value
        include Comparable

        # @return [String]
        attr_reader :name

        # @return [any]
        attr_reader :value

        # @param name [String]
        # @param value [any]
        def initialize(name, value)
          @name = PDF::Core::LiteralString.new(name)
          @value = value
        end

        # @param other [Value]
        # @return [-1, 0, 1]
        # @see Object#<=>
        # @see Enumerable
        def <=>(other)
          name <=> other.name
        end

        # @return [String] a string containing a human-readable representation
        #   of this value object
        def inspect
          "#<Value: #{name.inspect} : #{value.inspect}>"
        end

        # @return [String] a string representation of this value
        def to_s
          "#{name} : #{value}"
        end
      end
    end
  end
end