class PDF::Writer
Constants
- Compression
- FONT_PATH
The system font path. The sytem font path will be determined differently for each operating system.
- Win32
-
Uses ENV/Fonts as the system font path. There is an extension that will handle this better, but until and unless it is distributed with the standard Ruby Windows installer,
PDF::Writer
will not depend upon it. - OS X
-
The fonts are found in /System/Library/Fonts.
- Linux
-
The font path list will be found (usually) in /etc/fonts/fonts.conf or /usr/etc/fonts/fonts.conf. This XML file will be parsed (using REXML) to provide the value for
FONT_PATH
.
- PAGE_SIZES
Standard page size names. One of these may be provided to
PDF::Writer.new
as the:paper
parameter.Page sizes supported are:
-
4A0, 2A0
-
A0, A1 A2, A3, A4, A5, A6, A7, A8, A9, A10
-
B0, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10
-
C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10
-
RA0, RA1, RA2, RA3, RA4
-
SRA0, SRA1, SRA2, SRA3, SRA4
-
LETTER
-
LEGAL
-
FOLIO
-
EXECUTIVE
-
- PDF_VERSION_13
- PDF_VERSION_14
- PDF_VERSION_15
- PDF_VERSION_16
- TAGS
Callback tag relationships. All relationships are of the form “tagname” => CallbackClass.
There are three types of tag callbacks:
:pair
-
Paired callbacks, e.g., <c:alink></c:alink>.
:single
-
Single-tag callbacks, e.g., <C:bullet>.
:replace
-
Single-tag replacement callbacks, e.g., <r:xref>.
- VERSION
The version of
PDF::Writer
.
Attributes
Returns the absolute y position of the bottom margin.
The absolute x position of the left margin.
The absolute x position of the right margin.
Returns the absolute y position of the top margin.
The absolute x middle position.
The absolute y middle position.
The total number of columns. Returns zero (0) if columns are off.
The gutter between columns. This will return zero (0) if columns are off.
The current column number. Returns zero (0) if columns are off.
The width of the currently active column. This will return zero (0) if columns are off.
Sets the document to compressed (true
) or uncompressed (false
). Defaults to uncompressed. This can ONLY be set once and should be set as early as possible in the document creation process.
Returns the current contents object to which raw PDF
instructions may be written.
The string that will be used to encrypt this PDF
document.
Allows the user to find out what the ID is of the first page that was created during startup - useful if they wish to add something to it later.
Add a new translation table for a font family. A font family will be used to associate a single name and font styles with multiple fonts. A style will be identified with a single-character style identifier or a series of style identifiers. The only styles currently recognised are:
b
-
Bold (or heavy) fonts. Examples: Helvetica-Bold, Courier-Bold, Times-Bold.
i
-
Italic (or oblique) fonts. Examples: Helvetica-Oblique, Courier-Oblique, Times-Italic.
bi
-
Bold italic fonts. Examples Helvetica-BoldOblique, Courier-BoldOblique, Times-BoldItalic.
ib
-
Italic bold fonts. Generally defined the same as
bi
font styles. Examples: Helvetica-BoldOblique, Courier-BoldOblique, Times-BoldItalic.
Each font family key is the base name for the font.
The PDF::Writer::Object::Info
info object. This is used to provide certain metadata.
The height of the margin area.
The width of the margin area.
The middle of the writing area between the left and right margins.
The middle of the writing area between the top and bottom margins.
The vertical position of the writing point. If the vertical position is outside of the bottom margin, a new page will be created.
The version of PDF
to which this document conforms. Should be one of PDF_VERSION_13
, PDF_VERSION_14
, PDF_VERSION_15
, or PDF_VERSION_16
.
The vertical position of the writing point. The vertical position is constrained between the top and bottom margins. Any attempt to set it outside of those margins will cause the y pointer to be placed absolutely at the margins.
Public Class Methods
Convert a measurement in centimetres to points, which are the default PDF
userspace units.
# File lib/pdf/writer.rb 227 def cm2pts(x) 228 (x / 2.54) * 72 229 end
Escape the text so that it’s safe for insertion into the PDF
document.
# File lib/pdf/writer.rb 27 def self.escape(text) 28 text.gsub(/\\/, '\\\\\\\\'). 29 gsub(/\(/, '\\('). 30 gsub(/\)/, '\\)'). 31 gsub(/</, '<'). 32 gsub(/>/, '>'). 33 gsub(/&/, '&') 34 end
Convert a measurement in inches to points, which are the default PDF
userspace units.
# File lib/pdf/writer.rb 239 def in2pts(x) 240 x * 72 241 end
Convert a measurement in millimetres to points, which are the default PDF
userspace units.
# File lib/pdf/writer.rb 233 def mm2pts(x) 234 (x / 25.4) * 72 235 end
Creates a new PDF
document as a writing canvas. It accepts three named parameters:
:paper
-
Specifies the size of the default page in
PDF::Writer
. This may be a four-element array of coordinates specifying the lower-left(xll, yll)
and upper-right(xur, yur)
corners, a two-element array of width and height in centimetres, or a page name as defined inPAGE_SIZES
. :orientation
-
The orientation of the page, either long (:portrait) or wide (:landscape). This may be used to swap the width and the height of the page.
:version
-
The feature set available to the document is limited by the
PDF
version. Setting this version restricts the feature set available toPDF::Writer
.PDF::Writer
currently supportsPDF
version 1.3 features and does not yet support advanced features fromPDF
1.4, 1.5, or 1.6.
# File lib/pdf/writer.rb 326 def initialize(options = {}) 327 paper = options[:paper] || "LETTER" 328 orientation = options[:orientation] || :portrait 329 version = options[:version] || PDF_VERSION_13 330 331 @mutex = Mutex.new 332 @current_id = @current_font_id = 0 333 334 # Start the document 335 @objects = [] 336 @callbacks = [] 337 @font_families = {} 338 @fonts = {} 339 @stack = [] 340 @state_stack = StateStack.new 341 @loose_objects = [] 342 @current_text_state = "" 343 @options = {} 344 @destinations = {} 345 @add_loose_objects = {} 346 @images = [] 347 @word_space_adjust = nil 348 @current_stroke_style = PDF::Writer::StrokeStyle.new(1) 349 @page_numbering = nil 350 @arc4 = nil 351 @encryption = nil 352 @file_identifier = nil 353 354 @columns = {} 355 @columns_on = false 356 @insert_mode = nil 357 358 @catalog = PDF::Writer::Object::Catalog.new(self) 359 @outlines = PDF::Writer::Object::Outlines.new(self) 360 @pages = PDF::Writer::Object::Pages.new(self) 361 362 @current_node = @pages 363 @procset = PDF::Writer::Object::Procset.new(self) 364 @info = PDF::Writer::Object::Info.new(self) 365 @page = PDF::Writer::Object::Page.new(self) 366 @current_text_render_style = 0 367 @first_page = @page 368 369 @version = version 370 371 # Initialize the default font families. 372 init_font_families 373 374 @font_size = 10 375 @pageset = [@pages.first_page] 376 377 if paper.kind_of?(Array) 378 if paper.size == 4 379 size = paper # Coordinate Array 380 else 381 size = [0, 0, PDF::Writer.cm2pts(paper[0]), PDF::Writer.cm2pts(paper[1])] 382 # Paper size in centimeters has been passed 383 end 384 else 385 size = PAGE_SIZES[paper.upcase].dup 386 end 387 size[3], size[2] = size[2], size[3] if orientation == :landscape 388 389 @pages.media_box = size 390 391 @page_width = size[2] - size[0] 392 @page_height = size[3] - size[1] 393 @y = @page_height 394 395 # Also set the margins to some reasonable defaults -- 1.27 cm, 36pt, 396 # or 0.5 inches. 397 margins_pt(36) 398 399 # Set the current writing position to the top of the first page 400 @y = absolute_top_margin 401 # Get the ID of the page that was created during the instantiation 402 # process. 403 404 fill_color! Color::RGB::Black 405 stroke_color! Color::RGB::Black 406 407 yield self if block_given? 408 end
Create the document with prepress options. Uses the same options as PDF::Writer.new
(:paper
, :orientation
, and :version
). It also supports the following options:
:left_margin
-
The left margin.
:right_margin
-
The right margin.
:top_margin
-
The top margin.
:bottom_margin
-
The bottom margin.
:bleed_size
-
The size of the bleed area in points. Default 12.
:mark_length
-
The length of the prepress marks in points. Default 18.
The prepress marks are added to the loose objects and will appear on all pages.
# File lib/pdf/writer.rb 170 def prepress(options = { }) 171 pdf = self.new(options) 172 173 bleed_size = options[:bleed_size] || 12 174 mark_length = options[:mark_length] || 18 175 176 pdf.left_margin = options[:left_margin] if options[:left_margin] 177 pdf.right_margin = options[:right_margin] if options[:right_margin] 178 pdf.top_margin = options[:top_margin] if options[:top_margin] 179 pdf.bottom_margin = options[:bottom_margin] if options[:bottom_margin] 180 181 # This is in an "odd" order because the y-coordinate system in PDF 182 # is from bottom to top. 183 tx0 = pdf.pages.media_box[0] + pdf.left_margin 184 ty0 = pdf.pages.media_box[3] - pdf.top_margin 185 tx1 = pdf.pages.media_box[2] - pdf.right_margin 186 ty1 = pdf.pages.media_box[1] + pdf.bottom_margin 187 188 bx0 = tx0 - bleed_size 189 by0 = ty0 - bleed_size 190 bx1 = tx1 + bleed_size 191 by1 = ty1 + bleed_size 192 193 pdf.pages.trim_box = [ tx0, ty0, tx1, ty1 ] 194 pdf.pages.bleed_box = [ bx0, by0, bx1, by1 ] 195 196 all = pdf.open_object 197 pdf.save_state 198 kk = Color::CMYK.new(0, 0, 0, 100) 199 pdf.stroke_color! kk 200 pdf.fill_color! kk 201 pdf.stroke_style! StrokeStyle.new(0.3) 202 203 pdf.prepress_clip_mark(tx1, ty0, 0, mark_length, bleed_size) # Upper Right 204 pdf.prepress_clip_mark(tx0, ty0, 90, mark_length, bleed_size) # Upper Left 205 pdf.prepress_clip_mark(tx0, ty1, 180, mark_length, bleed_size) # Lower Left 206 pdf.prepress_clip_mark(tx1, ty1, -90, mark_length, bleed_size) # Lower Right 207 208 mid_x = pdf.pages.media_box[2] / 2.0 209 mid_y = pdf.pages.media_box[3] / 2.0 210 211 pdf.prepress_center_mark(mid_x, ty0, 0, mark_length, bleed_size) # Centre Top 212 pdf.prepress_center_mark(tx0, mid_y, 90, mark_length, bleed_size) # Centre Left 213 pdf.prepress_center_mark(mid_x, ty1, 180, mark_length, bleed_size) # Centre Bottom 214 pdf.prepress_center_mark(tx1, mid_y, -90, mark_length, bleed_size) # Centre Right 215 216 pdf.restore_state 217 pdf.close_object 218 pdf.add_object(all, :all) 219 220 yield pdf if block_given? 221 222 pdf 223 end
Private Class Methods
Parse the fonts.conf XML file.
# File lib/pdf/writer.rb 95 def parse_fonts_conf(filename) 96 doc = REXML::Document.new(File.open(filename, "rb")).root rescue nil 97 98 if doc 99 path = REXML::XPath.match(doc, '//dir').map do |el| 100 el.text.gsub($/, '') 101 end 102 doc = nil 103 else 104 path = [] 105 end 106 path 107 end
Public Instance Methods
memory improvement for transaction-simple
# File lib/pdf/writer.rb 2736 def _post_transaction_rewind 2737 @objects.each { |e| e.instance_variable_set(:@parent,self) } 2738 end
add content to the currently active object
# File lib/pdf/writer.rb 1030 def add_content(cc) 1031 @current_contents << cc 1032 end
Create a labelled destination within the document. The label is the name which will be used for <c:ilink> destinations.
- XYZ
-
The viewport will be opened at position
(left, top)
withzoom
percentage.params
must have three values representingleft
,top
, andzoom
, respectively. If the values are “null”, the current parameter values are unchanged. - Fit
-
Fit the page to the viewport (horizontal and vertical).
params
will be ignored. - FitH
-
Fit the page horizontally to the viewport. The top of the viewport is set to the first value in
params
. - FitV
-
Fit the page vertically to the viewport. The left of the viewport is set to the first value in
params
. - FitR
-
Fits the page to the provided rectangle.
params
must have four values representing theleft
,bottom
,right
, andtop
positions, respectively. - FitB
-
Fits the page to the bounding box of the page.
params
is ignored. - FitBH
-
Fits the page horizontally to the bounding box of the page. The top position is defined by the first value in
params
. - FitBV
-
Fits the page vertically to the bounding box of the page. The left position is defined by the first value in
params
.
# File lib/pdf/writer.rb 1861 def add_destination(label, style, *params) 1862 @destinations[label] = PDF::Writer::Object::Destination.new(self, @current_page, style, *params) 1863 end
Add content to the documents info object.
# File lib/pdf/writer.rb 1810 def add_info(label, value = 0) 1811 # This will only work if the label is one of the valid ones. Modify 1812 # this so that arrays can be passed as well. If @label is an array 1813 # then assume that it is key => value pairs else assume that they are 1814 # both scalar, anything else will probably error. 1815 if label.kind_of?(Hash) 1816 label.each { |kk, vv| @info.__send__(kk.downcase.intern, vv) } 1817 else 1818 @info.__send__(label.downcase.intern, value) 1819 end 1820 end
Add a link in the document to an internal destination (ie. within the document)
# File lib/pdf/writer.rb 682 def add_internal_link(label, x0, y0, x1, y1) 683 PDF::Writer::Object::Annotation.new(self, :ilink, [x0, y0, x1, y1], label) 684 end
Add a link in the document to an external URL.
# File lib/pdf/writer.rb 676 def add_link(uri, x0, y0, x1, y1) 677 PDF::Writer::Object::Annotation.new(self, :link, [x0, y0, x1, y1], uri) 678 end
After an object has been created, it will only show if it has been added, using this method.
# File lib/pdf/writer.rb 1783 def add_object(id, where = :this_page) 1784 obj = @loose_objects.detect { |ii| ii == id } 1785 1786 if obj and @current_contents != obj 1787 case where 1788 when :all_pages, :this_page 1789 @add_loose_objects[obj] = where if where == :all_pages 1790 @current_contents.on_page.contents << obj if @current_contents.on_page 1791 when :even_pages 1792 @add_loose_objects[obj] = where 1793 page = @current_contents.on_page 1794 add_object(id) if (page.info.page_number % 2) == 0 1795 when :odd_pages 1796 @add_loose_objects[obj] = where 1797 page = @current_contents.on_page 1798 add_object(id) if (page.info.page_number % 2) == 1 1799 when :all_following_pages 1800 @add_loose_objects[obj] = :all_pages 1801 when :following_even_pages 1802 @add_loose_objects[obj] = :even_pages 1803 when :following_odd_pages 1804 @add_loose_objects[obj] = :odd_pages 1805 end 1806 end 1807 end
Add an outline item (Bookmark).
# File lib/pdf/writer.rb 687 def add_outline_item(label, title = label) 688 PDF::Writer::Object::Outline.new(self, label, title) 689 end
Add text
to the document at (x, y)
location at size
and angle
. The word_space_adjust
parameter is an internal parameter that should not be used.
As of PDF::Writer
1.1, size
and text
have been reversed and size
is now optional, defaulting to the current font_size
if unset.
# File lib/pdf/writer.rb 1363 def add_text(x, y, text, size = nil, angle = 0, word_space_adjust = 0) 1364 if text.kind_of?(Numeric) and size.kind_of?(String) 1365 text, size = size, text 1366 warn PDF::Writer::Lang[:add_text_parameters_reversed] % caller[0] 1367 end 1368 1369 if size.nil? or size <= 0 1370 size = @font_size 1371 end 1372 1373 select_font("Helvetica") if @fonts.empty? 1374 1375 text = text.to_s 1376 1377 # If there are any open callbacks, then they should be called, to show 1378 # the start of the line 1379 @callbacks.reverse_each do |ii| 1380 info = ii.dup 1381 info[:x] = x 1382 info[:y] = y 1383 info[:angle] = angle 1384 info[:status] = :start_line 1385 1386 info[:tag][self, info] 1387 end 1388 if angle == 0 1389 add_content("\nBT %.3f %.3f Td" % [x, y]) 1390 else 1391 rad = PDF::Math.deg2rad(angle) 1392 tt = "\nBT %.3f %.3f %.3f %.3f %.3f %.3f Tm" 1393 tt = tt % [ Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad), x, y ] 1394 add_content(tt) 1395 end 1396 1397 if (word_space_adjust != 0) or not ((@word_space_adjust.nil?) and (@word_space_adjust != word_space_adjust)) 1398 @word_space_adjust = word_space_adjust 1399 add_content(" %.3f Tw" % word_space_adjust) 1400 end 1401 1402 pos = -1 1403 start = 0 1404 loop do 1405 pos += 1 1406 break if pos == text.size 1407 font_change = true 1408 tag_size, text, font_change = quick_text_tags(text, pos, font_change) 1409 1410 if tag_size != 0 1411 if pos > start 1412 part = text[start, pos - start] 1413 tt = " /F#{find_font(@current_font).font_id}" 1414 tt << " %.1f Tf %d Tr" % [ size, @current_text_render_style ] 1415 tt << " (#{PDF::Writer.escape(part)}) Tj" 1416 add_content(tt) 1417 end 1418 1419 if font_change 1420 current_font! 1421 else 1422 add_content(" ET") 1423 xp = x 1424 yp = y 1425 tag_size, text, font_change, xp, yp = text_tags(text, pos, font_change, true, xp, yp, size, angle, word_space_adjust) 1426 1427 # Restart the text object 1428 if angle.zero? 1429 add_content("\nBT %.3f %.3f Td" % [xp, yp]) 1430 else 1431 rad = PDF::Math.deg2rad(angle) 1432 tt = "\nBT %.3f %.3f %.3f %.3f %.3f %.3f Tm" 1433 tt = tt % [ Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad), xp, yp ] 1434 add_content(tt) 1435 end 1436 1437 if (word_space_adjust != 0) or (word_space_adjust != @word_space_adjust) 1438 @word_space_adjust = word_space_adjust 1439 add_content(" %.3f Tw" % [word_space_adjust]) 1440 end 1441 end 1442 1443 pos += tag_size - 1 1444 start = pos + 1 1445 end 1446 end 1447 1448 if start < text.size 1449 part = text[start..-1] 1450 1451 tt = " /F#{find_font(@current_font).font_id}" 1452 tt << " %.1f Tf %d Tr" % [ size, @current_text_render_style ] 1453 tt << " (#{PDF::Writer.escape(part)}) Tj" 1454 add_content(tt) 1455 end 1456 add_content(" ET") 1457 1458 # XXX: Experimental fix. 1459 @callbacks.reverse_each do |ii| 1460 info = ii.dup 1461 info[:x] = x 1462 info[:y] = y 1463 info[:angle] = angle 1464 info[:status] = :end_line 1465 info[:tag][self, info] 1466 end 1467 end
Add text to the page, but ensure that it fits within a certain width. If it does not fit then put in as much as possible, breaking at word boundaries; return the remainder. justification
and angle
can also be specified for the text.
This will display the text; if it goes beyond the width width
, it will backttrack to the previous space or hyphen and return the remainder of the text.
justification
-
:left, :right, :center, or :full
# File lib/pdf/writer.rb 1609 def add_text_wrap(x, y, width, text, size = nil, justification = :left, angle = 0, test = false) 1610 if text.kind_of?(Numeric) and size.kind_of?(String) 1611 text, size = size, text 1612 warn PDF::Writer::Lang[:add_textw_parameters_reversed] % caller[0] 1613 end 1614 1615 if size.nil? or size <= 0 1616 size = @font_size 1617 end 1618 1619 # Need to store the initial text state, as this will change during the 1620 # width calculation, but will need to be re-set before printing, so 1621 # that the chars work out right 1622 t_CTS = @current_text_state.dup 1623 1624 select_font("Helvetica") if @fonts.empty? 1625 return "" if width <= 0 1626 1627 w = brk = brkw = 0 1628 font = @current_font 1629 tw = width / size.to_f * 1000 1630 1631 pos = -1 1632 loop do 1633 pos += 1 1634 break if pos == text.size 1635 font_change = true 1636 tag_size, text, font_change = quick_text_tags(text, pos, font_change) 1637 if tag_size != 0 1638 if font_change 1639 current_font! 1640 font = @current_font 1641 end 1642 pos += (tag_size - 1) 1643 else 1644 w += char_width(font, text[pos, 1]) 1645 1646 if w > tw # We need to truncate this line 1647 if brk > 0 # There is somewhere to break the line. 1648 if text[brk] == " " 1649 tmp = text[0, brk] 1650 else 1651 tmp = text[0, brk + 1] 1652 end 1653 x, adjust = adjust_wrapped_text(tmp, brkw, width, x, justification) 1654 1655 # Reset the text state 1656 @current_text_state = t_CTS.dup 1657 current_font! 1658 add_text(x, y, tmp, size, angle, adjust) unless test 1659 return text[brk + 1..-1] 1660 else # just break before the current character 1661 tmp = text[0, pos] 1662 # tmpw = (w - char_width(font, text[pos, 1])) * size / 1000.0 1663 x, adjust = adjust_wrapped_text(tmp, brkw, width, x, justification) 1664 1665 # Reset the text state 1666 @current_text_state = t_CTS.dup 1667 current_font! 1668 add_text(x, y, tmp, size, angle, adjust) unless test 1669 return text[pos..-1] 1670 end 1671 end 1672 1673 if text[pos] == ?- 1674 brk = pos 1675 brkw = w * size / 1000.0 1676 end 1677 1678 if text[pos, 1] == " " 1679 brk = pos 1680 ctmp = text[pos] 1681 ctmp = @fonts[font].differences[ctmp] unless @fonts[font].differences.nil? 1682 z = @fonts[font].c[tmp].nil? ? 0 : @fonts[font].c[tmp]['WX'] 1683 brkw = (w - z) * size / 1000.0 1684 end 1685 end 1686 end 1687 1688 # There was no need to break this line. 1689 justification = :left if justification == :full 1690 tmpw = (w * size) / 1000.0 1691 x, adjust = adjust_wrapped_text(text, tmpw, width, x, justification) 1692 # reset the text state 1693 @current_text_state = t_CTS.dup 1694 current_font! 1695 add_text(x, y, text, size, angle, adjust) unless test 1696 return "" 1697 end
Changes the insert_page
property to append to the page set.
# File lib/pdf/writer.rb 2021 def append_page 2022 insert_mode(:last) 2023 end
Sets the bleed box area.
# File lib/pdf/writer.rb 658 def bleed_box(x0, y0, x1, y1) 659 @pages.bleed_box = [ x0, y0, x1, y1 ] 660 end
should be used for internal checks, not implemented as yet
# File lib/pdf/writer.rb 700 def check_all_here 701 end
Close an object for writing.
# File lib/pdf/writer.rb 1767 def close_object 1768 unless @stack.empty? 1769 obj = @stack.pop 1770 @current_contents = obj[:contents] 1771 @current_page = obj[:page] 1772 end 1773 end
Convert a measurement in centimetres to points, which are the default PDF
userspace units.
# File lib/pdf/writer.rb 246 def cm2pts(x) 247 PDF::Writer.cm2pts(x) 248 end
Indicates if columns are currently on.
# File lib/pdf/writer.rb 1907 def columns? 1908 @columns_on 1909 end
Returns true
if the document is compressed.
# File lib/pdf/writer.rb 439 def compressed? 440 @compressed == true 441 end
Selects the current font based on defined font families and the current text state. As noted in font_families
, a “bi” font can be defined differently than an “ib” font. It should not be possible to have a “bb” text state, but if one were to show up, an entry for the font_families
would have to be defined to select anything other than the default font. This function is to be called whenever the current text state is changed; it will update the current font to whatever the appropriate font defined in the font family.
When the user calls select_font
, both the current base font and the current font will be reset; this function only changes the current font, not the current base font.
This will probably not be needed by end users.
# File lib/pdf/writer.rb 998 def current_font! 999 select_font("Helvetica") unless @current_base_font 1000 1001 font = File.basename(@current_base_font) 1002 if @font_families[font] and @font_families[font][@current_text_state] 1003 # Then we are in some state or another and this font has a family, 1004 # and the current setting exists within it select the font, then 1005 # return it. 1006 if File.dirname(@current_base_font) != '.' 1007 nf = File.join(File.dirname(@current_base_font), @font_families[font][@current_text_state]) 1008 else 1009 nf = @font_families[font][@current_text_state] 1010 end 1011 1012 unless @fonts[nf] 1013 enc = { 1014 :encoding => @fonts[font].encoding, 1015 :differences => @fonts[font].differences 1016 } 1017 load_font(nf, enc) 1018 end 1019 @current_font = nf 1020 else 1021 @current_font = @current_base_font 1022 end 1023 end
Returns the current generic page number. This is based exclusively on the size of the page set.
# File lib/pdf/writer.rb 2116 def current_page_number 2117 @pageset.size 2118 end
Return the font descender, this will normally return a negative number. If you add this number to the baseline, you get the level of the bottom of the font it is in the PDF
user units. Uses the current font_size
if size is not provided.
# File lib/pdf/writer.rb 1048 def font_descender(size = nil) 1049 size = @font_size if size.nil? or size <= 0 1050 1051 select_font("Helvetica") if @fonts.empty? 1052 hi = @fonts[@current_font].fontbbox[1].to_f 1053 (size * hi / 1000.0) 1054 end
Return the height in units of the current font in the given size. Uses the current font_size
if size is not provided.
# File lib/pdf/writer.rb 1036 def font_height(size = nil) 1037 size = @font_size if size.nil? or size <= 0 1038 1039 select_font("Helvetica") if @fonts.empty? 1040 hh = @fonts[@current_font].fontbbox[3].to_f - @fonts[@current_font].fontbbox[1].to_f 1041 (size * hh / 1000.0) 1042 end
Convert a measurement in inches to points, which are the default PDF
userspace units.
# File lib/pdf/writer.rb 258 def in2pts(x) 259 PDF::Writer.in2pts(x) 260 end
Changes page insert mode. May be called as follows:
pdf.insert_mode # => current insert mode # The following four affect the insert mode without changing the # insert page or insert position. pdf.insert_mode(:on) # enables insert mode pdf.insert_mode(true) # enables insert mode pdf.insert_mode(:off) # disables insert mode pdf.insert_mode(false) # disables insert mode # Changes the insert mode, the insert page, and the insert # position at the same time. opts = { :on => true, :page => :last, :position => :before } pdf.insert_mode(opts)
# File lib/pdf/writer.rb 1985 def insert_mode(options = {}) 1986 case options 1987 when :on, true 1988 @insert_mode = true 1989 when :off, false 1990 @insert_mode = false 1991 else 1992 return @insert_mode unless options 1993 1994 @insert_mode = options[:on] unless options[:on].nil? 1995 1996 unless options[:page].nil? 1997 if @pageset[options[:page]].nil? or options[:page] == :last 1998 @insert_page = @pageset[-1] 1999 else 2000 @insert_page = @pageset[options[:page]] 2001 end 2002 end 2003 2004 @insert_position = options[:position] if options[:position] 2005 end 2006 end
Returns or changes the insert page property.
pdf.insert_page # => current insert page pdf.insert_page(35) # insert at page 35 pdf.insert_page(:last) # insert at the last page
# File lib/pdf/writer.rb 2012 def insert_page(page = nil) 2013 return @insert_page unless page 2014 if page == :last 2015 @insert_page = @pageset[-1] 2016 else 2017 @insert_page = @pageset[page] 2018 end 2019 end
Returns or changes the insert position to be before or after the specified page.
pdf.insert_position # => current insert position pdf.insert_position(:before) # insert before #insert_page pdf.insert_position(:after) # insert before #insert_page
# File lib/pdf/writer.rb 2030 def insert_position(position = nil) 2031 return @insert_position unless position 2032 @insert_position = position 2033 end
Returns the estimated number of lines remaining given the default or specified font size.
# File lib/pdf/writer.rb 2457 def lines_remaining(font_size = nil) 2458 font_size ||= @font_size 2459 remaining = @y - @bottom_margin 2460 remaining / font_height(font_size).to_f 2461 end
Define the margins in centimetres.
# File lib/pdf/writer.rb 567 def margins_cm(top, left = top, bottom = top, right = left) 568 margins_pt(cm2pts(top), cm2pts(left), cm2pts(bottom), cm2pts(right)) 569 end
Define the margins in inches.
# File lib/pdf/writer.rb 572 def margins_in(top, left = top, bottom = top, right = left) 573 margins_pt(in2pts(top), in2pts(left), in2pts(bottom), in2pts(right)) 574 end
Define the margins in millimetres.
# File lib/pdf/writer.rb 562 def margins_mm(top, left = top, bottom = top, right = left) 563 margins_pt(mm2pts(top), mm2pts(left), mm2pts(bottom), mm2pts(right)) 564 end
Define the margins in points. This will move the y
pointer
# T L B R pdf.margins_pt(36) # 36 36 36 36 pdf.margins_pt(36, 54) # 36 54 36 54 pdf.margins_pt(36, 54, 72) # 36 54 72 54 pdf.margins_pt(36, 54, 72, 90) # 36 54 72 90
# File lib/pdf/writer.rb 583 def margins_pt(top, left = top, bottom = top, right = left) 584 # Set the margins to new values 585 @top_margin = top 586 @bottom_margin = bottom 587 @left_margin = left 588 @right_margin = right 589 # Check to see if this means that the current writing position is 590 # outside the writable area 591 if @y > (@page_height - top) 592 # Move y down 593 @y = @page_height - top 594 end 595 596 start_new_page if @y < bottom # Make a new page 597 end
Convert a measurement in millimetres to points, which are the default PDF
userspace units.
# File lib/pdf/writer.rb 252 def mm2pts(x) 253 PDF::Writer.mm2pts(x) 254 end
Used to change the vertical position of the writing point. The pointer is moved down the page by dy
(that is, y
is reduced by dy
), so if the pointer is to be moved up, a negative number must be used. Moving up the page will not move to the previous page because of limitations in the way that PDF::Writer
works. The writing point will be limited to the top margin position.
If make_space
is true and a new page is forced, then the pointer will be moved down on the new page. This will allow space to be reserved for graphics.
# File lib/pdf/writer.rb 551 def move_pointer(dy, make_space = false) 552 @y -= dy 553 if @y < @bottom_margin 554 start_new_page 555 @y -= dy if make_space 556 elsif @y > absolute_top_margin 557 @y = absolute_top_margin 558 end 559 end
Add a new page to the document. This also makes the new page the current active object. This allows for mandatory page creation regardless of multi-column output.
For most purposes, start_new_page
is preferred.
# File lib/pdf/writer.rb 2089 def new_page(insert = false, page = nil, pos = :after) 2090 reset_state_at_page_finish 2091 2092 if insert 2093 # The id from the PDF::Writer class is the id of the contents of the 2094 # page, not the page object itself. Query that object to find the 2095 # parent. 2096 _new_page = PDF::Writer::Object::Page.new(self, { :rpage => page, :pos => pos }) 2097 else 2098 _new_page = PDF::Writer::Object::Page.new(self) 2099 end 2100 2101 reset_state_at_page_start 2102 2103 # If there has been a stroke or fill color set, transfer them. 2104 fill_color! 2105 stroke_color! 2106 stroke_style! 2107 2108 # the call to the page object set @current_contents to the present page, 2109 # so this can be returned as the page id 2110 # @current_contents 2111 _new_page 2112 end
Specify the Destination object where the document should open when it first starts. style
must be one of the following values. The value of style
affects the interpretation of params
. Uses page
as the starting location.
# File lib/pdf/writer.rb 1834 def open_at(page, style, *params) 1835 d = PDF::Writer::Object::Destination.new(self, page, style, *params) 1836 @catalog.open_here = d 1837 end
Specify the Destination object where the document should open when it first starts. style
must be one of the values detailed for destinations. The value of style
affects the interpretation of params
. Uses the current page as the starting location.
# File lib/pdf/writer.rb 1826 def open_here(style, *params) 1827 open_at(@current_page, style, *params) 1828 end
Opens a new PDF
object for operating against. Returns the object’s identifier. To close the object, you’ll need to do:
ob = open_new_object # Opens the object # do stuff here close_object # Closes the PDF document # do stuff here reopen_object(ob) # Reopens the custom object. close_object # Closes it. restore_state # Returns full control to the PDF document.
… I think. I haven’t examined the full details to be sure of what this is doing, but the code works.
# File lib/pdf/writer.rb 2721 def open_new_object 2722 save_state 2723 oid = open_object 2724 close_object 2725 add_object(oid) 2726 reopen_object(oid) 2727 oid 2728 end
Make a loose object. The output will go into this object, until it is closed, then will revert to the current one. This object will not appear until it is included within a page. The function will return the object reference.
# File lib/pdf/writer.rb 1748 def open_object 1749 @stack << { :contents => @current_contents, :page => @current_page } 1750 @current_contents = PDF::Writer::Object::Contents.new(self) 1751 @loose_objects << @current_contents 1752 yield @current_contents if block_given? 1753 @current_contents 1754 end
Set the page mode of the catalog. Must be one of the following:
- UseNone
-
Neither document outline nor thumbnail images are visible.
- UseOutlines
-
Document outline visible.
- UseThumbs
-
Thumbnail images visible.
- FullScreen
-
Full-screen mode, with no menu bar, window controls, or any other window visible.
- UseOC
-
Optional content group panel is visible.
# File lib/pdf/writer.rb 1874 def page_mode=(mode) 1875 @catalog.page_mode = value 1876 end
Return the PDF
stream as a string.
# File lib/pdf/writer.rb 704 def render(debug = false) 705 add_page_numbers 706 @compression = false if $DEBUG or debug 707 @arc4.init(@encryption_key) unless @arc4.nil? 708 709 check_all_here 710 711 xref = [] 712 713 content = "%PDF-#{@version}\n%\303\242\303\243\303\217\303\223\n" 714 pos = content.size 715 716 objects.each do |oo| 717 cont = oo.to_s 718 content << cont 719 xref << pos 720 pos += cont.size 721 end 722 723 # pos += 1 # Newline character before XREF 724 725 content << "\nxref\n0 #{xref.size + 1}\n0000000000 65535 f \n" 726 xref.each { |xx| content << "#{'%010d' % [xx]} 00000 n \n" } 727 content << "\ntrailer\n" 728 content << " << /Size #{xref.size + 1}\n" 729 content << " /Root 1 0 R\n /Info #{@info.oid} 0 R\n" 730 # If encryption has been applied to this document, then add the marker 731 # for this dictionary 732 if @arc4 and @encryption 733 content << "/Encrypt #{@encryption.oid} 0 R\n" 734 end 735 736 if @file_identifier 737 content << "/ID[<#{@file_identifier}><#{@file_identifier}>]\n" 738 end 739 content << " >>\nstartxref\n#{pos}\n%%EOF\n" 740 content 741 end
Opens an existing object for editing.
# File lib/pdf/writer.rb 1757 def reopen_object(id) 1758 @stack << { :contents => @current_contents, :page => @current_page } 1759 @current_contents = id 1760 # if this object is the primary contents for a page, then set the 1761 # current page to its parent 1762 @current_page = @current_contents.on_page unless @current_contents.on_page.nil? 1763 @current_contents 1764 end
Restore a previously saved state.
# File lib/pdf/writer.rb 1726 def restore_state 1727 unless @state_stack.empty? 1728 state = @state_stack.pop 1729 @current_fill_color = state.fill_color 1730 @current_stroke_color = state.stroke_color 1731 @current_text_render_style = state.text_render_style 1732 @current_stroke_style = state.stroke_style 1733 stroke_style! 1734 end 1735 add_content("\nQ") 1736 end
Save the PDF
as a file to disk.
# File lib/pdf/writer.rb 2731 def save_as(name) 2732 File.open(name, "wb") { |f| f.write self.render } 2733 end
Saves the state.
# File lib/pdf/writer.rb 1700 def save_state 1701 PDF::Writer::State.new do |state| 1702 state.fill_color = @current_fill_color 1703 state.stroke_color = @current_stroke_color 1704 state.text_render_style = @current_text_render_style 1705 state.stroke_style = @current_stroke_style 1706 @state_stack.push state 1707 end 1708 add_content("\nq") 1709 end
If the named font
is not loaded, then load it and make the required PDF
objects to represent the font. If the font is already loaded, then make it the current font.
The parameter encoding
applies only when the font is first being loaded; it may not be applied later. It may either be an encoding name or a hash. The Hash must contain two keys:
:encoding
-
The name of the encoding. Either none, WinAnsiEncoding, MacRomanEncoding, or MacExpertEncoding. For symbolic fonts, an encoding of none is recommended with a differences Hash.
:differences
-
This Hash value is a mapping between character byte values (0 .. 255) and character names from the AFM file for the font.
The standard PDF
encodings are detailed fully in the PDF
Reference version 1.6, Appendix D.
Note that WinAnsiEncoding is not the same as Windows code page 1252 (roughly equivalent to latin-1), Most characters map, but not all. The encoding value currently defaults to WinAnsiEncoding.
If the font’s “natural” encoding is desired, then it is necessary to specify the encoding
parameter as { :encoding => nil }
.
# File lib/pdf/writer.rb 976 def select_font(font, encoding = nil) 977 load_font(font, encoding) unless @fonts[font] 978 979 @current_base_font = font 980 current_font! 981 @current_base_font 982 end
The number of PDF
objects in the document
# File lib/pdf/writer.rb 138 def size 139 @objects.size 140 end
Starts multi-column output. Creates size
number of columns with a gutter
PDF
unit space between each column.
If columns are already started, this will return false
.
# File lib/pdf/writer.rb 1915 def start_columns(size = 2, gutter = 10) 1916 # Start from the current y-position; make the set number of columns. 1917 return false if @columns_on 1918 1919 @columns = { 1920 :current => 1, 1921 :bot_y => @y 1922 } 1923 @columns_on = true 1924 # store the current margins 1925 @columns[:left] = @left_margin 1926 @columns[:right] = @right_margin 1927 @columns[:top] = @top_margin 1928 @columns[:bottom] = @bottom_margin 1929 # Reset the margins to suit the new columns. Safe enough to assume the 1930 # first column here, but start from the current y-position. 1931 @top_margin = @page_height - @y 1932 @columns[:size] = size || 2 1933 @columns[:gutter] = gutter || 10 1934 w = absolute_right_margin - absolute_left_margin 1935 @columns[:width] = (w - ((size - 1) * gutter)) / size.to_f 1936 @right_margin = @page_width - (@left_margin + @columns[:width]) 1937 end
Creates a new page. If multi-column output is turned on, this will change the column to the next greater or create a new page as necessary. If force
is true, then a new page will be created even if multi-column output is on.
# File lib/pdf/writer.rb 2039 def start_new_page(force = false) 2040 page_required = true 2041 2042 if @columns_on 2043 # Check if this is just going to a new column. Increment the column 2044 # number. 2045 @columns[:current] += 1 2046 2047 if @columns[:current] <= @columns[:size] and not force 2048 page_required = false 2049 @columns[:bot_y] = @y if @y < @columns[:bot_y] 2050 else 2051 @columns[:current] = 1 2052 @top_margin = @columns[:top] 2053 @columns[:bot_y] = absolute_top_margin 2054 end 2055 2056 w = @columns[:width] 2057 g = @columns[:gutter] 2058 n = @columns[:current] - 1 2059 @left_margin = @columns[:left] + n * (g + w) 2060 @right_margin = @page_width - (@left_margin + w) 2061 end 2062 2063 if page_required or force 2064 # make a new page, setting the writing point back to the top. 2065 @y = absolute_top_margin 2066 # make the new page with a call to the basic class 2067 if @insert_mode 2068 id = new_page(true, @insert_page, @insert_position) 2069 @pageset << id 2070 # Manipulate the insert options so that inserted pages follow each 2071 # other 2072 @insert_page = id 2073 @insert_position = :after 2074 else 2075 @pageset << new_page 2076 end 2077 2078 else 2079 @y = absolute_top_margin 2080 end 2081 @pageset 2082 end
Put page numbers on the pages from the current page. Place them relative to the coordinates (x, y)
with the text horizontally relative according to pos
, which may be :left
, :right
, or :center
. The page numbers will be written on each page using pattern
.
When pattern
is rendered, <PAGENUM> will be replaced with the current page number; <TOTALPAGENUM> will be replaced with the total number of pages in the page numbering scheme. The default pattern
is “<PAGENUM> of <TOTALPAGENUM>”.
Each time page numbers are started, a new page number scheme will be started. The scheme number will be returned.
# File lib/pdf/writer.rb 2134 def start_page_numbering(x, y, size, pos = nil, pattern = nil, starting = nil) 2135 pos ||= :left 2136 pattern ||= "<PAGENUM> of <TOTALPAGENUM>" 2137 starting ||= 1 2138 2139 @page_numbering ||= [] 2140 @page_numbering << (o = {}) 2141 2142 page = @pageset.size - 1 2143 o[page] = { 2144 :x => x, 2145 :y => y, 2146 :pos => pos, 2147 :pattern => pattern, 2148 :starting => starting, 2149 :size => size, 2150 :start => true 2151 } 2152 @page_numbering.index(o) 2153 end
Turns off multi-column output. If we are in the first column, or the lowest point at which columns were written is higher than the bottom of the page, then the writing pointer will be placed at the lowest point. Otherwise, a new page will be started.
# File lib/pdf/writer.rb 1951 def stop_columns 1952 return false unless @columns_on 1953 @columns_on = false 1954 1955 @columns[:bot_y] = @y if @y < @columns[:bot_y] 1956 1957 if (@columns[:bot_y] > @bottom_margin) or @column_number == 1 1958 @y = @columns[:bot_y] 1959 else 1960 start_new_page 1961 end 1962 restore_margins_after_columns 1963 @columns = {} 1964 true 1965 end
Stop an object from appearing on pages from this point on.
# File lib/pdf/writer.rb 1776 def stop_object(id) 1777 obj = @loose_objects.detect { |ii| ii.oid == id.oid } 1778 @add_loose_objects[obj] = nil 1779 end
Stop page numbering. Returns false
if page numbering is off.
If stop_total
is true, then then the totaling of pages for this page numbering scheme
will be stopped as well. If stop_at
is :current
, then the page numbering will stop at this page; otherwise, it will stop at the next page.
This method has been dprecated.
# File lib/pdf/writer.rb 2192 def stop_page_numbering(stop_total = false, stop_at = :current, scheme = 0) 2193 return false unless @page_numbering 2194 2195 page = @pageset.size - 1 2196 2197 @page_numbering[scheme][page] ||= {} 2198 o = @page_numbering[scheme][page] 2199 2200 case [ stop_total, stop_at == :current ] 2201 when [ true, true ] 2202 o[:stop] = :stop_total 2203 when [ true, false ] 2204 o[:stop] = :stop_total_next 2205 when [ false, true ] 2206 o[:stop] = :stop_next 2207 else 2208 o[:stop] = :stop 2209 end 2210 end
This will add a string of text
to the document, starting at the current drawing position. It will wrap to keep within the margins, including optional offsets from the left and the right. The text will go to the start of the next line when a return code “n” is found.
Possible options
are:
:font_size
-
The font size to be used. If not specified, is either the last font size or the default font size of 12 points. Setting this value changes the current
font_size
. :left
-
number, gap to leave from the left margin
:right
-
number, gap to leave from the right margin
:absolute_left
-
number, absolute left position (overrides
:left
) :absolute_right
-
number, absolute right position (overrides
:right
) :justification
-
:left
,:right
,:center
,:full
:leading
-
number, defines the total height taken by the line, independent of the font height.
:spacing
-
a Floating point number, though usually set to one of 1, 1.5, 2 (line spacing as used in word processing)
Only one of :leading
or :spacing
should be specified (leading overrides spacing).
If the :test
option is true
, then this should just check to see if the text is flowing onto a new page or not; returns true
or false
. Note that the new page test is only sensitive to exceeding the bottom margin of the page. It is not known whether the writing of the text will require a new physical page or whether it will require a new column.
# File lib/pdf/writer.rb 2339 def text(text, options = {}) 2340 # Apply the filtering which will make underlining (and other items) 2341 # function. 2342 2343 # If the text is for barcode3-9 apply * before and after 2344 if text != '' && options[:font_type] == 'ArchonCode39Barcode' 2345 text = "*#{text}*" 2346 end 2347 2348 text = preprocess_text(text) 2349 2350 options ||= {} 2351 2352 new_page_required = false 2353 __y = @y 2354 2355 if options[:absolute_left] 2356 left = options[:absolute_left] 2357 else 2358 left = @left_margin 2359 left += options[:left] if options[:left] 2360 end 2361 2362 if options[:absolute_right] 2363 right = options[:absolute_right] 2364 else 2365 right = absolute_right_margin 2366 right -= options[:right] if options[:right] 2367 end 2368 2369 size = options[:font_size] || 0 2370 if size <= 0 2371 size = @font_size 2372 else 2373 @font_size = size 2374 end 2375 2376 just = options[:justification] || :left 2377 2378 if options[:leading] # leading instead of spacing 2379 height = options[:leading] 2380 elsif options[:spacing] 2381 height = options[:spacing] * font_height(size) 2382 else 2383 height = font_height(size) 2384 end 2385 2386 text.each_line do |line| 2387 start = true 2388 loop do # while not line.empty? or start 2389 break if (line.nil? or line.empty?) and not start 2390 2391 start = false 2392 2393 @y -= height 2394 2395 if @y < @bottom_margin 2396 if options[:test] 2397 new_page_required = true 2398 else 2399 # and then re-calc the left and right, in case they have 2400 # changed due to columns 2401 start_new_page 2402 @y -= height 2403 2404 if options[:absolute_left] 2405 left = options[:absolute_left] 2406 else 2407 left = @left_margin 2408 left += options[:left] if options[:left] 2409 end 2410 2411 if options[:absolute_right] 2412 right = options[:absolute_right] 2413 else 2414 right = absolute_right_margin 2415 right -= options[:right] if options[:right] 2416 end 2417 end 2418 end 2419 2420 line = add_text_wrap(left, @y, right - left, line, size, just, 0, options[:test]) 2421 end 2422 end 2423 2424 if options[:test] 2425 @y = __y 2426 new_page_required 2427 else 2428 @y 2429 end 2430 end
Calculate how wide a given text string will be on a page, at a given size. This may be called externally, but is alse used by text_width
. If size
is not specified, PDF::Writer
will use the current font_size
.
The argument list is reversed from earlier versions.
# File lib/pdf/writer.rb 1494 def text_line_width(text, size = nil) 1495 if text.kind_of?(Numeric) and size.kind_of?(String) 1496 text, size = size, text 1497 warn PDF::Writer::Lang[:text_width_parameters_reversed] % caller[0] 1498 end 1499 1500 if size.nil? or size <= 0 1501 size = @font_size 1502 end 1503 1504 # This function should not change any of the settings, though it will 1505 # need to track any tag which change during calculation, so copy them 1506 # at the start and put them back at the end. 1507 t_CTS = @current_text_state.dup 1508 1509 select_font("Helvetica") if @fonts.empty? 1510 # converts a number or a float to a string so it can get the width 1511 tt = text.to_s 1512 # hmm, this is where it all starts to get tricky - use the font 1513 # information to calculate the width of each character, add them up 1514 # and convert to user units 1515 width = 0 1516 font = @current_font 1517 1518 pos = -1 1519 loop do 1520 pos += 1 1521 break if pos == tt.size 1522 font_change = true 1523 tag_size, text, font_change = quick_text_tags(text, pos, font_change) 1524 if tag_size != 0 1525 if font_change 1526 current_font! 1527 font = @current_font 1528 end 1529 pos += tag_size - 1 1530 else 1531 if "<" == tt[pos, 4] 1532 width += char_width(font, '<') 1533 pos += 3 1534 elsif ">" == tt[pos, 4] 1535 width += char_width(font, '>') 1536 pos += 3 1537 elsif "&" == tt[pos, 5] 1538 width += char_width(font, '&') 1539 pos += 4 1540 else 1541 width += char_width(font, tt[pos, 1]) 1542 end 1543 end 1544 end 1545 1546 @current_text_state = t_CTS.dup 1547 current_font! 1548 1549 (width * size / 1000.0) 1550 end
Calculate how wide a given text string will be on a page, at a given size. If size
is not specified, PDF::Writer
will use the current font_size
. The difference between this method and text_line_width
is that this method will iterate over lines separated with newline characters.
The argument list is reversed from earlier versions.
# File lib/pdf/writer.rb 1559 def text_width(text, size = nil) 1560 if text.kind_of?(Numeric) and size.kind_of?(String) 1561 text, size = size, text 1562 warn PDF::Writer::Lang[:text_width_parameters_reversed] % caller[0] 1563 end 1564 1565 if size.nil? or size <= 0 1566 size = @font_size 1567 end 1568 1569 max = 0 1570 1571 text.to_s.each_line do |line| 1572 width = text_line_width(line, size) 1573 max = width if width > max 1574 end 1575 max 1576 end
Sets the trim box area.
# File lib/pdf/writer.rb 653 def trim_box(x0, y0, x1, y1) 654 @pages.trim_box = [ x0, y0, x1, y1 ] 655 end
set the viewer preferences of the document, it is up to the browser to obey these.
# File lib/pdf/writer.rb 664 def viewer_preferences(label, value = 0) 665 @catalog.viewer_preferences ||= PDF::Writer::Object::ViewerPreferences.new(self) 666 667 # This will only work if the label is one of the valid ones. 668 if label.kind_of?(Hash) 669 label.each { |kk, vv| @catalog.viewer_preferences.__send__("#{kk.downcase}=".intern, vv) } 670 else 671 @catalog.viewer_preferences.__send__("#{label.downcase}=".intern, value) 672 end 673 end
Given a particular generic page number page_num
(numbered sequentially from the beginning of the page set), return the page number under a particular page numbering scheme
(defaults to the first scheme turned on). Returns nil
if page numbering is not turned on or if the page is not under the current numbering scheme.
This method has been dprecated.
# File lib/pdf/writer.rb 2162 def which_page_number(page_num, scheme = 0) 2163 return nil unless @page_numbering 2164 2165 num = nil 2166 start = start_num = 1 2167 2168 @page_numbering[scheme].each do |kk, vv| 2169 if kk <= page_num 2170 if vv.kind_of?(Hash) 2171 unless vv[:starting].nil? 2172 start = vv[:starting] 2173 start_num = kk 2174 num = page_num - start_num + start 2175 end 2176 else 2177 num = nil 2178 end 2179 end 2180 end 2181 num 2182 end
Private Instance Methods
# File lib/pdf/writer.rb 2219 def add_page_numbers 2220 # This will go through the @page_numbering array and add the page 2221 # numbers are required. 2222 if @page_numbering 2223 page_count = @pageset.size 2224 pn_tmp = @page_numbering.dup 2225 2226 # Go through each of the page numbering schemes. 2227 pn_tmp.each do |scheme| 2228 # First, find the total pages for this schemes. 2229 page = page_number_search(:stop_total, scheme) 2230 2231 if page 2232 total_pages = page 2233 else 2234 page = page_number_search(:stop_total_next, scheme) 2235 if page 2236 total_pages = page 2237 else 2238 total_pages = page_count - 1 2239 end 2240 end 2241 2242 status = nil 2243 delta = pattern = pos = x = y = size = nil 2244 pattern = pos = x = y = size = nil 2245 2246 @pageset.each_with_index do |page, index| 2247 next if status.nil? and scheme[index].nil? 2248 2249 info = scheme[index] 2250 if info 2251 if info[:start] 2252 status = true 2253 if info[:starting] 2254 delta = info[:starting] - index 2255 else 2256 delta = index 2257 end 2258 2259 pattern = info[:pattern] 2260 pos = info[:pos] 2261 x = info[:x] 2262 y = info[:y] 2263 size = info[:size] 2264 2265 # Check for the special case of page numbering starting and 2266 # stopping on the same page. 2267 status = :stop_next if info[:stop] 2268 elsif [:stop, :stop_total].include?(info[:stop]) 2269 status = :stop_now 2270 elsif status == true and [:stop_next, :stop_total_next].include?(info[:stop]) 2271 status = :stop_next 2272 end 2273 end 2274 2275 if status 2276 # Add the page numbering to this page 2277 num = index + delta.to_i 2278 total = total_pages + num - index 2279 patt = pattern.gsub(/<PAGENUM>/, num.to_s).gsub(/<TOTALPAGENUM>/, total.to_s) 2280 reopen_object(page.contents.first) 2281 2282 case pos 2283 when :left # Write the page number from x. 2284 w = 0 2285 when :right # Write the page number to x. 2286 w = text_width(patt, size) 2287 when :center # Write the page number around x. 2288 w = text_width(patt, size) / 2.0 2289 end 2290 add_text(x - w, y, patt, size) 2291 close_object 2292 status = nil if [ :stop_now, :stop_next ].include?(status) 2293 end 2294 end 2295 end 2296 end 2297 end
Partially calculate the values necessary to sort out the justification of text.
# File lib/pdf/writer.rb 1580 def adjust_wrapped_text(text, actual, width, x, just) 1581 adjust = 0 1582 1583 case just 1584 when :left 1585 nil 1586 when :right 1587 x += (width - actual) 1588 when :center 1589 x += (width - actual) / 2.0 1590 when :full 1591 spaces = text.count(" ") 1592 adjust = (width - actual) / spaces.to_f if spaces > 0 1593 end 1594 1595 [x, adjust] 1596 end
# File lib/pdf/writer.rb 1469 def char_width(font, char) 1470 if RUBY_VERSION >= '1.9' 1471 char = char.bytes.to_a.first unless @fonts[font].c[char] 1472 else 1473 char = char[0] unless @fonts[font].c[char] 1474 end 1475 1476 if @fonts[font].differences and @fonts[font].c[char].nil? 1477 name = @fonts[font].differences[char] || 'M' 1478 width = @fonts[font].c[name]['WX'] if @fonts[font].c[name]['WX'] 1479 elsif @fonts[font].c[char] 1480 width = @fonts[font].c[char]['WX'] 1481 else 1482 width = @fonts[font].c['M']['WX'] 1483 end 1484 width 1485 end
# File lib/pdf/writer.rb 755 def find_font(fontname) 756 name = File.basename(fontname, ".afm") 757 @objects.detect do |oo| 758 oo.kind_of?(PDF::Writer::Object::Font) and /#{oo.basefont}$/ =~ name 759 end 760 end
# File lib/pdf/writer.rb 763 def font_file(fontfile) 764 path = "#{fontfile}.pfb" 765 return path if File.exists?(path) 766 path = "#{fontfile}.ttf" 767 return path if File.exists?(path) 768 nil 769 end
Generate a new font ID.
# File lib/pdf/writer.rb 149 def generate_font_id 150 @mutex.synchronize { @current_font_id += 1 } 151 end
Generate an ID for a new PDF
object.
# File lib/pdf/writer.rb 143 def generate_id 144 @mutex.synchronize { @current_id += 1 } 145 end
Initialize the font families for the default fonts.
# File lib/pdf/writer.rb 624 def init_font_families 625 # Set the known family groups. These font families will be used to 626 # enable bold and italic markers to be included within text 627 # streams. HTML forms will be used... <b></b> <i></i> 628 @font_families["Helvetica"] = 629 { 630 "b" => 'Helvetica-Bold', 631 "i" => 'Helvetica-Oblique', 632 "bi" => 'Helvetica-BoldOblique', 633 "ib" => 'Helvetica-BoldOblique' 634 } 635 @font_families['Courier'] = 636 { 637 "b" => 'Courier-Bold', 638 "i" => 'Courier-Oblique', 639 "bi" => 'Courier-BoldOblique', 640 "ib" => 'Courier-BoldOblique' 641 } 642 @font_families['Times-Roman'] = 643 { 644 "b" => 'Times-Bold', 645 "i" => 'Times-Italic', 646 "bi" => 'Times-BoldItalic', 647 "ib" => 'Times-BoldItalic' 648 } 649 end
# File lib/pdf/writer.rb 772 def load_font(font, encoding = nil) 773 metrics = load_font_metrics(font) 774 775 name = File.basename(font).gsub(/\.afm$/o, "") 776 777 encoding_diff = nil 778 case encoding 779 when Hash 780 encoding_name = encoding[:encoding] 781 encoding_diff = encoding[:differences] 782 encoding = PDF::Writer::Object::FontEncoding.new(self, encoding_name, encoding_diff) 783 when NilClass 784 encoding_name = encoding = 'WinAnsiEncoding' 785 else 786 encoding_name = encoding 787 end 788 789 wfo = PDF::Writer::Object::Font.new(self, name, encoding) 790 791 # We have an Adobe Font Metrics (.afm) file. We need to find the 792 # associated Type1 (.pfb) or TrueType (.ttf) files (we do not yet 793 # support OpenType fonts); we need to load it into a 794 # PDF::Writer::Object and put the references into the metrics object. 795 base = metrics.path.sub(/\.afm$/o, "") 796 fontfile = font_file(base) 797 unless fontfile 798 base = File.basename(base) 799 FONT_PATH.each do |path| 800 fontfile = font_file(File.join(path, base)) 801 break if fontfile 802 end 803 end 804 805 if font =~ /afm/o and fontfile 806 # Find the array of font widths, and put that into an object. 807 first_char = -1 808 last_char = 0 809 810 widths = {} 811 metrics.c.each_value do |details| 812 num = details["C"] 813 814 if num >= 0 815 # warn "Multiple definitions of #{num}" if widths.has_key?(num) 816 widths[num] = details['WX'] 817 first_char = num if num < first_char or first_char < 0 818 last_char = num if num > last_char 819 end 820 end 821 822 # Adjust the widths for the differences array. 823 if encoding_diff 824 encoding_diff.each do |cnum, cname| 825 (cnum - last_char).times { widths << 0 } if cnum > last_char 826 last_char = cnum 827 widths[cnum - first_char] = metrics.c[cname]['WX'] if metrics.c[cname] 828 end 829 end 830 831 raise RuntimeError, 'Font metrics file (.afm) invalid - no charcters described' if first_char == -1 and last_char == 0 832 833 widthid = PDF::Writer::Object::Contents.new(self, :raw) 834 widthid << "[" 835 (first_char .. last_char).each do |ii| 836 if widths.has_key?(ii) 837 widthid << " #{widths[ii].to_i}" 838 else 839 widthid << " 0" 840 end 841 end 842 widthid << "]" 843 844 # Load the pfb file, and put that into an object too. Note that PDF 845 # supports only binary format Type1 font files and TrueType font 846 # files. There is a simple utility to convert Type1 from pfa to pfb. 847 data = File.open(fontfile, "rb") { |ff| ff.read } 848 849 # Create the font descriptor. 850 fdsc = PDF::Writer::Object::FontDescriptor.new(self) 851 # Raw contents causes problems with Acrobat Reader. 852 pfbc = PDF::Writer::Object::Contents.new(self) 853 854 # Determine flags (more than a little flakey, hopefully will not 855 # matter much). 856 flags = 0 857 if encoding == "none" 858 flags += 2 ** 2 859 else 860 flags += 2 ** 6 if metrics.italicangle.nonzero? 861 flags += 2 ** 0 if metrics.isfixedpitch == "true" 862 flags += 2 ** 5 # Assume a non-symbolic font 863 end 864 865 # 1: FixedPitch: All glyphs have the same width (as opposed to 866 # proportional or variable-pitch fonts, which have 867 # different widths). 868 # 2: Serif: Glyphs have serifs, which are short strokes drawn 869 # at an angle on the top and bottom of glyph stems. 870 # (Sans serif fonts do not have serifs.) 871 # 3: Symbolic Font contains glyphs outside the Adobe standard 872 # Latin character set. This flag and the Nonsymbolic 873 # flag cannot both be set or both be clear (see 874 # below). 875 # 4: Script: Glyphs resemble cursive handwriting. 876 # 6: Nonsymbolic: Font uses the Adobe standard Latin character set 877 # or a subset of it (see below). 878 # 7: Italic: Glyphs have dominant vertical strokes that are 879 # slanted. 880 # 17: AllCap: Font contains no lowercase letters; typically used 881 # for display purposes, such as for titles or 882 # headlines. 883 # 18: SmallCap: Font contains both uppercase and lowercase 884 # letters. The uppercase letters are similar to 885 # those in the regular version of the same typeface 886 # family. The glyphs for the lowercase letters have 887 # the same shapes as the corresponding uppercase 888 # letters, but they are sized and their proportions 889 # adjusted so that they have the same size and 890 # stroke weight as lowercase glyphs in the same 891 # typeface family. 892 # 19: ForceBold: See below. 893 894 list = { 895 'Ascent' => 'Ascender', 896 'CapHeight' => 'CapHeight', 897 'Descent' => 'Descender', 898 'FontBBox' => 'FontBBox', 899 'ItalicAngle' => 'ItalicAngle' 900 } 901 fdopt = { 902 'Flags' => flags, 903 'FontName' => metrics.fontname, 904 'StemV' => 100 # Don't know what the value for this should be! 905 } 906 907 list.each do |kk, vv| 908 zz = metrics.__send__(vv.downcase.intern) 909 fdopt[kk] = zz if zz 910 end 911 912 # Determine the cruicial lengths within this file 913 if fontfile =~ /\.pfb$/o 914 fdopt['FontFile'] = pfbc.oid 915 i1 = data.index('eexec') + 6 916 i2 = data.index('00000000') - i1 917 i3 = data.size - i2 - i1 918 pfbc.add('Length1' => i1, 'Length2' => i2, 'Length3' => i3) 919 elsif fontfile =~ /\.ttf$/o 920 fdopt['FontFile2'] = pfbc.oid 921 pfbc.add('Length1' => data.size) 922 end 923 924 fdsc.options = fdopt 925 # Embed the font program 926 pfbc << data 927 928 # Tell the font object about all this new stuff 929 tmp = { 930 'BaseFont' => metrics.fontname, 931 'Widths' => widthid.oid, 932 'FirstChar' => first_char, 933 'LastChar' => last_char, 934 'FontDescriptor' => fdsc.oid 935 } 936 tmp['SubType'] = 'TrueType' if fontfile =~ /\.ttf/ 937 938 tmp.each { |kk, vv| wfo.__send__("#{kk.downcase}=".intern, vv) } 939 end 940 941 # Also set the differences here. Note that this means that these will 942 # take effect only the first time that a font is selected, else they 943 # are ignored. 944 metrics.differences = encoding_diff unless encoding_diff.nil? 945 metrics.encoding = encoding_name 946 metrics 947 end
Loads the font metrics. This is now thread-safe.
# File lib/pdf/writer.rb 745 def load_font_metrics(font) 746 metrics = PDF::Writer::FontMetrics.open(font) 747 @mutex.synchronize do 748 @fonts[font] = metrics 749 @fonts[font].font_num = @fonts.size 750 end 751 metrics 752 end
# File lib/pdf/writer.rb 2212 def page_number_search(condition, scheme) 2213 res = nil 2214 scheme.each { |page, value| res = page if value[:stop] == condition } 2215 res 2216 end
# File lib/pdf/writer.rb 1347 def parse_tag_params(params) 1348 params ||= "" 1349 ph = {} 1350 params.scan(TAG_PARAM_RE) do |param| 1351 ph[param[0]] = param[1] || param[2] || param[3] 1352 end 1353 ph 1354 end
# File lib/pdf/writer.rb 2300 def preprocess_text(text) 2301 text 2302 end
Restore the state at the end of a page.
# File lib/pdf/writer.rb 1739 def reset_state_at_page_finish 1740 add_content("\nQ" * @state_stack.size) 1741 end
This will be called at a new page to return the state to what it was on the end of the previous page, before the stack was closed down. This is to get around not being able to have open ‘q’ across pages.
# File lib/pdf/writer.rb 1714 def reset_state_at_page_start 1715 @state_stack.each do |state| 1716 fill_color! state.fill_color 1717 stroke_color! state.stroke_color 1718 text_render_style! state.text_render_style 1719 stroke_style! state.stroke_style 1720 add_content("\nq") 1721 end 1722 end
# File lib/pdf/writer.rb 1939 def restore_margins_after_columns 1940 @left_margin = @columns[:left] 1941 @right_margin = @columns[:right] 1942 @top_margin = @columns[:top] 1943 @bottom_margin = @columns[:bottom] 1944 end
Given a start position and information about how text is to be laid out, calculate where on the page the text will end.
# File lib/pdf/writer.rb 1058 def text_end_position(x, y, angle, size, wa, text) 1059 width = text_width(text, size) 1060 width += wa * (text.count(" ")) 1061 rad = PDF::Math.deg2rad(angle) 1062 [Math.cos(rad) * width + x, ((-Math.sin(rad)) * width + y)] 1063 end