Module: CombinePDF::Page_Methods
Overview
This module injects page editing methods into existing page objects and the PDFWriter objects.
Constant Summary collapse
- CONTENT_CONTAINER_START =
holds the string that starts a PDF graphic state container - used for wrapping malformed PDF content streams.
'q'
- CONTENT_CONTAINER_MIDDLE =
holds the string that ends a PDF graphic state container - used for wrapping malformed PDF content streams.
"Q\nq"
- CONTENT_CONTAINER_END =
holds the string that ends a PDF graphic state container - used for wrapping malformed PDF content streams.
'Q'
Instance Method Summary collapse
-
#<<(obj) ⇒ Object
the injection method.
- #>>(obj) ⇒ Object
-
#copy(secure = false) ⇒ Object
creates a copy of the page.
-
#cropbox ⇒ Object
accessor (getter) for the :CropBox element of the page.
-
#cropbox=(dimensions = [0.0, 0.0, 612.0, 792.0]) ⇒ Object
- accessor (setter) for the :CropBox element of the page dimensions
-
an Array consisting of four numbers (can be floats) setting the size of the media box.
-
#dimensions_of(text, fonts, size = 1000) ⇒ Object
gets the dimentions (width and height) of the text, as it will be printed in the PDF.
-
#fit_text(text, font, length, height = 10000000) ⇒ Object
this method returns the size for which the text fits the requested metrices the size is type Float and is rather exact if the text cannot fit such a small place, returns zero (0).
-
#fix_rotation ⇒ Object
This method moves the Page property into the page’s data stream, so that “what you see is what you get”.
- #inject_page(obj, top = true) ⇒ Object
-
#make_secure ⇒ Object
sets secure_injection to ‘true` and returns self, allowing for chaining methods.
-
#make_unsecure ⇒ Object
sets secure_injection to ‘false` and returns self, allowing for chaining methods.
-
#mediabox ⇒ Object
accessor (getter) for the :MediaBox element of the page.
-
#mediabox=(dimensions = [0.0, 0.0, 612.0, 792.0]) ⇒ Object
- accessor (setter) for the :MediaBox element of the page dimensions
-
an Array consisting of four numbers (can be floats) setting the size of the media box.
-
#orientation(force = nil, clockwise = true) ⇒ Object
get or set (by clockwise rotation) the page’s orientation.
-
#page_size ⇒ Object
get page size.
-
#resize(new_size = nil, conserve_aspect_ratio = true) ⇒ Object
resizes the page relative to it’s current viewport (either the cropbox or the mediabox), setting the new viewport to the requested size.
-
#resources ⇒ Object
accessor (getter) for the :Resources element of the page.
-
#rotate_180 ⇒ Object
rotate the page by 180 degrees.
-
#rotate_left ⇒ Object
rotate the page 90 degrees counter clockwise.
-
#rotate_right ⇒ Object
rotate the page 90 degrees clockwise.
-
#secure_injection ⇒ Object
accessor (getter) for the secure_injection setting.
-
#secure_injection=(safe) ⇒ Object
accessor (setter) for the secure_injection setting.
-
#textbox(text, properties = {}) ⇒ Object
This method adds a simple text box to the Page represented by the PDFWriter class.
-
#write_table(options = {}) ⇒ Object
Writes a table to the current page, removing(!) the written rows from the table_data Array.
Instance Method Details
#<<(obj) ⇒ Object
the injection method
48 49 50 |
# File 'lib/combine_pdf/page_methods.rb', line 48 def << obj inject_page obj, true end |
#>>(obj) ⇒ Object
51 52 53 |
# File 'lib/combine_pdf/page_methods.rb', line 51 def >> obj inject_page obj, false end |
#copy(secure = false) ⇒ Object
creates a copy of the page. if the :secure flag is set to true, the resource indentifiers (fonts etc’) will be renamed in order to secure their uniqueness.
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 |
# File 'lib/combine_pdf/page_methods.rb', line 581 def copy(secure = false) # since only the Content streams are modified (Resource hashes are created anew), # it should be safe (and a lot faster) to create a deep copy only for the content hashes and streams. delete :Parent prep_content_array page_copy = self.clone page_copy[:Contents] = page_copy[:Contents].map do |obj| obj = obj.dup obj[:referenced_object] = obj[:referenced_object].dup if obj[:referenced_object] obj[:referenced_object][:raw_stream_content] = obj[:referenced_object][:raw_stream_content].dup if obj[:referenced_object] && obj[:referenced_object][:raw_stream_content] obj end if page_copy[:Resources] page_res = page_copy[:Resources] = page_copy[:Resources].dup page_res = page_copy[:Resources][:referenced_object] = page_copy[:Resources][:referenced_object].dup if page_copy[:Resources][:referenced_object] page_res.each do |k, v| v = page_res[k] = v.dup if v.is_a?(Array) || v.is_a?(Hash) v = v[:referenced_object] = v[:referenced_object].dup if v.is_a?(Hash) && v[:referenced_object] v = v[:referenced_object] = v[:referenced_object].dup if v.is_a?(Hash) && v[:referenced_object] end end return page_copy.instance_exec(secure || @secure_injection) { |s| secure_for_copy if s ; init_contents; self } end |
#cropbox ⇒ Object
accessor (getter) for the :CropBox element of the page
112 113 114 |
# File 'lib/combine_pdf/page_methods.rb', line 112 def cropbox actual_object self[:CropBox] end |
#cropbox=(dimensions = [0.0, 0.0, 612.0, 792.0]) ⇒ Object
accessor (setter) for the :CropBox element of the page
- dimensions
-
an Array consisting of four numbers (can be floats) setting the size of the media box.
107 108 109 |
# File 'lib/combine_pdf/page_methods.rb', line 107 def cropbox=(dimensions = [0.0, 0.0, 612.0, 792.0]) self[:CropBox] = dimensions end |
#dimensions_of(text, fonts, size = 1000) ⇒ Object
gets the dimentions (width and height) of the text, as it will be printed in the PDF.
- text
-
the text to measure
- 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.
- size
-
the size of the font (defaults to 1000 points).
351 352 353 |
# File 'lib/combine_pdf/page_methods.rb', line 351 def dimensions_of(text, fonts, size = 1000) Fonts.dimensions_of text, fonts, size end |
#fit_text(text, font, length, height = 10000000) ⇒ Object
this method returns the size for which the text fits the requested metrices the size is type Float and is rather exact if the text cannot fit such a small place, returns zero (0). maximum font size possible is set to 100,000 - which should be big enough for anything
- text
-
the text to fit
- font
-
the font name. @see font
- length
-
the length to fit
- height
-
the height to fit (optional - normally length is the issue)
362 363 364 365 366 367 368 369 370 371 372 373 |
# File 'lib/combine_pdf/page_methods.rb', line 362 def fit_text(text, font, length, height = 10000000) size = 100000 size_array = [size] metrics = Fonts.dimensions_of text, font, size if metrics[0] > length size_array << size * length/metrics[0] end if metrics[1] > height size_array << size * height/metrics[1] end size_array.min end |
#fix_rotation ⇒ Object
This method moves the Page property into the page’s data stream, so that “what you see is what you get”.
This is usful in cases where there might be less control over the source PDF files, and the user assums that the PDF page’s data is the same as the PDF’s pages on screen display (Rotate rotates a page but leaves the data in the original orientation).
The method returns the page object, thus allowing method chaining (i.e. ‘page = 90; page.textbox(’hello!‘).fix_rotation.textbox(’hello!‘)`)
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 |
# File 'lib/combine_pdf/page_methods.rb', line 384 def fix_rotation return self if self[:Rotate].to_f == 0.0 || mediabox.nil? # calculate the rotation r = self[:Rotate].to_f * Math::PI / 180 s = Math.sin(r).round 6 c = Math.cos(r).round 6 ctm = [c, s, -s, c] # calculate the translation (move the origin of x,y to the new origin). x = mediabox[2] - mediabox[0] y = mediabox[3] - mediabox[1] ctm.push( ( (x*c).abs - x*c + (y*s).abs + y*s )/2 , ( (x*s).abs - x*s + (y*c).abs - y*c )/2 ) # insert the rotation stream into the current content stream insert_content "q\n#{ctm.join ' '} cm\n", 0 # close the rotation stream insert_content CONTENT_CONTAINER_END # reset the mediabox and cropbox values - THIS IS ONLY FOR ORIENTATION CHANGE... if ((self[:Rotate].to_f / 90)%2) != 0 self[:MediaBox] = self[:MediaBox].values_at(1,0,3,2) self[:CropBox] = self[:CropBox].values_at(1,0,3,2) if self[:CropBox] end # reset the Rotate property self.delete :Rotate # disconnect the content stream, so that future inserts aren't rotated @contents = false #init_contents # always return self, for chaining. self end |
#inject_page(obj, top = true) ⇒ Object
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/combine_pdf/page_methods.rb', line 54 def inject_page obj, top = true raise TypeError, "couldn't inject data, expecting a PDF page (Hash type)" unless obj.is_a?(Page_Methods) obj = obj.copy( should_secure?(obj) ) #obj.copy(secure_injection) # following the reference chain and assigning a pointer to the correct Resouces object. # (assignments of Strings, Arrays and Hashes are pointers in Ruby, unless the .dup method is called) # injecting each of the values in the injected Page res = resources obj.resources.each do |key, new_val| unless PDF::PRIVATE_HASH_KEYS.include? key # keep CombinePDF structual data intact. if res[key].nil? res[key] = new_val elsif res[key].is_a?(Hash) && new_val.is_a?(Hash) new_val = new_val[:referenced_object] || new_val new_val.update (res[key][:referenced_object] || res[key]) # make sure the old values are respected (res[key][:referenced_object] || res[key]).update new_val # transfer old and new values to the injected page end #Do nothing if array - ot is the PROC array, which is an issue end end resources[:ProcSet] = [:PDF, :Text, :ImageB, :ImageC, :ImageI] # this was recommended by the ISO. 32000-1:2008 if top # if this is a stamp (overlay) insert_content CONTENT_CONTAINER_START, 0 insert_content CONTENT_CONTAINER_MIDDLE self[:Contents].concat obj[:Contents] insert_content CONTENT_CONTAINER_END else #if this was a watermark (underlay? would be lost if the page was scanned, as white might not be transparent) insert_content CONTENT_CONTAINER_MIDDLE, 0 insert_content CONTENT_CONTAINER_START, 0 self[:Contents].insert 1, *obj[:Contents] insert_content CONTENT_CONTAINER_END end init_contents self end |
#make_secure ⇒ Object
sets secure_injection to ‘true` and returns self, allowing for chaining methods
35 36 37 38 39 |
# File 'lib/combine_pdf/page_methods.rb', line 35 def make_secure warn "**Deprecation Warning**: the `Page_Methods#secure_injection`, `Page_Methods#make_unsecure` and `Page_Methods#make_secure` methods are deprecated. Use `Page_Methods#copy(true)` for safeguarding against font/resource conflicts when 'stamping' one PDF page over another." @secure_injection = true self end |
#make_unsecure ⇒ Object
sets secure_injection to ‘false` and returns self, allowing for chaining methods
41 42 43 44 45 |
# File 'lib/combine_pdf/page_methods.rb', line 41 def make_unsecure warn "**Deprecation Warning**: the `Page_Methods#secure_injection`, `Page_Methods#make_unsecure` and `Page_Methods#make_secure` methods are deprecated. Use `Page_Methods#copy(true)` for safeguarding against font/resource conflicts when 'stamping' one PDF page over another." @secure_injection = false self end |
#mediabox ⇒ Object
accessor (getter) for the :MediaBox element of the page
101 102 103 |
# File 'lib/combine_pdf/page_methods.rb', line 101 def mediabox actual_object self[:MediaBox] end |
#mediabox=(dimensions = [0.0, 0.0, 612.0, 792.0]) ⇒ Object
accessor (setter) for the :MediaBox element of the page
- dimensions
-
an Array consisting of four numbers (can be floats) setting the size of the media box.
96 97 98 |
# File 'lib/combine_pdf/page_methods.rb', line 96 def mediabox=(dimensions = [0.0, 0.0, 612.0, 792.0]) self[:MediaBox] = dimensions end |
#orientation(force = nil, clockwise = true) ⇒ Object
get or set (by clockwise rotation) the page’s orientation
accepts one optional parameter:
- force
-
to get the orientation, pass nil. to set the orientatiom, set fource to either :portrait or :landscape. defaults to nil (get orientation).
- clockwise
-
sets the rotation directions. defaults to true (clockwise rotation).
returns the current orientation (:portrait or :landscape) if used to get the orientation. otherwise, if used to set the orientation, returns the page object to allow method chaining.
-
Notice: a square page always returns the :portrait value and is ignored when trying to set the orientation.
469 470 471 472 473 474 475 476 477 478 479 |
# File 'lib/combine_pdf/page_methods.rb', line 469 def orientation force = nil, clockwise = true a = page_size unless force return (a[2] - a[0] > a[3] - a[1]) ? :landscape : :portrait end unless orientation == force || (a[2] - a[0] == a[3] - a[1]) self[:Rotate] = 0; clockwise ? rotate_right : rotate_left end self end |
#page_size ⇒ Object
get page size
117 118 119 |
# File 'lib/combine_pdf/page_methods.rb', line 117 def page_size cropbox || mediabox end |
#resize(new_size = nil, conserve_aspect_ratio = true) ⇒ Object
resizes the page relative to it’s current viewport (either the cropbox or the mediabox), setting the new viewport to the requested size.
accepts:
- new_size
-
an Array with four elements: [X0, Y0, X_max, Y_max]. For example, A4: ‘[0, 0, 595, 842]`. It is important that the first two numbers are 0 unless a special effect is attempted. If the first two numbers change, the final result might not be the size requested, but the nearest possible transformation (calling the method again will allow a better resizing).
- conserve_aspect_ratio
-
whether to keep the current content in the same aspect ratio or to allow streaching. Defaults to true - so that although the content is resized, it might not fill the new size completely.
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 |
# File 'lib/combine_pdf/page_methods.rb', line 419 def resize new_size = nil, conserve_aspect_ratio = true return page_size unless new_size c_mediabox = mediabox c_cropbox = cropbox c_size = c_cropbox || c_mediabox x_ratio = 1.0 * (new_size[2]-new_size[0]) / (c_size[2])#-c_size[0]) y_ratio = 1.0 * (new_size[3]-new_size[1]) / (c_size[3])#-c_size[1]) x_move = new_size[0] - c_size[0] y_move = new_size[1] - c_size[1] puts "ctm will be: #{x_ratio.round(4).to_s} 0 0 #{y_ratio.round(4).to_s} #{x_move} #{y_move}" self[:MediaBox] = [(c_mediabox[0] + x_move), (c_mediabox[1] + y_move), ((c_mediabox[2] * x_ratio) + x_move ), ((c_mediabox[3] * y_ratio) + y_move)] self[:CropBox] = [(c_cropbox[0] + x_move), (c_cropbox[1] + y_move), ((c_cropbox[2] * x_ratio) + x_move), ((c_cropbox[3] * y_ratio) + y_move)] if c_cropbox x_ratio = y_ratio = [x_ratio, y_ratio].min if conserve_aspect_ratio # insert the rotation stream into the current content stream # insert_content "q\n#{x_ratio.round(4).to_s} 0 0 #{y_ratio.round(4).to_s} 0 0 cm\n1 0 0 1 #{x_move} #{y_move} cm\n", 0 insert_content "q\n#{x_ratio.round(4).to_s} 0 0 #{y_ratio.round(4).to_s} #{x_move} #{y_move} cm\n", 0 # close the rotation stream insert_content CONTENT_CONTAINER_END # disconnect the content stream, so that future inserts aren't rotated @contents = false #init_contents # always return self, for chaining. self end |
#resources ⇒ Object
accessor (getter) for the :Resources element of the page
122 123 124 125 |
# File 'lib/combine_pdf/page_methods.rb', line 122 def resources self[:Resources] ||= {} self[:Resources][:referenced_object] || self[:Resources] end |
#rotate_180 ⇒ Object
rotate the page by 180 degrees
455 456 457 458 |
# File 'lib/combine_pdf/page_methods.rb', line 455 def rotate_180 self[:Rotate] = self[:Rotate].to_f +180 fix_rotation end |
#rotate_left ⇒ Object
rotate the page 90 degrees counter clockwise
445 446 447 448 |
# File 'lib/combine_pdf/page_methods.rb', line 445 def rotate_left self[:Rotate] = self[:Rotate].to_f + 90 fix_rotation end |
#rotate_right ⇒ Object
rotate the page 90 degrees clockwise
450 451 452 453 |
# File 'lib/combine_pdf/page_methods.rb', line 450 def rotate_right self[:Rotate] = self[:Rotate].to_f - 90 fix_rotation end |
#secure_injection ⇒ Object
accessor (getter) for the secure_injection setting
25 26 27 28 |
# File 'lib/combine_pdf/page_methods.rb', line 25 def secure_injection warn "**Deprecation Warning**: the `Page_Methods#secure_injection`, `Page_Methods#make_unsecure` and `Page_Methods#make_secure` methods are deprecated. Use `Page_Methods#copy(true)` for safeguarding against font/resource conflicts when 'stamping' one PDF page over another." @secure_injection end |
#secure_injection=(safe) ⇒ Object
accessor (setter) for the secure_injection setting
30 31 32 33 |
# File 'lib/combine_pdf/page_methods.rb', line 30 def secure_injection= safe warn "**Deprecation Warning**: the `Page_Methods#secure_injection`, `Page_Methods#make_unsecure` and `Page_Methods#make_secure` methods are deprecated. Use `Page_Methods#copy(true)` for safeguarding against font/resource conflicts when 'stamping' one PDF page over another." @secure_injection = safe end |
#textbox(text, properties = {}) ⇒ Object
This method adds a simple text box to the Page represented by the PDFWriter class. This function takes two values:
- text
-
the text to potin the box.
- properties
-
a Hash of box properties.
the symbols and values in the properties Hash could be any or all of the following:
- x
-
the left position of the box.
- y
-
the BUTTOM position of the box.
- width
-
the width/length of the box. negative values will be computed from edge of page. defaults to 0 (end of page).
- height
-
the height of the box. negative values will be computed from edge of page. defaults to 0 (end of page).
- text_align
-
symbol for horizontal text alignment, can be “:center” (default), “:right”, “:left”
- text_valign
-
symbol for vertical text alignment, can be “:center” (default), “:top”, “:buttom”
- text_padding
-
a Float between 0 and 1, setting the padding for the text. defaults to 0.05 (5%).
- font
-
a registered font name or an Array of names. defaults to “:Helvetica”. The 14 standard fonts names are:
-
:“Times-Roman”
-
:“Times-Bold”
-
:“Times-Italic”
-
:“Times-BoldItalic”
-
:Helvetica
-
:“Helvetica-Bold”
-
:“Helvetica-BoldOblique”
-
:“Helvetica- Oblique”
-
:Courier
-
:“Courier-Bold”
-
:“Courier-Oblique”
-
:“Courier-BoldOblique”
-
:Symbol
-
:ZapfDingbats
- font_size
-
a Fixnum for the font size, or :fit_text to fit the text in the box. defaults to “:fit_text”
- max_font_size
-
if font_size is set to :fit_text, this will be the maximum font size. defaults to nil (no maximum)
- 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.
- 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).
- stroke_width
-
text stroke width in PDF units. defaults to 0 (none).
- 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).
- 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).
- border_width
-
border width in PDF units. defaults to nil (none).
- box_radius
-
border radius in PDF units. defaults to 0 (no corner rounding).
- opacity
-
textbox opacity, a float between 0 (transparent) and 1 (opaque)
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/combine_pdf/page_methods.rb', line 165 def textbox(text, properties = {}) = { x: 0, y: 0, width: 0, height: -1, text_align: :center, text_valign: :center, text_padding: 0.1, font: nil, font_size: :fit_text, max_font_size: nil, font_color: [0,0,0], stroke_color: nil, stroke_width: 0, box_color: nil, border_color: nil, border_width: 0, box_radius: 0, opacity: 1, ctm: nil # ~= [1,0,0,1,0,0] } .update properties # reset the length and height to meaningful values, if negative [:width] = mediabox[2] - [:x] + [:width] if [:width] <= 0 [:height] = mediabox[3] - [:y] + [:height] if [:height] <= 0 # reset the padding value [:text_padding] = 0 if [:text_padding].to_f >= 1 # create box stream box_stream = "" # set graphic state for box if [:box_color] || ([:border_width].to_i > 0 && [:border_color]) # compute x and y position for text x = [:x] y = [:y] # set graphic state for the box box_stream << "q\n" box_stream << "#{[:ctm].join ' '} cm\n" if [:ctm] box_graphic_state = { ca: [:opacity], CA: [:opacity], LW: [:border_width], LC: 0, LJ: 0, LD: 0} if [:box_radius] != 0 # if the text box has rounded corners box_graphic_state[:LC], box_graphic_state[:LJ] = 2, 1 end box_graphic_state = graphic_state box_graphic_state # adds the graphic state to Resources and gets the reference box_stream << "#{object_to_pdf box_graphic_state} gs\n" # the following line was removed for Acrobat Reader compatability # box_stream << "DeviceRGB CS\nDeviceRGB cs\n" if [:box_color] box_stream << "#{[:box_color].join(' ')} rg\n" end if [:border_width].to_i > 0 && [:border_color] box_stream << "#{[:border_color].join(' ')} RG\n" end # create the path radius = [:box_radius] half_radius = (radius.to_f / 2).round 4 ## set starting point box_stream << "#{[:x] + radius} #{[:y]} m\n" ## buttom and right corner - first line and first corner box_stream << "#{[:x] + [:width] - radius} #{[:y]} l\n" #buttom if [:box_radius] != 0 # make first corner, if not straight. box_stream << "#{[:x] + [:width] - half_radius} #{[:y]} " box_stream << "#{[:x] + [:width]} #{[:y] + half_radius} " box_stream << "#{[:x] + [:width]} #{[:y] + radius} c\n" end ## right and top-right corner box_stream << "#{[:x] + [:width]} #{[:y] + [:height] - radius} l\n" if [:box_radius] != 0 box_stream << "#{[:x] + [:width]} #{[:y] + [:height] - half_radius} " box_stream << "#{[:x] + [:width] - half_radius} #{[:y] + [:height]} " box_stream << "#{[:x] + [:width] - radius} #{[:y] + [:height]} c\n" end ## top and top-left corner box_stream << "#{[:x] + radius} #{[:y] + [:height]} l\n" if [:box_radius] != 0 box_stream << "#{[:x] + half_radius} #{[:y] + [:height]} " box_stream << "#{[:x]} #{[:y] + [:height] - half_radius} " box_stream << "#{[:x]} #{[:y] + [:height] - radius} c\n" end ## left and buttom-left corner box_stream << "#{[:x]} #{[:y] + radius} l\n" if [:box_radius] != 0 box_stream << "#{[:x]} #{[:y] + half_radius} " box_stream << "#{[:x] + half_radius} #{[:y]} " box_stream << "#{[:x] + radius} #{[:y]} c\n" end # fill / stroke path box_stream << "h\n" if [:box_color] && [:border_width].to_i > 0 && [:border_color] box_stream << "B\n" elsif [:box_color] # fill if fill color is set box_stream << "f\n" elsif [:border_width].to_i > 0 && [:border_color] # stroke if border is set box_stream << "S\n" end # exit graphic state for the box box_stream << "Q\n" end contents << box_stream # reset x,y by text alignment - x,y are calculated from the buttom left # each unit (1) is 1/72 Inch # create text stream text_stream = "" if !text.to_s.empty? && [:font_size] != 0 && ([:font_color] || [:stroke_color]) # compute x and y position for text x = [:x] + ([:width]*[:text_padding]) y = [:y] + ([:height]*[:text_padding]) # set the fonts (fonts array, with :Helvetica as fallback). fonts = [*[:font], :Helvetica] # fit text in box, if requested font_size = [:font_size] if [:font_size] == :fit_text font_size = self.fit_text text, fonts, ([:width]*(1-[:text_padding])), ([:height]*(1-[:text_padding])) font_size = [:max_font_size] if [:max_font_size] && font_size > [:max_font_size] end text_size = dimensions_of text, fonts, font_size if [:text_align] == :center x = ( ( [:width]*(1-(2*[:text_padding])) ) - text_size[0] )/2 + x elsif [:text_align] == :right x = ( ( [:width]*(1-(1.5*[:text_padding])) ) - text_size[0] ) + x end if [:text_valign] == :center y = ( ( [:height]*(1-(2*[:text_padding])) ) - text_size[1] )/2 + y elsif [:text_valign] == :top y = ( [:height]*(1-(1.5*[:text_padding])) ) - text_size[1] + y end # set graphic state for text text_stream << "q\n" text_stream << "#{[:ctm].join ' '} cm\n" if [:ctm] text_graphic_state = graphic_state({ca: [:opacity], CA: [:opacity], LW: [:stroke_width].to_f, LC: 2, LJ: 1, LD: 0 }) text_stream << "#{object_to_pdf text_graphic_state} gs\n" # the following line was removed for Acrobat Reader compatability # text_stream << "DeviceRGB CS\nDeviceRGB cs\n" # set text render mode if [:font_color] text_stream << "#{[:font_color].join(' ')} rg\n" end if [:stroke_width].to_i > 0 && [:stroke_color] text_stream << "#{[:stroke_color].join(' ')} RG\n" if [:font_color] text_stream << "2 Tr\n" else final_stream << "1 Tr\n" end elsif [:font_color] text_stream << "0 Tr\n" else text_stream << "3 Tr\n" end # format text object(s) # text_stream << "#{options[:font_color].join(' ')} rg\n" # sets the color state encode_text(text, fonts).each do |encoded| text_stream << "BT\n" # the Begine Text marker text_stream << format_name_to_pdf(set_font encoded[0]) # Set font name text_stream << " #{font_size.round 3} Tf\n" # set font size and add font operator text_stream << "#{x.round 4} #{y.round 4} Td\n" # set location for text object text_stream << ( encoded[1] ) # insert the encoded string to the stream text_stream << " Tj\n" # the Text object operator and the End Text marker text_stream << "ET\n" # the Text object operator and the End Text marker x += encoded[2]/1000*font_size #update text starting point y -= encoded[3]/1000*font_size #update text starting point end # exit graphic state for text text_stream << "Q\n" end contents << text_stream self end |
#write_table(options = {}) ⇒ Object
Writes a table to the current page, removing(!) the written rows from the table_data Array.
since the table_data Array is updated, it is possible to call this method a few times, each time creating or moving to the next page, until table_data.empty? returns true.
accepts a Hash with any of the following keys as well as any of the PDFWriter#textbox options:
- headers
-
an Array of strings with the headers (will be repeated every page).
- table_data
-
as Array of Arrays, each containing a string for each column. the first row sets the number of columns. extra columns will be ignored.
- font
-
a registered or standard font name (see PDFWriter). defaults to nil (:Helvetica).
- header_font
-
a registered or standard font name for the headers (see PDFWriter). defaults to nil (the font for all the table rows).
- max_font_size
-
the maximum font size. if the string doesn’t fit, it will be resized. defaults to 14.
- column_widths
-
an array of relative column widths ([1,2] will display only the first two columns, the second twice as big as the first). defaults to nil (even widths).
- header_color
-
the header color. defaults to [0.8, 0.8, 0.8] (light gray).
- main_color
-
main row color. defaults to nil (transparent / white).
- alternate_color
-
alternate row color. defaults to [0.95, 0.95, 0.95] (very light gray).
- font_color
-
font color. defaults to [0,0,0] (black).
- border_color
-
border color. defaults to [0,0,0] (black).
- border_width
-
border width in PDF units. defaults to 1.
- header_align
-
the header text alignment within each column (:right, :left, :center). defaults to :center.
- row_align
-
the row text alignment within each column. defaults to :left (:right for RTL table).
- direction
-
the table’s writing direction (:ltr or :rtl). this reffers to the direction of the columns and doesn’t effect text (rtl text is automatically recognized). defaults to :ltr.
- max_rows
-
the maximum number of rows to actually draw, INCLUDING the header row. deafults to 25.
- xy
-
an Array specifying the top-left corner of the table. defaulte to [page_width*0.1, page_height*0.9].
- size
-
an Array specifying the height and the width of the table. defaulte to [page_width*0.8, page_height*0.8].
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 |
# File 'lib/combine_pdf/page_methods.rb', line 506 def write_table( = {}) defaults = { headers: nil, table_data: [[]], font: nil, header_font: nil, max_font_size: 14, column_widths: nil, header_color: [0.8, 0.8, 0.8], main_color: nil, alternate_color: [0.95, 0.95, 0.95], font_color: [0,0,0], border_color: [0,0,0], border_width: 1, header_align: :center, row_align: nil, direction: :ltr, max_rows: 25, xy: nil, size: nil } = defaults.merge raise "method call error! not enough rows allowed to create table" if ([:max_rows].to_i < 1 && [:headers]) || ([:max_rows].to_i <= 0) [:header_font] ||= [:font] [:row_align] ||= ( ([:direction] == :rtl) ? :right : :left ) [:xy] ||= [( (page_size[2]-page_size[0])*0.1 ), ( (page_size[3]-page_size[1])*0.9 )] [:size] ||= [( (page_size[2]-page_size[0])*0.8 ), ( (page_size[3]-page_size[1])*0.8 )] # assert table_data is an array of arrays return false unless ([:table_data].select {|r| !r.is_a?(Array) }).empty? # compute sizes top = [:xy][1] height = [:size][1] / [:max_rows] from_side = [:xy][0] width = [:size][0] columns = [:table_data][0].length column_widths = [] columns.times {|i| column_widths << (width/columns) } if [:column_widths] scale = 0 [:column_widths].each {|w| scale += w} column_widths = [] [:column_widths].each { |w| column_widths << (width*w/scale) } end column_widths = column_widths.reverse if [:direction] == :rtl # set count and start writing the data row_number = 1 until ([:table_data].empty? || row_number > [:max_rows]) # add headers if [:headers] && row_number == 1 x = from_side headers = [:headers] headers = headers.reverse if [:direction] == :rtl column_widths.each_index do |i| text = headers[i].to_s textbox text, {x: x, y: (top - (height*row_number)), width: column_widths[i], height: height, box_color: [:header_color], text_align: [:header_align] }.merge().merge({font: [:header_font]}) x += column_widths[i] end row_number += 1 end x = from_side row_data = [:table_data].shift row_data = row_data.reverse if [:direction] == :rtl column_widths.each_index do |i| text = row_data[i].to_s box_color = ([:alternate_color] && ( (row_number.odd? && [:headers]) || row_number.even? ) ) ? [:alternate_color] : [:main_color] textbox text, {x: x, y: (top - (height*row_number)), width: column_widths[i], height: height, box_color: box_color, text_align: [:row_align]}.merge() x += column_widths[i] end row_number += 1 end self end |