combine_pdf 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c17b1bcd778d60ecce5f1941ad98801e02ef1934
4
- data.tar.gz: 74759b9ad7a766a4e50d891e9ad4629cf11b1f50
3
+ metadata.gz: 169d63fc09f86f09a633e199582c65212fbcd158
4
+ data.tar.gz: 6f5d94234603827a33100c77e4b66ac41492c60c
5
5
  SHA512:
6
- metadata.gz: 549eb5a4caaa6b07bd522ea497537568932a67e79427a3e32bd28827e72dcfb8e15f4602e63337dae1f63ca49de7c0f08a19afc108b75a506276d4e719f1b7a1
7
- data.tar.gz: a48b950943f4cc36712f96815aab68f310b8bc0f9debc0d2497ee3b5d7d3e3fd0a88f2957c7c6dc9ccdfbe2a2a498762494d5ed72e45686203d2de9633d9cec2
6
+ metadata.gz: ffb49820460e29f7d6d857fe6f66af7a9b0f97edae28a2ab1caa814eb5e84d521f91b76e091b2b33fe36f716684253a30fc51cc777e8696c5ee09ed21ebae0ae
7
+ data.tar.gz: fb6e175436280fd64499684151f29c7cf9254ca21be6a0052e42f80c6d29ede70ed982afbf7aef07fd31cbcf88c460f9eb1a84bea3f363a74ac78d3bb5bc44e2
data/lib/combine_pdf.rb CHANGED
@@ -1,10 +1,33 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  require 'zlib'
3
+ require 'securerandom'
3
4
  require 'strscan'
4
- require 'combine_pdf/combine_pdf_pdf'
5
- require 'combine_pdf/combine_pdf_decrypt'
6
- require 'combine_pdf/combine_pdf_filter'
7
- require 'combine_pdf/combine_pdf_parser'
5
+
6
+ require "combine_pdf/combine_pdf_operations.rb"
7
+ require "combine_pdf/combine_pdf_basic_writer.rb"
8
+ require "combine_pdf/combine_pdf_decrypt.rb"
9
+ require "combine_pdf/combine_pdf_filter.rb"
10
+ require "combine_pdf/combine_pdf_parser.rb"
11
+ require "combine_pdf/combine_pdf_pdf.rb"
12
+
13
+ require "combine_pdf/font_metrics/courier-bold_metrics.rb"
14
+ require "combine_pdf/font_metrics/courier-boldoblique_metrics.rb"
15
+ require "combine_pdf/font_metrics/courier-oblique_metrics.rb"
16
+ require "combine_pdf/font_metrics/courier_metrics.rb"
17
+ require "combine_pdf/font_metrics/helvetica-bold_metrics.rb"
18
+ require "combine_pdf/font_metrics/helvetica-boldoblique_metrics.rb"
19
+ require "combine_pdf/font_metrics/helvetica-oblique_metrics.rb"
20
+ require "combine_pdf/font_metrics/helvetica_metrics.rb"
21
+ require "combine_pdf/font_metrics/symbol_metrics.rb"
22
+ require "combine_pdf/font_metrics/times-bold_metrics.rb"
23
+ require "combine_pdf/font_metrics/times-bolditalic_metrics.rb"
24
+ require "combine_pdf/font_metrics/times-italic_metrics.rb"
25
+ require "combine_pdf/font_metrics/times-roman_metrics.rb"
26
+ require "combine_pdf/font_metrics/zapfdingbats_metrics.rb"
27
+
28
+ require "combine_pdf/font_metrics/metrics_dictionary.rb"
29
+
30
+
8
31
 
9
32
 
10
33
  # This is a pure ruby library to merge PDF files.
@@ -74,378 +97,7 @@ module CombinePDF
74
97
  end
75
98
  end
76
99
 
