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

absolute_bottom_margin[R]

Returns the absolute y position of the bottom margin.

absolute_left_margin[R]

The absolute x position of the left margin.

absolute_right_margin[R]

The absolute x position of the right margin.

absolute_top_margin[R]

Returns the absolute y position of the top margin.

absolute_x_middle[R]

The absolute x middle position.

absolute_y_middle[R]

The absolute y middle position.

bottom_margin[RW]
column_count[R]

The total number of columns. Returns zero (0) if columns are off.

column_gutter[R]

The gutter between columns. This will return zero (0) if columns are off.

column_number[R]

The current column number. Returns zero (0) if columns are off.

column_width[R]

The width of the currently active column. This will return zero (0) if columns are off.

compressed[RW]

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.

current_base_font[R]
current_contents[R]

Returns the current contents object to which raw PDF instructions may be written.

current_font[R]
encryption_key[RW]

The string that will be used to encrypt this PDF document.

first_page[R]

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.

font_families[R]

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.

font_size[RW]
info[R]

The PDF::Writer::Object::Info info object. This is used to provide certain metadata.

left_margin[RW]
margin_height[R]

The height of the margin area.

margin_width[R]

The width of the margin area.

margin_x_middle[R]

The middle of the writing area between the left and right margins.

margin_y_middle[R]

The middle of the writing area between the top and bottom margins.

page_height[R]
page_width[R]
pointer[RW]

The vertical position of the writing point. If the vertical position is outside of the bottom margin, a new page will be created.

right_margin[RW]
top_margin[RW]
version[R]

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.

y[RW]

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

cm2pts(x) click to toggle source

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(text) click to toggle source

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(/&lt;/, '<').
32        gsub(/&gt;/, '>').
33        gsub(/&amp;/, '&')
34 end
in2pts(x) click to toggle source

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
mm2pts(x) click to toggle source

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
new(options = {}) { |self| ... } click to toggle source

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 in PAGE_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 to PDF::Writer. PDF::Writer currently supports PDF version 1.3 features and does not yet support advanced features from PDF 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
prepress(options = { }) { |pdf| ... } click to toggle source

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_fonts_conf(filename) click to toggle source

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

_post_transaction_rewind() click to toggle source

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(cc) click to toggle source

add content to the currently active object

     # File lib/pdf/writer.rb
1030 def add_content(cc)
1031   @current_contents << cc
1032 end
add_destination(label, style, *params) click to toggle source

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) with zoom percentage. params must have three values representing left, top, and zoom, 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 the left, bottom, right, and top 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_info(label, value = 0) click to toggle source

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_object(id, where = :this_page) click to toggle source

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_outline_item(label, title = label) click to toggle source

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(x, y, text, size = nil, angle = 0, word_space_adjust = 0) click to toggle source

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_wrap(x, y, width, text, size = nil, justification = :left, angle = 0, test = false) click to toggle source

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
append_page() click to toggle source

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
bleed_box(x0, y0, x1, y1) click to toggle source

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
check_all_here() click to toggle source

should be used for internal checks, not implemented as yet

    # File lib/pdf/writer.rb
700 def check_all_here
701 end
close_object() click to toggle source

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
cm2pts(x) click to toggle source

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
columns?() click to toggle source

Indicates if columns are currently on.

     # File lib/pdf/writer.rb
1907 def columns?
1908   @columns_on
1909 end
compressed?() click to toggle source

Returns true if the document is compressed.

    # File lib/pdf/writer.rb
