Class | PDF::Writer |
In: |
lib/pdf/writer/state.rb
lib/pdf/writer.rb |
Parent: | Object |
VERSION | = | '1.1.8' | The version of PDF::Writer. | |||||||
FONT_PATH | = | [] |
The system font path. The sytem font path will be determined differently
for each operating system.
|
|||||||
PAGE_SIZES | = | { # :value {...}: "4A0" => [0, 0, 4767.87, 6740.79], "2A0" => [0, 0, 3370.39, 4767.87], "A0" => [0, 0, 2383.94, 3370.39], "A1" => [0, 0, 1683.78, 2383.94], "A2" => [0, 0, 1190.55, 1683.78], "A3" => [0, 0, 841.89, 1190.55], "A4" => [0, 0, 595.28, 841.89], "A5" => [0, 0, 419.53, 595.28], "A6" => [0, 0, 297.64, 419.53], "A7" => [0, 0, 209.76, 297.64], "A8" => [0, 0, 147.40, 209.76], "A9" => [0, 0, 104.88, 147.40], "A10" => [0, 0, 73.70, 104.88], "B0" => [0, 0, 2834.65, 4008.19], "B1" => [0, 0, 2004.09, 2834.65], "B2" => [0, 0, 1417.32, 2004.09], "B3" => [0, 0, 1000.63, 1417.32], "B4" => [0, 0, 708.66, 1000.63], "B5" => [0, 0, 498.90, 708.66], "B6" => [0, 0, 354.33, 498.90], "B7" => [0, 0, 249.45, 354.33], "B8" => [0, 0, 175.75, 249.45], "B9" => [0, 0, 124.72, 175.75], "B10" => [0, 0, 87.87, 124.72], "C0" => [0, 0, 2599.37, 3676.54], "C1" => [0, 0, 1836.85, 2599.37], "C2" => [0, 0, 1298.27, 1836.85], "C3" => [0, 0, 918.43, 1298.27], "C4" => [0, 0, 649.13, 918.43], "C5" => [0, 0, 459.21, 649.13], "C6" => [0, 0, 323.15, 459.21], "C7" => [0, 0, 229.61, 323.15], "C8" => [0, 0, 161.57, 229.61], "C9" => [0, 0, 113.39, 161.57], "C10" => [0, 0, 79.37, 113.39], "RA0" => [0, 0, 2437.80, 3458.27], "RA1" => [0, 0, 1729.13, 2437.80], "RA2" => [0, 0, 1218.90, 1729.13], "RA3" => [0, 0, 864.57, 1218.90], "RA4" => [0, 0, 609.45, 864.57], "SRA0" => [0, 0, 2551.18, 3628.35], "SRA1" => [0, 0, 1814.17, 2551.18], "SRA2" => [0, 0, 1275.59, 1814.17], "SRA3" => [0, 0, 907.09, 1275.59], "SRA4" => [0, 0, 637.80, 907.09], "LETTER" => [0, 0, 612.00, 792.00], "LEGAL" => [0, 0, 612.00, 1008.00], "FOLIO" => [0, 0, 612.00, 936.00], "EXECUTIVE" => [0, 0, 521.86, 756.00] |
Standard page size names. One of these
may be provided to PDF::Writer.new as the
:paper parameter.
Page sizes supported are:
|
|||||||
PDF_VERSION_13 | = | '1.3' | ||||||||
PDF_VERSION_14 | = | '1.4' | ||||||||
PDF_VERSION_15 | = | '1.5' | ||||||||
PDF_VERSION_16 | = | '1.6' | ||||||||
ENCRYPT_OPTIONS | = | { #:nodoc: :print => 4, :modify => 8, :copy => 16, :add => 32 | Standard encryption/DRM options. | |||||||
TAGS | = | { :pair => { }, :single => { }, :replace => { } |
Callback tag relationships. All relationships are of the form
"tagname" => CallbackClass.
There are three types of tag callbacks:
|
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:
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. |
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, line 325 325: def initialize(options = {}) 326: paper = options[:paper] || "LETTER" 327: orientation = options[:orientation] || :portrait 328: version = options[:version] || PDF_VERSION_13 329: 330: @mutex = Mutex.new 331: @current_id = @current_font_id = 0 332: 333: # Start the document 334: @objects = [] 335: @callbacks = [] 336: @font_families = {} 337: @fonts = {} 338: @stack = [] 339: @state_stack = StateStack.new 340: @loose_objects = [] 341: @current_text_state = "" 342: @options = {} 343: @destinations = {} 344: @add_loose_objects = {} 345: @images = [] 346: @word_space_adjust = nil 347: @current_stroke_style = PDF::Writer::StrokeStyle.new(1) 348: @page_numbering = nil 349: @arc4 = nil 350: @encryption = nil 351: @file_identifier = nil 352: 353: @columns = {} 354: @columns_on = false 355: @insert_mode = nil 356: 357: @catalog = PDF::Writer::Object::Catalog.new(self) 358: @outlines = PDF::Writer::Object::Outlines.new(self) 359: @pages = PDF::Writer::Object::Pages.new(self) 360: 361: @current_node = @pages 362: @procset = PDF::Writer::Object::Procset.new(self) 363: @info = PDF::Writer::Object::Info.new(self) 364: @page = PDF::Writer::Object::Page.new(self) 365: @current_text_render_style = 0 366: @first_page = @page 367: 368: @version = version 369: 370: # Initialize the default font families. 371: init_font_families 372: 373: @font_size = 10 374: @pageset = [@pages.first_page] 375: 376: if paper.kind_of?(Array) 377: if paper.size == 4 378: size = paper # Coordinate Array 379: else 380: size = [0, 0, PDF::Writer.cm2pts(paper[0]), PDF::Writer.cm2pts(paper[1])] 381: # Paper size in centimeters has been passed 382: end 383: else 384: size = PAGE_SIZES[paper.upcase].dup 385: end 386: size[3], size[2] = size[2], size[3] if orientation == :landscape 387: 388: @pages.media_box = size 389: 390: @page_width = size[2] - size[0] 391: @page_height = size[3] - size[1] 392: @y = @page_height 393: 394: # Also set the margins to some reasonable defaults -- 1.27 cm, 36pt, 395: # or 0.5 inches. 396: margins_pt(36) 397: 398: # Set the current writing position to the top of the first page 399: @y = absolute_top_margin 400: # Get the ID of the page that was created during the instantiation 401: # process. 402: 403: fill_color! Color::RGB::Black 404: stroke_color! Color::RGB::Black 405: 406: yield self if block_given? 407: 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, line 169 169: def prepress(options = { }) 170: pdf = self.new(options) 171: 172: bleed_size = options[:bleed_size] || 12 173: mark_length = options[:mark_length] || 18 174: 175: pdf.left_margin = options[:left_margin] if options[:left_margin] 176: pdf.right_margin = options[:right_margin] if options[:right_margin] 177: pdf.top_margin = options[:top_margin] if options[:top_margin] 178: pdf.bottom_margin = options[:bottom_margin] if options[:bottom_margin] 179: 180: # This is in an "odd" order because the y-coordinate system in PDF 181: # is from bottom to top. 182: tx0 = pdf.pages.media_box[0] + pdf.left_margin 183: ty0 = pdf.pages.media_box[3] - pdf.top_margin 184: tx1 = pdf.pages.media_box[2] - pdf.right_margin 185: ty1 = pdf.pages.media_box[1] + pdf.bottom_margin 186: 187: bx0 = tx0 - bleed_size 188: by0 = ty0 - bleed_size 189: bx1 = tx1 + bleed_size 190: by1 = ty1 + bleed_size 191: 192: pdf.pages.trim_box = [ tx0, ty0, tx1, ty1 ] 193: pdf.pages.bleed_box = [ bx0, by0, bx1, by1 ] 194: 195: all = pdf.open_object 196: pdf.save_state 197: kk = Color::CMYK.new(0, 0, 0, 100) 198: pdf.stroke_color! kk 199: pdf.fill_color! kk 200: pdf.stroke_style! StrokeStyle.new(0.3) 201: 202: pdf.prepress_clip_mark(tx1, ty0, 0, mark_length, bleed_size) # Upper Right 203: pdf.prepress_clip_mark(tx0, ty0, 90, mark_length, bleed_size) # Upper Left 204: pdf.prepress_clip_mark(tx0, ty1, 180, mark_length, bleed_size) # Lower Left 205: pdf.prepress_clip_mark(tx1, ty1, -90, mark_length, bleed_size) # Lower Right 206: 207: mid_x = pdf.pages.media_box[2] / 2.0 208: mid_y = pdf.pages.media_box[3] / 2.0 209: 210: pdf.prepress_center_mark(mid_x, ty0, 0, mark_length, bleed_size) # Centre Top 211: pdf.prepress_center_mark(tx0, mid_y, 90, mark_length, bleed_size) # Centre Left 212: pdf.prepress_center_mark(mid_x, ty1, 180, mark_length, bleed_size) # Centre Bottom 213: pdf.prepress_center_mark(tx1, mid_y, -90, mark_length, bleed_size) # Centre Right 214: 215: pdf.restore_state 216: pdf.close_object 217: pdf.add_object(all, :all) 218: 219: yield pdf if block_given? 220: 221: pdf 222: end
Parse the fonts.conf XML file.
# File lib/pdf/writer.rb, line 94 94: def parse_fonts_conf(filename) 95: doc = REXML::Document.new(File.open(filename, "rb")).root rescue nil 96: 97: if doc 98: path = REXML::XPath.match(doc, '//dir').map do |el| 99: el.text.gsub($/, '') 100: end 101: doc = nil 102: else 103: path = [] 104: end 105: path 106: end
memory improvement for transaction-simple
# File lib/pdf/writer.rb, line 2725 2725: def _post_transaction_rewind 2726: @objects.each { |e| e.instance_variable_set(:@parent,self) } 2727: end
add content to the currently active object
# File lib/pdf/writer.rb, line 1029 1029: def add_content(cc) 1030: @current_contents << cc 1031: 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) 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, line 1856 1856: def add_destination(label, style, *params) 1857: @destinations[label] = PDF::Writer::Object::Destination.new(self, @current_page, style, *params) 1858: end
Add content to the documents info object.
# File lib/pdf/writer.rb, line 1805 1805: def add_info(label, value = 0) 1806: # This will only work if the label is one of the valid ones. Modify 1807: # this so that arrays can be passed as well. If @label is an array 1808: # then assume that it is key => value pairs else assume that they are 1809: # both scalar, anything else will probably error. 1810: if label.kind_of?(Hash) 1811: label.each { |kk, vv| @info.__send__(kk.downcase.intern, vv) } 1812: else 1813: @info.__send__(label.downcase.intern, value) 1814: end 1815: end
Add a link in the document to an internal destination (ie. within the document)
# File lib/pdf/writer.rb, line 681 681: def add_internal_link(label, x0, y0, x1, y1) 682: PDF::Writer::Object::Annotation.new(self, :ilink, [x0, y0, x1, y1], label) 683: end
Add a link in the document to an external URL.
# File lib/pdf/writer.rb, line 675 675: def add_link(uri, x0, y0, x1, y1) 676: PDF::Writer::Object::Annotation.new(self, :link, [x0, y0, x1, y1], uri) 677: end
After an object has been created, it will only show if it has been added, using this method.
# File lib/pdf/writer.rb, line 1778 1778: def add_object(id, where = :this_page) 1779: obj = @loose_objects.detect { |ii| ii == id } 1780: 1781: if obj and @current_contents != obj 1782: case where 1783: when :all_pages, :this_page 1784: @add_loose_objects[obj] = where if where == :all_pages 1785: @current_contents.on_page.contents << obj if @current_contents.on_page 1786: when :even_pages 1787: @add_loose_objects[obj] = where 1788: page = @current_contents.on_page 1789: add_object(id) if (page.info.page_number % 2) == 0 1790: when :odd_pages 1791: @add_loose_objects[obj] = where 1792: page = @current_contents.on_page 1793: add_object(id) if (page.info.page_number % 2) == 1 1794: when :all_following_pages 1795: @add_loose_objects[obj] = :all_pages 1796: when :following_even_pages 1797: @add_loose_objects[obj] = :even_pages 1798: when :following_odd_pages 1799: @add_loose_objects[obj] = :odd_pages 1800: end 1801: end 1802: end
Add an outline item (Bookmark).
# File lib/pdf/writer.rb, line 686 686: def add_outline_item(label, title = label) 687: PDF::Writer::Object::Outline.new(self, label, title) 688: 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, line 1362 1362: def add_text(x, y, text, size = nil, angle = 0, word_space_adjust = 0) 1363: if text.kind_of?(Numeric) and size.kind_of?(String) 1364: text, size = size, text 1365: warn PDF::Writer::Lang[:add_text_parameters_reversed] % caller[0] 1366: end 1367: 1368: if size.nil? or size <= 0 1369: size = @font_size 1370: end 1371: 1372: select_font("Helvetica") if @fonts.empty? 1373: 1374: text = text.to_s 1375: 1376: # If there are any open callbacks, then they should be called, to show 1377: # the start of the line 1378: @callbacks.reverse_each do |ii| 1379: info = ii.dup 1380: info[:x] = x 1381: info[:y] = y 1382: info[:angle] = angle 1383: info[:status] = :start_line 1384: 1385: info[:tag][self, info] 1386: end 1387: if angle == 0 1388: add_content("\nBT %.3f %.3f Td" % [x, y]) 1389: else 1390: rad = PDF::Math.deg2rad(angle) 1391: tt = "\nBT %.3f %.3f %.3f %.3f %.3f %.3f Tm" 1392: tt = tt % [ Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad), x, y ] 1393: add_content(tt) 1394: end 1395: 1396: if (word_space_adjust != 0) or not ((@word_space_adjust.nil?) and (@word_space_adjust != word_space_adjust)) 1397: @word_space_adjust = word_space_adjust 1398: add_content(" %.3f Tw" % word_space_adjust) 1399: end 1400: 1401: pos = -1 1402: start = 0 1403: loop do 1404: pos += 1 1405: break if pos == text.size 1406: font_change = true 1407: tag_size, text, font_change = quick_text_tags(text, pos, font_change) 1408: 1409: if tag_size != 0 1410: if pos > start 1411: part = text[start, pos - start] 1412: tt = " /F#{find_font(@current_font).font_id}" 1413: tt << " %.1f Tf %d Tr" % [ size, @current_text_render_style ] 1414: tt << " (#{PDF::Writer.escape(part)}) Tj" 1415: add_content(tt) 1416: end 1417: 1418: if font_change 1419: current_font! 1420: else 1421: add_content(" ET") 1422: xp = x 1423: yp = y 1424: tag_size, text, font_change, xp, yp = text_tags(text, pos, font_change, true, xp, yp, size, angle, word_space_adjust) 1425: 1426: # Restart the text object 1427: if angle.zero? 1428: add_content("\nBT %.3f %.3f Td" % [xp, yp]) 1429: else 1430: rad = PDF::Math.deg2rad(angle) 1431: tt = "\nBT %.3f %.3f %.3f %.3f %.3f %.3f Tm" 1432: tt = tt % [ Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad), xp, yp ] 1433: add_content(tt) 1434: end 1435: 1436: if (word_space_adjust != 0) or (word_space_adjust != @word_space_adjust) 1437: @word_space_adjust = word_space_adjust 1438: add_content(" %.3f Tw" % [word_space_adjust]) 1439: end 1440: end 1441: 1442: pos += tag_size - 1 1443: start = pos + 1 1444: end 1445: end 1446: 1447: if start < text.size 1448: part = text[start..-1] 1449: 1450: tt = " /F#{find_font(@current_font).font_id}" 1451: tt << " %.1f Tf %d Tr" % [ size, @current_text_render_style ] 1452: tt << " (#{PDF::Writer.escape(part)}) Tj" 1453: add_content(tt) 1454: end 1455: add_content(" ET") 1456: 1457: # XXX: Experimental fix. 1458: @callbacks.reverse_each do |ii| 1459: info = ii.dup 1460: info[:x] = x 1461: info[:y] = y 1462: info[:angle] = angle 1463: info[:status] = :end_line 1464: info[:tag][self, info] 1465: end 1466: 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, line 1604 1604: def add_text_wrap(x, y, width, text, size = nil, justification = :left, angle = 0, test = false) 1605: if text.kind_of?(Numeric) and size.kind_of?(String) 1606: text, size = size, text 1607: warn PDF::Writer::Lang[:add_textw_parameters_reversed] % caller[0] 1608: end 1609: 1610: if size.nil? or size <= 0 1611: size = @font_size 1612: end 1613: 1614: # Need to store the initial text state, as this will change during the 1615: # width calculation, but will need to be re-set before printing, so 1616: # that the chars work out right 1617: t_CTS = @current_text_state.dup 1618: 1619: select_font("Helvetica") if @fonts.empty? 1620: return "" if width <= 0 1621: 1622: w = brk = brkw = 0 1623: font = @current_font 1624: tw = width / size.to_f * 1000 1625: 1626: pos = -1 1627: loop do 1628: pos += 1 1629: break if pos == text.size 1630: font_change = true 1631: tag_size, text, font_change = quick_text_tags(text, pos, font_change) 1632: if tag_size != 0 1633: if font_change 1634: current_font! 1635: font = @current_font 1636: end 1637: pos += (tag_size - 1) 1638: else 1639: w += char_width(font, text[pos, 1]) 1640: 1641: if w > tw # We need to truncate this line 1642: if brk > 0 # There is somewhere to break the line. 1643: if text[brk] == " " 1644: tmp = text[0, brk] 1645: else 1646: tmp = text[0, brk + 1] 1647: end 1648: x, adjust = adjust_wrapped_text(tmp, brkw, width, x, justification) 1649: 1650: # Reset the text state 1651: @current_text_state = t_CTS.dup 1652: current_font! 1653: add_text(x, y, tmp, size, angle, adjust) unless test 1654: return text[brk + 1..-1] 1655: else # just break before the current character 1656: tmp = text[0, pos] 1657: # tmpw = (w - char_width(font, text[pos, 1])) * size / 1000.0 1658: x, adjust = adjust_wrapped_text(tmp, brkw, width, x, justification) 1659: 1660: # Reset the text state 1661: @current_text_state = t_CTS.dup 1662: current_font! 1663: add_text(x, y, tmp, size, angle, adjust) unless test 1664: return text[pos..-1] 1665: end 1666: end 1667: 1668: if text[pos] == ?- 1669: brk = pos 1670: brkw = w * size / 1000.0 1671: end 1672: 1673: if text[pos, 1] == " " 1674: brk = pos 1675: ctmp = text[pos] 1676: ctmp = @fonts[font].differences[ctmp] unless @fonts[font].differences.nil? 1677: z = @fonts[font].c[tmp].nil? ? 0 : @fonts[font].c[tmp]['WX'] 1678: brkw = (w - z) * size / 1000.0 1679: end 1680: end 1681: end 1682: 1683: # There was no need to break this line. 1684: justification = :left if justification == :full 1685: tmpw = (w * size) / 1000.0 1686: x, adjust = adjust_wrapped_text(text, tmpw, width, x, justification) 1687: # reset the text state 1688: @current_text_state = t_CTS.dup 1689: current_font! 1690: add_text(x, y, text, size, angle, adjust) unless test 1691: return "" 1692: end
Changes the insert_page property to append to the page set.
# File lib/pdf/writer.rb, line 2016 2016: def append_page 2017: insert_mode(:last) 2018: end
Sets the bleed box area.
# File lib/pdf/writer.rb, line 657 657: def bleed_box(x0, y0, x1, y1) 658: @pages.bleed_box = [ x0, y0, x1, y1 ] 659: end
should be used for internal checks, not implemented as yet
# File lib/pdf/writer.rb, line 699 699: def check_all_here 700: end
Close an object for writing.
# File lib/pdf/writer.rb, line 1762 1762: def close_object 1763: unless @stack.empty? 1764: obj = @stack.pop 1765: @current_contents = obj[:contents] 1766: @current_page = obj[:page] 1767: end 1768: end
Indicates if columns are currently on.
# File lib/pdf/writer.rb, line 1902 1902: def columns? 1903: @columns_on 1904: end
Returns true if the document is compressed.
# File lib/pdf/writer.rb, line 438 438: def compressed? 439: @compressed == true 440: 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, line 997 997: def current_font! 998: select_font("Helvetica") unless @current_base_font 999: 1000: font = File.basename(@current_base_font) 1001: if @font_families[font] and @font_families[font][@current_text_state] 1002: # Then we are in some state or another and this font has a family, 1003: # and the current setting exists within it select the font, then 1004: # return it. 1005: if File.dirname(@current_base_font) != '.' 1006: nf = File.join(File.dirname(@current_base_font), @font_families[font][@current_text_state]) 1007: else 1008: nf = @font_families[font][@current_text_state] 1009: end 1010: 1011: unless @fonts[nf] 1012: enc = { 1013: :encoding => @fonts[font].encoding, 1014: :differences => @fonts[font].differences 1015: } 1016: load_font(nf, enc) 1017: end 1018: @current_font = nf 1019: else 1020: @current_font = @current_base_font 1021: end 1022: 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, line 1047 1047: def font_descender(size = nil) 1048: size = @font_size if size.nil? or size <= 0 1049: 1050: select_font("Helvetica") if @fonts.empty? 1051: hi = @fonts[@current_font].fontbbox[1].to_f 1052: (size * hi / 1000.0) 1053: 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, line 1035 1035: def font_height(size = nil) 1036: size = @font_size if size.nil? or size <= 0 1037: 1038: select_font("Helvetica") if @fonts.empty? 1039: hh = @fonts[@current_font].fontbbox[3].to_f - @fonts[@current_font].fontbbox[1].to_f 1040: (size * hh / 1000.0) 1041: 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, line 1980 1980: def insert_mode(options = {}) 1981: case options 1982: when :on, true 1983: @insert_mode = true 1984: when :off, false 1985: @insert_mode = false 1986: else 1987: return @insert_mode unless options 1988: 1989: @insert_mode = options[:on] unless options[:on].nil? 1990: 1991: unless options[:page].nil? 1992: if @pageset[options[:page]].nil? or options[:page] == :last 1993: @insert_page = @pageset[-1] 1994: else 1995: @insert_page = @pageset[options[:page]] 1996: end 1997: end 1998: 1999: @insert_position = options[:position] if options[:position] 2000: end 2001: 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, line 2007 2007: def insert_page(page = nil) 2008: return @insert_page unless page 2009: if page == :last 2010: @insert_page = @pageset[-1] 2011: else 2012: @insert_page = @pageset[page] 2013: end 2014: 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, line 2025 2025: def insert_position(position = nil) 2026: return @insert_position unless position 2027: @insert_position = position 2028: end
Returns the estimated number of lines remaining given the default or specified font size.
# File lib/pdf/writer.rb, line 2446 2446: def lines_remaining(font_size = nil) 2447: font_size ||= @font_size 2448: remaining = @y - @bottom_margin 2449: remaining / font_height(font_size).to_f 2450: end
Define the margins in centimetres.
# File lib/pdf/writer.rb, line 566 566: def margins_cm(top, left = top, bottom = top, right = left) 567: margins_pt(cm2pts(top), cm2pts(left), cm2pts(bottom), cm2pts(right)) 568: end
Define the margins in inches.
# File lib/pdf/writer.rb, line 571 571: def margins_in(top, left = top, bottom = top, right = left) 572: margins_pt(in2pts(top), in2pts(left), in2pts(bottom), in2pts(right)) 573: end
Define the margins in millimetres.
# File lib/pdf/writer.rb, line 561 561: def margins_mm(top, left = top, bottom = top, right = left) 562: margins_pt(mm2pts(top), mm2pts(left), mm2pts(bottom), mm2pts(right)) 563: 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, line 582 582: def margins_pt(top, left = top, bottom = top, right = left) 583: # Set the margins to new values 584: @top_margin = top 585: @bottom_margin = bottom 586: @left_margin = left 587: @right_margin = right 588: # Check to see if this means that the current writing position is 589: # outside the writable area 590: if @y > (@page_height - top) 591: # Move y down 592: @y = @page_height - top 593: end 594: 595: start_new_page if @y < bottom # Make a new page 596: 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, line 550 550: def move_pointer(dy, make_space = false) 551: @y -= dy 552: if @y < @bottom_margin 553: start_new_page 554: @y -= dy if make_space 555: elsif @y > absolute_top_margin 556: @y = absolute_top_margin 557: end 558: 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, line 2084 2084: def new_page(insert = false, page = nil, pos = :after) 2085: reset_state_at_page_finish 2086: 2087: if insert 2088: # The id from the PDF::Writer class is the id of the contents of the 2089: # page, not the page object itself. Query that object to find the 2090: # parent. 2091: _new_page = PDF::Writer::Object::Page.new(self, { :rpage => page, :pos => pos }) 2092: else 2093: _new_page = PDF::Writer::Object::Page.new(self) 2094: end 2095: 2096: reset_state_at_page_start 2097: 2098: # If there has been a stroke or fill color set, transfer them. 2099: fill_color! 2100: stroke_color! 2101: stroke_style! 2102: 2103: # the call to the page object set @current_contents to the present page, 2104: # so this can be returned as the page id 2105: # @current_contents 2106: _new_page 2107: 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, line 1829 1829: def open_at(page, style, *params) 1830: d = PDF::Writer::Object::Destination.new(self, page, style, *params) 1831: @catalog.open_here = d 1832: 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, line 1821 1821: def open_here(style, *params) 1822: open_at(@current_page, style, *params) 1823: 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, line 2710 2710: def open_new_object 2711: save_state 2712: oid = open_object 2713: close_object 2714: add_object(oid) 2715: reopen_object(oid) 2716: oid 2717: 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, line 1743 1743: def open_object 1744: @stack << { :contents => @current_contents, :page => @current_page } 1745: @current_contents = PDF::Writer::Object::Contents.new(self) 1746: @loose_objects << @current_contents 1747: yield @current_contents if block_given? 1748: @current_contents 1749: 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, line 1869 1869: def page_mode=(mode) 1870: @catalog.page_mode = value 1871: end
Return the PDF stream as a string.
# File lib/pdf/writer.rb, line 703 703: def render(debug = false) 704: add_page_numbers 705: @compression = false if $DEBUG or debug 706: @arc4.init(@encryption_key) unless @arc4.nil? 707: 708: check_all_here 709: 710: xref = [] 711: 712: content = "%PDF-#{@version}\n%âãÏÓ\n" 713: pos = content.size 714: 715: objects.each do |oo| 716: cont = oo.to_s 717: content << cont 718: xref << pos 719: pos += cont.size 720: end 721: 722: # pos += 1 # Newline character before XREF 723: 724: content << "\nxref\n0 #{xref.size + 1}\n0000000000 65535 f \n" 725: xref.each { |xx| content << "#{'%010d' % [xx]} 00000 n \n" } 726: content << "\ntrailer\n" 727: content << " << /Size #{xref.size + 1}\n" 728: content << " /Root 1 0 R\n /Info #{@info.oid} 0 R\n" 729: # If encryption has been applied to this document, then add the marker 730: # for this dictionary 731: if @arc4 and @encryption 732: content << "/Encrypt #{@encryption.oid} 0 R\n" 733: end 734: 735: if @file_identifier 736: content << "/ID[<#{@file_identifier}><#{@file_identifier}>]\n" 737: end 738: content << " >>\nstartxref\n#{pos}\n%%EOF\n" 739: content 740: end
Opens an existing object for editing.
# File lib/pdf/writer.rb, line 1752 1752: def reopen_object(id) 1753: @stack << { :contents => @current_contents, :page => @current_page } 1754: @current_contents = id 1755: # if this object is the primary contents for a page, then set the 1756: # current page to its parent 1757: @current_page = @current_contents.on_page unless @current_contents.on_page.nil? 1758: @current_contents 1759: end
Restore a previously saved state.
# File lib/pdf/writer.rb, line 1721 1721: def restore_state 1722: unless @state_stack.empty? 1723: state = @state_stack.pop 1724: @current_fill_color = state.fill_color 1725: @current_stroke_color = state.stroke_color 1726: @current_text_render_style = state.text_render_style 1727: @current_stroke_style = state.stroke_style 1728: stroke_style! 1729: end 1730: add_content("\nQ") 1731: end
Saves the state.
# File lib/pdf/writer.rb, line 1695 1695: def save_state 1696: PDF::Writer::State.new do |state| 1697: state.fill_color = @current_fill_color 1698: state.stroke_color = @current_stroke_color 1699: state.text_render_style = @current_text_render_style 1700: state.stroke_style = @current_stroke_style 1701: @state_stack.push state 1702: end 1703: add_content("\nq") 1704: 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, line 975 975: def select_font(font, encoding = nil) 976: load_font(font, encoding) unless @fonts[font] 977: 978: @current_base_font = font 979: current_font! 980: @current_base_font 981: 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, line 1910 1910: def start_columns(size = 2, gutter = 10) 1911: # Start from the current y-position; make the set number of columns. 1912: return false if @columns_on 1913: 1914: @columns = { 1915: :current => 1, 1916: :bot_y => @y 1917: } 1918: @columns_on = true 1919: # store the current margins 1920: @columns[:left] = @left_margin 1921: @columns[:right] = @right_margin 1922: @columns[:top] = @top_margin 1923: @columns[:bottom] = @bottom_margin 1924: # Reset the margins to suit the new columns. Safe enough to assume the 1925: # first column here, but start from the current y-position. 1926: @top_margin = @page_height - @y 1927: @columns[:size] = size || 2 1928: @columns[:gutter] = gutter || 10 1929: w = absolute_right_margin - absolute_left_margin 1930: @columns[:width] = (w - ((size - 1) * gutter)) / size.to_f 1931: @right_margin = @page_width - (@left_margin + @columns[:width]) 1932: 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, line 2034 2034: def start_new_page(force = false) 2035: page_required = true 2036: 2037: if @columns_on 2038: # Check if this is just going to a new column. Increment the column 2039: # number. 2040: @columns[:current] += 1 2041: 2042: if @columns[:current] <= @columns[:size] and not force 2043: page_required = false 2044: @columns[:bot_y] = @y if @y < @columns[:bot_y] 2045: else 2046: @columns[:current] = 1 2047: @top_margin = @columns[:top] 2048: @columns[:bot_y] = absolute_top_margin 2049: end 2050: 2051: w = @columns[:width] 2052: g = @columns[:gutter] 2053: n = @columns[:current] - 1 2054: @left_margin = @columns[:left] + n * (g + w) 2055: @right_margin = @page_width - (@left_margin + w) 2056: end 2057: 2058: if page_required or force 2059: # make a new page, setting the writing point back to the top. 2060: @y = absolute_top_margin 2061: # make the new page with a call to the basic class 2062: if @insert_mode 2063: id = new_page(true, @insert_page, @insert_position) 2064: @pageset << id 2065: # Manipulate the insert options so that inserted pages follow each 2066: # other 2067: @insert_page = id 2068: @insert_position = :after 2069: else 2070: @pageset << new_page 2071: end 2072: 2073: else 2074: @y = absolute_top_margin 2075: end 2076: @pageset 2077: 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, line 2129 2129: def start_page_numbering(x, y, size, pos = nil, pattern = nil, starting = nil) 2130: pos ||= :left 2131: pattern ||= "<PAGENUM> of <TOTALPAGENUM>" 2132: starting ||= 1 2133: 2134: @page_numbering ||= [] 2135: @page_numbering << (o = {}) 2136: 2137: page = @pageset.size - 1 2138: o[page] = { 2139: :x => x, 2140: :y => y, 2141: :pos => pos, 2142: :pattern => pattern, 2143: :starting => starting, 2144: :size => size, 2145: :start => true 2146: } 2147: @page_numbering.index(o) 2148: 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, line 1946 1946: def stop_columns 1947: return false unless @columns_on 1948: @columns_on = false 1949: 1950: @columns[:bot_y] = @y if @y < @columns[:bot_y] 1951: 1952: if (@columns[:bot_y] > @bottom_margin) or @column_number == 1 1953: @y = @columns[:bot_y] 1954: else 1955: start_new_page 1956: end 1957: restore_margins_after_columns 1958: @columns = {} 1959: true 1960: end
Stop an object from appearing on pages from this point on.
# File lib/pdf/writer.rb, line 1771 1771: def stop_object(id) 1772: obj = @loose_objects.detect { |ii| ii.oid == id.oid } 1773: @add_loose_objects[obj] = nil 1774: 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, line 2187 2187: def stop_page_numbering(stop_total = false, stop_at = :current, scheme = 0) 2188: return false unless @page_numbering 2189: 2190: page = @pageset.size - 1 2191: 2192: @page_numbering[scheme][page] ||= {} 2193: o = @page_numbering[scheme][page] 2194: 2195: case [ stop_total, stop_at == :current ] 2196: when [ true, true ] 2197: o[:stop] = :stop_total 2198: when [ true, false ] 2199: o[:stop] = :stop_total_next 2200: when [ false, true ] 2201: o[:stop] = :stop_next 2202: else 2203: o[:stop] = :stop 2204: end 2205: 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, line 2334 2334: def text(text, options = {}) 2335: # Apply the filtering which will make underlining (and other items) 2336: # function. 2337: text = preprocess_text(text) 2338: 2339: options ||= {} 2340: 2341: new_page_required = false 2342: __y = @y 2343: 2344: if options[:absolute_left] 2345: left = options[:absolute_left] 2346: else 2347: left = @left_margin 2348: left += options[:left] if options[:left] 2349: end 2350: 2351: if options[:absolute_right] 2352: right = options[:absolute_right] 2353: else 2354: right = absolute_right_margin 2355: right -= options[:right] if options[:right] 2356: end 2357: 2358: size = options[:font_size] || 0 2359: if size <= 0 2360: size = @font_size 2361: else 2362: @font_size = size 2363: end 2364: 2365: just = options[:justification] || :left 2366: 2367: if options[:leading] # leading instead of spacing 2368: height = options[:leading] 2369: elsif options[:spacing] 2370: height = options[:spacing] * font_height(size) 2371: else 2372: height = font_height(size) 2373: end 2374: 2375: text.each do |line| 2376: start = true 2377: loop do # while not line.empty? or start 2378: break if (line.nil? or line.empty?) and not start 2379: 2380: start = false 2381: 2382: @y -= height 2383: 2384: if @y < @bottom_margin 2385: if options[:test] 2386: new_page_required = true 2387: else 2388: # and then re-calc the left and right, in case they have 2389: # changed due to columns 2390: start_new_page 2391: @y -= height 2392: 2393: if options[:absolute_left] 2394: left = options[:absolute_left] 2395: else 2396: left = @left_margin 2397: left += options[:left] if options[:left] 2398: end 2399: 2400: if options[:absolute_right] 2401: right = options[:absolute_right] 2402: else 2403: right = absolute_right_margin 2404: right -= options[:right] if options[:right] 2405: end 2406: end 2407: end 2408: 2409: line = add_text_wrap(left, @y, right - left, line, size, just, 0, options[:test]) 2410: end 2411: end 2412: 2413: if options[:test] 2414: @y = __y 2415: new_page_required 2416: else 2417: @y 2418: end 2419: 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, line 1489 1489: def text_line_width(text, size = nil) 1490: if text.kind_of?(Numeric) and size.kind_of?(String) 1491: text, size = size, text 1492: warn PDF::Writer::Lang[:text_width_parameters_reversed] % caller[0] 1493: end 1494: 1495: if size.nil? or size <= 0 1496: size = @font_size 1497: end 1498: 1499: # This function should not change any of the settings, though it will 1500: # need to track any tag which change during calculation, so copy them 1501: # at the start and put them back at the end. 1502: t_CTS = @current_text_state.dup 1503: 1504: select_font("Helvetica") if @fonts.empty? 1505: # converts a number or a float to a string so it can get the width 1506: tt = text.to_s 1507: # hmm, this is where it all starts to get tricky - use the font 1508: # information to calculate the width of each character, add them up 1509: # and convert to user units 1510: width = 0 1511: font = @current_font 1512: 1513: pos = -1 1514: loop do 1515: pos += 1 1516: break if pos == tt.size 1517: font_change = true 1518: tag_size, text, font_change = quick_text_tags(text, pos, font_change) 1519: if tag_size != 0 1520: if font_change 1521: current_font! 1522: font = @current_font 1523: end 1524: pos += tag_size - 1 1525: else 1526: if "<" == tt[pos, 4] 1527: width += char_width(font, '<') 1528: pos += 3 1529: elsif ">" == tt[pos, 4] 1530: width += char_width(font, '>') 1531: pos += 3 1532: elsif "&" == tt[pos, 5] 1533: width += char_width(font, '&') 1534: pos += 4 1535: else 1536: width += char_width(font, tt[pos, 1]) 1537: end 1538: end 1539: end 1540: 1541: @current_text_state = t_CTS.dup 1542: current_font! 1543: 1544: (width * size / 1000.0) 1545: 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, line 1554 1554: def text_width(text, size = nil) 1555: if text.kind_of?(Numeric) and size.kind_of?(String) 1556: text, size = size, text 1557: warn PDF::Writer::Lang[:text_width_parameters_reversed] % caller[0] 1558: end 1559: 1560: if size.nil? or size <= 0 1561: size = @font_size 1562: end 1563: 1564: max = 0 1565: 1566: text.to_s.each do |line| 1567: width = text_line_width(line, size) 1568: max = width if width > max 1569: end 1570: max 1571: end
Sets the trim box area.
# File lib/pdf/writer.rb, line 652 652: def trim_box(x0, y0, x1, y1) 653: @pages.trim_box = [ x0, y0, x1, y1 ] 654: end
set the viewer preferences of the document, it is up to the browser to obey these.
# File lib/pdf/writer.rb, line 663 663: def viewer_preferences(label, value = 0) 664: @catalog.viewer_preferences ||= PDF::Writer::Object::ViewerPreferences.new(self) 665: 666: # This will only work if the label is one of the valid ones. 667: if label.kind_of?(Hash) 668: label.each { |kk, vv| @catalog.viewer_preferences.__send__("#{kk.downcase}=".intern, vv) } 669: else 670: @catalog.viewer_preferences.__send__("#{label.downcase}=".intern, value) 671: end 672: 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, line 2157 2157: def which_page_number(page_num, scheme = 0) 2158: return nil unless @page_numbering 2159: 2160: num = nil 2161: start = start_num = 1 2162: 2163: @page_numbering[scheme].each do |kk, vv| 2164: if kk <= page_num 2165: if vv.kind_of?(Hash) 2166: unless vv[:starting].nil? 2167: start = vv[:starting] 2168: start_num = kk 2169: num = page_num - start_num + start 2170: end 2171: else 2172: num = nil 2173: end 2174: end 2175: end 2176: num 2177: end
# File lib/pdf/writer.rb, line 2214 2214: def add_page_numbers 2215: # This will go through the @page_numbering array and add the page 2216: # numbers are required. 2217: if @page_numbering 2218: page_count = @pageset.size 2219: pn_tmp = @page_numbering.dup 2220: 2221: # Go through each of the page numbering schemes. 2222: pn_tmp.each do |scheme| 2223: # First, find the total pages for this schemes. 2224: page = page_number_search(:stop_total, scheme) 2225: 2226: if page 2227: total_pages = page 2228: else 2229: page = page_number_search(:stop_total_next, scheme) 2230: if page 2231: total_pages = page 2232: else 2233: total_pages = page_count - 1 2234: end 2235: end 2236: 2237: status = nil 2238: delta = pattern = pos = x = y = size = nil 2239: pattern = pos = x = y = size = nil 2240: 2241: @pageset.each_with_index do |page, index| 2242: next if status.nil? and scheme[index].nil? 2243: 2244: info = scheme[index] 2245: if info 2246: if info[:start] 2247: status = true 2248: if info[:starting] 2249: delta = info[:starting] - index 2250: else 2251: delta = index 2252: end 2253: 2254: pattern = info[:pattern] 2255: pos = info[:pos] 2256: x = info[:x] 2257: y = info[:y] 2258: size = info[:size] 2259: 2260: # Check for the special case of page numbering starting and 2261: # stopping on the same page. 2262: status = :stop_next if info[:stop] 2263: elsif [:stop, :stop_total].include?(info[:stop]) 2264: status = :stop_now 2265: elsif status == true and [:stop_next, :stop_total_next].include?(info[:stop]) 2266: status = :stop_next 2267: end 2268: end 2269: 2270: if status 2271: # Add the page numbering to this page 2272: num = index + delta.to_i 2273: total = total_pages + num - index 2274: patt = pattern.gsub(/<PAGENUM>/, num.to_s).gsub(/<TOTALPAGENUM>/, total.to_s) 2275: reopen_object(page.contents.first) 2276: 2277: case pos 2278: when :left # Write the page number from x. 2279: w = 0 2280: when :right # Write the page number to x. 2281: w = text_width(patt, size) 2282: when :center # Write the page number around x. 2283: w = text_width(patt, size) / 2.0 2284: end 2285: add_text(x - w, y, patt, size) 2286: close_object 2287: status = nil if [ :stop_now, :stop_next ].include?(status) 2288: end 2289: end 2290: end 2291: end 2292: end
Partially calculate the values necessary to sort out the justification of text.
# File lib/pdf/writer.rb, line 1575 1575: def adjust_wrapped_text(text, actual, width, x, just) 1576: adjust = 0 1577: 1578: case just 1579: when :left 1580: nil 1581: when :right 1582: x += (width - actual) 1583: when :center 1584: x += (width - actual) / 2.0 1585: when :full 1586: spaces = text.count(" ") 1587: adjust = (width - actual) / spaces.to_f if spaces > 0 1588: end 1589: 1590: [x, adjust] 1591: end
# File lib/pdf/writer.rb, line 1468 1468: def char_width(font, char) 1469: char = char[0] unless @fonts[font].c[char] 1470: 1471: if @fonts[font].differences and @fonts[font].c[char].nil? 1472: name = @fonts[font].differences[char] || 'M' 1473: width = @fonts[font].c[name]['WX'] if @fonts[font].c[name]['WX'] 1474: elsif @fonts[font].c[char] 1475: width = @fonts[font].c[char]['WX'] 1476: else 1477: width = @fonts[font].c['M']['WX'] 1478: end 1479: width 1480: end
# File lib/pdf/writer.rb, line 754 754: def find_font(fontname) 755: name = File.basename(fontname, ".afm") 756: @objects.detect do |oo| 757: oo.kind_of?(PDF::Writer::Object::Font) and /#{oo.basefont}$/ =~ name 758: end 759: end
# File lib/pdf/writer.rb, line 762 762: def font_file(fontfile) 763: path = "#{fontfile}.pfb" 764: return path if File.exists?(path) 765: path = "#{fontfile}.ttf" 766: return path if File.exists?(path) 767: nil 768: end
Initialize the font families for the default fonts.
# File lib/pdf/writer.rb, line 623 623: def init_font_families 624: # Set the known family groups. These font families will be used to 625: # enable bold and italic markers to be included within text 626: # streams. HTML forms will be used... <b></b> <i></i> 627: @font_families["Helvetica"] = 628: { 629: "b" => 'Helvetica-Bold', 630: "i" => 'Helvetica-Oblique', 631: "bi" => 'Helvetica-BoldOblique', 632: "ib" => 'Helvetica-BoldOblique' 633: } 634: @font_families['Courier'] = 635: { 636: "b" => 'Courier-Bold', 637: "i" => 'Courier-Oblique', 638: "bi" => 'Courier-BoldOblique', 639: "ib" => 'Courier-BoldOblique' 640: } 641: @font_families['Times-Roman'] = 642: { 643: "b" => 'Times-Bold', 644: "i" => 'Times-Italic', 645: "bi" => 'Times-BoldItalic', 646: "ib" => 'Times-BoldItalic' 647: } 648: end
# File lib/pdf/writer.rb, line 771 771: def load_font(font, encoding = nil) 772: metrics = load_font_metrics(font) 773: 774: name = File.basename(font).gsub(/\.afm$/o, "") 775: 776: encoding_diff = nil 777: case encoding 778: when Hash 779: encoding_name = encoding[:encoding] 780: encoding_diff = encoding[:differences] 781: encoding = PDF::Writer::Object::FontEncoding.new(self, encoding_name, encoding_diff) 782: when NilClass 783: encoding_name = encoding = 'WinAnsiEncoding' 784: else 785: encoding_name = encoding 786: end 787: 788: wfo = PDF::Writer::Object::Font.new(self, name, encoding) 789: 790: # We have an Adobe Font Metrics (.afm) file. We need to find the 791: # associated Type1 (.pfb) or TrueType (.ttf) files (we do not yet 792: # support OpenType fonts); we need to load it into a 793: # PDF::Writer::Object and put the references into the metrics object. 794: base = metrics.path.sub(/\.afm$/o, "") 795: fontfile = font_file(base) 796: unless fontfile 797: base = File.basename(base) 798: FONT_PATH.each do |path| 799: fontfile = font_file(File.join(path, base)) 800: break if fontfile 801: end 802: end 803: 804: if font =~ /afm/o and fontfile 805: # Find the array of font widths, and put that into an object. 806: first_char = -1 807: last_char = 0 808: 809: widths = {} 810: metrics.c.each_value do |details| 811: num = details["C"] 812: 813: if num >= 0 814: # warn "Multiple definitions of #{num}" if widths.has_key?(num) 815: widths[num] = details['WX'] 816: first_char = num if num < first_char or first_char < 0 817: last_char = num if num > last_char 818: end 819: end 820: 821: # Adjust the widths for the differences array. 822: if encoding_diff 823: encoding_diff.each do |cnum, cname| 824: (cnum - last_char).times { widths << 0 } if cnum > last_char 825: last_char = cnum 826: widths[cnum - first_char] = metrics.c[cname]['WX'] if metrics.c[cname] 827: end 828: end 829: 830: raise RuntimeError, 'Font metrics file (.afm) invalid - no charcters described' if first_char == -1 and last_char == 0 831: 832: widthid = PDF::Writer::Object::Contents.new(self, :raw) 833: widthid << "[" 834: (first_char .. last_char).each do |ii| 835: if widths.has_key?(ii) 836: widthid << " #{widths[ii].to_i}" 837: else 838: widthid << " 0" 839: end 840: end 841: widthid << "]" 842: 843: # Load the pfb file, and put that into an object too. Note that PDF 844: # supports only binary format Type1 font files and TrueType font 845: # files. There is a simple utility to convert Type1 from pfa to pfb. 846: data = File.open(fontfile, "rb") { |ff| ff.read } 847: 848: # Create the font descriptor. 849: fdsc = PDF::Writer::Object::FontDescriptor.new(self) 850: # Raw contents causes problems with Acrobat Reader. 851: pfbc = PDF::Writer::Object::Contents.new(self) 852: 853: # Determine flags (more than a little flakey, hopefully will not 854: # matter much). 855: flags = 0 856: if encoding == "none" 857: flags += 2 ** 2 858: else 859: flags += 2 ** 6 if metrics.italicangle.nonzero? 860: flags += 2 ** 0 if metrics.isfixedpitch == "true" 861: flags += 2 ** 5 # Assume a non-symbolic font 862: end 863: 864: # 1: FixedPitch: All glyphs have the same width (as opposed to 865: # proportional or variable-pitch fonts, which have 866: # different widths). 867: # 2: Serif: Glyphs have serifs, which are short strokes drawn 868: # at an angle on the top and bottom of glyph stems. 869: # (Sans serif fonts do not have serifs.) 870: # 3: Symbolic Font contains glyphs outside the Adobe standard 871: # Latin character set. This flag and the Nonsymbolic 872: # flag cannot both be set or both be clear (see 873: # below). 874: # 4: Script: Glyphs resemble cursive handwriting. 875: # 6: Nonsymbolic: Font uses the Adobe standard Latin character set 876: # or a subset of it (see below). 877: # 7: Italic: Glyphs have dominant vertical strokes that are 878: # slanted. 879: # 17: AllCap: Font contains no lowercase letters; typically used 880: # for display purposes, such as for titles or 881: # headlines. 882: # 18: SmallCap: Font contains both uppercase and lowercase 883: # letters. The uppercase letters are similar to 884: # those in the regular version of the same typeface 885: # family. The glyphs for the lowercase letters have 886: # the same shapes as the corresponding uppercase 887: # letters, but they are sized and their proportions 888: # adjusted so that they have the same size and 889: # stroke weight as lowercase glyphs in the same 890: # typeface family. 891: # 19: ForceBold: See below. 892: 893: list = { 894: 'Ascent' => 'Ascender', 895: 'CapHeight' => 'CapHeight', 896: 'Descent' => 'Descender', 897: 'FontBBox' => 'FontBBox', 898: 'ItalicAngle' => 'ItalicAngle' 899: } 900: fdopt = { 901: 'Flags' => flags, 902: 'FontName' => metrics.fontname, 903: 'StemV' => 100 # Don't know what the value for this should be! 904: } 905: 906: list.each do |kk, vv| 907: zz = metrics.__send__(vv.downcase.intern) 908: fdopt[kk] = zz if zz 909: end 910: 911: # Determine the cruicial lengths within this file 912: if fontfile =~ /\.pfb$/o 913: fdopt['FontFile'] = pfbc.oid 914: i1 = data.index('eexec') + 6 915: i2 = data.index('00000000') - i1 916: i3 = data.size - i2 - i1 917: pfbc.add('Length1' => i1, 'Length2' => i2, 'Length3' => i3) 918: elsif fontfile =~ /\.ttf$/o 919: fdopt['FontFile2'] = pfbc.oid 920: pfbc.add('Length1' => data.size) 921: end 922: 923: fdsc.options = fdopt 924: # Embed the font program 925: pfbc << data 926: 927: # Tell the font object about all this new stuff 928: tmp = { 929: 'BaseFont' => metrics.fontname, 930: 'Widths' => widthid.oid, 931: 'FirstChar' => first_char, 932: 'LastChar' => last_char, 933: 'FontDescriptor' => fdsc.oid 934: } 935: tmp['SubType'] = 'TrueType' if fontfile =~ /\.ttf/ 936: 937: tmp.each { |kk, vv| wfo.__send__("#{kk.downcase}=".intern, vv) } 938: end 939: 940: # Also set the differences here. Note that this means that these will 941: # take effect only the first time that a font is selected, else they 942: # are ignored. 943: metrics.differences = encoding_diff unless encoding_diff.nil? 944: metrics.encoding = encoding_name 945: metrics 946: end
Loads the font metrics. This is now thread-safe.
# File lib/pdf/writer.rb, line 744 744: def load_font_metrics(font) 745: metrics = PDF::Writer::FontMetrics.open(font) 746: @mutex.synchronize do 747: @fonts[font] = metrics 748: @fonts[font].font_num = @fonts.size 749: end 750: metrics 751: end
# File lib/pdf/writer.rb, line 2207 2207: def page_number_search(condition, scheme) 2208: res = nil 2209: scheme.each { |page, value| res = page if value[:stop] == condition } 2210: res 2211: end
# File lib/pdf/writer.rb, line 1346 1346: def parse_tag_params(params) 1347: params ||= "" 1348: ph = {} 1349: params.scan(TAG_PARAM_RE) do |param| 1350: ph[param[0]] = param[1] || param[2] || param[3] 1351: end 1352: ph 1353: end
Restore the state at the end of a page.
# File lib/pdf/writer.rb, line 1734 1734: def reset_state_at_page_finish 1735: add_content("\nQ" * @state_stack.size) 1736: 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, line 1709 1709: def reset_state_at_page_start 1710: @state_stack.each do |state| 1711: fill_color! state.fill_color 1712: stroke_color! state.stroke_color 1713: text_render_style! state.text_render_style 1714: stroke_style! state.stroke_style 1715: add_content("\nq") 1716: end 1717: end
# File lib/pdf/writer.rb, line 1934 1934: def restore_margins_after_columns 1935: @left_margin = @columns[:left] 1936: @right_margin = @columns[:right] 1937: @top_margin = @columns[:top] 1938: @bottom_margin = @columns[:bottom] 1939: 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, line 1057 1057: def text_end_position(x, y, angle, size, wa, text) 1058: width = text_width(text, size) 1059: width += wa * (text.count(" ")) 1060: rad = PDF::Math.deg2rad(angle) 1061: [Math.cos(rad) * width + x, ((-Math.sin(rad)) * width + y)] 1062: end
Checks if text contains a control tag at pos. Control tags are XML-like tags that contain tag information.
<b>: | Adds b to the end of the current text state. If this is the closing tag, </b>, b is removed from the end of the current text state. |
<i>: | Adds i to the end of the current text state. If this is the closing tag, </i, i is removed from the end of the current text state. |
<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. |
<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. |
<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 </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.
: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. |
:start: | The callback has been started. This applies either when the callback is a stand-alone callback (<C:TAG/>) or the opening tag of a paired tag (<c:TAG>). |
:end: | The callback has been manually terminated with a closing tag (</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.
<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". |
<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". |
<c:uline>: | underlines the specified text. |
<C:bullet>: | Draws a solid bullet at the tag position. |
<C:disc>: | Draws a disc bullet at the tag position. |
# File lib/pdf/writer.rb, line 1180 1180: def text_tags(text, pos, font_change, final = false, x = 0, y = 0, size = 0, angle = 0, word_space_adjust = 0) 1181: tag_size = 0 1182: 1183: tag_match = %r!^<(/)?([^>]+)>!.match(text[pos..-1]) 1184: 1185: if tag_match 1186: closed, tag_name = tag_match.captures 1187: cts = @current_text_state # Alias for shorter lines. 1188: tag_size = tag_name.size + 2 + (closed ? 1 : 0) 1189: 1190: case tag_name 1191: when %r{^(?:b|strong)$}o 1192: if closed 1193: cts.slice!(-1, 1) if ?b == cts[-1] 1194: else 1195: cts << ?b 1196: end 1197: when %r{^(?:i|em)$}o 1198: if closed 1199: cts.slice!(-1, 1) if ?i == cts[-1] 1200: else 1201: cts << ?i 1202: end 1203: when %r{^r:}o 1204: _match = MATCH_TAG_REPLACE_RE.match(tag_name) 1205: if _match.nil? 1206: warn PDF::Writer::Lang[:callback_warning] % [ 'r:', tag_name ] 1207: tag_size = 0 1208: else 1209: func = _match.captures[0] 1210: params = parse_tag_params(_match.captures[1] || "") 1211: tag = TAGS[:replace][func] 1212: 1213: if tag 1214: text[pos, tag_size] = tag[self, params] 1215: tag_size, text, font_change, x, y = text_tags(text, pos, 1216: font_change, 1217: final, x, y, size, 1218: angle, 1219: word_space_adjust) 1220: else 1221: warn PDF::Writer::Lang[:callback_warning] % [ 'r:', func ] 1222: tag_size = 0 1223: end 1224: end 1225: when %r{^C:}o 1226: _match = MATCH_TAG_DRAW_ONE_RE.match(tag_name) 1227: if _match.nil? 1228: warn PDF::Writer::Lang[:callback_warning] % [ 'C:', tag_name ] 1229: tag_size = 0 1230: else 1231: func = _match.captures[0] 1232: params = parse_tag_params(_match.captures[1] || "") 1233: tag = TAGS[:single][func] 1234: 1235: if tag 1236: font_change = false 1237: 1238: if final 1239: # Only call the function if this is the "final" call. Assess 1240: # the text position. Calculate the text width to this point. 1241: x, y = text_end_position(x, y, angle, size, word_space_adjust, 1242: text[0, pos]) 1243: info = { 1244: :x => x, 1245: :y => y, 1246: :angle => angle, 1247: :params => params, 1248: :status => :start, 1249: :cbid => @callbacks.size + 1, 1250: :callback => func, 1251: :height => font_height(size), 1252: :descender => font_descender(size) 1253: } 1254: 1255: ret = tag[self, info] 1256: if ret.kind_of?(Hash) 1257: ret.each do |rk, rv| 1258: x = rv if rk == :x 1259: y = rv if rk == :y 1260: font_change = rv if rk == :font_change 1261: end 1262: end 1263: end 1264: else 1265: warn PDF::Writer::Lang[:callback_Warning] % [ 'C:', func ] 1266: tag_size = 0 1267: end 1268: end 1269: when %r{^c:}o 1270: _match = MATCH_TAG_DRAW_PAIR_RE.match(tag_name) 1271: 1272: if _match.nil? 1273: warn PDF::Writer::Lang[:callback_warning] % [ 'c:', tag_name ] 1274: tag_size = 0 1275: else 1276: func = _match.captures[0] 1277: params = parse_tag_params(_match.captures[1] || "") 1278: tag = TAGS[:pair][func] 1279: 1280: if tag 1281: font_change = false 1282: 1283: if final 1284: # Only call the function if this is the "final" call. Assess 1285: # the text position. Calculate the text width to this point. 1286: x, y = text_end_position(x, y, angle, size, word_space_adjust, 1287: text[0, pos]) 1288: info = { 1289: :x => x, 1290: :y => y, 1291: :angle => angle, 1292: :params => params, 1293: } 1294: 1295: if closed 1296: info[:status] = :end 1297: info[:cbid] = @callbacks.size 1298: 1299: ret = tag[self, info] 1300: 1301: if ret.kind_of?(Hash) 1302: ret.each do |rk, rv| 1303: x = rv if rk == :x 1304: y = rv if rk == :y 1305: font_change = rv if rk == :font_change 1306: end 1307: end 1308: 1309: @callbacks.pop 1310: else 1311: info[:status] = :start 1312: info[:cbid] = @callbacks.size + 1 1313: info[:tag] = tag 1314: info[:callback] = func 1315: info[:height] = font_height(size) 1316: info[:descender] = font_descender(size) 1317: 1318: @callbacks << info 1319: 1320: ret = tag[self, info] 1321: 1322: if ret.kind_of?(Hash) 1323: ret.each do |rk, rv| 1324: x = rv if rk == :x 1325: y = rv if rk == :y 1326: font_change = rv if rk == :font_change 1327: end 1328: end 1329: end 1330: end 1331: else 1332: warn PDF::Writer::Lang[:callback_warning] % [ 'c:', func ] 1333: tag_size = 0 1334: end 1335: end 1336: else 1337: tag_size = 0 1338: end 1339: end 1340: [ tag_size, text, font_change, x, y ] 1341: end