77
- module CombinePDF
78
-
79
- #:nodoc: all
80
- ################################################################
81
- ## These are common functions, used within the different classes
82
- ## These functions aren't open to the public.
83
- ################################################################
84
- #@private
85
- PRIVATE_HASH_KEYS = [:indirect_reference_id, :indirect_generation_number, :raw_stream_content, :is_reference_only, :referenced_object, :indirect_without_dictionary]
86
- #@private
87
- LITERAL_STRING_REPLACEMENT_HASH = {
88
- 110 => 10, # "\\n".bytes = [92, 110] "\n".ord = 10
89
- 114 => 13, #r
90
- 116 => 9, #t
91
- 98 => 8, #b
92
- 102 => 255, #f
93
- 40 => 40, #(
94
- 41 => 41, #)
95
- 92 => 92 #\
96
- }
97
- #@private
98
- #:nodoc: all
99
- module PDFOperations
100
- module_function
101
- def inject_to_page page = {Type: :Page, MediaBox: [0,0,612.0,792.0], Resources: {}, Contents: []}, stream = nil, top = true
102
- # make sure both the page reciving the new data and the injected page are of the correct data type.
103
- return false unless page.is_a?(Hash) && stream.is_a?(Hash)
104
-
105
- # following the reference chain and assigning a pointer to the correct Resouces object.
106
- # (assignments of Strings, Arrays and Hashes are pointers in Ruby, unless the .dup method is called)
107
- original_resources = page[:Resources]
108
- if original_resources[:is_reference_only]
109
- original_resources = original_resources[:referenced_object]
110
- raise "Couldn't tap into resources dictionary, as it is a reference and isn't linked." unless original_resources
111
- end
112
- original_contents = page[:Contents]
113
- original_contents = [original_contents] unless original_contents.is_a? Array
114
-
115
- stream_resources = stream[:Resources]
116
- if stream_resources[:is_reference_only]
117
- stream_resources = stream_resources[:referenced_object]
118
- raise "Couldn't tap into resources dictionary, as it is a reference and isn't linked." unless stream_resources
119
- end
120
- stream_contents = stream[:Contents]
121
- stream_contents = [stream_contents] unless stream_contents.is_a? Array
122
-
123
- # collect keys as objects - this is to make sure that
124
- # we are working on the actual resource data, rather then references
125
- flatten_resources_dictionaries stream_resources
126
- flatten_resources_dictionaries original_resources
127
-
128
- # injecting each of the values in the injected Page
129
- stream_resources.each do |key, new_val|
130
- unless PRIVATE_HASH_KEYS.include? key # keep CombinePDF structual data intact.
131
- if original_resources[key].nil?
132
- original_resources[key] = new_val
133
- elsif original_resources[key].is_a?(Hash) && new_val.is_a?(Hash)
134
- new_val.update original_resources[key] # make sure the old values are respected
135
- original_resources[key].update new_val # transfer old and new values to the injected page
136
- end #Do nothing if array - ot is the PROC array, which is an issue
137
- end
138
- end
139
- original_resources[:ProcSet] = [:PDF, :Text, :ImageB, :ImageC, :ImageI] # this was recommended by the ISO. 32000-1:2008
140
-
141
- if top # if this is a stamp (overlay)
142
- page[:Contents] = original_contents
143
- page[:Contents].push *stream_contents
144
- else #if this was a watermark (underlay? would be lost if the page was scanned, as white might not be transparent)
145
- page[:Contents] = stream_contents
146
- page[:Contents].push *original_contents
147
- end
148
-
149
- page
150
- end
151
- # copy_and_secure_for_injection(page)
152
- # - page is a page in the pages array, i.e.
153
- # pdf.pages[0]
154
- # takes a page object and:
155
- #
156
- # makes a deep copy of the page (Ruby defaults to pointers, so this will copy the memory).
157
- #
158
- # then it will rewrite the content stream with renamed resources, so as to avoid name conflicts.
159
- def copy_and_secure_for_injection(page)
160
- # copy page
161
- new_page = create_deep_copy page
162
-
163
- # initiate dictionary from old names to new names
164
- names_dictionary = {}
165
-
166
- # itirate through all keys that are name objects and give them new names (add to dic)
167
- # this should be done for every dictionary in :Resources
168
- # this is a few steps stage:
169
-
170
- # 1. get resources object
171
- resources = new_page[:Resources]
172
- if resources[:is_reference_only]
173
- resources = resources[:referenced_object]
174
- raise "Couldn't tap into resources dictionary, as it is a reference and isn't linked." unless resources
175
- end
176
-
177
- # 2. establich direct access to dictionaries and remove reference values
178
- flatten_resources_dictionaries resources
179
-
180
- # 3. travel every dictionary to pick up names (keys), change them and add them to the dictionary
181
- resources.each do |k,v|
182
- if v.is_a?(Hash)
183
- new_dictionary = {}
184
- v.each do |old_key, value|
185
- new_key = ("CombinePDF" + SecureRandom.urlsafe_base64(9)).to_sym
186
- names_dictionary[old_key] = new_key
187
- new_dictionary[new_key] = value
188
- end
189
- resources[k] = new_dictionary
190
- end
191
- end
192
100
 
