combine_pdf 0.1.23 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,168 @@
1
+ module CombinePDF
2
+
3
+ ################################################################
4
+ ## These are common functions, used within the different classes
5
+ ## These functions aren't open to the public.
6
+ ################################################################
7
+
8
+
9
+ # This is an internal module used to render ruby objects into pdf objects.
10
+ module Renderer
11
+
12
+ # @!visibility private
13
+
14
+ protected
15
+
16
+
17
+ # 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.
18
+ def object_to_pdf object
19
+ case
20
+ when object.nil?
21
+ return "null"
22
+ when object.is_a?(String)
23
+ return format_string_to_pdf object
24
+ when object.is_a?(Symbol)
25
+ return format_name_to_pdf object
26
+ when object.is_a?(Array)
27
+ return format_array_to_pdf object
28
+ when object.is_a?(Fixnum), object.is_a?(Float), object.is_a?(TrueClass), object.is_a?(FalseClass)
29
+ return object.to_s + " "
30
+ when object.is_a?(Hash)
31
+ return format_hash_to_pdf object
32
+ else
33
+ return ''
34
+ end
35
+ end
36
+
37
+ def format_string_to_pdf(object)
38
+ object.force_encoding(Encoding::ASCII_8BIT)
39
+ if !object.force_encoding(Encoding::ASCII_8BIT).match(/[^D\:\d\+\-\Z\']/) #if format is set to Literal
40
+ #### can be better...
41
+ replacement_hash = {
42
+ "\x0A" => "\\n",
43
+ "\x0D" => "\\r",
44
+ "\x09" => "\\t",
45
+ "\x08" => "\\b",
46
+ "\xFF" => "\\f",
47
+ "\x28" => "\\(",
48
+ "\x29" => "\\)",
49
+ "\x5C" => "\\\\"
50
+ }
51
+ 32.times {|i| replacement_hash[i.chr] ||= "\\#{i}"}
52
+ (256-128).times {|i| replacement_hash[(i + 127).chr] ||= "\\#{i+127}"}
53
+ ("(" + ([].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)
54
+ else
55
+ # A hexadecimal string shall be written as a sequence of hexadecimal digits (0–9 and either A–F or a–f)
56
+ # encoded as ASCII characters and enclosed within angle brackets (using LESS-THAN SIGN (3Ch) and GREATER- THAN SIGN (3Eh)).
57
+ ("<" + object.unpack('H*')[0] + ">").force_encoding(Encoding::ASCII_8BIT)
58
+ end
59
+ end
60
+ def format_name_to_pdf(object)
61
+ # a name object is an atomic symbol uniquely defined by a sequence of ANY characters (8-bit values) except null (character code 0).
62
+ # print name as a simple string. all characters between ~ and ! (except #) can be raw
63
+ # the rest will have a number sign and their HEX equivalant
64
+ # from the standard:
65
+ # 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:
66
+ # a) A NUMBER SIGN (23h) (#) in a name shall be written by using its 2-digit hexadecimal code (23), preceded by the NUMBER SIGN.
67
+ # 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.
68
+ # c) Any character that is not a regular character shall be written using its 2-digit hexadecimal code, preceded by the NUMBER SIGN only.
69
+ # [0x00, 0x09, 0x0a, 0x0c, 0x0d, 0x20, 0x28, 0x29, 0x3c, 0x3e, 0x5b, 0x5d, 0x7b, 0x7d, 0x2f, 0x25]
70
+ out = object.to_s.bytes.to_a.map do |b|
71
+ case b
72
+ when 0..15
73
+ '#0' + b.to_s(16)
74
+ when 15..32, 35, 37, 40, 41, 47, 60, 62, 91, 93, 123, 125, 127..256
75
+ '#' + b.to_s(16)
76
+ else
77
+ b.chr
78
+ end
79
+ end
80
+ "/" + out.join()
81
+ end
82
+ def format_array_to_pdf(object)
83
+ # An array shall be written as a sequence of objects enclosed in SQUARE BRACKETS (using LEFT SQUARE BRACKET (5Bh) and RIGHT SQUARE BRACKET (5Dh)).
84
+ # EXAMPLE [549 3.14 false (Ralph) /SomeName]
85
+ ("[" + (object.collect {|item| object_to_pdf(item)}).join(' ') + "]").force_encoding(Encoding::ASCII_8BIT)
86
+
87
+ end
88
+
89
+ def format_hash_to_pdf(object)
90
+ # if the object is only a reference:
91
+ # special conditions apply, and there is only the setting of the reference (if needed) and output
92
+ if object[:is_reference_only]
93
+ #
94
+ if object[:referenced_object] && object[:referenced_object].is_a?(Hash)
95
+ object[:indirect_reference_id] = object[:referenced_object][:indirect_reference_id]
96
+ object[:indirect_generation_number] = object[:referenced_object][:indirect_generation_number]
97
+ end
98
+ object[:indirect_reference_id] ||= 0
99
+ object[:indirect_generation_number] ||= 0
100
+ return "#{object[:indirect_reference_id].to_s} #{object[:indirect_generation_number].to_s} R".force_encoding(Encoding::ASCII_8BIT)
101
+ end
102
+
103
+ # if the object is indirect...
104
+ out = []
105
+ if object[:indirect_reference_id]
106
+ object[:indirect_reference_id] ||= 0
107
+ object[:indirect_generation_number] ||= 0
108
+ out << "#{object[:indirect_reference_id].to_s} #{object[:indirect_generation_number].to_s} obj\n".force_encoding(Encoding::ASCII_8BIT)
109
+ if object[:indirect_without_dictionary]
110
+ out << object_to_pdf(object[:indirect_without_dictionary])
111
+ out << "\nendobj\n"
112
+ return out.join().force_encoding(Encoding::ASCII_8BIT)
113
+ end
114
+ end
115
+ # correct stream length, if the object is a stream.
116
+ object[:Length] = object[:raw_stream_content].bytesize if object[:raw_stream_content]
117
+
118
+ # if the object is not a simple object, it is a dictionary
119
+ # A dictionary shall be written as a sequence of key-value pairs enclosed in double angle brackets (<<...>>)
120
+ # (using LESS-THAN SIGNs (3Ch) and GREATER-THAN SIGNs (3Eh)).
121
+ out << "<<\n".force_encoding(Encoding::ASCII_8BIT)
122
+ object.each do |key, value|
123
+ out << "#{object_to_pdf key} #{object_to_pdf value}\n".force_encoding(Encoding::ASCII_8BIT) unless PDF::PRIVATE_HASH_KEYS.include? key
124
+ end
125
+ out << ">>".force_encoding(Encoding::ASCII_8BIT)
126
+ out << "\nstream\n#{object[:raw_stream_content]}\nendstream".force_encoding(Encoding::ASCII_8BIT) if object[:raw_stream_content]
127
+ out << "\nendobj\n" if object[:indirect_reference_id]
128
+ out.join().force_encoding(Encoding::ASCII_8BIT)
129
+ end
130
+
131
+ def actual_object obj
132
+ obj.is_a?(Hash) ? (obj[:referenced_object] || obj) : obj
133
+ end
134
+
135
+ # Ruby normally assigns pointes.
136
+ # noramlly:
137
+ # a = [1,2,3] # => [1,2,3]
138
+ # b = a # => [1,2,3]
139
+ # a << 4 # => [1,2,3,4]
140
+ # b # => [1,2,3,4]
141
+ # This method makes sure that the memory is copied instead of a pointer assigned.
142
+ # this works using recursion, so that arrays and hashes within arrays and hashes are also copied and not pointed to.
143
+ # One needs to be careful of infinit loops using this function.
144
+ def create_deep_copy object
145
+ if object.is_a?(Array)
146
+ return object.map { |e| create_deep_copy e }
147
+ elsif object.is_a?(Hash)
148
+ return {}.tap {|out| object.each {|k,v| out[create_deep_copy(k)] = create_deep_copy(v) unless k == :Parent} }
149
+ elsif object.is_a?(String)
150
+ return object.dup
151
+ else
152
+ return object # objects that aren't Strings, Arrays or Hashes (such as Symbols and Fixnums) won't be edited inplace.
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ #########################################################
159
+ # this file is part of the CombinePDF library and the code
160
+ # is subject to the same license (MIT).
161
+ #########################################################
162
+ # PDF object types cross reference:
163
+ # Indirect objects, references, dictionaries and streams are Hash
164
+ # arrays are Array
165
+ # strings are String
166
+ # names are Symbols (String.to_sym)
167
+ # numbers are Fixnum or Float
168
+ # boolean are TrueClass or FalseClass
@@ -1,3 +1,3 @@
1
1
  module CombinePDF
2
- VERSION = "0.1.23"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: combine_pdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.23
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boaz Segev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-28 00:00:00.000000000 Z
11
+ date: 2015-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-rc4
@@ -53,8 +53,8 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '10.0'
55
55
  description: A nifty gem, in pure Ruby, to parse PDF files and combine (merge) them
56
- with other PDF files, number the pages, watermark them or stamp them, create tables
57
- or basic text objects etc` (all using the PDF file format).
56
+ with other PDF files, number the pages, watermark them or stamp them, create tables,
57
+ add basic text objects etc` (all using the PDF file format).
58
58
  email:
59
59
  - boaz@2be.co.il
60
60
  executables: []
@@ -69,15 +69,17 @@ files:
69
69
  - Rakefile
70
70
  - combine_pdf.gemspec
71
71
  - lib/combine_pdf.rb
72
- - lib/combine_pdf/combine_pdf_basic_writer.rb
73
- - lib/combine_pdf/combine_pdf_decrypt.rb
74
- - lib/combine_pdf/combine_pdf_filter.rb
75
- - lib/combine_pdf/combine_pdf_fonts.rb
76
- - lib/combine_pdf/combine_pdf_methods.rb
77
- - lib/combine_pdf/combine_pdf_operations.rb
78
- - lib/combine_pdf/combine_pdf_page.rb
79
- - lib/combine_pdf/combine_pdf_parser.rb
80
- - lib/combine_pdf/combine_pdf_pdf.rb
72
+ - lib/combine_pdf/api.rb
73
+ - lib/combine_pdf/basic_writer.rb
74
+ - lib/combine_pdf/decrypt.rb
75
+ - lib/combine_pdf/filter.rb
76
+ - lib/combine_pdf/fonts.rb
77
+ - lib/combine_pdf/operations.rb
78
+ - lib/combine_pdf/page_methods.rb
79
+ - lib/combine_pdf/parser.rb
80
+ - lib/combine_pdf/pdf_protected.rb
81
+ - lib/combine_pdf/pdf_public.rb
82
+ - lib/combine_pdf/renderer.rb
81
83
  - lib/combine_pdf/version.rb
82
84
  homepage: https://github.com/boazsegev/combine_pdf
83
85
  licenses:
@@ -1,451 +0,0 @@
1
- # -*- encoding : utf-8 -*-
2
- ########################################################
3
- ## Thoughts from reading the ISO 32000-1:2008
4
- ## this file is part of the CombinePDF library and the code
5
- ## is subject to the same license.
6
- ########################################################
7
-
8
-
9
-
10
-
11
- module CombinePDF
12
-
13
- # Limited Unicode Support (font dependent)!
14
- #
15
- # The PDFWriter class is a subclass of Hash and represents a PDF Page object.
16
- #
17
- # Writing on this Page is done using the textbox function.
18
- #
19
- # Setting the page dimensions can be either at the new or using the mediabox method. New pages default to size A4, which is: [0, 0, 595.3, 841.9].
20
- #
21
- # Once the Page is completed (the last text box was added),
22
- # we can insert the page to a CombinePDF object.
23
- #
24
- # We can either insert the PDFWriter as a new page:
25
- # pdf = CombinePDF.new
26
- # new_page = PDFWriter.new
27
- # new_page.textbox "some text"
28
- # pdf << new_page
29
- # pdf.save "file_with_new_page.pdf"
30
- # Or we can insert the PDFWriter as an overlay (stamp / watermark) over existing pages:
31
- # pdf = CombinePDF.new
32
- # new_page = PDFWriter.new "some_file.pdf"
33
- # new_page.textbox "some text"
34
- # pdf.pages.each {|page| page << new_page }
35
- # pdf.save "stamped_file.pdf"
36
- class PDFWriter < Hash
37
-
38
- # create a new PDFWriter object.
39
- #
40
- # mediabox:: the PDF page size in PDF points. defaults to [0, 0, 595.3, 841.9] (A4)
41
- def initialize(mediabox = [0, 0, 595.3, 841.9])
42
- # indirect_reference_id, :indirect_generation_number
43
- @contents = ""
44
- @base_font_name = "Writer" + SecureRandom.hex(7) + "PDF"
45
- self[:Type] = :Page
46
- self[:indirect_reference_id] = 0
47
- self[:Resources] = {}
48
- self[:Contents] = { is_reference_only: true , referenced_object: {indirect_reference_id: 0, raw_stream_content: @contents} }
49
- self[:MediaBox] = mediabox
50
- end
51
-
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
443
-
444
- end
445
-
446
- end
447
-
448
-
449
-
450
-
451
-