439 def compressed?
440   @compressed == true
441 end
current_font!() click to toggle source

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
current_page_number() click to toggle source

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
font_descender(size = nil) click to toggle source

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
font_height(size = nil) click to toggle source

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
in2pts(x) click to toggle source

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
insert_mode(options = {}) click to toggle source

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
insert_page(page = nil) click to toggle source

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
insert_position(position = nil) click to toggle source

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
lines_remaining(font_size = nil) click to toggle source

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
margins_cm(top, left = top, bottom = top, right = left) click to toggle source

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
margins_in(top, left = top, bottom = top, right = left) click to toggle source

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
margins_mm(top, left = top, bottom = top, right = left) click to toggle source

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
margins_pt(top, left = top, bottom = top, right = left) click to toggle source

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
mm2pts(x) click to toggle source

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
move_pointer(dy, make_space = false) click to toggle source

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
new_page(insert = false, page = nil, pos = :after) click to toggle source

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
open_at(page, style, *params) click to toggle source

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
open_here(style, *params) click to toggle source

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
open_new_object() click to toggle source

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
open_object() { |current_contents| ... } click to toggle source

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
page_mode=(mode) click to toggle source

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
render(debug = false) click to toggle source

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
Also aliased as: to_s
reopen_object(id) click to toggle source

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_state() click to toggle source

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_as(name) click to toggle source

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
save_state() click to toggle source

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
select_font(font, encoding = nil) click to toggle source

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
size() click to toggle source

The number of PDF objects in the document

    # File lib/pdf/writer.rb
138 def size
139   @objects.size
140 end
start_columns(size = 2, gutter = 10) click to toggle source

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
start_new_page(force = false) click to toggle source

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
start_page_numbering(x, y, size, pos = nil, pattern = nil, starting = nil) click to toggle source

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
stop_columns() click to toggle source

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_object(id) click to toggle source

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(stop_total = false, stop_at = :current, scheme = 0) click to toggle source

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
text(text, options = {}) click to toggle source

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
text_line_width(text, size = nil) click to toggle source

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 "&lt;" == tt[pos, 4]
1532         width += char_width(font, '<')
1533         pos += 3
1534       elsif "&gt;" == tt[pos, 4]
1535         width += char_width(font, '>')
1536         pos += 3
1537       elsif "&amp;" == 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
text_width(text, size = nil) click to toggle source

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
to_s(debug = false)
Alias for: render
trim_box(x0, y0, x1, y1) click to toggle source

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
viewer_preferences(label, value = 0) click to toggle source

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
which_page_number(page_num, scheme = 0) click to toggle source

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

add_page_numbers() click to toggle source
     # 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
adjust_wrapped_text(text, actual, width, x, just) click to toggle source

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
char_width(font, char) click to toggle source
     # 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
find_font(fontname) click to toggle source
    # 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
font_file(fontfile) click to toggle source
    # 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_font_id() click to toggle source

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_id() click to toggle source

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
init_font_families() click to toggle source

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
load_font(font, encoding = nil) click to toggle source
    # 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
load_font_metrics(font) click to toggle source

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
parse_tag_params(params) click to toggle source
     # 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
preprocess_text(text) click to toggle source
     # File lib/pdf/writer.rb
2300 def preprocess_text(text)
2301   text
2302 end
quick_text_tags(text, ii, font_change) click to toggle source

Wrapper function for text_tags

     # File lib/pdf/writer.rb
1067 def quick_text_tags(text, ii, font_change)
1068   ret = text_tags(text, ii, font_change)
1069   [ret[0], ret[1], ret[2]]
1070 end
reset_state_at_page_finish() click to toggle source

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
reset_state_at_page_start() click to toggle source

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
restore_margins_after_columns() click to toggle source
     # 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
text_end_position(x, y, angle, size, wa, text) click to toggle source

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
text_tags(text, pos, font_change, final = false, x = 0, y = 0, size = 0, angle = 0, word_space_adjust = 0) click to toggle source

Checks if text contains a control tag at pos. Control tags are XML-like tags that contain tag information.

Supported Tag Formats

&lt;b>

Adds b to the end of the current text state. If this is the closing tag, &lt;/b>, b is removed from the end of the current text state.

&lt;i>

Adds i to the end of the current text state. If this is the closing tag, &lt;/i, i is removed from the end of the current text state.

&lt;r:TAG[ PARAMS]/>

Calls a stand-alone replace callback method of the form tag_TAG_replace. PARAMS must be separated from the TAG name by a single space. The PARAMS, if present, are passed to the replace callback unmodified, whose responsibility it is to interpret the parameters. The replace callback is expected to return text that will be used in the place of the tag. text_tags is called again immediately so that if the replacement text has tags, they will be dealt with properly.

