pdf-core 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,126 @@
1
+ # encoding: utf-8
2
+
3
+ # Describes PDF page geometries
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.
8
+
9
+ module PDF
10
+ module Core
11
+
12
+ # 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
+ 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
+
123
+ end
124
+ end
125
+ end
126
+
@@ -0,0 +1,99 @@
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.
8
+
9
+ # Top level Module
10
+ #
11
+ module PDF
12
+ module Core
13
+ module_function
14
+
15
+ def utf8_to_utf16(str)
16
+ "\xFE\xFF".force_encoding(::Encoding::UTF_16BE) + str.encode(::Encoding::UTF_16BE)
17
+ end
18
+
19
+ # encodes any string into a hex representation. The result is a string
20
+ # 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
22
+ def string_to_hex(str)
23
+ str.unpack("H*").first.force_encoding(::Encoding::US_ASCII)
24
+ end
25
+
26
+ # Serializes Ruby objects to their PDF equivalents. Most primitive objects
27
+ # 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)
30
+ #
31
+ # Examples:
32
+ #
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]]"
39
+ #
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"
45
+ 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
52
+ when Array
53
+ "[" << obj.map { |e| PdfObject(e, in_content_stream) }.join(' ') << "]"
54
+ when PDF::Core::LiteralString
55
+ obj = obj.gsub(/[\\\n\r\t\b\f\(\)]/n) { |m| "\\#{m}" }
56
+ "(#{obj})"
57
+ 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}" }
60
+ "(#{obj})"
61
+ when PDF::Core::ByteString
62
+ "<" << obj.unpack("H*").first << ">"
63
+ when String
64
+ 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
70
+ else
71
+ [n].pack("C*")
72
+ end
73
+ }.join
74
+ when ::Hash
75
+ output = "<< "
76
+ obj.each do |k,v|
77
+ unless String === k || Symbol === k
78
+ raise PDF::Core::Errors::FailedObjectConversion,
79
+ "A PDF Dictionary must be keyed by names"
80
+ end
81
+ output << PdfObject(k.to_sym, in_content_stream) << " " <<
82
+ PdfObject(v, in_content_stream) << "\n"
83
+ end
84
+ output << ">>"
85
+ when PDF::Core::Reference
86
+ obj.to_s
87
+ when PDF::Core::NameTree::Node
88
+ PdfObject(obj.to_hash)
89
+ 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)
93
+ else
94
+ raise PDF::Core::Errors::FailedObjectConversion,
95
+ "This object cannot be serialized to PDF (#{obj.inspect})"
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,103 @@
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.
8
+
9
+
10
+ module PDF
11
+ module Core
12
+ class Reference #:nodoc:
13
+
14
+ attr_accessor :gen, :data, :offset, :stream, :live, :identifier
15
+
16
+ def initialize(id, data)
17
+ @identifier = id
18
+ @gen = 0
19
+ @data = data
20
+ @stream = Stream.new
21
+ end
22
+
23
+ def object
24
+ output = "#{@identifier} #{gen} obj\n"
25
+ unless @stream.empty?
26
+ output << PDF::Core::PdfObject(data.merge @stream.data) << "\n" << @stream.object
27
+ else
28
+ output << PDF::Core::PdfObject(data) << "\n"
29
+ end
30
+
31
+ output << "endobj\n"
32
+ end
33
+
34
+ def <<(io)
35
+ raise "Cannot attach stream to non-dictionary object" unless @data.is_a?(::Hash)
36
+ (@stream ||= Stream.new) << io
37
+ end
38
+
39
+ def to_s
40
+ "#{@identifier} #{gen} R"
41
+ end
42
+
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.
45
+ #
46
+ def deep_copy(share=[])
47
+ r = dup
48
+
49
+ case r.data
50
+ when ::Hash
51
+ # Copy each entry not in +share+.
52
+ (r.data.keys - share).each do |k|
53
+ r.data[k] = Marshal.load(Marshal.dump(r.data[k]))
54
+ end
55
+ when PDF::Core::NameTree::Node
56
+ r.data = r.data.deep_copy
57
+ else
58
+ r.data = Marshal.load(Marshal.dump(r.data))
59
+ end
60
+
61
+ r.stream = Marshal.load(Marshal.dump(r.stream))
62
+ r
63
+ end
64
+
65
+ # Replaces the data and stream with that of other_ref.
66
+ def replace(other_ref)
67
+ @data = other_ref.data
68
+ @stream = other_ref.stream
69
+ end
70
+
71
+ # Marks this and all referenced objects live, recursively.
72
+ def mark_live
73
+ return if defined?(@live) && @live
74
+ @live = true
75
+ referenced_objects.each { |o| o.mark_live }
76
+ end
77
+
78
+ private
79
+
80
+ # All objects referenced by this one. Used for GC.
81
+ def referenced_objects(obj=@data)
82
+ case obj
83
+ when self.class
84
+ []
85
+ when ::Hash
86
+ obj.values.map{|v| [v] + referenced_objects(v) }
87
+ when Array
88
+ obj.map{|v| [v] + referenced_objects(v) }
89
+ when PDF::Core::OutlineRoot, PDF::Core::OutlineItem
90
+ referenced_objects(obj.to_hash)
91
+ else []
92
+ end.flatten.grep(self.class)
93
+ end
94
+
95
+ end
96
+
97
+ module_function
98
+
99
+ def Reference(*args, &block) #:nodoc:
100
+ Reference.new(*args, &block)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,98 @@
1
+ # encoding: utf-8
2
+
3
+ # prawn/core/stream.rb : Implements Stream objects
4
+ #
5
+ # Copyright February 2013, Alexander Mankuta. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ module PDF
10
+ module Core
11
+ class Stream
12
+ attr_reader :filters
13
+
14
+ def initialize(io = nil)
15
+ @filtered_stream = ''
16
+ @stream = io
17
+ @filters = FilterList.new
18
+ end
19
+
20
+ def <<(io)
21
+ (@stream ||= '') << io
22
+ @filtered_stream = nil
23
+ self
24
+ end
25
+
26
+ def compress!
27
+ unless @filters.names.include? :FlateDecode
28
+ @filtered_stream = nil
29
+ @filters << :FlateDecode
30
+ end
31
+ end
32
+
33
+ def compressed?
34
+ @filters.names.include? :FlateDecode
35
+ end
36
+
37
+ def empty?
38
+ @stream.nil?
39
+ end
40
+
41
+ def filtered_stream
42
+ if @stream
43
+ if @filtered_stream.nil?
44
+ @filtered_stream = @stream.dup
45
+
46
+ @filters.each do |(filter_name, params)|
47
+ if filter = PDF::Core::Filters.const_get(filter_name)
48
+ @filtered_stream = filter.encode @filtered_stream, params
49
+ end
50
+ end
51
+ end
52
+
53
+ @filtered_stream
54
+ # XXX Fillter stream
55
+ else
56
+ nil
57
+ end
58
+ end
59
+
60
+ def length
61
+ @stream.length
62
+ end
63
+
64
+ def object
65
+ if filtered_stream
66
+ "stream\n#{filtered_stream}\nendstream\n"
67
+ else
68
+ ''
69
+ end
70
+ end
71
+
72
+ def data
73
+ if @stream
74
+ filter_names = @filters.names
75
+ filter_params = @filters.decode_params
76
+
77
+ d = {
78
+ :Length => filtered_stream.length
79
+ }
80
+ if filter_names.any?
81
+ d[:Filter] = filter_names
82
+ end
83
+ if filter_params.any? {|f| !f.nil? }
84
+ d[:DecodeParms] = filter_params
85
+ end
86
+
87
+ d
88
+ else
89
+ {}
90
+ end
91
+ end
92
+
93
+ def inspect
94
+ "#<#{self.class.name}:0x#{'%014x' % object_id} @stream=#{@stream.inspect}, @filters=#{@filters.inspect}>"
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,275 @@
1
+ # encoding: utf-8
2
+
3
+ # prawn/core/text.rb : Implements low level text helpers for Prawn
4
+ #
5
+ # Copyright January 2010, Daniel Nelson. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ module PDF
10
+ module Core
11
+ module Text #:nodoc:
12
+
13
+ # These should be used as a base. Extensions may build on this list
14
+ #
15
+ VALID_OPTIONS = [:kerning, :size, :style]
16
+ MODES = { :fill => 0, :stroke => 1, :fill_stroke => 2, :invisible => 3,
17
+ :fill_clip => 4, :stroke_clip => 5, :fill_stroke_clip => 6,
18
+ :clip => 7 }
19
+
20
+ attr_reader :skip_encoding
21
+
22
+ # Low level text placement method. All font and size alterations
23
+ # should already be set
24
+ #
25
+ def draw_text!(text, options)
26
+ x,y = map_to_absolute(options[:at])
27
+ add_text_content(text,x,y,options)
28
+ end
29
+
30
+ # Low level call to set the current font style and extract text options from
31
+ # an options hash. Should be called from within a save_font block
32
+ #
33
+ def process_text_options(options)
34
+ if options[:style]
35
+ raise "Bad font family" unless font.family
36
+ font(font.family, :style => options[:style])
37
+ end
38
+
39
+ # must compare against false to keep kerning on as default
40
+ unless options[:kerning] == false
41
+ options[:kerning] = font.has_kerning_data?
42
+ end
43
+
44
+ options[:size] ||= font_size
45
+ end
46
+
47
+ # Retrieve the current default kerning setting.
48
+ #
49
+ # Defaults to true
50
+ #
51
+ def default_kerning?
52
+ return true if !defined?(@default_kerning)
53
+ @default_kerning
54
+ end
55
+
56
+ # Call with a boolean to set the document-wide kerning setting. This can be
57
+ # overridden using the :kerning text option when drawing text or a text
58
+ # box.
59
+ #
60
+ # pdf.default_kerning = false
61
+ # pdf.text("hello world") # text is not kerned
62
+ # pdf.text("hello world", :kerning => true) # text is kerned
63
+ #
64
+ def default_kerning(boolean)
65
+ @default_kerning = boolean
66
+ end
67
+
68
+ alias_method :default_kerning=, :default_kerning
69
+
70
+ # Call with no argument to retrieve the current default leading.
71
+ #
72
+ # Call with a number to set the document-wide text leading. This can be
73
+ # overridden using the :leading text option when drawing text or a text
74
+ # box.
75
+ #
76
+ # pdf.default_leading = 7
77
+ # pdf.text("hello world") # a leading of 7 is used
78
+ # pdf.text("hello world", :leading => 0) # a leading of 0 is used
79
+ #
80
+ # Defaults to 0
81
+ #
82
+ def default_leading(number=nil)
83
+ if number.nil?
84
+ defined?(@default_leading) && @default_leading || 0
85
+ else
86
+ @default_leading = number
87
+ end
88
+ end
89
+
90
+ alias_method :default_leading=, :default_leading
91
+
92
+ # Call with no argument to retrieve the current text direction.
93
+ #
94
+ # Call with a symbol to set the document-wide text direction. This can be
95
+ # overridden using the :direction text option when drawing text or a text
96
+ # box.
97
+ #
98
+ # pdf.text_direction = :rtl
99
+ # pdf.text("hello world") # prints "dlrow olleh"
100
+ # pdf.text("hello world", :direction => :ltr) # prints "hello world"
101
+ #
102
+ # Valid directions are:
103
+ #
104
+ # * :ltr - left-to-right (default)
105
+ # * :rtl - right-to-left
106
+ #
107
+ # Side effects:
108
+ #
109
+ # * When printing left-to-right, the default text alignment is :left
110
+ # * When printing right-to-left, the default text alignment is :right
111
+ #
112
+ def text_direction(direction=nil)
113
+ if direction.nil?
114
+ defined?(@text_direction) && @text_direction || :ltr
115
+ else
116
+ @text_direction = direction
117
+ end
118
+ end
119
+
120
+ alias_method :text_direction=, :text_direction
121
+
122
+ # Call with no argument to retrieve the current fallback fonts.
123
+ #
124
+ # Call with an array of font names. Each name must be the name of an AFM
125
+ # font or the name that was used to register a family of TTF fonts (see
126
+ # Prawn::Document#font_families). If present, then each glyph will be
127
+ # rendered using the first font that includes the glyph, starting with the
128
+ # current font and then moving through :fallback_fonts from left to right.
129
+ #
130
+ # Call with an empty array to turn off fallback fonts
131
+ #
132
+ # file = "#{Prawn::DATADIR}/fonts/gkai00mp.ttf"
133
+ # font_families["Kai"] = {
134
+ # :normal => { :file => file, :font => "Kai" }
135
+ # }
136
+ # file = "#{Prawn::DATADIR}/fonts/Action Man.dfont"
137
+ # font_families["Action Man"] = {
138
+ # :normal => { :file => file, :font => "ActionMan" },
139
+ # }
140
+ # fallback_fonts ["Times-Roman", "Kai"]
141
+ # font "Action Man"
142
+ # text "hello ƒ 你好"
143
+ # > hello prints in Action Man
144
+ # > ƒ prints in Times-Roman
145
+ # > 你好 prints in Kai
146
+ #
147
+ # fallback_fonts [] # clears document-wide fallback fonts
148
+ #
149
+ # Side effects:
150
+ #
151
+ # * Increased overhead when fallback fonts are declared as each glyph is
152
+ # checked to see whether it exists in the current font
153
+ #
154
+ def fallback_fonts(fallback_fonts=nil)
155
+ if fallback_fonts.nil?
156
+ defined?(@fallback_fonts) && @fallback_fonts || []
157
+ else
158
+ @fallback_fonts = fallback_fonts
159
+ end
160
+ end
161
+
162
+ alias_method :fallback_fonts=, :fallback_fonts
163
+
164
+ # Call with no argument to retrieve the current text rendering mode.
165
+ #
166
+ # Call with a symbol and block to temporarily change the current
167
+ # text rendering mode.
168
+ #
169
+ # pdf.text_rendering_mode(:stroke) do
170
+ # pdf.text("Outlined Text")
171
+ # end
172
+ #
173
+ # Valid modes are:
174
+ #
175
+ # * :fill - fill text (default)
176
+ # * :stroke - stroke text
177
+ # * :fill_stroke - fill, then stroke text
178
+ # * :invisible - invisible text
179
+ # * :fill_clip - fill text then add to path for clipping
180
+ # * :stroke_clip - stroke text then add to path for clipping
181
+ # * :fill_stroke_clip - fill then stroke text, then add to path for clipping
182
+ # * :clip - add text to path for clipping
183
+ #
184
+ # There's the special mode :unknown which only occurs when we're working
185
+ # with templates. If left in :unknown, the first text command will force
186
+ # an assertion to :fill.
187
+ def text_rendering_mode(mode=nil)
188
+ return (defined?(@text_rendering_mode) && @text_rendering_mode || :fill) if mode.nil?
189
+ unless MODES.key?(mode)
190
+ raise ArgumentError, "mode must be between one of #{MODES.keys.join(', ')} (#{mode})"
191
+ end
192
+ original_mode = self.text_rendering_mode
193
+ if original_mode == :unknown
194
+ original_mode = :fill
195
+ add_content "\n#{MODES[:fill]} Tr"
196
+ end
197
+ if original_mode == mode
198
+ yield
199
+ else
200
+ @text_rendering_mode = mode
201
+ add_content "\n#{MODES[mode]} Tr"
202
+ yield
203
+ add_content "\n#{MODES[original_mode]} Tr"
204
+ @text_rendering_mode = original_mode
205
+ end
206
+ end
207
+
208
+ def forget_text_rendering_mode!
209
+ @text_rendering_mode = :unknown
210
+ end
211
+
212
+ # Increases or decreases the space between characters.
213
+ # For horizontal text, a positive value will increase the space.
214
+ # For veritical text, a positive value will decrease the space.
215
+ #
216
+ def character_spacing(amount=nil)
217
+ return defined?(@character_spacing) && @character_spacing || 0 if amount.nil?
218
+ original_character_spacing = character_spacing
219
+ if original_character_spacing == amount
220
+ yield
221
+ else
222
+ @character_spacing = amount
223
+ add_content "\n%.3f Tc" % amount
224
+ yield
225
+ add_content "\n%.3f Tc" % original_character_spacing
226
+ @character_spacing = original_character_spacing
227
+ end
228
+ end
229
+
230
+ # Increases or decreases the space between words.
231
+ # For horizontal text, a positive value will increase the space.
232
+ # For veritical text, a positive value will decrease the space.
233
+ #
234
+ def word_spacing(amount=nil)
235
+ return defined?(@word_spacing) && @word_spacing || 0 if amount.nil?
236
+ original_word_spacing = word_spacing
237
+ if original_word_spacing == amount
238
+ yield
239
+ else
240
+ @word_spacing = amount
241
+ add_content "\n%.3f Tw" % amount
242
+ yield
243
+ add_content "\n%.3f Tw" % original_word_spacing
244
+ @word_spacing = original_word_spacing
245
+ end
246
+ end
247
+
248
+ private
249
+
250
+ def add_text_content(text, x, y, options)
251
+ chunks = font.encode_text(text,options)
252
+
253
+ add_content "\nBT"
254
+
255
+ if options[:rotate]
256
+ rad = options[:rotate].to_f * Math::PI / 180
257
+ arr = [ Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad), x, y ]
258
+ add_content "%.3f %.3f %.3f %.3f %.3f %.3f Tm" % arr
259
+ else
260
+ add_content "#{x} #{y} Td"
261
+ end
262
+
263
+ chunks.each do |(subset, string)|
264
+ font.add_to_current_page(subset)
265
+ add_content "/#{font.identifier_for(subset)} #{font_size} Tf"
266
+
267
+ operation = options[:kerning] && string.is_a?(Array) ? "TJ" : "Tj"
268
+ add_content PDF::Core::PdfObject(string, true) << " " << operation
269
+ end
270
+
271
+ add_content "ET\n"
272
+ end
273
+ end
274
+ end
275
+ end