combine_pdf 0.1.13 → 0.1.15

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 836492be159026a99d02304d504ddbf5fa2fb842
4
- data.tar.gz: 518dc2a996a97b25c4143ee4be147fef1dd65e94
3
+ metadata.gz: fcabdcfbb7f58ec22dbc23d3edc0182f3a8dc2ca
4
+ data.tar.gz: 9ff09d74f401086ac834c21a0059266c08b3c1ef
5
5
  SHA512:
6
- metadata.gz: c8c55e40b2f752757fe8588ec30b9a29767d6c28a9957d2a94ecf869b08c8ab07aa8560cc3d81590a2fa8721a5f547ca95d2aa9f721c2af50cfd6611131da617
7
- data.tar.gz: fd0be69a79f8048b39cdcdd45aabce11439cba505ae1703e54f1b135eaa8cd5d0697df8bfb08357556afa5ea2eb99f4fa9177f45adcad4ee83312c9e19369482
6
+ metadata.gz: f103e64dc370eb7742add0f21a1263adda67d53a2fd9e72848c29a9f81dac9db9b392a8a6b51dcc61aa2c78995fc2de8fb00aa561f376959498e357938bf9a08
7
+ data.tar.gz: 8c6fc6df3c6f046d1eb7a1da2f50088cb4e246cfd2ffd69edcbf74711f1a04d339bf782979e28cd8458beb1b28c61431938c1b3b8f413cddf5afd310e172415c
data/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ***
4
4
 