&lt;C:TAG[ PARAMS]/>

Calls a stand-alone drawing callback method. The method will be provided an information hash (see below for the data provided). It is expected to use this information to perform whatever drawing tasks are needed to perform its task.

&lt;c:TAG[ PARAMS]>

Calls a paired drawing callback method. The method will be provided an information hash (see below for the data provided). It is expected to use this information to perform whatever drawing tasks are needed to perform its task. It must have a corresponding &lt;/c:TAG> closing tag. Paired callback behaviours will be preserved over page breaks and line changes.

Drawing callback tags will be provided an information hash that tells the callback method where it must perform its drawing tasks.

Drawing Callback Parameters

:x

The current X position of the text.

:y

The current y position of the text.

:angle

The current text drawing angle.

:params

Any parameters that may be important to the callback. This value is only guaranteed to have meaning when a stand-alone callback is made or the opening tag is processed.

:status

:start, :end, :start_line, :end_line

:cbid

The identifier of this callback. This may be used as a key into a different variable where state may be kept.

:callback

The name of the callback function. Only set for stand-alone or opening callback tags.

:height

The font height.

:descender

The font descender size.

:status Values and Meanings

:start

The callback has been started. This applies either when the callback is a stand-alone callback (&lt;C:TAG/>) or the opening tag of a paired tag (&lt;c:TAG>).

:end

The callback has been manually terminated with a closing tag (&lt;/c:TAG>).

:start_line

Called when a new line is to be drawn. This allows the callback to perform any updates necessary to permit paired callbacks to cross line boundaries. This will usually involve updating x, y positions.

:end_line

Called when the end of a line is reached. This permits the callback to perform any drawing necessary to permit paired callbacks to cross line boundaries.

Drawing callback methods may return a hash of the :x and :y position that the drawing pointer should take after the callback is complete.

Known Callback Tags

&lt;c:alink URI>

makes an external link around text between the opening and closing tags of this callback. The URI may be any URL, including http://, ftp://, and mailto:, as long as there is a URL handler registered. URI is of the form uri=“URI”.

&lt;c:ilink DEST>

makes an internal link within the document. The DEST must refer to a known named destination within the document. DEST is of the form dest=“DEST”.

&lt;c:uline>

underlines the specified text.

&lt;C:bullet>

Draws a solid bullet at the tag position.

&lt;C:disc>

Draws a disc bullet at the tag position.

     # File lib/pdf/writer.rb