193
- # now that we have replaced the names in the resources dictionaries,
194
- # it is time to replace the names inside the stream
195
- # we will need to make sure we have access to the stream injected
196
- # we will user PDFFilter.inflate_object
197
- (new_page[:Contents].is_a?(Array) ? new_page[:Contents] : [new_page[:Contents] ]).each do |c|
198
- stream = c[:referenced_object]
199
- PDFFilter.inflate_object stream
200
- names_dictionary.each do |old_key, new_key|
201
- stream[:raw_stream_content].gsub! _object_to_pdf(old_key), _object_to_pdf(new_key) ##### PRAY(!) that the parsed datawill be correctly reproduced!
202
- end
203
- end
204
-
205
- new_page
206
- end
207
- def flatten_resources_dictionaries(resources)
208
- resources.each do |k,v|
209
- if v.is_a?(Hash) && v[:is_reference_only]
210
- if v[:referenced_object]
211
- resources[k] = resources[k][:referenced_object].dup
212
- resources[k].delete(:indirect_reference_id)
213
- resources[k].delete(:indirect_generation_number)
214
- elsif v[:indirect_without_dictionary]
215
- resources[k] = resources[k][:indirect_without_dictionary]
216
- end
217
- end
218
- end
219
- end
220
-
221
-
222
- # Ruby normally assigns pointes.
223
- # noramlly:
224
- # a = [1,2,3] # => [1,2,3]
225
- # b = a # => [1,2,3]
226
- # a << 4 # => [1,2,3,4]
227
- # b # => [1,2,3,4]
228
- # This method makes sure that the memory is copied instead of a pointer assigned.
229
- # this works using recursion, so that arrays and hashes within arrays and hashes are also copied and not pointed to.
230
- # One needs to be careful of infinit loops using this function.
231
- def create_deep_copy object
232
- if object.is_a?(Array)
233
- return object.map { |e| create_deep_copy e }
234
- elsif object.is_a?(Hash)
235
- return {}.tap {|out| object.each {|k,v| out[create_deep_copy(k)] = create_deep_copy(v) unless k == :Parent} }
236
- elsif object.is_a?(String)
237
- return object.dup
238
- else
239
- return object # objects that aren't Strings, Arrays or Hashes (such as Symbols and Fixnums) aren't pointers in Ruby and are always copied.
240
- end
241
- end
242
- def get_refernced_object(objects_array = [], reference_hash = {})
243
- objects_array.each do |stored_object|
244
- return stored_object if ( stored_object.is_a?(Hash) &&
245
- reference_hash[:indirect_reference_id] == stored_object[:indirect_reference_id] &&
246
- reference_hash[:indirect_generation_number] == stored_object[:indirect_generation_number] )
247
- end
248
- warn "didn't find reference #{reference_hash}"
249
- nil
250
- end
251
- def change_references_to_actual_values(objects_array = [], hash_with_references = {})
252
- hash_with_references.each do |k,v|
253
- if v.is_a?(Hash) && v[:is_reference_only]
254
- hash_with_references[k] = PDFOperations.get_refernced_object( objects_array, v)
255
- hash_with_references[k] = hash_with_references[k][:indirect_without_dictionary] if hash_with_references[k].is_a?(Hash) && hash_with_references[k][:indirect_without_dictionary]
256
- warn "Couldn't connect all values from references - didn't find reference #{hash_with_references}!!!" if hash_with_references[k] == nil
257
- hash_with_references[k] = v unless hash_with_references[k]
258
- end
259
- end
260
- hash_with_references
261
- end
262
- def change_connected_references_to_actual_values(hash_with_references = {})
263
- if hash_with_references.is_a?(Hash)
264
- hash_with_references.each do |k,v|
265
- if v.is_a?(Hash) && v[:is_reference_only]
266
- if v[:indirect_without_dictionary]
267
- hash_with_references[k] = v[:indirect_without_dictionary]
268
- elsif v[:referenced_object]
269
- hash_with_references[k] = v[:referenced_object]
270
- else
271
- raise "Cannot change references to values, as they are disconnected!"
272
- end
273
- end
274
- end
275
- hash_with_references.each {|k, v| change_connected_references_to_actual_values(v) if v.is_a?(Hash) || v.is_a?(Array)}
276
- elsif hash_with_references.is_a?(Array)
277
- hash_with_references.each {|item| change_connected_references_to_actual_values(item) if item.is_a?(Hash) || item.is_a?(Array)}
278
- end
279
- hash_with_references
280
- end
281
- def connect_references_and_actual_values(objects_array = [], hash_with_references = {})
282
- ret = true
283
- hash_with_references.each do |k,v|
284
- if v.is_a?(Hash) && v[:is_reference_only]
285
- ref_obj = PDFOperations.get_refernced_object( objects_array, v)
286
- hash_with_references[k] = ref_obj[:indirect_without_dictionary] if ref_obj.is_a?(Hash) && ref_obj[:indirect_without_dictionary]
287
- ret = false
288
- end
289
- end
290
- ret
291
- end
292
-
293
-
294
- def _each_object(object, limit_references = true, first_call = true, &block)
295
- # #####################
296
- # ## v.1.2 needs optimazation
297
- # case
298
- # when object.is_a?(Array)
299
- # object.each {|obj| _each_object(obj, limit_references, &block)}
300
- # when object.is_a?(Hash)
301
- # yield(object)
302
- # object.each do |k,v|
303
- # unless (limit_references && k == :referenced_object)
304
- # unless k == :Parent
305
- # _each_object(v, limit_references, &block)
306
- # end
307
- # end
308
- # end
309
- # end
310
- #####################
311
- ## v.2.1 needs optimazation
312
- ## version 2.1 is slightly faster then v.1.2
313
- @already_visited = [] if first_call
314
- unless limit_references
315
- @already_visited << object.object_id
316
- end
317
- case
318
- when object.is_a?(Array)
319
- object.each {|obj| _each_object(obj, limit_references, false, &block)}
320
- when object.is_a?(Hash)
321
- yield(object)
322
- unless limit_references && object[:is_reference_only]
323
- object.each do |k,v|
324
- _each_object(v, limit_references, false, &block) unless @already_visited.include? v.object_id
325
- end
326
- end
327
- end
328
- end
329
-
330
-
331
-
332
- # Formats an object into PDF format. This is used my the PDF object to format the PDF file and it is used in the secure injection which is still being developed.
333
- def _object_to_pdf object
334
- case
335
- when object.nil?
336
- return "null"
337
- when object.is_a?(String)
338
- return _format_string_to_pdf object
339
- when object.is_a?(Symbol)
340
- return _format_name_to_pdf object
341
- when object.is_a?(Array)
342
- return _format_array_to_pdf object
343
- when object.is_a?(Fixnum), object.is_a?(Float), object.is_a?(TrueClass), object.is_a?(FalseClass)
344
- return object.to_s + " "
345
- when object.is_a?(Hash)
346
- return _format_hash_to_pdf object
347
- else
348
- return ''
349
- end
350
- end
351
-
352
- def _format_string_to_pdf(object)
353
- if @string_output == :literal #if format is set to Literal
354
- #### can be better...
355
- replacement_hash = {
356
- "\x0A" => "\\n",
357
- "\x0D" => "\\r",
358
- "\x09" => "\\t",
359
- "\x08" => "\\b",
360
- "\xFF" => "\\f",
361
- "\x28" => "\\(",
362
- "\x29" => "\\)",
363
- "\x5C" => "\\\\"
364
- }
365
- 32.times {|i| replacement_hash[i.chr] ||= "\\#{i}"}
366
- (256-128).times {|i| replacement_hash[(i + 127).chr] ||= "\\#{i+127}"}
367
- ("(" + ([].tap {|out| object.bytes.each {|byte| replacement_hash[ byte.chr ] ? (replacement_hash[ byte.chr ].bytes.each {|b| out << b}) : out << byte } }).pack('C*') + ")").force_encoding(Encoding::ASCII_8BIT)
368
- else
369
- # A hexadecimal string shall be written as a sequence of hexadecimal digits (0–9 and either A–F or a–f)
370
- # encoded as ASCII characters and enclosed within angle brackets (using LESS-THAN SIGN (3Ch) and GREATER- THAN SIGN (3Eh)).
371
- ("<" + object.unpack('H*')[0] + ">").force_encoding(Encoding::ASCII_8BIT)
372
- end
373
- end
374
- def _format_name_to_pdf(object)
375
- # a name object is an atomic symbol uniquely defined by a sequence of ANY characters (8-bit values) except null (character code 0).
376
- # print name as a simple string. all characters between ~ and ! (except #) can be raw
377
- # the rest will have a number sign and their HEX equivalant
378
- # from the standard:
379
- # When writing a name in a PDF file, a SOLIDUS (2Fh) (/) shall be used to introduce a name. The SOLIDUS is not part of the name but is a prefix indicating that what follows is a sequence of characters representing the name in the PDF file and shall follow these rules:
380
- # a) A NUMBER SIGN (23h) (#) in a name shall be written by using its 2-digit hexadecimal code (23), preceded by the NUMBER SIGN.
381
- # b) Any character in a name that is a regular character (other than NUMBER SIGN) shall be written as itself or by using its 2-digit hexadecimal code, preceded by the NUMBER SIGN.
382
- # c) Any character that is not a regular character shall be written using its 2-digit hexadecimal code, preceded by the NUMBER SIGN only.
383
- # [0x00, 0x09, 0x0a, 0x0c, 0x0d, 0x20, 0x28, 0x29, 0x3c, 0x3e, 0x5b, 0x5d, 0x7b, 0x7d, 0x2f, 0x25]
384
- out = object.to_s.bytes.map do |b|
385
- case b
386
- when 0..15
387
- '#0' + b.to_s(16)
388
- when 15..32, 35, 37, 40, 41, 47, 60, 62, 91, 93, 123, 125, 127..256
389
- '#' + b.to_s(16)
390
- else
391
- b.chr
392
- end
393
- end
394
- "/" + out.join()
395
- end
396
- def _format_array_to_pdf(object)
397
- # An array shall be written as a sequence of objects enclosed in SQUARE BRACKETS (using LEFT SQUARE BRACKET (5Bh) and RIGHT SQUARE BRACKET (5Dh)).
398
- # EXAMPLE [549 3.14 false (Ralph) /SomeName]
399
- ("[" + (object.collect {|item| _object_to_pdf(item)}).join(' ') + "]").force_encoding(Encoding::ASCII_8BIT)
400
-
401
- end
402
-
403
- def _format_hash_to_pdf(object)
404
- # if the object is only a reference:
405
- # special conditions apply, and there is only the setting of the reference (if needed) and output
406
- if object[:is_reference_only]
407
- #
408
- if object[:referenced_object] && object[:referenced_object].is_a?(Hash)
409
- object[:indirect_reference_id] = object[:referenced_object][:indirect_reference_id]
410
- object[:indirect_generation_number] = object[:referenced_object][:indirect_generation_number]
411
- end
412
- object[:indirect_reference_id] ||= 0
413
- object[:indirect_generation_number] ||= 0
414
- return "#{object[:indirect_reference_id].to_s} #{object[:indirect_generation_number].to_s} R".force_encoding(Encoding::ASCII_8BIT)
415
- end
416
-
417
- # if the object is indirect...
418
- out = []
419
- if object[:indirect_reference_id]
420
- object[:indirect_reference_id] ||= 0
421
- object[:indirect_generation_number] ||= 0
422
- out << "#{object[:indirect_reference_id].to_s} #{object[:indirect_generation_number].to_s} obj\n".force_encoding(Encoding::ASCII_8BIT)
423
- if object[:indirect_without_dictionary]
424
- out << _object_to_pdf(object[:indirect_without_dictionary])
425
- out << "\nendobj\n"
426
- return out.join().force_encoding(Encoding::ASCII_8BIT)
427
- end
428
- end
429
- # correct stream length, if the object is a stream.
430
- object[:Length] = object[:raw_stream_content].bytesize if object[:raw_stream_content]
431
-
432
- # if the object is not a simple object, it is a dictionary
433
- # A dictionary shall be written as a sequence of key-value pairs enclosed in double angle brackets (<<...>>)
434
- # (using LESS-THAN SIGNs (3Ch) and GREATER-THAN SIGNs (3Eh)).
435
- out << "<<\n".force_encoding(Encoding::ASCII_8BIT)
436
- object.each do |key, value|
437
- out << "#{_object_to_pdf key} #{_object_to_pdf value}\n".force_encoding(Encoding::ASCII_8BIT) unless PRIVATE_HASH_KEYS.include? key
438
- end
439
- out << ">>".force_encoding(Encoding::ASCII_8BIT)
440
- out << "\nstream\n#{object[:raw_stream_content]}\nendstream".force_encoding(Encoding::ASCII_8BIT) if object[:raw_stream_content]
441
- out << "\nendobj\n" if object[:indirect_reference_id]
442
- out.join().force_encoding(Encoding::ASCII_8BIT)
443
- end
444
-
445
-
446
-
447
- end
448
- end
449
101
 