5
+ Change log v.0.1.14
6
+
7
+ **changes**: changed the way the PDF Page objects are 'injected' with their methods, so that the PDF#pages method is faster and more methods can be injected into the Hash object. For instance, textbox can now be called on an existing page without creating a PDFWriter object and 'stumping' the new data.
8
+
9
+ (the number_pages method hasn't been update to use this new feature as of yet)
10
+
11
+ ***
12
+
5
13
  Change log v.0.1.13
6
14
 
7
15
  **fix**: fix for Acrobat Reader compatablity (by removing color-space declarations). Should solve issue #13 , reported originaly by Imanol and Diyei Gomi.
data/lib/combine_pdf.rb CHANGED
@@ -3,12 +3,14 @@
3
3
  require 'zlib'
4
4
  require 'securerandom'
5
5
  require 'strscan'
6
+ require 'matrix'
6
7
 
7
8
  #require the RC4 Gem
8
9
  require 'rc4'
9
10
 
10
11
 
11
12
  load "combine_pdf/combine_pdf_operations.rb"
13
+ load "combine_pdf/combine_pdf_page.rb"
12
14
  load "combine_pdf/combine_pdf_basic_writer.rb"
13
15
  load "combine_pdf/combine_pdf_decrypt.rb"
14
16
  load "combine_pdf/combine_pdf_fonts.rb"
@@ -48,394 +48,399 @@ module CombinePDF
48
48
  self[:Contents] = { is_reference_only: true , referenced_object: {indirect_reference_id: 0, raw_stream_content: @contents} }
49
49
  self[:MediaBox] = mediabox
50
50
  end
51
- # accessor (getter) for the :MediaBox element of the page
52
- def mediabox
53
- self[:MediaBox]
54
- end
55
- # accessor (setter) for the :MediaBox element of the page
56
- # dimensions:: an Array consisting of four numbers (can be floats) setting the size of the media box.
57
- def mediabox=(dimensions = [0.0, 0.0, 612.0, 792.0])
58
- self[:MediaBox] = dimensions
59
- end
60
-
61
- # This method adds a simple text box to the Page represented by the PDFWriter class.
62
- # This function takes two values:
63
- # text:: the text to potin the box.
64
- # properties:: a Hash of box properties.
65
- # the symbols and values in the properties Hash could be any or all of the following:
66
- # x:: the left position of the box.
67
- # y:: the BUTTOM position of the box.
68
- # width:: the width/length of the box. negative values will be computed from edge of page. defaults to 0 (end of page).
69
- # height:: the height of the box. negative values will be computed from edge of page. defaults to 0 (end of page).
70
- # text_align:: symbol for horizontal text alignment, can be ":center" (default), ":right", ":left"
71
- # text_valign:: symbol for vertical text alignment, can be ":center" (default), ":top", ":buttom"
72
- # text_padding:: a Float between 0 and 1, setting the padding for the text. defaults to 0.05 (5%).
73
- # font:: a registered font name or an Array of names. defaults to ":Helvetica". The 14 standard fonts names are:
74
- # - :"Times-Roman"
75
- # - :"Times-Bold"
76
- # - :"Times-Italic"
77
- # - :"Times-BoldItalic"
78
- # - :Helvetica
79
- # - :"Helvetica-Bold"
80
- # - :"Helvetica-BoldOblique"
81
- # - :"Helvetica- Oblique"
82
- # - :Courier
83
- # - :"Courier-Bold"
84
- # - :"Courier-Oblique"
85
- # - :"Courier-BoldOblique"
86
- # - :Symbol
87
- # - :ZapfDingbats
88
- # font_size:: a Fixnum for the font size, or :fit_text to fit the text in the box. defaults to ":fit_text"
89
- # max_font_size:: if font_size is set to :fit_text, this will be the maximum font size. defaults to nil (no maximum)
90
- # font_color:: text color in [R, G, B], an array with three floats, each in a value between 0 to 1 (gray will be "[0.5, 0.5, 0.5]"). defaults to black.
91
- # stroke_color:: text stroke color in [R, G, B], an array with three floats, each in a value between 0 to 1 (gray will be "[0.5, 0.5, 0.5]"). defounlts to nil (no stroke).
92
- # stroke_width:: text stroke width in PDF units. defaults to 0 (none).
93
- # box_color:: box fill color in [R, G, B], an array with three floats, each in a value between 0 to 1 (gray will be "[0.5, 0.5, 0.5]"). defaults to nil (none).
94
- # border_color:: box border color in [R, G, B], an array with three floats, each in a value between 0 to 1 (gray will be "[0.5, 0.5, 0.5]"). defaults to nil (none).
95
- # border_width:: border width in PDF units. defaults to nil (none).
96
- # box_radius:: border radius in PDF units. defaults to 0 (no corner rounding).
97
- # opacity:: textbox opacity, a float between 0 (transparent) and 1 (opaque)
98
- def textbox(text, properties = {})
99
- options = {
100
- x: 0,
101
- y: 0,
102
- width: 0,
103
- height: -1,
104
- text_align: :center,
105
- text_valign: :center,
106
- text_padding: 0.1,
107
- font: nil,
108
- font_size: :fit_text,
109
- max_font_size: nil,
110
- font_color: [0,0,0],
111
- stroke_color: nil,
112
- stroke_width: 0,
113
- box_color: nil,
114
- border_color: nil,
115
- border_width: 0,
116
- box_radius: 0,
117
- opacity: 1,
118
- ctm: [1,0,0,1,0,0]
119
- }
120
- options.update properties
121
- # reset the length and height to meaningful values, if negative
122
- options[:width] = mediabox[2] - options[:x] + options[:width] if options[:width] <= 0
123
- options[:height] = mediabox[3] - options[:y] + options[:height] if options[:height] <= 0
124
-
125
- # reset the padding value
126
- options[:text_padding] = 0 if options[:text_padding].to_f >= 1
127
-
128
- # create box stream
129
- box_stream = ""
130
- # set graphic state for box
131
- if options[:box_color] || (options[:border_width].to_i > 0 && options[:border_color])
132
- # compute x and y position for text
133
- x = options[:x]
134
- y = options[:y]
135
-
136
- # set graphic state for the box
137
- box_stream << "q\n"
138
- box_graphic_state = { ca: options[:opacity], CA: options[:opacity], LW: options[:border_width], LC: 0, LJ: 0, LD: 0, CTM: options[:ctm]}
139
- if options[:box_radius] != 0 # if the text box has rounded corners
140
- box_graphic_state[:LC], box_graphic_state[:LJ] = 2, 1
141
- end
142
- box_graphic_state = graphic_state box_graphic_state # adds the graphic state to Resources and gets the reference
143
- box_stream << "#{PDFOperations._object_to_pdf box_graphic_state} gs\n"
144
-
145
- # the following line was removed for Acrobat Reader compatability
146
- # box_stream << "DeviceRGB CS\nDeviceRGB cs\n"
147
-
148
- if options[:box_color]
149
- box_stream << "#{options[:box_color].join(' ')} rg\n"
150
- end
151
- if options[:border_width].to_i > 0 && options[:border_color]
152
- box_stream << "#{options[:border_color].join(' ')} RG\n"
153
- end
154
- # create the path
155
- radius = options[:box_radius]
156
- half_radius = (radius.to_f / 2).round 4
157
- ## set starting point
158
- box_stream << "#{options[:x] + radius} #{options[:y]} m\n"
159
- ## buttom and right corner - first line and first corner
160
- box_stream << "#{options[:x] + options[:width] - radius} #{options[:y]} l\n" #buttom
161
- if options[:box_radius] != 0 # make first corner, if not straight.
162
- box_stream << "#{options[:x] + options[:width] - half_radius} #{options[:y]} "
163
- box_stream << "#{options[:x] + options[:width]} #{options[:y] + half_radius} "
164
- box_stream << "#{options[:x] + options[:width]} #{options[:y] + radius} c\n"
165
- end
166
- ## right and top-right corner
167
- box_stream << "#{options[:x] + options[:width]} #{options[:y] + options[:height] - radius} l\n"
168
- if options[:box_radius] != 0
169
- box_stream << "#{options[:x] + options[:width]} #{options[:y] + options[:height] - half_radius} "
170
- box_stream << "#{options[:x] + options[:width] - half_radius} #{options[:y] + options[:height]} "
171
- box_stream << "#{options[:x] + options[:width] - radius} #{options[:y] + options[:height]} c\n"
172
- end
173
- ## top and top-left corner
174
- box_stream << "#{options[:x] + radius} #{options[:y] + options[:height]} l\n"
175
- if options[:box_radius] != 0
176
- box_stream << "#{options[:x] + half_radius} #{options[:y] + options[:height]} "
177
- box_stream << "#{options[:x]} #{options[:y] + options[:height] - half_radius} "
178
- box_stream << "#{options[:x]} #{options[:y] + options[:height] - radius} c\n"
179
- end
180
- ## left and buttom-left corner
181
- box_stream << "#{options[:x]} #{options[:y] + radius} l\n"
182
- if options[:box_radius] != 0
183
- box_stream << "#{options[:x]} #{options[:y] + half_radius} "
184
- box_stream << "#{options[:x] + half_radius} #{options[:y]} "
185
- box_stream << "#{options[:x] + radius} #{options[:y]} c\n"
186
- end
187
- # fill / stroke path
188
- box_stream << "h\n"
189
- if options[:box_color] && options[:border_width].to_i > 0 && options[:border_color]
190
- box_stream << "B\n"
191
- elsif options[:box_color] # fill if fill color is set
192
- box_stream << "f\n"
193
- elsif options[:border_width].to_i > 0 && options[:border_color] # stroke if border is set
194
- box_stream << "S\n"
195
- end
196
-
197
- # exit graphic state for the box
198
- box_stream << "Q\n"
199
- end
200
- contents << box_stream
201
-
202
- # reset x,y by text alignment - x,y are calculated from the buttom left
203
- # each unit (1) is 1/72 Inch
204
- # create text stream
205
- text_stream = ""
206
- if text.to_s != "" && options[:font_size] != 0 && (options[:font_color] || options[:stroke_color])
207
- # compute x and y position for text
208
- x = options[:x] + (options[:width]*options[:text_padding])
209
- y = options[:y] + (options[:height]*options[:text_padding])
210
-
211
- # set the fonts (fonts array, with :Helvetica as fallback).
212
- fonts = [*options[:font], :Helvetica]
213
- # fit text in box, if requested
214
- font_size = options[:font_size]
215
- if options[:font_size] == :fit_text
216
- font_size = self.fit_text text, fonts, (options[:width]*(1-options[:text_padding])), (options[:height]*(1-options[:text_padding]))
217
- font_size = options[:max_font_size] if options[:max_font_size] && font_size > options[:max_font_size]
218
- end
219
-
220
- text_size = dimensions_of text, fonts, font_size
221
-
222
- if options[:text_align] == :center
223
- x = ( ( options[:width]*(1-(2*options[:text_padding])) ) - text_size[0] )/2 + x
224
- elsif options[:text_align] == :right
225
- x = ( ( options[:width]*(1-(1.5*options[:text_padding])) ) - text_size[0] ) + x
226
- end
227
- if options[:text_valign] == :center
228
- y = ( ( options[:height]*(1-(2*options[:text_padding])) ) - text_size[1] )/2 + y
229
- elsif options[:text_valign] == :top
230
- y = ( options[:height]*(1-(1.5*options[:text_padding])) ) - text_size[1] + y
231
- end
232
-
233
- # set graphic state for text
234
- text_stream << "q\n"
235
- text_graphic_state = graphic_state({ca: options[:opacity], CA: options[:opacity], LW: options[:stroke_width].to_f, LC: 2, LJ: 1, LD: 0, CTM: options[:ctm]})
236
- text_stream << "#{PDFOperations._object_to_pdf text_graphic_state} gs\n"
237
-
238
- # the following line was removed for Acrobat Reader compatability
239
- # text_stream << "DeviceRGB CS\nDeviceRGB cs\n"
240
-
241
- # set text render mode
242
- if options[:font_color]
243
- text_stream << "#{options[:font_color].join(' ')} rg\n"
244
- end
245
- if options[:stroke_width].to_i > 0 && options[:stroke_color]
246
- text_stream << "#{options[:stroke_color].join(' ')} RG\n"
247
- if options[:font_color]
248
- text_stream << "2 Tr\n"
249
- else
250
- final_stream << "1 Tr\n"
251
- end
252
- elsif options[:font_color]
253
- text_stream << "0 Tr\n"
254
- else
255
- text_stream << "3 Tr\n"
256
- end
257
- # format text object(s)
258
- # text_stream << "#{options[:font_color].join(' ')} rg\n" # sets the color state
259
- encode(text, fonts).each do |encoded|
260
- text_stream << "BT\n" # the Begine Text marker
261
- text_stream << PDFOperations._format_name_to_pdf(set_font encoded[0]) # Set font name
262
- text_stream << " #{font_size.round 3} Tf\n" # set font size and add font operator
263
- text_stream << "#{x.round 4} #{y.round 4} Td\n" # set location for text object
264
- text_stream << ( encoded[1] ) # insert the encoded string to the stream
265
- text_stream << " Tj\n" # the Text object operator and the End Text marker
266
- text_stream << "ET\n" # the Text object operator and the End Text marker
267
- x += encoded[2]/1000*font_size #update text starting point
268
- y -= encoded[3]/1000*font_size #update text starting point
269
- end
270
- # exit graphic state for text
271
- text_stream << "Q\n"
272
- end
273
- contents << text_stream
274
-
275
- self
276
- end
277
- # gets the dimentions (width and height) of the text, as it will be printed in the PDF.
278
- #
279
- # text:: the text to measure
280
- # font:: a font name or an Array of font names. Font names should be registered fonts. The 14 standard fonts are pre regitered with the font library.
281
- # size:: the size of the font (defaults to 1000 points).
282
- def dimensions_of(text, fonts, size = 1000)
283
- Fonts.dimensions_of text, fonts, size
284
- end
285
- # this method returns the size for which the text fits the requested metrices
286
- # the size is type Float and is rather exact
287
- # if the text cannot fit such a small place, returns zero (0).
288
- # maximum font size possible is set to 100,000 - which should be big enough for anything
289
- # text:: the text to fit
290
- # font:: the font name. @see font
291
- # length:: the length to fit
292
- # height:: the height to fit (optional - normally length is the issue)
293
- def fit_text(text, font, length, height = 10000000)
294
- size = 100000
295
- size_array = [size]
296
- metrics = Fonts.dimensions_of text, font, size
297
- if metrics[0] > length
298
- size_array << size * length/metrics[0]
299
- end
300
- if metrics[1] > height
301
- size_array << size * height/metrics[1]
302
- end
303
- size_array.min
304
- end
305
-
306
51
 
307
- protected
52
+ # includes the PDF Page_Methods module, including all page methods (textbox etc').
53
+ include Page_Methods
54
+
55
+ # # accessor (getter) for the :MediaBox element of the page
56
+ # def mediabox
57
+ # self[:MediaBox]
58
+ # end
59
+ # # accessor (setter) for the :MediaBox element of the page
60
+ # # dimensions:: an Array consisting of four numbers (can be floats) setting the size of the media box.
61
+ # def mediabox=(dimensions = [0.0, 0.0, 612.0, 792.0])
62
+ # self[:MediaBox] = dimensions
63
+ # end
64
+
65
+ # # This method adds a simple text box to the Page represented by the PDFWriter class.
66
+ # # This function takes two values:
67
+ # # text:: the text to potin the box.
68
+ # # properties:: a Hash of box properties.
69
+ # # the symbols and values in the properties Hash could be any or all of the following:
70
+ # # x:: the left position of the box.
71
+ # # y:: the BUTTOM position of the box.
72
+ # # width:: the width/length of the box. negative values will be computed from edge of page. defaults to 0 (end of page).
73
+ # # height:: the height of the box. negative values will be computed from edge of page. defaults to 0 (end of page).
74
+ # # text_align:: symbol for horizontal text alignment, can be ":center" (default), ":right", ":left"
75
+ # # text_valign:: symbol for vertical text alignment, can be ":center" (default), ":top", ":buttom"
76
+ # # text_padding:: a Float between 0 and 1, setting the padding for the text. defaults to 0.05 (5%).
77
+ # # font:: a registered font name or an Array of names. defaults to ":Helvetica". The 14 standard fonts names are:
78
+ # # - :"Times-Roman"
79
+ # # - :"Times-Bold"
80
+ # # - :"Times-Italic"
81
+ # # - :"Times-BoldItalic"
82
+ # # - :Helvetica
83
+ # # - :"Helvetica-Bold"
84
+ # # - :"Helvetica-BoldOblique"
85
+ # # - :"Helvetica- Oblique"
86
+ # # - :Courier
87
+ # # - :"Courier-Bold"
88
+ # # - :"Courier-Oblique"
89
+ # # - :"Courier-BoldOblique"
90
+ # # - :Symbol
91
+ # # - :ZapfDingbats
92
+ # # font_size:: a Fixnum for the font size, or :fit_text to fit the text in the box. defaults to ":fit_text"
93
+ # # max_font_size:: if font_size is set to :fit_text, this will be the maximum font size. defaults to nil (no maximum)
94
+ # # font_color:: text color in [R, G, B], an array with three floats, each in a value between 0 to 1 (gray will be "[0.5, 0.5, 0.5]"). defaults to black.
95
+ # # stroke_color:: text stroke color in [R, G, B], an array with three floats, each in a value between 0 to 1 (gray will be "[0.5, 0.5, 0.5]"). defounlts to nil (no stroke).
96
+ # # stroke_width:: text stroke width in PDF units. defaults to 0 (none).
97
+ # # box_color:: box fill color in [R, G, B], an array with three floats, each in a value between 0 to 1 (gray will be "[0.5, 0.5, 0.5]"). defaults to nil (none).
98
+ # # border_color:: box border color in [R, G, B], an array with three floats, each in a value between 0 to 1 (gray will be "[0.5, 0.5, 0.5]"). defaults to nil (none).
99
+ # # border_width:: border width in PDF units. defaults to nil (none).
100
+ # # box_radius:: border radius in PDF units. defaults to 0 (no corner rounding).
101
+ # # opacity:: textbox opacity, a float between 0 (transparent) and 1 (opaque)
102
+ # def textbox(text, properties = {})
103
+ # options = {
104
+ # x: 0,
105
+ # y: 0,
106
+ # width: 0,
107
+ # height: -1,
108
+ # text_align: :center,
109
+ # text_valign: :center,
110
+ # text_padding: 0.1,
111
+ # font: nil,
112
+ # font_size: :fit_text,
113
+ # max_font_size: nil,
114
+ # font_color: [0,0,0],
115
+ # stroke_color: nil,
116
+ # stroke_width: 0,
117
+ # box_color: nil,
118
+ # border_color: nil,
119
+ # border_width: 0,
120
+ # box_radius: 0,
121
+ # opacity: 1,
122
+ # ctm: [1,0,0,1,0,0]
123
+ # }
124
+ # options.update properties
125
+ # # reset the length and height to meaningful values, if negative
126
+ # options[:width] = mediabox[2] - options[:x] + options[:width] if options[:width] <= 0
127
+ # options[:height] = mediabox[3] - options[:y] + options[:height] if options[:height] <= 0
128
+
129
+ # # reset the padding value
130
+ # options[:text_padding] = 0 if options[:text_padding].to_f >= 1
131
+
132
+ # # create box stream
133
+ # box_stream = ""
134
+ # # set graphic state for box
135
+ # if options[:box_color] || (options[:border_width].to_i > 0 && options[:border_color])
136
+ # # compute x and y position for text
137
+ # x = options[:x]
138
+ # y = options[:y]
139
+
140
+ # # set graphic state for the box
141
+ # box_stream << "q\n"
142
+ # box_graphic_state = { ca: options[:opacity], CA: options[:opacity], LW: options[:border_width], LC: 0, LJ: 0, LD: 0, CTM: options[:ctm]}
143
+ # if options[:box_radius] != 0 # if the text box has rounded corners
144
+ # box_graphic_state[:LC], box_graphic_state[:LJ] = 2, 1
145
+ # end
146
+ # box_graphic_state = graphic_state box_graphic_state # adds the graphic state to Resources and gets the reference
147
+ # box_stream << "#{PDFOperations._object_to_pdf box_graphic_state} gs\n"
148
+
149
+ # # the following line was removed for Acrobat Reader compatability
150
+ # # box_stream << "DeviceRGB CS\nDeviceRGB cs\n"
151
+
152
+ # if options[:box_color]
153
+ # box_stream << "#{options[:box_color].join(' ')} rg\n"
154
+ # end
155
+ # if options[:border_width].to_i > 0 && options[:border_color]
156
+ # box_stream << "#{options[:border_color].join(' ')} RG\n"
157
+ # end
158
+ # # create the path
159
+ # radius = options[:box_radius]
160
+ # half_radius = (radius.to_f / 2).round 4
161
+ # ## set starting point
162
+ # box_stream << "#{options[:x] + radius} #{options[:y]} m\n"
163
+ # ## buttom and right corner - first line and first corner
164
+ # box_stream << "#{options[:x] + options[:width] - radius} #{options[:y]} l\n" #buttom
165
+ # if options[:box_radius] != 0 # make first corner, if not straight.
166
+ # box_stream << "#{options[:x] + options[:width] - half_radius} #{options[:y]} "
167
+ # box_stream << "#{options[:x] + options[:width]} #{options[:y] + half_radius} "
168
+ # box_stream << "#{options[:x] + options[:width]} #{options[:y] + radius} c\n"
169
+ # end
170
+ # ## right and top-right corner
171
+ # box_stream << "#{options[:x] + options[:width]} #{options[:y] + options[:height] - radius} l\n"
172
+ # if options[:box_radius] != 0
173
+ # box_stream << "#{options[:x] + options[:width]} #{options[:y] + options[:height] - half_radius} "
174
+ # box_stream << "#{options[:x] + options[:width] - half_radius} #{options[:y] + options[:height]} "
175
+ # box_stream << "#{options[:x] + options[:width] - radius} #{options[:y] + options[:height]} c\n"
176
+ # end
177
+ # ## top and top-left corner
178
+ # box_stream << "#{options[:x] + radius} #{options[:y] + options[:height]} l\n"
179
+ # if options[:box_radius] != 0
180
+ # box_stream << "#{options[:x] + half_radius} #{options[:y] + options[:height]} "
181
+ # box_stream << "#{options[:x]} #{options[:y] + options[:height] - half_radius} "
182
+ # box_stream << "#{options[:x]} #{options[:y] + options[:height] - radius} c\n"
183
+ # end
184
+ # ## left and buttom-left corner
185
+ # box_stream << "#{options[:x]} #{options[:y] + radius} l\n"
186
+ # if options[:box_radius] != 0
187
+ # box_stream << "#{options[:x]} #{options[:y] + half_radius} "
188
+ # box_stream << "#{options[:x] + half_radius} #{options[:y]} "
189
+ # box_stream << "#{options[:x] + radius} #{options[:y]} c\n"
190
+ # end
191
+ # # fill / stroke path
192
+ # box_stream << "h\n"
193
+ # if options[:box_color] && options[:border_width].to_i > 0 && options[:border_color]
194
+ # box_stream << "B\n"
195
+ # elsif options[:box_color] # fill if fill color is set
196
+ # box_stream << "f\n"
197
+ # elsif options[:border_width].to_i > 0 && options[:border_color] # stroke if border is set
198
+ # box_stream << "S\n"
199
+ # end
200
+
201
+ # # exit graphic state for the box
202
+ # box_stream << "Q\n"
203
+ # end
204
+ # contents << box_stream
205
+
206
+ # # reset x,y by text alignment - x,y are calculated from the buttom left
207
+ # # each unit (1) is 1/72 Inch
208
+ # # create text stream
209
+ # text_stream = ""
210
+ # if text.to_s != "" && options[:font_size] != 0 && (options[:font_color] || options[:stroke_color])
211
+ # # compute x and y position for text
212
+ # x = options[:x] + (options[:width]*options[:text_padding])
213
+ # y = options[:y] + (options[:height]*options[:text_padding])
214
+
215
+ # # set the fonts (fonts array, with :Helvetica as fallback).
216
+ # fonts = [*options[:font], :Helvetica]
217
+ # # fit text in box, if requested
218
+ # font_size = options[:font_size]
219
+ # if options[:font_size] == :fit_text
220
+ # font_size = self.fit_text text, fonts, (options[:width]*(1-options[:text_padding])), (options[:height]*(1-options[:text_padding]))
221
+ # font_size = options[:max_font_size] if options[:max_font_size] && font_size > options[:max_font_size]
222
+ # end
223
+
224
+ # text_size = dimensions_of text, fonts, font_size
225
+
226
+ # if options[:text_align] == :center
227
+ # x = ( ( options[:width]*(1-(2*options[:text_padding])) ) - text_size[0] )/2 + x
228
+ # elsif options[:text_align] == :right
229
+ # x = ( ( options[:width]*(1-(1.5*options[:text_padding])) ) - text_size[0] ) + x
230
+ # end
231
+ # if options[:text_valign] == :center
232
+ # y = ( ( options[:height]*(1-(2*options[:text_padding])) ) - text_size[1] )/2 + y
233
+ # elsif options[:text_valign] == :top
234
+ # y = ( options[:height]*(1-(1.5*options[:text_padding])) ) - text_size[1] + y
235
+ # end
236
+
237
+ # # set graphic state for text
238
+ # text_stream << "q\n"
239
+ # text_graphic_state = graphic_state({ca: options[:opacity], CA: options[:opacity], LW: options[:stroke_width].to_f, LC: 2, LJ: 1, LD: 0, CTM: options[:ctm]})
240
+ # text_stream << "#{PDFOperations._object_to_pdf text_graphic_state} gs\n"
241
+
242
+ # # the following line was removed for Acrobat Reader compatability
243
+ # # text_stream << "DeviceRGB CS\nDeviceRGB cs\n"
244
+
245
+ # # set text render mode
246
+ # if options[:font_color]
247
+ # text_stream << "#{options[:font_color].join(' ')} rg\n"
248
+ # end
249
+ # if options[:stroke_width].to_i > 0 && options[:stroke_color]
250
+ # text_stream << "#{options[:stroke_color].join(' ')} RG\n"
251
+ # if options[:font_color]
252
+ # text_stream << "2 Tr\n"
253
+ # else
254
+ # final_stream << "1 Tr\n"
255
+ # end
256
+ # elsif options[:font_color]
257
+ # text_stream << "0 Tr\n"
258
+ # else
259
+ # text_stream << "3 Tr\n"
260
+ # end
261
+ # # format text object(s)
262
+ # # text_stream << "#{options[:font_color].join(' ')} rg\n" # sets the color state
263
+ # encode(text, fonts).each do |encoded|
264
+ # text_stream << "BT\n" # the Begine Text marker
265
+ # text_stream << PDFOperations._format_name_to_pdf(set_font encoded[0]) # Set font name
266
+ # text_stream << " #{font_size.round 3} Tf\n" # set font size and add font operator
267
+ # text_stream << "#{x.round 4} #{y.round 4} Td\n" # set location for text object
268
+ # text_stream << ( encoded[1] ) # insert the encoded string to the stream
269
+ # text_stream << " Tj\n" # the Text object operator and the End Text marker
270
+ # text_stream << "ET\n" # the Text object operator and the End Text marker
271
+ # x += encoded[2]/1000*font_size #update text starting point
272
+ # y -= encoded[3]/1000*font_size #update text starting point
273
+ # end
274
+ # # exit graphic state for text
275
+ # text_stream << "Q\n"
276
+ # end
277
+ # contents << text_stream
278
+
279
+ # self
280
+ # end
281
+ # # gets the dimentions (width and height) of the text, as it will be printed in the PDF.
282
+ # #
283
+ # # text:: the text to measure
284
+ # # font:: a font name or an Array of font names. Font names should be registered fonts. The 14 standard fonts are pre regitered with the font library.
285
+ # # size:: the size of the font (defaults to 1000 points).
286
+ # def dimensions_of(text, fonts, size = 1000)
287
+ # Fonts.dimensions_of text, fonts, size
288
+ # end
289
+ # # this method returns the size for which the text fits the requested metrices
290
+ # # the size is type Float and is rather exact
291
+ # # if the text cannot fit such a small place, returns zero (0).
292
+ # # maximum font size possible is set to 100,000 - which should be big enough for anything
293
+ # # text:: the text to fit
294
+ # # font:: the font name. @see font
295
+ # # length:: the length to fit
296
+ # # height:: the height to fit (optional - normally length is the issue)
297
+ # def fit_text(text, font, length, height = 10000000)
298
+ # size = 100000
299
+ # size_array = [size]
300
+ # metrics = Fonts.dimensions_of text, font, size
301
+ # if metrics[0] > length
302
+ # size_array << size * length/metrics[0]
303
+ # end
304
+ # if metrics[1] > height
305
+ # size_array << size * height/metrics[1]
306
+ # end
307
+ # size_array.min
308
+ # end
309
+
310
+
311
+ # protected
312
+
313
+ # # accessor (getter) for the :Resources element of the page
314
+ # def resources
315
+ # self[:Resources]
316
+ # end
317
+ # # accessor (getter) for the stream in the :Contents element of the page
318
+ # # after getting the string object, you can operate on it but not replace it (use << or other String methods).
319
+ # def contents
320
+ # @contents
321
+ # end
322
+ # # creates a font object and adds the font to the resources dictionary
323
+ # # returns the name of the font for the content stream.
324
+ # # font:: a Symbol of one of the fonts registered in the library, or:
325
+ # # - :"Times-Roman"
326
+ # # - :"Times-Bold"
327
+ # # - :"Times-Italic"
328
+ # # - :"Times-BoldItalic"
329
+ # # - :Helvetica
330
+ # # - :"Helvetica-Bold"
331
+ # # - :"Helvetica-BoldOblique"
332
+ # # - :"Helvetica- Oblique"
333
+ # # - :Courier
334
+ # # - :"Courier-Bold"
335
+ # # - :"Courier-Oblique"
336
+ # # - :"Courier-BoldOblique"
337
+ # # - :Symbol
338
+ # # - :ZapfDingbats
339
+ # def set_font(font = :Helvetica)
340
+ # # if the font exists, return it's name
341
+ # resources[:Font] ||= {}
342
+ # resources[:Font].each do |k,v|
343
+ # if v.is_a?(Fonts::Font) && v.name && v.name == font
344
+ # return k
345
+ # end
346
+ # end
347
+ # # set a secure name for the font
348
+ # name = (@base_font_name + (resources[:Font].length + 1).to_s).to_sym
349
+ # # get font object
350
+ # font_object = Fonts.get_font(font)
351
+ # # return false if the font wan't found in the library.
352
+ # return false unless font_object
353
+ # # add object to reasource
354
+ # resources[:Font][name] = font_object
355
+ # #return name
356
+ # name
357
+ # end
358
+ # # register or get a registered graphic state dictionary.
359
+ # # the method returns the name of the graphos state, for use in a content stream.
360
+ # def graphic_state(graphic_state_dictionary = {})
361
+ # # if the graphic state exists, return it's name
362
+ # resources[:ExtGState] ||= {}
363
+ # resources[:ExtGState].each do |k,v|
364
+ # if v.is_a?(Hash) && v == graphic_state_dictionary
365
+ # return k
366
+ # end
367
+ # end
368
+ # # set graphic state type
369
+ # graphic_state_dictionary[:Type] = :ExtGState
370
+ # # set a secure name for the graphic state
371
+ # name = (SecureRandom.hex(9)).to_sym
372
+ # # add object to reasource
373
+ # resources[:ExtGState][name] = graphic_state_dictionary
374
+ # #return name
375
+ # name
376
+ # end
377
+
378
+ # # encodes the text in an array of [:font_name, <PDFHexString>] for use in textbox
379
+ # def encode text, fonts
380
+ # # text must be a unicode string and fonts must be an array.
381
+ # # this is an internal method, don't perform tests.
382
+ # fonts_array = []
383
+ # fonts.each do |name|
384
+ # f = Fonts.get_font name
385
+ # fonts_array << f if f
386
+ # end
387
+
388
+ # # before starting, we should reorder any RTL content in the string
389
+ # text = reorder_rtl_content text
390
+
391
+ # out = []
392
+ # text.chars.each do |c|
393
+ # fonts_array.each_index do |i|
394
+ # if fonts_array[i].cmap.nil? || (fonts_array[i].cmap && fonts_array[i].cmap[c])
395
+ # #add to array
396
+ # if out.last.nil? || out.last[0] != fonts[i]
397
+ # out.last[1] << ">" unless out.last.nil?
398
+ # out << [fonts[i], "<" , 0, 0]
399
+ # end
400
+ # out.last[1] << ( fonts_array[i].cmap.nil? ? ( c.unpack("H*")[0] ) : (fonts_array[i].cmap[c]) )
401
+ # if fonts_array[i].metrics[c]
402
+ # out.last[2] += fonts_array[i].metrics[c][:wx].to_f
403
+ # out.last[3] += fonts_array[i].metrics[c][:wy].to_f
404
+ # end
405
+ # break
406
+ # end
407
+ # end
408
+ # end
409
+ # out.last[1] << ">" if out.last
410
+ # out
411
+ # end
412
+
413
+ # # a very primitive text reordering algorithm... I was lazy...
414
+ # # ...still, it works (I think).
415
+ # def reorder_rtl_content text
416
+ # rtl_characters = "\u05d0-\u05ea\u05f0-\u05f4\u0600-\u06ff\u0750-\u077f"
417
+ # rtl_replaces = { '(' => ')', ')' => '(',
418
+ # '[' => ']', ']'=>'[',
419
+ # '{' => '}', '}'=>'{',
420
+ # '<' => '>', '>'=>'<',
421
+ # }
422
+ # return text unless text =~ /[#{rtl_characters}]/
423
+
424
+ # out = []
425
+ # scanner = StringScanner.new text
426
+ # until scanner.eos? do
427
+ # if scanner.scan /[#{rtl_characters} ]/
428
+ # out.unshift scanner.matched
429
+ # elsif scanner.scan /[^#{rtl_characters}]+/
430
+ # if out.empty? && scanner.matched.match(/[\s]$/) && !scanner.eos?
431
+ # white_space_to_move = scanner.matched.match(/[\s]+$/).to_s
432
+ # out.unshift scanner.matched[0..-1-white_space_to_move.length]
433
+ # out.unshift white_space_to_move
434
+ # elsif scanner.matched.match /^[\(\)\[\]\{\}\<\>]$/
435
+ # out.unshift rtl_replaces[scanner.matched]
436
+ # else
437
+ # out.unshift scanner.matched
438
+ # end
439
+ # end
440
+ # end
441
+ # out.join.strip
442
+ # end
308
443
 
309
- # accessor (getter) for the :Resources element of the page
310
- def resources
311
- self[:Resources]
312
- end
313
- # accessor (getter) for the stream in the :Contents element of the page
314
- # after getting the string object, you can operate on it but not replace it (use << or other String methods).
315
- def contents
316
- @contents
317
- end
318
- # creates a font object and adds the font to the resources dictionary
319
- # returns the name of the font for the content stream.
320
- # font:: a Symbol of one of the fonts registered in the library, or:
321
- # - :"Times-Roman"
322
- # - :"Times-Bold"
323
- # - :"Times-Italic"
324
- # - :"Times-BoldItalic"
325
- # - :Helvetica
326
- # - :"Helvetica-Bold"
327
- # - :"Helvetica-BoldOblique"
328
- # - :"Helvetica- Oblique"
329
- # - :Courier
330
- # - :"Courier-Bold"
331
- # - :"Courier-Oblique"
332
- # - :"Courier-BoldOblique"
333
- # - :Symbol
334
- # - :ZapfDingbats
335
- def set_font(font = :Helvetica)
336
- # if the font exists, return it's name
337
- resources[:Font] ||= {}
338
- resources[:Font].each do |k,v|
339
- if v.is_a?(Fonts::Font) && v.name && v.name == font
340
- return k
341
- end
342
- end
343
- # set a secure name for the font
344
- name = (@base_font_name + (resources[:Font].length + 1).to_s).to_sym
345
- # get font object
346
- font_object = Fonts.get_font(font)
347
- # return false if the font wan't found in the library.
348
- return false unless font_object
349
- # add object to reasource
350
- resources[:Font][name] = font_object
351
- #return name
352
- name
353
- end
354
- # register or get a registered graphic state dictionary.
355
- # the method returns the name of the graphos state, for use in a content stream.
356
- def graphic_state(graphic_state_dictionary = {})
357
- # if the graphic state exists, return it's name
358
- resources[:ExtGState] ||= {}
359
- resources[:ExtGState].each do |k,v|
360
- if v.is_a?(Hash) && v == graphic_state_dictionary
361
- return k
362
- end
363
- end
364
- # set graphic state type
365
- graphic_state_dictionary[:Type] = :ExtGState
366
- # set a secure name for the graphic state
367
- name = (SecureRandom.hex(9)).to_sym
368
- # add object to reasource
369
- resources[:ExtGState][name] = graphic_state_dictionary
370
- #return name
371
- name
372
- end
373
-
374
- # encodes the text in an array of [:font_name, <PDFHexString>] for use in textbox
375
- def encode text, fonts
376
- # text must be a unicode string and fonts must be an array.
377
- # this is an internal method, don't perform tests.
378
- fonts_array = []
379
- fonts.each do |name|
380
- f = Fonts.get_font name
381
- fonts_array << f if f
382
- end
383
-
384
- # before starting, we should reorder any RTL content in the string
385
- text = reorder_rtl_content text
386
-
387
- out = []
388
- text.chars.each do |c|
389
- fonts_array.each_index do |i|
390
- if fonts_array[i].cmap.nil? || (fonts_array[i].cmap && fonts_array[i].cmap[c])
391
- #add to array
392
- if out.last.nil? || out.last[0] != fonts[i]
393
- out.last[1] << ">" unless out.last.nil?
394
- out << [fonts[i], "<" , 0, 0]
395
- end
396
- out.last[1] << ( fonts_array[i].cmap.nil? ? ( c.unpack("H*")[0] ) : (fonts_array[i].cmap[c]) )
397
- if fonts_array[i].metrics[c]
398
- out.last[2] += fonts_array[i].metrics[c][:wx].to_f
399
- out.last[3] += fonts_array[i].metrics[c][:wy].to_f
400
- end
401
- break
402
- end
403
- end
404
- end
405
- out.last[1] << ">" if out.last
406
- out
407
- end
408
-
409
- # a very primitive text reordering algorithm... I was lazy...
410
- # ...still, it works (I think).
411
- def reorder_rtl_content text
412
- rtl_characters = "\u05d0-\u05ea\u05f0-\u05f4\u0600-\u06ff\u0750-\u077f"
413
- rtl_replaces = { '(' => ')', ')' => '(',
414
- '[' => ']', ']'=>'[',
415
- '{' => '}', '}'=>'{',
416
- '<' => '>', '>'=>'<',
417
- }
418
- return text unless text =~ /[#{rtl_characters}]/
419
-
420
- out = []
421
- scanner = StringScanner.new text
422
- until scanner.eos? do
423
- if scanner.scan /[#{rtl_characters} ]/
424
- out.unshift scanner.matched
425
- elsif scanner.scan /[^#{rtl_characters}]+/
426
- if out.empty? && scanner.matched.match(/[\s]$/) && !scanner.eos?
427
- white_space_to_move = scanner.matched.match(/[\s]+$/).to_s
428
- out.unshift scanner.matched[0..-1-white_space_to_move.length]
429
- out.unshift white_space_to_move
430
- elsif scanner.matched.match /^[\(\)\[\]\{\}\<\>]$/
431
- out.unshift rtl_replaces[scanner.matched]
432
- else
433
- out.unshift scanner.matched
434
- end
435
- end
436
- end
437
- out.join.strip
438
- end
439
444
  end
440
445
 
441
446
  end