1181 def text_tags(text, pos, font_change, final = false, x = 0, y = 0, size = 0, angle = 0, word_space_adjust = 0)
1182   tag_size = 0
1183 
1184   tag_match = %r!^<(/)?([^>]+)>!.match(text[pos..-1])
1185 
1186   if tag_match
1187     closed, tag_name = tag_match.captures
1188     cts = @current_text_state # Alias for shorter lines.
1189     tag_size = tag_name.size + 2 + (closed ? 1 : 0)
1190 
1191     case tag_name
1192     when %r{^(?:b|strong)$}o
1193       if closed
1194         cts.slice!(-1, 1) if ?b == cts[-1]
1195       else
1196         cts << ?b
1197       end
1198     when %r{^(?:i|em)$}o
1199       if closed
1200         cts.slice!(-1, 1) if ?i == cts[-1]
1201       else
1202         cts << ?i
1203       end
1204     when %r{^r:}o
1205       _match = MATCH_TAG_REPLACE_RE.match(tag_name)
1206       if _match.nil?
1207         warn PDF::Writer::Lang[:callback_warning] % [ 'r:', tag_name ]
1208         tag_size = 0
1209       else
1210         func    = _match.captures[0]
1211         params  = parse_tag_params(_match.captures[1] || "")
1212         tag     = TAGS[:replace][func]
1213 
1214         if tag
1215           text[pos, tag_size] = tag[self, params]
1216           tag_size, text, font_change, x, y = text_tags(text, pos,
1217                                                         font_change,
1218                                                         final, x, y, size,
1219                                                         angle,
1220                                                         word_space_adjust)
1221         else
1222           warn PDF::Writer::Lang[:callback_warning] % [ 'r:', func ]
1223           tag_size = 0
1224         end
1225       end
1226     when %r{^C:}o
1227       _match = MATCH_TAG_DRAW_ONE_RE.match(tag_name)
1228       if _match.nil?
1229         warn PDF::Writer::Lang[:callback_warning] % [ 'C:', tag_name ]
1230         tag_size = 0
1231       else
1232         func    = _match.captures[0]
1233         params  = parse_tag_params(_match.captures[1] || "")
1234         tag     = TAGS[:single][func]
1235 
1236         if tag
1237           font_change = false
1238 
1239           if final
1240             # Only call the function if this is the "final" call. Assess
1241             # the text position. Calculate the text width to this point.
1242             x, y = text_end_position(x, y, angle, size, word_space_adjust,
1243                                      text[0, pos])
1244             info = {
1245               :x          => x,
1246               :y          => y,
1247               :angle      => angle,
1248               :params     => params,
1249               :status     => :start,
1250               :cbid       => @callbacks.size + 1,
1251               :callback   => func,
1252               :height     => font_height(size),
1253               :descender  => font_descender(size)
1254             }
1255 
1256             ret = tag[self, info]
1257             if ret.kind_of?(Hash)
1258               ret.each do |rk, rv|
1259                 x           = rv if rk == :x
1260                 y           = rv if rk == :y
1261                 font_change = rv if rk == :font_change
1262               end
1263             end
1264           end
1265         else
1266           warn PDF::Writer::Lang[:callback_Warning] % [ 'C:', func ]
1267           tag_size = 0
1268         end
1269       end
1270     when %r{^c:}o
1271       _match = MATCH_TAG_DRAW_PAIR_RE.match(tag_name)
1272 
1273       if _match.nil?
1274         warn PDF::Writer::Lang[:callback_warning] % [ 'c:', tag_name ]
1275         tag_size = 0
1276       else
1277         func    = _match.captures[0]
1278         params  = parse_tag_params(_match.captures[1] || "")
1279         tag     = TAGS[:pair][func]
1280 
1281         if tag
1282           font_change = false
1283 
1284           if final
1285               # Only call the function if this is the "final" call. Assess
1286               # the text position. Calculate the text width to this point.
1287             x, y = text_end_position(x, y, angle, size, word_space_adjust,
1288                                      text[0, pos])
1289             info = {
1290               :x          => x,
1291               :y          => y,
1292               :angle      => angle,
1293               :params     => params,
1294             }
1295 
1296             if closed
1297               info[:status] = :end
1298               info[:cbid]   = @callbacks.size
1299 
1300               ret = tag[self, info]
1301 
1302               if ret.kind_of?(Hash)
1303                 ret.each do |rk, rv|
1304                   x           = rv if rk == :x
1305                   y           = rv if rk == :y
1306                   font_change = rv if rk == :font_change
1307                 end
1308               end
1309 
1310               @callbacks.pop
1311             else
1312               info[:status]     = :start
1313               info[:cbid]       = @callbacks.size + 1
1314               info[:tag]        = tag
1315               info[:callback]   = func
1316               info[:height]     = font_height(size)
1317               info[:descender]  = font_descender(size)
1318 
1319               @callbacks << info
1320 
1321               ret = tag[self, info]
1322 
1323               if ret.kind_of?(Hash)
1324                 ret.each do |rk, rv|
1325                   x           = rv if rk == :x
1326                   y           = rv if rk == :y
1327                   font_change = rv if rk == :font_change
1328                 end
1329               end
1330             end
1331           end
1332         else
1333           warn PDF::Writer::Lang[:callback_warning] % [ 'c:', func ]
1334           tag_size = 0
1335         end
1336       end
1337     else
1338       tag_size = 0
1339     end
1340   end
1341   [ tag_size, text, font_change, x, y ]
1342 end