450
102
  #########################################################
451
103
  # this file is part of the CombinePDF library and the code
@@ -7,58 +7,181 @@
7
7
 
8
8
 
9
9
 
10
+
10
11
  module CombinePDF
11
12
 
12
13
  #@private
13
14
  #:nodoc: all
14
- # This doesn't work yet!
15
+ #
16
+ # <b>This doesn't work yet!</b>
17
+ #
18
+ # and alsom, for even when it will work, UNICODE SUPPORT IS MISSING!
19
+ #
15
20
  # in the future I wish to make a simple PDF page writer, that has only one functions - the text box.
16
21
  # Once the simple writer is ready (creates a text box in a self contained Page element),
17
22
  # I could add it to the << operators and add it as either a self contained page or as an overlay.
18
23
  # if all goes well, maybe I will also create an add_image function.
19
- class PDFWriter
24
+ #
25
+ # The PDFWriter class is a subclass of Hash and represents a PDF Page object.
26
+ #
27
+ # Writing on this Page is done using the text_box function.
28
+ #
29
+ # the rest of the functions are for internal use.
30
+ #
31
+ # Once the Page is completed (the last text box was added),
32
+ # we can insert the page to a CombinePDF object.
33
+ #
34
+ # We can either insert the PDFWriter as a new page:
35
+ # pdf = CombinePDF.new
36
+ # new_page = PDFWriter.new
37
+ # new_page.text_box "some text"
38
+ # pdf << new_page
39
+ # pdf.save "file_with_new_page.pdf"
40
+ # Or we can insert the PDFWriter as an overlay (stamp / watermark) over existing pages:
41
+ # pdf = CombinePDF.new
42
+ # new_page = PDFWriter.new "some_file.pdf"
43
+ # new_page.text_box "some text"
44
+ # pdf.pages.each {|page| page << new_page }
45
+ # pdf.save "stamped_file.pdf"
46
+ class PDFWriter < Hash
20
47
 
21
48
  def initialize(media_box = [0.0, 0.0, 612.0, 792.0])
22
- @content_stream = {}
23
- @media_box = media_box
49
+ # indirect_reference_id, :indirect_generation_number
50
+ self[:Type] = :Page
51
+ self[:indirect_reference_id] = 0
52
+ self[:Resources] = {}
53
+ self[:Contents] = { is_reference_only: true , referenced_object: {indirect_reference_id: 0, raw_stream_content: ""} }
54
+ self[:MediaBox] = media_box
55
+ end
56
+
57
+ # accessor (getter) for the :Resources element of the page
58
+ def resources
59
+ self[:Resources]
60
+ end
61
+ # accessor (getter) for the stream in the :Contents element of the page
62
+ # after getting the string object, you can operate on it but not replace it (use << or other String methods).
63
+ def contents
64
+ self[:Contents][:referenced_object][:raw_stream_content]
65
+ end
66
+ # accessor (getter) for the :MediaBox element of the page
67
+ def media_box
68
+ self[:MediaBox]
69
+ end
70
+ # accessor (setter) for the :MediaBox element of the page
71
+ # dimentions:: an Array consisting of four numbers (can be floats) setting the size of the media box.
72
+ def media_box=(dimentions = [0.0, 0.0, 612.0, 792.0])
73
+ self[:MediaBox] = dimentions
74
+ end
75
+ # creates a font object and adds the font to the resources dictionary
76
+ # returns the name of the font for the content stream.
77
+ # font_name:: a Symbol of one of the 14 Type 1 fonts, known as the standard 14 fonts:
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
+ def font(font_name = :Helvetica)
93
+ # refuse any other fonts that arn't basic standard fonts
94
+ allow_fonts = [ :"Times-Roman",
95
+ :"Times-Bold",
96
+ :"Times-Italic",
97
+ :"Times-BoldItalic",
98
+ :Helvetica,
99
+ :"Helvetica-Bold",
100
+ :"Helvetica-BoldOblique",
101
+ :"Helvetica-Oblique",
102
+ :Courier,
103
+ :"Courier-Bold",
104
+ :"Courier-Oblique",
105
+ :"Courier-BoldOblique",
106
+ :Symbol,
107
+ :ZapfDingbats ]
108
+ raise "add_font(font_name) accepts only one of the 14 standards fonts - wrong font_name!" unless allow_fonts.include? font_name
109
+ # if the font exists, return it's name
110
+ resources[:Font] ||= {}
111
+ resources[:Font].each do |k,v|
112
+ if v.is_a?(Hash) && v[:Type] == :Font && v[:BaseFont] == font_name
113
+ return k
114
+ end
115
+ end
116
+ # create font object
117
+ font_object = { Type: :Font, Subtype: :Type1, BaseFont: font_name}
118
+ # set a secure name for the font
119
+ name = (SecureRandom.urlsafe_base64(9)).to_sym
120
+ # add object to reasource
121
+ resources[:Font][name] = font_object
122
+ #return name
123
+ name
124
+ end
125
+ def graphic_state(graphic_state_dictionary = {})
126
+ # if the graphic state exists, return it's name
127
+ resources[:ExtGState] ||= {}
128
+ resources[:ExtGState].each do |k,v|
129
+ if v.is_a?(Hash) && v == graphic_state_dictionary
130
+ return k
131
+ end
132
+ end
133
+ # set graphic state type
134
+ graphic_state_dictionary[:Type] = :ExtGState
135
+ # set a secure name for the graphic state
136
+ name = (SecureRandom.urlsafe_base64(9)).to_sym
137
+ # add object to reasource
138
+ resources[:ExtGState][name] = graphic_state_dictionary
139
+ #return name
140
+ name
24
141
  end
25
142
 
26
- ########################################################
27
- ## textbox
28
- ## - font_name: :font_name
29
- ## The PostScript names of 14 Type 1 fonts, known as the standard 14 fonts, are as follows:
30
- ## Times-Roman, Helvetica, Courier, Symbol, Times-Bold, Helvetica-Bold, Courier-Bold, ZapfDingbats, Times-Italic, Helvetica- Oblique, Courier-Oblique, Times-BoldItalic, Helvetica-BoldOblique, Courier-BoldOblique
31
- ## - text_color: [R, G, B]
32
- ## an array with three floats, each in a value between 0 to 1.
33
- ## First value is Red, second Green and last is Blue (RGB color system)
34
- def add_text_box(text, args = {})
143
+ # <b>INCOMPLETE</b>
144
+ #
145
+ # This function, when completed, will add a simple text box to the Page represented by the PDFWriter class.
146
+ # This function takes two values:
147
+ # text:: the text to potin the box.
148
+ # properties:: a Hash of box properties.
149
+ # the symbols and values in the properties Hash could be any or all of the following:
150
+ # x:: the left position of the box.
151
+ # y:: the BUTTOM position of the box.
152
+ # length:: the length of the box.
153
+ # height:: the height of the box.
154
+ # font_name:: a Symbol representing one of the 14 standard fonts. defaults to ":Helvetica" @see add_font
155
+ # font_size:: a Fixnum for the font size, or :fit_text to fit the text in the box. defaults to ":fit_text"
156
+ # text_color:: [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]").
157
+ def text_box(text, properties = {})
35
158
  options = {
36
159
  text_alignment: :center,
37
- text_color: [1,1,1],
38
- # text_stroke: nil,
160
+ text_color: [0,0,0],
161
+ text_stroke_color: nil,
162
+ text_stroke_width: 0,
39
163
  font_name: :Helvetica,
40
- font_type: :Type1,
41
- font_object: nil,
42
- font_size: 12,
43
- border_color: nil,
44
- border_width: nil,
45
- border_radius: nil,
46
- background_color: nil,
164
+ font_size: :fit_text,
165
+ border_color: [0.5,0.5,0.5],
166
+ border_width: 2,
167
+ border_radius: 0,
168
+ background_color: [0.7,0.7,0.7],
47
169
  opacity: 1,
48
170
  x: 0,
49
171
  y: 0,
50
172
  length: -1,
51
173
  height: -1,
52
174
  }
53
- # create font object
54
- font_object = { Type: :Font, Subtype: options[:font_type], BaseFont: options[:font_name]}
55
- if options[:font_object].is_a?(Hash) && options[:font_object][:indirect_reference_id] && options[:font_object][:indirect_generation_number] && (options[:font_object][:is_reference_only] != true)
56
- font_object = {is_reference_only: true, referenced_object: font_object}
175
+ options.update properties
176
+ # reset the length and height to meaningful values, if negative
177
+ options[:length] = media_box[2] - options[:x] if options[:length] < 0
178
+ options[:height] = media_box[3] - options[:y] if options[:height] < 0
179
+ # fit text in box, if requested
180
+ if options[:font_size] == :fit_text
181
+ options[:font_size] = self.fit_text text, options[:font_name], options[:length], options[:height]
57
182
  end
58
183
 
59
- # create resources object
60
- font_name = ("MyFont" + rand(99) ).to_sym
61
- resources_object = {Resources: {Font: { font_name => font_object } } }
184
+
62
185
  # create box stream
63
186
 
64
187
  # reset x,y by text alignment - x,y are calculated from the buttom left
@@ -68,47 +191,39 @@ module CombinePDF
68
191
  # create text stream
69
192
  text_stream = ""
70
193
  text_stream << "BT\n" # the Begine Text marker
71
- text_stream << PDFOperations._format_name_to_pdf(font_name) # Set font name
194
+ text_stream << PDFOperations._format_name_to_pdf(font options[:font_name]) # Set font name
72
195
  text_stream << " #{options[:font_size].to_f} Tf\n" # set font size and add font operator
73
196
  text_stream << "#{options[:text_color][0]} #{options[:text_color][0]} #{options[:text_color][0]} rg\n" # sets the color state
74
- text_stream << " #{options[:opacity].to_f} ca\n" # set opacity (alpha) for graphic state.
75
197
  text_stream << "#{x} #{y} Td\n" # set location for text object
76
198
  text_stream << PDFOperations._format_string_to_pdf(text) # insert the string in PDF format
77
199
  text_stream << " Tj\n ET\n" # the Text object operator and the End Text marker
78
- end
79
200
 
80
- ########################################################
81
- ## add_content_to_pages(pages = [], location = :above)
82
- ## pages - a page hash or an array of pages
83
- ## location - :above to place content over existing content or :below to place content under existing content
84
- def add_content_to_pages(pages = [], location = :above)
85
- if pages.is_a?(Array)
86
- pages.each {|p| add_content_to_pages p, location}
87
- elsif pages.is_a?(Hash)
88
- #####
89
- ##add content stream to page
90
- end
91
- end
92
- ########################################################
93
- ## make_into_page()
94
- ## takes no arguments and returns the contents stream within a page (to be added as an indipendent page to the PDF object)
95
- def make_into_page
96
- {Type: :Page, }
97
- end
201
+ final_stream = ""
202
+ # set graphic state for box
203
+ final_stream << "q\nq\nq\n"
204
+ box_graphic_state = graphic_state ca: options[:opacity], CA: options[:opacity], LW: options[:border_width], LC: 2, LJ:1, LD: 0
205
+ final_stream << "#{PDFOperations._object_to_pdf box_graphic_state} gs\n"
206
+ final_stream << "DeviceRGB CS\nDeviceRGB cs\n"
98
207
 
99
- ########################################################
100
- ## to_pdf()
101
- ## prints out the content stream as raw PDF
102
- ## file_name - the name of the file to which to save the data (will be overwritten).
103
- ## if file_name is given, save to file.
104
- def to_pdf( file_name = nil)
105
- pdf = PDF.new
106
- pdf << make_into_page
107
- if file_name
108
- pdf.save file_name
208
+ # set graphic state for text
209
+ final_stream << "q\nq\nq\n"
210
+ text_graphic_state = graphic_state({ca: options[:opacity], CA: options[:opacity], LW: options[:text_stroke_width], LC: 2, LJ: 1, LD: 0})
211
+ final_stream << "#{PDFOperations._object_to_pdf text_graphic_state} gs\n"
212
+ final_stream << "DeviceRGB CS\nDeviceRGB cs\n"
213
+ final_stream << "#{options[:text_color][0]} #{options[:text_color][1]} #{options[:text_color][2]} scn\n"
214
+ if options[:text_stroke_width].to_i > 0 && options[:text_stroke_color]
215
+ final_stream << "#{options[:text_stroke_color][0]} #{options[:text_stroke_color][1]} #{options[:text_stroke_color][2]} SCN\n"
216
+ final_stream << "2 Tr\n"
109
217
  else
110
- pdf.to_pdf
218
+ final_stream << "0 Tr\n"
111
219
  end
220
+
221
+ # clear graphic states
222
+ final_stream << "Q\nQ\nQ\n"
223
+ final_stream << "Q\nQ\nQ\n"
224
+
225
+ contents << final_stream
226
+ self
112
227
  end
113
228
 
114
229
  end
@@ -117,3 +232,76 @@ end
117
232
 
118
233
 
119
234
 
235
+
236
+ # # text_box output example
237
+ # q
238
+ # q
239
+ # /GraphiStateName gs
240
+ # /DeviceRGB cs
241
+ # 0.867 0.867 0.867 scn
242
+ # 293.328 747.000 m
243
+ # 318.672 747.000 l
244
+ # 323.090 747.000 326.672 743.418 326.672 739.000 c
245
+ # 326.672 735.800 l
246
+ # 326.672 731.382 323.090 727.800 318.672 727.800 c
247
+ # 293.328 727.800 l
248
+ # 288.910 727.800 285.328 731.382 285.328 735.800 c
249
+ # 285.328 739.000 l
250
+ # 285.328 743.418 288.910 747.000 293.328 747.000 c
251
+ # h
252
+ # 293.328 64.200 m
253
+ # 318.672 64.200 l
254
+ # 323.090 64.200 326.672 60.618 326.672 56.200 c
255
+ # 326.672 53.000 l
256
+ # 326.672 48.582 323.090 45.000 318.672 45.000 c
257
+ # 293.328 45.000 l
258
+ # 288.910 45.000 285.328 48.582 285.328 53.000 c
259
+ # 285.328 56.200 l
260
+ # 285.328 60.618 288.910 64.200 293.328 64.200 c
261
+ # h
262
+ # f
263
+ # 0.000 0.000 0.000 scn
264
+ # /DeviceRGB CS
265
+ # 1.000 1.000 1.000 SCN
266
+
267
+ # 2 Tr
268
+ # 0.000 0.000 0.000 scn
269
+ # 0.000 0.000 0.000 SCN
270
+ # 1.000 1.000 1.000 SCN
271
+ # 0.000 0.000 0.000 scn
272
+ # 0.000 0.000 0.000 scn
273
+ # 0.000 0.000 0.000 SCN
274
+
275
+ # BT
276
+ # 291.776 733.3119999999999 Td
277
+ # /FontName 16 Tf
278
+ # [<2d2032202d>] TJ
279
+ # ET
280
+
281
+ # 1.000 1.000 1.000 SCN
282
+ # 0.000 0.000 0.000 scn
283
+ # 0.000 0.000 0.000 scn
284
+ # 0.000 0.000 0.000 SCN
285
+ # 1.000 1.000 1.000 SCN
286
+ # 0.000 0.000 0.000 scn
287
+ # 0.000 0.000 0.000 scn
288
+ # 0.000 0.000 0.000 SCN
289
+
290
+ # BT
291
+ # 291.776 50.512 Td
292
+ # /FontName 16 Tf
293
+ # [<2d2032202d>] TJ
294
+ # ET
295
+
296
+ # 1.000 1.000 1.000 SCN
297
+ # 0.000 0.000 0.000 scn
298
+
299
+ # 0 Tr
300
+ # Q
301
+ # Q
302
+
303
+
304
+
305
+
306
+
307
+