JezK
Edit File: rbpdf.rb
# coding: ASCII-8BIT #============================================================+ # File name : rbpdf.rb # Begin : 2002-08-03 # Last Update : 2010-06-02 # Author : Nicola Asuni # Version : 5.2.000 # License : GNU LGPL (http://www.gnu.org/copyleft/lesser.html) # ---------------------------------------------------------------------------- # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 2.1 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # ---------------------------------------------------------------------------- # # Description : This is a Ruby class for generating PDF files # on-the-fly without requiring external # extensions. # # IMPORTANT: # This class is an extension and improvement of the Public Domain # FPDF class by Olivier Plathey (http://www.fpdf.org). # # Main changes by Nicola Asuni: # Ruby porting; # UTF-8 Unicode support; # code refactoring; # source code clean up; # code style and formatting; # source code documentation using phpDocumentor (www.phpdoc.org); # All ISO page formats were included; # image scale factor; # includes methods to parse and printsome XHTML code, supporting the following elements: h1, h2, h3, h4, h5, h6, b, u, i, a, img, p, br, strong, em, font, blockquote, li, ul, ol, hr, td, th, tr, table, sup, sub, small; # includes a method to print various barcode formats using an improved version of "Generic Barcode Render Class" by Karim Mribti (http://www.mribti.com/barcode/) (require GD library: http://www.boutell.com/gd/); # defines standard Header() and Footer() methods. # # Ported to Ruby by Ed Moss 2007-08-06 # #============================================================+ require "rbpdf/version" require 'htmlentities' require 'rbpdf-font' require 'erb' begin # RMagick 2.14.0 # [DEPRECATION] requiring "RMagick" is deprecated. Use "rmagick" instead # https://github.com/gemhome/rmagick/pull/141 require 'rmagick' unless Object.const_defined?(:Magick) rescue LoadError # RMagick is not available end begin require 'RMagick' unless Object.const_defined?(:Magick) rescue LoadError # RMagick is not available end require 'core/rmagick' # Needed to run the test suite outside of a Rails environment. require 'rubygems' if RUBY_VERSION < '1.9' # Ruby 1.8.7 require 'action_view' require 'tempfile' require 'uri' require 'net/http' # # RBPDF Class. # PDF_PRODUCER = 'RBPDF 5.2.000' # == This is a Ruby class for generating PDF files on-the-fly without requiring external extensions. # * This class is a Ruby port of the TCPDF class by Nicola Asuni (http://www.tcpdf.org). # * This class is an extension and improvement of the FPDF class by Olivier Plathey (http://www.fpdf.org). # * This version contains some changes: [porting to Ruby, support for UTF-8 Unicode, code style and formatting, php documentation (www.phpdoc.org), ISO page formats, minor improvements, image scale factor] # * TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten. # * To add your own TTF fonts please read /fonts/README.TXT # @name:: RBPDF # @@version:: 5.1.002 # @author:: Nicola Asuni # @license:: http://www.gnu.org/copyleft/lesser.html LGPL 2.1 # class RBPDFError < StandardError end class RBPDF include ActionView::Helpers include Rbpdf require 'unicode_data.rb' require 'htmlcolors.rb' include Unicode_data include Html_colors def logger if defined? Rails.logger Rails.logger else # This particular error will occur when the test suite is run from outside a Rails environment. # We use the standard Ruby stdout logger in that case. require 'logger' return Logger.new(STDOUT) end end @@version = "5.1.002" @@fpdf_charwidths = {} cattr_accessor :k_cell_height_ratio @@k_cell_height_ratio = 1.25 cattr_accessor :k_blank_image @@k_blank_image = "" cattr_accessor :k_small_ratio @@k_small_ratio = 2/3.0 cattr_accessor :k_path_cache cattr_accessor :k_path_main cattr_accessor :k_path_url @@k_path_images = "" @@k_thai_topchars = nil cattr_accessor :decoder attr_accessor :barcode attr_accessor :buffer attr_accessor :diffs attr_accessor :color_flag attr_accessor :default_font attr_accessor :draw_color attr_accessor :encoding attr_accessor :fill_color attr_accessor :fonts attr_accessor :font_family attr_accessor :font_files cattr_accessor :k_path_fonts attr_accessor :font_style attr_accessor :font_size_pt attr_accessor :header_width attr_accessor :header_logo attr_accessor :header_logo_width attr_accessor :header_title attr_accessor :header_string attr_accessor :images attr_accessor :img_scale attr_accessor :in_footer attr_accessor :is_unicode attr_accessor :lasth attr_accessor :links attr_accessor :listordered attr_accessor :listcount attr_accessor :lispacer attr_accessor :n attr_accessor :offsets attr_accessor :page attr_accessor :pages attr_accessor :pdf_version attr_accessor :print_header attr_accessor :print_footer attr_accessor :state attr_accessor :text_color attr_accessor :underline attr_accessor :diskcache attr_accessor :cache_file_length attr_accessor :prev_pages # # This is the class constructor. # It allows to set up the page format, the orientation and # the measure unit used in all the methods (except for the font sizes). # @since 1.0 # [@param string :orientation] # page orientation. Possible values are (case insensitive): # * P or Portrait (default) # * L or Landscape # [@param string :unit] # User measure unit. Possible values are: # * pt: point # * mm: millimeter (default) # * cm: centimeter # * in: inch # A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit. # [@param mixed :format] # The format used for pages. It can be either: <b>A string indicating the page format:</b> # * 4A0,2A0,A0,A1,A2,A3,A4 (default),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,EXECUTIVE,FOLIO # <b>An array containing page measures and advanced options:</b> see setPageFormat() # [@param boolean :unicode] TRUE means that the input text is unicode (default = true) # [@param String :encoding] charset encoding; default is UTF-8 # [@param boolean :diskcache] if TRUE reduce the RAM memory usage by caching temporary data on filesystem (slower). # [@access public] # def initialize(orientation = 'P', unit = 'mm', format = 'A4', unicode = true, encoding = "UTF-8", diskcache = false) # Set internal character encoding to ASCII# #FIXME 2007-05-25 (EJM) Level=0 - # if (respond_to?("mb_internal_encoding") and mb_internal_encoding()) # @internal_encoding = mb_internal_encoding(); # mb_internal_encoding("ASCII"); # } if defined? Rails.root @@k_path_cache = Rails.root.join('tmp').to_s @@k_path_main = Rails.root.join('tmp').to_s @@k_path_url = Rails.root.join('tmp').to_s else # This particular error will occur when the test suite is run from outside a Rails environment. # We want to use the system's temp directory in that case. require 'tmpdir' @@k_path_cache = Dir.tmpdir @@k_path_main = Dir.tmpdir @@k_path_url = Dir.tmpdir end @@k_path_fonts = RBPDFFontDescriptor.getfontpath # set disk caching @diskcache = diskcache ? true : false # set language direction @rtl = false @tmprtl = false @x ||= 0 @y ||= 0 ####################### @offsets ||= [] @alias_nb_pages = '{nb}' @alias_num_page = '{pnb}' @img_scale ||= 1 @r_margin ||= 0 @l_margin ||= 0 @original_r_margin ||= nil @original_l_margin ||= nil @t_margin ||= nil @b_margin ||= nil @auto_page_break ||= nil @page_annots ||= [] @header_font ||= ['', '', 10] @footer_font ||= ['', '', 8] @l ||= {} @header_margin ||= 10 @footer_margin ||= 10 @barcode ||= false @print_header ||= true @print_footer ||= true @header_width ||= 0 @header_logo ||= "" @header_logo_width ||= 30 @header_title ||= "" @header_string ||= "" @listordered ||= [] @listcount ||= [] @listnum ||= 0 @listindent ||= 0 @listindentlevel ||= 0 @lispacer ||= "" # bookmark @outlines ||= [] # --- javascript and form --- @javascript ||= '' @js_objects ||= [] @dpi = 72.0 @newpagegroup ||= [] @pagegroups ||= {} @currpagegroup ||= nil @visibility ||= 'all' @cell_height_ratio = @@k_cell_height_ratio @viewer_preferences ||= {} @intmrk ||= [] @cntmrk ||= [] @footerpos ||= [] @footerlen ||= [] @newline ||= true @endlinex ||= 0 @open_marked_content ||= false @htmlvspace ||= 0 @spot_colors ||= {} @lisymbol ||= '' @epsmarker ||= 'x#!#EPS#!#x' @transfmatrix ||= [] @transfmatrix_key ||= 0 @booklet ||= false @feps ||= 0.005 @tagvspaces ||= {} @customlistindent ||= -1 @opencell = true @embeddedfiles ||= {} @html_link_color_array ||= [0, 0, 255] @html_link_font_style ||= 'U' @numpages ||= 0 @pagelen ||= [] @numimages ||= 0 @imagekeys ||= [] @bufferlen ||= 0 @numfonts ||= 0 @fontkeys ||= [] @font_obj_ids ||= {} @pageopen ||= [] # Store the fage status (true when opened, false when closed). @default_monospaced_font = 'courier' @cache_file_length = {} @thead ||= '' @thead_margins ||= {} @cache_utf8_string_to_array = {} @cache_maxsize_utf8_string_to_array = 8 @cache_size_utf8_string_to_array = 0 @signature_data ||= {} @sig_annot_ref ||= '***SIGANNREF*** 0 R' @page_obj_id ||= [] @embedded_start_obj_id ||= 100000 @form_obj_id ||= [] @apxo_start_obj_id ||= 400000 @apxo_obj_id ||= 400000 @annotation_fonts ||= {} @radiobutton_groups ||= [] @radio_groups ||= [] @textindent ||= 0 @start_transaction_y ||= 0 @in_thead ||= false @columns ||= [] @num_columns ||= 0 @current_column ||= 0 @column_start_page ||= 0 @cur_orientation ||= nil # Text rendering mode: # 0 = Fill text; # 1 = Stroke text; # 2 = Fill, then stroke text; # 3 = Neither fill nor stroke text (invisible); # 4 = Fill text and add to path for clipping; # 5 = Stroke text and add to path for clipping; # 6 = Fill, then stroke text and add to path for clipping; # 7 = Add text to path for clipping. @textrendermode ||= 0 @textstrokewidth ||= 0 @pdfunit ||= 'mm' @tocpage ||= false ####################### #Some checks dochecks(); begin @@decoder = HTMLEntities.new rescue @@decoder = nil end #Initialization of properties @is_unicode = unicode @page ||= 0 @transfmrk ||= [] @pagedim ||= [] @n ||= 2 if @diskcache @buffer ||= nil else @buffer ||= '' end @pages ||= [] @prev_pages ||= [] @state ||= 0 @fonts ||= {} @font_files ||= {} @diffs ||= [] @images ||= {} @links ||= [] # array of internal links @html_anchor ||= nil @html_anchors ||= {} @html_anchor_links ||= {} @gradients ||= [] @in_footer ||= false @lasth ||= 0 @font_family ||= 'helvetica' @font_style ||= '' @font_size_pt ||= 12 @font_subsetting ||= true @underline ||= false @overline ||= false @linethrough ||= false @draw_color ||= '0 G' @fill_color ||= '0 g' @text_color ||= '0 g' @color_flag ||= false @title ||= nil @author ||= nil @subject ||= nil @keywords ||= nil @creator ||= nil # encryption values @encrypted ||= false @last_enc_key ||= '' # Standard Unicode fonts @core_fonts = { 'courier'=>'Courier', 'courierB'=>'Courier-Bold', 'courierI'=>'Courier-Oblique', 'courierBI'=>'Courier-BoldOblique', 'helvetica'=>'Helvetica', 'helveticaB'=>'Helvetica-Bold', 'helveticaI'=>'Helvetica-Oblique', 'helveticaBI'=>'Helvetica-BoldOblique', 'times'=>'Times-Roman', 'timesB'=>'Times-Bold', 'timesI'=>'Times-Italic', 'timesBI'=>'Times-BoldItalic', 'symbol'=>'Symbol', 'zapfdingbats'=>'ZapfDingbats'} # Set scale factor setPageUnit(unit) # set page format and orientation setPageFormat(format, orientation) # Page margins (1 cm) margin = 28.35/@k SetMargins(margin, margin) #Interior cell margin (1 mm) @c_margin = margin / 10 #Line width (0.2 mm) @line_width = 0.57 / @k @linestyle_width ||= sprintf('%.2f w', (@line_width * @k)) @linestyle_cap ||= '0 J' @linestyle_join ||= '0 j' @linestyle_dash ||= '[] 0 d' #Automatic page break SetAutoPageBreak(true, 2 * margin) #Full width display mode SetDisplayMode('fullwidth') #Compression SetCompression(true) #Set default PDF version number @pdf_version ||= "1.7" @encoding = encoding @href ||= {} @fontlist ||= [] getFontsList() @fgcolor = ActiveSupport::OrderedHash.new @fgcolor['R'] = 0 @fgcolor['G'] = 0 @fgcolor['B'] = 0 @strokecolor = ActiveSupport::OrderedHash.new @strokecolor['R'] = 0 @strokecolor['G'] = 0 @strokecolor['B'] = 0 @bgcolor = ActiveSupport::OrderedHash.new @bgcolor['R'] = 255 @bgcolor['G'] = 255 @bgcolor['B'] = 255 @extgstates ||= [] # user's rights @sign ||= false @ur = false @ur_document = "/FullSave" @ur_annots = "/Create/Delete/Modify/Copy/Import/Export" @ur_form = "/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate" @ur_signature = "/Modify" # set default JPEG quality @jpeg_quality ||= 75 # initialize some settings # utf8Bidi([''], '') # set default font SetFont(@font_family, @font_style, @font_size_pt) @annots_start_obj_id ||= 200000 @annot_obj_id ||= @annots_start_obj_id @curr_annot_obj_id ||= @annots_start_obj_id @apxo_obj_id ||= @apxo_start_obj_id @objcopy ||= nil end # # Set the units of measure for the document. # [@param string :unit] # User measure unit. Possible values are: # * pt: point # * mm: millimeter (default) # * cm: centimeter # * in: inch # A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit. # [@access public] # [@since 3.0.015 (2008-06-06)] # def setPageUnit(unit) unit = unit.downcase # Set scale factor case unit when 'px', 'pt'; @k=1.0 # points when 'mm'; @k = @dpi / 25.4 # millimeters when 'cm'; @k = @dpi / 2.54 # centimeters when 'in'; @k = @dpi # inches # unsupported unit else Error("Incorrect unit: #{unit}") end @pdfunit = unit unless @cur_orientation.nil? setPageOrientation(@cur_orientation) end end alias_method :set_page_unit, :setPageUnit # # Get page dimensions from fromat name. # [@param mixed :format] # The format name. It can be: # * 4A0,2A0,A0,A1,A2,A3,A4 (default),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,EXECUTIVE,FOLIO # [@return array] containing page width and height in points # [@access public] # [@since 5.0.010 (2010-05-17)] # def getPageSizeFromFormat(format) # Page formats (45 standard ISO paper formats and 4 american common formats). # Paper cordinates are calculated in this way: (inches * 72) where (1 inch = 2.54 cm) case format.upcase when '4A0'; pf = [4767.87,6740.79] when '2A0'; pf = [3370.39,4767.87] when 'A0'; pf = [2383.94,3370.39] when 'A1'; pf = [1683.78,2383.94] when 'A2'; pf = [1190.55,1683.78] when 'A3'; pf = [841.89,1190.55] when 'A4'; pf = [595.28,841.89] when 'A5'; pf = [419.53,595.28] when 'A6'; pf = [297.64,419.53] when 'A7'; pf = [209.76,297.64] when 'A8'; pf = [147.40,209.76] when 'A9'; pf = [104.88,147.40] when 'A10'; pf = [73.70,104.88] when 'B0'; pf = [2834.65,4008.19] when 'B1'; pf = [2004.09,2834.65] when 'B2'; pf = [1417.32,2004.09] when 'B3'; pf = [1000.63,1417.32] when 'B4'; pf = [708.66,1000.63] when 'B5'; pf = [498.90,708.66] when 'B6'; pf = [354.33,498.90] when 'B7'; pf = [249.45,354.33] when 'B8'; pf = [175.75,249.45] when 'B9'; pf = [124.72,175.75] when 'B10'; pf = [87.87,124.72] when 'C0'; pf = [2599.37,3676.54] when 'C1'; pf = [1836.85,2599.37] when 'C2'; pf = [1298.27,1836.85] when 'C3'; pf = [918.43,1298.27] when 'C4'; pf = [649.13,918.43] when 'C5'; pf = [459.21,649.13] when 'C6'; pf = [323.15,459.21] when 'C7'; pf = [229.61,323.15] when 'C8'; pf = [161.57,229.61] when 'C9'; pf = [113.39,161.57] when 'C10'; pf = [79.37,113.39] when 'RA0'; pf = [2437.80,3458.27] when 'RA1'; pf = [1729.13,2437.80] when 'RA2'; pf = [1218.90,1729.13] when 'RA3'; pf = [864.57,1218.90] when 'RA4'; pf = [609.45,864.57] when 'SRA0'; pf = [2551.18,3628.35] when 'SRA1'; pf = [1814.17,2551.18] when 'SRA2'; pf = [1275.59,1814.17] when 'SRA3'; pf = [907.09,1275.59] when 'SRA4'; pf = [637.80,907.09] when 'LETTER'; pf = [612.00,792.00] when 'LEGAL'; pf = [612.00,1008.00] when 'EXECUTIVE'; pf = [521.86,756.00] when 'FOLIO'; pf = [612.00,936.00] else pf = [595.28,841.89] # DEFAULT A4 end return pf end alias_method :get_page_size_from_format, :getPageSizeFromFormat # # Change the format of the current page # [@param mixed :format] # The format used for pages. It can be either: <b>A string indicating the page format:</b> # * 4A0,2A0,A0,A1,A2,A3,A4 (default),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,EXECUTIVE,FOLIO # <b>An array containing page measures and advanced options:</b> # ['format'] = page format name (one of the above) # ['Rotate'] : The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90. # ['PZ'] : The page's preferred zoom (magnification) factor. # ['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed: # ['MediaBox']['llx'] : lower-left x coordinate in points # ['MediaBox']['lly'] : lower-left y coordinate in points # ['MediaBox']['urx'] : upper-right x coordinate in points # ['MediaBox']['ury'] : upper-right y coordinate in points # ['CropBox'] : the visible region of default user space: # ['CropBox']['llx'] : lower-left x coordinate in points # ['CropBox']['lly'] : lower-left y coordinate in points # ['CropBox']['urx'] : upper-right x coordinate in points # ['CropBox']['ury'] : upper-right y coordinate in points # ['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment: # ['BleedBox']['llx'] : lower-left x coordinate in points # ['BleedBox']['lly'] : lower-left y coordinate in points # ['BleedBox']['urx'] : upper-right x coordinate in points # ['BleedBox']['ury'] : upper-right y coordinate in points # ['TrimBox'] : the intended dimensions of the finished page after trimming: # ['TrimBox']['llx'] : lower-left x coordinate in points # ['TrimBox']['lly'] : lower-left y coordinate in points # ['TrimBox']['urx'] : upper-right x coordinate in points # ['TrimBox']['ury'] : upper-right y coordinate in points # ['ArtBox'] : the extent of the page's meaningful content: # ['ArtBox']['llx'] : lower-left x coordinate in points # ['ArtBox']['lly'] : lower-left y coordinate in points # ['ArtBox']['urx'] : upper-right x coordinate in points # ['ArtBox']['ury'] : upper-right y coordinate in points # ['BoxColorInfo'] :specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for each of the possible page boundaries other than the MediaBox: # ['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space. # ['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units # ['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed # ['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines # ['trans'] : the style and duration of the visual transition to use when moving from another page to the given page during a presentation # ['trans']['Dur'] : The page's display duration (also called its advance timing): the maximum length of time, in seconds, that the page shall be displayed during presentations before the viewer application shall automatically advance to the next page. # ['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade # ['trans']['D'] : The duration of the transition effect, in seconds. # ['trans']['Dm'] : (Split and Blinds transition styles only) The dimension in which the specified transition effect shall occur: H = Horizontal, V = Vertical. Default value: H. # ['trans']['M'] : (Split, Box and Fly transition styles only) The direction of motion for the specified transition effect: I = Inward from the edges of the page, O = Outward from the center of the pageDefault value: I. # ['trans']['Di'] : (Wipe, Glitter, Fly, Cover, Uncover and Push transition styles only) The direction in which the specified transition effect shall moves, expressed in degrees counterclockwise starting from a left-to-right direction. If the value is a number, it shall be one of: 0 = Left to right, 90 = Bottom to top (Wipe only), 180 = Right to left (Wipe only), 270 = Top to bottom, 315 = Top-left to bottom-right (Glitter only). If the value is a name, it shall be None, which is relevant only for the Fly transition when the value of SS is not 1.0. Default value: 0. # ['trans']['SS'] : (Fly transition style only) The starting or ending scale at which the changes shall be drawn. If M specifies an inward transition, the scale of the changes drawn shall progress from SS to 1.0 over the course of the transition. If M specifies an outward transition, the scale of the changes drawn shall progress from 1.0 to SS over the course of the transition. Default: 1.0. # ['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false. # [@param string :orientation] # page orientation. Possible values are (case insensitive): # * P or PORTRAIT (default) # * L or LANDSCAPE # [@access protected] # [@since 3.0.015 (2008-06-06)] # def setPageFormat(format, orientation="P") if !format.empty? and @pagedim[@page] # remove inherited values @pagedim[@page] = nil end if format.is_a?(String) # get page measures from format name pf = getPageSizeFromFormat(format) @fw_pt = pf[0] @fh_pt = pf[1] else # the boundaries of the physical medium on which the page shall be displayed or printed if format['MediaBox'] setPageBoxes(@page, 'MediaBox', format['MediaBox']['llx'], format['MediaBox']['lly'], format['MediaBox']['urx'], format['MediaBox']['ury']) @fw_pt = (format['MediaBox']['urx'] - format['MediaBox']['llx']) * @k @fh_pt = (format['MediaBox']['ury'] - format['MediaBox']['lly']) * @k else if format[0] and format[0].is_a?(Numeric) and format[1] and format[1].is_a?(Numeric) pf = [format[0], format[1]] else if format['format'].nil? # default value format['format'] = 'A4' end pf = getPageSizeFromFormat(format['format']) end @fw_pt = pf[0] @fh_pt = pf[1] setPageBoxes(@page, 'MediaBox', 0, 0, @fw_pt, @fh_pt, true) end # the visible region of default user space if format['CropBox'] setPageBoxes(@page, 'CropBox', format['CropBox']['llx'], format['CropBox']['lly'], format['CropBox']['urx'], format['CropBox']['ury']) end # the region to which the contents of the page shall be clipped when output in a production environment if format['BleedBox'] setPageBoxes(@page, 'BleedBox', format['BleedBox']['llx'], format['BleedBox']['lly'], format['BleedBox']['urx'], format['BleedBox']['ury']) end # the intended dimensions of the finished page after trimming if format['TrimBox'] setPageBoxes(@page, 'TrimBox', format['TrimBox']['llx'], format['TrimBox']['lly'], format['TrimBox']['urx'], format['TrimBox']['ury']) end # the page's meaningful content (including potential white space) if format['ArtBox'] setPageBoxes(@page, 'ArtBox', format['ArtBox']['llx'], format['ArtBox']['lly'], format['ArtBox']['urx'], format['ArtBox']['ury']) end # specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries if format['BoxColorInfo'] @pagedim[@page]['BoxColorInfo'] = format['BoxColorInfo'] end if format['Rotate'] and ((format['Rotate'] % 90) == 0) # The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90. @pagedim[@page]['Rotate'] = format['Rotate'].to_i end if format['PZ'] # The page's preferred zoom (magnification) factor @pagedim[@page]['PZ'] = format['PZ'].to_f end if format['trans'] @pagedim[@page]['trans'] = {} # The style and duration of the visual transition to use when moving from another page to the given page during a presentation if format['trans']['Dur'] # The page's display duration @pagedim[@page]['trans']['Dur'] = format['trans']['Dur'].to_f end stansition_styles = ['Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade'] if format['trans']['S'] and stansition_styles.include?(format['trans']['S']) # The transition style that shall be used when moving to this page from another during a presentation @pagedim[@page]['trans']['S'] = format['trans']['S'] valid_effect = ['Split', 'Blinds'] valid_vals = ['H', 'V'] if format['trans']['Dm'] and valid_effect.include?(format['trans']['S']) and valid_vals.include?(format['trans']['Dm']) @pagedim[@page]['trans']['Dm'] = format['trans']['Dm'] end valid_effect = ['Split', 'Box', 'Fly'] valid_vals = ['I', 'O'] if format['trans']['M'] and valid_effect.include?(format['trans']['S']) and valid_vals.include?(format['trans']['M']) @pagedim[@page]['trans']['M'] = format['trans']['M'] end valid_effect = ['Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push'] if format['trans']['Di'] and valid_effect.include?(format['trans']['S']) if (((format['trans']['Di'] == 90) or (format['trans']['Di'] == 180)) and (format['trans']['S'] == 'Wipe')) or ((format['trans']['Di'] == 315) and (format['trans']['S'] == 'Glitter')) or ((format['trans']['Di'] == 0) or (format['trans']['Di'] == 270)) @pagedim[@page]['trans']['Di'] = format['trans']['Di'].to_i end end if format['trans']['SS'] and (format['trans']['S'] == 'Fly') @pagedim[@page]['trans']['SS'] = format['trans']['SS'].to_f end if format['trans']['B'] and (format['trans']['B'] === true) and (format['trans']['S'] == 'Fly') @pagedim[@page]['trans']['B'] = 'true' end else @pagedim[@page]['trans']['S'] = 'R' end if format['trans']['D'] # The duration of the transition effect, in seconds @pagedim[@page]['trans']['D'] = format['trans']['D'].to_f else @pagedim[@page]['trans']['D'] = 1 end end end setPageOrientation(orientation) end protected :setPageFormat # # Set page boundaries. # [@param int :page] page number # [@param string :type] # valid values are: # * 'MediaBox' : the boundaries of the physical medium on which the page shall be displayed or printed # * 'CropBox' : the visible region of default user space # * 'BleedBox' : the region to which the contents of the page shall be clipped when output in a production environment # * 'TrimBox' : the intended dimensions of the finished page after trimming # * 'ArtBox' : the page's meaningful content (including potential white space). # [@param float :llx] lower-left x coordinate in user units # [@param float :lly] lower-left y coordinate in user units # [@param float :urx] upper-right x coordinate in user units # [@param float :ury] upper-right y coordinate in user units # [@param boolean :points] if true uses user units as unit of measure, if false uses PDF points # [@access public] # [@since 5.0.010 (2010-05-17)] # def setPageBoxes(page, type, llx, lly, urx, ury, points=false) pageboxes = ['MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox'] unless pageboxes.include?(type) return end if @pagedim[page].nil? # initialize array @pagedim[page] = {} end points ? k = 1 : k = @k @pagedim[page][type] = {} @pagedim[page][type]['llx'] = llx * k @pagedim[page][type]['lly'] = lly * k @pagedim[page][type]['urx'] = urx * k @pagedim[page][type]['ury'] = ury * k end alias_method :set_page_boxes, :setPageBoxes # # Swap X and Y coordinates of page boxes (change page boxes orientation). # [@param int :page] page number # [@access protected] # [@since 5.0.010 (2010-05-17)] # def swapPageBoxCoordinates(page) pageboxes = ['MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox'] pageboxes.each {|type| # swap X and Y coordinates if @pagedim[page][type] tmp = @pagedim[page][type]['llx'] @pagedim[page][type]['llx'] = @pagedim[page][type]['lly'] @pagedim[page][type]['lly'] = tmp tmp = @pagedim[page][type]['urx'] @pagedim[page][type]['urx'] = @pagedim[page][type]['ury'] @pagedim[page][type]['ury'] = tmp end } end protected :swapPageBoxCoordinates # # Set page orientation. # [@param string :orientation] # page orientation. Possible values are (case insensitive): # * P or PORTRAIT (default) # * L or LANDSCAPE # [@param boolean :autopagebreak] Boolean indicating if auto-page-break mode should be on or off. # [@param float :bottommargin] bottom margin of the page. # [@access public] # [@since 3.0.015 (2008-06-06)] # def setPageOrientation(orientation, autopagebreak='', bottommargin='') if @pagedim[@page].nil? or @pagedim[@page]['MediaBox'].nil? # the boundaries of the physical medium on which the page shall be displayed or printed setPageBoxes(@page, 'MediaBox', 0, 0, @fw_pt, @fh_pt, true) end if @pagedim[@page]['CropBox'].nil? # the visible region of default user space setPageBoxes(@page, 'CropBox', @pagedim[@page]['MediaBox']['llx'], @pagedim[@page]['MediaBox']['lly'], @pagedim[@page]['MediaBox']['urx'], @pagedim[@page]['MediaBox']['ury'], true) end if @pagedim[@page]['BleedBox'].nil? # the region to which the contents of the page shall be clipped when output in a production environment setPageBoxes(@page, 'BleedBox', @pagedim[@page]['CropBox']['llx'], @pagedim[@page]['CropBox']['lly'], @pagedim[@page]['CropBox']['urx'], @pagedim[@page]['CropBox']['ury'], true) end if @pagedim[@page]['TrimBox'].nil? # the intended dimensions of the finished page after trimming setPageBoxes(@page, 'TrimBox', @pagedim[@page]['CropBox']['llx'], @pagedim[@page]['CropBox']['lly'], @pagedim[@page]['CropBox']['urx'], @pagedim[@page]['CropBox']['ury'], true) end if @pagedim[@page]['ArtBox'].nil? # the page's meaningful content (including potential white space) setPageBoxes(@page, 'ArtBox', @pagedim[@page]['CropBox']['llx'], @pagedim[@page]['CropBox']['lly'], @pagedim[@page]['CropBox']['urx'], @pagedim[@page]['CropBox']['ury'], true) end if @pagedim[@page]['Rotate'].nil? # The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90. @pagedim[@page]['Rotate'] = 0 end if @pagedim[@page]['PZ'].nil? # The page's preferred zoom (magnification) factor @pagedim[@page]['PZ'] = 1 end if @fw_pt > @fh_pt # landscape default_orientation = 'L' else # portrait default_orientation = 'P' end valid_orientations = ['P', 'L'] if orientation.empty? orientation = default_orientation else orientation = orientation[0, 1].upcase end if valid_orientations.include?(orientation) and (orientation != default_orientation) @cur_orientation = orientation @w_pt = @fh_pt @h_pt = @fw_pt else @cur_orientation = default_orientation @w_pt = @fw_pt @h_pt = @fh_pt end if ((@pagedim[@page]['MediaBox']['urx'] - @h_pt).abs < @feps) and ((@pagedim[@page]['MediaBox']['ury'] - @w_pt).abs < @feps) # swap X and Y coordinates (change page orientatio swapPageBoxCoordinates(@page) end @w = @w_pt / @k @h = @h_pt / @k if empty_string(autopagebreak) unless @auto_page_break.nil? autopagebreak = @auto_page_break else autopagebreak = true end end if empty_string(bottommargin) unless @b_margin.nil? bottommargin = @b_margin else # default value = 2 cm bottommargin = 2 * 28.35 / @k end end SetAutoPageBreak(autopagebreak, bottommargin) # store page dimensions @pagedim[@page]['w'] = @w_pt @pagedim[@page]['h'] = @h_pt @pagedim[@page]['wk'] = @w @pagedim[@page]['hk'] = @h @pagedim[@page]['tm'] = @t_margin @pagedim[@page]['bm'] = bottommargin @pagedim[@page]['lm'] = @l_margin @pagedim[@page]['rm'] = @r_margin @pagedim[@page]['pb'] = autopagebreak @pagedim[@page]['or'] = @cur_orientation @pagedim[@page]['olm'] = @original_l_margin @pagedim[@page]['orm'] = @original_r_margin @pagedim[@page] end alias_method :set_page_orientation, :setPageOrientation # # Enable or disable Right-To-Left language mode # [@param Boolean :enable] if true enable Right-To-Left language mode. # [@param Boolean :resetx] if true reset the X position on direction change. # [@access public] # [@since 2.0.000 (2008-01-03)] # def setRTL(enable, resetx=true) enable = enable ? true : false resetx = resetx and (enable != @rtl) @rtl = enable @tmprtl = false Ln(0) if resetx end alias_method :set_rtl, :setRTL # # Return the RTL status # [@return boolean] # [@access public] # [@since 4.0.012 (2008-07-24)] # def getRTL() return @rtl end alias_method :get_rtl, :getRTL # # Force temporary RTL language direction # [@param mixed :mode] can be false, 'L' for LTR or 'R' for RTL # [@access public] # [@since 2.1.000 (2008-01-09)] # def setTempRTL(mode) newmode = false case mode when 'ltr', 'LTR', 'L' newmode = 'L' if @rtl when 'rtl', 'RTL', 'R' newmode = 'R' if !@rtl end @tmprtl = newmode end alias_method :set_temp_rtl, :setTempRTL # # Return the current temporary RTL status # [@return boolean] true: RTL, false: LTR # [@access public] # [@since 4.8.014 (2009-11-04)] # def isRTLTextDir() if @tmprtl != false return @tmprtl == 'R' else return @rtl end end alias_method :is_rtl_text_dir, :isRTLTextDir # # Return the current text RTL status # [@return direction] 'R' : RTL, 'L' LTR # [@access protected] # def rtl_text_dir() if @tmprtl != false return @tmprtl # 'R' or 'L' else return @rtl ? 'R' : 'L' end end protected :rtl_text_dir # # Set the last cell height. # [@param float :h] cell height. # [@author] Nicola Asuni # [@access public] # [@since 1.53.0.TC034] # def setLastH(h) @lasth = h end alias_method :set_last_h, :setLastH # # Get the last cell height. # [@return] last cell height # [@access public] # [@since 4.0.017 (2008-08-05)] # def getLastH() return @lasth end alias_method :get_last_h, :getLastH # # Set the adjusting factor to convert pixels to user units. # [@param float :scale] adjusting factor to convert pixels to user units. # [@author] Nicola Asuni # [@access public] # [@since 1.5.2] # def setImageScale(scale) @img_scale = scale; end alias_method :set_image_scale, :setImageScale def SetImageScale(scale) warn "[DEPRECATION] 'SetImageScale' is deprecated. Please use 'set_image_scale' instead." setImageScale(scale) end # # Returns the adjusting factor to convert pixels to user units. # [@return float] adjusting factor to convert pixels to user units. # [@author] Nicola Asuni # [@access public] # [@since 1.5.2] # def getImageScale() return @img_scale; end alias_method :get_image_scale, :getImageScale def GetImageScale() warn "[DEPRECATION] 'GetImageScale' is deprecated. Please use 'get_image_scale' instead." getImageScale() end # # Returns an array of page dimensions: # @pagedim[@page]['w'] = page width in points # @pagedim[@page]['h'] = height in points # @pagedim[@page]['wk'] = page width in user units # @pagedim[@page]['hk'] = page height in user units # @pagedim[@page]['tm'] = top margin # @pagedim[@page]['bm'] = bottom margin # @pagedim[@page]['lm'] = left margin # @pagedim[@page]['rm'] = right margin # @pagedim[@page]['pb'] = auto page break # @pagedim[@page]['or'] = page orientation # @pagedim[@page]['olm'] = original left margin # @pagedim[@page]['orm'] = original right margin # @pagedim[@page]['Rotate'] = The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90. # @pagedim[@page]['PZ'] = The page's preferred zoom (magnification) factor. # @pagedim[@page]['trans'] : the style and duration of the visual transition to use when moving from another page to the given page during a presentation # @pagedim[@page]['trans']['Dur'] = The page's display duration (also called its advance timing): the maximum length of time, in seconds, that the page shall be displayed during presentations before the viewer application shall automatically advance to the next page. # @pagedim[@page]['trans']['S'] = transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade # @pagedim[@page]['trans']['D'] = The duration of the transition effect, in seconds. # @pagedim[@page]['trans']['Dm'] = (Split and Blinds transition styles only) The dimension in which the specified transition effect shall occur: H = Horizontal, V = Vertical. Default value: H. # @pagedim[@page]['trans']['M'] = (Split, Box and Fly transition styles only) The direction of motion for the specified transition effect: I = Inward from the edges of the page, O = Outward from the center of the pageDefault value: I. # @pagedim[@page]['trans']['Di'] = (Wipe, Glitter, Fly, Cover, Uncover and Push transition styles only) The direction in which the specified transition effect shall moves, expressed in degrees counterclockwise starting from a left-to-right direction. If the value is a number, it shall be one of: 0 = Left to right, 90 = Bottom to top (Wipe only), 180 = Right to left (Wipe only), 270 = Top to bottom, 315 = Top-left to bottom-right (Glitter only). If the value is a name, it shall be None, which is relevant only for the Fly transition when the value of SS is not 1.0. Default value: 0. # @pagedim[@page]['trans']['SS'] = (Fly transition style only) The starting or ending scale at which the changes shall be drawn. If M specifies an inward transition, the scale of the changes drawn shall progress from SS to 1.0 over the course of the transition. If M specifies an outward transition, the scale of the changes drawn shall progress from 1.0 to SS over the course of the transition. Default: 1.0. # @pagedim[@page]['trans']['B'] = (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false. # @pagedim[@page]['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed # @pagedim[@page]['MediaBox']['llx'] = lower-left x coordinate in points # @pagedim[@page]['MediaBox']['lly'] = lower-left y coordinate in points # @pagedim[@page]['MediaBox']['urx'] = upper-right x coordinate in points # @pagedim[@page]['MediaBox']['ury'] = upper-right y coordinate in points # @pagedim[@page]['CropBox'] : the visible region of default user space # @pagedim[@page]['CropBox']['llx'] = lower-left x coordinate in points # @pagedim[@page]['CropBox']['lly'] = lower-left y coordinate in points # @pagedim[@page]['CropBox']['urx'] = upper-right x coordinate in points # @pagedim[@page]['CropBox']['ury'] = upper-right y coordinate in points # @pagedim[@page]['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment # @pagedim[@page]['BleedBox']['llx'] = lower-left x coordinate in points # @pagedim[@page]['BleedBox']['lly'] = lower-left y coordinate in points # @pagedim[@page]['BleedBox']['urx'] = upper-right x coordinate in points # @pagedim[@page]['BleedBox']['ury'] = upper-right y coordinate in points # @pagedim[@page]['TrimBox'] : the intended dimensions of the finished page after trimming # @pagedim[@page]['TrimBox']['llx'] = lower-left x coordinate in points # @pagedim[@page]['TrimBox']['lly'] = lower-left y coordinate in points # @pagedim[@page]['TrimBox']['urx'] = upper-right x coordinate in points # @pagedim[@page]['TrimBox']['ury'] = upper-right y coordinate in points # @pagedim[@page]['ArtBox'] : the extent of the page's meaningful content # @pagedim[@page]['ArtBox']['llx'] = lower-left x coordinate in points # @pagedim[@page]['ArtBox']['lly'] = lower-left y coordinate in points # @pagedim[@page]['ArtBox']['urx'] = upper-right x coordinate in points # @pagedim[@page]['ArtBox']['ury'] = upper-right y coordinate in points # [@param int :pagenum] page number (empty = current page) # [@return] array of page dimensions. # [@author] Nicola Asuni # [@access public] # [@since 4.5.027 (2009-03-16)] # def getPageDimensions(pagenum=0) if !pagenum.is_a? Integer or pagenum.zero? pagenum = @page end return @pagedim[pagenum] end alias_method :get_page_dimensions, :getPageDimensions # # Returns the page width in units. # [@return int] page width. # [@author] Nicola Asuni # [@access public] # [@since 1.5.2] # def getPageWidth() return @w; end alias_method :get_page_width, :getPageWidth def GetPageWidth() warn "[DEPRECATION] 'GetPageWidth' is deprecated. Please use 'get_page_width' instead." getPageWidth() end # # Returns the page height in units. # [@return int] page height. # [@author] Nicola Asuni # [@access public] # [@since 1.5.2] # def getPageHeight() return @h; end alias_method :get_page_height, :getPageHeight def GetPageHeight() warn "[DEPRECATION] 'GetPageHeight' is deprecated. Please use 'get_page_height' instead." getPageHeight() end # # Returns the page break margin. # [@param int :pagenum] page number (empty = current page) # [@return int page] break margin. # [@author] Nicola Asuni # [@access publi]c # [@since 1.5.2] # [@see] getPageDimensions() # def getBreakMargin(pagenum=0) if !pagenum.is_a? Integer or pagenum.zero? return @b_margin end return @pagedim[pagenum]['bm'] end alias_method :get_break_margin, :getBreakMargin def GetBreakMargin(pagenum=0) warn "[DEPRECATION] 'GetBreakMargin' is deprecated. Please use 'get_break_margin' instead." getBreakMargin(pagenum) end # # Returns the scale factor (number of points in user unit). # [@return int] scale factor. # [@author] Nicola Asuni # [@access public] # [@since 1.5.2] # def getScaleFactor() return @k; end alias_method :get_scale_factor, :getScaleFactor def GetScaleFactor() warn "[DEPRECATION] 'GetScaleFactor' is deprecated. Please use 'get_scale_factor' instead." getScaleFactor() end # # Defines the left, top and right margins. # [@param float :left] Left margin. # [@param float :top] Top margin. # [@param float :right] Right margin. Default value is the left one. # [@param boolean :keepmargins] if true overwrites the default page margins # [@access public] # [@since 1.0] # [@see] SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak() # def SetMargins(left, top, right=-1, keepmargins=false) #Set left, top and right margins @l_margin = left @t_margin = top if (right == -1) right = left end @r_margin = right if keepmargins # overwrite original values @original_l_margin = @l_margin @original_r_margin = @r_margin end end alias_method :set_margins, :SetMargins # # Defines the left margin. The method can be called before creating the first page. If the current abscissa gets out of page, it is brought back to the margin. # [@param float :margin] The margin. # [@access public] # [@since 1.4] # [@see] SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins() # def SetLeftMargin(margin) #Set left margin @l_margin = margin if (@page > 0) and (@x < margin) @x = margin end end alias_method :set_left_margin, :SetLeftMargin # # Defines the top margin. The method can be called before creating the first page. # [@param float :margin] The margin. # [@access public] # [@since 1.5] # [@see] SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins() # def SetTopMargin(margin) #Set top margin @t_margin = margin if (@page > 0) and (@y < margin) @y = margin end end alias_method :set_top_margin, :SetTopMargin # # Defines the right margin. The method can be called before creating the first page. # [@param float :margin] The margin. # [@access public] # [@since 1.5] # [@see] SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins() # def SetRightMargin(margin) @r_margin = margin if (@page > 0) and (@x > (@w - margin)) @x = @w - margin end end alias_method :set_right_margin, :SetRightMargin # # Set the internal Cell padding. # [@param float :pad] internal padding. # [@access public] # [@since 2.1.000 (2008-01-09)] # [@see] Cell(), SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins() # def SetCellPadding(pad) @c_margin = pad end alias_method :set_cell_padding, :SetCellPadding # # Enables or disables the automatic page breaking mode. When enabling, the second parameter is the distance from the bottom of the page that defines the triggering limit. By default, the mode is on and the margin is 2 cm. # [@param boolean :auto] Boolean indicating if mode should be on or off. # [@param float :margin] Distance from the bottom of the page. # [@access public] # [@since 1.0] # [@see] Cell(), MultiCell(), AcceptPageBreak() # def SetAutoPageBreak(auto, margin=0) #Set auto page break mode and triggering margin @auto_page_break = auto @b_margin = margin @page_break_trigger = @h - margin end alias_method :set_auto_page_break, :SetAutoPageBreak # # Defines the way the document is to be displayed by the viewer. # [@param mixed :zoom] # The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. # * fullpage: displays the entire page on screen # * fullwidth: uses maximum width of window # * real: uses real size (equivalent to 100% zoom) # * default: uses viewer default mode # [@param string :layout] # The page layout. Possible values are: # * SinglePage Display one page at a time # * OneColumn Display the pages in one column # * TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left # * TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right # * TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left # * TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right # [@param string :mode] # A name object specifying how the document should be displayed when opened: # * UseNone Neither document outline nor thumbnail images 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 (PDF 1.5) Optional content group panel visible # * UseAttachments (PDF 1.6) Attachments panel visible # [@access public] # [@since 1.2] # def SetDisplayMode(zoom, layout='SinglePage', mode='UseNone') #Set display mode in viewer if (zoom == 'fullpage' or zoom == 'fullwidth' or zoom == 'real' or zoom == 'default' or zoom.is_a?(Numeric)) @zoom_mode = zoom else Error('Incorrect zoom display mode: ' + zoom) end case layout when 'default', 'single', 'SinglePage' @layout_mode = 'SinglePage' when 'continuous', 'OneColumn' @layout_mode = 'OneColumn' when 'two', 'TwoColumnLeft' @layout_mode = 'TwoColumnLeft' when 'TwoColumnRight' @layout_mode = 'TwoColumnRight' when 'TwoPageLeft' @layout_mode = 'TwoPageLeft' when 'TwoPageRight' @layout_mode = 'TwoPageRight' else @layout_mode = 'SinglePage' end case mode when 'UseNone' @page_mode = 'UseNone' when 'UseOutlines' @page_mode = 'UseOutlines' when 'UseThumbs' @page_mode = 'UseThumbs' when 'FullScreen' @page_mode = 'FullScreen' when 'UseOC' @page_mode = 'UseOC' when '' @page_mode = 'UseAttachments' else @page_mode = 'UseNone' end end alias_method :set_display_mode, :SetDisplayMode # # Activates or deactivates page compression. When activated, the internal representation of each page is compressed, which leads to a compression ratio of about 2 for the resulting document. Compression is on by default. # * Note: the Zlib extension is required for this feature. If not present, compression will be turned off. # [@param boolean :compress] Boolean indicating if compression must be enabled. # [@access public] # [@since 1.4] # def SetCompression(compress) #Set page compression if Object.const_defined?(:Zlib) @compress = compress else @compress = false end end alias_method :set_compression, :SetCompression # # Defines the title of the document. # [@param string :title] The title. # [@access public$ # [@since 1.2] # [@see] SetAuthor(), SetCreator(), SetKeywords(), SetSubject() # def SetTitle(title) #Title of document @title = title end alias_method :set_title, :SetTitle # # Defines the subject of the document. # [@param string :subject] The subject. # [@access public] # [@since 1.2] # [@see] SetAuthor(), SetCreator(), SetKeywords(), SetTitle() # def SetSubject(subject) #Subject of document @subject = subject end alias_method :set_subject, :SetSubject # # Defines the author of the document. # [@param string :author] The name of the author. # [@access public] # [@since 1.2] # [@see] SetCreator(), SetKeywords(), SetSubject(), SetTitle() # def SetAuthor(author) #Author of document @author = author end alias_method :set_author, :SetAuthor # # Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'. # [@param string :keywords] The list of keywords. # [@access public] # [@since 1.2] # [@see] SetAuthor(), SetCreator(), SetSubject(), SetTitle() # def SetKeywords(keywords) #Keywords of document @keywords = keywords end alias_method :set_keywords, :SetKeywords # # Defines the creator of the document. This is typically the name of the application that generates the PDF. # [@param string :creator] The name of the creator. # [@access public] # [@since 1.2] # [@see] SetAuthor(), SetKeywords(), SetSubject(), SetTitle() # def SetCreator(creator) #Creator of document @creator = creator end alias_method :set_creator, :SetCreator # # This method is automatically called in case of fatal error; it simply outputs the message and halts the execution. An inherited class may override it to customize the error handling but should always halt the script, or the resulting document would probably be invalid. # * 2004-06-11 :: Nicola Asuni : changed bold tag with strong # [@param string :msg] The error message # [@param string :err] Exception object # [@access public] # [@since 1.0] # def Error(msg, err = nil) if /^.+?:\d+(?::in `(.*)')?/ =~ caller(2).first method = $1 end if err and err.class != RBPDFError logger.error "pdf: #{method}: #{err.message}" err.backtrace.each{|trace| logger.error trace } end raise RBPDFError, "RBPDF error: #{msg}" end alias_method :error, :Error # # This method begins the generation of the PDF document. It is not necessary to call it explicitly because AddPage() does it automatically. # * Note: no page is created by this method # [@access public] # [@since 1.0] # [@see] AddPage(), Close() # def Open() #Begin document @state = 1 end # alias_method :open, :Open # # Terminates the PDF document. It is not necessary to call this method explicitly because Output() does it automatically. If the document contains no page, AddPage() is called to prevent from getting an invalid document. # [@since 1.0] # [@see] Open(), Output() # def Close() #Terminate document if (@state==3) return; end if (@page==0) AddPage(); end # close page endPage() lastPage() resetLinksAfterCurrentPage() @state = 2 SetAutoPageBreak(false) @y = @h - (1 / @k) @r_margin = 0 out('q') setVisibility('screen') SetFont('helvetica', '', 1) SetTextColor(127,127,127) setAlpha(0) msg = "\x50\x6f\x77\x65\x72\x65\x64\x20\x62\x79\x20\x54\x43\x50\x44\x46\x20\x28\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67\x29" lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67" Cell(0, 0, msg, 0, 0, 'R', 0, lnk, 0, false, 'D', 'B') out('Q') setVisibility('all') @state = 1 # close document enddoc(); end # alias_method :close, :Close # # Move pointer at the specified document page and update page dimensions. # [@param int :pnum] page number (1 ... numpages) # [@param boolean :resetmargins] if true reset left, right, top margins and Y position. # [@access public] # [@since 2.1.000 (2008-01-07)] # [@see] getPage(), lastPage(), getNumPages() # def setPage(pnum, resetmargins=false) if pnum == @page return end if (pnum > 0) and (pnum <= @numpages) @state = 2 # save current graphic settings # gvars = getGraphicVars() oldpage = @page @page = pnum @w_pt = @pagedim[@page]['w'] @h_pt = @pagedim[@page]['h'] @w = @pagedim[@page]['wk'] @h = @pagedim[@page]['hk'] @t_margin = @pagedim[@page]['tm'] @b_margin = @pagedim[@page]['bm'] @original_l_margin = @pagedim[@page]['olm'] @original_r_margin = @pagedim[@page]['orm'] @auto_page_break = @pagedim[@page]['pb'] @cur_orientation = @pagedim[@page]['or'] SetAutoPageBreak(@auto_page_break, @b_margin) # restore graphic settings # setGraphicVars(gvars) if resetmargins @l_margin = @pagedim[@page]['olm'] @r_margin = @pagedim[@page]['orm'] SetY(@t_margin) else # account for booklet mode if @pagedim[@page]['olm'] != @pagedim[oldpage]['olm'] deltam = @pagedim[@page]['olm'] - @pagedim[@page]['orm'] @l_margin += deltam @r_margin -= deltam end end else Error('Wrong page number on setPage() function.') end end alias_method :set_page, :setPage # # Reset pointer to the last document page. # [@param boolean :resetmargins] if true reset left, right, top margins and Y position. # [@access public] # [@since 2.0.000 (2008-01-04)] # [@see] setPage(), getPage(), getNumPages() # def lastPage(resetmargins=false) setPage(getNumPages(), resetmargins) end alias_method :last_page, :lastPage # # Get current document page number. # [@return int] page number # [@access public] # [@since 2.1.000 (2008-01-07)] # [@see] setPage(), lastPage(), getNumPages() # def getPage() return @page end alias_method :get_page, :getPage # # Get the total number of insered pages. # [@return int] number of pages # [@access public] # [@since 2.1.000 (2008-01-07)] # [@see] setPage(), getPage(), lastPage() # def getNumPages() return @numpages end alias_method :get_num_pages, :getNumPages # # Adds a new TOC (Table Of Content) page to the document. # [@param string :orientation] page orientation. # [@param boolean :keepmargins] if true overwrites the default page margins with the current margins # [@access public] # [@since 5.0.001 (2010-05-06)] # [@see] AddPage(), startPage(), endPage(), endTOCPage() # def addTOCPage(orientation='', format='', keepmargins=false) AddPage(orientation, format, keepmargins, true) end alias_method :add_toc_page, :addTOCPage # # Terminate the current TOC (Table Of Content) page # [@access public] # [@since 5.0.001 (2010-05-06)] # [@see] AddPage(), startPage(), endPage(), addTOCPage() # def endTOCPage() endPage(true) end alias_method :end_toc_page, :endTOCPage # # Adds a new page to the document. If a page is already present, the Footer() method is called first to output the footer (if enabled). Then the page is added, the current position set to the top-left corner according to the left and top margins (or top-right if in RTL mode), and Header() is called to display the header (if enabled). # The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards. # [@param string :orientation] # page orientation. Possible values are (case insensitive): # * P or PORTRAIT (default) # * L or LANDSCAPE # [@param mixed :format] # The format used for pages. It can be either: <b>A string indicating the page format:</b> # * 4A0,2A0,A0,A1,A2,A3,A4 (default),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,EXECUTIVE,FOLIO # <b>An array containing page measures and advanced options:</b> see setPageFormat() # [@param boolean :keepmargins] if true overwrites the default page margins with the current margin # [@param boolean :tocpage] if true set the tocpage state to true (the added page will be used to display Table Of Content). # [@access public] # [@since 1.0] # [@see] startPage, endPage(), addTOCPage(), endTOCPage() # def AddPage(orientation='', format='', keepmargins=false, tocpage=false) if @original_l_margin.nil? or keepmargins @original_l_margin = @l_margin end if @original_r_margin.nil? or keepmargins @original_r_margin = @r_margin end # terminate previous page endPage() # start new page startPage(orientation, format, tocpage) end alias_method :add_page, :AddPage # # Terminate the current page # [@param boolean :tocpage] if true set the tocpage state to false (end the page used to display Table Of Content). # [@access public] # [@since 4.2.010 (2008-11-14)] # [@see] AddPage(), startPage(), addTOCPage(), endTOCPage() # def endPage(tocpage=false) # check if page is already closed if (@page == 0) or (@numpages > @page) or !@pageopen[@page] return end @in_footer = true # print page footer setFooter() # close page endpage() # mark page as closed @pageopen[@page] = false @in_footer = false if tocpage @tocpage = false end end alias_method :end_page, :endPage # # Starts a new page to the document. The page must be closed using the endPage() function. # The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards. # [@param string :orientation] # page orientation. Possible values are (case insensitive): # * P or PORTRAIT (default) # * L or LANDSCAPE # [@param mixed :format] # The format used for pages. It can be either: <b>A string indicating the page format:</b> # * 4A0,2A0,A0,A1,A2,A3,A4 (default),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,EXECUTIVE,FOLIO # <b>An array containing page measures and advanced options:</b> see setPageFormat() # [@param boolean :tocpage] if true set the tocpage state to true (the added page will be used to display Table of Content). # [@access public] # [@since 4.2.010 (2008-11-14)] # [@see] AddPage(), endPage(), addTOCPage(), endTOCPage() # def startPage(orientation='', format='', tocpage=false) if tocpage @tocpage = true end if @numpages > @page # this page has been already added setPage(@page + 1) SetY(@t_margin) return end # start a new page if @state == 0 Open() end @numpages += 1 swapMargins(@booklet) # save current graphic settings gvars = getGraphicVars() # start new page beginpage(orientation, format) # mark page as open @pageopen[@page] = true # restore graphic settings setGraphicVars(gvars) # mark this point setPageMark() # print page header setHeader() # restore graphic settings setGraphicVars(gvars) # mark this point setPageMark() # print table header (if any) setTableHeader() end alias_method :start_page, :startPage # # Set start-writing mark on current page stream used to put borders and fills. # Borders and fills are always created after content and inserted on the position marked by this method. # This function must be called after calling Image() function for a background image. # Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions. # [@access public] # [@since 4.0.016 (2008-07-30)] # def setPageMark() @intmrk[@page] = @pagelen[@page] setContentMark() end alias_method :set_page_mark, :setPageMark # # Set start-writing mark on selected page. # Borders and fills are always created after content and inserted on the position marked by this method. # [@param int :page] page number (default is the current page) # [@access protected] # [@since 4.6.021 (2009-07-20)] # def setContentMark(page=0) if page <= 0 page = @page end if @footerlen[page] @cntmrk[page] = @pagelen[page] - @footerlen[page] else @cntmrk[page] = @pagelen[page] end end protected :setContentMark # # Set header data. # [@param string :ln] header image logo # [@param string :lw] header image logo width in mm # [@param string :ht] string to print as title on document header # [@param string :hs] string to print on document header # [@access public] # def setHeaderData(ln="", lw=0, ht="", hs="") @header_logo = ln || "" @header_logo_width = lw || 0 @header_title = ht || "" @header_string = hs || "" end alias_method :set_header_data, :setHeaderData def SetHeaderData(ln="", lw=0, ht="", hs="") warn "[DEPRECATION] 'SetHeaderData' is deprecated. Please use 'set_header_data' instead." setHeaderData(ln, lw, ht, hs) end # # Returns header data: # ret['logo'] = logo image # ret['logo_width'] = width of the image logo in user units # ret['title'] = header title # ret['string'] = header description string # [@return hash] # [@access public] # [@since 4.0.012 (2008-07-24)] # def getHeaderData() ret = {} ret['logo'] = @header_logo ret['logo_width'] = @header_logo_width ret['title'] = @header_title ret['string'] = @header_string return ret end alias_method :get_header_data, :getHeaderData # # Set header margin. # (minimum distance between header and top page margin) # [@param int :hm] distance in user units # [@access public] # def setHeaderMargin(hm=10) @header_margin = hm; end alias_method :set_header_margin, :setHeaderMargin def SetHeaderMargin(hm=10) warn "[DEPRECATION] 'SetHeaderMargin' is deprecated. Please use 'set_header_margin' instead." setHeaderMargin(hm) end # # Returns header margin in user units. # [@return float] # [@since 4.0.012 (2008-07-24)] # [@access public] # def getHeaderMargin() return @header_margin end alias_method :get_header_margin, :getHeaderMargin # # Set footer margin. # (minimum distance between footer and bottom page margin) # [@param int :fm] distance in millimeters # [@access public] # def setFooterMargin(fm=10) @footer_margin = fm; end alias_method :set_footer_margin, :setFooterMargin def SetFooterMargin(fm=10) warn "[DEPRECATION] 'SetFooterMargin' is deprecated. Please use 'set_footer_margin' instead." setFooterMargin(fm) end # # Returns footer margin in user units. # [@return float] # [@since 4.0.012 (2008-07-24)] # [@access public] # def getFooterMargin() return @footer_margin end alias_method :get_footer_margin, :getFooterMargin # # Set a flag to print page header. # [@param boolean :val] set to true to print the page header (default), false otherwise. # [@access public] # def setPrintHeader(val=true) @print_header = val; end alias_method :set_print_header, :setPrintHeader def SetPrintHeader(val=true) warn "[DEPRECATION] 'SetPrintHeader' is deprecated. Please use 'set_print_header' instead." setPrintHeader(val) end # # Set a flag to print page footer. # [@param boolean :value] set to true to print the page footer (default), false otherwise. # [@access public] # def setPrintFooter(val=true) @print_footer = val; end alias_method :set_print_footer, :setPrintFooter def SetPrintFooter(val=true) warn "[DEPRECATION] 'SetPrintFooter' is deprecated. Please use 'set_print_footer' instead." setPrintFooter(val) end # # Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image # [@return float] # [@access public] # def getImageRBX() return @img_rb_x end alias_method :get_image_rbx, :getImageRBX # # Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image # [@return float] # [@access public] # def getImageRBY() return @img_rb_y end alias_method :get_image_rby, :getImageRBY # # This method is used to render the page header. # It is automatically called by AddPage() and could be overwritten in your own inherited class. # [@access public] # def Header() ormargins = getOriginalMargins() headerfont = getHeaderFont() headerdata = getHeaderData() if headerdata['logo'] and (headerdata['logo'] != @@k_blank_image) result_img = Image(@@k_path_images + headerdata['logo'], '', '', headerdata['logo_width']) if result_img != false imgy = getImageRBY() else Write(@lasth, File.basename(headerdata['logo']), '', false, '', false, 0, false) imgy = GetY() end else imgy = GetY() end cell_height = ((getCellHeightRatio() * headerfont[2]) / getScaleFactor()).round(2) # set starting margin for text data cell if getRTL() header_x = ormargins['right'] + (headerdata['logo_width'] * 1.1) else header_x = ormargins['left'] + (headerdata['logo_width'] * 1.1) end SetTextColor(0, 0, 0) # header title SetFont(headerfont[0], 'B', headerfont[2] + 1) SetX(header_x) Cell(0, cell_height, headerdata['title'], 0, 1, '', 0, '', 0) # header string SetFont(headerfont[0], headerfont[1], headerfont[2]) SetX(header_x) MultiCell(0, cell_height, headerdata['string'], 0, '', 0, 1, '', '', true, 0, false) # print an ending header line SetLineStyle({'width' => 0.85 / getScaleFactor(), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => [0, 0, 0]}) SetY((2.835 / getScaleFactor()) + (imgy > GetY() ? imgy : GetY())) if getRTL() SetX(ormargins['right']) else SetX(ormargins['left']) end Cell(0, 0, '', 'T', 0, 'C') end alias_method :header, :Header # # This method is used to render the page footer. # It is automatically called by AddPage() and could be overwritten in your own inherited class. # [@access public] # def Footer() cur_y = GetY() ormargins = getOriginalMargins() SetTextColor(0, 0, 0) # set style for cell border line_width = 0.85 / getScaleFactor() SetLineStyle({'width' => line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => [0, 0, 0]}) # print document barcode #barcode = getBarcode() #if !barcode.empty? # Ln(line_width) # barcode_width = ((getPageWidth() - ormargins['left'] - ormargins['right']) / 3.0).round # write1DBarcode(barcode, 'C128B', GetX(), cur_y + line_width, barcode_width, ((getFooterMargin() / 3.0) - line_width), 0.3, '', '') #end w_page = (@l.nil? or @l['w_page'].nil?) ? '' : @l['w_page'] if @pagegroups.empty? pagenumtxt = w_page + ' ' + getAliasNumPage() + ' / ' + getAliasNbPages() else pagenumtxt = w_page + ' ' + getPageNumGroupAlias() + ' / ' + getPageGroupAlias() end SetY(cur_y) # Print page number if getRTL() SetX(ormargins['right']) Cell(0, 0, pagenumtxt, 'T', 0, 'L') else SetX(ormargins['left']) Cell(0, 0, pagenumtxt, 'T', 0, 'R') end end alias_method :footer, :Footer # # This method is used to render the page header. # [@access protected] # [@since 4.0.012 (2008-07-24)] # def setHeader() if @print_header temp_thead = @thead temp_theadMargins = @thead_margins lasth = @lasth out('q') @r_margin = @original_r_margin @l_margin = @original_l_margin @c_margin = 0 # set current position if @rtl SetXY(@original_r_margin, @header_margin) else SetXY(@original_l_margin, @header_margin) end SetFont(@header_font[0], @header_font[1], @header_font[2]) Header() # restore position if @rtl SetXY(@original_r_margin, @t_margin) else SetXY(@original_l_margin, @t_margin) end out('Q') @lasth = lasth @thead = temp_thead @thead_margins = temp_theadMargins @newline = false end end protected :setHeader # # This method is used to render the page footer. # [@access protected] # [@since 4.0.012 (2008-07-24)] # def setFooter() # Page footer # save current graphic settings gvars = getGraphicVars() # mark this point @footerpos[@page] = @pagelen[@page] out("\n") if @print_footer temp_thead = @thead temp_theadMargins = @thead_margins lasth = @lasth out('q') @r_margin = @original_r_margin @l_margin = @original_l_margin @c_margin = 0 # set current position footer_y = @h - @footer_margin if @rtl SetXY(@original_r_margin, footer_y) else SetXY(@original_l_margin, footer_y) end SetFont(@footer_font[0], @footer_font[1] , @footer_font[2]) Footer() # restore position if @rtl SetXY(@original_r_margin, @t_margin) else SetXY(@original_l_margin, @t_margin) end out('Q') @lasth = lasth @thead = temp_thead @thead_margins = temp_theadMargins end # restore graphic settings setGraphicVars(gvars) # calculate footer length @footerlen[@page] = @pagelen[@page] - @footerpos[@page] + 1 end protected :setFooter # # This method is used to render the table header on new page (if any). # [@access protected] # [@since 4.5.030 (2009-03-25)] # def setTableHeader() if @num_columns > 1 # multi column mode return end if !@thead_margins['top'].nil? # restore the original top-margin @t_margin = @thead_margins['top'] @pagedim[@page]['tm'] = @t_margin @y = @t_margin end if !empty_string(@thead) and !@in_thead # set margins prev_lMargin = @l_margin prev_rMargin = @r_margin prev_cMargin = @c_margin @l_margin = @thead_margins['lmargin'] @r_margin = @thead_margins['rmargin'] @c_margin = @thead_margins['cmargin'] if @rtl @x = @w - @r_margin else @x = @l_margin end # print table header writeHTML(@thead, false, false, false, false, '') # set new top margin to skip the table headers if @thead_margins['top'].nil? @thead_margins['top'] = @t_margin end @t_margin = @y @pagedim[@page]['tm'] = @t_margin @lasth = 0 @l_margin = prev_lMargin @r_margin = prev_rMargin @c_margin = prev_cMargin end end protected :setTableHeader # # Returns the current page number. # [@return int] page number # [@access public] # [@since 1.0] # [@see] alias_nb_pages() # def PageNo() #Get current page number return @page; end alias_method :page_no, :PageNo # # Defines a new spot color. # It can be expressed in RGB components or gray scale. # The method can be called before the first page is created and the value is retained from page to page. # [@param int :c] Cyan color for CMYK. Value between 0 and 255 # [@param int :m] Magenta color for CMYK. Value between 0 and 255 # [@param int :y] Yellow color for CMYK. Value between 0 and 255 # [@param int :k] Key (Black) color for CMYK. Value between 0 and 255 # [@access public] # [@since 4.0.024 (2008-09-12)] # [@see] SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor() # def AddSpotColor(name, c, m, y, k) if @spot_colors[name].nil? i = 1 + @spot_colors.length @spot_colors[name] = {'i' => i, 'c' => c, 'm' => m, 'y' => y, 'k' => k} end end alias_method :add_spot_color, :AddSpotColor # # Defines the color used for all drawing operations (lines, rectangles and cell borders). # It can be expressed in RGB components or gray scale. # The method can be called before the first page is created and the value is retained from page to page. # [@param array or ordered hash :color] array(or ordered hash) of colors # [@access public] # [@since 3.1.000 (2008-6-11)] # [@see] SetDrawColor() # def SetDrawColorArray(color) if !color.nil? color = color.values if color.is_a? Hash r = !color[0].nil? ? color[0] : -1 g = !color[1].nil? ? color[1] : -1 b = !color[2].nil? ? color[2] : -1 k = !color[3].nil? ? color[3] : -1 if r >= 0 SetDrawColor(r, g, b, k) end end end alias_method :set_draw_color_array, :SetDrawColorArray # # Defines the color used for all drawing operations (lines, rectangles and cell borders). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page. # [@param int :col1] Gray level for single color, or Red color for RGB, or Cyan color for CMYK. Value between 0 and 255 # [@param int :col2] Green color for RGB, or Magenta color for CMYK. Value between 0 and 255 # [@param int :col3] Blue color for RGB, or Yellow color for CMYK. Value between 0 and 255 # [@param int :col4] Key (Black) color for CMYK. Value between 0 and 255 # [@access public] # [@since 1.3] # [@see] SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell() # def SetDrawColor(col1=0, col2=-1, col3=-1, col4=-1) # set default values unless col1.is_a?(Numeric) col1 = 0 end unless col2.is_a?(Numeric) col2 = 0 end unless col3.is_a?(Numeric) col3 = 0 end unless col4.is_a?(Numeric) col4 = 0 end #Set color for all stroking operations if (col2 == -1) and (col3 == -1) and (col4 == -1) # Grey scale @draw_color = sprintf('%.3f G', col1 / 255.0) @strokecolor['G'] = col1 elsif col4 == -1 # RGB @draw_color = sprintf('%.3f %.3f %.3f RG', col1 / 255.0, col2 / 255.0, col3 / 255.0) @strokecolor['R'] = col1 @strokecolor['G'] = col2 @strokecolor['B'] = col3 else # CMYK @draw_color = sprintf('%.3f %.3f %.3f %.3f K', col1 / 100.0, col2 / 100.0, col3 / 100.0, col4 / 100.0) @strokecolor['C'] = col1 @strokecolor['M'] = col2 @strokecolor['Y'] = col3 @strokecolor['K'] = col4 end if (@page>0) out(@draw_color + ' ') end end alias_method :set_draw_color, :SetDrawColor # # Defines the color used for all filling operations (filled rectangles and cell backgrounds). # It can be expressed in RGB components or gray scale. # The method can be called before the first page is created and the value is retained from page to page. # [@param array or ordered hash :color] array(or ordered hash) of colors # [@access public] # [@since 3.1.000 (2008-6-11)] # [@see] SetFillColor() # def SetFillColorArray(color) if !color.nil? color = color.values if color.is_a? Hash r = !color[0].nil? ? color[0] : -1 g = !color[1].nil? ? color[1] : -1 b = !color[2].nil? ? color[2] : -1 k = !color[3].nil? ? color[3] : -1 if r >= 0 SetFillColor(r, g, b, k) end end end alias_method :set_fill_color_array, :SetFillColorArray # # Defines the color used for all filling operations (filled rectangles and cell backgrounds). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page. # [@param int :col1] Gray level for single color, or Red color for RGB, or Cyan color for CMYK. Value between 0 and 255 # [@param int :col2] Green color for RGB, or Magenta color for CMYK. Value between 0 and 255 # [@param int :col3] Blue color for RGB, or Yellow color for CMYK. Value between 0 and 255 # [@param int :col4] Key (Black) color for CMYK. Value between 0 and 255 # [@access public] # [@since 1.3] # [@see] SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell() # def SetFillColor(col1=0, col2=-1, col3=-1, col4=-1) # set default values unless col1.is_a?(Numeric) col1 = 0 end unless col2.is_a?(Numeric) col2 = -1 end unless col3.is_a?(Numeric) col3 = -1 end unless col4.is_a?(Numeric) col4 = -1 end # Set color for all filling operations if (col2 == -1) and (col3 == -1) and (col4 == -1) # Grey scale @fill_color = sprintf('%.3f g', col1 / 255.0) @bgcolor['G'] = col1 elsif col4 == -1 # RGB @fill_color = sprintf('%.3f %.3f %.3f rg', col1 / 255.0, col2 / 255.0, col3 / 255.0) @bgcolor['R'] = col1 @bgcolor['G'] = col2 @bgcolor['B'] = col3 else # CMYK @fill_color = sprintf('%.3f %.3f %.3f %.3f k', col1 / 100.0, col2 / 100.0, col3 / 100.0, col4 / 100.0) @bgcolor['C'] = col1 @bgcolor['M'] = col2 @bgcolor['Y'] = col3 @bgcolor['K'] = col4 end @color_flag = (@fill_color != @text_color) if @page > 0 out(@fill_color) end end alias_method :set_fill_color, :SetFillColor =begin # This hasn't been ported from tcpdf, it's a variation on SetTextColor for setting cmyk colors def SetCmykFillColor(c, m, y, k, storeprev=false) #Set color for all filling operations @fill_color=sprintf('%.3f %.3f %.3f %.3f k', c, m, y, k); @color_flag=(@fill_color!=@text_color); if (storeprev) # store color as previous value @prevtext_color = [c, m, y, k] end if (@page>0) out(@fill_color); end end alias_method :set_cmyk_fill_color, :SetCmykFillColor =end # # Defines the color used for text. It can be expressed in RGB components or gray scale. # The method can be called before the first page is created and the value is retained from page to page. # [@param array or ordered hash :color] array(or ordered hash) of colors # [@access public] # [@since 3.1.000 (2008-6-11)] # [@see] SetFillColor() # def SetTextColorArray(color) unless color.nil? color = color.values if color.is_a? Hash r = !color[0].nil? ? color[0] : -1 g = !color[1].nil? ? color[1] : -1 b = !color[2].nil? ? color[2] : -1 k = !color[3].nil? ? color[3] : -1 if r >= 0 SetTextColor(r, g, b, k) end end end alias_method :set_text_color_array, :SetTextColorArray # # Defines the color used for text. It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page. # [@param int :col1] Gray level for single color, or Red color for RGB, or Cyan color for CMYK. Value between 0 and 255 # [@param int :col2] Green color for RGB, or Magenta color for CMYK. Value between 0 and 255 # [@param int :col3] Blue color for RGB, or Yellow color for CMYK. Value between 0 and 255 # [@param int :col4] Key (Black) color for CMYK. Value between 0 and 255 # [@access public] # [@since 1.3] # [@see] SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell() # def SetTextColor(col1=0, col2=-1, col3=-1, col4=-1) # set default values unless col1.is_a?(Numeric) col1 = 0 end unless col2.is_a?(Numeric) col2 = -1 end unless col3.is_a?(Numeric) col3 = -1 end unless col4.is_a?(Numeric) col4 = -1 end # Set color for text if (col2 == -1) and (col3 == -1) and (col4 == -1) # Grey scale @text_color = sprintf('%.3f g', col1 / 255.0) @fgcolor['G'] = col1 elsif col4 == -1 # RGB @text_color = sprintf('%.3f %.3f %.3f rg', col1 / 255.0, col2 / 255.0, col3 / 255.0) @fgcolor['R'] = col1 @fgcolor['G'] = col2 @fgcolor['B'] = col3 else # CMYK @text_color = sprintf('%.3f %.3f %.3f %.3f k', col1 / 100.0, col2 / 100.0, col3 / 100.0, col4 / 100.0) @fgcolor['C'] = col1 @fgcolor['M'] = col2 @fgcolor['Y'] = col3 @fgcolor['K'] = col4 end @color_flag = (@fill_color != @text_color) end alias_method :set_text_color, :SetTextColor =begin # This hasn't been ported from tcpdf, it's a variation on SetTextColor for setting cmyk colors def SetCmykTextColor(c, m, y, k, storeprev=false) #Set color for text @text_color=sprintf('%.3f %.3f %.3f %.3f k', c, m, y, k); @color_flag=(@fill_color!=@text_color); if (storeprev) # store color as previous value @prevtext_color = [c, m, y, k] end end alias_method :set_cmyk_text_color, :SetCmykTextColor =end # # Returns the length of a string in user unit. A font must be selected. # [@param string :s] The string whose length is to be computed # [@param string :fontname] Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained. # [@param string :fontstyle] # Font style. Possible values are (case insensitive): # * empty string: regular # * B: bold # * I: italic # * U: underline # * D: line trough # * O: overline # or any combination. The default value is regular. # [@param float :fontsize] Font size in points. The default value is the current size. # [@param boolean :getarray] if true returns an array of characters widths, if false returns the total length. # [@return mixed int] total string length or array of characted widths # [@author] Nicola Asuni # [@access public] # [@since 1.2] # def GetStringWidth(s, fontname='', fontstyle='', fontsize=0, getarray=false) return GetArrStringWidth(utf8Bidi(UTF8StringToArray(s), s, rtl_text_dir), fontname, fontstyle, fontsize, getarray) end alias_method :get_string_width, :GetStringWidth # # Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected. # [@param string :sa] The array of chars whose total length is to be computed # [@param string :fontname] Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained. # [@param string :fontstyle] # Font style. Possible values are (case insensitive): # * empty string: regular # * B: bold # * I: italic # * U: underline # * D: line trough # * O: overline # or any combination. The default value is regular. # [@param float :fontsize] Font size in points. The default value is the current size. # [@param boolean :getarray] if true returns an array of characters widths, if false returns the total length. # [@return mixed int] total string length or array of characted widths # [@author] Nicola Asuni # [@access public] # [@since 2.4.000 (2008-03-06)] # def GetArrStringWidth(sa, fontname='', fontstyle='', fontsize=0, getarray=false) # store current values if !empty_string(fontname) prev_FontFamily = @font_family prev_FontStyle = @font_style prev_FontSizePt = @font_size_pt SetFont(fontname, fontstyle, fontsize) end # convert UTF-8 array to Latin1 if required sa = UTF8ArrToLatin1(sa) w = 0 # total width wa = [] # array of characters widths sa.each do |char| # character width cw = GetCharWidth(char) wa.push cw w += cw end # restore previous values if !empty_string(fontname) SetFont(prev_FontFamily, prev_FontStyle, prev_FontSizePt) end if getarray return wa end return w end alias_method :get_arr_string_width, :GetArrStringWidth # # Returns the length of the char in user unit for the current font. # [@param int :char] The char code whose length is to be returned # [@return int] char width # [@author] Nicola Asuni # [@access public] # [@since 2.4.000 (2008-03-06)] # def GetCharWidth(char) if char == 173 # SHY character will not be printed return 0 end cw = @current_font['cw'] if !cw[char].nil? w = cw[char] elsif !@current_font['dw'].nil? # default width w = @current_font['dw'] elsif !cw[32].nil? # default width w = cw[32] else w = 600 end return (w * @font_size / 1000.0) end alias_method :get_char_width, :GetCharWidth # # Returns the numbero of characters in a string. # [@param string :s] The input string. # [@return int] number of characters # [@access public] # [@since 2.0.0001 (2008-01-07)] # def GetNumChars(s) if (@current_font['type'] == 'TrueTypeUnicode') or (@current_font['type'] == 'cidfont0') return UTF8StringToArray(s).length end return s.length end alias_method :get_num_chars, :GetNumChars # # Fill the list of available fonts (@fontlist). # [@access protected] # [@since 4.0.013 (2008-07-28)] # def getFontsList() Dir.glob(File.join getfontpath(), '*.rb').each {|file| @fontlist.push File.basename(file, '.rb').downcase } end protected :getFontsList # # Imports a TrueType, Type1, core, or CID0 font and makes it available. # It is necessary to generate a font definition file first with the makefont.rb utility. # The definition file (and the font file itself when embedding) must be present either in the current directory or in the one indicated by FPDF_FONTPATH if the constant is defined. If it could not be found, the error "Could not include font definition file" is generated. # # === Example # # :pdf.add_font('Comic','I') # # is equivalent to: # :pdf.add_font('Comic','I','comici.rb') # # [@param string :family] Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font. # [@param string :style] # Font style. Possible values are (case insensitive): # * empty string: regular (default) # * B: bold # * I: italic # * BI or IB: bold italic # [@param string :fontfile] The font definition file. By default, the name is built from the family and style, in lower case with no space. # [@return array] containing the font data, or false in case of error. # [@param boolean :subset] if true embedd only a subset of the font (stores only the information related to the used characters); this option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font. # [@access public] # [@since 1.5] # [@see] SetFont() # def AddFont(family, style='', fontfile='', subset=nil) if empty_string(family) if !empty_string(@font_family) family = @font_family else Error('Empty font family') end end subset = @font_subsetting if subset.nil? family = family.downcase if ((!@is_unicode) and (family == 'arial')) family = 'helvetica'; end if (family == "symbol") or (family == "zapfdingbats") style = '' end tempstyle = style.upcase style = '' # underline if tempstyle.index('U') != nil @underline = true else @underline = false end # line-through (deleted) if tempstyle.index('D') != nil @linethrough = true else @linethrough = false end # overline if tempstyle.index('O') != nil @overline = true else @overline = false end # bold if tempstyle.index('B') != nil style << 'B'; end # oblique if tempstyle.index('I') != nil style << 'I'; end bistyle = style fontkey = family + style; font_style = style + (@underline ? 'U' : '') + (@linethrough ? 'D' : '') + (@overline ? 'O' : '') fontdata = {'fontkey' => fontkey, 'family' => family, 'style' => font_style} # check if the font has been already added if getFontBuffer(fontkey) != false return fontdata end # get specified font directory (if any) fontdir = false if !empty_string(fontfile) fontdir = File.dirname(fontfile) if empty_string(fontdir) or (fontdir == '.') fontdir = '' else fontdir << '/' end end fontname = family.gsub(' ', '') + style.downcase # search and include font file if empty_string(fontfile) # build a standard filenames for specified font fontfile1 = fontname + '.rb' fontfile2 = family.gsub(' ', '') + '.rb' # search files on various directories if (fontdir != false) and File.exist?(fontdir + fontfile1) fontfile = fontdir + fontfile1 fontname = fontfile1 elsif fontfile = getfontpath(fontfile1) fontname = fontfile1 elsif File.exist?(fontfile1) fontfile = fontfile1 fontname = fontfile1 elsif (fontdir != false) and File.exist?(fontdir + fontfile2) fontfile = fontdir + fontfile2 fontname = fontfile2 elsif fontfile = getfontpath(fontfile2) fontname = fontfile2 else fontfile = fontfile2 fontname = fontfile2 end end # include font file if File.exist?(fontfile) require(fontfile) else Error('Could not include font definition file: ' + family + '') end font_desc = RBPDFFontDescriptor.font(fontname).dup if font_desc[:desc].nil? desc = {} else desc = font_desc[:desc].dup end # check font parameters if font_desc[:type].nil? or font_desc[:cw].nil? Error('The font definition file has a bad format: ' + fontfile + '') end # SET default parameters font_desc[:file] ||= '' font_desc[:enc] ||= '' if font_desc[:cidinfo].nil? font_desc[:cidinfo] = {'Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0} font_desc[:cidinfo]['uni2cid'] = {} end font_desc[:ctg] ||= '' font_desc[:up] ||= -100 font_desc[:ut] ||= 50 font_desc[:cw] ||= {} if empty_string(font_desc[:dw]) # set default width if !desc['MissingWidth'].nil? and (desc['MissingWidth'] > 0) font_desc[:dw] = desc['MissingWidth'] elsif font_desc[:cw][32] font_desc[:dw] = font_desc[:cw][32] else font_desc[:dw] = 600 end end @numfonts += 1 if font_desc[:type] == 'cidfont0' # register CID font (all styles at once) styles = {'' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic'} font_desc[:name] = font_desc[:name] + styles[bistyle] # artificial bold if bistyle.index('B') != nil if desc['StemV'] desc['StemV'] *= 2 else desc['StemV'] = 120 end end # artificial italic if bistyle.index('I') != nil if desc['ItalicAngle'] desc['ItalicAngle'] -= 11 else desc['ItalicAngle'] = -11 end end elsif font_desc[:type] == 'core' font_desc[:name] = @core_fonts[fontkey] subset = false elsif (font_desc[:type] == 'TrueType') or (font_desc[:type] == 'Type1') # ... subset = false elsif font_desc[:type] == 'TrueTypeUnicode' font_desc[:enc] = 'Identity-H' else Error('Unknow font type: ' + font_desc[:type] + '') end # initialize subsetchars to contain default ASCII values (0-255) subsetchars = Array.new(256, true) setFontBuffer(fontkey, {'fontkey' => fontkey, 'i' => @numfonts, 'type' => font_desc[:type], 'name' => font_desc[:name], 'desc' => desc, 'up' => font_desc[:up], 'ut' => font_desc[:ut], 'cw' => font_desc[:cw], 'dw' => font_desc[:dw], 'enc' => font_desc[:enc], 'cidinfo' => font_desc[:cidinfo], 'file' => font_desc[:file], 'ctg' => font_desc[:ctg], 'subset' => subset, 'subsetchars' => subsetchars}) if (!font_desc[:diff].nil? and (!font_desc[:diff].empty?)) #Search existing encodings d=0; nb=@diffs.length; 1.upto(nb) do |i| if (@diffs[i]== font_desc[:diff]) d = i; break; end end if (d==0) d = nb+1; @diffs[d] = font_desc[:diff]; end setFontSubBuffer(fontkey, 'diff', d) end if !empty_string(font_desc[:file]) if @font_files[font_desc[:file]].nil? if (font_desc[:type] == 'TrueType') or (font_desc[:type] == 'TrueTypeUnicode') @font_files[font_desc[:file]] = {'length1' => font_desc[:originalsize], 'fontdir' => fontdir, 'subset' => subset, 'fontkeys' => [fontkey]} elsif font_desc[:type] != 'core' @font_files[font_desc[:file]] = {'length1' => font_desc[:size1], 'length2' => font_desc[:size2], 'fontdir' => fontdir, 'subset' => subset, 'fontkeys' => [fontkey]} end else # update fontkeys that are sharing this font file @font_files[font_desc[:file]]['subset'] = (@font_files[font_desc[:file]]['subset'] and subset) unless @font_files[font_desc[:file]]['fontkeys'].include? fontkey @font_files[font_desc[:file]]['fontkeys'] ||= [] @font_files[font_desc[:file]]['fontkeys'].push fontkey end end end return fontdata end alias_method :add_font, :AddFont # # Sets the font used to print character strings. # The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe). # The method can be called before the first page is created and the font is retained from page to page. # If you just wish to change the current font size, it is simpler to call SetFontSize(). # * Note: for the standard fonts, the font metric files must be accessible. There are three possibilities for this: # * They are in the current directory (the one where the running script lies) # * They are in one of the directories defined by the include_path parameter # * They are in the directory defined by the FPDF_FONTPATH constant # [@param string :family] # Family font. It can be either a name defined by AddFont() or one of the standard Type1 families (case insensitive): # * times (Times-Roman) # * timesb (Times-Bold) # * timesi (Times-Italic) # * timesbi (Times-BoldItalic) # * helvetica (Helvetica) # * helveticab (Helvetica-Bold) # * helveticai (Helvetica-Oblique) # * helveticabi (Helvetica-BoldOblique) # * courier (Courier) # * courierb (Courier-Bold) # * courieri (Courier-Oblique) # * courierbi (Courier-BoldOblique) # * symbol (Symbol) # * zapfdingbats (ZapfDingbats) # It is also possible to pass an empty string. In that case, the current family is retained. # [@param string :style] # Font style. Possible values are (case insensitive): # * empty string: regular # * B: bold # * I: italic # * U: underline # * D: line trough # * O: overline # or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats basic fonts or other fonts when not defined. # [@param float :size] Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12 # [@param string :fontfile] The font definition file. By default, the name is built from the family and style, in lower case with no spaces. # [@param boolean :subset] if true embedd only a subset of the font (stores only the information related to the used characters); this option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font. # [@author Nicola Asuni] # [@access public] # [@since 1.0] # [@see] AddFont(), SetFontSize() # def SetFont(family, style='', size=0, fontfile='', subset=nil) # Select a font; size given in points if size == 0 size = @font_size_pt end # try to add font (if not already added) fontdata = AddFont(family, style, fontfile, subset) @font_family = fontdata['family'] @font_style = fontdata['style'] @current_font = getFontBuffer(fontdata['fontkey']) SetFontSize(size) end alias_method :set_font, :SetFont # # Defines the size of the current font. # [@param float :size] The size (in points) # [@access public] # [@since 1.0] # [@see] SetFont() # def SetFontSize(size) #Set font size in points @font_size_pt = size; @font_size = size.to_f / @k; if !@current_font['desc'].nil? and !@current_font['desc']['Ascent'].nil? and (@current_font['desc']['Ascent'] > 0) @font_ascent = @current_font['desc']['Ascent'] * @font_size / 1000.0 else @font_ascent = 0.85 * @font_size end if !@current_font['desc'].nil? and !@current_font['desc']['Descent'].nil? and (@current_font['desc']['Descent'] <= 0) @font_descent = - @current_font['desc']['Descent'] * @font_size / 1000.0 else @font_descent = 0.15 * @font_size end if (@page > 0) and !@current_font['i'].nil? out(sprintf('BT /F%d %.2f Tf ET ', @current_font['i'], @font_size_pt)); end end alias_method :set_font_size, :SetFontSize # # Return the font descent value # [@param string :font] font name # [@param string :style] font style # [@param float :size] The size (in points) # [@return int] font descent # [@access public] # [@since 4.9.003 (2010-03-30)] # def getFontDescent(font, style='', size=0) # Set font size in points sizek = size / @k fontdata = AddFont(font, style) font = getFontBuffer(fontdata['fontkey']) if font['desc'] and font['desc']['Descent'] and (font['desc']['Descent'] <= 0) descent = - font['desc']['Descent'] * sizek / 1000.0 else descent = 0.15 * sizek end return descent end alias_method :get_font_descent, :getFontDescent # # Return the font ascent value # [@param string :font] font name # [@param string :style] font style # [@param float :size] The size (in points) # [@return int] font ascent # [@access public] # [@since 4.9.003 (2010-03-30)] # def getFontAscent(font, style='', size=0) # Set font size in points sizek = size / @k fontdata = AddFont(font, style) font = getFontBuffer(fontdata['fontkey']) if font['desc'] and font['desc']['Ascent'] and (font['desc']['Ascent'] > 0) ascent = font['desc']['Ascent'] * sizek / 1000.0 else ascent = 0.85 * sizek end return ascent end alias_method :get_font_ascent, :getFontAscent # # Defines the default monospaced font. # [@param string :font] Font name. # [@access public] # [@since 4.5.025] # def SetDefaultMonospacedFont(font) @default_monospaced_font = font end alias_method :set_default_monospaced_font, :SetDefaultMonospacedFont # # Creates a new internal link and returns its identifier. An internal link is a clickable area which directs to another place within the document. # The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink(). # [@access public] # [@since 1.5] # [@see] Cell(), Write(), Image(), Link(), SetLink() # def AddLink() #Create a new internal link n = @links.length + 1 @links[n]=[0,0]; return n; end alias_method :add_link, :AddLink # # Defines the page and position a link points to # [@param int :link] The link identifier returned by AddLink() # [@param float :y] Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page) # [@param int :page] Number of target page; -1 indicates the current page. This is the default value # [@since 1.5] # [@see] AddLink() # def SetLink(link, y=0, page=-1) #Set destination of internal link if (y==-1) y=@y; end if (page==-1) page=@page; end @links[link] = [page, y] end alias_method :set_link, :SetLink # # Puts a link on a rectangular area of the page. # Text or image links are generally put via Cell(), Write() or Image(), but this method can be useful for instance to define a clickable area inside an image. # [@param float :x] Abscissa of the upper-left corner of the rectangle # [@param float :y] Ordinate of the upper-left corner of the rectangle # [@param float :w] Width of the rectangle # [@param float :h] Height of the rectangle # [@param mixed :link] URL or identifier returned by AddLink() # [@param int :spaces] number of spaces on the text to link # [@access public] # [@since 1.5] # [@see] AddLink(), Annotation(), Cell(), Write(), Image() # def Link(x, y, w, h, link, spaces=0) Annotation(x, y, w, h, link, {'Subtype'=>'Link'}, spaces) end alias_method :link, :Link # # Puts a markup annotation on a rectangular area of the page. # * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!! # [@param float :x] Abscissa of the upper-left corner of the rectangle # [@param float :y] Ordinate of the upper-left corner of the rectangle # [@param float :w] Width of the rectangle # [@param float :h] Height of the rectangle # [@param mixed :text] annotation text or alternate content # [@param array :opt] array of options (see section 8.4 of PDF reference 1.7). # [@param int :spaces] number of spaces on the text to link # [@access public] # [@since 4.0.018 (2008-08-06)] # def Annotation(x, y, w, h, text, opt={'Subtype'=>'Text'}, spaces=0) x = @x if x == '' y = @y if y == '' # recalculate coordinates to account for graphic transformations if !@transfmatrix.nil? @transfmatrix_key.downto(1) do |i| maxid = @transfmatrix[i].length - 1 maxid.downto(0) do |j| ctm = @transfmatrix[i][j] if !ctm['a'].nil? x = x * @k y = (@h - y) * @k w = w * @k h = h * @k # top left xt = x yt = y x1 = (ctm['a'] * xt) + (ctm['c'] * yt) + ctm['e'] y1 = (ctm['b'] * xt) + (ctm['d'] * yt) + ctm['f'] # top right xt = x + w yt = y x2 = (ctm['a'] * xt) + (ctm['c'] * yt) + ctm['e'] y2 = (ctm['b'] * xt) + (ctm['d'] * yt) + ctm['f'] # bottom left xt = x yt = y - h x3 = (ctm['a'] * xt) + (ctm['c'] * yt) + ctm['e'] y3 = (ctm['b'] * xt) + (ctm['d'] * yt) + ctm['f'] # bottom right xt = x + w yt = y - h x4 = (ctm['a'] * xt) + (ctm['c'] * yt) + ctm['e'] y4 = (ctm['b'] * xt) + (ctm['d'] * yt) + ctm['f'] # new coordinates (rectangle area) x = [x1, x2, x3, x4].min y = [y1, y2, y3, y4].max w = ([x1, x2, x3, x4].max - x) / @k h = (y - [y1, y2, y3, y4].min) / @k x = x / @k y = @h - (y / @k) end end end end if @page <= 0 page = 1 else page = @page end @page_annots[page] ||= [] @page_annots[page].push 'x' => x, 'y' => y, 'w' => w, 'h' => h, 'txt' => text, 'opt' => opt, 'numspaces' => spaces if ((opt['Subtype'] == 'FileAttachment') or (opt['Subtype'] == 'Sound')) and !empty_string(opt['FS']) and File.exist?(opt['FS']) and @embeddedfiles[File.basename(opt['FS'])].nil? @embeddedfiles[File.basename(opt['FS'])] = {'file' => opt['FS'], 'n' => (@embeddedfiles.length + @embedded_start_obj_id)} end # Add widgets annotation's icons if opt['mk'] and opt['mk']['i'] and File.exist?(opt['mk']['i']) Image(opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true) end if opt['mk'] and opt['mk']['ri'] and File.exist?(opt['mk']['ri']) Image(opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true) end if opt['mk'] and opt['mk']['ix'] and File.exist?(opt['mk']['ix']) Image(opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true) end @annot_obj_id += 1 end alias_method :annotation, :Annotation # # Embedd the attached files. # [@since 4.4.000 (2008-12-07)] # [@access protected] # [@see] Annotation() # def putEmbeddedFiles() # reset(@embeddedfiles) @embeddedfiles.each { |filename, filedata| data = '' open(filedata['file'],'rb') do |f| data = f.read() end filter = '' if @compress data = Zlib::Deflate.deflate(data) filter = ' /Filter /FlateDecode' end @offsets[filedata['n']] = @bufferlen out = filedata['n'].to_s + ' 0 obj' out << '<</Type /EmbeddedFile' + filter + ' /Length ' + data.length.to_s + ' >>' out << ' ' + getstream(data, filedata['n']) out << ' endobj' out(out) } end protected :putEmbeddedFiles # # Prints a text cell at the specified position. # The origin is on the left of the first charcter, on the baseline. # This method allows to place a string precisely on the page. # [@param float :x] Abscissa of the cell origin # [@param float :y] Ordinate of the cell origin # [@param string :txt] String to print # [@param int :fstroke] outline size in user units (false = disable) # [@param boolean :fclip] if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation). # [@param boolean :ffill] if true fills the text # [@param mixed :border] # Indicates if borders must be drawn around the cell. The value can be either a number: # * 0: no border (default) # * 1: frame # or a string containing some or all of the following characters (in any order): # * L: left # * T: top # * R: right # * B: bottom # [@param int :ln] # Indicates where the current position should go after the call. Possible values are: # * 0: to the right (or left for RTL languages) # * 1: to the beginning of the next line # * 2: below # Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. # [@param string :align] # Allows to center or align the text. Possible values are: # * L or empty string: left align (default value) # * C: center # * R: right align # * J: justify # [@param int :fill] Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. # [@param mixed :link] URL or identifier returned by AddLink(). # [@param int :stretch] # stretch carachter mode: # * 0 = disabled # * 1 = horizontal scaling only if necessary # * 2 = forced horizontal scaling # * 3 = character spacing only if necessary # * 4 = forced character spacing # [@param boolean :ignore_min_height] if true ignore automatic minimum height value. # [@param string :calign] # cell vertical alignment relative to the specified Y value. Possible values are: # * T : cell top # * A : font top # * L : font baseline # * D : font bottom # * B : cell bottom # [@param string :valign] # text vertical alignment inside the cell. Possible values are: # * T : top # * C : center # * B : bottom # [@param boolean :rtloff] if true uses the page top-left corner as origin of axis for :x and :y initial position. # [@access public] # [@since 1.0] # [@see] SetFont(), SetTextColor(), Cell(), MultiCell(), Write() # def Text(x, y, txt, fstroke=false, fclip=false, ffill=true, border=0, ln=0, align='', fill=0, link='', stretch=0, ignore_min_height=false, calign='T', valign='M', rtloff=false) fstroke = 0 if fstroke == false textrendermode = @textrendermode textstrokewidth = @textstrokewidth setTextRenderingMode(fstroke, ffill, fclip) SetXY(x, y, rtloff) Cell(0, 0, txt, border, ln, align, fill, link, stretch, ignore_min_height, calign, valign) # restore previous rendering mode @textrendermode = textrendermode @textstrokewidth = textstrokewidth end alias_method :text, :Text # # Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value. # The default implementation returns a value according to the mode selected by SetAutoPageBreak(). # This method is called automatically and should not be called directly by the application. # [@return boolean] # [@access public] # [@since 1.4] # [@see] SetAutoPageBreak() # def AcceptPageBreak() #if @num_columns > 1 # # multi column mode # if @current_column < (@num_columns - 1) # # go to next column # selectColumn(@current_column + 1) # else # # add a new page # AddPage() # # set first column # selectColumn(0) # end # # avoid page breaking from checkPageBreak() # return false #end return @auto_page_break; end alias_method :accept_page_break, :AcceptPageBreak # # Add page if needed. # [@param float :h] Cell height. Default value: 0. # [@param mixed :y] starting y position, leave empty for current position. # [@param boolean :addpage] if true add a page, otherwise only return the true/false state # [@return boolean] true in case of page break, false otherwise. # [@since 3.2.000 (2008-07-01)] # [@access protected] # def checkPageBreak(h=0, y='', addpage=true) if empty_string(y) y = @y end current_page = @page if (y + h > @page_break_trigger) and !@in_footer and AcceptPageBreak() if addpage # Automatic page break x = @x AddPage(@cur_orientation) @y = @t_margin oldpage = @page - 1 if @rtl if @pagedim[@page]['orm'] != @pagedim[oldpage]['orm'] @x = x - (@pagedim[@page]['orm'] - @pagedim[oldpage]['orm']) else @x = x end else if @pagedim[@page]['olm'] != @pagedim[oldpage]['olm'] @x = x + (@pagedim[@page]['olm'] - @pagedim[oldpage]['olm']) else @x = x end end end @newline = true return true end if current_page != @page # account for columns mode @newline = true return true end return false end protected :checkPageBreak def BreakThePage?(h) warn "[DEPRECATION] 'BreakThePage/break_the_page' is deprecated. Please don't Use." if ((@y + h) > @page_break_trigger and !@in_footer and AcceptPageBreak()) true else false end end alias_method :break_the_page?, :BreakThePage? # # Removes SHY characters from text. # Unicode Data: # * Name : SOFT HYPHEN, commonly abbreviated as SHY # * HTML Entity (decimal): ­ # * HTML Entity (hex): ­ # * HTML Entity (named): ­ # * How to type in Microsoft Windows: [Alt +00AD] or [Alt 0173] # * UTF-8 (hex): 0xC2 0xAD (c2ad) # * UTF-8 character: chr(194).chr(173) # [@param string :txt] input string # [@return string] without SHY characters. # [@access public] # [@since (4.5.019) 2009-02-28] # def removeSHY(txt='') txt = txt.dup txt.force_encoding('ASCII-8BIT') if txt.respond_to?(:force_encoding) txt.gsub!(/([\xc2]{1}[\xad]{1})/, '') if !@is_unicode txt.gsub!(/([\xad]{1})/, '') return txt end txt.force_encoding('UTF-8') if txt.respond_to?(:force_encoding) return txt end alias_method :remove_shy, :removeSHY # # Prints a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text. # If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting. # [@param float :w] Cell width. If 0, the cell extends up to the right margin. # [@param float :h] Cell height. Default value: 0. # [@param string :txt] String to print. Default value: empty string. # [@param mixed :border] # Indicates if borders must be drawn around the cell. The value can be either a number: # * 0: no border (default) # * 1: frame # or a string containing some or all of the following characters (in any order): # * L: left # * T: top # * R: right # * B: bottom # [@param int :ln] # Indicates where the current position should go after the call. Possible values are: # * 0: to the right # * 1: to the beginning of the next line # * 2: below # Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. # [@param string :align] # Allows to center or align the text. Possible values are: # * L or empty string: left align (default value) # * C: center # * R: right align # [@param int :fill] Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. # [@param mixed :link] URL or identifier returned by AddLink(). # [@param int :stretch] # stretch carachter mode: # * 0 = disabled # * 1 = horizontal scaling only if necessary # * 2 = forced horizontal scaling # * 3 = character spacing only if necessary # * 4 = forced character spacing # [@param boolean :ignore_min_height] if true ignore automatic minimum height value. # [@param string :calign] # cell vertical alignment relative to the specified Y value. Possible values are: # * T : cell top # * C : center # * B : cell bottom # * A : font top # * L : font baseline # * D : font bottom # [@param string :valign] # text vertical alignment inside the cell. Possible values are: # * T : top # * M : middle # * B : bottom # [@access public] # [@since 1.0] # [@see] SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak() # def Cell(w, h=0, txt='', border=0, ln=0, align='', fill=0, link=nil, stretch=0, ignore_min_height=false, calign='T', valign='M') if !ignore_min_height min_cell_height = @font_size * @cell_height_ratio if h < min_cell_height h = min_cell_height end end checkPageBreak(h) out(getCellCode(w, h, txt, border, ln, align, fill, link, stretch, ignore_min_height, calign, valign)) rescue => err Error('Cell Error.', err) end alias_method :cell, :Cell # # Returns the PDF string code to print a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text. # If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting. # [@param float :w] Cell width. If 0, the cell extends up to the right margin. # [@param float :h] Cell height. Default value: 0. # [@param string :txt] String to print. Default value: empty string. # [@param mixed :border] # Indicates if borders must be drawn around the cell. The value can be either a number: # * 0: no border (default) # * 1: frame # or a string containing some or all of the following characters (in any order): # * L: left # * T: top # * R: right # * B: bottom # [@param int :ln] # Indicates where the current position should go after the call. Possible values are: # * 0: to the right (or left for RTL languages) # * 1: to the beginning of the next line # * 2: below # Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. # [@param string :align] # Allows to center or align the text. Possible values are: # * L or empty string: left align (default value) # * C: center # * R: right align # * J: justify # [@param int :fill] Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. # [@param mixed :link] URL or identifier returned by AddLink(). # [@param int :stretch] # stretch carachter mode: # * 0 = disabled # * 1 = horizontal scaling only if necessary # * 2 = forced horizontal scaling # * 3 = character spacing only if necessary # * 4 = forced character spacing # [@param boolean :ignore_min_height] if true ignore automatic minimum height value. # [@param string :calign] # cell vertical alignment relative to the specified Y value. Possible values are: # * T : cell top # * C : center # * B : cell bottom # * A : font top # * L : font baseline # * D : font bottom # [@param string :valign] # text vertical alignment inside the cell. Possible values are: # * T : top # * M : middle # * B : bottom # [@access protected] # [@since 1.0] # [@see] Cell() # def getCellCode(w, h=0, txt='', border=0, ln=0, align='', fill=0, link=nil, stretch=0, ignore_min_height=false, calign='T', valign='M') txt = '' if txt.nil? rs = "" # string to be returned txt = removeSHY(txt) if !ignore_min_height min_cell_height = @font_size * @cell_height_ratio if h < min_cell_height h = min_cell_height end end k = @k x = @x y = @y # cell vertical alignment case calign when 'A' # font top case valign when 'T' # top y -= @line_width / 2.0 when 'B' # bottom y -= (h - @font_ascent - @font_descent - @line_width / 2.0) else # 'M' # center y -= (h - @font_ascent - @font_descent) / 2.0 end when 'L' # font baseline case valign when 'T' # top y -= (@font_ascent + @line_width / 2.0) when 'B' # bottom y -= (h - @font_descent - @line_width / 2.0) else # 'M' # center y -= (h + @font_ascent - @font_descent) / 2.0 end when 'D' # font bottom case valign when 'T' # top y -= (@font_ascent + @font_descent + @line_width / 2.0) when 'B' # bottom y -= (h - @line_width / 2.0) else # 'M' # center y -= (h + @font_ascent + @font_descent) / 2.0 end when 'B' # cell bottom y -= h when 'C' # cell center y -= h / 2.0 else # 'T' # cell top end # text vertical alignment case valign when 'T' # top basefonty = y + @font_ascent + @line_width / 2.0 when 'B' # bottom basefonty = y + h - @font_descent - @line_width / 2.0 else # 'M' # center basefonty = y + (h + @font_ascent - @font_descent) / 2.0 end if empty_string(w) or (w <= 0) if @rtl w = x - @l_margin else w = @w - @r_margin - x end end s = ''; # fill and borders if (fill == 1) or (border.to_i == 1) if (fill == 1) op = (border.to_i == 1) ? 'B' : 'f'; else op = 'S'; end if @rtl xk = (@x - w) * k else xk = @x * k end s << sprintf('%.2f %.2f %.2f %.2f re %s ', xk, (@h - y) * k, w * k, -h * k, op) end if (border.is_a?(String)) lm = @line_width / 2.0 if (border.include?('L')) if @rtl xk = (x - w) * k else xk = x * k end s << sprintf('%.2f %.2f m %.2f %.2f l S ', xk, (@h - y + lm) * k, xk, (@h - (y + h + lm)) * k) end if (border.include?('T')) if @rtl xk = (x - w + lm) * k xwk = (x - lm) * k else xk = (x - lm) * k xwk = (x + w + lm) * k end s << sprintf('%.2f %.2f m %.2f %.2f l S ', xk,(@h-y)*k,xwk,(@h-y)*k) end if (border.include?('R')) if @rtl xk = x * k else xk = (x + w) * k end s << sprintf('%.2f %.2f m %.2f %.2f l S ', xk, (@h - y + lm) * k, xk, (@h - (y + h + lm)) * k) end if (border.include?('B')) if @rtl xk = (x - w + lm) * k xwk = (x - lm) * k else xk = (x - lm) * k xwk = (x + w + lm) * k end s << sprintf('%.2f %.2f m %.2f %.2f l S ', xk,(@h-(y+h))*k,xwk,(@h-(y+h))*k) end end if (txt != '') txt2 = txt if @is_unicode if (@current_font['type'] == 'core') or (@current_font['type'] == 'TrueType') or (@current_font['type'] == 'Type1') txt2 = UTF8ToLatin1(txt2) else unicode = UTF8StringToArray(txt) # array of UTF-8 unicode values unicode = utf8Bidi(unicode, '', rtl_text_dir) if @@k_thai_topchars and @@k_thai_topchars == true # ---- Fix for bug #2977340 "Incorrect Thai characters position arrangement" ---- # NOTE: this doesn't work with HTML justification # Symbols that could overlap on the font top (only works in LTR) topchar = [3611, 3613, 3615, 3650, 3651, 3652] # chars that extends on top topsym = [3633, 3636, 3637, 3638, 3639, 3655, 3656, 3657, 3658, 3659, 3660, 3661, 3662] # symbols with top positi numchars = unicode.length # number of chars unik = 0; uniblock = [] uniblock[unik] = [] uniblock[unik].push unicode[0] # resolve overlapping conflicts by splitting the string in several parts 1.upto(numchars - 1) do |i| # check if symbols overlaps at top if topsym.include?(unicode[i]) and (topsym.include?(unicode[i - 1]) or topchar.inclue?(unicode[i - 1])) # move symbols to another array unik += 1 uniblock[unik] = [] uniblock[unik].push unicode[i] unik += 1 uniblock[unik] = [] unicode[i] = 8203 # Unicode Character 'ZERO WIDTH SPACE' (U+200B) else uniblock[unik].push unicode[i] end end # ---- END OF Fix for bug #2977340 end txt2 = arrUTF8ToUTF16BE(unicode, false) end end txt2 = escape(txt2) # text length width = txwidth = GetStringWidth(txt) # ratio between cell length and text length if width <= 0 ratio = 1 else ratio = (w - (2 * @c_margin)) / width end # stretch text if required if (stretch > 0) and ((ratio < 1) or ((ratio > 1) and ((stretch % 2) == 0))) if stretch > 2 # spacing # Calculate character spacing in points txt_length = GetNumChars(txt) - 1 char_space = ((w - width - (2 * @c_margin)) * @k) / (txt_length > 1 ? txt_length : 1) # Set character spacing rs << sprintf('BT %.2f Tc ET ', char_space) else # scaling # Calculate horizontal scaling horiz_scale = ratio * 100.0 # Set horizontal scaling rs << sprintf('BT %.2f Tz ET ', horiz_scale) end align = '' width = w - (2 * @c_margin) else stretch == 0 end if (@color_flag) s << 'q ' + @text_color + ' '; end # rendering mode # s << sprintf('BT %d Tr %.2f w ET ', @textrendermode, @textstrokewidth) opt = sprintf('%d Tr %.2f w ', @textrendermode, @textstrokewidth) # fix for compatibility # count number of spaces ns = txt.count(' ') # Justification spacewidth = 0 if (align == 'J') and (ns > 0) if (@current_font['type'] == "TrueTypeUnicode") or (@current_font['type'] == "cidfont0") # get string width without spaces width = GetStringWidth(txt.gsub(' ', '')) # calculate average space width spacewidth = -1000 * (w - width - (2 * @c_margin)) / (ns ? ns : 1) / @font_size # set word position to be used with TJ operator txt2 = txt2.gsub(0.chr + 32.chr, ') ' + sprintf('%.3f', spacewidth) + ' (') unicode_justification = true else # get string width width = txwidth spacewidth = ((w - width - (2 * @c_margin)) / (ns ? ns : 1)) * @k # set word spacing rs << sprintf('BT %.3f Tw ET ', spacewidth) end width = w - (2 * @c_margin) end # replace carriage return characters txt2 = txt2.gsub("\r", ' ') case align when 'C' dx = (w - width) / 2.0 when 'R' if @rtl dx = @c_margin else dx = w - width - @c_margin end when 'L' if @rtl dx = w - width - @c_margin else dx = @c_margin end else # 'J' dx = @c_margin end if @rtl xdx = @x - dx - width else xdx = @x + dx end xdk = xdx * k # print text # s << sprintf('BT %.2f %.2f Td [(%s)] TJ ET', xdk, (@h - basefonty) * k, txt2) s << sprintf('BT %.2f %.2f Td %s[(%s)] TJ ET', xdk, (@h - basefonty) * k, opt, txt2) # fix for compatibility if !uniblock.nil? # print overlapping characters as separate string xshift = 0 # horizontal shift ty = (@h - basefonty + (0.2 * @font_size)) * k spw = (w - txwidth - (2 * @c_margin)) / (ns ? ns : 1) uniblock.each_with_index {|uniarr, uk| if (uk % 2) == 0 # x space to skip if spacewidth != 0 # justification shift xshift += uniarr.select {|item| item == 32 }.length * spw end xshift += GetArrStringWidth(uniarr) # + shift justification else # character to print topchr = arrUTF8ToUTF16BE(uniarr, false) topchr = escape(topchr) s << sprintf(' BT %.2f %.2f Td [(%s)] TJ ET', xdk + (xshift * k), ty, topchr) end } end if @underline s << ' ' + dounderlinew(xdx, basefonty, width) end if @linethrough s << ' ' + dolinethroughw(xdx, basefonty, width) end if @overline s << ' ' + dooverlinew(xdx, basefonty, width) end if (@color_flag) s<<' Q'; end if link && ((link.is_a?(String) and !link.empty?) or (link.is_a?(Integer) and link != 0)) # Integer is PDF file Page No. Link(xdx, y + ((h - @font_size) / 2.0), width, @font_size, link, ns) end end # output cell if (s) # output cell rs << s # reset text stretching if stretch > 2 # Reset character horizontal spacing rs << ' BT 0 Tc ET' elsif stretch > 0 # Reset character horizontal scaling rs << ' BT 100 Tz ET' end end # reset word spacing if !((@current_font['type'] == 'TrueTypeUnicode') or (@current_font['type'] == 'cidfont0')) and (align == 'J') rs << ' BT 0 Tw ET' end @lasth = h; if (ln.to_i>0) # Go to the beginning of the next line @y = y + h if (ln == 1) if @rtl @x = @w - @r_margin else @x = @l_margin end end else # go left or right by case if @rtl @x -= w else @x += w end end gstyles = '' + @linestyle_width + ' ' + @linestyle_cap + ' ' + @linestyle_join + ' ' + @linestyle_dash + ' ' + @draw_color + ' ' + @fill_color + "\n" rs = gstyles + rs return rs end protected :getCellCode # # This method allows printing text with line breaks. # They can be automatic (as soon as the text reaches the right border of the cell) or explicit (via the \n character). As many cells as necessary are output, one below the other. # Text can be aligned, centered or justified. The cell block can be framed and the background painted. # # [@param float :w] Width of cells. If 0, they extend up to the right margin of the page. # [@param float :h] Cell minimum height. The cell extends automatically if needed. # [@param string :txt] String to print # [@param mixed :border] # Indicates if borders must be drawn around the cell block. The value can be either a number: # * 0: no border (default) # * 1: frame # or a string containing some or all of the following characters (in any order): # * L: left # * T: top # * R: right # * B: bottom # [@param string :align] # Allows to center or align the text. Possible values are: # * L or empty string: left align # * C: center # * R: right align # * J: justification (default value when :ishtml=false) # [@param int :fill] Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. # [@param int :ln] # Indicates where the current position should go after the call. Possible values are: # * 0: to the right # * 1: to the beginning of the next line [DEFAULT] # * 2: below # [@param float :x] x position in user units # [@param float :y] y position in user units # [@param boolean :reseth] if true reset the last cell height (default true). # [@param int :stretch] # stretch carachter mode: # * 0 = disabled # * 1 = horizontal scaling only if necessary # * 2 = forced horizontal scaling # * 3 = character spacing only if necessary # * 4 = forced character spacing # [@param boolean :ishtml] set to true if :txt is HTML content (default = false). # [@param boolean :autopadding] if true, uses internal padding and automatically adjust it to account for line width. # [@param float :maxh] maximum height. It should be >= :h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when :ishtml=false. # [@return int] Rerurn the number of cells or 1 for html mode. # [@access public] # [@since 1.3] # [@see] # SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak() # def MultiCell(w, h, txt, border=0, align='J', fill=0, ln=1, x='', y='', reseth=true, stretch=0, ishtml=false, autopadding=true, maxh=0) @x ||= 0 w = 0 unless w.is_a?(Numeric) h = 0 unless h.is_a?(Numeric) if empty_string(@lasth) or reseth # set row height @lasth = @font_size * @cell_height_ratio end if !empty_string(y) SetY(y) else y = GetY() end resth = 0 if !@in_footer and (y + h > @page_break_trigger) # spit cell in two pages newh = @page_break_trigger - y resth = h - newh # cell to be printed on the next page h = newh end # get current page number startpage = @page if !empty_string(x) SetX(x) else x = GetX() end if empty_string(w) or (w <= 0) if @rtl w = @x - @l_margin else w = @w - @r_margin - @x end end # store original margin values l_margin = @l_margin r_margin = @r_margin if @rtl SetRightMargin(@w - @x) SetLeftMargin(@x - w) else SetLeftMargin(@x) SetRightMargin(@w - @x - w) end starty = @y if autopadding # Adjust internal padding if @c_margin < (@line_width / 2.0) @c_margin = @line_width / 2.0 end # Add top space if needed if (@lasth - @font_size) < @line_width @y += @line_width / 2.0 end # add top padding @y += @c_margin end if ishtml # ******* Write HTML text writeHTML(txt, true, false, reseth, true, align) nl = 1 else # ******* Write text nl = Write(@lasth, txt, '', 0, align, true, stretch, false, true, maxh) end if autopadding # add bottom padding @y += @c_margin # Add bottom space if needed if (@lasth - @font_size) < @line_width @y += @line_width / 2.0 end end # Get end-of-text Y position currentY = @y # get latest page number end_page = @page if resth > 0 skip = end_page - startpage tmpresth = resth while tmpresth > 0 if skip <= 0 # add a page (or trig AcceptPageBreak() for multicolumn mode) checkPageBreak(@page_break_trigger + 1) end tmpresth -= (@h - @t_margin - @b_margin) skip -= 1 end currentY = @y end_page = @page end # check if a new page has been created if end_page > startpage # design borders around HTML cells. for page in startpage..end_page setPage(page) if page == startpage # first page @y = starty # put cursor at the beginning of cell on the first page h = @h - starty - @b_margin cborder = getBorderMode(border, position='start') elsif page == end_page # last page @y = @t_margin # put cursor at the beginning of last page h = currentY - @t_margin if resth > h h = resth end cborder = getBorderMode(border, position='end') else @y = @t_margin # put cursor at the beginning of the current page h = @h - @t_margin - @b_margin resth -= h cborder = getBorderMode(border, position='middle') end nx = x # account for margin changes if page > startpage if @rtl and (@pagedim[page]['orm'] != @pagedim[startpage]['orm']) nx = x + (@pagedim[page]['orm'] - @pagedim[startpage]['orm']) elsif !@rtl and (@pagedim[page]['olm'] != @pagedim[startpage]['olm']) nx = x + (@pagedim[page]['olm'] - @pagedim[startpage]['olm']) end end SetX(nx) prevLastH = @lasth ccode = getCellCode(w, h, '', cborder, 1, '', fill, '', 0, false) @lasth = prevLastH if (cborder != 0) or (fill == 1) pagebuff = getPageBuffer(@page) pstart = pagebuff[0, @intmrk[@page]] pend = pagebuff[@intmrk[@page]..-1] setPageBuffer(@page, pstart + ccode + "\n" + pend) @intmrk[@page] += (ccode + "\n").length end end else h = [h, currentY - y].max # put cursor at the beginning of text SetY(y) SetX(x) prevLastH = @lasth # design a cell around the text ccode = getCellCode(w, h, '', border, 1, '', fill, '', 0, true) @lasth = prevLastH if (border != 0) or (fill == 1) if !@transfmrk[@page].nil? pagemark = @transfmrk[@page] @transfmrk[@page] += (ccode + "\n").length elsif @in_footer pagemark = @footerpos[@page] @footerpos[@page] += (ccode + "\n").length else pagemark = @intmrk[@page] @intmrk[@page] += (ccode + "\n").length end pagebuff = getPageBuffer(@page) pstart = pagebuff[0, pagemark] pend = pagebuff[pagemark..-1].to_s setPageBuffer(@page, pstart + ccode + "\n" + pend) end end # Get end-of-cell Y position currentY = GetY() # restore original margin values SetLeftMargin(l_margin) SetRightMargin(r_margin) if ln > 0 # Go to the beginning of the next line SetY(currentY) if ln == 2 SetX(x + w) end else # go left or right by case setPage(startpage) @y = y SetX(x + w) end setContentMark() return nl rescue => err Error('MultiCell Error.', err) end alias_method :multi_cell, :MultiCell # # Get the border mode accounting for multicell position (opens bottom side of multicell crossing pages) # [@param mixed :border] # Indicates if borders must be drawn around the cell block. The value can be either a number: # * 0: no border (default) # * 1: frame # or a string containing some or all of the following characters (in any order): # * L: left # * T: top # * R: right # * B: bottom # [@param string multicell position: 'start', 'middle', 'end' # [@return mixed] border mode # [@access protected] # [@since 4.4.002 (2008-12-09)] # def getBorderMode(border, position='start') if !@opencell and (border == 1) return 1 end return 0 if border == 0 cborder = '' case position when 'start' if border == 1 cborder = 'LTR' else if nil != border.index('L') cborder << 'L' end if nil != border.index('T') cborder << 'T' end if nil != border.index('R') cborder << 'R' end if !@opencell and (nil != border.index('B')) cborder << 'B' end end when 'middle' if border == 1 cborder = 'LR' else if nil != border.index('L') cborder << 'L' end if !@opencell and (nil != border.index('T')) cborder << 'T' end if nil != border.index('R') cborder << 'R' end if !@opencell and (nil != border.index('B')) cborder << 'B' end end when 'end' if border == 1 cborder = 'LRB' else if nil != border.index('L') cborder << 'L' end if !@opencell and (nil != border.index('T')) cborder << 'T' end if nil != border.index('R') cborder << 'R' end if nil != border.index('B') cborder << 'B' end end else cborder = border end return cborder end protected :getBorderMode # # This method return the estimated number of lines for print a simple text string in Multicell() method. # [@param string :txt] String for calculating his height # [@param float :w] Width of cells. If 0, they extend up to the right margin of the page. # [@param boolean :reseth] if true reset the last cell height (default false). # [@param boolean :autopadding] if true, uses internal padding and automatically adjust it to account for line width (default true). # [@param float :cellMargin] Internal cell margin, if empty or <= 0, extended up to current pdf cell margin (default ''). # [@param float :lineWidth] Line width, if empty or <= 0, extended up to current pdf line width (default ''). # [@return float] Return the minimal height needed for multicell method for printing the :txt param. # [@author] Alexander Escalona Fernendez, Nicola Asuni # [@access public] # [@since 4.5.011] # def getNumLines(txt, w=0, reseth=false, autopadding=true, cellMargin='', lineWidth='') if empty_string(w) or (w <= 0) if @rtl w = @x - @l_margin else w = @w - @r_margin - @x end end if empty_string(cellMargin) or (cellMargin <= 0) cellMargin = @c_margin end if empty_string(lineWidth) or (lineWidth <= 0) lineWidth = @line_width end if autopadding # adjust internal padding if cellMargin < (lineWidth / 2.0) cellMargin = lineWidth / 2.0 end end wmax = w - (2 * cellMargin) if reseth @lasth = @font_size * @cell_height_ratio end lines = 1 sum = 0 chars = UTF8StringToArray(txt) chars = utf8Bidi(chars, txt, rtl_text_dir) charsWidth = GetArrStringWidth(chars, '', '', 0, true) if @rtl charsWidth.reverse! chars.reverse! end length = chars.length lastSeparator = -1 i = 0 while i < length charWidth = charsWidth[i] if unichr(chars[i]) =~ /\s/ lastSeparator = i end if (sum + charWidth >= wmax) or (chars[i] == 10) # 10 = "\n" = new line lines += 1 if lastSeparator != -1 i = lastSeparator lastSeparator = -1 sum = 0 else sum = charWidth end else sum += charWidth end i += 1 end return lines end alias_method :get_num_lines, :getNumLines # # This method return the estimated needed height for print a simple text string in Multicell() method. # Generally, if you want to know the exact height for a block of content you can use the following technique: # # # store current object # pdf.start_transaction() # # store starting values # start_y = pdf.get_y() # start_page = pdf.get_page() # # call your printing functions with your parameters # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # pdf.multi_cell(w=0, h=0, txt, border=1, align='L', fill=0, ln=1, x='', y='', reseth=true, stretch=0, ishtml=false, autopadding=true, maxh=0) # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # get the new Y # end_y = pdf.get_y() # end_page = pdf.get_page() # # calculate height # height = 0 # if end_page == start_page # height = end_y - start_y # else # start_page.upto(end_page) do |page| # pdf.set_page(page) # if page == start_page # # first page # height = @h - start_y - @b_margin # elsif page == end_page # # last page # height = end_y - @t_margin # else # height = @h - @t_margin - @b_margin # end # end # end # # restore previous object # pdf = pdf.rollbackTransaction() # # [@param float :w] Width of cells. If 0, they extend up to the right margin of the page. # [@param string :txt] String for calculating his height # [@param boolean :reseth] if true reset the last cell height (default false). # [@param boolean :autopadding] if true, uses internal padding and automatically adjust it to account for line width (default true). # [@param float :cellMargin] Internal cell margin, if empty or <= 0, extended up to current pdf cell margin (default ''). # [@param float :lineWidth] Line width, if empty or <= 0, extended up to current pdf line width (default ''). # [@return float] Return the minimal height needed for multicell method for printing the :txt param. # [@author] Nicola Asuni, Alexander Escalona Fern<E1>ndez # [@access public] # def getStringHeight(w, txt, reseth=false, autopadding=true, cellMargin='', lineWidth='') lines = getNumLines(txt, w, reseth, autopadding, cellMargin, lineWidth) height = lines * (@font_size * @cell_height_ratio) if autopadding if empty_string(cellMargin) or (cellMargin <= 0) cellMargin = @c_margin end if empty_string(lineWidth) or (lineWidth <= 0) lineWidth = @line_width end # adjust internal padding if cellMargin < (lineWidth / 2.0) cellMargin = lineWidth / 2.0 end # add top and bottom space if needed if (@lasth - @font_size) < lineWidth height += lineWidth end # add top and bottom padding height += 2 * cellMargin end return height end alias_method :get_string_height, :getStringHeight # # This method prints text from the current position. # [@param float :h] Line height # [@param string :txt] String to print # [@param mixed :link] URL or identifier returned by AddLink() # [@param int :fill] Indicates if the background must be painted (1) or transparent (0). Default value: 0. # [@param string :align] # Allows to center or align the text. Possible values are: # * L or empty string: left align (default value) # * C: center # * R: right align # * J: justify # [@param boolean :ln] if true set cursor at the bottom of the line, otherwise set cursor at the top of the line. # [@param int :stretch] # stretch carachter mode: # * 0 = disabled # * 1 = horizontal scaling only if necessary # * 2 = forced horizontal scaling # * 3 = character spacing only if necessary # * 4 = forced character spacing # [@param boolean :firstline] if true prints only the first line and return the remaining string. # [@param boolean :firstblock] if true the string is the starting of a line. # [@param float :maxh] maximum height. The remaining unprinted text will be returned. It should be >= :h and less then remaining space to the bottom of the page, or 0 for disable this feature. # [@return mixed] Return the number of cells or the remaining string if :firstline = true. # [@access public] # [@since 1.5] # def Write(h, txt, link=nil, fill=0, align='', ln=false, stretch=0, firstline=false, firstblock=false, maxh=0) txt = txt.dup txt.force_encoding('ASCII-8BIT') if txt.respond_to?(:force_encoding) if txt.length == 0 txt = ' ' end # remove carriage returns s = txt.gsub("\r", ''); # check if string contains arabic text if s =~ @@k_re_pattern_arabic arabic = true else arabic = false end # check if string contains RTL text if arabic or isRTLTextDir or (txt =~ @@k_re_pattern_rtl) rtlmode = true else rtlmode = false end # get a char width chrwidth = GetCharWidth('.') # get array of unicode values chars = UTF8StringToArray(s) # get array of chars uchars = UTF8ArrayToUniArray(chars) # get the number of characters nb = chars.size # replacement for SHY character (minus symbol) shy_replacement = 45 shy_replacement_char = unichr(shy_replacement) # widht for SHY replacement shy_replacement_width = GetCharWidth(shy_replacement) # store current position prevx = @x prevy = @y # max Y maxy = @y + maxh - h - (2 * @c_margin) # calculating remaining line width (w) if @rtl w = @x - @l_margin else w = @w - @r_margin - @x end # max column width wmax = w - (2 * @c_margin) if !firstline and (chrwidth > wmax or (GetCharWidth(chars[0]) > wmax)) # a single character do not fit on column return '' end i = 0 # character position j = 0 # current starting position sep = -1 # position of the last blank space shy = false # true if the last blank is a soft hypen (SHY) l = 0 # current string length nl = 0 # number of lines linebreak = false pc = 0 # previous character # for each character while(i<nb) if (maxh > 0) and (@y >= maxy) break end # Get the current character c = chars[i] if (c == 10) # 10 = "\n" = new line #Explicit line break if align == 'J' if @rtl talign = 'R' else talign = 'L' end else talign = align end tmpstr = UniArrSubString(uchars, j, i) if firstline startx = @x tmparr = chars[j, i - j] if rtlmode tmparr = utf8Bidi(tmparr, tmpstr, rtl_text_dir) end linew = GetArrStringWidth(tmparr) tmparr = '' if @rtl @endlinex = startx - linew else @endlinex = startx + linew end w = linew tmpcmargin = @c_margin if maxh == 0 @c_margin = 0 end end if firstblock and isRTLTextDir() tmpstr = tmpstr.rstrip end Cell(w, h, tmpstr, 0, 1, talign, fill, link, stretch) tmpstr = '' if firstline @c_margin = tmpcmargin return UniArrSubString(uchars, i) end nl += 1 j = i + 1 l = 0 sep = -1 shy = false; # account for margin changes if ((@y + @lasth) > @page_break_trigger) and !@in_footer AcceptPageBreak() end w = getRemainingWidth() wmax = w - (2 * @c_margin) else # 160 is the non-breaking space, 173 is SHY (Soft Hypen) if (c != 160) and ((unichr(c) =~ /\s/) or (c == 173)) # update last blank space position sep = i # check if is a SHY if c == 173 shy = true if pc == 45 tmp_shy_replacement_width = 0 tmp_shy_replacement_char = '' else tmp_shy_replacement_width = shy_replacement_width tmp_shy_replacement_char = shy_replacement_char end else shy = false end end # update string length if ((@current_font['type'] == 'TrueTypeUnicode') or (@current_font['type'] == 'cidfont0')) and arabic # with bidirectional algorithm some chars may be changed affecting the line length # *** very slow *** l = GetArrStringWidth(utf8Bidi(chars[j,i-j], '', rtl_text_dir)) else l += GetCharWidth(c) end if (l > wmax) or ((c == 173) and ((l + tmp_shy_replacement_width) > wmax)) # we have reached the end of column if (sep == -1) # check if the line was already started if (@rtl and (@x <= @w - @r_margin - chrwidth)) or (!@rtl and (@x >= @l_margin + chrwidth)) # print a void cell and go to next line Cell(w, h, '', 0, 1) linebreak = true if firstline return UniArrSubString(uchars, j) end else # truncate the word because do not fit on column tmpstr = UniArrSubString(uchars, j, i) if firstline startx = @x tmparr = chars[j, i - j] if rtlmode tmparr = utf8Bidi(tmparr, tmpstr, rtl_text_dir) end linew = GetArrStringWidth(tmparr) tmparr = '' if @rtl @endlinex = startx - linew else @endlinex = startx + linew end w = linew tmpcmargin = @c_margin if maxh == 0 @c_margin = 0 end end if firstblock and isRTLTextDir() tmpstr = tmpstr.rstrip end Cell(w, h, tmpstr, 0, 1, align, fill, link, stretch) tmpstr = '' if firstline @c_margin = tmpcmargin return UniArrSubString(uchars, i) end j = i i -= 1 end else # word wrapping if @rtl and !firstblock endspace = 1 else endspace = 0 end if shy # add hypen (minus symbol) at the end of the line shy_width = tmp_shy_replacement_width if @rtl shy_char_left = tmp_shy_replacement_char shy_char_right = '' else shy_char_left = '' shy_char_right = tmp_shy_replacement_char end else shy_width = 0 shy_char_left = '' shy_char_right = '' end tmpstr = UniArrSubString(uchars, j, sep + endspace) if firstline startx = @x tmparr = chars[j, sep + endspace - j] if rtlmode tmparr = utf8Bidi(tmparr, tmpstr, rtl_text_dir) end linew = GetArrStringWidth(tmparr) tmparr = '' if @rtl @endlinex = startx - linew - shy_width else @endlinex = startx + linew + shy_width end w = linew tmpcmargin = @c_margin if maxh == 0 @c_margin = 0 end end # print the line if firstblock and isRTLTextDir() tmpstr = tmpstr.rstrip end Cell(w, h, shy_char_left + tmpstr + shy_char_right, 0, 1, align, fill, link, stretch) tmpstr = '' if firstline # return the remaining text @c_margin = tmpcmargin return UniArrSubString(uchars, sep + endspace) end i = sep sep = -1 shy = false j = i + 1 end # account for margin changes if (@y + @lasth > @page_break_trigger) and !@in_footer AcceptPageBreak() end w = getRemainingWidth() wmax = w - (2 * @c_margin) if linebreak linebreak = false else nl += 1 l = 0 end end end # save last character pc = c i +=1 end # end while i < nb # print last substring (if any) if l > 0 case align when 'J' , 'C' w = w when 'L' if @rtl w = w else w = l end when 'R' if @rtl w = l else w = w end else w = l end tmpstr = UniArrSubString(uchars, j, nb) if firstline startx = @x tmparr = chars[j, nb - j] if rtlmode tmparr = utf8Bidi(tmparr, tmpstr, rtl_text_dir) end linew = GetArrStringWidth(tmparr) tmparr = '' if @rtl @endlinex = startx - linew else @endlinex = startx + linew end w = linew tmpcmargin = @c_margin if maxh == 0 @c_margin = 0 end end if firstblock and isRTLTextDir() tmpstr = tmpstr.rstrip end Cell(w, h, tmpstr, 0, (ln ? 1 : 0), align, fill, link, stretch) tmpstr = '' if firstline @c_margin = tmpcmargin return UniArrSubString(uchars, nb) end nl += 1 end if firstline return '' end return nl rescue => err Error('Write Error.', err) end alias_method :write, :Write # # Returns the remaining width between the current position and margins. # [@return int] Return the remaining width # [@access protected] # def getRemainingWidth() if @rtl return @x - @l_margin else return @w - @r_margin - @x end end protected :getRemainingWidth # # Extract a slice of the :strarr array and return it as string. # [@param string :strarr] The input array of characters. (UCS4) # [@param int :start] the starting element of :strarr. # [@param int :last] first element that will not be returned. # [@return] Return part of a string (UTF-8) # [@access public] # def UTF8ArrSubString(strarr, start=0, last=strarr.size) string = "" start.upto(last - 1) do |i| string << unichr(strarr[i]) end return string end alias_method :utf8_arr_sub_string, :UTF8ArrSubString # # Extract a slice of the :uniarr array and return it as string. # [@param string :uniarr] The input array of characters. (UTF-8) # [@param int :start] the starting element of :strarr. # [@param int :las]t first element that will not be returned. # [@return] Return part of a string (UTF-8) # [@access public] # [@since 4.5.037 (2009-04-07)] # def UniArrSubString(uniarr, start=0, last=uniarr.length) string = '' start.upto(last - 1) do |i| string << uniarr[i] end return string end alias_method :uni_arr_sub_string, :UniArrSubString # # Convert an array of UTF8 values to array of unicode characters # [@param string :ta] The input array of UTF8 values. (UCS4) # [@return] Return array of unicode characters (UTF-8) # [@access public] # [@since 4.5.037 (2009-04-07)] # def UTF8ArrayToUniArray(ta) string = [] ta.each do |i| string << unichr(i) end return string end alias_method :utf8_array_to_uni_array, :UTF8ArrayToUniArray # # Returns the unicode caracter specified by UTF-8 value # [@param int :c] UTF-8 value (UCS4) # [@return] Returns the specified character. (UTF-8) # [@author] Miguel Perez, Nicola Asuni # [@access public] # [@since 2.3.000 (2008-03-05)] # def unichr(c) if !@is_unicode return c.chr elsif c <= 0x7F # one byte return c.chr elsif c <= 0x7FF # two bytes return (0xC0 | c >> 6).chr + (0x80 | c & 0x3F).chr elsif c <= 0xFFFF # three bytes return (0xE0 | c >> 12).chr + (0x80 | c >> 6 & 0x3F).chr + (0x80 | c & 0x3F).chr elsif c <= 0x10FFFF # four bytes return (0xF0 | c >> 18).chr + (0x80 | c >> 12 & 0x3F).chr + (0x80 | c >> 6 & 0x3F).chr + (0x80 | c & 0x3F).chr else return "" end end # # Return the image type given the file name or array returned by getimagesize() function. # [@param string :imgfile] image file name # [@param hash :iminfo] array of image information returned by getimagesize() function. # [@return string] image type # [@access public] # [@since 4.8.017 (2009-11-27)] # def getImageFileType(imgfile, iminfo={}) if iminfo.is_a? Hash and iminfo['mime'] and !iminfo['mime'].empty? mime = iminfo['mime'].split('/') if (mime.length > 1) and (mime[0] == 'image') and !mime[1].empty? return mime[1].strip end end type = '' return type if imgfile.nil? fileinfo = File::extname(imgfile) type = fileinfo.sub(/^\./, '').downcase if fileinfo != '' if type == 'jpg' type = 'jpeg' end return type end alias_method :get_image_file_type, :getImageFileType # # Puts an image in the page. # The upper-left corner must be given. # The dimensions can be specified in different ways: # * explicit width and height (expressed in user unit) # * one explicit dimension, the other being calculated automatically in order to keep the original proportions # * no explicit dimension, in which case the image is put at 72 dpi # Supported formats are PNG images whitout RMagick library and JPEG and GIF images supported by RMagick. # For JPEG, all flavors are allowed: # * gray scales # * true colors (24 bits) # * CMYK (32 bits) # For PNG, are allowed: # * gray scales on at most 8 bits (256 levels) # * indexed colors # * true colors (24 bits) # If a transparent color is defined, it will be taken into account (but will be only interpreted by Acrobat 4 and above). # The format can be specified explicitly or inferred from the file extension. # It is possible to put a link on the image. # * Remark: if an image is used several times, only one copy will be embedded in the file. # [@param string :file] Name of the file containing the image. # [@param float :x] Abscissa of the upper-left corner. # [@param float :y] Ordinate of the upper-left corner. # [@param float :w] Width of the image in the page. If not specified or equal to zero, it is automatically calculated. # [@param float :h] Height of the image in the page. If not specified or equal to zero, it is automatically calculated. # [@param string :type] Image format. Possible values are (case insensitive): JPG, JPEG, PNG. If not specified, the type is inferred from the file extension. # [@param mixed :link] URL or identifier returned by AddLink(). # [@param string :align] # Indicates the alignment of the pointer next to image insertion relative to image height. The value can be: # * T: top-right for LTR or top-left for RTL # * M: middle-right for LTR or middle-left for RTL # * B: bottom-right for LTR or bottom-left for RTL # * N: next line # [@param mixed :resize] If true resize (reduce) the image to fit :w and :h (requires RMagick library); if false do not resize; if 2 force resize in all cases (upscaling and downscaling). # [@param int :dpi] dot-per-inch resolution used on resize # [@param string :palign] # Allows to center or align the image on the current line. Possible values are: # * L : left align # * C : center # * R : right align # * '' : empty string : left for LTR or right for RTL # [@param boolean :ismask] true if this image is a mask, false otherwise # [@param mixed :imgmask] image object returned by this function or false # [@param mixed :border] # Indicates if borders must be drawn around the image. The value can be either a number: # * 0: no border (default) # * 1: frame # or a string containing some or all of the following characters (in any order): # * L: left # * T: top # * R: right # * B: bottom # [@param boolean :fitbox] If true scale image dimensions proportionally to fit within the (:w, :h) box. # [@param boolean :hidden] if true do not display the image. # [@param boolean :fitonpage] if true the image is resized to not exceed page dimensions. # [@return] image information # [@access public] # [@since 1.1] # def Image(file, x='', y='', w=0, h=0, type='', link=nil, align='', resize=false, dpi=300, palign='', ismask=false, imgmask=false, border=0, fitbox=false, hidden=false, fitonpage=false) w = 0 if w == '' h = 0 if h == '' x = @x if x == '' y = @y if y == '' # set bottomcoordinates @img_rb_y = y + h Error('Image filename is empty.') if file.nil? or file.length == 0 unless File.exist?(file) Error('Image file is not found. : ' + file) end # get image dimensions imsize = getimagesize(file) if imsize.nil? or imsize == false # encode spaces on filename file = file.gsub(' ', '%20') imsize = getimagesize(file) if imsize.nil? Error("Can't get image dimension. : " + file) elsif imsize == false if (w > 0) and (h > 0) pw = getHTMLUnitToUnits(w, 0, @pdfunit, true) * @img_scale * @k ph = getHTMLUnitToUnits(h, 0, @pdfunit, true) * @img_scale * @k imsize = [pw, ph] else Error('Unable to get image width and height: ' + file) end end end # get original image width and height in pixels pixw = imsize[0] pixh = imsize[1] # calculate image width and height on document if (w <= 0) and (h <= 0) # convert image size to document unit w = pixelsToUnits(pixw) h = pixelsToUnits(pixh) elsif w <= 0 w = h * pixw / pixh elsif h <= 0 h = w * pixh / pixw elsif fitbox and (w > 0) and (h > 0) # scale image dimensions proportionally to fit within the (:w, :h) box if ((w * pixh) / (h * pixw)) < 1 h = w * pixh / pixw else w = h * pixw / pixh end end # resize image to be contained on a single page # fix at page break case. if fitonpage ratio_wh = w / h if (@t_margin + h) > @page_break_trigger h = @page_break_trigger - @t_margin w = h * ratio_wh end if !@rtl and ((x + w) > (@w - @r_margin)) w = @w - @r_margin - x h = w / ratio_wh elsif @rtl and ((x - w) < @l_margin) w = x - @l_margin h = w / ratio_wh end end # Check whether we need a new page first as this does not fit prev_x = @x if checkPageBreak(h, y) y = @y y += @c_margin if !empty_string(@thead) and !@in_thead ### fix ### if @rtl x += prev_x - @x else x += @x - prev_x end end # resize image to be contained on a single page if fitonpage ratio_wh = w / h if (y + h) > @page_break_trigger h = @page_break_trigger - y w = h * ratio_wh end if !@rtl and ((x + w) > (@w - @r_margin)) w = @w - @r_margin - x h = w / ratio_wh elsif @rtl and ((x - w) < @l_margin) w = x - @l_margin h = w / ratio_wh end end # calculate new minimum dimensions in pixels neww = (w * @k * dpi / @dpi).round newh = (h * @k * dpi / @dpi).round # check if resize is necessary (resize is used only to reduce the image) newsize = neww * newh pixsize = pixw * pixh if resize == 2 resize = true elsif newsize >= pixsize resize = false end # check if image has been already added on document newimage = true if @imagekeys.include?(file) newimage = false; # get existing image data info = getImageBuffer(file) # check if the newer image is larger oldsize = info['w'] * info['h'] if ((oldsize < newsize) and resize) or ((oldsize < pixsize) and !resize) newimage = true end end if newimage #First use of image, get info if (type == '') type = getImageFileType(file, imsize) else type = type.downcase type = 'jpeg' if type == 'jpg' end info = false if !resize if (type == 'jpeg') info=parsejpeg(file) elsif (type == 'png') info=parsepng(file); elsif (type == 'gif') tmpFile = imageToPNG(file) if tmpFile != false info=parsepng(tmpFile.path) tmpFile.close(true) end else #Allow for additional formats mtd='parse' + type; unless self.respond_to?(mtd, true) Error('Unsupported image type: ' + type); end info=send(mtd, file); end if info == 'pngalpha' and ismask == false and Object.const_defined?(:Magick) info = ImagePngAlpha(file, x, y, w, h, 'PNG', link, align, resize, dpi, palign) if false != info return true end end end if !info if Object.const_defined?(:Magick) # RMagick library ### T.B.D ### TCPDF 5.0.000 ### # if type == 'SVG' # else img = Magick::ImageList.new(file) # end if resize img.resize(neww,newh) end img.format = 'JPEG' tmpname = Tempfile.new(File::basename(file), @@k_path_cache) tmpname.binmode jpeg_quality = @jpeg_quality tmpname.print img.to_blob { self.quality = jpeg_quality } tmpname.fsync info = parsejpeg(tmpname.path) tmpname.close(true) else return false end end if info == false # If false, we cannot process image return false end if ismask # force grayscale info['cs'] = 'DeviceGray' end info['i'] = @numimages if !@imagekeys.include?(file) info['i'] += 1 end if imgmask != false info['masked'] = imgmask end # add image to document setImageBuffer(file, info) end # set alignment @img_rb_y = y + h # set alignment if @rtl if palign == 'L' ximg = @l_margin elsif palign == 'C' ximg = (@w + @l_margin - @r_margin - w) / 2.0 elsif palign == 'R' ximg = @w - @r_margin - w else ximg = x - w end @img_rb_x = ximg else if palign == 'L' ximg = @l_margin elsif palign == 'C' ximg = (@w + @l_margin - @r_margin - w) / 2.0 elsif palign == 'R' ximg = @w - @r_margin - w else ximg = x end @img_rb_x = ximg + w end if ismask or hidden # image is not displayed return info['i'] end xkimg = ximg * @k out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q', w * @k, h * @k, xkimg, (@h - (y + h)) * @k, info['i'])) if border != 0 bx = x by = y @x = ximg if @rtl @x += w end @y = y Cell(w, h, '', border, 0, '', 0, '', 0) @x = bx @y = by end if link and !link.empty? Link(ximg, y, w, h, link) end # set pointer to align the successive text/objects case align when 'T' @y = y @x = @img_rb_x when 'M' @y = y + (h/2.0).round @x = @img_rb_x when 'B' @y = @img_rb_y @x = @img_rb_x when 'N' SetY(@img_rb_y) end @endlinex = @img_rb_x return info['i'] end alias_method :image, :Image # # Extract info from a JPEG file # [@access protected] # def parsejpeg(file) a=getimagesize(file); if a == false or a.nil? or a.empty? Error('Missing or incorrect image file: ' + file); end if (a[2]!='JPEG') Error('Not a JPEG file: ' + file); end if (a['channels'].nil? or a['channels']==3) colspace='DeviceRGB'; elsif (a['channels']==4) colspace='DeviceCMYK'; else colspace='DeviceGray'; end bpc=!a['bits'].nil? ? a['bits'] : 8; #Read whole file data=''; open(file,'rb') do |f| data << f.read() end return {'w' => a[0],'h' => a[1],'cs' => colspace,'bpc' => bpc,'f'=>'DCTDecode','data' => data} end protected :parsejpeg def imageToPNG(file) img = Magick::ImageList.new(file) img.format = 'PNG' # convert to PNG from gif if img.alpha? img.alpha = Magick::DeactivateAlphaChannel # PNG alpha channel delete if img.alpha? return false end end #use a temporary file.... tmpFile = Tempfile.new(['', '_' + File::basename(file) + '.png'], @@k_path_cache); tmpFile.binmode tmpFile.print img.to_blob tmpFile.fsync tmpFile end protected :imageToPNG # # Extract info from a PNG file # [@access protected] # def parsepng(file) f=open(file,'rb'); #Check signature if (f.read(8)!=137.chr + 'PNG' + 13.chr + 10.chr + 26.chr + 10.chr) Error('Not a PNG file: ' + file); end #Read header chunk f.read(4); if (f.read(4)!='IHDR') Error('Incorrect PNG file: ' + file); end w=freadint(f); h=freadint(f); bpc=f.read(1).unpack('C')[0] ct=f.read(1).unpack('C')[0] if (ct==0) colspace='DeviceGray'; elsif (ct==2) colspace='DeviceRGB'; elsif (ct==3) colspace='Indexed'; else if Object.const_defined?(:Magick) # alpha channel return 'pngalpha' else Error('No RMagick: Alpha channel not supported: ' + file); end end if (f.read(1).unpack('C')[0] != 0) # Error('Unknown compression method: ' + file) return false end if (f.read(1).unpack('C')[0]!=0) # Error('Unknown filter method: ' + file) return false end if (bpc>8) if Object.const_defined?(:Magick) return false else Error('No RMagick: 16-bit depth not supported: ' + file) end end if (f.read(1).unpack('C')[0]!=0) # Error('Interlacing not supported: ' + file) return false end f.read(4); parms='/DecodeParms <</Predictor 15 /Colors ' + (ct==2 ? 3 : 1).to_s + ' /BitsPerComponent ' + bpc.to_s + ' /Columns ' + w.to_s + '>>'; #Scan chunks looking for palette, transparency and image data pal=''; trns=''; data=''; begin n=freadint(f); type=f.read(4); if (type=='PLTE') #Read palette pal=f.read( n); f.read(4); elsif (type=='tRNS') #Read transparency info t=f.read( n); if (ct==0) trns = t[1].unpack('C')[0] elsif (ct==2) trns = t[[1].unpack('C')[0], t[3].unpack('C')[0], t[5].unpack('C')[0]] else pos=t.include?(0.chr); if (pos!=false) trns = ['1'] end end f.read(4); elsif (type=='IDAT') #Read image data block data<<f.read( n); f.read(4); elsif (type=='IEND') break; else f.read( n+4); end end while(n) if (colspace=='Indexed' and pal.empty?) # Error('Missing palette in ' + file) return false end return {'w' => w, 'h' => h, 'cs' => colspace, 'bpc' => bpc, 'f'=>'FlateDecode', 'parms' => parms, 'pal' => pal, 'trns' => trns, 'data' => data} ensure f.close unless f.nil? end protected :parsepng def image_alpha_mask(file) img = Magick::ImageList.new(file) if img.alpha? img.alpha = Magick::ExtractAlphaChannel # PNG alpha channel Mask else return false end #use a temporary file.... tmpFile = Tempfile.new(['msk_', '.png'], @@k_path_cache) tmpFile.binmode tmpFile.print img.to_blob tmpFile.fsync tmpFile end protected :image_alpha_mask # # Extract info from a PNG image with alpha channel using the GD library. # [@param string :file] Name of the file containing the image. # [@param float :x] Abscissa of the upper-left corner. # [@param float :y] Ordinate of the upper-left corner. # [@param float :w] Width of the image in the page. If not specified or equal to zero, it is automatically calculated. # [@param float :h] Height of the image in the page. If not specified or equal to zero, it is automatically calculated. # [@param string :type] Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension. # [@param mixed :link] URL or identifier returned by AddLink(). # [@param string :align] # Indicates the alignment of the pointer next to image insertion relative to image height. The value can be: # * T: top-right for LTR or top-left for RTL # * M: middle-right for LTR or middle-left for RTL # * B: bottom-right for LTR or bottom-left for RTL # * N: next line # [@param boolean :resize] If true resize (reduce) the image to fit :w and :h (requires GD library). # [@param int :dpi] dot-per-inch resolution used on resize # [@param string :palign] # Allows to center or align the image on the current line. Possible values are: # * L : left align # * C : center # * R : right align # * '' : empty string : left for LTR or right for RTL # [@author] Valentin Schmidt, Nicola Asuni # [@access protected] # [[@since 4.3.007 (2008-12-04)] # [@see] Image() # def ImagePngAlpha(file, x='', y='', w=0, h=0, type='', link='', align='', resize=false, dpi=300, palign='') tempfile_plain = imageToPNG(file) if tempfile_plain == false return false end tempfile_alpha = image_alpha_mask(file) if tempfile_alpha == false return false end # embed mask image imgmask = Image(tempfile_alpha.path, x, y, w, h, 'PNG', '', '', resize, dpi, '', true, false) tempfile_alpha.close(true) # embed image, masked with previously embedded mask Image(tempfile_plain.path, x, y, w, h, type, link, align, resize, dpi, palign, false, imgmask) tempfile_plain.close(true) return true end protected :ImagePngAlpha # # Performs a line break. # The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter. # [@param float :h] The height of the break. By default, the value equals the height of the last printed cell. # [@param boolean :cell] if true add a c_margin to the x coordinate # [@access public] # [@since 1.0] # [@see] Cell() # def Ln(h='', cell=false) if (@num_columns > 0) and (@y == @columns[@current_column]['y']) and @columns[@current_column]['x'] and (@x == @columns[@current_column]['x']) # revove vertical space from the top of the column return end if cell cellmargin = @c_margin else cellmargin = 0 end if @rtl @x = @w - @r_margin - cellmargin else @x = @l_margin + cellmargin end if h.is_a?(String) @y += @lasth else @y += h end @newline = true end alias_method :ln, :Ln # # Returns the relative X value of current position. # The value is relative to the left border for LTR languages and to the right border for RTL languages. # [@return float] # [@access public] # [@since 1.2] # [@see] SetX(), GetY(), SetY() # def GetX() if @rtl return @w - @x else return @x end end alias_method :get_x, :GetX # # Returns the absolute X value of current position. # [@return float] # [@access public] # [@since 1.2] # [@see] SetY(), GetX(), SetX() # def GetAbsX() return @x end alias_method :get_abs_x, :GetAbsX # # Returns the ordinate of the current position. # [@return float] # [@access public] # [@since 1.0] # [@see] SetY(), GetX(), SetX() # def GetY() return @y; end alias_method :get_y, :GetY # # Defines the abscissa of the current position. # If the passed value is negative, it is relative to the right of the page (or left if language is RTL). # [@param float :x] The value of the abscissa. # [@param boolean :rtloff] if true always uses the page top-left corner as origin of axis. # [@access public] # [@since 1.2] # [@see] GetX(), GetY(), SetY(), SetXY() # def SetX(x, rtloff=false) #Set x position if !rtloff and @rtl if x >= 0 @x = @w - x else @x = x.abs end else if x >= 0 @x = x else @x = @w + x end end end alias_method :set_x, :SetX # # Moves the current abscissa back to the left margin and sets the ordinate. # If the passed value is negative, it is relative to the bottom of the page. # [@param float :y] The value of the ordinate. # [@param bool :resetx] if true (default) reset the X position. # [@param boolean :rtloff] if true always uses the page top-left corner as origin of axis. # [@access public] # [@since 1.0] # [@see] GetX(), GetY(), SetY(), SetXY() # def SetY(y, resetx=true, rtloff=false) if resetx # reset x if !rtloff and @rtl @x = @w - @r_margin else @x = @l_margin end end if (y>=0) @y = y; else @y=@h+y; end if @y < 0 @y = 0 end if @y > @h @y = @h end end alias_method :set_y, :SetY # # Defines the abscissa and ordinate of the current position. If the passed values are negative, they are relative respectively to the right and bottom of the page. # [@param float :x] The value of the abscissa. # [@param float :y] The value of the ordinate. # [@param boolean :rtloff] if true always uses the page top-left corner as origin of axis. # [@access public] # [@since 1.2] # [@see] SetX(), SetY() # def SetXY(x, y, rtloff=false) SetY(y, false, rtloff) SetX(x, rtloff) end alias_method :set_xy, :SetXY # # Send the document to a given destination: string, local file or browser. # In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced. # The method first calls Close() if necessary to terminate the document. # [@param string :name] The name of the file when saved. Note that special characters are removed and blanks characters are replaced with the underscore character. # [@param string :dest] # Destination where to send the document. It can take one of the following values: # * I: send the file inline to the browser (default). The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF. # * D: send to the browser and force a file download with the name given by name. # * F: save to a local server file with the name given by name. # * S: return the document as a string. name is ignored. # * FI: equivalent to F + I option # * FD: equivalent to F + D option # [@access public] # [@since 1.0] # [@see] Close() # def Output(name='', dest='') #Output PDF to some destination #Finish document if necessary lastPage() if (@state < 3) Close(); end #Normalize parameters # Boolean no longer supported # if (dest.is_a?(Boolean)) # dest = dest ? 'D' : 'F'; # end dest = dest.upcase if (dest=='') if (name=='') name='doc.pdf'; dest='I'; else dest='F'; end end case (dest) when 'I' # This is PHP specific code ##Send to standard output # if (ob_get_contents()) # Error('Some data has already been output, can\'t send PDF file'); # end # if (php_sapi_name()!='cli') # #We send to a browser # header('Content-Type: application/pdf'); # if (headers_sent()) # Error('Some data has already been output to browser, can\'t send PDF file'); # end # header('Content-Length: ' + @buffer.length); # header('Content-disposition: inline; filename="' + name + '"'); # end return getBuffer when 'D' # PHP specific #Download file # if (ob_get_contents()) # Error('Some data has already been output, can\'t send PDF file'); # end # if (!_SERVER['HTTP_USER_AGENT'].nil? && SERVER['HTTP_USER_AGENT'].include?('MSIE')) # header('Content-Type: application/force-download'); # else # header('Content-Type: application/octet-stream'); # end # if (headers_sent()) # Error('Some data has already been output to browser, can\'t send PDF file'); # end # header('Content-Length: '+ @buffer.length); # header('Content-disposition: attachment; filename="' + name + '"'); return getBuffer when 'F', 'FI', 'FD' # Save PDF to a local file if @diskcache FileUtils.copy(@buffer.path, name) else open(name,'wb') do |f| f.write(@buffer) end end if dest == 'FI' # This is PHP specific code # # send headers to browser # header('Content-Type: application/pdf') # header('Cache-Control: public, must-revalidate, max-age=0') # HTTP/1.1 # header('Pragma: public') # header('Expires: Sat, 26 Jul 1997 05:00:00 GMT') # Date in the past # header('Last-Modified: ' + gmdate('D, d M Y H:i:s') + ' GMT') # header('Content-Length: ' + filesize(name)) # header('Content-Disposition: inline; filename="' + File.basename(name) + '";') # send document to the browser data = '' open(name) do |f| data<< f.read ;end return data elsif dest == 'FD' # This is PHP specific code # # send headers to browser # if ob_get_contents() # Error('Some data has already been output, can\'t send PDF file') # end # header('Content-Description: File Transfer') # if headers_sent()) # Error('Some data has already been output to browser, can\'t send PDF file') # end # header('Cache-Control: public, must-revalidate, max-age=0') # HTTP/1.1 # header('Pragma: public') # header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); # Date in the past # header('Last-Modified: ' + gmdate('D, d M Y H:i:s') + ' GMT') # # force download dialog # header('Content-Type: application/force-download') # header('Content-Type: application/octet-stream', false) # header('Content-Type: application/download', false) # header('Content-Type: application/pdf', false) # # use the Content-Disposition header to supply a recommended filename # header('Content-Disposition: attachment; filename="' + File.basename(name) + '";') # header('Content-Transfer-Encoding: binary') # header('Content-Length: ' + filesize(name)) # send document to the browser data = '' open(name) do |f| data<< f.read ;end return data end when 'S' # Returns PDF as a string return getBuffer else Error('Incorrect output destination: ' + dest); end return ''; ensure destroy(true) end alias_method :output, :Output # # Unset all class variables except the following critical variables: internal_encoding, state, bufferlen, buffer and diskcache. # [@param boolean :destroyall] if true destroys all class variables, otherwise preserves critical variables. # [@param boolean :preserve_objcopy] if true preserves the objcopy variable # [@access public] # def destroy(destroyall=false, preserve_objcopy=false) if destroyall and @diskcache and !preserve_objcopy and !empty_string(@buffer.path) # remove buffer file from cache File.delete(@buffer.path) end self.instance_variables.each { |val| if destroyall or ((val != '@internal_encoding') and (val != '@state') and (val != '@bufferlen') and (val != '@buffer') and (val != '@diskcache') and (val != '@sign') and (val != '@signature_data') and (val != '@signature_max_length') and (val != '@byterange_string')) if (!preserve_objcopy or (val.to_s != '@objcopy')) and !val.nil? eval("#{val} = nil") end end } end # Protected methods protected # # Check for locale-related bug # [@access protected] # def dochecks() #Check for locale-related bug if (1.1==1) Error('Don\'t alter the locale before including class file'); end #Check for decimal separator if (sprintf('%.1f',1.0)!='1.0') setlocale(LC_NUMERIC,'C'); end end # # Return fonts path # [@access protected] # def getfontpath(file='') # Is it in the @@k_path_fonts? if @@k_path_fonts fpath = File.join @@k_path_fonts, file if File.exist?(fpath) return fpath end end # Is it in this plugin's font folder? fpath = File.join File.dirname(__FILE__), 'fonts', file if File.exist?(fpath) return fpath end # Could not find it. nil end # # Start document # [@access protected] # def begindoc() #Start document @state=1; out('%PDF-' + @pdf_version) end # # putpages # [@access protected] # def putpages() nb = @numpages if @alias_nb_pages nbs = formatPageNumber(nb) nbu = UTF8ToUTF16BE(nbs, false) # replacement for unicode font alias_a = escape(@alias_nb_pages) alias_au = escape('{' + @alias_nb_pages + '}') if @is_unicode alias_b = escape(UTF8ToLatin1(@alias_nb_pages)) alias_bu = escape(UTF8ToLatin1('{' + @alias_nb_pages + '}')) alias_c = escape(utf8StrRev(@alias_nb_pages, false, rtl_text_dir)) alias_cu = escape(utf8StrRev('{' + @alias_nb_pages + '}', false, rtl_text_dir)) end end if @alias_num_page alias_pa = escape(@alias_num_page) alias_pau = escape('{' + @alias_num_page + '}') if @is_unicode alias_pb = escape(UTF8ToLatin1(@alias_num_page)) alias_pbu = escape(UTF8ToLatin1('{' + @alias_num_page + '}')) alias_pc = escape(utf8StrRev(@alias_num_page, false, rtl_text_dir)) alias_pcu = escape(utf8StrRev('{' + @alias_num_page + '}', false, rtl_text_dir)) end end pagegroupnum = 0 filter=(@compress) ? '/Filter /FlateDecode ' : '' 1.upto(nb) do |n| temppage = getPageBuffer(n) if !@pagegroups.empty? if !@newpagegroup[n].nil? pagegroupnum = 0 end pagegroupnum += 1 @pagegroups.each { |k, v| # replace total pages group numbers vs = formatPageNumber(v) vu = UTF8ToUTF16BE(vs, false) alias_ga = escape(k) alias_gau = escape('{' + k + '}') if @is_unicode alias_gb = escape(UTF8ToLatin1(k)) alias_gbu = escape(UTF8ToLatin1('{' + k + '}')) alias_gc = escape(utf8StrRev(k.dup, false, rtl_text_dir)) alias_gcu = escape(utf8StrRev('{' + k + '}', false, rtl_text_dir)) end temppage = temppage.gsub(alias_gau, vu) if @is_unicode temppage = temppage.gsub(alias_gbu, vu) temppage = temppage.gsub(alias_gcu, vu) temppage = temppage.gsub(alias_gb, vs) temppage = temppage.gsub(alias_gc, vs) end temppage = temppage.gsub(alias_ga, vs) # replace page group numbers pvs = formatPageNumber(pagegroupnum) pvu = UTF8ToUTF16BE(pvs, false) pk = k.gsub('{nb', '{pnb') alias_pga = escape(pk) alias_pgau = escape('{' + pk + '}') if @is_unicode alias_pgb = escape(UTF8ToLatin1(pk)) alias_pgbu = escape(UTF8ToLatin1('{' + pk + '}')) alias_pgc = escape(utf8StrRev(pk, false, rtl_text_dir)) alias_pgcu = escape(utf8StrRev('{' + pk + '}', false, rtl_text_dir)) end temppage = temppage.gsub(alias_pgau, pvu) if @is_unicode temppage = temppage.gsub(alias_pgbu, pvu) temppage = temppage.gsub(alias_pgcu, pvu) temppage = temppage.gsub(alias_pgb, pvs) temppage = temppage.gsub(alias_pgc, pvs) end temppage = temppage.gsub(alias_pga, pvs) } end if @alias_nb_pages # replace total pages number temppage = temppage.gsub(alias_au, nbu) if @is_unicode temppage = temppage.gsub(alias_bu, nbu) temppage = temppage.gsub(alias_cu, nbu) temppage = temppage.gsub(alias_b, nbs) temppage = temppage.gsub(alias_c, nbs) end temppage = temppage.gsub(alias_a, nbs) end if @alias_num_page # replace page number pnbs = formatPageNumber(n) pnbu = UTF8ToUTF16BE(pnbs, false) # replacement for unicode font temppage = temppage.gsub(alias_pau, pnbu) if @is_unicode temppage = temppage.gsub(alias_pbu, pnbu) temppage = temppage.gsub(alias_pcu, pnbu) temppage = temppage.gsub(alias_pb, pnbs) temppage = temppage.gsub(alias_pc, pnbs) end temppage = temppage.gsub(alias_pa, pnbs) end temppage = temppage.gsub(@epsmarker, '') #Page @page_obj_id[n] = newobj() out = '<<' out << ' /Type /Page' out << ' /Parent 1 0 R' out << ' /LastModified ' + datestring() out << ' /Resources 2 0 R' boxes = ['MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox'] boxes.each { |box| out << ' /' + box out << sprintf(' [%.2f %.2f %.2f %.2f]', @pagedim[n][box]['llx'], @pagedim[n][box]['lly'], @pagedim[n][box]['urx'], @pagedim[n][box]['ury']) } if @pagedim[n]['BoxColorInfo'] and !@pagedim[n]['BoxColorInfo'].empty? out << ' /BoxColorInfo <<' boxes.each { |box| if @pagedim[n]['BoxColorInfo'][box] out << ' /' + box + ' <<' if @pagedim[n]['BoxColorInfo'][box]['C'] color = @pagedim[n]['BoxColorInfo'][box]['C'] out <<= ' /C [' out << sprintf(' %.3f %.3f %.3f', color[0]/255.0, color[1]/255.0, color[2]/255.0) out << ' ]' end if @pagedim[n]['BoxColorInfo'][box]['W'] out << ' /W ' + (@pagedim[n]['BoxColorInfo'][box]['W'] * @k).to_s end if @pagedim[n]['BoxColorInfo'][box]['S'] out << ' /S /' + @pagedim[n]['BoxColorInfo'][box]['S'] end if @pagedim[n]['BoxColorInfo'][box]['D'] dashes = @pagedim[n]['BoxColorInfo'][box]['D'] out << ' /D [' dashes.each { |dash| out << sprintf(' %.3f', dash * @k) } out << ' ]' end out << ' >>' end } out << ' >>' end out << ' /Contents ' + (@n + 1).to_s + ' 0 R' out << ' /Rotate ' + @pagedim[n]['Rotate'].to_s out << ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>' if @pagedim[n]['trans'] and !@pagedim[n]['trans'].empty? # page transitions if @pagedim[n]['trans']['Dur'] out << ' /Dur ' + @pagedim[n]['trans']['Dur'] end out << ' /Trans <<' out << ' /Type /Trans' if @pagedim[n]['trans']['S'] out << ' /S /' + @pagedim[n]['trans']['S'] end if @pagedim[n]['trans']['D'] out << ' /D ' + @pagedim[n]['trans']['D'].to_s end if @pagedim[n]['trans']['Dm'] out << ' /Dm /' + @pagedim[n]['trans']['Dm'] end if @pagedim[n]['trans']['M'] out << ' /M /' + @pagedim[n]['trans']['M'] end if @pagedim[n]['trans']['Di'] out << ' /Di ' + @pagedim[n]['trans']['Di'] end if @pagedim[n]['trans']['SS'] out << ' /SS ' + @pagedim[n]['trans']['SS'] end if @pagedim[n]['trans']['B'] out << ' /B ' + @pagedim[n]['trans']['B'] end out << ' >>' end out << getannotsrefs(n) out << ' /PZ ' + @pagedim[n]['PZ'].to_s out << ' >> endobj' out(out) #Page content p=(@compress) ? Zlib::Deflate.deflate(temppage) : temppage newobj(); out('<<' + filter + ' /Length ' + p.length.to_s + '>> ' + getstream(p) + ' endobj') if @diskcache # remove temporary files File.delete(@pages[n].path) end end #Pages root @offsets[1]=@bufferlen out = '1 0 obj << /Type /Pages /Kids [' @page_obj_id.each { |page_obj| out << ' ' + page_obj.to_s + ' 0 R' unless page_obj.nil? } out << ' ] /Count ' + nb.to_s + ' >> endobj' out(out) end # # Output references to page annotations # [@param int :n] page number # [@access protected] # [@author] Nicola Asuni # [@deprecated] # [@since 4.7.000 (2008-08-29)] # def putannotsrefs(n) out(getannotsrefs(n)) end # # Get references to page annotations. # [@param int :n] page number # [@return string] # [@access protected] # [@author] Nicola Asuni # [@since 5.0.010 (2010-05-17)] # def getannotsrefs(n) unless @page_annots[n] or (@sign and @signature_data['cert_type']) return '' end out = ' /Annots [' if @page_annots[n] num_annots = @page_annots[n].length 0.upto(num_annots - 1) do |i| @curr_annot_obj_id += 1 if !@radio_groups.include?(@curr_annot_obj_id) out << ' ' + @curr_annot_obj_id.to_s + ' 0 R' else num_annots += 1 end end end if (n == 1) and @sign and @signature_data['cert_type'] # set reference for signature object out << ' ' + @sig_annot_ref end out << ' ]' return out end # # Output annotations objects for all pages. # * !!! THIS METHOD IS NOT YET COMPLETED !!! # * See section 12.5 of PDF 32000_2008 reference. # [@access protected] # [@author] Nicola Asuni # [@since 4.0.018 (2008-08-06)] # def putannotsobjs() # reset object counter @annot_obj_id = @annots_start_obj_id 1.upto(@numpages) do |n| if !@page_annots[n].nil? # set page annotations @page_annots[n].each_with_index { |pl, key| # create annotation object for grouping radiobuttons if @radiobutton_groups[n] and @radiobutton_groups[n][pl['txt']] and @radiobutton_groups[n][pl['txt']].is_a?(Array) annots = '<<' annots << ' /Type /Annot' annots << ' /Subtype /Widget' annots << ' /T ' + dataannobjstring(pl['txt']) annots << ' /FT /Btn' annots << ' /Ff 49152' annots << ' /Kids [' @radiobutton_groups[n][pl['txt']].each {|data| annots << ' ' + data['kid'] + ' 0 R' if data['def'] != 'Off' defval = data['def'] end } annots << ' ]' if defval annots << ' /V /' + defval end annots << ' >>' @annot_obj_id += 1 @offsets[@annot_obj_id] = @bufferlen out(@annot_obj_id + ' 0 obj ' + annots + ' endobj') @form_obj_id.push = @annot_obj_id # store object id to be used on Parent entry of Kids @radiobutton_groups[n][pl['txt']] = @annot_obj_id end formfield = false pl['opt'] = pl['opt'].inject({}) do |pl_opt, keys| pl_opt[keys[0].downcase] = keys[1] pl_opt end a = pl['x'] * @k b = @pagedim[n]['h'] - ((pl['y'] + pl['h']) * @k) c = pl['w'] * @k d = pl['h'] * @k rect = sprintf('%.2f %.2f %.2f %.2f', a, b, a + c, b + d) # create new annotation object annots = '<</Type /Annot' annots << ' /Subtype /' + pl['opt']['subtype'] annots << ' /Rect [' + rect + ']' ft = ['Btn', 'Tx', 'Ch', 'Sig'] if pl['opt']['ft'] and ft.include?(pl['opt']['ft']) annots << ' /FT /' + pl['opt']['ft'] formfield = true end annots << ' /Contents ' + textannobjstring(pl['txt']) annots << ' /P ' + @page_obj_id[n].to_s + ' 0 R' annots << ' /NM ' + dataannobjstring(sprintf('%04u-%04u', n, key)) annots << ' /M ' + datestring() if !pl['opt']['f'].nil? val = 0 if pl['opt']['f'].is_a?(Array) pl['opt']['f'].each {|f| case f.downcase when 'invisible' val += 1 << 0 when 'hidden' val += 1 << 1 when 'print' val += 1 << 2 when 'nozoom' val += 1 << 3 when 'norotate' val += 1 << 4 when 'noview' val += 1 << 5 when 'readonly' val += 1 << 6 when 'locked' val += 1 << 8 when 'togglenoview' val += 1 << 9 when 'lockedcontents' val += 1 << 10 end } else val = pl['opt']['f'].to_i end annots << ' /F ' + val.to_i end # annots << ' /AP ' # annots << ' /AS ' if pl['opt']['as'] and pl['opt']['as'].is_a?(String) annots << ' /AS /' + pl['opt']['as'] end if pl['opt']['ap'] # appearance stream annots << ' /AP << ' + pl['opt']['ap'] + ' >>' annots << ' /AP <<' if pl['opt']['ap'].is_a?(Hash) pl['opt']['ap'].each {|apmode, apdef| # apmode can be: n = normal; r = rollover; d = down annots << ' /' + apmode.upcase if apdef.is_a?(Array) annots << ' <<' apdef.each {|apstate, stream| # reference to XObject that define the appearance for this mode-state apsobjid = putAPXObject(c, d, stream) annots << ' /' + apstate + ' ' + apsobjid + ' 0 R' } annots << ' >>' else # reference to XObject that define the appearance for this mode apsobjid = putAPXObject(c, d, apdef) annots << ' ' + apsobjid + ' 0 R' end } else annots << pl['opt']['ap'] end annots << ' >>' end if !pl['opt']['bs'].nil? and pl['opt']['bs'].is_a?(Hash) annots << ' /BS <<' annots << ' /Type /Border' if !pl['opt']['bs']['w'].nil? annots << ' /W ' + pl['opt']['bs']['w'].to_i end bstyles = ['S', 'D', 'B', 'I', 'U'] if !pl['opt']['bs']['s'].nil? and bstyles.include?(pl['opt']['bs']['s']) annots << ' /S /' + pl['opt']['bs']['s'] end if !pl['opt']['bs']['d'].nil? and pl['opt']['bs']['d'].is_a?(Array) annots << ' /D [' pl['opt']['bs']['d'].each {|cord| annots << ' ' + cord.to_i.to_s } annots << ']' end annots << ' >>' else annots << ' /Border [' if !pl['opt']['border'].nil? and (pl['opt']['border'].length >= 3) annots << pl['opt']['border'][0].to_i + ' ' annots << pl['opt']['border'][1].to_i + ' ' annots << pl['opt']['border'][2].to_i if pl['opt']['border'][3].nil? and pl['opt']['border'][3].is_a?(Array) annots << ' [' pl['opt']['border'][3].each {|dash| annots << dash.to_i + ' ' } annots << ']' end else annots << '0 0 0' end annots << ']' end if !pl['opt']['be'].nil? and pl['opt']['be'].is_a?(Hash) annots << ' /BE <<' bstyles = ['S', 'C'] if !pl['opt']['be']['s'].nil? and markups.include?(pl['opt']['be']['s']) annots << ' /S /' + pl['opt']['bs']['s'] else annots << ' /S /S' end if !pl['opt']['be']['i'].nil? and (pl['opt']['be']['i'] >= 0) and (pl['opt']['be']['i'] <= 2) annots << ' /I ' + sprintf(" %.4f", pl['opt']['be']['i']) end annots << '>>' end if !pl['opt']['c'].nil? and pl['opt']['c'].is_a?(Array) and !pl['opt']['c'].empty? annots << ' /C [' pl['opt']['c'].each {|col| col = col.to_i color = col <= 0 ? 0 : (col >= 255 ? 1 : col / 255.0) annots << sprintf(" %.4f", color) } annots << ']' end # annots << ' /StructParent ' # annots << ' /OC ' markups = ['text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound'] if markups.include?(pl['opt']['subtype'].downcase) # this is a markup type if !pl['opt']['t'].nil? and pl['opt']['t'].is_a?(String) annots << ' /T ' + textannobjstring(pl['opt']['t']) end # annots .= ' /Popup ' if !pl['opt']['ca'].nil? annots << ' /CA ' + sprintf("%.4f", pl['opt']['ca'].to_f) end if !pl['opt']['rc'].nil? annots << ' /RC ' + textannobjstring(pl['opt']['rc']) end annots << ' /CreationDate ' + datestring() # annots << ' /IRT ' if !pl['opt']['subj'].nil? annots << ' /Subj ' + textannobjstring(pl['opt']['subj']) end # annots << ' /RT ' # annots << ' /IT ' # annots << ' /ExData ' end lineendings = ['Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash'] # Annotation types case pl['opt']['subtype'].downcase when 'text' if !pl['opt']['open'].nil? annots << ' /Open ' + (pl['opt']['open'].downcase == 'true' ? 'true' : 'false') end iconsapp = ['Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph'] if !pl['opt']['name'].nil? and iconsapp.include?(pl['opt']['name']) annots << ' /Name /' + pl['opt']['name'] else annots << ' /Name /Note' end statemodels = ['Marked', 'Review'] if !pl['opt']['statemodel'].nil? and statemodels.include?(pl['opt']['statemodel']) annots << ' /StateModel /' + pl['opt']['statemodel'] else pl['opt']['statemodel'] = 'Marked' annots << ' /StateModel /' + pl['opt']['statemodel'] end if pl['opt']['statemodel'] == 'Marked' states = ['Accepted', 'Unmarked'] else states = ['Accepted', 'Rejected', 'Cancelled', 'Completed', 'None'] end if !pl['opt']['state'].nil? and states.include?(pl['opt']['state']) annots << ' /State /' + pl['opt']['state'] else if pl['opt']['statemodel'] == 'Marked' annots << ' /State /Unmarked' else annots << ' /State /None' end end when 'link' if pl['txt'].is_a?(String) # external URI link annots << ' /A <</S /URI /URI ' + dataannobjstring(unhtmlentities(pl['txt'])) + '>>' else # internal link l = @links[pl['txt']] annots << sprintf(' /Dest [%d 0 R /XYZ 0 %.2f null]', 1 + (2 * l[0]), @pagedim[l[0]]['h'] - (l[1] * @k)) end hmodes = ['N', 'I', 'O', 'P'] if !pl['opt']['h'].nil? and hmodes.include?(pl['opt']['h']) annots << ' /H /' + pl['opt']['h'] else annots << ' /H /I' end # annots << ' /PA ' # annots << ' /Quadpoints ' when 'freetext' if pl['opt']['da'] and !pl['opt']['da'].empty? annots << ' /DA (' + pl['opt']['da'] + ')' end if !pl['opt']['q'].nil? and (pl['opt']['q'] >= 0) and (pl['opt']['q'] <= 2) annots << ' /Q ' + pl['opt']['q'].to_i end if !pl['opt']['rc'].nil? annots << ' /RC ' + textannobjstring(pl['opt']['rc']) end if !pl['opt']['ds'].nil? annots << ' /DS ' + textannobjstring(pl['opt']['ds']) end if !['opt']['cl'].nil? and pl['opt']['cl'].is_a?(Array) annots << ' /CL [' pl['opt']['cl'].each {|cl| annots << sprintf("%.4f ", cl * @k) } annots << ']' end tfit = ['FreeText', 'FreeTextCallout', 'FreeTextTypeWriter'] if !pl['opt']['it'].nil? and tfit.include?(pl['opt']['it']) annots << ' /IT /' + pl['opt']['it'] end if !pl['opt']['rd'].nil? and pl['opt']['rd'].is_a?(Array) l = pl['opt']['rd'][0] * @k r = pl['opt']['rd'][1] * @k t = pl['opt']['rd'][2] * @k b = pl['opt']['rd'][3] * @k annots << ' /RD [' + sprintf('%.2f %.2f %.2f %.2f', l, r, t, b) + ']' end if pl['opt']['le'] and lineendings.include?(pl['opt']['le']) annots << ' /LE /' + pl['opt']['le'] end # ... to be completed ... when 'fileattachment' if pl['opt']['fs'].nil? break end filename = File.basename(pl['opt']['fs']) if !@embeddedfiles[filename]['n'].nil? annots << ' /FS <</Type /Filespec /F ' + dataannobjstring(filename) + ' /EF <</F ' + @embeddedfiles[filename]['n'].to_s + ' 0 R>> >>' iconsapp = ['Graph', 'Paperclip', 'PushPin', 'Tag'] if !pl['opt']['name'].nil? and iconsapp.include?(pl['opt']['name']) annots << ' /Name /' + pl['opt']['name'] else annots << ' /Name /PushPin' end end when 'sound' if pl['opt']['fs'].nil? break end filename = File.basename(pl['opt']['fs']) if !@embeddedfiles[filename]['n'].nil? # ... TO BE COMPLETED ... # /R /C /B /E /CO /CP annots << ' /Sound <</Type /Filespec /F ' + dataannobjstring(filename) + ' /EF <</F ' + @embeddedfiles[filename]['n'] + ' 0 R>> >>' iconsapp = ['Speaker', 'Mic'] if !pl['opt']['name'].nil? and iconsapp.include?(pl['opt']['name']) annots << ' /Name /' + pl['opt']['name'] else annots << ' /Name /Speaker' end end when 'widget' hmode = ['N', 'I', 'O', 'P', 'T'] if pl['opt']['h'] and hmode.include?(pl['opt']['h']) annots << ' /H /' + pl['opt']['h'] end if pl['opt']['mk'] and pl['opt']['mk'].is_a?(Hash) and !pl['opt']['mk'].empty? annots << ' /MK <<' if pl['opt']['mk']['r'] annots << ' /R ' + pl['opt']['mk']['r'] end if pl['opt']['mk']['bc'] and (pl['opt']['mk']['bc'] != false) annots << ' /BC [' pl['opt']['mk']['bc'].each {|col| col = col.to_i color = col <= 0 ? 0 : (col >= 255 ? 1 : col / 255.0) annots << sprintf(' %.2f', color) } annots << ']' end if pl['opt']['mk']['bg'] and (pl['opt']['mk']['bg'] != false) annots << ' /BG [' pl['opt']['mk']['bg'].each {|col| col = col.to_i color = col <= 0 ? 0 : (col >= 255 ? 1 : col / 255.0) annots << sprintf(' %.2f', color) } annots << ']' end if pl['opt']['mk']['ca'] annots << ' /CA ' + pl['opt']['mk']['ca'] + '' end if pl['opt']['mk']['rc'] annots << ' /RC ' + pl['opt']['mk']['ca'] + '' end if pl['opt']['mk']['ac'] annots << ' /AC ' + pl['opt']['mk']['ca'] + '' end if pl['opt']['mk']['i'] info = getImageBuffer(pl['opt']['mk']['i']) if info != false annots << ' /I ' + info['n'] + ' 0 R' end end if pl['opt']['mk']['ri'] info = getImageBuffer(pl['opt']['mk']['ri']) if info != false annots << ' /RI ' + info['n'] + ' 0 R' end end if pl['opt']['mk']['ix'] info = getImageBuffer(pl['opt']['mk']['ix']) if info != false annots << ' /IX ' + info['n'] + ' 0 R' end end if pl['opt']['mk']['if'] and pl['opt']['mk']['if'].is_a?(Hash) and !pl['opt']['mk']['if'].empty? annots << ' /IF <<' if_sw = ['A', 'B', 'S', 'N'] if pl['opt']['mk']['if']['sw'] and if_sw.include?(pl['opt']['mk']['if']['sw']) annots << ' /SW /' + pl['opt']['mk']['if']['sw'] end if_s = ['A', 'P'] if pl['opt']['mk']['if']['s'] and if_s.include?(pl['opt']['mk']['if']['s']) annots << ' /S /' + pl['opt']['mk']['if']['s'] end if pl['opt']['mk']['if']['a'] and pl['opt']['mk']['if']['a'].is_a?(Array) and !pl['opt']['mk']['if']['a'].empty? annots << sprintf(' /A [%.2f %.2f]', pl['opt']['mk']['if']['a'][0], pl['opt']['mk']['if']['a'][1]) end if pl['opt']['mk']['if']['fb'] and pl['opt']['mk']['if']['fb'] annots << ' /FB true' end annots << '>>' end if pl['opt']['mk']['tp'] and (pl['opt']['mk']['tp'] >= 0) and (pl['opt']['mk']['tp'] <= 6) annots << ' /TP ' + pl['opt']['mk']['tp'] else annots << ' /TP 0' end annots << '>>' end # end MK # --- Entries for field dictionaries --- if @radiobutton_groups[n][pl['txt']] # set parent annots << ' /Parent ' + @radiobutton_groups[n][pl['txt']] + ' 0 R' end if pl['opt']['t'] and pl['opt']['t'].is_a?(String) annots << ' /T ' + dataannobjstring(pl['opt']['t']) end if pl['opt']['tu'] and pl['opt']['tu'].is_a?(String) annots << ' /TU ' + dataannobjstring(pl['opt']['tu']) end if pl['opt']['tm'] and pl['opt']['tm'].is_a?(String) annots << ' /TM ' + dataannobjstring(pl['opt']['tm']) end if pl['opt']['ff'] if pl['opt']['ff'].is_a?(Array) # array of bit settings flag = 0 pl['opt']['ff'].each {|val| flag += 1 << (val - 1) } else flag = pl['opt']['ff'].to_i end annots << ' /Ff ' + flag end if pl['opt']['maxlen'] annots << ' /MaxLen ' + pl['opt']['maxlen'].to_i.to_s end if pl['opt']['v'] annots << ' /V' if pl['opt']['v'].is_a?(Array) pl['opt']['v'].each { |optval| if optval.is_a? Float optval = sprintf('%.2f', optval) end annots << ' ' + optval } else annots << ' ' + textannobjstring(pl['opt']['v']) end end if pl['opt']['dv'] annots << ' /DV' if pl['opt']['dv'].is_a?(Array) pl['opt']['dv'].eath {|optval| if optval.is_a? Float optval = sprintf('%.2f', optval) end annots << ' ' + optval } else annots << ' ' + textannobjstring(pl['opt']['dv']) end end if pl['opt']['rv'] annots << ' /RV' if pl['opt']['rv'].is_a?(Array) pl['opt']['rv'].eath {|optval| if optval.is_a? Float optval = sprintf('%.2f', optval) end annots << ' ' + optval } else annots << ' ' + textannobjstring(pl['opt']['rv']) end end if pl['opt']['a'] and !pl['opt']['a'].empty? annots << ' /A << ' + pl['opt']['a'] + ' >>' end if pl['opt']['aa'] and !pl['opt']['aa'].empty? annots << ' /AA << ' + pl['opt']['aa'] + ' >>' end if pl['opt']['da'] and !pl['opt']['da'].empty? annots << ' /DA (' + pl['opt']['da'] + ')' end if pl['opt']['q'] and (pl['opt']['q'] >= 0) and (pl['opt']['q'] <= 2) annots << ' /Q ' + pl['opt']['q'].to_i.to_s end if pl['opt']['opt'] and pl['opt']['opt'].is_a?(Array) and !pl['opt']['opt'].empty? annots << ' /Opt [' pl['opt']['opt'].each {|copt| if copt.is_a?(Array) annots << ' [' + textannobjstring(copt[0]) + ' ' + textannobjstring(copt[1]) + ']' else annots << ' ' + textannobjstring(copt) end } annots << ']' end if pl['opt']['ti'] annots << ' /TI ' + pl['opt']['ti'].to_i.to_s end if pl['opt']['i'] and pl['opt']['i'].is_a?(Array) and !pl['opt']['i'].empty? annots << ' /I [' pl['opt']['i'].each {|copt| annots << copt.to_i.to_s + ' ' } annots << ']' end else end annots << '>>' # create new annotation object @annot_obj_id += 1 @offsets[@annot_obj_id] = @bufferlen out(@annot_obj_id.to_s + ' 0 obj ' + annots + ' endobj') if formfield and ! @radiobutton_groups[n][pl['txt']] # store reference of form object @form_obj_id.push = @annot_obj_id end } end # end for each page end end # # Put appearance streams XObject used to define annotation's appearance states # [@param int :w] annotation width # [@param int :h] annotation height # [@param string :stream] appearance stream # [@return int] object ID # [@access protected] # [@since 4.8.001 (2009-09-09)] # def putAPXObject(w=0, h=0, stream='') stream = stream.strip @apxo_obj_id += 1 @offsets[@apxo_obj_id] = @bufferlen out = @apxo_obj_id.to_s + ' 0 obj' out << ' <<' out << ' /Type /XObject' out << ' /Subtype /Form' out << ' /FormType 1' if @compress stream = Zlib::Deflate.deflate(stream) out << ' /Filter /FlateDecode' end rect = sprintf('%.2f %.2f', w, h) out << ' /BBox [0 0 ' + rect + ']' out << ' /Matrix [1 0 0 1 0 0]' out << ' /Resources <</ProcSet [/PDF]>>' out << ' /Length ' + stream.length.to_s out << ' >>' out << ' ' + getstream(stream) out << ' endobj' out(out) return @apxo_obj_id end # # Get ULONG from string (Big Endian 32-bit unsigned integer). # [@parameter string :str] string from where to extract value # [@parameter int :offset] point from where to read the data # [@return int] 32 bit value # [@author Nicola Asuni] # [@access protected] # [@since 5.2.000 (2010-06-02)] # def getULONG(str, offset) v = str[offset, 4].unpack('N') return v[0].to_i end # # Get USHORT from string (Big Endian 16-bit unsigned integer). # [@parameter string :str] string from where to extract value # [@parameter int :offset] point from where to read the data # [@return int] 16 bit value # [@author Nicola Asuni] # [@access protected] # [@since 5.2.000 (2010-06-02)] # def getUSHORT(str, offset) v = str[offset, 2].unpack('n') return v[0].to_i end # # Get SHORT from string (Big Endian 16-bit signed integer). # [@parameter string :str] string from where to extract value # [@parameter int :offset] point from where to read the data # [@return int] 16 bit value # [@author Nicola Asuni] # [@access protected] # [@since 5.2.000 (2010-06-02)] # def getSHORT(str, offset) v = str[offset, 2].unpack('s') return v[0].to_i end # # Get BYTE from string (8-bit unsigned integer). # [@parameter string :str] string from where to extract value # [@parameter int :offset] point from where to read the data # [@return int] 8 bit value # [@author Nicola Asuni] # [@access protected] # [@since 5.2.000 (2010-06-02)] # def getBYTE(str, offset) v = str[offset, 1].unpack('C') return v[0].to_i end # # Returns a subset of the TrueType font data without the unused glyphs. # [@parameter string :font] TrueType font data # [@parameter array :subsetchars] array of used characters (the glyphs to keep) # [@return string] a subset of TrueType font data without the unused glyphs # [@author Nicola Asuni] # [@access protected] # [@since 5.2.000 (2010-06-02)] # def getTrueTypeFontSubset(font, subsetchars) #ksort(subsetchars) offset = 0 # offset position of the font data if getULONG(font, offset) != 0x10000 # sfnt version must be 0x00010000 for TrueType version 1.0. return font end offset += 4 # get number of tables numTables = getUSHORT(font, offset); offset += 2 # skip searchRange, entrySelector and rangeShift offset += 6 # tables array(Hash) table = {} # for each table numTables.times { # get table info tag = font[offset, 4] offset += 4 table[tag] = {} table[tag]['checkSum'] = getULONG(font, offset); offset += 4 table[tag]['offset'] = getULONG(font, offset); offset += 4 table[tag]['length'] = getULONG(font, offset); offset += 4 } # check magicNumber offset = table['head']['offset'] + 12 if getULONG(font, offset) != 0x5F0F3CF5 # magicNumber must be 0x5F0F3CF5 return font end offset += 4 # get offset mode (indexToLocFormat : 0 = short, 1 = long) offset = table['head']['offset'] + 50 short_offset = (getSHORT(font, offset) == 0); offset += 2 # get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table indexToLoc = [] offset = table['loca']['offset'] if short_offset # short version n = table['loca']['length'] / 2 # numGlyphs + 1 n.times {|i| indexToLoc[i] = getUSHORT(font, offset) * 2; offset += 2 } else # long version n = table['loca']['length'] / 4 # numGlyphs + 1 n.times {|i| indexToLoc[i] = getULONG(font, offset); offset += 4 } end # get glyphs indexes of chars from cmap table subsetglyphs = [] # glyph IDs on key subsetglyphs[0] = true # character codes that do not correspond to any glyph in the font should be mapped to glyph index 0 offset = table['cmap']['offset'] + 2 numEncodingTables = getUSHORT(font, offset); offset += 2 encodingTables = [] numEncodingTables.times {|i| encodingTables[i] ||= {} encodingTables[i]['platformID'] = getUSHORT(font, offset); offset += 2 encodingTables[i]['encodingID'] = getUSHORT(font, offset); offset += 2 encodingTables[i]['offset'] = getULONG(font, offset); offset += 4 } encodingTables.each {|enctable| if (enctable['platformID'] == 3) and (enctable['encodingID'] == 0) modesymbol = true else modesymbol = false end offset = table['cmap']['offset'] + enctable['offset'] format = getUSHORT(font, offset); offset += 2 case format when 0 # Format 0: Byte encoding table offset += 4 # skip length and version/language 256.times {|k| if subsetchars[k] g = getBYTE(font, offset); offset += 1 subsetglyphs[g] = k else offset += 1 end } when 2 # Format 2: High-byte mapping through table offset += 4 # skip length and version # to be implemented ... when 4 # Format 4: Segment mapping to delta values length = getUSHORT(font, offset); offset += 2 offset += 2 # skip version/language segCount = getUSHORT(font, offset) / 2; offset += 2 offset += 6 # skip searchRange, entrySelector, rangeShift endCount = [] # array of end character codes for each segment segCount.times {|k| endCount[k] = getUSHORT(font, offset); offset += 2 } offset += 2 # skip reservedPad startCount = [] # array of start character codes for each segment segCount.times {|k| startCount[k] = getUSHORT(font, offset); offset += 2 } idDelta = [] # delta for all character codes in segment segCount.times {|k| idDelta[k] = getUSHORT(font, offset); offset += 2 } idRangeOffset = [] # Offsets into glyphIdArray or 0 segCount.times {|k| idRangeOffset[k] = getUSHORT(font, offset); offset += 2 } gidlen = (length / 2) - 8 - (4 * segCount) glyphIdArray = [] # glyph index array gidlen.times {|k| glyphIdArray[k] = getUSHORT(font, offset); offset += 2 } segCount.times {|k| startCount[k].upto(endCount[k]) {|c| if subsetchars[c] if idRangeOffset[k] == 0 g = c else gid = (idRangeOffset[k] / 2) + (c - startCount[k]) - (segCount - k) g = glyphIdArray[gid] end g += (idDelta[k] - 65536) if g < 0 g = 0 end subsetglyphs[g] = c end } } when 6 # Format 6: Trimmed table mapping offset += 4 # skip length and version/language firstCode = getUSHORT(font, offset); offset += 2 entryCount = getUSHORT(font, offset); offset += 2 entryCount.times {|k| c = k + firstCode if subsetchars[c] g = getUSHORT(font, offset); offset += 2 subsetglyphs[g] = c else offset += 2 end } when 8 # Format 8: Mixed 16-bit and 32-bit coverage offset += 10 # skip length and version # to be implemented ... when 10 # Format 10: Trimmed array offset += 10 # skip length and version/language startCharCode = getULONG(font, offset); offset += 4 numChars = getULONG(font, offset); offset += 4 numChars.times {|k| c = k + startCharCode if subsetchars[c] g = getUSHORT(font, offset); offset += 2 subsetglyphs[g] = c else offset += 2 end } when 12 # Format 12: Segmented coverage offset += 10 # skip length and version/language nGroups = getULONG(font, offset); offset += 4 nGroups.times {|k| startCharCode = getULONG(font, offset); offset += 4 endCharCode = getULONG(font, offset); offset += 4 startGlyphCode = getULONG(font, offset); offset += 4 startCharCode.upto(endCharCode) {|c| if subsetchars[c] subsetglyphs[startGlyphCode] = c end startGlyphCode += 1 } } end } # sort glyphs by key #ksort(subsetglyphs) # add composite glyps to subsetglyphs and remove missing glyphs subsetglyphs.each_with_index {|val, key| next if val.nil? if indexToLoc[key] offset = table['glyf']['offset'] + indexToLoc[key] numberOfContours = getSHORT(font, offset); offset += 2 if numberOfContours < 0 # composite glyph offset += 8 # skip xMin, yMin, xMax, yMax loop { flags = getUSHORT(font, offset); offset += 2 glyphIndex = getUSHORT(font, offset); offset += 2 if subsetglyphs[glyphIndex].nil? and indexToLoc[glyphIndex] # add missing glyphs subsetglyphs[glyphIndex] = true end # skip some bytes by case if (flags & 1) != 0 offset += 4 else offset += 2 end if (flags & 8) != 0 offset += 2 elsif (flags & 64) != 0 offset += 4 elsif (flags & 128) != 0 offset += 8 end break if (flags & 32) == 0 } end else subsetglyphs.delete_at(key) end } # build new glyf table with only used glyphs glyf = '' glyfSize = 0 # create new empty indexToLoc table newIndexToLoc = Array.new(indexToLoc.length, 0) goffset = 0 subsetglyphs.each_with_index {|char, glyphID| next if char.nil? if indexToLoc[glyphID] and indexToLoc[glyphID + 1] start = indexToLoc[glyphID] length = indexToLoc[glyphID + 1] - start glyf << font[table['glyf']['offset'] + start, length] newIndexToLoc[glyphID] = goffset goffset += length end } # build new loca table loca = '' if short_offset newIndexToLoc.each {|offset| loca << [offset / 2].pack('n') } else newIndexToLoc.each {|offset| loca << [offset].pack('N') } end # array of table names to preserve (loca and glyf tables will be added later) # table_names = ['cmap', 'head', 'hhea', 'hmtx', 'maxp', 'name', 'OS/2', 'post', 'cvt ', 'fpgm', 'prep'] table_names = ['head', 'hhea', 'hmtx', 'maxp', 'OS/2', 'cvt ', 'fpgm', 'prep'] # get the tables to preserve offset = 12 table.each {|tag, val| if table_names.include?(tag) table[tag]['data'] = font[table[tag]['offset'], table[tag]['length']] if tag == 'head' # set the checkSumAdjustment to 0 table[tag]['data'] = table[tag]['data'][0, 8] + "\x0\x0\x0\x0" + table[tag]['data'][12..-1] end pad = 4 - (table[tag]['length'] % 4) if pad != 4 # the length of a table must be a multiple of four bytes table[tag]['length'] += pad table[tag]['data'] << "\x0" * pad end table[tag]['offset'] = offset offset += table[tag]['length'] # table[tag]['checkSum'] = getTTFtableChecksum(table[tag]['data'], table[tag]['length']) else table.delete(tag) end } # add loca table['loca'] = {} table['loca']['data'] = loca table['loca']['length'] = loca.length pad = 4 - (table['loca']['length'] % 4) if pad != 4 # the length of a table must be a multiple of four bytes table['loca']['length'] += pad table['loca']['data'] << "\x0" * pad end table['loca']['offset'] = offset table['loca']['checkSum'] = getTTFtableChecksum(table['loca']['data'], table['loca']['length']) offset += table['loca']['length'] # add glyf table['glyf'] = {} table['glyf']['data'] = glyf table['glyf']['length'] = glyf.length pad = 4 - (table['glyf']['length'] % 4) if pad != 4 # the length of a table must be a multiple of four bytes table['glyf']['length'] += pad table['glyf']['data'] << "\x0" * pad end table['glyf']['offset'] = offset table['glyf']['checkSum'] = getTTFtableChecksum(table['glyf']['data'], table['glyf']['length']) # rebuild font font = '' font << [0x10000].pack('N') # sfnt version numTables = table.length font << [numTables].pack('n') # numTables entrySelector = (Math.log(numTables) / Math.log(2.0)).floor searchRange = 2 ** entrySelector * 16 rangeShift = numTables * 16 - searchRange font << [searchRange].pack('n') # searchRange font << [entrySelector].pack('n') # entrySelector font << [rangeShift].pack('n') # rangeShift offset = numTables * 16 table.each{|tag, data| font << tag # tag font << [data['checkSum']].pack('N') # checkSum font << [data['offset'] + offset].pack('N') # offset font << [data['length']].pack('N') # length } table.each{|tag, data| font << data['data'] } # set checkSumAdjustment on head table checkSumAdjustment = 0xB1B0AFBA - getTTFtableChecksum(font, font.length) font = font[0, table['head']['offset'] + 8] + [checkSumAdjustment].pack('N') + font[(table['head']['offset'] + 12)..-1] return font end # # Returs the checksum of a TTF table. # [@parameter string :table] table to check # [@parameter int :length] lenght of table in bytes # [@return int] checksum # [@author Nicola Asuni] # [@access protected] # [@since 5.2.000 (2010-06-02)] # def getTTFtableChecksum(table, length) sum = 0 tlen = length / 4 offset = 0 tlen.times { v = table[offset, 4].unpack('N') sum += v[0] offset += 4 } sum %= 0x100000000 sum = [sum].pack('N').unpack('N') return sum[0] end # # Outputs font widths # [@parameter array :font] font data # [@parameter int :cidoffset] offset for CID values # [@return] PDF command string for font widths # [@author] Nicola Asuni # [@access protected] # [@since 4.4.000 (2008-12-07)] # def putfontwidths(font, cidoffset=0) font_cw = font['cw'].sort rangeid = 0 range = [] prevcid = -2 prevwidth = -1 interval = false range_interval = [] # for each character font_cw.each {|cid, width| cid -= cidoffset if font['subset'] and (cid > 255) and font['subsetchars'][cid].nil? # ignore the unused characters (font subsetting) next end if width != font['dw'] if cid == prevcid + 1 # consecutive CID if width == prevwidth if width == range[rangeid][0] range[rangeid].push width else range[rangeid].pop # new range rangeid = prevcid range[rangeid] = [] range[rangeid].push prevwidth range[rangeid].push width end interval = true range_interval[rangeid] = true else if interval # new range rangeid = cid range[rangeid] = [] range[rangeid].push width else range[rangeid].push width end interval = false end else # new range rangeid = cid range[rangeid] = [] range[rangeid].push width interval = false end prevcid = cid prevwidth = width end } # optimize ranges prevk = -1 nextk = -1 prevint = false range.each_with_index {|ws, k| cws = ws ? ws.length : 0 if (k == nextk) and !prevint and (range_interval[k].nil? or (cws < 4)) if !range_interval[k].nil? range_interval[k] = nil end range[prevk] = range[prevk].concat(range[k]) if range[k] range[k] = nil else prevk = k end nextk = k + cws if !range_interval[k].nil? if cws > 3 prevint = true else prevint = false end range_interval[k] = nil nextk -= 1 else prevint = false end } # output data w = '' range.each_with_index {|ws, k| if ws and ws.uniq.length == 1 # interval mode is more compact w << ' ' + k.to_s + ' ' + (k + ws.length - 1).to_s + ' ' + ws[0].to_s elsif ws # range mode w << ' ' + k.to_s + ' [ ' + ws.join(' ') + ' ]' end } return ('/W [' + w + ' ]') end # # Output fonts. # [@author Nicola Asuni] # [@access protected] # def putfonts() nf=@n; @diffs.each do |diff| #Encodings newobj(); out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [' + diff + '] >> endobj') end @font_files.each do |file, info| # search and get font file to embedd fontdir = info['fontdir'] file = file.downcase fontfile = '' # search files on various directories if (fontdir != false) and File.exist?(fontdir + file) fontfile = fontdir + file elsif fontfile = getfontpath(file) elsif File.exist?(file) fontfile = file end if !empty_string(fontfile) font = '' open(fontfile,'rb') do |f| font = f.read() end compressed = (file[-2,2] == '.z') if !compressed && !info['length2'].nil? header = (font[0][0] == 128) if header # Strip first binary header font = font[6] end if header && (font[info['length1']][0] == 128) # Strip second binary header font = font[0..info['length1']] + font[info['length1'] + 6] end elsif info['subset'] and (!compressed or (compressed and Object.const_defined?(:Zlib))) if compressed # uncompress font font = Zlib::Inflate.inflate(font) end # merge subset characters subsetchars = [] # used chars info['fontkeys'].each {|fontkey| fontinfo = getFontBuffer(fontkey) subsetchars += fontinfo['subsetchars'] } font = getTrueTypeFontSubset(font, subsetchars) if compressed # recompress font font = Zlib::Deflate.deflate(font) end end newobj() @font_files[file]['n'] = @n out = '<</Length '+ font.length.to_s if compressed out << ' /Filter /FlateDecode' end out << ' /Length1 ' + info['length1'].to_s if !info['length2'].nil? out << ' /Length2 ' + info['length2'].to_s + ' /Length3 0' end out << ' >>' out << ' '+ getstream(font) out << ' endobj' out(out) end end @fontkeys.each do |k| #Font objects setFontSubBuffer(k, 'n', @n + 1) font = getFontBuffer(k) type = font['type']; name = font['name']; if (type=='core') # standard core font obj_id = newobj() out = '<</Type /Font' out << ' /Subtype /Type1' out << ' /BaseFont /' + name out << ' /Name /F' + font['i'].to_s if (name.downcase != 'symbol' && name.downcase != 'zapfdingbats') out << ' /Encoding /WinAnsiEncoding' end if name.downcase == 'helvetica' # add default font for annotations @annotation_fonts['helvetica'] = k end out << ' >> endobj' out(out) elsif (type=='Type1' || type=='TrueType') # additional Type1 or TrueType font obj_id = newobj() out = '<</Type /Font' out << ' /Subtype /' + type out << ' /BaseFont /' + name out << ' /Name /F' + font['i'].to_s out << ' /FirstChar 32 /LastChar 255' out << ' /Widths ' + (@n+1).to_s + ' 0 R' out << ' /FontDescriptor ' + (@n+2).to_s + ' 0 R' if (font['enc']) if (!font['diff'].nil?) out << ' /Encoding ' + (nf+font['diff']).to_s + ' 0 R' else out << ' /Encoding /WinAnsiEncoding' end end out << ' >> endobj' out(out) #Widths newobj(); cw=font['cw']; # & s='['; 32.upto(255) do |i| s << cw[i.chr] + ' '; end out(s + '] endobj') #Descriptor newobj(); s='<</Type /FontDescriptor /FontName /' + name; font['desc'].each do |fdk, fdv| if fdv.is_a? Float fdv = sprintf('%.3f', fdv) end s << ' /' + fdk + ' ' + fdv + '' end if !empty_string(font['file']) s << ' /FontFile' + (type=='Type1' ? '' : '2') + ' ' + @font_files[font['file']]['n'] + ' 0 R' end out(s + '>> endobj') else # additional types mtd='put' + type.downcase; unless self.respond_to?(mtd, true) Error('Unsupported font type: ' + type) end obj_id = self.send(mtd,font) # store object ID for current font @font_obj_ids[k] = obj_id end end end # # Adds unicode fonts. # Based on PDF Reference 1.3 (section 5) # [@parameter array :font] font data # [@return int] font object ID # [@access protected] # [@author] Nicola Asuni # [@since 1.52.0.TC005 (2005-01-05)] # def puttruetypeunicode(font) fontname = '' if font['subset'] # change name for font subsetting subtag = sprintf('%06u', font['i']) subtag = subtag.gsub('0123456789', 'ABCDEFGHIJ') fontname << subtag + '+' end fontname << font['name'] # Type0 Font # A composite font composed of other fonts, organized hierarchically obj_id = newobj() out = '<</Type /Font' out << ' /Subtype /Type0' out << ' /BaseFont /' + fontname + '' out << ' /Name /F' + font['i'].to_s out << ' /Encoding /' + font['enc'] out << ' /DescendantFonts [' + (@n + 1).to_s + ' 0 R]' out << ' >>' out << ' endobj' out(out) # CIDFontType2 # A CIDFont whose glyph descriptions are based on TrueType font technology newobj(); out = '<</Type /Font' out << ' /Subtype /CIDFontType2' out << ' /BaseFont /' + fontname # A dictionary containing entries that define the character collection of the CIDFont. cidinfo = '/Registry ' + datastring(font['cidinfo']['Registry']) cidinfo << ' /Ordering ' + datastring(font['cidinfo']['Ordering']) cidinfo << ' /Supplement ' + font['cidinfo']['Supplement'].to_s out << ' /CIDSystemInfo <<' + cidinfo + '>>' out << ' /FontDescriptor ' + (@n + 1).to_s + ' 0 R' out << ' /DW ' + font['dw'].to_s + '' # default width out << "\n" + putfontwidths(font, 0) out << ' /CIDToGIDMap ' + (@n + 2).to_s + ' 0 R' out << ' >> endobj' out(out) # Font descriptor # A font descriptor describing the CIDFont default metrics other than its glyph widths newobj(); out = '<</Type /FontDescriptor' out << ' /FontName /' + fontname font['desc'].each do |key, value| if value.is_a? Float value = sprintf('%.3f', value) end out << ' /' + key.to_s + ' ' + value.to_s end fontdir = false if !empty_string(font['file']) # A stream containing a TrueType font out << ' /FontFile2 ' + @font_files[font['file']]['n'].to_s + ' 0 R' fontdir = @font_files[font['file']]['fontdir'] end out << ' >> endobj' out(out) if font['ctg'] and !empty_string(font['ctg']) newobj() # Embed CIDToGIDMap # A specification of the mapping from CIDs to glyph indices # search and get CTG font file to embedd ctgfile = font['ctg'].downcase # search and get ctg font file to embedd fontfile = '' # search files on various directories if (fontdir != false) and File.exist?(fontdir + ctgfile) fontfile = fontdir + ctgfile elsif fontfile = getfontpath(ctgfile) elsif File.exist?(ctgfile) fontfile = ctgfile end if empty_string(fontfile) Error('Font file not found: ' + ctgfile) end size = File.size(fontfile) out = '<</Length ' + size.to_s + '' if (fontfile[-2,2] == '.z') # check file extension # Decompresses data encoded using the public-domain # zlib/deflate compression method, reproducing the # original text or binary data out << ' /Filter /FlateDecode' end out << ' >>' open(fontfile, 'rb') do |f| out << ' ' + getstream(f.read()) end out << ' endobj' out(out) end return obj_id end # # Output CID-0 fonts. # A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format # [@param array :font] font data # [@return int] font object ID # [@access protected] # [@author] Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira # [@since 3.2.000 (2008-06-23)] # def putcidfont0(font) cidoffset = 0 if font['cw'][1].nil? cidoffset = 31 end if font['cidinfo']['uni2cid'] # convert unicode to cid. uni2cid = font['cidinfo']['uni2cid'] cw = {} font['cw'].each { |uni, width| if uni2cid[uni] cw[(uni2cid[uni] + cidoffset)] = width elsif uni < 256 cw[uni] = width end # else unknown character } font = font.merge({'cw' => cw}) end name = font['name'] enc = font['enc'] if enc longname = name + '-' + enc else longname = name end obj_id = newobj() out = '<</Type /Font' out << ' /Subtype /Type0' out << ' /BaseFont /' + longname out << ' /Name /F' + font['i'].to_s if enc out << ' /Encoding /' + enc end out << ' /DescendantFonts [' + (@n + 1).to_s + ' 0 R]' out << ' >> endobj' out(out) newobj() out = '<</Type /Font' out << ' /Subtype /CIDFontType0' out << ' /BaseFont /' + name cidinfo = '/Registry ' + datastring(font['cidinfo']['Registry']) cidinfo << ' /Ordering ' + datastring(font['cidinfo']['Ordering']) cidinfo << ' /Supplement ' + font['cidinfo']['Supplement'].to_s out << ' /CIDSystemInfo <<' + cidinfo + '>>' out << ' /FontDescriptor ' + (@n + 1).to_s + ' 0 R' out << ' /DW ' + font['dw'].to_s out << "\n" + putfontwidths(font, cidoffset) out << ' >> endobj' out(out) newobj() s = '<</Type /FontDescriptor /FontName /' + name font['desc'].each {|k, v| if k != 'Style' if v.is_a? Float v = sprintf('%.3f', v) end s << ' /' + k + ' ' + v.to_s + '' end } out(s + '>> endobj') return obj_id end # # Output images. # [@access protected] # def putimages() filter=(@compress) ? '/Filter /FlateDecode ' : ''; @imagekeys.each do |file| info = getImageBuffer(file) newobj(); setImageSubBuffer(file, 'n', @n) out = '<</Type /XObject' out << ' /Subtype /Image' out << ' /Width ' + info['w'].to_s out << ' /Height ' + info['h'].to_s if info.key?('masked') out << ' /SMask ' + (@n - 1).to_s + ' 0 R' end if (info['cs']=='Indexed') out << ' /ColorSpace [/Indexed /DeviceRGB ' + (info['pal'].length / 3 - 1).to_s + ' ' + (@n + 1).to_s + ' 0 R]' else out << ' /ColorSpace /' + info['cs'] if (info['cs']=='DeviceCMYK') out << ' /Decode [1 0 1 0 1 0 1 0]' end end out << ' /BitsPerComponent ' + info['bpc'].to_s if (!info['f'].nil?) out << ' /Filter /' + info['f'] end if (!info['parms'].nil?) out << ' ' + info['parms'] end if (!info['trns'].nil? and info['trns'].kind_of?(Array)) trns=''; count_info = info['trns'].length count_info.times do |i| trns << info['trns'][i] + ' ' + info['trns'][i] + ' '; end out << ' /Mask [' + trns + ']' end out << ' /Length ' + info['data'].length.to_s + '>>' out << ' ' + getstream(info['data']) out << ' endobj' out(out) #Palette if (info['cs']=='Indexed') newobj(); pal = @compress ? Zlib::Deflate.deflate(info['pal']) : info['pal'] out('<<' + filter + '/Length ' + pal.length.to_s + '>> ' + getstream(pal) + ' endobj') end end end # # Maps HTML anchor links to anchors. # [@access protected] # def mapLinksToHtmlAnchors @html_anchors.each do |anchor, positon| links = @html_anchor_links.find_all { |url, link_anchor| link_anchor == anchor} links.each {|url, link_anchor| @links[url] = positon } end end # # Output Spot Colors Resources. # [@access protected[ # [@since 4.0.024 (2008-09-12)] # def putspotcolors() @spot_colors.each { |name, color| newobj() @spot_colors[name]['n'] = @n out = '[/Separation /' + name.gsub(' ', '#20') out << ' /DeviceCMYK <<' out << ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]' out << ' ' + sprintf('/C1 [%.4f %.4f %.4f %.4f] ', color['c']/100.0, color['m']/100.0, color['y']/100.0, color['k']/100.0) out << ' /FunctionType 2 /Domain [0 1] /N 1>>]' out << ' endobj' out(out) } end # # putresourcedict # [@access protected] # def putresourcedict() out = '2 0 obj' out << ' << /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]' out << ' /Font <<' @fontkeys.each do |fontkey| font = getFontBuffer(fontkey) out << ' /F' + font['i'].to_s + ' ' + font['n'].to_s + ' 0 R' end out << ' >>' out << ' /XObject <<' @imagekeys.each do |file| info = getImageBuffer(file) out << ' /I' + info['i'].to_s + ' ' + info['n'].to_s + ' 0 R' end out << ' >>' # visibility out << ' /Properties <</OC1 ' + @n_ocg_print.to_s + ' 0 R /OC2 ' + @n_ocg_view.to_s + ' 0 R>>' # transparency out << ' /ExtGState <<' @extgstates.each_with_index { |extgstate, k| if extgstate if extgstate['name'] out << ' /' + extgstate['name'] else out << ' /GS' + k.to_s end out << ' ' + extgstate['n'].to_s + ' 0 R' end } out << ' >>' =begin # gradient patterns if @gradients and !@gradients.empty? out << ' /Pattern <<' @gradients.each_with_index {|grad, id| out << ' /p' + id + ' ' + grad['pattern'] + ' 0 R' } out << ' >>' end # gradient shadings if @gradients and !@gradients.empty? out << ' /Shading <<' @gradients.each_with_index {|grad, id| out << ' /Sh' + id + ' ' + grad['id'] + ' 0 R' } out << ' >>' end # spot colors if @spot_colors and !@spot_colors.empty? out << ' /ColorSpace <<' @spot_colors.each {|color| out << ' /CS' + color['i'] + ' ' + color['n'] + ' 0 R' } out << ' >>' end =end out << ' >> endobj' out(out) end # # Output Resources. # [@access protected] # def putresources() mapLinksToHtmlAnchors() putextgstates() putocg() putfonts(); putimages(); putspotcolors() #Resource dictionary @offsets[2]=@bufferlen putresourcedict(); putbookmarks() putEmbeddedFiles() putannotsobjs() # encryption ### T.B.D ### TCPDF 5.0.000 ### end # # Adds some Metadata information (Document Information Dictionary) # * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference) # [@access protected] # def putinfo() newobj() out = '<<' if !empty_string(@title) # The document's title. out << ' /Title ' + textstring(@title) end if !empty_string(@author) # The name of the person who created the document. out << ' /Author ' + textstring(@author) end if !empty_string(@subject) # The subject of the document. out << ' /Subject ' + textstring(@subject) end if !empty_string(@keywords) # Keywords associated with the document. out << ' /Keywords ' + textstring(@keywords) end if !empty_string(@creator) # If the document was converted to PDF from another format, the name of the conforming product that created the original document from which it was converted. out << ' /Creator ' + textstring(@creator) end if defined?(PDF_PRODUCER) # If the document was converted to PDF from another format, the name of the conforming product that converted it to PDF. out << ' /Producer ' + textstring(PDF_PRODUCER) else # default producer out << ' /Producer ' + textstring('RBPDF') end # The date and time the document was created, in human-readable form out << ' /CreationDate ' + datestring() # The date and time the document was most recently modified, in human-readable form out << ' /ModDate ' + datestring() # A name object indicating whether the document has been modified to include trapping information # out << ' /Trapped /False' out << ' >> endobj' out(out) end # # putcatalog # [@access protected] # def putcatalog() newobj() out = '<< /Type /Catalog' out << ' /Pages 1 0 R' if (@zoom_mode=='fullpage') out << ' /OpenAction [3 0 R /Fit]' elsif (@zoom_mode=='fullwidth') out << ' /OpenAction [3 0 R /FitH null]' elsif (@zoom_mode=='real') out << ' /OpenAction [3 0 R /XYZ null null 1]' elsif @zoom_mode.is_a?(Numeric) out << ' /OpenAction [3 0 R /XYZ null null ' + (@zoom_mode/100.0).to_s + ']' end if @layout_mode and !empty_string(@layout_mode) out << ' /PageLayout /' + @layout_mode end if @page_mode and !empty_string(@page_mode) out << ' /PageMode /' + @page_mode end if @l['a_meta_language'] out << ' /Lang /' + @l['a_meta_language'] end out << ' /Names <<' if !@javascript.empty? or !@js_objects.empty? out << ' /JavaScript ' + @n_js + ' 0 R' end out << ' >>' if @outlines.size > 0 out << ' /Outlines ' + @outline_root.to_s + ' 0 R' out << ' /PageMode /UseOutlines' end out << ' ' + putviewerpreferences() p = @n_ocg_print.to_s + ' 0 R' v = @n_ocg_view.to_s + ' 0 R' as = '<</Event /Print /OCGs [' + p + ' ' + v + '] /Category [/Print]>> <</Event /View /OCGs [' + p + ' ' + v + '] /Category [/View]>>' out << ' /OCProperties <</OCGs [' + p + ' ' + v + '] /D <</ON [' + p + '] /OFF [' + v + '] /AS [' + as + ']>>>>' ### T.B.D ### TCPDF 5.0.000 ### out << ' >> endobj' out(out) end # # Output viewer preferences. # [@return string] for viewer preferences # [@author] Nicola asuni # [@since 3.1.000 (2008-06-09)] # [@access protected] # def putviewerpreferences() out = '/ViewerPreferences <<' if @rtl out << ' /Direction /R2L' else out << ' /Direction /L2R' end if @viewer_preferences['HideToolbar'] and (@viewer_preferences['HideToolbar'] == true) out << ' /HideToolbar true' end if @viewer_preferences['HideMenubar'] and (@viewer_preferences['HideMenubar'] == true) out << ' /HideMenubar true' end if @viewer_preferences['HideWindowUI'] and (@viewer_preferences['HideWindowUI'] == true) out << ' /HideWindowUI true' end if @viewer_preferences['FitWindow'] and (@viewer_preferences['FitWindow'] == true) out << ' /FitWindow true' end if @viewer_preferences['CenterWindow'] and (@viewer_preferences['CenterWindow'] == true) out << ' /CenterWindow true' end if @viewer_preferences['DisplayDocTitle'] and (@viewer_preferences['DisplayDocTitle'] == true) out << ' /DisplayDocTitle true' end if @viewer_preferences['NonFullScreenPageMode'] out << ' /NonFullScreenPageMode /' + @viewer_preferences['NonFullScreenPageMode'] end if @viewer_preferences['ViewArea'] out << ' /ViewArea /' + @viewer_preferences['ViewArea'] end if @viewer_preferences['ViewClip'] out << ' /ViewClip /' + @viewer_preferences['ViewClip'] end if @viewer_preferences['PrintArea'] out << ' /PrintArea /' + @viewer_preferences['PrintArea'] end if @viewer_preferences['PrintClip'] out << ' /PrintClip /' + @viewer_preferences['PrintClip'] end if @viewer_preferences['PrintScaling'] out << ' /PrintScaling /' + @viewer_preferences['PrintScaling'] end if @viewer_preferences['Duplex'] and !empty_string(@viewer_preferences['Duplex']) out << ' /Duplex /' + @viewer_preferences['Duplex'] end if @viewer_preferences['PickTrayByPDFSize'] if @viewer_preferences['PickTrayByPDFSize'] out << ' /PickTrayByPDFSize true' else out << ' /PickTrayByPDFSize false' end end if @viewer_preferences['PrintPageRange'] print_page_range_num = '' @viewer_preferences['PrintPageRange'].each { |v| print_page_range_num << ' ' + (v - 1).to_s + '' } out << ' /PrintPageRange [' + print_page_range_num.sub(/^ /, '') + ']' end if @viewer_preferences['NumCopies'] out << ' /NumCopies ' + @viewer_preferences['NumCopies'].to_s end out << ' >>' return out end # # puttrailer # [@access protected] # def puttrailer() out = 'trailer <<' out << ' /Size ' + (@n+1).to_s out << ' /Root ' + @n.to_s + ' 0 R' out << ' /Info ' + (@n-1).to_s + ' 0 R' #if @encrypted # out << ' /Encrypt ' + @encryptdata['objid'] + ' 0 R' # out << ' /ID [ <' + @file_id + '> <' + @file_id + '> ]' #end out << ' >>' out(out) end # # putheader # [@access protected] # def putheader() out('%PDF-' + @pdf_version); end # # Output end of document (EOF). # [@access protected] # def enddoc() @state = 1 putheader(); putpages(); putresources(); ### T.B.D ### TCPDF 5.0.000 ### #Info putinfo(); #Catalog putcatalog(); #Cross-ref o=@bufferlen out('xref'); out('0 ' + (@n+1).to_s); out('0000000000 65535 f '); 1.upto(@n) do |i| out(sprintf('%010d 00000 n ',@offsets[i])); end # Embedded Files if !@embeddedfiles.nil? and (@embeddedfiles.length > 0) out(@embedded_start_obj_id.to_s + ' ' + @embeddedfiles.length.to_s) @embeddedfiles.each { |filename, filedata| out(sprintf('%010d 00000 n ', @offsets[filedata['n']])) } end # Annotation Objects if @annot_obj_id > @annots_start_obj_id out((@annots_start_obj_id + 1).to_s + ' ' + (@annot_obj_id - @annots_start_obj_id).to_s) (@annots_start_obj_id + 1).upto(@annot_obj_id) do |i| out(sprintf('%010d 00000 n ', @offsets[i])) end end #Trailer puttrailer(); out('startxref'); out(o); out('%%EOF'); @state=3 # end-of-doc if @diskcache # remove temporary files used for images @imagekeys.each do |key| # remove temporary files File.delete(@images[key].path) end @fontkeys.each do |key| # remove temporary files File.delete(@fonts[key].path) end end end # # Initialize a new page # beginpage # [@param string :orientation] # page orientation. Possible values are (case insensitive): # * P or PORTRAIT (default) # * L or LANDSCAPE # [@param mixed :format] # The format used for pages. It can be either:<b>A string indicating the page format:</b> # * 4A0,2A0,A0,A1,A2,A3,A4 (default),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,EXECUTIVE,FOLIO # <b>An array containing page measures and advanced options:</b> see setPageFormat() # [@access protected] # def beginpage(orientation='', format='') @page += 1; setPageBuffer(@page, '') # initialize array for graphics tranformation positions inside a page buffer @state=2; if empty_string(orientation) if @cur_orientation orientation = @cur_orientation elsif @fw_pt > @fh_pt # landscape orientation = 'L' else # portrait orientation = 'P' end end if empty_string(format) @pagedim[@page] = @pagedim[@page - 1] setPageOrientation(orientation) else setPageFormat(format, orientation) end if @rtl @x = @w - @r_margin else @x = @l_margin end @y=@t_margin; if !@newpagegroup[@page].nil? # start a new group n = @pagegroups.size + 1 alias_nb = '{nb' + n.to_s + '}' @pagegroups[alias_nb] = 1 @currpagegroup = alias_nb elsif @currpagegroup @pagegroups[@currpagegroup] += 1 end end # # End of page contents # [@access protected] # def endpage() setVisibility('all') @state=1; end # # Begin a new object and return the object number # [@return int] object number # [@access protected] # def newobj() @n += 1; @offsets[@n]=@bufferlen out(@n.to_s + ' 0 obj'); return @n end # # Underline text # [@param int :x] X coordinate # [@param int :y] Y coordinate # [@param string :txt] text to underline # [@access protected] # def dounderline(x, y, txt) w = GetStringWidth(txt) return dounderlinew(x, y, w) end # # Underline for rectangular text area. # [@param int :x] X coordinate # [@param int :y] Y coordinate # [@param int :w] width to underline # [@access protected] # [@since 4.8.008 (2009-09-29)] # def dounderlinew(x, y, w) linew = - @current_font['ut'] / 1000.0 * @font_size_pt return sprintf('%.2f %.2f %.2f %.2f re f', x * @k, (@h - y) * @k + linew, w * @k, linew) end # # Line through text # [@param int :x] X coordinate # [@param int :y] Y coordinate # [@param string :txt] text to linethrough # [@access protected] # def dolinethrough(x, y, txt) w = GetStringWidth(txt) return dolinethroughw(x, y, w) end # # Line through for rectangular text area. # [@param int :x] X coordinate # [@param int :y] Y coordinate # [@param int :w] width to linethrough # [@access protected] # [@since 4.9.008 (2009-09-29)] # def dolinethroughw(x, y, w) linew = - @current_font['ut'] / 1000.0 * @font_size_pt return sprintf('%.2f %.2f %.2f %.2f re f', x * @k, (@h - y) * @k + linew + (@font_size_pt / 3.0), w * @k, linew) end # # Overline text. # [@param int :x] X coordinate # [@param int :y] Y coordinate # [@param string :txt] text to overline # [@access protected] # [@since 4.9.015 (2010-04-19)] # def dooverline(x, y, txt) w = GetStringWidth(txt) return dooverlinew(x, y, w) end # # Overline for rectangular text area. # [@param int :x] X coordinate # [@param int :y] Y coordinate # [@param int :w] width to overline # [@access protected] # [@since 4.9.015 (2010-04-19)] # def dooverlinew(x, y, w) linew = - @current_font['ut'] / 1000.0 * @font_size_pt return sprintf('%.2f %.2f %.2f %.2f re f', x * @k, (@h - y + @font_ascent) * @k - linew, w * @k, linew) end # # Read a 4-byte (32 bit) integer from file # [@param string :f] file name. # [@return] 4-byte integer # [@access protected] # def freadint(f) # Read a 4-byte integer from file a = f.read(4).unpack('N') return a[0] end # # Add \ before \, ( and ) # [@access protected] # def escape(s) # Add \ before \, ( and ) s.gsub('\\','\\\\\\').gsub('(','\\(').gsub(')','\\)').gsub(13.chr, '\r') end # # Format a data string for meta information # [@param string :s] data string to escape. # [@return string] escaped string. # [@access protected] # def datastring(s) s = encrypt_data(@n, s) return '(' + escape(s) + ')' end # # Format a data string for annotation objects # [@param string :s] data string to escape. # [@return string] escaped string. # [@access protected] # def dataannobjstring(s) s = encrypt_data(@annot_obj_id + 1, s) return '(' + escape(s) + ')' end # # Returns a formatted date for meta information # [@return string] escaped date string. # [@access protected] # [@since 4.6.028 (2009-08-25)] # def datestring() current_time = Time.now.strftime('%Y%m%d%H%M%S%z').insert(-3, '\'') + '\'' return datastring('D:' + current_time) end # # Format a text string for meta information # [@param string :s] string to escape. # [@return string] escaped string. # [@access protected] # def textstring(s) if (@is_unicode) #Convert string to UTF-16BE s = UTF8ToUTF16BE(s, true); end return datastring(s) end # # Format a UTF-8 text string for meta information on annotations # [@param string :s] string to escape. # [@return string] escaped string. # [@access protected] # def textannobjstring(s) if @is_unicode # Convert string to UTF-16BE s = UTF8ToUTF16BE(s, true) end return dataannobjstring(s) end # # Format a text string # [@param string :s] string to escape. # [@return string] escaped string. # [@access protected] # def escapetext(s) if (@is_unicode) if (@current_font['type'] == 'core') or (@current_font['type'] == 'TrueType') or (@current_font['type'] == 'Type1') s = UTF8ToLatin1(s) else # Convert string to UTF-16BE and reverse RTL language s = utf8StrRev(s, false, rtl_text_dir) end end return escape(s); end # # Format output stream # @param string :s string to output. # @param int :n object reference for encryption mode # @access protected # def getstream(s, n=0) if n <= 0 # default to current object n = @n end s = encrypt_data(n, s) return "stream\n" + s + "\nendstream" end # # Output a stream. # [@param string :s] string to output. # [@param int :n] object reference for encryption mode # [@access protected] # def putstream(s, n=0) out(getstream(s, n)) end # # Output a string to the document. # [@param string :s] string to output. # [@access protected] # def out(s) s.force_encoding('ASCII-8BIT') if s.respond_to?(:force_encoding) if (@state==2) if !@in_footer and !@footerlen[@page].nil? and (@footerlen[@page] > 0) # puts data before page footer pagebuff = getPageBuffer(@page) page = pagebuff[0..(-@footerlen[@page]-1)] footer = pagebuff[-@footerlen[@page]..-1] setPageBuffer(@page, page + s.to_s + "\n" + footer) # update footer position @footerpos[@page] += (s.to_s + "\n").length else setPageBuffer(@page, s.to_s + "\n", true) end else setBuffer(s.to_s + "\n") end end # # Converts UTF-8 strings to codepoints array. # Invalid byte sequences will be replaced with 0xFFFD (replacement character) # * Based on: http://www.faqs.org/rfcs/rfc3629.html # # Char. number range | UTF-8 octet sequence # (hexadecimal) | (binary) # --------------------+------------------------------------------------ # 0000 0000-0000 007F | 0xxxxxxx # 0000 0080-0000 07FF | 110xxxxx 10xxxxxx # 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx # 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx # --------------------------------------------------------------------- # # ABFN notation: # --------------------------------------------------------------------- # UTF8-octets =#( UTF8-char ) # UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 # UTF8-1 = %x00-7F # UTF8-2 = %xC2-DF UTF8-tail # # UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / # %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) # UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / # %xF4 %x80-8F 2( UTF8-tail ) # UTF8-tail = %x80-BF # --------------------------------------------------------------------- # # [@param string :str] string to process. (UTF-8) # [@return array] containing codepoints (UTF-8 characters values) (UCS4) # [@access protected] # [@author] Nicola Asuni # [@since 1.53.0.TC005 (2005-01-05)] # def UTF8StringToArray(str) if @cache_utf8_string_to_array[str] # return cached value return @cache_utf8_string_to_array[str].dup end # check cache size if @cache_size_utf8_string_to_array >= @cache_maxsize_utf8_string_to_array # remove first element @cache_utf8_string_to_array.shift end @cache_size_utf8_string_to_array += 1 if !@is_unicode # split string into array of chars strarr = str.split(//) # convert chars to equivalent code strarr.each_with_index do |char, pos| # was while(list(pos,char)=each(strarr)) strarr[pos] = char.unpack('C')[0] end # insert new value on cache @cache_utf8_string_to_array[str] = strarr.dup return strarr end unichar = -1 # last unicode char unicode = [] # array containing unicode values bytes = [] # array containing single character byte sequences numbytes = 1; # number of octetc needed to represent the UTF-8 character str = str.to_s; # force :str to be a string str.each_byte do |char| if (bytes.length == 0) # get starting octect if (char <= 0x7F) unichar = char # use the character "as is" because is ASCII numbytes = 1 elsif ((char >> 0x05) == 0x06) # 2 bytes character (0x06 = 110 BIN) bytes << ((char - 0xC0) << 0x06) numbytes = 2 elsif ((char >> 0x04) == 0x0E) # 3 bytes character (0x0E = 1110 BIN) bytes << ((char - 0xE0) << 0x0C) numbytes = 3 elsif ((char >> 0x03) == 0x1E) # 4 bytes character (0x1E = 11110 BIN) bytes << ((char - 0xF0) << 0x12) numbytes = 4 else # use replacement character for other invalid sequences unichar = 0xFFFD bytes = [] numbytes = 1 end elsif ((char >> 0x06) == 0x02) # bytes 2, 3 and 4 must start with 0x02 = 10 BIN bytes << (char - 0x80) if (bytes.length == numbytes) # compose UTF-8 bytes to a single unicode value char = bytes[0] 1.upto(numbytes-1) do |j| char += (bytes[j] << ((numbytes - j - 1) * 0x06)) end if (((char >= 0xD800) and (char <= 0xDFFF)) or (char >= 0x10FFFF)) # The definition of UTF-8 prohibits encoding character numbers between # U+D800 and U+DFFF, which are reserved for use with the UTF-16 # encoding form (as surrogate pairs) and do not directly represent # characters unichar = 0xFFFD # use replacement character else unichar = char # add char to array end # reset data for next char bytes = [] numbytes = 1 end else # use replacement character for other invalid sequences unichar = 0xFFFD bytes = [] numbytes = 1; end if unichar >= 0 # insert unicode value into array unicode.push unichar # store this char for font subsetting @current_font['subsetchars'][unichar] = true unichar = -1 end end # update font subsetchars setFontSubBuffer(@current_font['fontkey'], 'subsetchars', @current_font['subsetchars']) # insert new value on cache @cache_utf8_string_to_array[str] = unicode.dup return unicode; end # # Converts UTF-8 strings to UTF16-BE. # [@param string :str] string to process. # [@param boolean :setbom] if true set the Byte Order Mark (BOM = 0xFEFF) # [@return string] # [@access protected] # [@author] Nicola Asuni # [@since 1.53.0.TC005 (2005-01-05)] # [@uses] UTF8StringToArray(), arrUTF8ToUTF16BE() # def UTF8ToUTF16BE(str, setbom=true) if !@is_unicode return str # string is not in unicode end unicode = UTF8StringToArray(str) # array containing UTF-8 unicode values (UCS4) return arrUTF8ToUTF16BE(unicode, setbom) end # # Converts UTF-8 strings to Latin1 when using the standard 14 core fonts. # [@param string :str] string to process. # [@return string] # [@author] Andrew Whitehead, Nicola Asuni # [@access protected] # [@since 3.2.000 (2008-06-23)] # def UTF8ToLatin1(str) if !@is_unicode return str # string is not in unicode end outstr = '' # string to be returned unicode = UTF8StringToArray(str) # array containing UTF-8 unicode values unicode.each {|char| if char < 256 outstr << char.chr elsif @@utf8tolatin.key?(char) # map from UTF-8 outstr << @@utf8tolatin[char].chr elsif char == 0xFFFD # skip else outstr << '?' end } return outstr end # # Converts UTF-8 characters array to array of Latin1 characters # [@param array :unicode] array containing UTF-8 unicode values # [@return array] # [@author] Nicola Asuni # [@access protected] # [@since 4.8.023 (2010-01-15)] # def UTF8ArrToLatin1(unicode) if !@is_unicode or (@current_font['type'] == 'TrueTypeUnicode') or (@current_font['type'] == 'cidfont0') return unicode # string is not in unicode end outarr = [] # array to be returned unicode.each {|char| if char < 256 outarr.push char elsif @@utf8tolatin.key?(char) # map from UTF-8 outarr.push @@utf8tolatin[char] elsif char == 0xFFFD # skip else outarr.push 63 # '?' character end } return outarr end # # Converts array of UTF-8 characters to UTF16-BE string. # * Based on: http://www.faqs.org/rfcs/rfc2781.html # # Encoding UTF-16: # # Encoding of a single character from an ISO 10646 character value to # UTF-16 proceeds as follows. Let U be the character number, no greater # than 0x10FFFF. # # 1) If U < 0x10000, encode U as a 16-bit unsigned integer and # terminate. # # 2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF, # U' must be less than or equal to 0xFFFFF. That is, U' can be # represented in 20 bits. # # 3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and # 0xDC00, respectively. These integers each have 10 bits free to # encode the character value, for a total of 20 bits. # # 4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order # bits of W1 and the 10 low-order bits of U' to the 10 low-order # bits of W2. Terminate. # # Graphically, steps 2 through 4 look like: # U' = yyyyyyyyyyxxxxxxxxxx # W1 = 110110yyyyyyyyyy # W2 = 110111xxxxxxxxxx # # [@param array :unicode] array containing UTF-8 unicode values (UCS4) # [@param boolean :setbom] if true set the Byte Order Mark (BOM = 0xFEFF) # [@return] string (UTF-16BE) # [@access protected] # [@author] Nicola Asuni # [@since 2.1.000 (2008-01-08)] # [@see] UTF8ToUTF16BE() # def arrUTF8ToUTF16BE(unicode, setbom=true) outstr = ""; # string to be returned if (setbom) outstr << "\xFE\xFF"; # Byte Order Mark (BOM) end unicode.each do |char| if (char == 0xFFFD) outstr << "\xFF\xFD"; # replacement character elsif (char < 0x10000) outstr << (char >> 0x08).chr; outstr << (char & 0xFF).chr; else char -= 0x10000; w1 = 0xD800 | (char >> 0x10); w2 = 0xDC00 | (char & 0x3FF); outstr << (w1 >> 0x08).chr; outstr << (w1 & 0xFF).chr; outstr << (w2 >> 0x08).chr; outstr << (w2 & 0xFF).chr; end end return outstr; end # ==================================================== public # # Set Font Subsetting. # [@param boolean :subset] subset of the font default setting. # [@access public] # def setFontSubsetting(subset) @font_subsetting = (subset == true ? true : false) end alias_method :set_font_subsetting, :setFontSubsetting # # Get Font Subsetting. # [@return boolean] # [@access public] # def getFontSubsetting() return @font_subsetting end alias_method :get_font_subsetting, :getFontSubsetting # # Set header font. # [@param array :font] font # [@access public] # [@since 1.1] # def setHeaderFont(font) @header_font = font; end alias_method :set_header_font, :setHeaderFont def SetHeaderFont(font) warn "[DEPRECATION] 'SetHeaderFont' is deprecated. Please use 'set_header_font' instead." setHeaderFont(font) end # # Get header font. # [@return array] # [@access public] # [@since 4.0.012 (2008-07-24)] # def getHeaderFont() return @header_font end alias_method :get_header_font, :getHeaderFont # # Set footer font. # [@param array :font] font # [@access public] # [@since 1.1] # def setFooterFont(font) @footer_font = font; end alias_method :set_footer_font, :setFooterFont def SetFooterFont(font) warn "[DEPRECATION] 'SetFooterFont' is deprecated. Please use 'set_footer_font' instead." setFooterFont(font) end # # Get Footer font. # [@return array] # [@access public] # [@since 4.0.012 (2008-07-24)] # def getFooterFont(font) return @footer_font end alias_method :get_footer_font, :getFooterFont # # Set language array. # [@param array :language] # [@since 1.1] # def setLanguageArray(language) @l = language; if @l['a_meta_dir'] @rtl = (@l['a_meta_dir'] == 'rtl') ? true : false else @rtl = false end end alias_method :set_language_array, :setLanguageArray def SetLanguageArray(language) warn "[DEPRECATION] 'SetLanguageArray' is deprecated. Please use 'set_language_array' instead." setLanguageArray(language) end # # Returns the PDF data. # [@access public] # def getPDFData() if (@state < 3) Close(); end return @buffer; end alias_method :get_pdf_data, :getPDFData def GetPDFData() warn "[DEPRECATION] 'GetPDFData' is deprecated. Please use 'get_pdf_data' instead." getPDFData() end # # Output anchor link. # [@param string :url] link URL or internal link (i.e.: <a href="#23">link to page 23</a>) # [@param string :name] link name # [@param int :fill] Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. # [@param boolean :firstline] if true prints only the first line and return the remaining string. # [@param array :color] array of RGB text color # [@param string :style] font style (U, D, B, I) # [@param boolean :firstblock] if true the string is the starting of a line. # [@return] the number of cells used or the remaining text if :firstline = true # [@access public] # def addHtmlLink(url, name, fill=0, firstline=false, color='', style=-1, firstblock=false) if !empty_string(url) and (url[0, 1] == '#') # convert url to internal link anchor = url.sub(/^#/, "") page = anchor.to_i url = AddLink() @html_anchor_links[url] = anchor SetLink(url, 0, page) end # store current settings prevcolor = @fgcolor prevstyle = @font_style if color.empty? SetTextColorArray(@html_link_color_array) else SetTextColorArray(color) end if style == -1 SetFont('', @font_style + @html_link_font_style) else SetFont('', @font_style + style) end ret = Write(@lasth, name, url, fill, '', false, 0, firstline, firstblock, 0) # restore settings SetFont('', prevstyle) SetTextColorArray(prevcolor) return ret end alias_method :add_html_link, :addHtmlLink # # Adds HTML anchor and remembers it's position # [@param string :anchor] html anchor id # [@access public] # def addHtmlAnchor(anchor) @html_anchors[anchor] = [@page, @y] end alias_method :add_html_anchor, :addHtmlAnchor # # Outputs HTML anchor position # [@param string :anchor] html anchor id # [@return array] [Page, Y] of anchor # [@access public] # def getHtmlAnchorPosition(anchor) @html_anchors[anchor] end alias_method :get_html_anchor_position, :getHtmlAnchorPosition # # Returns an associative array (keys: R,G,B) from an html color name or a six-digit or three-digit hexadecimal color representation (i.e. #3FE5AA or #7FF). # [@param string :color] html color # [@return array] RGB color or empty array in case of error. # [@access public] # def convertHTMLColorToDec(color = "#FFFFFF") color = color.gsub(/[\s]*/, '') # remove extra spaces color = color.downcase if !(dotpos = color.index('.')).nil? # remove class parent (i.e.: color.red) color = color[(dotpos + 1)..-1] end if color.length == 0 return [] end returncolor = ActiveSupport::OrderedHash.new # RGB ARRAY if color[0,3] == 'rgb' codes = color.sub(/^rgb\(/, '') codes = codes.gsub(')', '') returncolor = codes.split(',', 3) returncolor[0] = returncolor[0].to_i returncolor[1] = returncolor[1].to_i returncolor[2] = returncolor[2].to_i return returncolor end # CMYK ARRAY if color[0,4] == 'cmyk' codes = color.sub(/^cmyk\(/, '') codes = codes.gsub(')', '') returncolor[0] = returncolor[0].to_i returncolor[1] = returncolor[1].to_i returncolor[2] = returncolor[2].to_i returncolor[3] = returncolor[3].to_i return returncolor end # COLOR NAME if color[0].chr != "#" # decode color name if @@webcolor[color] color_code = @@webcolor[color] else return [] end else color_code = color.sub(/^#/, "") end # RGB VALUE case color_code.length when 3 # three-digit hexadecimal representation r = color_code[0] g = color_code[1] b = color_code[2] returncolor['R'] = (r + r).hex returncolor['G'] = (g + g).hex returncolor['B'] = (b + b).hex when 6 # six-digit hexadecimal representation returncolor['R'] = color_code[0,2].hex returncolor['G'] = color_code[2,2].hex returncolor['B'] = color_code[4,2].hex else returncolor = [] end return returncolor end alias_method :convert_html_color_to_dec, :convertHTMLColorToDec # # Converts pixels to Units. # [@param int] :px pixels # [@return float] millimeters # [@access public] # [@see] setImageScale(), getImageScale() # def pixelsToUnits(px) return (px.to_f / (@img_scale * @k)) end alias_method :pixels_to_units, :pixelsToUnits # # Reverse function for htmlentities. # Convert entities in UTF-8. # # [@param :text_to_convert] Text to convert. # [@return string] converted # [@access public] # def unhtmlentities(string) if @@decoder.nil? CGI.unescapeHTML(string) else @@decoder.decode(string) end end # ENCRYPTION METHODS ---------------------------------- # # Encrypt the input string. # * Note: not yet implemented. # [@param int :n] object number # [@param string :s] data string to encrypt # [@access protected] # [@author] Nicola Asuni # [@since 5.0.005 (2010-05-11)] # def encrypt_data(n, s) #unless @encrypted return s #end #case @encryptdata['mode'] #when 0, 1: # 0: RC4 40 bit, 1: RC4 128 bit # s = _RC4(objectkey(n), s) #when 2: # AES 128 bit # s = _AES(objectkey(n), s) #end #return s end protected :encrypt_data # END OF ENCRYPTION FUNCTIONS ------------------------- # START TRANSFORMATIONS SECTION ----------------------- # # Starts a 2D tranformation saving current graphic state. # This function must be called before scaling, mirroring, translation, rotation and skewing. # Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior. # [@access public] # [@since 2.1.000 (2008-01-07)] # [@see] StartTransform(), StopTransform() # def StartTransform out('q'); @transfmrk[@page] = @pagelen[@page] @transfmatrix_key += 1 @transfmatrix[@transfmatrix_key] = [] end alias_method :start_transform, :StartTransform # # Stops a 2D tranformation restoring previous graphic state. # This function must be called after scaling, mirroring, translation, rotation and skewing. # Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior. # [@access public] # [@since 2.1.000 (2008-01-07)] # [@see] StartTransform(), StopTransform() # def StopTransform out('Q'); if @transfmatrix[@transfmatrix_key] @transfmatrix[@transfmatrix_key].pop @transfmatrix_key -= 1 end @transfmrk[@page] = nil end alias_method :stop_transform, :StopTransform # # Rotate object. # [@param float :angle] angle in degrees for counter-clockwise rotation # [@param int :x] abscissa of the rotation center. Default is current x position # [@param int :y] ordinate of the rotation center. Default is current y position # [@access public] # [@since 2.1.000 (2008-01-07)] # [@see] StartTransform(), StopTransform() # def Rotate(angle, x="", y="") if (x == '') x = @x end if (y == '') y = @y end y = (@h - y) * @k x *= @k # calculate elements of transformation matrix tm = [] tm[0] = ::Math::cos(angle * ::Math::PI / 180) # deg2rad tm[1] = ::Math::sin(angle * ::Math::PI / 180) # deg2rad tm[2] = -tm[1] tm[3] = tm[0] tm[4] = x + tm[1] * y - tm[0] * x tm[5] = y - tm[0] * y - tm[1] * x # generate the transformation matrix Transform(tm) end alias_method :rotate, :Rotate # # Apply graphic transformations. # [@param array :tm] transformation matrix # [@access protected] # [@since 2.1.000 (2008-01-07)] # [@see] StartTransform(), StopTransform() # def Transform(tm) out(sprintf('%.3f %.3f %.3f %.3f %.3f %.3f cm', tm[0], tm[1], tm[2], tm[3], tm[4], tm[5])) # add tranformation matrix @transfmatrix[@transfmatrix_key].push 'a' => tm[0], 'b' => tm[1], 'c' => tm[2], 'd' => tm[3], 'e' => tm[4], 'f' => tm[5] # update tranformation mark if !@transfmrk[@page].nil? @transfmrk[@page] = @pagelen[@page] end end protected :Transform # END TRANSFORMATIONS SECTION ------------------------- # START GRAPHIC FUNCTIONS SECTION --------------------- # The following section is based on the code provided by David Hernandez Sanz # # Defines the line width. By default, the value equals 0.2 mm. The method can be called before the first page is created and the value is retained from page to page. # [@param float :width] The width. # [@access public] # [@since 1.0] # [@see] Line(), Rect(), Cell(), MultiCell() # def SetLineWidth(width) #Set line width @line_width = width; @linestyle_width = sprintf('%.2f w', width * @k) if (@page>0) out(@linestyle_width) end end alias_method :set_line_width, :SetLineWidth # # Returns the current the line width. # [@return int] Line width # [@access public] # [@since 2.1.000 (2008-01-07)] # [@see] Line(), SetLineWidth() # def GetLineWidth() return @line_width end alias_method :get_line_width, :GetLineWidth # # Set line style. # [@param hash :style] # Line style. Array with keys among the following: # * width (float): Width of the line in user units. # * cap (string): Type of cap to put on the line. Possible values are: butt, round, square. The difference between "square" and "butt" is that "square" projects a flat end past the end of the line. # * join (string): Type of join. Possible values are: miter, round, bevel. # * dash (mixed): Dash pattern. Is 0 (without dash) or string with series of length values, which are the lengths of the on and off dashes. For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on, 1 off, 2 on, 1 off, ... # * phase (integer): Modifier on the dash pattern which is used to shift the point at which the pattern starts. # * color (array): Draw color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). # [@access public] # [@since 2.1.000 (2008-01-08)] # def SetLineStyle(style) unless style.is_a? Hash return end if !style['width'].nil? width = style['width'] width_prev = @line_width SetLineWidth(width) @line_width = width_prev end if !style['cap'].nil? cap = style['cap'] ca = {'butt' => 0, 'round'=> 1, 'square' => 2} if !ca[cap].nil? @linestyle_cap = ca[cap].to_s + ' J' out(@linestyle_cap) end end if !style['join'].nil? join = style['join'] ja = {'miter' => 0, 'round' => 1, 'bevel' => 2} if !ja[join].nil? @linestyle_join = ja[join].to_s + ' j' out(@linestyle_join); end end if !style['dash'].nil? dash = style['dash'] dash_string = '' if dash != 0 and dash != '' if dash =~ /^.+,/ tab = dash.split(',') else tab = [dash] end dash_string = '' tab.each_with_index { |v, i| if i != 0 dash_string << ' ' end dash_string << sprintf("%.2f", v.to_f) } end phase = 0 @linestyle_dash = sprintf("[%s] %.2f d", dash_string, phase) out(@linestyle_dash) end if !style['color'].nil? color = style['color'] SetDrawColorArray(color) end end alias_method :set_line_style, :SetLineStyle protected # # Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment. # [@param float :x] Abscissa of point. # [@param float :y] Ordinate of point. # [@access protected] # [@since 2.1.000 (2008-01-08)] # def outPoint(x, y) out(sprintf("%.2f %.2f m", x * @k, (@h - y) * @k)) end # # Append a straight line segment from the current point to the point (x, y). # The new current point shall be (x, y). # [@param float :x] Abscissa of end point. # [@param float :y] Ordinate of end point. # [@access protected] # [@since 2.1.000 (2008-01-08)] # def outLine(x, y) out(sprintf("%.2f %.2f l", x * @k, (@h - y) * @k)) end # # Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space. # [@param float :x] Abscissa of upper-left corner (or upper-right corner for RTL language). # [@param float :y] Ordinate of upper-left corner (or upper-right corner for RTL language). # [@param float :w] Width. # [@param float :h] Height. # [@param string :op] options # [@access protected] # [@since 2.1.000 (2008-01-08)] # def outRect(x, y, w, h, op) out(sprintf('%.2f %.2f %.2f %.2f re %s', x * @k, (@h - y) * @k, w * @k, -h * @k, op)) end # # Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x2, y2) as the Bezier control points. # The new current point shall be (x3, y3). # [@param float :x1] Abscissa of control point 1. # [@param float :y1] Ordinate of control point 1. # [@param float :x2] Abscissa of control point 2. # [@param float :y2] Ordinate of control point 2. # [@param float :x3] Abscissa of end point. # [@param float :y3] Ordinate of end point. # [@access protected] # [@since 2.1.000 (2008-01-08)] # def outCurve(x1, y1, x2, y2, x3, y3) out(sprintf("%.2f %.2f %.2f %.2f %.2f %.2f c", x1 * @k, (@h - y1) * @k, x2 * @k, (@h - y2) * @k, x3 * @k, (@h - y3) * @k)) end # # Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using the current point and (x2, y2) as the Bezier control points. # The new current point shall be (x3, y3). # [@param float :x2] Abscissa of control point 2. # [@param float :y2] Ordinate of control point 2. # [@param float :x3] Abscissa of end point. # [@param float :y3] Ordinate of end point. # [@access protected] # [@since 4.9.019 (2010-04-26)] # def outCurveV(x2, y2, x3, y3) out(sprintf('%.2f %.2f %.2f %.2f v', x2 * @k, (@h - y2) * @k, x3 * @k, (@h - y3) * @k)) end # # Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x3, y3) as the Bezier control points. # The new current point shall be (x3, y3). # [@param float :x1] Abscissa of control point 1. # [@param float :y1] Ordinate of control point 1. # [@param float :x2] Abscissa of control point 2. # [@param float :y2] Ordinate of control point 2. # [@param float :x3] Abscissa of end point. # [@param float :y3] Ordinate of end point. # [@access protected] # [@since 4.9.019 (2010-04-26)] # def outCurveY(x1, y1, x3, y3) out(sprintf('%.2f %.2f %.2f %.2f y', x1 * @k, (@h - y1) * @k, x3 * @k, (@h - y3) * @k)) end public # # Draws a line between two points. # [@param float :x1] Abscissa of first point # [@param float :y1] Ordinate of first point # [@param float :x2] Abscissa of second point # [@param float :y2]] Ordinate of second point # [@param hash :style] Line style. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array). # [@access public] # [@since 1.0] # [@see] SetLineWidth(), SetDrawColor(), SetLineStyle() # def Line(x1, y1, x2, y2, style=nil) if style.is_a? Hash SetLineStyle(style) end outPoint(x1, y1) outLine(x2, y2) out('S') end alias_method :line, :Line # # Draws a rectangle. # [@param float :x] Abscissa of upper-left corner (or upper-right corner for RTL language). # [@param float :y] Ordinate of upper-left corner (or upper-right corner for RTL language). # [@param float :w] Width. # [@param float :h] Height. # [@param string :style] # Style of rendering. See the getPathPaintOperator() function for more information. # * all: Line style of all borders. Array like for {@link SetLineStyle SetLineStyle}. # * L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for {@link SetLineStyle SetLineStyle}. # If a key is not present or is null, not draws the border. Default value: default line style (empty array). # [@param array :border_style] Border style of rectangle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array). # [@param array :fill_color] Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array). # [@access public] # [@since 1.0] # [@see] SetLineStyle() # def Rect(x, y, w, h, style='', border_style={}, fill_color={}) if style.index('F') != nil and !fill_color.empty? SetFillColorArray(fill_color) end op = getPathPaintOperator(style) if !border_style or !border_style['all'].nil? if !border_style['all'].nil? and border_style['all'] SetLineStyle(border_style['all']) border_style = {} end end outRect(x, y, w, h, op) if border_style border_style2 = {} border_style.each { |line, value| length = line.length 0.upto(length - 1) do |i| border_style2[line[i]] = value end } border_style = border_style2 if !border_style['L'].nil? and border_style['L'] Line(x, y, x, y + h, border_style['L']) end if !border_style['T'].nil? and border_style['T'] Line(x, y, x + w, y, border_style['T']) end if !border_style['R'].nil? and border_style['R'] Line(x + w, y, x + w, y + h, border_style['R']) end if !border_style['B'].nil? and border_style['B'] Line(x, y + h, x + w, y + h, border_style['B']) end end end alias_method :rect, :Rect # # Draws a Bezier curve. # The Bezier curve is a tangent to the line between the control points at # either end of the curve. # [@param float :x0] Abscissa of start point. # [@param float :y0] Ordinate of start point. # [@param float :x1] Abscissa of control point 1. # [@param float :y1] Ordinate of control point 1. # [@param float :x2] Abscissa of control point 2. # [@param float :y2] Ordinate of control point 2. # [@param float :x3] Abscissa of end point. # [@param float :y3] Ordinate of end point. # [@param string :style] Style of rendering. See the getPathPaintOperator() function for more information. # [@param array :line_style] Line style of curve. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array). # [@param array :fill_color] Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array). # [@access public] # [@see] SetLineStyle() # [@since 2.1.000 (2008-01-08)] # def Curve(x0, y0, x1, y1, x2, y2, x3, y3, style='', line_style=nil, fill_color=nil) if style and (style.index('F') != nil) and fill_color SetFillColorArray(fill_color) end op = getPathPaintOperator(style) if line_style SetLineStyle(line_style) end outPoint(x0, y0) outCurve(x1, y1, x2, y2, x3, y3) out(op) end alias_method :curve, :Curve # # Draws an ellipse. # An ellipse is formed from n Bezier curves. # [@param float :x0] Abscissa of center point. # [@param float :y0] Ordinate of center point. # [@param float :rx] Horizontal radius. # [@param float :ry] Vertical radius (if ry = 0 then is a circle, see {@link Circle Circle}). Default value: 0. # [@param float :angle] Angle oriented (anti-clockwise). Default value: 0. # [@param float :astart] Angle start of draw line. Default value: 0. # [@param float :afinish] Angle finish of draw line. Default value: 360. # [@param string :style] Style of rendering. See the getPathPaintOperator() function for more information. # [@param array :line_style] Line style of ellipse. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array). # [@param array :fill_color] Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array). # [@param integer :nc] Number of curves used to draw a 90 degrees portion of ellipse. # [@author] Nicola Asuni # [@access public] # [@since 2.1.000 (2008-01-08)] # def Ellipse(x0, y0, rx, ry='', angle=0, astart=0, afinish=360, style='', line_style=nil, fill_color=nil, nc=2) style = '' if style.nil? if empty_string(ry) or (ry == 0) ry = rx end if (nil != style.index('F')) and fill_color SetFillColorArray(fill_color) end op = getPathPaintOperator(style) if op == 'f' line_style = nil end if line_style and !line_style.empty? SetLineStyle(line_style) end outellipticalarc(x0, y0, rx, ry, angle, astart, afinish, false, nc) out(op) end alias_method :ellipse, :Ellipse # # Append an elliptical arc to the current path. # An ellipse is formed from n Bezier curves. # [@param float :xc] Abscissa of center point. # [@param float :yc] Ordinate of center point. # [@param float :rx] Horizontal radius. # [@param float :ry] Vertical radius (if ry = 0 then is a circle, see {@link Circle Circle}). Default value: 0. # [@param float :xang] Angle between the X-axis and the major axis of the ellipse. Default value: 0. # [@param float :angs] Angle start of draw line. Default value: 0. # [@param float :angf] Angle finish of draw line. Default value: 360. # [@param boolean :pie] if true do not mark the border point (used to draw pie sectors). # [@param integer :nc] Number of curves used to draw a 90 degrees portion of ellipse. # [@author] Nicola Asuni # [@access protected] # [@since 4.9.019 (2010-04-26)] # def outellipticalarc(xc, yc, rx, ry, xang=0, angs=0, angf=360, pie=false, nc=2) k = @k if nc < 2 nc = 2 end if pie # center of the arc outPoint(xc, yc) end xang = xang * ::Math::PI / 180 # deg2rad angs = angs * ::Math::PI / 180 # deg2rad angf = angf * ::Math::PI / 180 # deg2rad as = ::Math.atan2((::Math.sin(angs) / ry), (::Math.cos(angs) / rx)) af = ::Math.atan2((::Math.sin(angf) / ry), (::Math.cos(angf) / rx)) if as < 0 as += (2 * ::Math::PI) end if af < 0 af += (2 * ::Math::PI) end if as > af # reverse rotation go clockwise as -= (2 * ::Math::PI) end total_angle = af - as if nc < 2 nc = 2 end # total arcs to draw nc *= (2 * total_angle.abs / ::Math::PI) nc = nc.round + 1 # angle of each arc arcang = total_angle / nc # center point in PDF coordiantes x0 = xc y0 = @h - yc # starting angle ang = as alpha = ::Math.sin(arcang) * (::Math.sqrt(4 + 3 * (::Math.tan(arcang / 2) ** 2)) - 1) / 3 cos_xang = ::Math.cos(xang) sin_xang = ::Math.sin(xang) cos_ang = ::Math.cos(ang) sin_ang = ::Math.sin(ang) # first arc point px1 = x0 + (rx * cos_xang * cos_ang) - (ry * sin_xang * sin_ang) py1 = y0 + (rx * sin_xang * cos_ang) + (ry * cos_xang * sin_ang) # first Bezier control point qx1 = alpha * ((-rx * cos_xang * sin_ang) - (ry * sin_xang * cos_ang)) qy1 = alpha * ((-rx * sin_xang * sin_ang) + (ry * cos_xang * cos_ang)) if pie outLine(px1, @h - py1) else outPoint(px1, @h - py1) end # draw arcs 1.upto(nc) do |i| # starting angle ang = as + i * arcang cos_xang = ::Math.cos(xang) sin_xang = ::Math.sin(xang) cos_ang = ::Math.cos(ang) sin_ang = ::Math.sin(ang) # second arc point px2 = x0 + (rx * cos_xang * cos_ang) - (ry * sin_xang * sin_ang) py2 = y0 + (rx * sin_xang * cos_ang) + (ry * cos_xang * sin_ang) # second Bezier control point qx2 = alpha * ((-rx * cos_xang * sin_ang) - (ry * sin_xang * cos_ang)) qy2 = alpha * ((-rx * sin_xang * sin_ang) + (ry * cos_xang * cos_ang)) # draw arc outCurve(px1 + qx1, @h - (py1 + qy1), px2 - qx2, @h - (py2 - qy2), px2, @h - py2) # move to next point px1 = px2 py1 = py2 qx1 = qx2 qy1 = qy2 end if pie outLine(xc, yc) end end protected :outellipticalarc # # Draws a circle. # A circle is formed from n Bezier curves. # [@param float :x0] Abscissa of center point. # [@param float :y0] Ordinate of center point. # [@param float :r] Radius. # [@param float :angstr] Angle start of draw line. Default value: 0. # [@param float :angend] Angle finish of draw line. Default value: 360. # [@param string :style] Style of rendering. See the getPathPaintOperator() function for more information. # [@param array :line_style] Line style of circle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array). # [@param array :fill_color] Fill color. Format: array(red, green, blue). Default value: default color (empty array). # [@param integer :nc] Number of curves used to draw a 90 degrees portion of circle. # [@access public] # [@since 2.1.000 (2008-01-08)] # def Circle(x0, y0, r, angstr=0, angend=360, style='', line_style=nil, fill_color=nil, nc=2) Ellipse(x0, y0, r, r, 0, angstr, angend, style, line_style, fill_color, nc) end alias_method :circle, :Circle # # Draws a polygonal line # [@param array :p] Points 0 to (:np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1)) # [@param string :style] Style of rendering. See the getPathPaintOperator() function for more information. # [@param array :line_style] # Line style of polygon. Array with keys among the following: # * all: Line style of all lines. Array like for {@link SetLineStyle SetLineStyle}. # * 0 to (:np - 1): Line style of each line. Array like for {@link SetLineStyle SetLineStyle}. # If a key is not present or is null, not draws the line. Default value is default line style (empty array). # [@param array :fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array). # [@param boolean :closed if true the polygon is closes, otherwise will remain open # [@access public] # [@since 4.8.003 (2009-09-15)] # def PolyLine(p, style='', line_style=nil, fill_color=nil) Polygon(p, style, line_style, fill_color, false) end alias_method :poly_line, :PolyLine # # Draws a polygon. # [@param array :p] Points 0 to (np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1)) # [@param string :style] Style of rendering. See the getPathPaintOperator() function for more information. # [@param array :line_style] # Line style of polygon. Array with keys among the following: # * all: Line style of all lines. Array like for {@link SetLineStyle SetLineStyle}. # * 0 to (:np - 1): Line style of each line. Array like for {@link SetLineStyle SetLineStyle}. # If a key is not present or is null, not draws the line. Default value is default line style (empty array). # [@param array :fill_color] Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array). # [@param boolean :closed] if true the polygon is closes, otherwise will remain open # [@access public] # [@since 2.1.000 (2008-01-08)] # def Polygon(p, style='', line_style=nil, fill_color=nil, closed=true) style = '' if style.nil? nc = p.length # number of coordinates np = nc / 2 # number of points if closed # close polygon by adding the first 2 points at the end (one line) 0.upto(3) do |i| p[nc + i] = p[i] end # copy style for the last added line if line_style if line_style.is_a? Array and line_style[0] line_style[np] = line_style[0] elsif line_style.is_a? Hash and line_style['all'] line_style[np] = line_style['all'] end end nc += 4 end if (nil != style.index('F')) and fill_color SetFillColorArray(fill_color) end op = getPathPaintOperator(style) if op == 'f' line_style = [] end draw = true if line_style if line_style.is_a? Hash and line_style['all'] SetLineStyle(line_style['all']) else draw = false if op == 'B' # draw fill op = 'f' outPoint(p[0], p[1]) 2.step(nc - 1, 2) do |i| outLine(p[i], p[i + 1]) end out(op) end # draw outline outPoint(p[0], p[1]) 2.step(nc - 1, 2) do |i| line_num = i / 2 - 1 if line_style[line_num] if line_style[line_num] != 0 if line_style[line_num].is_a? Hash out('S') SetLineStyle(line_style[line_num]) outPoint(p[i - 2], p[i - 1]) outLine(p[i], p[i + 1]) out('S') outPoint(p[i], p[i + 1]) else outLine(p[i], p[i + 1]) end end else outLine(p[i], p[i + 1]) end end out(op) end end if draw outPoint(p[0], p[1]) 2.step(nc - 1, 2) do |i| outLine(p[i], p[i + 1]) end out(op) end end alias_method :polygon, :Polygon # # Draws a regular polygon. # [@param float :x0] Abscissa of center point. # [@param float :y0] Ordinate of center point. # [@param float :r] Radius of inscribed circle. # [@param integer :ns] Number of sides. # [@param float :angle] Angle oriented (anti-clockwise). Default value: 0. # [@param boolean :draw_circle] Draw inscribed circle or not. Default value: false. # [@param string :style] Style of rendering. See the getPathPaintOperator() function for more information. # [@param array :line_style] # Line style of polygon sides. Array with keys among the following: # * all: Line style of all sides. Array like for {@link SetLineStyle SetLineStyle}. # * 0 to (:ns - 1): Line style of each side. Array like for {@link SetLineStyle SetLineStyle}. # If a key is not present or is null, not draws the side. Default value is default line style (empty array). # [@param array :fill_color] Fill color. Format: array(red, green, blue). Default value: default color (empty array). # [@param string :circle_style] # Style of rendering of inscribed circle (if draws). Possible values are: # * D or empty string: Draw (default). # * F: Fill. # * DF or FD: Draw and fill. # * CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path). # * CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path). # [@param array :circle_outLine_style] Line style of inscribed circle (if draws). Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array). # [@param array :circle_fill_color] Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array). # [@access public] # [@since 2.1.000 (2008-01-08)] # def RegularPolygon(x0, y0, r, ns, angle=0, draw_circle=false, style='', line_style=nil, fill_color=nil, circle_style='', circle_outLine_style=nil, circle_fill_color=nil) draw_circle = false if draw_circle == 0 if 3 > ns ns = 3 end if draw_circle Circle(x0, y0, r, 0, 360, circle_style, circle_outLine_style, circle_fill_color) end p = [] 0.upto(ns - 1) do |i| a = angle + i * 360 / ns a_rad = a * ::Math::PI / 180 # deg2rad p.push x0 + (r * ::Math.sin(a_rad)) p.push y0 + (r * ::Math.cos(a_rad)) end Polygon(p, style, line_style, fill_color) end alias_method :regular_polygon, :RegularPolygon # # Draws a star polygon # [@param float :x0] Abscissa of center point. # [@param float :y0] Ordinate of center point. # [@param float :r] Radius of inscribed circle. # [@param integer :nv] Number of vertices. # [@param integer :ng] Number of gap (if (:ng % :nv = 1) then is a regular polygon). # [@param float :angle] Angle oriented (anti-clockwise). Default value: 0. # [@param boolean :draw_circle] Draw inscribed circle or not. Default value is false. # [@param string :style] Style of rendering. See the getPathPaintOperator() function for more information. # [@param array :line_style] # Line style of polygon sides. Array with keys among the following: # * all: Line style of all sides. Array like for {@link SetLineStyle SetLineStyle}. # * 0 to (n - 1): Line style of each side. Array like for {@link SetLineStyle SetLineStyle}. # If a key is not present or is null, not draws the side. Default value is default line style (empty array). # [@param array :fill_color ]Fill color. Format: array(red, green, blue). Default value: default color (empty array). # [@param string :circle_style] # Style of rendering of inscribed circle (if draws). Possible values are: # * D or empty string: Draw (default). # * F: Fill. # * DF or FD: Draw and fill. # * CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path). # * CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path). # [@param array :circle_outLine_style] Line style of inscribed circle (if draws). Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array). # [@param array :circle_fill_color] Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array). # [@access public] # [@since 2.1.000 (2008-01-08)] # def StarPolygon(x0, y0, r, nv, ng, angle=0, draw_circle=false, style='', line_style=nil, fill_color=nil, circle_style='', circle_outLine_style=nil, circle_fill_color=nil) draw_circle = false if draw_circle == 0 if nv < 2 nv = 2 end if draw_circle Circle(x0, y0, r, 0, 360, circle_style, circle_outLine_style, circle_fill_color) end p2 = [] visited = [] 0.upto(nv - 1) do |i| a = angle + i * 360 / nv a_rad = a * ::Math::PI / 180 # deg2rad p2.push x0 + r * ::Math.sin(a_rad) p2.push y0 + r * ::Math.cos(a_rad) visited.push false end p = [] i = 0 while true p.push p2[i * 2] p.push p2[i * 2 + 1] visited[i] = true i += ng i %= nv break if visited[i] end Polygon(p, style, line_style, fill_color) end alias_method :star_polygon, :StarPolygon # # Draws a rounded rectangle. # [@param float :x] Abscissa of upper-left corner. # [@param float :y] Ordinate of upper-left corner. # [@param float :w] Width. # [@param float :h] Height. # [@param float :r] the radius of the circle used to round off the corners of the rectangle. # [@param string :round_corner] Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top left, top right, bottom right and bottom left. Default value: all rounded corner ("1111"). # [@param string :style] Style of rendering. See the getPathPaintOperator() function for more information. # [@param array :border_style] Border style of rectangle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array). # [@param array :fill_color] Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array). # [@access public] # [@since 2.1.000 (2008-01-08)] # def RoundedRect(x, y, w, h, r, round_corner='1111', style='', border_style=nil, fill_color=nil) RoundedRectXY(x, y, w, h, r, r, round_corner, style, border_style, fill_color) end alias_method :rounded_rect, :RoundedRect # # Draws a rounded rectangle. # [@param float :x] Abscissa of upper-left corner. # [@param float :y] Ordinate of upper-left corner. # [@param float :w] Width. # [@param float :h] Height. # [@param float :rx] the x-axis radius of the ellipse used to round off the corners of the rectangle. # [@param float :ry] the y-axis radius of the ellipse used to round off the corners of the rectangle. # [@param string :round_corner] Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top left, top right, bottom right and bottom left. Default value: all rounded corner ("1111"). # [@param string :style] Style of rendering. See the getPathPaintOperator() function for more information. # [@param array :border_style] Border style of rectangle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array). # [@param array :fill_color] Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array). # [@access public] # [@since 4.9.019 (2010-04-22)] # def RoundedRectXY(x, y, w, h, rx, ry, round_corner='1111', style='', border_style=nil, fill_color=nil) style = '' if style.nil? if (round_corner == '0000') or ((rx == ry) and (rx == 0)) # Not rounded Rect(x, y, w, h, style, border_style, fill_color) return end # Rounded if (nil != style.index('F')) and fill_color SetFillColorArray(fill_color) end op = getPathPaintOperator(style) if op == 'f' border_style = [] end if border_style SetLineStyle(border_style) end myArc = 4 / 3.0 * (::Math.sqrt(2) - 1) outPoint(x + rx, y) xc = x + w - rx yc = y + ry outLine(xc, y) if round_corner[0,1] == '1' outCurve(xc + (rx * myArc), yc - ry, xc + rx, yc - (ry * myArc), xc + rx, yc) else outLine(x + w, y) end xc = x + w - rx yc = y + h - ry outLine(x + w, yc) if round_corner[1,1] == '1' outCurve(xc + rx, yc + (ry * myArc), xc + (rx * myArc), yc + ry, xc, yc + ry) else outLine(x + w, y + h) end xc = x + rx yc = y + h - ry outLine(xc, y + h) if round_corner[2,1] == '1' outCurve(xc - (rx * myArc), yc + ry, xc - rx, yc + (ry * myArc), xc - rx, yc) else outLine(x, y + h) end xc = x + rx yc = y + ry outLine(x, yc) if round_corner[3,1] == '1' outCurve(xc - rx, yc - (ry * myArc), xc - (rx * myArc), yc - ry, xc, yc - ry) else outLine(x, y) outLine(x + rx, y) end out(op) end alias_method :rounded_rect_xy, :RoundedRectXY # # Draws a grahic arrow. # [@parameter float :x0] Abscissa of first point. # [@parameter float :y0] Ordinate of first point. # [@parameter float :x0] Abscissa of second point. # [@parameter float :y1] Ordinate of second point. # [@parameter int :head_style] (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead) # [@parameter float :arm_size] length of arrowhead arms # [@parameter int :arm_angle] angle between an arm and the shaft # [@author] Piotr Galecki, Nicola Asuni, Andy Meier # [@access public] # [@since 4.6.018 (2009-07-10)] # def Arrow(x0, y0, x1, y1, head_style=0, arm_size=5, arm_angle=15) # getting arrow direction angle # 0 deg angle is when both arms go along X axis. angle grows clockwise. dir_angle = ::Math.atan2(y0 - y1, x0 - x1) if dir_angle < 0 dir_angle += 2 * ::Math::PI end arm_angle = arm_angle * ::Math::PI / 180 # deg2rad sx1 = x1 sy1 = y1 if head_style > 0 # calculate the stopping point for the arrow shaft sx1 = x1 + (arm_size - @line_width) * ::Math.cos(dir_angle) sy1 = y1 + (arm_size - @line_width) * ::Math.sin(dir_angle) end # main arrow line / shaft Line(x0, y0, sx1, sy1) # left arrowhead arm tip x2L = x1 + (arm_size * ::Math.cos(dir_angle + arm_angle)) y2L = y1 + (arm_size * ::Math.sin(dir_angle + arm_angle)) # right arrowhead arm tip x2R = x1 + (arm_size * ::Math.cos(dir_angle - arm_angle)) y2R = y1 + (arm_size * ::Math.sin(dir_angle - arm_angle)) mode = 'D' style = [] case head_style when 0 # draw only arrowhead arms mode = 'D' style = [1, 1, 0] when 1 # draw closed arrowhead, but no fill mode = 'D' when 2 # closed and filled arrowhead mode = 'DF' when 3 # filled arrowhead mode = 'F' end Polygon([x2L, y2L, x1, y1, x2R, y2R], mode, style, nil) end alias_method :arrow, :Arrow # END GRAPHIC FUNCTIONS SECTION ----------------------- # BIDIRECTIONAL TEXT SECTION -------------------------- # # Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/). # [@param string :str] string to manipulate. (UTF-8) # [@param bool :setbom] if true set the Byte Order Mark (BOM = 0xFEFF) # [@param bool :forcertl] if true forces RTL text direction # [@return] string (UTF-16BE) # [@access protected] # [@author] Nicola Asuni # [@since 2.1.000 (2008-01-08)] # def utf8StrRev(str, setbom=false, forcertl=false) return utf8StrArrRev(UTF8StringToArray(str), str, setbom, forcertl) end protected :utf8StrRev # # Reverse the RLT substrings array using the Bidirectional Algorithm (http://unicode.org/reports/tr9/). # [@param array :arr] array of unicode values. # [@param string :str] string to manipulate (or empty value). # [@param bool :setbom] if true set the Byte Order Mark (BOM = 0xFEFF) # [@param bool :forcertl] if true forces RTL text direction # [@return string] # [@access protected] # [@author] Nicola Asuni # [@since 4.9.000 (2010-03-27)] # def utf8StrArrRev(arr, str='', setbom=false, forcertl=false) return arrUTF8ToUTF16BE(utf8Bidi(arr, str, forcertl), setbom) end protected :utf8StrArrRev # # Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/). # [@param array :ta] array of characters composing the string. (UCS4) # [@param string :str] string to process # [@param bool :forcertl] if 'R' forces RTL, if 'L' forces LTR # [@return] array of unicode chars (UCS4) # [@author] Nicola Asuni # [@access protected] # [@since 2.4.000 (2008-03-06)] # def utf8Bidi(ta, str='', forcertl=false) str = str.dup # paragraph embedding level pel = 0 # max level maxlevel = 0 if empty_string(str) # create string from array str = UTF8ArrSubString(ta) end # check if string contains arabic text str.force_encoding('ASCII-8BIT') if str.respond_to?(:force_encoding) if str =~ @@k_re_pattern_arabic arabic = true else arabic = false end # check if string contains RTL text unless forcertl or arabic or (str =~ @@k_re_pattern_rtl) return ta end # get number of chars numchars = ta.length if forcertl == 'R' pel = 1 elsif forcertl == 'L' pel = 0 else # P2. In each paragraph, find the first character of type L, AL, or R. # P3. If a character is found in P2 and it is of type AL or R, then set the paragraph embedding level to one; otherwise, set it to zero. numchars.times do |i| type = @@unicode[ta[i]] if type == 'L' pel = 0 break elsif (type == 'AL') or (type == 'R') pel = 1 break end end end # Current Embedding Level cel = pel # directional override status dos = 'N' remember = [] # start-of-level-run sor = (pel % 2 == 1) ? 'R' : 'L' eor = sor # Array of characters data chardata = [] # X1. Begin by setting the current embedding level to the paragraph embedding level. Set the directional override status to neutral. Process each character iteratively, applying rules X2 through X9. Only embedding levels from 0 to 61 are valid in this phase. # In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached. numchars.times do |i| case ta[i] when @@k_rle, @@k_lre, @@k_rlo, @@k_lro, @@k_pdf else # X6. For all types besides RLE, LRE, RLO, LRO, and PDF: # a. Set the level of the current character to the current embedding level. # b. Whenever the directional override status is not neutral, reset the current character type to the directional override status. if dos != 'N' chardir = dos else chardir = @@unicode[ta[i]] chardir = 'L' if chardir.nil? end # stores string characters and other information chardata << {:char => ta[i], :level => cel, :type => chardir, :sor => sor, :eor => eor} next end case ta[i] when @@k_rle # X2. With each RLE, compute the least greater odd embedding level. # a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral. # b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status. next_level = cel + (cel % 2) + 1 if next_level < 62 remember << {:num => @@k_rle, :cel => cel, :dos => dos} cel = next_level dos = 'N' sor = eor eor = (cel % 2 == 1) ? 'R' : 'L' end when @@k_lre # X3. With each LRE, compute the least greater even embedding level. # a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral. # b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status. next_level = cel + 2 - (cel % 2) if next_level < 62 remember << {:num => @@k_lre, :cel => cel, :dos => dos} cel = next_level dos = 'N' sor = eor eor = (cel % 2 == 1) ? 'R' : 'L' end when @@k_rlo # X4. With each RLO, compute the least greater odd embedding level. # a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to right-to-left. # b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status. next_level = cel + (cel % 2) + 1 if next_level < 62 remember << {:num => @@k_rlo, :cel => cel, :dos => dos} cel = next_level dos = 'R' sor = eor eor = (cel % 2 == 1) ? 'R' : 'L' end when @@k_lro # X5. With each LRO, compute the least greater even embedding level. # a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to left-to-right. # b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status. next_level = cel + 2 - (cel % 2) if next_level < 62 remember << {:num => @@k_lro, :cel => cel, :dos => dos} cel = next_level dos = 'L' sor = eor eor = (cel % 2 == 1) ? 'R' : 'L' end when @@k_pdf # X7. With each PDF, determine the matching embedding or override code. If there was a valid matching code, restore (pop) the last remembered (pushed) embedding level and directional override. if remember.length last = remember.length - 1 case remember[last][:num] when @@k_rle, @@k_lre, @@k_rlo, @@k_lro match = remember.pop cel = match[:cel] dos = match[:dos] sor = eor eor = ((cel > match[:cel] ? cel : match[:cel]) % 2 == 1) ? 'R' : 'L' end end end end # end for each char # X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph. Paragraph separators are not included in the embedding. # X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes. # X10. The remaining rules are applied to each run of characters at the same level. For each run, determine the start-of-level-run (sor) and end-of-level-run (eor) type, either L or R. This depends on the higher of the two levels on either side of the boundary (at the start or end of the paragraph, the level of the 'other' run is the base embedding level). If the higher level is odd, the type is R; otherwise, it is L. # 3.3.3 Resolving Weak Types # Weak types are now resolved one level run at a time. At level run boundaries where the type of the character on the other side of the boundary is required, the type assigned to sor or eor is used. # Nonspacing marks are now resolved based on the previous characters. numchars = chardata.length # W1. Examine each nonspacing mark (NSM) in the level run, and change the type of the NSM to the type of the previous character. If the NSM is at the start of the level run, it will get the type of sor. prevlevel = -1 # track level changes levcount = 0 # counts consecutive chars at the same level numchars.times do |i| if chardata[i][:type] == 'NSM' if levcount chardata[i][:type] = chardata[i][:sor] elsif i > 0 chardata[i][:type] = chardata[i-1][:type] end end if chardata[i][:level] != prevlevel levcount = 0 else levcount += 1 end prevlevel = chardata[i][:level] end # W2. Search backward from each instance of a European number until the first strong type (R, L, AL, or sor) is found. If an AL is found, change the type of the European number to Arabic number. prevlevel = -1 levcount = 0 numchars.times do |i| if chardata[i][:type] == 'EN' levcount.downto(0) do |j| if chardata[j][:type] == 'AL' chardata[i][:type] = 'AN' elsif (chardata[j][:type] == 'L') or (chardata[j][:type] == 'R') break end end end if chardata[i][:level] != prevlevel levcount = 0 else levcount +=1 end prevlevel = chardata[i][:level] end # W3. Change all ALs to R. numchars.times do |i| if chardata[i][:type] == 'AL' chardata[i][:type] = 'R' end end # W4. A single European separator between two European numbers changes to a European number. A single common separator between two numbers of the same type changes to that type. prevlevel = -1 levcount = 0 numchars.times do |i| if (levcount > 0) and (i+1 < numchars) and (chardata[i+1][:level] == prevlevel) if (chardata[i][:type] == 'ES') and (chardata[i-1][:type] == 'EN') and (chardata[i+1][:type] == 'EN') chardata[i][:type] = 'EN' elsif (chardata[i][:type] == 'CS') and (chardata[i-1][:type] == 'EN') and (chardata[i+1][:type] == 'EN') chardata[i][:type] = 'EN' elsif (chardata[i][:type] == 'CS') and (chardata[i-1][:type] == 'AN') and (chardata[i+1][:type] == 'AN') chardata[i][:type] = 'AN' end end if chardata[i][:level] != prevlevel levcount = 0 else levcount += 1 end prevlevel = chardata[i][:level] end # W5. A sequence of European terminators adjacent to European numbers changes to all European numbers. prevlevel = -1 levcount = 0 numchars.times do |i| if chardata[i][:type] == 'ET' if (levcount > 0) and (chardata[i-1][:type] == 'EN') chardata[i][:type] = 'EN' else j = i+1 while (j < numchars) and (chardata[j][:level] == prevlevel) if chardata[j][:type] == 'EN' chardata[i][:type] = 'EN' break elsif chardata[j][:type] != 'ET' break end j += 1 end end end if chardata[i][:level] != prevlevel levcount = 0 else levcount += 1 end prevlevel = chardata[i][:level] end # W6. Otherwise, separators and terminators change to Other Neutral. prevlevel = -1 levcount = 0 reg_ET_ES_CS = /^(ET|ES|CS)$/ numchars.times do |i| if chardata[i][:type] =~ reg_ET_ES_CS chardata[i][:type] = 'ON' end if chardata[i][:level] != prevlevel levcount = 0 else levcount += 1 end prevlevel = chardata[i][:level] end # W7. Search backward from each instance of a European number until the first strong type (R, L, or sor) is found. If an L is found, then change the type of the European number to L. prevlevel = -1 levcount = 0 numchars.times do |i| if chardata[i][:type] == 'EN' levcount.downto(0) do |j| if chardata[j][:type] == 'L' chardata[i][:type] = 'L' elsif chardata[j][:type] == 'R' break end end end if chardata[i][:level] != prevlevel levcount = 0 else levcount += 1 end prevlevel = chardata[i][:level] end # N1. A sequence of neutrals takes the direction of the surrounding strong text if the text on both sides has the same direction. European and Arabic numbers act as if they were R in terms of their influence on neutrals. Start-of-level-run (sor) and end-of-level-run (eor) are used at level run boundaries. prevlevel = -1 levcount = 0 reg_NI = /^(B|S|WS|ON)$/ reg_R_EN_AN = /^(R|EN|AN)$/ reg_EN_AN = /^(EN|AN)$/ ni = nil numchars.times do |i| if (chardata[i][:type] =~ reg_NI) if (levcount > 0) and (i+1 < numchars) and (chardata[i+1][:level] == prevlevel) if !ni.nil? and ni > i next_non_space_char = chardata[ni][:type] else ni = chardata[i+1..-1].index {|item| item[:type] !~ reg_NI} unless ni.nil? ni += i+1 next_non_space_char = chardata[ni][:type] end end if (chardata[i-1][:type] == 'L') and (next_non_space_char == 'L') chardata[i][:type] = 'L' elsif ((chardata[i-1][:type] == 'R') and (next_non_space_char =~ reg_R_EN_AN) or (chardata[i-1][:type] =~ reg_EN_AN) and (next_non_space_char == 'R')) chardata[i][:type] = 'R' else # N2. Any remaining neutrals take the embedding direction chardata[i][:type] = chardata[i][:sor] end elsif (levcount == 0) and (i+1 < numchars) and (chardata[i+1][:level] == prevlevel) ni = chardata[i+1..-1].index {|item| item[:type] !~ reg_NI} unless ni.nil? ni += i+1 next_non_space_char = chardata[ni][:type] end # first char if (chardata[i][:sor] == 'L') and (next_non_space_char == 'L') chardata[i][:type] = 'L' elsif ((chardata[i][:sor] == 'R') and (next_non_space_char =~ reg_R_EN_AN) or (chardata[i][:sor] =~ reg_EN_AN) and (next_non_space_char == 'R')) chardata[i][:type] = 'R' else # N2. Any remaining neutrals take the embedding direction chardata[i][:type] = chardata[i][:sor] end elsif (levcount > 0) and ((i+1 == numchars) or ((i+1 < numchars) and (chardata[i+1][:level] != prevlevel))) # last char if (chardata[i-1][:type] == 'L') and (chardata[i][:eor] == 'L') chardata[i][:type] = 'L' elsif ((chardata[i-1][:type] == 'R') and (chardata[i][:eor] =~ reg_R_EN_AN) or (chardata[i-1][:type] =~ reg_EN_AN) and (chardata[i][:eor] == 'R')) chardata[i][:type] = 'R' else # N2. Any remaining neutrals take the embedding direction chardata[i][:type] = chardata[i][:sor] end else # N2. Any remaining neutrals take the embedding direction chardata[i][:type] = chardata[i][:sor] end end if chardata[i][:level] != prevlevel levcount = 0 else levcount += 1 end prevlevel = chardata[i][:level] end # I1. For all characters with an even (left-to-right) embedding direction, those of type R go up one level and those of type AN or EN go up two levels. # I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level. prevlevel = -1 reg_L_AN_EN = /^(L|AN|EN)$/ reg_AN_EN = /^(AN|EN)$/ numchars.times do |i| odd = chardata[i][:level] % 2 if odd == 1 # I2. if chardata[i][:type] =~ reg_L_AN_EN chardata[i][:level] += 1 end else # I1. if chardata[i][:type] == 'R' chardata[i][:level] += 1 elsif chardata[i][:type] == 'BN' and prevlevel != -1 chardata[i][:level] = prevlevel elsif chardata[i][:type] =~ reg_AN_EN chardata[i][:level] += 2 end end prevlevel = chardata[i][:level] maxlevel = [chardata[i][:level],maxlevel].max end # L1. On each line, reset the embedding level of the following characters to the paragraph embedding level: # 1. Segment separators, # 2. Paragraph separators, # 3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and # 4. Any sequence of white space characters at the end of the line. reg_B_S = /^(B|S)$/ numchars.times do |i| if chardata[i][:type] =~ reg_B_S chardata[i][:level] = pel elsif chardata[i][:type] == 'WS' j = i+1 while j < numchars if (chardata[j][:type] =~ reg_B_S) or ((j == numchars-1) and (chardata[j][:type] == 'WS')) chardata[i][:level] = pel break elsif chardata[j][:type] != 'WS' break end j += 1 end end end # Arabic Shaping # Cursively connected scripts, such as Arabic or Syriac, require the selection of positional character shapes that depend on adjacent characters. Shaping is logically applied after the Bidirectional Algorithm is used and is limited to characters within the same directional run. if arabic endedletter = [1569,1570,1571,1572,1573,1575,1577,1583,1584,1585,1586,1608,1688] alfletter = [1570,1571,1573,1575] chardata2 = chardata.dup laaletter = false charAL = [] x = 0 numchars.times do |i| c = chardata[i][:char] if (@@unicode[c] == 'AL') or (c == 32) or (c == 8204) # Unicode Character 'ZERO WIDTH NON-JOINER' (U+200C) charAL[x] = chardata[i].dup charAL[x][:i] = i chardata[i][:x] = x x += 1 end end numAL = x reg_AL_NSM = /^(AL|NSM)$/ numchars.times do |i| thischar = chardata[i].dup if i > 0 prevchar = chardata[i-1] else prevchar = false end if i+1 < numchars nextchar = chardata[i+1] else nextchar = false end if @@unicode[thischar[:char]] == 'AL' x = thischar[:x] if x > 0 prevchar = charAL[x-1] else prevchar = false end if x+1 < numAL nextchar = charAL[x+1] else nextchar = false end # if laa letter if (prevchar != false) and (prevchar[:char] == 1604) and alfletter.include?(thischar[:char]) arabicarr = @@laa_array laaletter = true if x > 1 prevchar = charAL[x-2] else prevchar = false end else arabicarr = @@unicode_arlet laaletter = false end if (prevchar != false) and (nextchar != false) and (@@unicode[prevchar[:char]] =~ reg_AL_NSM) and (@@unicode[nextchar[:char]] =~ reg_AL_NSM) and (nextchar[:type] == thischar[:type]) and (nextchar[:char] != 1567) # medial if endedletter.include?(prevchar[:char]) if !arabicarr[thischar[:char]].nil? and !arabicarr[thischar[:char]][2].nil? # initial chardata2[i][:char] = arabicarr[thischar[:char]][2] end else if !arabicarr[thischar[:char]].nil? and !arabicarr[thischar[:char]][3].nil? # medial chardata2[i][:char] = arabicarr[thischar[:char]][3] end end elsif (nextchar != false) and (@@unicode[nextchar[:char]] =~ reg_AL_NSM) and (nextchar[:type] == thischar[:type]) and (nextchar[:char] != 1567) if !arabicarr[thischar[:char]].nil? and !arabicarr[thischar[:char]][2].nil? # initial chardata2[i][:char] = arabicarr[thischar[:char]][2] end elsif ((prevchar != false) and (@@unicode[prevchar[:char]] =~ reg_AL_NSM) and (prevchar[:type] == thischar[:type])) or ((nextchar != false) and (nextchar[:char] == 1567)) # final if (i > 1) and (thischar[:char] == 1607) and (chardata[i-1][:char] == 1604) and (chardata[i-2][:char] == 1604) # Allah Word # mark characters to delete with false chardata2[i-2][:char] = false chardata2[i-1][:char] = false chardata2[i][:char] = 65010 else if (prevchar != false) and endedletter.include?(prevchar[:char]) if !arabicarr[thischar[:char]].nil? and !arabicarr[thischar[:char]][0].nil? # isolated chardata2[i][:char] = arabicarr[thischar[:char]][0] end else if !arabicarr[thischar[:char]].nil? and !arabicarr[thischar[:char]][1].nil? # final chardata2[i][:char] = arabicarr[thischar[:char]][1] end end end elsif !arabicarr[thischar[:char]].nil? and !arabicarr[thischar[:char]][0].nil? # isolated chardata2[i][:char] = arabicarr[thischar[:char]][0] end # if laa letter if laaletter # mark characters to delete with false chardata2[charAL[x-1][:i]][:char] = false end end # end if AL (Arabic Letter) end # end for each char # # Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced. # Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner. # cw = @current_font['cw'] (numchars-1).times do |i| if (chardata2[i][:char] == 1617) and !@@diacritics[chardata2[i+1][:char]].nil? # check if the subtitution font is defined on current font unless cw[@@diacritics[chardata2[i+1][:char]]].nil? chardata2[i][:char] = false chardata2[i+1][:char] = @@diacritics[chardata2[i+1][:char]] end end end # remove marked characters chardata2.each_with_index do |value, key| if value[:char] == false chardata2.delete_at(key) end end chardata = chardata2 numchars = chardata.size chardata2 = nil arabicarr = nil laaletter = nil charAL = nil end # L2. From the highest level found in the text to the lowest odd level on each line, including intermediate levels not actually present in the text, reverse any contiguous sequence of characters that are at that level or higher. maxlevel.downto(1) do |j| ordarray = [] revarr = [] onlevel = false numchars.times do |i| if chardata[i][:level] >= j onlevel = true um = @@unicode_mirror[chardata[i][:char]] if um # L4. A character is depicted by a mirrored glyph if and only if (a) the resolved directionality of that character is R, and (b) the Bidi_Mirrored property value of that character is true. chardata[i][:char] = um end revarr << chardata[i] else if onlevel revarr.reverse! ordarray.concat(revarr) revarr = [] onlevel = false end ordarray << chardata[i] end end if onlevel revarr.reverse! ordarray.concat(revarr) end chardata = ordarray end ordarray = [] numchars.times do |i| ordarray.push chardata[i][:char] # store char values for subsetting @current_font['subsetchars'][chardata[i][:char]] = true end # update font subsetchars setFontSubBuffer(@current_font['fontkey'], 'subsetchars', @current_font['subsetchars']) return ordarray end protected :utf8Bidi # END OF BIDIRECTIONAL TEXT SECTION ------------------- # # Adds a bookmark. # [@param string :txt] bookmark description. # [@param int :level] bookmark level. # [@param float :y] Ordinate of the boorkmark position (default = -1 = current position). # [@param int :page] target page number (leave empty for current page). # [@access public] # [@author] Olivier Plathey, Nicola Asuni # [@since 2.1.002 (2008-02-12)] # def Bookmark(txt, level=0, y=-1, page=nil) if level < 0 level = 0 end if @outlines[0] lastoutline = @outlines[-1] maxlevel = lastoutline[:l] + 1 else maxlevel = 0 end if level > maxlevel level = maxlevel end if y == -1 y = GetY() end if page.nil? page = PageNo() end @outlines.push :t => txt, :l => level, :y => y, :p => page end alias_method :bookmark, :Bookmark # # Create a bookmark PDF string. # [@access private] # [@author] Olivier Plathey, Nicola Asuni # [@since 2.1.002 (2008-02-12)] # def putbookmarks() nb = @outlines.size if nb == 0 return end # sort outlines by page and original position @outlines = @outlines.sort_by {|x| x[:p]} lru = [] level = 0 @outlines.each_with_index do |o, i| if o[:l] > 0 parent = lru[o[:l] - 1] # Set parent and last pointers @outlines[i][:parent] = parent @outlines[parent][:last] = i if o[:l] > level # Level increasing: set first pointer @outlines[parent][:first] = i end else @outlines[i][:parent] = nb end if o[:l] <= level and i > 0 # Set prev and next pointers prev = lru[o[:l]] @outlines[prev][:next] = i @outlines[i][:prev] = prev end lru[o[:l]] = i level = o[:l] end # Outline items n = @n + 1 @outlines.each_with_index do |o, i| newobj() # covert HTML title to string nltags = /<br[\s]?\/>|<\/(blockquote|dd|dl|div|dt|h1|h2|h3|h4|h5|h6|hr|li|ol|p|pre|ul|table|tr|td)>/mi title = o[:t].gsub(nltags, "\n") title = title.gsub(/[\r]+/mi, '') title = title.gsub(/[\n]+/mi, "\n") title = title.strip.gsub(/<[^>]+>/, '') out = '<</Title ' + textstring(title) out << ' /Parent ' + (n + o[:parent]).to_s + ' 0 R' out << ' /Prev ' + (n + o[:prev]).to_s + ' 0 R' if !o[:prev].nil? out << ' /Next ' + (n + o[:next]).to_s + ' 0 R' if !o[:next].nil? out << ' /First ' + (n + o[:first]).to_s + ' 0 R' if !o[:first].nil? out << ' /Last ' + (n + o[:last]).to_s + ' 0 R' if !o[:last].nil? out << ' ' + sprintf('/Dest [%d 0 R /XYZ 0 %.2f null]', 1 + 2 * o[:p], @pagedim[o[:p]]['h'] - o[:y] * @k) out << ' /Count 0>> endobj' out(out) end # Outline root newobj() @outline_root = @n out('<</Type /Outlines /First ' + n.to_s + ' 0 R /Last ' + (n + lru[0]).to_s + ' 0 R>> endobj') end protected :putbookmarks # --- JAVASCRIPT ------------------------------------------------------ # --- FORM FIELDS ----------------------------------------------------- # --- END FORMS FIELDS ------------------------------------------------ # # Create a new page group. # * NOTE: call this function before calling AddPage() # [@param int :page] starting group page (leave empty for next page). # [@access public] # [@since 3.0.000 (2008-03-27)] # def startPageGroup(page=0) if !page.is_a? Integer or page.zero? page = @page + 1 end @newpagegroup[page] = true end alias_method :start_page_group, :startPageGroup # # Defines an alias for the total number of pages. # It will be substituted as the document is closed. # [@param string :alias] The alias. # [@access public] # [@since 1.4] # [@see] getAliasNbPages(), PageNo(), Footer() # def AliasNbPages(alias_nb ='{nb}') @alias_nb_pages = alias_nb end alias_method :alias_nb_pages, :AliasNbPages # # Returns the string alias used for the total number of pages. # If the current font is unicode type, the returned string is surrounded by additional curly braces. # [@return string] # [@access public] # [@since 4.0.018 (2008-08-08)] # [@see] AliasNbPages(), PageNo(), Footer() # def getAliasNbPages() if (@current_font['type'] == 'TrueTypeUnicode') or (@current_font['type'] == 'cidfont0') return '{' + @alias_nb_pages + '}' end return @alias_nb_pages end alias_method :get_alias_nb_pages, :getAliasNbPages # # Defines an alias for the page number. # It will be substituted as the document is closed. # [@param string :alias] The alias. # [@access public] # [@since 4.5.000 (2009-01-02)] # [@see] getAliasNbPages(), PageNo(), Footer() # def AliasNumPage(alias_num='{pnb}') # Define an alias for total number of pages @alias_num_page = alias_num end alias_method :alias_num_page, :AliasNumPage # # Returns the string alias used for the page number. # If the current font is unicode type, the returned string is surrounded by additional curly braces. # [@return string] # [@access public] # [@since 4.5.000 (2009-01-02)] # [@see] AliasNbPages(), PageNo(), Footer() # def getAliasNumPage() if (@current_font['type'] == 'TrueTypeUnicode') or (@current_font['type'] == 'cidfont0') return '{' + @alias_num_page + '}' end return @alias_num_page end alias_method :get_alias_num_page, :getAliasNumPage # # Return the current page in the group. # [@return] current page in the group # [@access public] # [@since 3.0.000 (2008-03-27)] # def getGroupPageNo() return @pagegroups[@currpagegroup] end alias_method :get_group_page_no, :getGroupPageNo # # Returns the current group page number formatted as a string. # [@access public] # [@since 4.3.003 (2008-11-18)] # [@see] PaneNo(), formatPageNumber() # def getGroupPageNoFormatted() return formatPageNumber(getGroupPageNo()) end alias_method :get_group_page_no_formatted, :getGroupPageNoFormatted # # Return the alias of the current page group # If the current font is unicode type, the returned string is surrounded by additional curly braces. # (will be replaced by the total number of pages in this group). # [@return] alias of the current page group # [@access public] # [@since 3.0.000 (2008-03-27)] # def getPageGroupAlias() if (@current_font['type'] == 'TrueTypeUnicode') or (@current_font['type'] == 'cidfont1') return '{' + @currpagegroup + '}' end return @currpagegroup end alias_method :get_page_group_alias, :getPageGroupAlias # # Return the alias for the page number on the current page group # If the current font is unicode type, the returned string is surrounded by additional curly braces. # (will be replaced by the total number of pages in this group). # [@return] alias of the current page group # [@access public] # [@since 4.5.000 (2009-01-02)] # def getPageNumGroupAlias() if (@current_font['type'] == 'TrueTypeUnicode') or (@current_font['type'] == 'cidfont0') return '{' + @currpagegroup.gsub('{nb', '{pnb') +'}' end return @currpagegroup.gsub('{nb', '{pnb') end alias_method :get_page_num_group_alias, :getPageNumGroupAlias # # Format the page numbers. # This method can be overriden for custom formats. # [@param int :num] page number # [@access protected] # [@since 4.2.005 (2008-11-06)] # def formatPageNumber(num) return number_with_delimiter(num, :delimiter => ",") end protected :formatPageNumber # # Format the page numbers on the Table Of Content. # This method can be overriden for custom formats. # [@param int :num] page number # [@access protected] # [@since 4.5.001 (2009-01-04)] # [@see] addTOC(), addHTMLTOC() # def formatTOCPageNumber(num) return number_with_delimiter(num, :delimiter => ",") end protected :formatTOCPageNumber # # Returns the current page number formatted as a string. # [@access public] # [@since 4.2.005 (2008-11-06)] # [@see] PaneNo(), formatPageNumber() # def PageNoFormatted() return formatPageNumber(PageNo()) end alias_method :page_no_formatted, :PageNoFormatted # # Put visibility settings. # [@access protected] # [@since 3.0.000 (2008-03-27)] # def putocg() newobj() @n_ocg_print = @n out('<< /Type /OCG /Name ' + textstring('print') + ' /Usage << /Print <</PrintState /ON>> /View <</ViewState /OFF>> >> >> endobj') newobj() @n_ocg_view = @n out('<< /Type /OCG /Name ' + textstring('view') + ' /Usage << /Print <</PrintState /OFF>> /View <</ViewState /ON>> >> >> endobj') end protected :putocg # # Set the visibility of the successive elements. # This can be useful, for instance, to put a background # image or color that will show on screen but won't print. # [@param string :v] visibility mode. Legal values are: all, print, screen. # [@access public] # [@since 3.0.000 (2008-03-27)] # def setVisibility(v) if @open_marked_content # close existing open marked-content out('EMC') @open_marked_content = false end case v when 'print' out('/OC /OC1 BDC') @open_marked_content = true when 'screen' out('/OC /OC2 BDC') @open_marked_content = true when 'all' out('') else Error('Incorrect visibility: ' + v) end @visibility = v end alias_method :set_visibility, :setVisibility # # Add transparency parameters to the current extgstate # [@param array :params] parameters # [@return] the number of extgstates # [@access protected] # [@since 3.0.000 (2008-03-27)] # def addExtGState(parms) n = @extgstates.length + 1 # check if this ExtGState already exist 1.upto(n - 1) do |i| if @extgstates[i] and (@extgstates[i]['parms'] == parms) # return reference to existing ExtGState return i end end @extgstates[n] ||= {} @extgstates[n]['parms'] = parms return n end protected :addExtGState # # Add an extgstate # [@param array :gs] extgstate # [@access protected] # [@since 3.0.000 (2008-03-27)] # def setExtGState(gs) out(sprintf('/GS%d gs', gs)) end protected :setExtGState # # Put extgstates for object transparency # [@param array :gs] extgstate # [@access protected] # [@since 3.0.000 (2008-03-27)] # def putextgstates() ne = @extgstates.length 1.upto(ne) do |i| newobj() @extgstates[i] ||= {} @extgstates[i]['n'] = @n out = '<< /Type /ExtGState' if @extgstates[i]['parms'] @extgstates[i]['parms'].each {|k, v| if v.is_a? Float v = sprintf('%.2f', v) end out << ' /' + k + ' ' + v.to_s } end out << ' >> endobj' out(out) end end protected :putextgstates # # Set alpha for stroking (CA) and non-stroking (ca) operations. # [@param float :alpha] real value from 0 (transparent) to 1 (opaque) # [@param string :bm] blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity # [@access public] # [@since 3.0.000 (2008-03-27)] # def setAlpha(alpha, bm='Normal') gs = addExtGState({'ca' => alpha, 'CA' => alpha, 'BM' => '/' + bm, 'AIS' => 'false'}) setExtGState(gs) end alias_method :set_alpha, :setAlpha # # Set the default JPEG compression quality (1-100) # [@param int :quality] JPEG quality, integer between 1 and 100 # [@access public] # [@since 3.0.000 (2008-03-27)] # def setJPEGQuality(quality) if (quality < 1) or (quality > 100) quality = 75 end @jpeg_quality = quality end alias_method :set_jpeg_quality, :setJPEGQuality # # Set the height of cell repect font height. # [@param int :h] cell proportion respect font height (typical value = 1.25). # [@access public] # [@since 3.0.014 (2008-06-04)] # def setCellHeightRatio(h) @cell_height_ratio = h end alias_method :set_cell_height_ratio, :setCellHeightRatio # # return the height of cell repect font height. # [@access public] # [@since 4.0.012 (2008-07-24)] # def getCellHeightRatio() return @cell_height_ratio end alias_method :get_cell_height_ratio, :getCellHeightRatio # # Set the PDF version (check PDF reference for valid values). # Default value is 1.t # [@access public] # [@since 3.1.000 (2008-06-09)] # def setPDFVersion(version='1.7') @pdf_version = version end alias_method :set_pdf_version, :setPDFVersion # # Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print. # (see Section 8.1 of PDF reference, "Viewer Preferences"). # * HideToolbar boolean (Optional) A flag specifying whether to hide the viewer application's tool bars when the document is active. Default value: false. # * HideMenubar boolean (Optional) A flag specifying whether to hide the viewer application's menu bar when the document is active. Default value: false. # * HideWindowUI boolean (Optional) A flag specifying whether to hide user interface elements in the document's window (such as scroll bars and navigation controls), leaving only the document's contents displayed. Default value: false. # * FitWindow boolean (Optional) A flag specifying whether to resize the document's window to fit the size of the first displayed page. Default value: false. # * CenterWindow boolean (Optional) A flag specifying whether to position the document's window in the center of the screen. Default value: false. # * DisplayDocTitle boolean (Optional; PDF 1.4) A flag specifying whether the window's title bar should display the document title taken from the Title entry of the document information dictionary (see Section 10.2.1, "Document Information Dictionary"). If false, the title bar should instead display the name of the PDF file containing the document. Default value: false. # * NonFullScreenPageMode name (Optional) The document's page mode, specifying how to display the document on exiting full-screen mode: # * UseNone Neither document outline nor thumbnail images visible # * UseOutlines Document outline visible # * UseThumbs Thumbnail images visible # * UseOC Optional content group panel visible # This entry is meaningful only if the value of the PageMode entry in the catalog dictionary (see Section 3.6.1, "Document Catalog") is FullScreen; it is ignored otherwise. Default value: UseNone. # * ViewArea name (Optional; PDF 1.4) The name of the page boundary representing the area of a page to be displayed when viewing the document on the screen. Valid values are (see Section 10.10.1, "Page Boundaries").: # * MediaBox # * CropBox (default) # * BleedBox # * TrimBox # * ArtBox # * ViewClip name (Optional; PDF 1.4) The name of the page boundary to which the contents of a page are to be clipped when viewing the document on the screen. Valid values are (see Section 10.10.1, "Page Boundaries").: # * MediaBox # * CropBox (default) # * BleedBox # * TrimBox # * ArtBox # * PrintArea name (Optional; PDF 1.4) The name of the page boundary representing the area of a page to be rendered when printing the document. Valid values are (see Section 10.10.1, "Page Boundaries").: # * MediaBox # * CropBox (default) # * BleedBox # * TrimBox # * ArtBox # * PrintClip name (Optional; PDF 1.4) The name of the page boundary to which the contents of a page are to be clipped when printing the document. Valid values are (see Section 10.10.1, "Page Boundaries").: # * MediaBox # * CropBox (default) # * BleedBox # * TrimBox # * ArtBox # * PrintScaling name (Optional; PDF 1.6) The page scaling option to be selected when a print dialog is displayed for this document. Valid values are: # * None, which indicates that the print dialog should reflect no page scaling # * AppDefault (default), which indicates that applications should use the current print scaling # * Duplex name (Optional; PDF 1.7) The paper handling option to use when printing the file from the print dialog. The following values are valid: # * Simplex - Print single-sided # * DuplexFlipShortEdge - Duplex and flip on the short edge of the sheet # * DuplexFlipLongEdge - Duplex and flip on the long edge of the sheet # Default value: none # * PickTrayByPDFSize boolean (Optional; PDF 1.7) A flag specifying whether the PDF page size is used to select the input paper tray. This setting influences only the preset values used to populate the print dialog presented by a PDF viewer application. If PickTrayByPDFSize is true, the check box in the print dialog associated with input paper tray is checked. Note: This setting has no effect on Mac OS systems, which do not provide the ability to pick the input tray by size. # * PrintPageRange array (Optional; PDF 1.7) The page numbers used to initialize the print dialog box when the file is printed. The first page of the PDF file is denoted by 1. Each pair consists of the first and last pages in the sub-range. An odd number of integers causes this entry to be ignored. Negative numbers cause the entire array to be ignored. Default value: as defined by PDF viewer application # * NumCopies integer (Optional; PDF 1.7) The number of copies to be printed when the print dialog is opened for this file. Supported values are the integers 2 through 5. Values outside this range are ignored. Default value: as defined by PDF viewer application, but typically 1 # [@param array :preferences] array of options. # [@author] Nicola Asuni # [@access public] # [@since 3.1.000 (2008-06-09)] # def setViewerPreferences(preferences) @viewer_preferences = preferences end alias_method :set_viewer_preferences, :setViewerPreferences # # Draw the sector of a circle. # It can be used for instance to render pie charts. # [@param float :xc] abscissa of the center. # [@param float :yc] ordinate of the center. # [@param float :r] radius. # [@param float :a] start angle (in degrees). # [@param float :b] end angle (in degrees). # [@param string :style] Style of rendering. See the getPathPaintOperator() function for more information. # [@param float :cw] indicates whether to go clockwise (default: true). # [@param float :o] origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). Default: 90. # [@author] Maxime Delorme, Nicola Asuni # [@since 3.1.000 (2008-06-09)] # [@access public] # def PieSector(xc, yc, r, a, b, style='FD', cw=true, o=90) PieSectorXY(xc, yc, r, r, a, b, style, cw, o) end alias_method :pie_sector, :PieSector # # Draw the sector of an ellipse. # It can be used for instance to render pie charts. # [@param float :xc] abscissa of the center. # [@param float :yc] ordinate of the center. # [@param float :rx] the x-axis radius. # [@param float :ry] the y-axis radius. # [@param float :a] start angle (in degrees). # [@param float :b] end angle (in degrees). # [@param string :style] Style of rendering. See the getPathPaintOperator() function for more information. # [@param float :cw] indicates whether to go clockwise. # [@param float :o] origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). # [@param integer :nc] Number of curves used to draw a 90 degrees portion of arc. # [@author] Maxime Delorme, Nicola Asuni # [@since 3.1.000 (2008-06-09)] # [@access public] # def PieSectorXY(xc, yc, rx, ry, a, b, style='FD', cw=false, o=0, nc=2) if @rtl xc = @w - xc end op = getPathPaintOperator(style) ### not use ### #if op == 'f' # line_style = nil #end if cw d = b b = 360 - a + o a = 360 - d + o else b += o a += o end outellipticalarc(xc, yc, rx, ry, 0, a, b, true, nc) out(op) end alias_method :pie_sector_xy, :PieSectorXY =begin # # Set document barcode. # [@param string :bc] barcode # [@access public] # def setBarcode(bc="") @barcode = bc; end alias_method :set_barcode, :setBarcode def SetBarcode(bc="") warn "[DEPRECATION] 'SetBarcode' is deprecated. Please use 'set_barcode' instead." setBarcode(bc) end # # Get current barcode. # [@return string] # [@access public] # [@since 4.0.012 (2008-07-24)] # def getBarcode() return @barcode end alias_method :get_barcode, :getBarcode # # Print Barcode. # [@param int :x] x position in user units # [@param int :y] y position in user units # [@param int :w] width in user units # [@param int :h] height position in user units # [@param string :type] type of barcode (I25, C128A, C128B, C128C, C39) # [@param string :style] barcode style # [@param string :font] font for text # [@param int :xres] x resolution # [@param string :code] code to print # [@access public] # def writeBarcode(x, y, w, h, type, style, font, xres, code) require(File.dirname(__FILE__) + "/barcode/barcode.rb"); require(File.dirname(__FILE__) + "/barcode/i25object.rb"); require(File.dirname(__FILE__) + "/barcode/c39object.rb"); require(File.dirname(__FILE__) + "/barcode/c128aobject.rb"); require(File.dirname(__FILE__) + "/barcode/c128bobject.rb"); require(File.dirname(__FILE__) + "/barcode/c128cobject.rb"); if (code.empty?) return; end if (style.empty?) style = BCS_ALIGN_LEFT; style |= BCS_IMAGE_PNG; style |= BCS_TRANSPARENT; #:style |= BCS_BORDER; #:style |= BCS_DRAW_TEXT; #:style |= BCS_STRETCH_TEXT; #:style |= BCS_REVERSE_COLOR; end if (font.empty?) then font = BCD_DEFAULT_FONT; end if (xres.empty?) then xres = BCD_DEFAULT_XRES; end scale_factor = 1.5 * xres * @k; bc_w = (w * scale_factor).round #width in points bc_h = (h * scale_factor).round #height in points case (type.upcase) when "I25" obj = I25Object.new(bc_w, bc_h, style, code); when "C128A" obj = C128AObject.new(bc_w, bc_h, style, code); when "C128B" obj = C128BObject.new(bc_w, bc_h, style, code); when "C128C" obj = C128CObject.new(bc_w, bc_h, style, code); when "C39" obj = C39Object.new(bc_w, bc_h, style, code); end obj.SetFont(font); obj.DrawObject(xres); #use a temporary file.... tmpName = tempnam(@@k_path_cache,'img'); imagepng(obj.getImage(), tmpName); Image(tmpName, x, y, w, h, 'png'); obj.DestroyObject(); obj = nil unlink(tmpName); end alias_method :write_barcode, :writeBarcode =end # # Returns an array containing current margins: # # * ret['left'] = left margin # * ret['right'] = right margin # * ret['top'] = top margin # * ret['bottom'] = bottom margin # * ret['header'] = header margin # * ret['footer'] = footer margin # * ret['cell'] = cell margin # # [@return array] containing all margins measures # [@access public] # [@since 3.2.000 (2008-06-23)] # def getMargins() ret = { 'left' => @l_margin, 'right' => @r_margin, 'top' => @t_margin, 'bottom' => @b_margin, 'header' => @header_margin, 'footer' => @footer_margin, 'cell' => @c_margin, } return ret end alias_method :get_margins, :getMargins # # Returns an array containing original margins: # ret['left'] = left margin # ret['right'] = right margin # [@return array] containing all margins measures # [@access public] # [@since 4.0.012 (2008-07-24)] # def getOriginalMargins() ret = { 'left' => @original_l_margin, 'right' => @original_r_margin } return ret end alias_method :get_original_margins, :getOriginalMargins # # Returns the current font size. # [@return] current font size # [@access public] # [@since 3.2.000 (2008-06-23)] # def getFontSize() return @font_size end alias_method :get_font_size, :getFontSize # # Returns the current font size in points unit. # [@return] current font size in points unit # [@access public] # [@since 3.2.000 (2008-06-23)] # def getFontSizePt() return @font_size_pt end alias_method :get_font_size_pt, :getFontSizePt # # Returns the current font family name. # [@return string] current font family name # [@access public] # [@since 4.3.008 (2008-12-05)] # def getFontFamily() return @font_family end alias_method :get_font_family, :getFontFamily # # Returns the current font style. # [@return string] current font style # [@access public] # [@since 4.3.008 (2008-12-05)] # def getFontStyle() return @font_style end alias_method :get_font_style, :getFontStyle # --- HTML PARSER FUNCTIONS --- protected # # Extracts the CSS properties from a CSS string. # [@param string :cssdata] string containing CSS definitions. # [@return] A hash where the keys are the CSS selectors and the values are the CSS properties. # [@author] Nicola Asuni # [@since 5.1.000 (2010-05-25)] # [@access protected] # def extractCSSproperties(cssdata) if cssdata.empty? return {} end # remove comments cssdata = cssdata.gsub(/\/\*[^\*]*\*\//, '') # remove newlines and multiple spaces cssdata.gsub!(/[\s]+/, ' ') # remove some spaces cssdata.gsub!(/[\s]*([;:\{\}]{1})[\s]*/, '\\1') # remove empty blocks cssdata.gsub!(/([^\}\{]+)\{\}/, '') # replace media type parenthesis cssdata.gsub!(/@media[\s]+([^\{]*)\{/i, "@media \\1\t") cssdata.gsub!(/\}\}/mi, "}\t") # trim string cssdata = cssdata.lstrip # find media blocks (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv) cssblocks = {} matches = cssdata.scan(/@media +([^\t]*)\t([^\t]*)\t/i) unless matches.empty? matches.each { |type| cssblocks[type[0]] = type[1] } # remove media blocks cssdata.gsub!(/@media +([^\t]*)\t([^\t]*)\t/i, '') end # keep 'all' and 'print' media, other media types are discarded if cssblocks['all'] and !cssblocks['all'].empty? cssdata << cssblocks['all'] end if cssblocks['print'] and !cssblocks['print'].empty? cssdata << cssblocks['print'] end # reset css blocks array cssblocks = [] # explode css data string into array if cssdata[-1, 1] == '}' # remove last parethesis cssdata = cssdata.chop end matches = cssdata.split('}') matches.each_with_index { |block, key| # index 0 contains the CSS selector, index 1 contains CSS properties cssblocks[key] = block.split('{') } # split groups of selectors (comma-separated list of selectors) cssblocks.each_with_index { |block, key| # index 0 contains the CSS selector, index 1 contains CSS properties if block[0].index(',') selectors = block[0].split(',') selectors.each {|sel| cssblocks.push [sel.strip, block[1]] } cssblocks.delete_at(key) end } # covert array to selector => properties cssdata = {} cssblocks.each { |block| selector = block[0] # calculate selector's specificity a = 0 # the declaration is not from is a 'style' attribute b = selector.scan(/[\#]/).size # number of ID attributes c = selector.scan(/[\[\.]/).size # number of other attributes c += selector.scan(/[\:]link|visited|hover|active|focus|target|lang|enabled|disabled|checked|indeterminate|root|nth|first|last|only|empty|contains|not/i).size # number of pseudo-classes d = (' ' + selector).scan(/[\>\+\~\s]{1}[a-zA-Z0-9\*]+/).size # number of element names d += selector.scan(/[\:][\:]/).size # number of pseudo-elements specificity = a.to_s + b.to_s + c.to_s + d.to_s # add specificity to the beginning of the selector cssdata[specificity + ' ' + selector] = block[1] } # sort selectors alphabetically to account for specificity # ksort(cssdata, SORT_STRING) # return array return cssdata end # # Returns true if the CSS selector is valid for the selected HTML tag # [@param array :dom] array of HTML tags and properties # [@param int :key] key of the current HTML tag # [@param string :selector] CSS selector string # [@return] true if the selector is valid, false otherwise # [@access protected] # [@since 5.1.000 (2010-05-25)] # def isValidCSSSelectorForTag(dom, key, selector) valid = false; # value to be returned tag = dom[key]['value'] selector_class = '' if dom[key]['attribute'] and dom[key]['attribute']['class'] and !dom[key]['attribute']['class'].empty? selector_class = dom[key]['attribute']['class'].downcase end id = '' if dom[key]['attribute'] and dom[key]['attribute']['id'] and !dom[key]['attribute']['id'].empty? selector_id = dom[key]['attribute']['id'].downcase end selector_offset = 0 offset = nil operator = '' lasttag = '' attrib = '' while selector_offset = selector.index(/([\>\+\~\s]{1})([a-zA-Z0-9\*]+)([^\>\+\~\s]*)/mi, selector_offset) offset = selector_offset selector_offset += $&.length operator = $1 lasttag = $2.strip.downcase attrib = $3.strip.downcase end if offset if (lasttag == '*') or (lasttag == tag) # the last element on selector is our tag or 'any tag' if !attrib.empty? # check if matches class, id, attribute, pseudo-class or pseudo-element case attrib[0, 1] when '.' # class valid = true if attrib.sub(/^./, "") == selector_class when '#' # ID valid = true if attrib.sub(/^#/, "") == selector_id when '[' # attribute attrmatch = attrib.scan(/\[([a-zA-Z0-9]*)[\s]*([\~\^\$\*\|\=]*)[\s]*["]?([^"\]]*)["]?\]/i) if !attrmatch.empty? att = attrmatch[0].downcase val = attrmatch[2] if dom[key]['attribute'][att] case attrmatch[1] when '=' valid = true if dom[key]['attribute'][att] == val when '~=' valid = true if dom[key]['attribute'][att].split(' ').include?(val) when '^=' valid = true if val == substr(dom[key]['attribute'][att], 0, val.length) when '$=' valid = true if val == substr(dom[key]['attribute'][att], -val.length) when '*=' valid = true if dom[key]['attribute'][att].index(val) != nil when '|=' if dom[key]['attribute'][att] == val valid = true elsif ! dom[key]['attribute'][att].scan(/#{val}[\-]{1}/i).empty? valid = true end else valid = true end end end when ':' # pseudo-class or pseudo-element if attrib[1] == ':' # pseudo-element # pseudo-elements are not supported! # (::first-line, ::first-letter, ::before, ::after) else # pseudo-class # pseudo-classes are not supported! # (:root, :nth-child(n), :nth-last-child(n), :nth-of-type(n), :nth-last-of-type(n), :first-child, :last-child, :first-of-type, :last-of-type, :only-child, :only-of-type, :empty, :link, :visited, :active, :hover, :focus, :target, :lang(fr), :enabled, :disabled, :checked) end end # end of switch else valid = true end if valid and (offset > 0) valid = false # check remaining selector part selector = selector[0, offset] case operator when ' ' # descendant of an element while dom[key]['parent'] > 0 if isValidCSSSelectorForTag(dom, dom[key]['parent'], selector) valid = true break else key = dom[key]['parent'] end end when '>' # child of an element valid = isValidCSSSelectorForTag(dom, dom[key]['parent'], selector) when '+' # immediately preceded by an element (key - 1).downto(dom[key]['parent'] + 1) do |i| if dom[i]['tag'] and dom[i]['opening'] valid = isValidCSSSelectorForTag(dom, i, selector) break end end when '~' # preceded by an element (key - 1).downto(dom[key]['parent'] + 1) do |i| if dom[i]['tag'] and dom[i]['opening'] if isValidCSSSelectorForTag(dom, i, selector) break end end end end end end end return valid end # # Returns the styles that apply for the selected HTML tag. # [@param array :dom] array of HTML tags and properties # [@param int :key] key of the current HTML tag # [@param hash :css] array of CSS properties # [@return string] containing CSS properties # [@access protected] # [@since 5.1.000 (2010-05-25)] # def getTagStyleFromCSS(dom, key, css) tagstyle = '' # style to be returned # get all styles that apply css.each { |selector, style| # remove specificity selector = selector[selector.index(' ')..-1] if selector.index(' ') # check if this selector apply to current tag if isValidCSSSelectorForTag(dom, key, selector) # apply style tagstyle << ';' + style end } if dom[key]['attribute'] and dom[key]['attribute']['style'] # attach inline style (latest properties have high priority) tagstyle << ';' + dom[key]['attribute']['style'] end # remove multiple semicolons tagstyle.gsub!(/[;]+/, ';') return tagstyle end # # Returns the HTML DOM array. # dom[key]['tag'] = true if tag, false otherwise # dom[key]['value'] = tag name or text # dom[key]['opening'] = true if opening tag, false otherwise # dom[key]['attribute'] = array of attributes (attribute name is the key) # dom[key]['style'] = array of style attributes (attribute name is the key) # dom[key]['parent'] = id of parent element # dom[key]['fontname'] = font family name # dom[key]['fontstyle'] = font style # dom[key]['fontsize'] = font size in points # dom[key]['bgcolor'] = RGB array of background color # dom[key]['fgcolor'] = RGB array of foreground color # dom[key]['width'] = width in pixels # dom[key]['height'] = height in pixels # dom[key]['align'] = text alignment # dom[key]['cols'] = number of colums in table # dom[key]['rows'] = number of rows in table # [@param string :html] html code # [@return array] # [@access protected] # [@since 3.2.000 (2008-06-20)] # def getHtmlDomArray(html) html = html.dup html.force_encoding('UTF-8') if @is_unicode and html.respond_to?(:force_encoding) # array of CSS styles ( selector => properties). css = {} # extract external CSS files matches = html.scan(/<link([^\>]*?)>/mi) unless matches.empty? matches.each { |link| type = link[0].scan(/type[\s]*=[\s]*"text\/css"/) next if type.empty? type = link[0].scan(/media[\s]*=[\s]*"([^"]*)"/) # get 'all' and 'print' media, other media types are discarded # (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv) if type.empty? or (type[0][0] and ((type[0][0] == 'all') or (type[0][0] == 'print'))) type = link[0].scan(/href[\s]*=[\s]*"([^"]*)"/) next if type.empty? # read CSS data file uri = type[0][0].strip if uri =~ %r{^/} uri_path = Rails.root.join('public') uri.sub!(%r{^/}, '') uri.split('/').each {|path| uri_path = uri_path.join(path) } cssdata = '' next unless File.exist?(uri_path) open(uri_path) do |f| cssdata << f.read end else uri = URI(uri) next if !uri.scheme or !uri.host res = Net::HTTP.get_response(uri) cssdata = res.body end css = css.merge(extractCSSproperties(cssdata)) end } end # extract style tags matches = html.scan(/<style([^\>]*?)>([^\<]*?)<\/style>/mi) unless matches.empty? matches.each { |media| type = media[0].scan(/media[\s]*=[\s]*"([^"]*)"/) # get 'all' and 'print' media, other media types are discarded # (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv) if type.empty? or (type[0] and ((type[0] == 'all') or (type[0] == 'print'))) cssdata = media[1] css = css.merge(extractCSSproperties(cssdata)) end } end # remove heade and style blocks html.gsub!(/<head([^\>]*?)>(.*)<\/head>/mi, '') html.gsub!(/<style([^\>]*?)>([^\<]*?)<\/style>/mi, '') # remove comments html.gsub!(/<!--(.|\s)*?-->/m, '') # define block tags blocktags = ['blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','table','tr','td'] # remove all unsupported tags (the line below lists all supported tags) html = sanitize_html(html) # replace some blank characters html.gsub!(/<pre/, '<xre') # preserve pre tag html.gsub!(/<(table|tr|td|th|blockquote|dd|div|dt|h1|h2|h3|h4|h5|h6|br|hr|li|ol|ul|p)([^\>]*)>[\n\r\t]+/, '<\\1\\2>') html.gsub!(/@(\r\n|\r)@/, "\n") html.gsub!(/[\t\0\x0B]/, " ") offset = 0 while (offset < html.length) and ((pos = html.index('</pre>', offset)) != nil) html_a = html[0, offset] html_b = html[offset, pos - offset + 6] while html_b =~ /<xre([^\>]*)>(.*?)\n(.*?)<\/pre>/mi # preserve newlines on <pre> tag html_b = html_b.gsub(/<xre([^\>]*)>(.*?)\n(.*?)<\/pre>/mi) do if ($2 != '') and ($3 != '') "<xre#{$1}>#{$2}<br />#{$3}</pre>" elsif ($2 == '') and ($3 != '') "<xre#{$1}>#{$3}</pre>" elsif ($2 != '') and ($3 == '') "<xre#{$1}>#{$2}</pre>" else "<xre#{$1}></pre>" end end end # Delete the <br />(<br>) tag located immediately after the <pre> tag. html_b = html_b.gsub(/<xre([^\>]*)>((?:<[^\>]+>)*)<br *\/*>/, "<xre\\1>\\2") # Delete the <br />(<br>) tag located immediately before the </pre> tag. html_b = html_b.gsub(/<br *\/*>((?:<\/[^\>]+>)*)<\/pre>/, "\\1</pre>") while html_b =~ /<xre([^\>]*)>(.*?)[\s](.*?)<\/pre>/mi # preserve whitespace on <pre> tag html_b = html_b.gsub(/<xre([^\>]*)>(.*?)[\s](.*?)<\/pre>/mi, "<xre\\1>\\2 \\3</pre>") end html = html_a + html_b + html[(pos + 6)..-1] offset = (html_a + html_b).length end html.gsub!(/[\n]/, " ") # remove extra spaces from code html.gsub!(/[\s]+<\/(table|tr|td|th|ul|ol|li|dl|dt|dd)>/, '</\\1>') html.gsub!(/[\s]+<(tr|td|th|ul|ol|li|dl|dt|dd|br)/, '<\\1') html.gsub!(/<\/(table|tr|td|th|blockquote|dd|dl|div|dt|h1|h2|h3|h4|h5|h6|hr|li|ol|ul|p)>[\s]+</, '</\\1><') html.gsub!(/<\/(td|th)>/, '<marker style="font-size:0"/></\\1>') html.gsub!(/<\/table>([\s]*)<marker style="font-size:0"\/>/, '</table>') html.gsub!(/[\s]*<img/, ' <img') html.gsub!(/<img([^\>]*)>/xi, '<img\\1><span><marker style="font-size:0"/></span>') html.gsub!(/<xre/, '<pre') # restore pre tag # trim string html.gsub!(/^[\s]+/, '') html.gsub!(/[\s]+$/, '') # pattern for generic tag tagpattern = /(<[^>]+>)/ # explodes the string a = html.split(tagpattern).compact.delete_if {|x| x.empty?} # count elements maxel = a.size elkey = 0 key = 0 # create an array of elements dom = [] dom[key] = {} # set inheritable properties fot the first void element # possible inheritable properties are: azimuth, border-collapse, border-spacing, caption-side, color, cursor, direction, empty-cells, font, font-family, font-stretch, font-size, font-size-adjust, font-style, font-variant, font-weight, letter-spacing, line-height, list-style, list-style-image, list-style-position, list-style-type, orphans, page, page-break-inside, quotes, speak, speak-header, text-align, text-indent, text-transform, volume, white-space, widows, word-spacing dom[key]['tag'] = false dom[key]['block'] = false dom[key]['value'] = '' dom[key]['parent'] = 0 dom[key]['fontname'] = @font_family.dup dom[key]['fontstyle'] = @font_style.dup dom[key]['fontsize'] = @font_size_pt dom[key]['stroke'] = @textstrokewidth dom[key]['fill'] = ((@textrendermode % 2) == 0) dom[key]['clip'] = (@textrendermode > 3) dom[key]['line-height'] = @cell_height_ratio dom[key]['bgcolor'] = ActiveSupport::OrderedHash.new dom[key]['fgcolor'] = @fgcolor.dup # color dom[key]['strokecolor'] = @strokecolor.dup dom[key]['align'] = '' dom[key]['listtype'] = '' dom[key]['text-indent'] = 0 dom[key]['attribute'] = {} # reset attribute array thead = false # true when we are inside the THEAD tag key += 1 level = [] level.push(0) # root while elkey < maxel dom[key] = {} element = a[elkey] dom[key]['elkey'] = elkey if element =~ tagpattern # html tag element = element[1..-2] # get tag name tag = element.scan(/[\/]?([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0} tagname = tag[0].downcase # check if we are inside a table header if tagname == 'thead' if element[0,1] == '/' thead = false else thead = true end elkey += 1 next end dom[key]['tag'] = true dom[key]['value'] = tagname if blocktags.include?(dom[key]['value']) dom[key]['block'] = true else dom[key]['block'] = false end if element[0,1] == '/' # *** closing html tag dom[key]['opening'] = false dom[key]['parent'] = level[-1] level.pop if level.length > 1 grandparent = dom[(dom[key]['parent'])]['parent'] dom[key]['fontname'] = dom[grandparent]['fontname'].dup dom[key]['fontstyle'] = dom[grandparent]['fontstyle'].dup dom[key]['fontsize'] = dom[grandparent]['fontsize'] dom[key]['stroke'] = dom[grandparent]['stroke'] dom[key]['fill'] = dom[grandparent]['fill'] dom[key]['clip'] = dom[grandparent]['clip'] dom[key]['line-height'] = dom[grandparent]['line-height'] dom[key]['bgcolor'] = dom[grandparent]['bgcolor'].dup dom[key]['fgcolor'] = dom[grandparent]['fgcolor'].dup dom[key]['strokecolor'] = dom[grandparent]['strokecolor'].dup dom[key]['align'] = dom[grandparent]['align'].dup if !dom[grandparent]['listtype'].nil? dom[key]['listtype'] = dom[grandparent]['listtype'].dup end # set the number of columns in table tag if (dom[key]['value'] == 'tr') and dom[grandparent]['cols'].nil? dom[grandparent]['cols'] = dom[(dom[key]['parent'])]['cols'] end if (dom[key]['value'] == 'td') or (dom[key]['value'] == 'th') dom[(dom[key]['parent'])]['content'] = '' (dom[key]['parent'] + 1).upto(key - 1) do |i| dom[(dom[key]['parent'])]['content'] << a[dom[i]['elkey']] end # mark nested tables dom[(dom[key]['parent'])]['content'] = dom[(dom[key]['parent'])]['content'].gsub('<table', '<table nested="true"') # remove thead sections from nested tables dom[(dom[key]['parent'])]['content'] = dom[(dom[key]['parent'])]['content'].gsub('<thead>', '') dom[(dom[key]['parent'])]['content'] = dom[(dom[key]['parent'])]['content'].gsub('</thead>', '') end # store header rows on a new table if (dom[key]['value'] == 'tr') and (dom[(dom[key]['parent'])]['thead'] == true) if empty_string(dom[grandparent]['thead']) if dom[grandparent]['attribute'].nil? or dom[grandparent]['attribute']['style'].nil? dom[grandparent]['thead'] = a[dom[grandparent]['elkey']].dup else dom[grandparent]['thead'] = '<style>' + dom[grandparent]['value'] + ' {' + dom[grandparent]['attribute']['style'] + '}</style>' + a[dom[grandparent]['elkey']].dup end end dom[key]['parent'].upto(key) do |i| dom[grandparent]['thead'] << a[dom[i]['elkey']] end if dom[(dom[key]['parent'])]['attribute'].nil? dom[(dom[key]['parent'])]['attribute'] = {} end # header elements must be always contained in a single page dom[(dom[key]['parent'])]['attribute']['nobr'] = 'true' end if (dom[key]['value'] == 'table') and !empty_string(dom[(dom[key]['parent'])]['thead']) # remove the nobr attributes from the table header dom[(dom[key]['parent'])]['thead'] = dom[(dom[key]['parent'])]['thead'].gsub(' nobr="true"', '').sub(/<table([ >])/, '<table tablehead="1"\1') dom[(dom[key]['parent'])]['thead'] << '</table>' end else # *** opening html tag dom[key]['opening'] = true dom[key]['parent'] = level[-1] if element[-1, 1] == '/' or (dom[key]['value'] =~ /(br|img|hr)/) # self-closing tag dom[key]['self'] = true else level.push(key) dom[key]['self'] = false end # copy some values from parent parentkey = 0 if key > 0 parentkey = dom[key]['parent'] dom[key]['fontname'] = dom[parentkey]['fontname'].dup dom[key]['fontstyle'] = dom[parentkey]['fontstyle'].dup dom[key]['fontsize'] = dom[parentkey]['fontsize'] dom[key]['stroke'] = dom[parentkey]['stroke'] dom[key]['fill'] = dom[parentkey]['fill'] dom[key]['clip'] = dom[parentkey]['clip'] dom[key]['line-height'] = dom[parentkey]['line-height'] dom[key]['bgcolor'] = dom[parentkey]['bgcolor'].dup dom[key]['fgcolor'] = dom[parentkey]['fgcolor'].dup dom[key]['strokecolor'] = dom[parentkey]['strokecolor'].dup dom[key]['align'] = dom[parentkey]['align'].dup dom[key]['listtype'] = dom[parentkey]['listtype'].dup dom[key]['text-indent'] = dom[parentkey]['text-indent'] end # get attributes attr_array = element.scan(/([^=\s]*)[\s]*=[\s]*"([^"]*)"/) dom[key]['attribute'] = {} # reset attribute array attr_array.each do |name, value| dom[key]['attribute'][name.downcase] = value end if !css.empty? # merge eternal CSS style to current style dom[key]['attribute']['style'] = getTagStyleFromCSS(dom, key, css) end # split style attributes if !dom[key]['attribute']['style'].nil? # get style attributes style_array = dom[key]['attribute']['style'].scan(/([^;:\s]*):([^;]*)/) dom[key]['style'] = {} # reset style attribute array style_array.each do |name, value| # in case of duplicate attribute the last replace the previous dom[key]['style'][name.downcase] = value.strip end # --- get some style attributes --- if !dom[key]['style']['font-family'].nil? # font family if !dom[key]['style']['font-family'].nil? fontslist = dom[key]['style']['font-family'].downcase.split(',') fontslist.each {|font| font = font.downcase.strip if @fontlist.include?(font) or @fontkeys.include?(font) dom[key]['fontname'] = font break end } end end # list-style-type if !dom[key]['style']['list-style-type'].nil? dom[key]['listtype'] = dom[key]['style']['list-style-type'].downcase.strip if dom[key]['listtype'] == 'inherit' dom[key]['listtype'] = dom[parentkey]['listtype'] end end # text-indent if dom[key]['style']['text-indent'] dom[key]['text-indent'] = getHTMLUnitToUnits(dom[key]['style']['text-indent']) if dom[key]['text-indent'] == 'inherit' dom[key]['text-indent'] = dom[parentkey]['text-indent'] end end # font size if !dom[key]['style']['font-size'].nil? fsize = dom[key]['style']['font-size'].strip case fsize # absolute-size when 'xx-small' dom[key]['fontsize'] = dom[0]['fontsize'] - 4 when 'x-small' dom[key]['fontsize'] = dom[0]['fontsize'] - 3 when 'small' dom[key]['fontsize'] = dom[0]['fontsize'] - 2 when 'medium' dom[key]['fontsize'] = dom[0]['fontsize'] when 'large' dom[key]['fontsize'] = dom[0]['fontsize'] + 2 when 'x-large' dom[key]['fontsize'] = dom[0]['fontsize'] + 4 when 'xx-large' dom[key]['fontsize'] = dom[0]['fontsize'] + 6 # relative-size when 'smaller' dom[key]['fontsize'] = dom[parentkey]['fontsize'] - 3 when 'larger' dom[key]['fontsize'] = dom[parentkey]['fontsize'] + 3 else dom[key]['fontsize'] = getHTMLUnitToUnits(fsize, dom[parentkey]['fontsize'], 'pt', true) end end # line-height if dom[key]['style']['line-height'] lineheight = dom[key]['style']['line-height'].strip case lineheight # A normal line height. This is default when 'normal' dom[key]['line-height'] = dom[0]['line-height'] else if lineheight =~ /^[\d]*[.]?[\d]+$/ # 1.2, .2, 0.33, etc.. lineheight = lineheight.to_f * 100 end dom[key]['line-height'] = getHTMLUnitToUnits(lineheight, 1, '%', true) end end # font style dom[key]['fontstyle'] ||= "" if !dom[key]['style']['font-weight'].nil? and (dom[key]['style']['font-weight'][0,1].downcase == 'b') dom[key]['fontstyle'] << 'B' end if !dom[key]['style']['font-style'].nil? and (dom[key]['style']['font-style'][0,1].downcase == 'i') dom[key]['fontstyle'] << 'I' end # font color if !empty_string(dom[key]['style']['color']) dom[key]['fgcolor'] = convertHTMLColorToDec(dom[key]['style']['color']) elsif dom[key]['value'] == 'a' dom[key]['fgcolor'] = @html_link_color_array end # background color if !empty_string(dom[key]['style']['background-color']) dom[key]['bgcolor'] = convertHTMLColorToDec(dom[key]['style']['background-color']) end # text-decoration if !dom[key]['style']['text-decoration'].nil? decors = dom[key]['style']['text-decoration'].downcase.split(' ') decors.each {|dec| dec = dec.strip unless empty_string(dec) if dec[0,1] == 'u' # underline dom[key]['fontstyle'] << 'U' elsif dec[0,1] == 'l' # line-trough dom[key]['fontstyle'] << 'D' elsif dec[0,1] == 'o' # overline dom[key]['fontstyle'] << 'O' end end } elsif dom[key]['value'] == 'a' dom[key]['fontstyle'] = @html_link_font_style end # check for width attribute if !dom[key]['style']['width'].nil? dom[key]['width'] = dom[key]['style']['width'] end # check for height attribute if !dom[key]['style']['height'].nil? dom[key]['height'] = dom[key]['style']['height'] end # check for text alignment if !dom[key]['style']['text-align'].nil? dom[key]['align'] = dom[key]['style']['text-align'][0,1].upcase end # check for border attribute if !dom[key]['style']['border'].nil? dom[key]['attribute']['border'] = dom[key]['style']['border'] end # page-break-inside if dom[key]['style']['page-break-inside'] and (dom[key]['style']['page-break-inside'] == 'avoid') dom[key]['attribute']['nobr'] = 'true' end # page-break-before if dom[key]['style']['page-break-before'] if dom[key]['style']['page-break-before'] == 'always' dom[key]['attribute']['pagebreak'] = 'true' elsif dom[key]['style']['page-break-before'] == 'left' dom[key]['attribute']['pagebreak'] = 'left' elsif dom[key]['style']['page-break-before'] == 'right' dom[key]['attribute']['pagebreak'] = 'right' end end # page-break-after if dom[key]['style']['page-break-after'] if dom[key]['style']['page-break-after'] == 'always' dom[key]['attribute']['pagebreakafter'] = 'true' elsif dom[key]['style']['page-break-after'] == 'left' dom[key]['attribute']['pagebreakafter'] = 'left' elsif dom[key]['style']['page-break-after'] == 'right' dom[key]['attribute']['pagebreakafter'] = 'right' end end end # check for font tag if dom[key]['value'] == 'font' # font family if !dom[key]['attribute']['face'].nil? fontslist = dom[key]['attribute']['face'].downcase.split(',') fontslist.each { |font| font = font.downcase.strip if @fontlist.include?(font) or @fontkeys.include?(font) dom[key]['fontname'] = font break end } end # font size if !dom[key]['attribute']['size'].nil? if key > 0 if dom[key]['attribute']['size'][0,1] == '+' dom[key]['fontsize'] = dom[(dom[key]['parent'])]['fontsize'] + dom[key]['attribute']['size'][1..-1].to_i elsif dom[key]['attribute']['size'][0,1] == '-' dom[key]['fontsize'] = dom[(dom[key]['parent'])]['fontsize'] - dom[key]['attribute']['size'][1..-1].to_i else dom[key]['fontsize'] = dom[key]['attribute']['size'].to_i end else dom[key]['fontsize'] = dom[key]['attribute']['size'].to_i end end end # force natural alignment for lists if (dom[key]['value'] == 'ul') or (dom[key]['value'] == 'ol') or (dom[key]['value'] == 'dl') and (empty_string(dom[key]['align']) or (dom[key]['align'] != 'J')) if @rtl dom[key]['align'] = 'R' else dom[key]['align'] = 'L' end end if (dom[key]['value'] == 'small') or (dom[key]['value'] == 'sup') or (dom[key]['value'] == 'sub') if dom[key]['attribute']['size'].nil? and (dom[key]['style'].nil? or dom[key]['style']['font-size'].nil?) dom[key]['fontsize'] = dom[key]['fontsize'] * @@k_small_ratio end end if (dom[key]['value'] == 'strong') or (dom[key]['value'] == 'b') dom[key]['fontstyle'] << 'B' end if (dom[key]['value'] == 'em') or (dom[key]['value'] == 'i') dom[key]['fontstyle'] << 'I' end if dom[key]['value'] == 'u' or dom[key]['value'] == 'ins' dom[key]['fontstyle'] << 'U' end if dom[key]['value'] == 'del' dom[key]['fontstyle'] << 'D' end if (dom[key]['style'].nil? or dom[key]['style']['text-decoration'].nil?) and (dom[key]['value'] == 'a') dom[key]['fontstyle'] = @html_link_font_style end if (dom[key]['value'] == 'pre') or (dom[key]['value'] == 'tt') dom[key]['fontname'] = @default_monospaced_font end if (dom[key]['value'][0,1] == 'h') and (dom[key]['value'][1,1].to_i > 0) and (dom[key]['value'][1,1].to_i < 7) # headings h1, h2, h3, h4, h5, h6 if dom[key]['attribute']['size'].nil? and (dom[key]['style'].nil? or dom[key]['style']['font-size'].nil?) headsize = (4 - dom[key]['value'][1,1].to_i) * 2 dom[key]['fontsize'] = dom[0]['fontsize'] + headsize end if dom[key]['style'].nil? or dom[key]['style']['font-weight'].nil? dom[key]['fontstyle'] << 'B' end end if dom[key]['value'] == 'table' dom[key]['rows'] = 0 # number of rows dom[key]['trids'] = [] # IDs of TR elements dom[key]['thead'] = '' # table header rows end if dom[key]['value'] == 'tr' dom[key]['cols'] = 0 if thead dom[key]['thead'] = true # rows on thead block are printed as a separate table else dom[key]['thead'] = false # store the number of rows on table element dom[(dom[key]['parent'])]['rows'] += 1 # store the TR elements IDs on table element dom[(dom[key]['parent'])]['trids'].push(key) end end if (dom[key]['value'] == 'th') or (dom[key]['value'] == 'td') if !dom[key]['attribute']['colspan'].nil? colspan = dom[key]['attribute']['colspan'].to_i else colspan = 1 end dom[key]['attribute']['colspan'] = colspan dom[(dom[key]['parent'])]['cols'] += colspan end # set foreground color attribute if !empty_string(dom[key]['attribute']['color']) dom[key]['fgcolor'] = convertHTMLColorToDec(dom[key]['attribute']['color']) elsif (dom[key]['style'].nil? or dom[key]['style']['color'].nil?) and (dom[key]['value'] == 'a') dom[key]['fgcolor'] = @html_link_color_array end # set background color attribute if !empty_string(dom[key]['attribute']['bgcolor']) dom[key]['bgcolor'] = convertHTMLColorToDec(dom[key]['attribute']['bgcolor']) end # set stroke color attribute if !empty_string(dom[key]['attribute']['strokecolor']) dom[key]['strokecolor'] = convertHTMLColorToDec(dom[key]['attribute']['strokecolor']) end # check for width attribute if !dom[key]['attribute']['width'].nil? dom[key]['width'] = dom[key]['attribute']['width'] end # check for height attribute if !dom[key]['attribute']['height'].nil? dom[key]['height'] = dom[key]['attribute']['height'] end # check for text alignment if !empty_string(dom[key]['attribute']['align']) and (dom[key]['value'] != 'img') dom[key]['align'] = dom[key]['attribute']['align'][0,1].upcase end # check for text rendering mode (the following attributes do not exist in HTML) if !dom[key]['attribute']['stroke'].nil? # font stroke width dom[key]['stroke'] = getHTMLUnitToUnits(dom[key]['attribute']['stroke'], dom[key]['fontsize'], 'pt', true) end if !dom[key]['attribute']['fill'].nil? # font fill if dom[key]['attribute']['fill'] == 'true' dom[key]['fill'] = true else dom[key]['fill'] = false end end if !dom[key]['attribute']['clip'].nil? # clipping mode if dom[key]['attribute']['clip'] == 'true' dom[key]['clip'] = true else dom[key]['clip'] = false end end end # end opening tag else # text dom[key]['tag'] = false dom[key]['block'] = false dom[key]['value'] = unhtmlentities(element) dom[key]['parent'] = level[-1] end elkey += 1 key += 1 end return dom end # # Convert to accessible file path # [@param string :attrname] image file name # def get_image_filename( attrname ) attrname_escape = File.join(File.dirname(attrname), ERB::Util.url_encode(File.basename(attrname))) testscrtype = URI.parse(attrname_escape) if testscrtype.query.nil? or testscrtype.query.empty? # convert URL to server path attrname = attrname.gsub(@@k_path_url, @@k_path_main) end end # # Get image file from remote URL # [@param string :image_uri] image URI path # def get_image_file(image_uri) image_uri = File.join(File.dirname(image_uri), ERB::Util.url_encode(File.basename(image_uri))) uri = URI.parse(image_uri) extname = File.extname(uri.path) #use a temporary file.... tmpFile = Tempfile.new(['tmp_', extname], @@k_path_cache) tmpFile.binmode Net::HTTP.start(uri.host, uri.port) do |http| res = http.get(uri.path) tmpFile.print res.body end tmpFile.fsync tmpFile end # # Convert to accessible url path # [@param string :url] url path # def get_sever_url(url) if !empty_string(url) and (url[0, 1] == '/') '' else url end end # # Returns the string used to find spaces # [@return string] # [@access protected] # [@author] Nicola Asuni # [@since 4.8.024 (2010-01-15)] # def getSpaceString() spacestr = 32.chr if (@current_font['type'] == 'TrueTypeUnicode') or (@current_font['type'] == 'cidfont0') spacestr = 0.chr + 32.chr end return spacestr end public # # Prints a cell (rectangular area) with optional borders, background color and html text string. The upper-left corner of the cell corresponds to the current position. After the call, the current position moves to the right or to the next line. # If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting. # [@param float :w] Cell width. If 0, the cell extends up to the right margin. # [@param float :h] Cell minimum height. The cell extends automatically if needed. # [@param float :x] upper-left corner X coordinate # [@param float :y] upper-left corner Y coordinate # [@param string :html] html text to print. Default value: empty string. # [@param mixed :border] # Indicates if borders must be drawn around the cell. The value can be either a number: # * 0: no border (default) # * 1: frame # or a string containing some or all of the following characters (in any order): # * L: left # * T: top # * R: right # * B: bottom # [@param int :ln] # Indicates where the current position should go after the call. Possible values are: # * 0: to the right (or left for RTL language) # * 1: to the beginning of the next line # * 2: below # Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. # [@param int :fill] Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. # [@param boolean :reseth] if true reset the last cell height (default true). # [@param string :align] # Allows to center or align the text. Possible values are: # * L : left align # * C : center # * R : right align # * '' : empty string : left for LTR or right for RTL # [@param boolean :autopadding] if true, uses internal padding and automatically adjust it to account for line width. # [@access public] # [@uses] MultiCell() # [@see] Multicell(), writeHTML(), Cell() # def writeHTMLCell(w, h, x, y, html='', border=0, ln=0, fill=0, reseth=true, align='', autopadding=true) rtn = MultiCell(w, h, html, border, align, fill, ln, x, y, reseth, 0, true, autopadding, 0) return rtn rescue => err Error('writeHTMLCell Error.', err) end alias_method :write_html_cell, :writeHTMLCell def sanitize_html(html) # Escape '<' character for not tag case. html = html.gsub(%r{(<+)([^/a-zA-Z])}){CGI.escapeHTML($1) + $2}.gsub(%r{</([^a-zA-Z])}){'</' + $1} html = "%s" % sanitize(html, :tags=> %w(a b blockquote body br dd del div dl dt em font h1 h2 h3 h4 h5 h6 hr i img li ol p pre small span strong sub sup table td th thead tr tt u ins ul), :attributes => %w(cellspacing cellpadding bgcolor color value width height src size colspan rowspan style align border face href name dir class id nobr stroke strokecolor fill nested tablehead)) end protected :sanitize_html # # Allows to preserve some HTML formatting (limited support). # IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting. # Supported tags are: a, b, blockquote, br, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, img, li, ol, p, pre, small, span, strong, sub, sup, table, td, th, thead, tr, tt, u, ul # [@param string :html] text to display # [@param boolean :ln] if true add a new line after text (default = true) # [@param int :fill] Indicates if the background must be painted (1:true) or transparent (0:false). # [@param boolean :reseth] if true reset the last cell height (default false). # [@param boolean :cell] if true add the default c_margin space to each Write (default false). # [@param string :align] # Allows to center or align the text. Possible values are: # * L : left align # * C : center # * R : right align # * '' : empty string : left for LTR or right for RTL # [@access public] # def writeHTML(html, ln=true, fill=0, reseth=false, cell=false, align='') ln = false if ln == 0 reseth = false if reseth == 0 cell = false if cell == 0 case fill when true fill = 1 when false fill = 0 end gvars = getGraphicVars() # store current values prevPage = @page prevlMargin = @l_margin prevrMargin = @r_margin curfontname = @font_family curfontstyle = @font_style curfontsize = @font_size_pt curfontascent = getFontAscent(curfontname, curfontstyle, curfontsize) curfontdescent = getFontDescent(curfontname, curfontstyle, curfontsize) @newline = true startlinepage = @page minstartliney = @y maxbottomliney = 0 startlinex = @x startliney = @y yshift = 0 newline = true loop = 0 curpos = 0 opentagpos = nil this_method_vars = {} undo = false fontaligned = false @premode = false if !@page_annots[@page].nil? pask = @page_annots[@page].length else pask = 0 end if !@in_footer if !@footerlen[@page].nil? @footerpos[@page] = @pagelen[@page] - @footerlen[@page] else @footerpos[@page] = @pagelen[@page] end startlinepos = @footerpos[@page] else startlinepos = @pagelen[@page] end lalign = align plalign = align if @rtl w = @x - @l_margin else w = @w - @r_margin - @x end w -= 2 * @c_margin if cell if @rtl @x -= @c_margin else @x += @c_margin end end if @customlistindent >= 0 @listindent = @customlistindent else @listindent = GetStringWidth('0000') end @listindentlevel = 0 # save previous states prev_cell_height_ratio = @cell_height_ratio prev_listnum = @listnum prev_listordered = @listordered prev_listcount = @listcount prev_lispacer = @lispacer @listnum = 0 @listordered = [] @listcount = [] @lispacer = '' if empty_string(@lasth) or reseth #set row height @lasth = @font_size * @cell_height_ratio end dom = getHtmlDomArray(html) maxel = dom.size key = 0 while key < maxel if dom[key]['tag'] and dom[key]['attribute'] and dom[key]['attribute']['pagebreak'] # check for pagebreak if (dom[key]['attribute']['pagebreak'] == 'true') or (dom[key]['attribute']['pagebreak'] == 'left') or (dom[key]['attribute']['pagebreak'] == 'right') # add a page (or trig AcceptPageBreak() for multicolumn mode) checkPageBreak(@page_break_trigger + 1) end if ((dom[key]['attribute']['pagebreak'] == 'left') and ((!@rtl and (@page % 2 == 0)) or (@rtl and (@page % 2 != 0)))) or ((dom[key]['attribute']['pagebreak'] == 'right') and ((!@rtl and (@page % 2 != 0)) or (@rtl and (@page % 2 == 0)))) # add a page (or trig AcceptPageBreak() for multicolumn mode) checkPageBreak(@page_break_trigger + 1) end end if dom[key]['tag'] and dom[key]['opening'] and dom[key]['attribute']['nobr'] and (dom[key]['attribute']['nobr'] == 'true') if dom[(dom[key]['parent'])]['attribute']['nobr'] and (dom[(dom[key]['parent'])]['attribute']['nobr'] == 'true') dom[key]['attribute']['nobr'] = false else # store current object startTransaction() # save this method vars this_method_vars['html'] = html.dup this_method_vars['ln'] = ln this_method_vars['fill'] = fill this_method_vars['reseth'] = reseth this_method_vars['cell'] = cell this_method_vars['align'] = align.dup this_method_vars['gvars'] = Marshal.load(Marshal.dump(gvars)) this_method_vars['prevPage'] = prevPage this_method_vars['prevlMargin'] = prevlMargin this_method_vars['prevrMargin'] = prevrMargin this_method_vars['curfontname'] = curfontname.dup this_method_vars['curfontstyle'] = curfontstyle.dup this_method_vars['curfontsize'] = curfontsize this_method_vars['curfontascent'] = curfontascent this_method_vars['curfontdescent'] = curfontdescent this_method_vars['minstartliney'] = minstartliney this_method_vars['maxbottomliney'] = maxbottomliney this_method_vars['yshift'] = yshift this_method_vars['startlinepage'] = startlinepage this_method_vars['startlinepos'] = startlinepos this_method_vars['startlinex'] = startlinex this_method_vars['startliney'] = startliney this_method_vars['newline'] = newline this_method_vars['loop'] = loop this_method_vars['curpos'] = curpos this_method_vars['pask'] = pask this_method_vars['lalign'] = lalign this_method_vars['plalign'] = plalign this_method_vars['w'] = w this_method_vars['prev_cell_height_ratio'] = prev_cell_height_ratio this_method_vars['prev_listnum'] = prev_listnum this_method_vars['prev_listordered'] = prev_listordered this_method_vars['prev_listcount'] = prev_listcount this_method_vars['prev_lispacer'] = prev_lispacer this_method_vars['fontaligned'] = fontaligned this_method_vars['key'] = key this_method_vars['dom'] = Marshal.load(Marshal.dump(dom)) end end # print THEAD block if (dom[key]['value'] == 'tr') and dom[key]['thead'] and dom[key]['thead'] if dom[key]['parent'] and dom[(dom[key]['parent'])]['thead'] and !empty_string(dom[(dom[key]['parent'])]['thead']) @in_thead = true prev_lMargin = @l_margin prev_rMargin = @r_margin @l_margin = @thead_margins['lmargin'] @r_margin = @thead_margins['rmargin'] # print table header (thead) writeHTML(@thead, false, false, false, false, '') @l_margin = prev_lMargin @r_margin = prev_rMargin if (@start_transaction_page == (@numpages - 1)) or (@y < @start_transaction_y) or checkPageBreak(@lasth, '', false) # restore previous object rollbackTransaction(true) # restore previous values this_method_vars.each {|vkey , vval| eval("#{vkey} = vval") } # add a page (or trig AcceptPageBreak() for multicolumn mode) pre_y = @y if !checkPageBreak(@page_break_trigger + 1) and (@y < pre_y) # fix for multicolumn mode startliney = @y end @start_transaction_page = @page @start_transaction_y = @y end end # move :key index forward to skip THEAD block while (key < maxel) and !((dom[key]['tag'] and dom[key]['opening'] and (dom[key]['value'] == 'tr') and (dom[key]['thead'].nil? or !dom[key]['thead'])) or (dom[key]['tag'] and !dom[key]['opening'] and (dom[key]['value'] == 'table'))) key += 1 end end if dom[key]['tag'] or (key == 0) if dom[key]['line-height'] # set line height @cell_height_ratio = dom[key]['line-height'] @lasth = @font_size * @cell_height_ratio end if ((dom[key]['value'] == 'table') or (dom[key]['value'] == 'tr')) and !dom[key]['align'].nil? dom[key]['align'] = @rtl ? 'R' : 'L' end # vertically align image in line if !@newline and (dom[key]['value'] == 'img') and dom[key]['height'] and (dom[key]['height'].to_i > 0) # get image height imgh = getHTMLUnitToUnits(dom[key]['height'], @lasth, 'px') # check for automatic line break autolinebreak = false if dom[key]['width'] and (dom[key]['width'].to_i > 0) imgw = getHTMLUnitToUnits(dom[key]['width'], 1, 'px', false) if (@rtl and (@x - imgw < @l_margin + @c_margin)) or (!@rtl and (@x + imgw > @w - @r_margin - @c_margin)) # add automatic line break autolinebreak = true Ln('', cell) # go back to evaluate this line break key -= 1 end end if !autolinebreak if !@in_footer pre_y = @y # check for page break if !checkPageBreak(imgh) and (@y < pre_y) # fix for multicolumn mode startliney = @y end end if @page > startlinepage # fix line splitted over two pages if !@footerlen[startlinepage].nil? curpos = @pagelen[startlinepage] - @footerlen[startlinepage] end # line to be moved one page forward pagebuff = getPageBuffer(startlinepage) linebeg = pagebuff[startlinepos, curpos - startlinepos] tstart = pagebuff[0, startlinepos] tend = pagebuff[curpos..-1] # remove line from previous page setPageBuffer(startlinepage, tstart + '' + tend) pagebuff = getPageBuffer(@page) tstart = pagebuff[0, @cntmrk[@page]] tend = pagebuff[@cntmrk[@page]..-1] # add line start to current page yshift = minstartliney - @y if fontaligned yshift += curfontsize / @k end try = sprintf('1 0 0 1 0 %.3f cm', (yshift * @k)) setPageBuffer(@page, tstart + "\nq\n" + try + "\n" + linebeg + "\nQ\n" + tend) # shift the annotations and links if @page_annots[@page] next_pask = @page_annots[@page].length else next_pask = 0 end if !@page_annots[startlinepage].nil? @page_annots[startlinepage].each_with_index { |pac, pak| if pak >= pask @page_annots[@page].push pac @page_annots[startlinepage].delete_at(pak) npak = @page_annots[@page].length - 1 @page_annots[@page][npak]['y'] -= yshift end } end pask = next_pask startlinepos = @cntmrk[@page] startlinepage = @page startliney = @y end @y += ((curfontsize * @cell_height_ratio / @k) + curfontascent - curfontdescent) / 2.0 - imgh minstartliney = [@y, minstartliney].min maxbottomliney = startliney + @font_size * @cell_height_ratio end elsif !dom[key]['fontname'].nil? or !dom[key]['fontstyle'].nil? or !dom[key]['fontsize'].nil? # account for different font size pfontname = curfontname pfontstyle = curfontstyle pfontsize = curfontsize fontname = !dom[key]['fontname'].nil? ? dom[key]['fontname'] : curfontname fontstyle = !dom[key]['fontstyle'].nil? ? dom[key]['fontstyle'] : curfontstyle fontsize = !dom[key]['fontsize'].nil? ? dom[key]['fontsize'] : curfontsize fontascent = getFontAscent(fontname, fontstyle, fontsize) fontdescent = getFontDescent(fontname, fontstyle, fontsize) if (fontname != curfontname) or (fontstyle != curfontstyle) or (fontsize != curfontsize) if fontsize.is_a?(Numeric) and (fontsize >= 0) and curfontsize.is_a?(Numeric) and (curfontsize >= 0) and (fontsize != curfontsize) and !@newline and (key < maxel - 1) if !@newline and (@page > startlinepage) # fix lines splitted over two pages if !@footerlen[startlinepage].nil? curpos = @pagelen[startlinepage] - @footerlen[startlinepage] end # line to be moved one page forward pagebuff = getPageBuffer(startlinepage) linebeg = pagebuff[startlinepos, curpos - startlinepos] tstart = pagebuff[0, startlinepos] tend = pagebuff[curpos..-1] # remove line from previous page setPageBuffer(startlinepage, tstart + '' + tend) pagebuff = getPageBuffer(@page) tstart = pagebuff[0, @cntmrk[@page]] tend = pagebuff[@cntmrk[@page]..-1] # add line start to current page yshift = minstartliney - @y try = sprintf('1 0 0 1 0 %.3f cm', yshift * @k) setPageBuffer(@page, tstart + "\nq\n" + try + "\n" + linebeg + "\nQ\n" + tend) # shift the annotations and links if @page_annots[@page] next_pask = @page_annots[@page].length else next_pask = 0 end if !@page_annots[startlinepage].nil? @page_annots[startlinepage].each_with_index { |pac, pak| if pak >= pask @page_annots[@page].push = pac @page_annots[startlinepage].delete_at(pak) npak = @page_annots[@page].length - 1 @page_annots[@page][npak]['y'] -= yshift end } end pask = next_pask startlinepos = @cntmrk[@page] startlinepage = @page startliney = @y end if !dom[key]['block'] @y += (((curfontsize - fontsize) * @cell_height_ratio / @k) + curfontascent - fontascent - curfontdescent + fontdescent) / 2.0 if (dom[key]['value'] != 'sup') and (dom[key]['value'] != 'sub') minstartliney = [@y, minstartliney].min maxbottomliney = [@y + ((fontsize * @cell_height_ratio) / @k), maxbottomliney].max end end fontaligned = true end SetFont(fontname, fontstyle, fontsize) @lasth = @font_size * @cell_height_ratio curfontname = fontname curfontstyle = fontstyle curfontsize = fontsize curfontascent = fontascent curfontdescent = fontdescent end end # set text rendering mode textstroke = !dom[key]['stroke'].nil? ? dom[key]['stroke'] : @textstrokewidth textfill = !dom[key]['fill'].nil? ? dom[key]['fill'] : ((@textrendermode % 2) == 0) textclip = !dom[key]['clip'].nil? ? dom[key]['clip'] : (@textrendermode > 3) setTextRenderingMode(textstroke, textfill, textclip) if (plalign == 'J') and dom[key]['block'] plalign = '' end # get current position on page buffer curpos = @pagelen[startlinepage] if !dom[key]['bgcolor'].nil? and (dom[key]['bgcolor'].length > 0) SetFillColorArray(dom[key]['bgcolor']) wfill = 1 else wfill = fill end if !dom[key]['fgcolor'].nil? and (dom[key]['fgcolor'].length > 0) SetTextColorArray(dom[key]['fgcolor']) end if !dom[key]['strokecolor'].nil? and (dom[key]['strokecolor'].length > 0) SetDrawColorArray(dom[key]['strokecolor']) end if !dom[key]['align'].nil? lalign = dom[key]['align'] end if empty_string(lalign) lalign = align end end # align lines if @newline and (dom[key]['value'].length > 0) and (dom[key]['value'] != 'td') and (dom[key]['value'] != 'th') newline = true fontaligned = false # we are at the beginning of a new line if !startlinex.nil? yshift = minstartliney - startliney if (yshift > 0) or (@page > startlinepage) yshift = 0 end t_x = 0 # the last line must be shifted to be aligned as requested linew = (@endlinex - startlinex).abs pstart = getPageBuffer(startlinepage)[0, startlinepos] if !opentagpos.nil? and !@footerlen[startlinepage].nil? and !@in_footer @footerpos[startlinepage] = @pagelen[startlinepage] - @footerlen[startlinepage] midpos = [opentagpos, @footerpos[startlinepage]].min elsif !opentagpos.nil? midpos = opentagpos elsif !@footerlen[startlinepage].nil? and !@in_footer @footerpos[startlinepage] = @pagelen[startlinepage] - @footerlen[startlinepage] midpos = @footerpos[startlinepage] else midpos = 0 end if midpos > 0 pmid = getPageBuffer(startlinepage)[startlinepos, midpos - startlinepos] pend = getPageBuffer(startlinepage)[midpos..-1] else pmid = getPageBuffer(startlinepage)[startlinepos..-1] pend = '' end if (!plalign.nil? and ((plalign == 'C') or (plalign == 'J') or ((plalign == 'R') and !@rtl) or ((plalign == 'L') and @rtl))) or (yshift < 0) # calculate shifting amount tw = w if (plalign == 'J') and isRTLTextDir() and (@num_columns > 1) tw += @c_margin end if @l_margin != prevlMargin tw += prevlMargin - @l_margin end if @r_margin != prevrMargin tw += prevrMargin - @r_margin end one_space_width = GetStringWidth(32.chr) mdiff = (tw - linew).abs if plalign == 'C' if @rtl t_x = -(mdiff / 2.0) else t_x = (mdiff / 2.0) end elsif (plalign == 'R') and !@rtl # right alignment on LTR document if revstrpos(pmid, ')]').to_i == revstrpos(pmid, ' )]').to_i + 1 # remove last space (if any) linew -= one_space_width mdiff = (tw - linew).abs end t_x = mdiff elsif (plalign == 'L') and @rtl # left alignment on RTL document if revstrpos(pmid, '[(') and ((revstrpos(pmid, '[( ').to_i == revstrpos(pmid, '[(').to_i) or (revstrpos(pmid, '[(' + 0.chr + 32.chr).to_i == revstrpos(pmid, '[(').to_i)) # remove first space (if any) linew -= one_space_width end if pmid.index('[(') and (pmid.index('[(').to_i == revstrpos(pmid, '[(').to_i) # remove last space (if any) linew -= one_space_width if (@current_font['type'] == 'TrueTypeUnicode') or (@current_font['type'] == 'cidfont0') linew -= one_space_width end end mdiff = (tw - linew).abs t_x = -mdiff elsif (plalign == 'J') and (plalign == lalign) # Justification if isRTLTextDir() t_x = @l_margin - @endlinex + @c_margin end no = 0 # spaces without trim ns = 0 # spaces with trim pmidtemp = pmid.dup # escape special characters pmidtemp.gsub!(/[\\][\(]/x, '\\#!#OP#!#') pmidtemp.gsub!(/[\\][\)]/x, '\\#!#CP#!#') # search spaces lnstring = pmidtemp.scan(/\[\(([^\)]*)\)\]/x) if !lnstring.empty? spacestr = getSpaceString() maxkk = lnstring.length - 1 0.upto(maxkk) do |kk| # restore special characters lnstring[kk][0].gsub!('#!#OP#!#', '(') lnstring[kk][0].gsub!('#!#CP#!#', ')') if kk == maxkk if isRTLTextDir() tvalue = lnstring[kk][0].lstrip else tvalue = lnstring[kk][0].rstrip end else tvalue = lnstring[kk][0] end # store number of spaces on the strings lnstring[kk][1] = lnstring[kk][0].count(spacestr) lnstring[kk][2] = tvalue.count(spacestr) # count total spaces on line no += lnstring[kk][1] ns += lnstring[kk][2] lnstring[kk][3] = no lnstring[kk][4] = ns end if isRTLTextDir() t_x = @l_margin - @endlinex + @c_margin - ((no - ns) * one_space_width) end # calculate additional space to add to each space spacelen = one_space_width spacewidth = (((tw - linew) + ((no - ns) * spacelen)) / (ns ? ns : 1)) * @k spacewidthu = -1000 * ((tw - linew) + (no * spacelen)) / (ns ? ns : 1) / @font_size nsmax = ns ns = 0 # reset(lnstring) offset = 0 strcount = 0 prev_epsposbeg = 0 textpos = 0; if isRTLTextDir() textpos = @w_pt end while pmid_offset = pmid.index(/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x, offset) pmid_data = $1 pmid_mark = $2 # check if we are inside a string section '[( ... )]' stroffset = pmid.index('[(', offset) if (stroffset != nil) and (stroffset <= pmid_offset) # set offset to the end of string section offset = pmid.index(')]', stroffset) while (offset != nil) and (pmid[offset - 1, 1] == '\\') offset = pmid.index(')]', offset + 1) end if offset == false Error('HTML Justification: malformed PDF code.') end next end if isRTLTextDir() spacew = spacewidth * (nsmax - ns) else spacew = spacewidth * ns end offset = pmid_offset + $&.length epsposbeg = pmid.index('q' + @epsmarker, offset) epsposbeg = 0 if epsposbeg.nil? epsposend = pmid.index(@epsmarker + 'Q', offset) epsposend = 0 if epsposend.nil? epsposend += (@epsmarker + 'Q').length if ((epsposbeg > 0) and (epsposend > 0) and (offset > epsposbeg) and (offset < epsposend)) or ((epsposbeg === false) and (epsposend > 0) and (offset < epsposend)) # shift EPS images trx = sprintf('1 0 0 1 %.3f 0 cm', spacew) epsposbeg = pmid.index('q' + @epsmarker, prev_epsposbeg - 6) epsposbeg = 0 if epsposbeg.nil? pmid_b = pmid[0, epsposbeg] pmid_m = pmid[epsposbeg, epsposend - epsposbeg] pmid_e = pmid[epsposend..-1] pmid = pmid_b + "\nq\n" + trx + "\n" + pmid_m + "\nQ\n" + pmid_e offset = epsposend next end prev_epsposbeg = epsposbeg currentxpos = 0 # shift blocks of code case pmid_mark when 'Td', 'cm', 'm', 'l' # get current X position pmid =~ /([0-9\.\+\-]*)[\s](#{pmid_data})[\s](#{pmid_mark})([\s]*)/x currentxpos = $1.to_i textpos = currentxpos if (strcount <= maxkk) and (pmid_mark == 'Td') if strcount == maxkk if isRTLTextDir() tvalue = lnstring[strcount][0] else tvalue = lnstring[strcount][0].rstrip end else tvalue = lnstring[strcount][0] end ns += tvalue.count(spacestr) strcount += 1 end if isRTLTextDir() spacew = spacewidth * (nsmax - ns) end # justify block pmid.sub!(/([0-9\.\+\-]*)[\s](#{pmid_data})[\s](#{pmid_mark})([\s]*)/x, "" + sprintf("%.2f", $1.to_f + spacew) + " " + $2 + " x*#!#*x" + $3 + $4) when 're' # justify block pmid =~ /([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s](#{pmid_data})[\s](re)([\s]*)/x currentxpos = $1.to_i x_diff = 0 w_diff = 0 if isRTLTextDir() # RTL if currentxpos < textpos x_diff = spacewidth * (nsmax - lnstring[strcount][4]) w_diff = spacewidth * lnstring[strcount][2] else if strcount > 0 x_diff = spacewidth * (nsmax - lnstring[strcount - 1][4]) w_diff = spacewidth * lnstring[strcount - 1][2] end end else # LTR if currentxpos > textpos if strcount > 0 x_diff = spacewidth * lnstring[strcount - 1][3] end w_diff = spacewidth * lnstring[strcount][2] else if strcount > 1 x_diff = spacewidth * lnstring[strcount - 2][3] end if strcount > 0 w_diff = spacewidth * lnstring[strcount - 1][2] end end end pmid.sub!(/(#{$1})[\s](#{$2})[\s](#{$3})[\s](#{pmid_data})[\s](re)([\s]*)/x, "" + sprintf("%.2f", $1.to_f + x_diff) + " " + $2 + " " + sprintf("%.2f", $3.to_f + w_diff) + " " + $4 + " x*#!#*x" + $5 + $6) when 'c' # get current X position pmid =~ /([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s](#{pmid_data})[\s](c)([\s]*)/x currentxpos = $1.to_i # justify block pmid.sub!(/(#{$1})[\s](#{$2})[\s](#{$3})[\s](#{$4})[\s](${5})[\s](#{pmid_data})[\s](c)([\s]*)/x, "" + sprintf("%.3f", $1.to_f + spacew) + " " + $2 + " " + sprintf("%.3f", $3.to_f + spacew) + " " + $4 + " " + sprintf("%.3f", $5.to_f + spacew) + " " + $6 + " x*#!#*x" + $7 + $8) end # shift the annotations and links if !@page_annots[@page].nil? cxpos = currentxpos / @k lmpos = @l_margin + @c_margin + @feps @page_annots[@page].each_with_index { |pac, pak| if (pac['y'] >= minstartliney) and (pac['x'] * @k >= currentxpos - @feps) and (pac['x'] * @k <= currentxpos + @feps) if cxpos > lmpos @page_annots[@page][pak]['x'] += (spacew - one_space_width) / @k @page_annots[@page][pak]['w'] += (spacewidth * pac['numspaces']) / @k else @page_annots[@page][pak]['w'] += ((spacewidth * pac['numspaces']) - one_space_width) / @k end break end } end end # end of while # remove markers pmid.gsub!('x*#!#*x', '') if (@current_font['type'] == 'TrueTypeUnicode') or (@current_font['type'] == 'cidfont0') # multibyte characters spacew = spacewidthu pmidtemp = pmid.dup # escape special characters pmidtemp.gsub!(/[\\][\(]/x, '\\#!#OP#!#') pmidtemp.gsub!(/[\\][\)]/x, '\\#!#CP#!#') pmidtemp =~ /\[\(([^\)]*)\)\]/x matches1 = $1.gsub("#!#OP#!#", "(") matches1.gsub!("#!#CP#!#", ")") pmid = pmidtemp.sub(/\[\(([^\)]*)\)\]/x, "[(" + matches1.gsub(0.chr + 32.chr, ") " + sprintf("%.3f", spacew) + " (") + ")]") setPageBuffer(startlinepage, pstart + "\n" + pmid + "\n" + pend) endlinepos = (pstart + "\n" + pmid + "\n").length else # non-unicode (single-byte characters) rs = sprintf("%.3f Tw", spacewidth) pmid.gsub!(/\[\(/x, "#{rs} [(") setPageBuffer(startlinepage, pstart + "\n" + pmid + "\nBT 0 Tw ET\n" + pend) endlinepos = (pstart + "\n" + pmid + "\nBT 0 Tw ET\n").length end end end # end of J end # end if $startlinex if (t_x != 0) or (yshift < 0) # shift the line trx = sprintf('1 0 0 1 %.3f %.3f cm', t_x * @k, yshift * @k) setPageBuffer(startlinepage, pstart + "\nq\n" + trx + "\n" + pmid + "\nQ\n" + pend) endlinepos = (pstart + "\nq\n" + trx + "\n" + pmid + "\nQ\n").length # shift the annotations and links if !@page_annots[@page].nil? @page_annots[@page].each_with_index { |pac, pak| if pak >= pask @page_annots[@page][pak]['x'] += t_x @page_annots[@page][pak]['y'] -= yshift end } end @y -= yshift end end pbrk = checkPageBreak(@lasth) @y += @c_margin if pbrk and !dom[key]['tag'] and !empty_string(@thead) and !@in_thead ### fix ### @newline = false startlinex = @x startliney = @y if dom[dom[key]['parent']]['value'] == 'sup' startliney -= (0.3 * @font_size_pt) / @k elsif dom[dom[key]['parent']]['value'] == 'sub' startliney -= (@font_size_pt / 0.7) / @k else minstartliney = startliney maxbottomliney = startliney + @font_size * @cell_height_ratio end if startlinepage == @page and !endlinepos.nil? and !pbrk # fix startlinepos at page break case. startlinepos = endlinepos else startlinepage = @page if !@in_footer if !@footerlen[@page].nil? @footerpos[@page] = @pagelen[@page] - @footerlen[@page] else @footerpos[@page] = @pagelen[@page] end startlinepos = @footerpos[@page] else startlinepos = @pagelen[@page] end end endlinepos = nil plalign = lalign if !@page_annots[@page].nil? pask = @page_annots[@page].length else pask = 0 end SetFont(fontname, fontstyle, fontsize) if wfill == 1 SetFillColorArray(@bgcolor) end end # end newline if !opentagpos.nil? opentagpos = nil end if dom[key]['tag'] addHtmlAnchor(@html_anchor) if @html_anchor if dom[key]['opening'] # get text indentation (if any) if dom[key]['text-indent'] and dom[key]['block'] @textindent = dom[key]['text-indent'] @newline = true end if dom[key]['value'] == 'table' # available page width if @rtl wtmp = @x - @l_margin else wtmp = @w - @r_margin - @x end if cell or (dom[key]['attribute']['nested'] and (dom[key]['attribute']['nested'] == 'true')) # add margin for nested tables wtmp -= @c_margin end # table width if !dom[key]['width'].nil? table_width = getHTMLUnitToUnits(dom[key]['width'], wtmp, 'px') else table_width = wtmp end end if (dom[key]['value'] == 'td') or (dom[key]['value'] == 'th') trid = dom[key]['parent'] table_el = dom[trid]['parent'] if dom[table_el]['cols'].nil? dom[table_el]['cols'] = dom[trid]['cols'] end oldmargin = @c_margin currentcmargin = @c_margin if !dom[(dom[trid]['parent'])]['attribute']['cellpadding'].nil? currentcmargin = getHTMLUnitToUnits(dom[(dom[trid]['parent'])]['attribute']['cellpadding'], 1, 'px') end if currentcmargin < (@line_width / 2.0) currentcmargin = @line_width / 2.0 end @c_margin = currentcmargin if !dom[(dom[trid]['parent'])]['attribute']['cellspacing'].nil? cellspacing = getHTMLUnitToUnits(dom[(dom[trid]['parent'])]['attribute']['cellspacing'], 1, 'px') else cellspacing = 0 end if @rtl cellspacingx = -cellspacing else cellspacingx = cellspacing end colspan = dom[key]['attribute']['colspan'] table_columns_width = table_width - (cellspacing * (dom[table_el]['cols'] - 1)) wtmp = colspan * (table_columns_width / dom[table_el]['cols']) + (colspan - 1) * cellspacing if !dom[key]['width'].nil? cellw = getHTMLUnitToUnits(dom[key]['width'], table_columns_width, 'px') else cellw = wtmp end if !dom[key]['height'].nil? # minimum cell height cellh = getHTMLUnitToUnits(dom[key]['height'], 0, 'px') else cellh = 0 end if !dom[key]['content'].nil? cell_content = dom[key]['content'] else cell_content = ' ' end tagtype = dom[key]['value'] parentid = key while (key < maxel) and !(dom[key]['tag'] and !dom[key]['opening'] and (dom[key]['value'] == tagtype) and (dom[key]['parent'] == parentid)) # move :key index forward key += 1 end if dom[trid]['startpage'].nil? dom[trid]['startpage'] = @page else setPage(dom[trid]['startpage']) end if dom[trid]['starty'].nil? dom[trid]['starty'] = @y else @y = dom[trid]['starty'] end if dom[trid]['startx'].nil? dom[trid]['startx'] = @x else @x += (cellspacingx / 2.0) end if !dom[parentid]['attribute']['rowspan'].nil? rowspan = dom[parentid]['attribute']['rowspan'].to_i else rowspan = 1 end # skip row-spanned cells started on the previous rows if !dom[table_el]['rowspans'].nil? rsk = 0 rskmax = dom[table_el]['rowspans'].length while rsk < rskmax trwsp = dom[table_el]['rowspans'][rsk] rsstartx = trwsp['startx'] rsendx = trwsp['endx'] # account for margin changes if trwsp['startpage'] < @page if @rtl and (@pagedim[@page]['orm'] != @pagedim[trwsp['startpage']]['orm']) dl = @pagedim[@page]['orm'] - @pagedim[trwsp['startpage']]['orm'] rsstartx -= dl rsendx -= dl elsif !@rtl and (@pagedim[@page]['olm'] != @pagedim[trwsp['startpage']]['olm']) dl = @pagedim[@page]['olm'] - @pagedim[trwsp['startpage']]['olm'] rsstartx += dl rsendx += dl end end if (trwsp['rowspan'] > 0) and (rsstartx > @x - cellspacing - currentcmargin - @feps) and (rsstartx < @x + cellspacing + currentcmargin + @feps) and ((trwsp['starty'] < @y - @feps) or (trwsp['startpage'] < @page)) # set the starting X position of the current cell @x = rsendx + cellspacingx if (trwsp['rowspan'] == 1) and !dom[trid]['endy'].nil? and !dom[trid]['endpage'].nil? and (trwsp['endpage'] == dom[trid]['endpage']) # set ending Y position for row dom[table_el]['rowspans'][rsk]['endy'] = [dom[trid]['endy'], trwsp['endy']].max dom[trid]['endy'] = dom[table_el]['rowspans'][rsk]['endy'] end rsk = 0 else rsk += 1 end end end # add rowspan information to table element if rowspan > 1 dom[table_el]['rowspans'].push({'trid' => trid, 'rowspan' => rowspan, 'mrowspan' => rowspan, 'colspan' => colspan, 'startpage' => @page, 'startx' => @x, 'starty' => @y}) trsid = dom[table_el]['rowspans'].size end dom[trid]['cellpos'].push({'startx' => @x}) cellid = dom[trid]['cellpos'].size if rowspan > 1 dom[trid]['cellpos'][cellid - 1]['rowspanid'] = trsid - 1 end # push background colors if !dom[parentid]['bgcolor'].nil? and (dom[parentid]['bgcolor'].length > 0) dom[trid]['cellpos'][cellid - 1]['bgcolor'] = dom[parentid]['bgcolor'].dup end prevLastH = @lasth # ****** write the cell content ****** MultiCell(cellw, cellh, cell_content, 0, lalign, 0, 2, '', '', true, 0, true) @lasth = prevLastH @c_margin = oldmargin dom[trid]['cellpos'][cellid - 1]['endx'] = @x # update the end of row position if rowspan <= 1 if !dom[trid]['endy'].nil? if @page == dom[trid]['endpage'] dom[trid]['endy'] = [@y, dom[trid]['endy']].max elsif @page > dom[trid]['endpage'] dom[trid]['endy'] = @y end else dom[trid]['endy'] = @y end if !dom[trid]['endpage'].nil? dom[trid]['endpage'] = [@page, dom[trid]['endpage']].max else dom[trid]['endpage'] = @page end else # account for row-spanned cells dom[table_el]['rowspans'][trsid - 1]['endx'] = @x dom[table_el]['rowspans'][trsid - 1]['endy'] = @y dom[table_el]['rowspans'][trsid - 1]['endpage'] = @page end if !dom[table_el]['rowspans'].nil? # update endy and endpage on rowspanned cells dom[table_el]['rowspans'].each_with_index { |trwsp, k| if trwsp['rowspan'] > 0 if !dom[trid]['endpage'].nil? if trwsp['endpage'] == dom[trid]['endpage'] dom[table_el]['rowspans'][k]['endy'] = [dom[trid]['endy'], trwsp['endy']].max elsif trwsp['endpage'] < dom[trid]['endpage'] dom[table_el]['rowspans'][k]['endy'] = dom[trid]['endy'] dom[table_el]['rowspans'][k]['endpage'] = dom[trid]['endpage'] else dom[trid]['endy'] = @pagedim[dom[trid]['endpage']]['hk'] - @pagedim[dom[trid]['endpage']]['bm'] end end end } end @x += (cellspacingx / 2.0) else # opening tag (or self-closing tag) if opentagpos.nil? if !@in_footer if !@footerlen[@page].nil? @footerpos[@page] = @pagelen[@page] - @footerlen[@page] else @footerpos[@page] = @pagelen[@page] end opentagpos = @footerpos[@page] end end dom = openHTMLTagHandler(dom, key, cell) end else # closing tag prev_numpages = @numpages dom = closeHTMLTagHandler(dom, key, cell, maxbottomliney) if prev_numpages > @numpages startlinepage = @page end end elsif dom[key]['value'].strip.length > 0 # print list-item if !empty_string(@lispacer) SetFont(pfontname, pfontstyle, pfontsize) @lasth = @font_size * @cell_height_ratio minstartliney = @y maxbottomliney = startliney + @font_size * @cell_height_ratio putHtmlListBullet(@listnum, @lispacer, pfontsize) SetFont(curfontname, curfontstyle, curfontsize) @lasth = @font_size * @cell_height_ratio if pfontsize.is_a?(Numeric) and (pfontsize > 0) and curfontsize.is_a?(Numeric) and (curfontsize > 0) and (pfontsize != curfontsize) pfontascent = getFontAscent(pfontname, pfontstyle, pfontsize) pfontdescent = getFontDescent(pfontname, pfontstyle, pfontsize) @y += ((pfontsize - curfontsize) * @cell_height_ratio / @k + pfontascent - curfontascent - pfontdescent + curfontdescent) / 2.0 minstartliney = [@y, minstartliney].min maxbottomliney = [@y + pfontsize * @cell_height_ratio / @k, maxbottomliney].max end end # text @htmlvspace = 0 if !@premode and isRTLTextDir() # reverse spaces order len1 = dom[key]['value'].length lsp = len1 - dom[key]['value'].lstrip.length rsp = len1 - dom[key]['value'].rstrip.length tmpstr = '' if rsp > 0 tmpstr << dom[key]['value'][-rsp..-1] end tmpstr << (dom[key]['value']).strip if lsp > 0 tmpstr << dom[key]['value'][0, lsp] end dom[key]['value'] = tmpstr end if newline if !@premode prelen = dom[key]['value'].length if isRTLTextDir() dom[key]['value'] = dom[key]['value'].rstrip + 0.chr else dom[key]['value'] = dom[key]['value'].lstrip end postlen = dom[key]['value'].length if (postlen == 0) and (prelen > 0) dom[key]['trimmed_space'] = true end end newline = false firstblock = true else firstblock = false end strrest = '' if @rtl @x -= @textindent else @x += @textindent end if !@href.empty? and @href['url'] # HTML <a> Link hrefcolor = '' if dom[(dom[key]['parent'])]['fgcolor'] and !dom[(dom[key]['parent'])]['fgcolor'].empty? hrefcolor = dom[(dom[key]['parent'])]['fgcolor'] end hrefstyle = -1 if dom[(dom[key]['parent'])]['fontstyle'] and (dom[(dom[key]['parent'])]['fontstyle'] != false) hrefstyle = dom[(dom[key]['parent'])]['fontstyle'] end strrest = addHtmlLink(@href['url'], dom[key]['value'], wfill, true, hrefcolor, hrefstyle, true) else # ****** write only until the end of the line and get the rest ****** strrest = Write(@lasth, dom[key]['value'], '', wfill, '', false, 0, true, firstblock, 0) end @textindent = 0 if !strrest.nil? and strrest.length > 0 # store the remaining string on the previous :key position @newline = true if cell if @rtl @x -= @c_margin else @x += @c_margin end end if strrest == dom[key]['value'] # used to avoid infinite loop loop += 1 else loop = 0 end if !@href.empty? and @href['url'] dom[key]['value'] = strrest.strip elsif @premode dom[key]['value'] = strrest elsif isRTLTextDir() dom[key]['value'] = strrest.rstrip else dom[key]['value'] = strrest.lstrip end if loop < 3 key -= 1 end else loop = 0 end end key += 1 if dom[key] and dom[key]['tag'] and (dom[key]['opening'].nil? or !dom[key]['opening']) and dom[(dom[key]['parent'])]['attribute']['nobr'] and (dom[(dom[key]['parent'])]['attribute']['nobr'] == 'true') if !undo and (@start_transaction_page == (@numpages - 1)) or (@y < @start_transaction_y) # restore previous object rollbackTransaction(true) # restore previous values this_method_vars.each {|vkey , vval| eval("#{vkey} = vval") } # add a page (or trig AcceptPageBreak() for multicolumn mode) pre_y = @y if !checkPageBreak(@page_break_trigger + 1) and (@y < pre_y) startliney = @y end undo = true # avoid infinite loop else undo = false end end end # end for each :key # align the last line if !startlinex.nil? yshift = minstartliney - startliney if (yshift > 0) or (@page > startlinepage) yshift = 0 end t_x = 0 # the last line must be shifted to be aligned as requested linew = (@endlinex - startlinex).abs pstart = getPageBuffer(startlinepage)[0, startlinepos] if !opentagpos.nil? and !@footerlen[startlinepage].nil? and !@in_footer @footerpos[startlinepage] = @pagelen[startlinepage] - @footerlen[startlinepage] midpos = [opentagpos, @footerpos[startlinepage]].min elsif !opentagpos.nil? midpos = opentagpos elsif !@footerlen[startlinepage].nil? and !@in_footer @footerpos[startlinepage] = @pagelen[startlinepage] - @footerlen[startlinepage] midpos = @footerpos[startlinepage] else midpos = 0 end if midpos > 0 pmid = getPageBuffer(startlinepage)[startlinepos, midpos - startlinepos] pend = getPageBuffer(startlinepage)[midpos..-1] else pmid = getPageBuffer(startlinepage)[startlinepos..-1] pend = "" end if (!plalign.nil? and (((plalign == 'C') or ((plalign == 'R') and !@rtl) or ((plalign == 'L') and @rtl)))) or (yshift < 0) # calculate shifting amount tw = w if @l_margin != prevlMargin tw += prevlMargin - @l_margin end if @r_margin != prevrMargin tw += prevrMargin - @r_margin end one_space_width = GetStringWidth(32.chr) mdiff = (tw - linew).abs if plalign == 'C' if @rtl t_x = -(mdiff / 2.0) else t_x = (mdiff / 2.0) end elsif (plalign == 'R') and !@rtl # right alignment on LTR document if revstrpos(pmid, ')]').to_i == revstrpos(pmid, ' )]').to_i + 1 # remove last space (if any) linew -= one_space_width mdiff = (tw - linew).abs end t_x = mdiff elsif (plalign == 'L') and @rtl # left alignment on RTL document if revstrpos(pmid, '[(') and ((revstrpos(pmid, '[( ').to_i == revstrpos(pmid, '[(').to_i) or (revstrpos(pmid, '[(' + 0.chr + 32.chr).to_i == revstrpos(pmid, '[(').to_i)) # remove first space (if any) linew -= one_space_width end if pmid.index('[(') and (pmid.index('[(').to_i == revstrpos(pmid, '[(').to_i) # remove last space (if any) linew -= one_space_width if (@current_font['type'] == 'TrueTypeUnicode') or (@current_font['type'] == 'cidfont0') linew -= one_space_width end end mdiff = (tw - linew).abs t_x = -mdiff end end # end if startlinex if (t_x != 0) or (yshift < 0) # shift the line trx = sprintf('1 0 0 1 %.3f %.3f cm', t_x * @k, yshift * @k) setPageBuffer(startlinepage, pstart + "\nq\n" + trx + "\n" + pmid + "\nQ\n" + pend) endlinepos = (pstart + "\nq\n" + trx + "\n" + pmid + "\nQ\n").length # shift the annotations and links if !@page_annots[@page].nil? @page_annots[@page].each_with_index { |pac, pak| if pak >= pask @page_annots[@page][pak]['x'] += t_x @page_annots[@page][pak]['y'] -= yshift end } end @y -= yshift end end if ln and !(cell and (dom[key-1]['value'] == 'table')) Ln(@lasth) if @y < maxbottomliney @y = maxbottomliney end end # restore previous values setGraphicVars(gvars) if @page > prevPage @l_margin = @pagedim[@page]['olm'] @r_margin = @pagedim[@page]['orm'] end # restore previous list state @cell_height_ratio = prev_cell_height_ratio @listnum = prev_listnum @listordered = prev_listordered @listcount = prev_listcount @lispacer = prev_lispacer dom = nil rescue => err Error('writeHTML Error.', err) end alias_method :write_html, :writeHTML # # Process opening tags. # [@param array :dom] html dom array # [@param int :key] current element id # [@param boolean :cell] if true add the default c_margin space to each new line (default false). # [@access protected] # def openHTMLTagHandler(dom, key, cell) tag = dom[key] parent = dom[(dom[key]['parent'])] firstorlast = (key == 1) # check for text direction attribute if !tag['attribute']['dir'].nil? setTempRTL(tag['attribute']['dir']) else @tmprtl = false end if tag['block'] hbz = 0 # distance from y to line bottom hb = 0 # vertical space between block tags # calculate vertical space for block tags if @tagvspaces[tag['value']] and @tagvspaces[tag['value']][0] and @tagvspaces[tag['value']][0]['h'] and (@tagvspaces[tag['value']][0]['h'] >= 0) cur_h = @tagvspaces[tag['value']][0]['h'] elsif !tag['fontsize'].nil? cur_h = (tag['fontsize'] / @k) * @cell_height_ratio else cur_h = @font_size * @cell_height_ratio end if @tagvspaces[tag['value']] and @tagvspaces[tag['value']][0] and @tagvspaces[tag['value']][0]['n'] n = @tagvspaces[tag['value']][0]['n'] elsif tag['value'] =~ /[h][0-9]/ n = 0.6 else n = 1 end hb = n * cur_h if (@htmlvspace <= 0) and (n > 0) if parent['fontsize'] hbz = (parent['fontsize'] / @k) * @cell_height_ratio else hbz = @font_size * @cell_height_ratio end end end #Opening tag case tag['value'] when 'table' cp = 0 cs = 0 dom[key]['rowspans'] = [] if dom[key]['attribute']['nested'].nil? or (dom[key]['attribute']['nested'] != 'true') # set table header if !empty_string(dom[key]['thead']) # set table header @thead = dom[key]['thead'] if @thead_margins.nil? or @thead_margins.empty? @thead_margins ||= {} if dom[key]['attribute']['cellpadding'] @thead_margins['cmargin'] = getHTMLUnitToUnits(dom[key]['attribute']['cellpadding'], 1, 'px') else @thead_margins['cmargin'] = @c_margin end if @thead_margins['cmargin'] < (@line_width / 2.0) @thead_margins['cmargin'] = @line_width / 2.0 end if cell @thead_margins['lmargin'] = @l_margin + @c_margin @thead_margins['rmargin'] = @r_margin + @c_margin else @thead_margins['lmargin'] = @l_margin @thead_margins['rmargin'] = @r_margin end end end end if !tag['attribute']['cellpadding'].nil? cp = getHTMLUnitToUnits(tag['attribute']['cellpadding'], 1, 'px') if cp < (@line_width / 2.0) cp = @line_width / 2.0 end @old_c_margin = @c_margin @c_margin = cp end if !tag['attribute']['cellspacing'].nil? cs = getHTMLUnitToUnits(tag['attribute']['cellspacing'], 1, 'px') end if checkPageBreak(((2 * cp) + (2 * cs) + @lasth), '', false) @in_thead = true # add a page (or trig AcceptPageBreak() for multicolumn mode) checkPageBreak(@page_break_trigger + 1) end when 'tr' # array of columns positions dom[key]['cellpos'] = [] when 'hr' if !tag['height'].nil? and (tag['height'] != '') hrHeight = getHTMLUnitToUnits(tag['height'], 1, 'px') else hrHeight = GetLineWidth() end addHTMLVertSpace(hbz, (hrHeight / 2.0), cell, firstorlast) x = GetX() y = GetY() wtmp = @w - @l_margin - @r_margin if cell wtmp -= 2 * @c_margin end if tag['width'] and (tag['width'] != '') hrWidth = getHTMLUnitToUnits(tag['width'], wtmp, 'px') else hrWidth = wtmp end prevlinewidth = GetLineWidth() SetLineWidth(hrHeight) Line(x, y, x + hrWidth, y) SetLineWidth(prevlinewidth) Ln('', cell) addHTMLVertSpace(hrHeight / 2.0, 0, cell, dom[key + 1].nil?) when 'a' if tag['attribute'].key?('href') @href['url'] = get_sever_url(tag['attribute']['href']) elsif tag['attribute'].key?('id') || tag['attribute'].key?('name') @html_anchor = tag['attribute']['id'] || tag['attribute']['name'] end when 'img' if !tag['attribute']['src'].nil? # replace relative path with real server path ### T.B.D ### TCPDF 5.0.000 ### #if tag['attribute']['src'][0] == '/' # findroot = tag['attribute']['src'].index($_SERVER['DOCUMENT_ROOT']) # if (findroot == nil) or (findroot.to_i > 1) # tag['attribute']['src'] = Rails.root.join('public') + tag['attribute']['src'] # end #end ### T.B.D ### TCPDF 5.0.000 ### tag['attribute']['src'].gsub!(/%([0-9a-fA-F]{2})/){$1.hex.chr} img_name = tag['attribute']['src'] type = getImageFileType(tag['attribute']['src']) tag['attribute']['src'] = get_image_filename(tag['attribute']['src']) if tag['width'].nil? tag['width'] = 0 end if tag['height'].nil? tag['height'] = 0 end #if tag['attribute']['align'].nil? # the only alignment supported is "bottom" # further development is required for other modes. tag['attribute']['align'] = 'bottom' #end case tag['attribute']['align'] when 'top' align = 'T' when 'middle' align = 'M' when 'bottom' align = 'B' else align = 'B' end prevy = @y xpos = @x # eliminate marker spaces if !dom[key - 1].nil? if (dom[key - 1]['value'] == ' ') or !dom[key - 1]['trimmed_space'].nil? xpos -= GetStringWidth(32.chr) elsif @rtl and (dom[key - 1]['value'] == ' ') xpos += 2 * GetStringWidth(32.chr) end end imglink = '' if !@href['url'].nil? and !empty_string(@href['url']) imglink = @href['url'] if imglink[0, 1] == '#' # convert url to internal link page = imglink.sub(/^#/, "").to_i imglink = AddLink() SetLink(imglink, 0, page) end end border = 0 if !tag['attribute']['border'].nil? and !tag['attribute']['border'].empty? # currently only support 1 (frame) or a combination of 'LTRB' border = tag['attribute']['border'] case tag['attribute']['border'] when '0' border = 0 when '1' border = 1 else border = tag['attribute']['border'] end end iw = 0 if tag['width'] iw = getHTMLUnitToUnits(tag['width'], 1, 'px', false) end ih = 0 if tag['height'] ih = getHTMLUnitToUnits(tag['height'], 1, 'px', false) end # store original margin values l_margin = @l_margin r_margin = @r_margin SetLeftMargin(@l_margin + @c_margin) SetRightMargin(@r_margin + @c_margin) begin if tag['attribute']['src'] =~ /^http/ tmpFile = get_image_file(tag['attribute']['src']) img_file = tmpFile.path else img_file = tag['attribute']['src'] end # if (type == 'eps') or (type == 'ai') # ImageEps(img_file, xpos, @y, iw, ih, imglink, true, align, '', border, true) # elsif type == 'svg' # ImageSVG(img_file, xpos, @y, iw, ih, imglink, align, '', border, true) # else result_img = Image(img_file, xpos, @y, iw, ih, '', imglink, align, false, 300, '', false, false, border, false, false, true) # end rescue => err logger.error "pdf: Image: error: #{err.message}" result_img = false ensure # remove temp files tmpFile.close(true) unless tmpFile.nil? end if result_img or ih != 0 case align when 'T' @y = prevy when 'M' @y = (@img_rb_y + prevy - (tag['fontsize'] / @k)) / 2 when 'B' @y = @img_rb_y - (tag['fontsize'] / @k) end else @y = prevy end # restore original margin values SetLeftMargin(l_margin) SetRightMargin(r_margin) if result_img == false Write(@lasth, File::basename(img_name) + ' ', '', false, '', false, 0, false) unless img_name.nil? end end when 'dl' @listnum += 1 if @listnum == 1 addHTMLVertSpace(hbz, hb, cell, firstorlast) else addHTMLVertSpace(0, 0, cell, firstorlast) end when 'dt' Ln('', cell) addHTMLVertSpace(hbz, 0, cell, firstorlast) when 'dd' if @rtl @r_margin += @listindent else @l_margin += @listindent end @listindentlevel += 1 addHTMLVertSpace(hbz, 0, cell, firstorlast) when 'ul', 'ol' @listnum += 1 if tag['value'] == "ol" @listordered[@listnum] = true else @listordered[@listnum] = false end if !tag['attribute']['start'].nil? @listcount[@listnum] = tag['attribute']['start'].to_i - 1 else @listcount[@listnum] = 0 end if @rtl @r_margin += @listindent @x -= @listindent else @l_margin += @listindent @x += @listindent end @listindentlevel += 1 if @listnum == 1 addHTMLVertSpace(hbz, hb, cell, firstorlast) else addHTMLVertSpace(0, 0, cell, firstorlast) end when 'li' addHTMLVertSpace(hbz, 0, cell, firstorlast) if @listordered[@listnum] # ordered item if !empty_string(parent['attribute']['type']) @lispacer = parent['attribute']['type'] elsif !empty_string(parent['listtype']) @lispacer = parent['listtype'] elsif !empty_string(@lisymbol) @lispacer = @lisymbol else @lispacer = '#' end @listcount[@listnum] += 1 if !tag['attribute']['value'].nil? @listcount[@listnum] = tag['attribute']['value'].to_i end else # unordered item if !empty_string(parent['attribute']['type']) @lispacer = parent['attribute']['type'] elsif !empty_string(parent['listtype']) @lispacer = parent['listtype'] elsif !empty_string(@lisymbol) @lispacer = @lisymbol else @lispacer = '!' end end when 'blockquote' if @rtl @r_margin += @listindent else @l_margin += @listindent end @listindentlevel += 1 addHTMLVertSpace(hbz, hb, cell, firstorlast) when 'br' addHTMLVertSpace(hbz, 0, cell, firstorlast) when 'div' addHTMLVertSpace(hbz, 0, cell, firstorlast) when 'p' addHTMLVertSpace(hbz, hb, cell, firstorlast) when 'pre' addHTMLVertSpace(hbz, hb, cell, firstorlast) @premode = true when 'sup' SetXY(GetX(), GetY() - ((0.7 * @font_size_pt) / @k)) when 'sub' SetXY(GetX(), GetY() + ((0.3 * @font_size_pt) / @k)) when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' addHTMLVertSpace(hbz, hb, cell, firstorlast) end if dom[key]['self'] and dom[key]['attribute']['pagebreakafter'] pba = dom[key]['attribute']['pagebreakafter'] # check for pagebreak if (pba == 'true') or (pba == 'left') or (pba == 'right') # add a page (or trig AcceptPageBreak() for multicolumn mode) checkPageBreak(@page_break_trigger + 1) end if ((pba == 'left') and ((!@rtl and (@page % 2 == 0)) or (@rtl and (@page % 2 != 0)))) or ((pba == 'right') and ((!@rtl and (@page % 2 != 0)) or (@rtl and (@page % 2 == 0)))) # add a page (or trig AcceptPageBreak() for multicolumn mode) checkPageBreak(@page_break_trigger + 1) end end dom end protected :openHTMLTagHandler # # Process closing tags. # [@param array :dom] html dom array # [@param int :key] current element id # [@param boolean :cell] if true add the default c_margin space to each new line (default false). # [@param int :maxbottomliney] maximum y value of current line # [@access protected] # def closeHTMLTagHandler(dom, key, cell, maxbottomliney=0) tag = dom[key].dup parent = dom[(dom[key]['parent'])].dup firstorlast = dom[key + 1].nil? or (dom[key + 2].nil? and (dom[key + 1]['value'] == 'marker')) in_table_head = false if tag['block'] hbz = 0 # distance from y to line bottom hb = 0 # vertical space between block tags # calculate vertical space for block tags if @tagvspaces[tag['value']] and @tagvspaces[tag['value']][1] and @tagvspaces[tag['value']][1]['h'] and (@tagvspaces[tag['value']][1]['h'] >= 0) pre_h = @tagvspaces[tag['value']][1]['h'] elsif parent['fontsize'] pre_h = (parent['fontsize'] / @k) * @cell_height_ratio else pre_h = @font_size * @cell_height_ratio end if @tagvspaces[tag['value']] and @tagvspaces[tag['value']][1] and @tagvspaces[tag['value']][1]['n'] n = @tagvspaces[tag['value']][1]['n'] elsif tag['value'] =~ /[h][0-9]/ n = 0.6 else n = 1 end hb = n * pre_h if @y < maxbottomliney hbz = maxbottomliney - @y end end # Closing tag case (tag['value']) when 'tr' table_el = dom[(dom[key]['parent'])]['parent'] if parent['endy'].nil? dom[(dom[key]['parent'])]['endy'] = @y parent['endy'] = @y end if parent['endpage'].nil? dom[(dom[key]['parent'])]['endpage'] = @page parent['endpage'] = @page end # update row-spanned cells if !dom[table_el]['rowspans'].nil? dom[table_el]['rowspans'].each_with_index { |trwsp, k| dom[table_el]['rowspans'][k]['rowspan'] -= 1 if dom[table_el]['rowspans'][k]['rowspan'] == 0 if dom[table_el]['rowspans'][k]['endpage'] == parent['endpage'] dom[(dom[key]['parent'])]['endy'] = [dom[table_el]['rowspans'][k]['endy'], parent['endy']].max elsif dom[table_el]['rowspans'][k]['endpage'] > parent['endpage'] dom[(dom[key]['parent'])]['endy'] = dom[table_el]['rowspans'][k]['endy'] dom[(dom[key]['parent'])]['endpage'] = dom[table_el]['rowspans'][k]['endpage'] end end } # report new endy and endpage to the rowspanned cells dom[table_el]['rowspans'].each_with_index { |trwsp, k| if dom[table_el]['rowspans'][k]['rowspan'] == 0 dom[table_el]['rowspans'][k]['endpage'] = [dom[table_el]['rowspans'][k]['endpage'], dom[(dom[key]['parent'])]['endpage']].max dom[(dom[key]['parent'])]['endpage'] = dom[table_el]['rowspans'][k]['endpage'] dom[table_el]['rowspans'][k]['endy'] = [dom[table_el]['rowspans'][k]['endy'], dom[(dom[key]['parent'])]['endy']].max dom[(dom[key]['parent'])]['endy'] = dom[table_el]['rowspans'][k]['endy'] end } # update remaining rowspanned cells dom[table_el]['rowspans'].each_with_index { |trwsp, k| if dom[table_el]['rowspans'][k]['rowspan'] == 0 dom[table_el]['rowspans'][k]['endpage'] = dom[(dom[key]['parent'])]['endpage'] dom[table_el]['rowspans'][k]['endy'] = dom[(dom[key]['parent'])]['endy'] end } end if (@num_columns > 1) and (dom[(dom[key]['parent'])]['endy'] >= (@page_break_trigger - @lasth)) and (@y < dom[(dom[key]['parent'])]['endy']) Ln(0, cell) else setPage(dom[(dom[key]['parent'])]['endpage']); @y = dom[(dom[key]['parent'])]['endy'] if !dom[table_el]['attribute']['cellspacing'].nil? cellspacing = getHTMLUnitToUnits(dom[table_el]['attribute']['cellspacing'], 1, 'px') @y += cellspacing end Ln(0, cell) @x = parent['startx'] # account for booklet mode if parent['startpage'] and @page > parent['startpage'] if @rtl and (@pagedim[@page]['orm'] != @pagedim[parent['startpage']]['orm']) @x -= @pagedim[@page]['orm'] - @pagedim[parent['startpage']]['orm'] elsif !@rtl and (@pagedim[@page]['olm'] != @pagedim[parent['startpage']]['olm']) @x += @pagedim[@page]['olm'] - @pagedim[parent['startpage']]['olm'] end end end when 'table' if dom[(dom[key]['parent'])]['attribute']['tablehead'] and dom[(dom[key]['parent'])]['attribute']['tablehead'] == "1" # closing tag used for the thead part in_table_head = true @in_thead = false end table_el = parent # draw borders if (!table_el['attribute']['border'].nil? and (table_el['attribute']['border'].to_i > 0)) or (!table_el['style'].nil? and !table_el['style']['border'].nil? and (table_el['style']['border'].to_i > 0)) border = 1 else border = 0 end startpage = 0 end_page = 0 # fix bottom line alignment of last line before page break dom[(dom[key]['parent'])]['trids'].each_with_index { |trkey, j| # update row-spanned cells if !dom[(dom[key]['parent'])]['rowspans'].nil? dom[(dom[key]['parent'])]['rowspans'].each_with_index { |trwsp, k| if trwsp['trid'] == trkey dom[(dom[key]['parent'])]['rowspans'][k]['mrowspan'] -= 1 end if defined?(prevtrkey) and (trwsp['trid'] == prevtrkey) and (trwsp['mrowspan'] >= 0) dom[(dom[key]['parent'])]['rowspans'][k]['trid'] = trkey end } end if defined?(prevtrkey) and (dom[trkey]['startpage'] > dom[prevtrkey]['endpage']) pgendy = @pagedim[dom[prevtrkey]['endpage']]['hk'] - @pagedim[dom[prevtrkey]['endpage']]['bm'] dom[prevtrkey]['endy'] = pgendy # update row-spanned cells if !dom[(dom[key]['parent'])]['rowspans'].nil? dom[(dom[key]['parent'])]['rowspans'].each_with_index { |trwsp, k| if (trwsp['trid'] == trkey) and (trwsp['mrowspan'] > 1) and (trwsp['endpage'] == dom[prevtrkey]['endpage']) dom[(dom[key]['parent'])]['rowspans'][k]['endy'] = pgendy dom[(dom[key]['parent'])]['rowspans'][k]['mrowspan'] = -1 end } end end prevtrkey = trkey table_el = dom[(dom[key]['parent'])].dup } # for each row table_el['trids'].each_with_index { |trkey, j| parent = dom[trkey] # for each cell on the row parent['cellpos'].each_with_index { |cellpos, k| if !cellpos['rowspanid'].nil? and (cellpos['rowspanid'] >= 0) cellpos['startx'] = table_el['rowspans'][(cellpos['rowspanid'])]['startx'] cellpos['endx'] = table_el['rowspans'][(cellpos['rowspanid'])]['endx'] endy = table_el['rowspans'][(cellpos['rowspanid'])]['endy'] startpage = table_el['rowspans'][(cellpos['rowspanid'])]['startpage'] end_page = table_el['rowspans'][(cellpos['rowspanid'])]['endpage'] else endy = parent['endy'] startpage = parent['startpage'] end_page = parent['endpage'] end cellpos['startx'] ||= 0 if end_page > startpage # design borders around HTML cells. startpage.upto(end_page) do |page| setPage(page) if page == startpage @y = parent['starty'] # put cursor at the beginning of row on the first page ch = getPageHeight() - parent['starty'] - getBreakMargin() cborder = getBorderMode(border, position='start') elsif page == end_page @y = @t_margin # put cursor at the beginning of last page ch = endy - @t_margin cborder = getBorderMode(border, position='end') else @y = @t_margin # put cursor at the beginning of the current page ch = getPageHeight() - @t_margin - getBreakMargin() cborder = getBorderMode(border, position='middle') end if !cellpos['bgcolor'].nil? and (cellpos['bgcolor'] != false) SetFillColorArray(cellpos['bgcolor']) fill = 1 else fill = 0 end cw = (cellpos['endx'] - cellpos['startx']).abs @x = cellpos['startx'] # account for margin changes if page > startpage if @rtl and (@pagedim[page]['orm'] != @pagedim[startpage]['orm']) @x -= @pagedim[page]['orm'] - @pagedim[startpage]['orm'] elsif !@rtl and (@pagedim[page]['lm'] != @pagedim[startpage]['olm']) @x += @pagedim[page]['olm'] - @pagedim[startpage]['olm'] end end prevLastH = @lasth # design a cell around the text ccode = @fill_color + "\n" + getCellCode(cw, ch, '', cborder, 1, '', fill, '', 0, true) @lasth = prevLastH if (cborder != 0) or (fill == 1) pagebuff = getPageBuffer(@page) pstart = pagebuff[0, @intmrk[@page]] pend = pagebuff[@intmrk[@page]..-1] setPageBuffer(@page, pstart + ccode + "\n" + pend) @intmrk[@page] += (ccode + "\n").length end end else setPage(startpage) if !cellpos['bgcolor'].nil? and (cellpos['bgcolor'] != false) SetFillColorArray(cellpos['bgcolor']) fill = 1 else fill = 0 end @x = cellpos['startx'] @y = parent['starty'] cw = (cellpos['endx'] - cellpos['startx']).abs ch = endy - parent['starty'] prevLastH = @lasth # design a cell around the text ccode = @fill_color + "\n" + getCellCode(cw, ch, '', border, 1, '', fill, '', 0, true) @lasth = prevLastH if (border != 0) or (fill == 1) if !@transfmrk[@page].nil? pagemark = @transfmrk[@page] @transfmrk[@page] += (ccode + "\n").length elsif @in_footer pagemark = @footerpos[@page] @footerpos[@page] += (ccode + "\n").length else pagemark = @intmrk[@page] @intmrk[@page] += (ccode + "\n").length end pagebuff = getPageBuffer(@page) pstart = pagebuff[0, pagemark] pend = pagebuff[pagemark..-1] setPageBuffer(@page, pstart + ccode + "\n" + pend) end end } if !table_el['attribute']['cellspacing'].nil? cellspacing = getHTMLUnitToUnits(table_el['attribute']['cellspacing'], 1, 'px') @y += cellspacing end Ln(0, cell) @x = parent['startx'] if end_page > startpage if @rtl and (@pagedim[end_page]['orm'] != @pagedim[startpage]['orm']) @x += @pagedim[end_page]['orm'] - @pagedim[startpage]['orm'] elsif !@rtl and (@pagedim[end_page]['olm'] != @pagedim[startpage]['olm']) @x += @pagedim[end_page]['olm'] - @pagedim[startpage]['olm'] end end } if !in_table_head # we are not inside a thead section if dom[(parent['parent'])]['attribute']['cellpadding'] ### fix ### @c_margin = @old_c_margin end @lasth = @font_size * @cell_height_ratio if (@page == @numpages - 1) and @pageopen[@numpages] # remove last blank page deletePage(@numpages) end if !@thead_margins['top'].nil? # restore top margin @t_margin = @thead_margins['top'] @pagedim[@page]['tm'] = @t_margin end if table_el['attribute']['nested'].nil? or (table_el['attribute']['nested'] != 'true') # reset main table header @thead = '' @thead_margins = {} end end if tag['block'] unless dom[(dom[key]['parent'])]['attribute']['tablehead'] and dom[(dom[key]['parent'])]['attribute']['tablehead'] == "1" ### fix ### addHTMLVertSpace(hbz / 2, 0, cell, (dom[key+1].nil? or (dom[key+1]['value'] != 'table'))) ### fix ### end end when 'a' @href = {} @html_anchor = nil when 'sup' SetXY(GetX(), GetY() + (0.7 * parent['fontsize'] / @k)) when 'sub' SetXY(GetX(), GetY() - (0.3 * parent['fontsize'] / @k)) when 'div' addHTMLVertSpace(hbz, 0, cell, firstorlast) when 'blockquote' if @rtl @r_margin -= @listindent else @l_margin -= @listindent end @listindentlevel -= 1 addHTMLVertSpace(hbz, hb, cell, firstorlast) when 'p' addHTMLVertSpace(hbz, hb, cell, firstorlast) when 'pre' addHTMLVertSpace(hbz, hb, cell, firstorlast) @premode = false when 'dl' @listnum -= 1 if @listnum <= 0 @listnum = 0 addHTMLVertSpace(hbz, hb, cell, firstorlast) else addHTMLVertSpace(0, 0, cell, firstorlast) end @lasth = @font_size * @cell_height_ratio when 'dt' @lispacer = '' addHTMLVertSpace(0, 0, cell, firstorlast) when 'dd' @lispacer = '' if @rtl @r_margin -= @listindent else @l_margin -= @listindent end @listindentlevel -= 1 addHTMLVertSpace(0, 0, cell, firstorlast) when 'ul', 'ol' @listnum -= 1 @lispacer = '' if @rtl @r_margin -= @listindent else @l_margin -= @listindent end @listindentlevel -= 1 if @listnum <= 0 @listnum = 0 addHTMLVertSpace(hbz, hb, cell, firstorlast) else addHTMLVertSpace(0, 0, cell, firstorlast) end @lasth = @font_size * @cell_height_ratio when 'li' @lispacer = '' addHTMLVertSpace(0, 0, cell, firstorlast) when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' addHTMLVertSpace(hbz, hb, cell, firstorlast) end if dom[(dom[key]['parent'])]['attribute']['pagebreakafter'] pba = dom[(dom[key]['parent'])]['attribute']['pagebreakafter'] # check for pagebreak if (pba == 'true') or (pba == 'left') or (pba == 'right') # add a page (or trig AcceptPageBreak() for multicolumn mode) checkPageBreak(@page_break_trigger + 1) end if ((pba == 'left') and ((!@rtl and (@page % 2 == 0)) or (@rtl and (@page % 2 != 0)))) or ((pba == 'right') and ((!@rtl and (@page % 2 != 0)) or (@rtl and (@page % 2 == 0)))) # add a page (or trig AcceptPageBreak() for multicolumn mode) checkPageBreak(@page_break_trigger + 1) end end @tmprtl = false dom end protected :closeHTMLTagHandler # # Add vertical spaces if needed. # [@param string :hbz] Distance between current y and line bottom. # [@param string :hb] The height of the break. # [@param boolean :cell] if true add the default cMargin space to each new line (default false). # [@param boolean :firstorlast] if true do not print additional empty lines. # [@access protected] # def addHTMLVertSpace(hbz=0, hb=0, cell=false, firstorlast=false) if firstorlast Ln(0, cell) @htmlvspace = 0 return end if hb < @htmlvspace hd = 0 else hd = hb - @htmlvspace @htmlvspace = hb end Ln(hbz + hd, cell) end protected :addHTMLVertSpace # # Set the booklet mode for double-sided pages. # [@param boolean :booklet] true set the booklet mode on, fals eotherwise. # [@param float :inner] Inner page margin. # [@param float :outer] Outer page margin. # [@access public] # [@since 4.2.000 (2008-10-29)] # def SetBooklet(booklet=true, inner=-1, outer=-1) @booklet = booklet if inner >= 0 @l_margin = inner end if outer >= 0 @r_margin = outer end end alias_method :set_booklet, :SetBooklet # # Swap the left and right margins. # [@param boolean :reverse] if true swap left and right margins. # [@access protected] # [@since 4.2.000 (2008-10-29)] # def swapMargins(reverse=true) if reverse # swap left and right margins mtemp = @original_l_margin @original_l_margin = @original_r_margin @original_r_margin = mtemp deltam = @original_l_margin - @original_r_margin @l_margin += deltam @r_margin -= deltam end end protected :swapMargins # # Set the vertical spaces for HTML tags. # The array must have the following structure (example): # :tagvs = {'h1' => [{'h' => '', 'n' => 2}, {'h' => 1.3, 'n' => 1}]} # The first array level contains the tag names, # the second level contains 0 for opening tags or 1 for closing tags, # the third level contains the vertical space unit (h) and the number spaces to add (n). # If the h parameter is not specified, default values are used. # [@param array :tagvs] array of tags and relative vertical spaces. # [@access public] # [@since 4.2.001 (2008-10-30)] # def setHtmlVSpace(tagvs) @tagvspaces = tagvs end alias_method :set_html_v_space, :setHtmlVSpace # # convert HTML string containing value and unit of measure to user's units or points. # [@param string :htmlval] string containing values and unit # [@param string :refsize] reference value in points # [@param string :defaultunit] default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt). # [@param boolean :point] if true returns points, otherwise returns value in user's units # [@return float] value in user's unit or point if :points=true # [@access public] # [@since 4.4.004 (2008-12-10)] # def getHTMLUnitToUnits(htmlval, refsize=1, defaultunit='px', points=false) supportedunits = ['%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt'] retval = 0 value = 0 unit = 'px' k = @k if points k = 1 end if supportedunits.include?(defaultunit) unit = defaultunit end if htmlval.is_a?(Numeric) value = htmlval.to_f else mnum = htmlval.scan(/[0-9\.\-\+]+/) unless mnum.empty? value = mnum[0].to_f munit = htmlval.scan(/[a-z%]+/) unless munit.empty? if supportedunits.include?(munit[0]) unit = munit[0] end end end end case unit when '%' # percentage retval = (value * refsize) / 100.0 when 'em' # relative-size retval = value * refsize when 'ex' # height of lower case 'x' (about half the font-size) retval = value * (refsize / 2.0) when 'in' # absolute-size retval = (value * @dpi) / k when 'cm' # centimeters retval = (value / 2.54 * @dpi) / k when 'mm' # millimeters retval = (value / 25.4 * @dpi) / k when 'pc' # one pica is 12 points retval = (value * 12) / k when 'pt' # points retval = value / k when 'px' # pixels retval = pixelsToUnits(value) end return retval end alias_method :get_html_unit_to_units, :getHTMLUnitToUnits # # Returns the Roman representation of an integer number # [@param int :number] to convert # [@return string] roman representation of the specified number # [@access public] # [@since 4.4.004 (2008-12-10)] # def intToRoman(number) roman = '' while number >= 1000 roman << 'M' number -= 1000 end while number >= 900 roman << 'CM' number -= 900 end while number >= 500 roman << 'D' number -= 500 end while number >= 400 roman << 'CD' number -= 400 end while number >= 100 roman << 'C' number -= 100 end while number >= 90 roman << 'XC' number -= 90 end while number >= 50 roman << 'L' number -= 50 end while number >= 40 roman << 'XL' number -= 40 end while number >= 10 roman << 'X' number -= 10 end while number >= 9 roman << 'IX' number -= 9 end while number >= 5 roman << 'V' number -= 5 end while number >= 4 roman << 'IV' number -= 4 end while number >= 1 roman << 'I' number -= 1 end return roman end alias_method :int_to_roman, :intToRoman protected # # Output an HTML list bullet or ordered item symbol # [@param int :listdepth] list nesting level # [@param string :listtype] type of list # [@param float :size] current font size # [@access protected] # [@since 4.4.004 (2008-12-10)] # def putHtmlListBullet(listdepth, listtype='', size=10) size /= @k fill = '' color = @fgcolor width = 0 textitem = '' tmpx = @x lspace = GetStringWidth(' ') if listtype == '!' # set default list type for unordered list deftypes = ['disc', 'circle', 'square'] listtype = deftypes[(listdepth - 1) % 3] elsif listtype == '#' # set default list type for ordered list listtype = 'decimal' end case listtype # unordered types when 'none' when 'disc', 'circle' fill = 'F' if listtype == 'disc' fill << 'D' r = size / 6.0 lspace += 2 * r if @rtl @x += lspace else @x -= lspace end Circle(@x + r, @y + @lasth / 2.0, r, 0, 360, fill, {'color'=>color}, color, 8) when 'square' l = size / 3.0 lspace += l if @rtl @x += lspace else @x -= lspace end Rect(@x, @y + (@lasth - l)/ 2.0, l, l, 'F', {}, color) # ordered types # listcount[@listnum] # textitem when '1', 'decimal' textitem = @listcount[@listnum].to_s when 'decimal-leading-zero' textitem = sprintf("%02d", @listcount[@listnum]) when 'i', 'lower-roman' textitem = (intToRoman(@listcount[@listnum])).downcase when 'I', 'upper-roman' textitem = intToRoman(@listcount[@listnum]) when 'a', 'lower-alpha', 'lower-latin' textitem = (97 + @listcount[@listnum] - 1).chr when 'A', 'upper-alpha', 'upper-latin' textitem = (65 + @listcount[@listnum] - 1).chr when 'lower-greek' textitem = unichr(945 + @listcount[@listnum] - 1) else textitem = @listcount[@listnum].to_s end if !empty_string(textitem) # print ordered item if @rtl textitem = '.' + textitem else textitem = textitem + '.' end lspace += GetStringWidth(textitem) if @rtl @x += lspace else @x -= lspace end Write(@lasth, textitem, '', false, '', false, 0, false) end @x = tmpx @lispacer = '' end # # Returns current graphic variables as array. # [@return array] graphic variables # [@access protected] # [@since 4.2.010 (2008-11-14)] # def getGraphicVars() grapvars = { 'FontFamily' => @font_family, 'FontStyle' => @font_style, 'FontSizePt' => @font_size_pt, 'rMargin' => @r_margin, 'lMargin' => @l_margin, 'cMargin' => @c_margin, 'LineWidth' => @line_width, 'linestyleWidth' => @linestyle_width, 'linestyleCap' => @linestyle_cap, 'linestyleJoin' => @linestyle_join, 'linestyleDash' => @linestyle_dash, 'textrendermode' => @textrendermode, 'textstrokewidth' => @textstrokewidth, 'DrawColor' => @draw_color, 'FillColor' => @fill_color, 'TextColor' => @text_color, 'ColorFlag' => @color_flag, 'bgcolor' => @bgcolor, 'fgcolor' => @fgcolor, 'htmlvspace' => @htmlvspace, 'listindent' => @listindent, 'listindentlevel' => @listindentlevel, 'listnum' => @listnum, 'listordered' => @listordered, 'listcount' => @listcount, 'lispacer' => @lispacer, 'lasth' => @lasth } return grapvars end # # Set graphic variables. # [@param :gvars] array graphic variables # [@access protected] # [@since 4.2.010 (2008-11-14)] # def setGraphicVars(gvars) @font_family = gvars['FontFamily'] @font_style = gvars['FontStyle'] @font_size_pt = gvars['FontSizePt'] @r_margin = gvars['rMargin'] @l_margin = gvars['lMargin'] @c_margin = gvars['cMargin'] @line_width = gvars['LineWidth'] @linestyle_width = gvars['linestyleWidth'] @linestyle_cap = gvars['linestyleCap'] @linestyle_join = gvars['linestyleJoin'] @linestyle_dash = gvars['linestyleDash'] @textrendermode = gvars['textrendermode'] @textstrokewidth = gvars['textstrokewidth'] @draw_color = gvars['DrawColor'] @fill_color = gvars['FillColor'] @text_color = gvars['TextColor'] @color_flag = gvars['ColorFlag'] @bgcolor = gvars['bgcolor'] @fgcolor = gvars['fgcolor'] @htmlvspace = gvars['htmlvspace'] @listindent = gvars['listindent'] @listindentlevel = gvars['listindentlevel'] @listnum = gvars['listnum'] @listordered = gvars['listordered'] @listcount = gvars['listcount'] @lispacer = gvars['lispacer'] #@lasth = gvars['lasth'] out('' + @linestyle_width + ' ' + @linestyle_cap + ' ' + @linestyle_join + ' ' + @linestyle_dash + ' ' + @draw_color + ' ' + @fill_color + '') unless empty_string(@font_family) SetFont(@font_family, @font_style, @font_size_pt) end end # --- END OF HTML PARSER FUNCTIONS --- # --- BUFFER FUNCTIONS --- # # Returns a temporary filename for caching object on filesystem. # [@param string :name] prefix to add to filename # [@return string] filename. # [@access protected] # [@since 4.5.000 (2008-12-31)] # def getObjFilename(name) tmpFile = Tempfile.new(name + '_', @@k_path_cache) tmpFile.binmode tmpFile end # # Writes data to a temporary file on filesystem. # [@param string :filename] file name # [@param mixed :data] data to write on file # [@param boolean :append] if true append data, false replace. # [@access protected] # [@since 4.5.000 (2008-12-31)] # def writeDiskCache(filename, data, append=false) filename = filename.path if append fmode = 'a+b' else fmode = 'w+b' end f = open(filename, fmode) if !f Error('Unable to write cache file: ' + filename) else f.write(data) f.close end # update file length (needed for transactions) if @cache_file_length[filename].nil? @cache_file_length[filename] = data.length elsif append == false @cache_file_length[filename] = data.length else @cache_file_length[filename] += data.length end end # # Read data from a temporary file on filesystem. # [@param string :filename] file name # [@return mixed] retrieved data # [@access protected] # [@since 4.5.000 (2008-12-31)] # def readDiskCache(filename) filename = filename.path data = '' open( filename,'rb') do |f| data << f.read() end return data end # # Set buffer content (always append data). # [@param string :data] data # [@access protected] # [@since 4.5.000 (2009-01-02)] # def setBuffer(data) @bufferlen += data.length if @diskcache if @buffer.nil? or empty_string(@buffer.path) @buffer = getObjFilename('buffer') end writeDiskCache(@buffer, data, true) else @buffer << data end end # # Get buffer content. # [@return string] buffer content # [@access protected] # [@since 4.5.000 (2009-01-02)] # def getBuffer if @diskcache return readDiskCache(@buffer) else return @buffer end end # # Set page buffer content. # [@param int :page] page number # [@param string :data] page data # [@param boolean :append] if true append data, false replace. # [@access protected] # [@since 4.5.000 (2008-12-31)] # def setPageBuffer(page, data, append=false) if @diskcache if @pages[page].nil? @pages[page] = getObjFilename('page' + page.to_s) elsif (@objcopy and !append and @prev_pages[page].nil?) @prev_pages[page] = @pages[page] @pages[page] = getObjFilename('page' + page.to_s) end writeDiskCache(@pages[page], data, append) else if append @pages[page] << data else @pages[page] = data end end if append and !@pagelen[page].nil? @pagelen[page] += data.length else @pagelen[page] = data.length end end # # Get page buffer content. # [@param int :page] page number # [@return string] page buffer content or false in case of error # [@access protected] # [@since 4.5.000 (2008-12-31)] # def getPageBuffer(page) if @diskcache return readDiskCache(@pages[page]) elsif !@pages[page].nil? return @pages[page] end return false end # # Set image buffer content. # [@param string :image] image key # [@param array :data] image data # [@access protected] # [@since 4.5.000 (2008-12-31)] # def setImageBuffer(image, data) if @diskcache if @images[image].nil? @images[image] = getObjFilename('image' + File::basename(image)) end writeDiskCache(@images[image], Marshal.dump(data)) else @images[image] = data end if !@imagekeys.include?(image) @imagekeys.push image @numimages += 1 end end # # Set image buffer content for a specified sub-key. # [@param string :image] image key # [@param string :key] image sub-key # [@param hash :data] image data # [@access protected] # [@since 4.5.000 (2008-12-31)] # def setImageSubBuffer(image, key, data) if @images[image].nil? setImageBuffer(image, {}) end if @diskcache tmpimg = getImageBuffer(image) tmpimg[key] = data writeDiskCache(@images[image], Marshal.dump(tmpimg)) else @images[image][key] = data end end # # Get image buffer content. # [@param string :image] image key # [@return string image] buffer content or false in case of error # [@access protected] # [@since 4.5.000 (2008-12-31)] # def getImageBuffer(image) if @diskcache and !@images[image].nil? return Marshal.load(readDiskCache(@images[image])) elsif !@images[image].nil? return @images[image] end return false end # # Set font buffer content. # [@param string :font] font key # [@param hash :data] font data # [@access protected] # [@since 4.5.000 (2009-01-02)] # def setFontBuffer(font, data) if @diskcache if @fonts[font].nil? @fonts[font] = getObjFilename('font') end writeDiskCache(@fonts[font], Marshal.dump(data)) else @fonts[font] = data end if !@fontkeys.include?(font) @fontkeys.push font end end # # Set font buffer content. # [@param string :font] font key # [@param string :key] font sub-key # [@param array :data] font data # [@access protected] # [@since 4.5.000 (2009-01-02)] # def setFontSubBuffer(font, key, data) if @fonts[font].nil? setFontBuffer(font, {}) end if @diskcache tmpfont = getFontBuffer(font) tmpfont[key] = data writeDiskCache(@fonts[font], Marshal.dump(tmpfont)) else @fonts[font][key] = data end end # # Reset all links that points to pages after current # [@access protected] # def resetLinksAfterCurrentPage() @links.each do |link| link[0] = @page if link.present? && link[0] > @page end end # # Get font buffer content. # [@param string :font] font key # [@return string font] buffer content or false in case of error # [@access protected] # [@since 4.5.000 (2009-01-02)] # def getFontBuffer(font) if @diskcache and !@fonts[font].nil? return Marshal.load(readDiskCache(@fonts[font])) elsif !@fonts[font].nil? return @fonts[font] end return false end # --- END OF BUFFER FUNCTIONS --- # --- PAGE OPERATION FUNCTIONS --- public # # Move a page to a previous position. # [@param int :frompage] number of the source page # [@param int :topage] number of the destination page (must be less than :frompage) # [@return] true in case of success, false in case of error. # [@access public] # [@since 4.5.000 (2009-01-02)] # def movePage(frompage, topage) if (frompage > @numpages) or (frompage <= topage) return false end if frompage == @page # close the page before moving it endPage() end # move all page-related states tmppage = @pages[frompage] tmppagedim = @pagedim[frompage] tmppagelen = @pagelen[frompage] tmpintmrk = @intmrk[frompage] if @footerpos[frompage] tmpfooterpos = @footerpos[frompage] end if @footerlen[frompage] tmpfooterlen = @footerlen[frompage] end if @transfmrk[frompage] tmptransfmrk = @transfmrk[frompage] end if @page_annots[frompage] tmpannots = @page_annots[frompage] end if @newpagegroup[frompage] tmpnewpagegroup = @newpagegroup[frompage] end frompage.downto(topage + 1) do |i| j = i - 1 # shift pages down @pages[i] = @pages[j] @pagedim[i] = @pagedim[j] @pagelen[i] = @pagelen[j] @intmrk[i] = @intmrk[j] if @footerpos[j] @footerpos[i] = @footerpos[j] elsif footerpos[i] @footerpos[i] = nil end if @footerlen[j] @footerlen[i] = @footerlen[j] elsif @footerlen[i] @footerlen[i] = nil end if @transfmrk[j] @transfmrk[i] = @transfmrk[j] elsif @transfmrk[i] @transfmrk[i] = nil end if @page_annots[j] @page_annots[i] = @page_annots[j] elsif @page_annots[i] @page_annots[i] = nil end if @newpagegroup[j] @newpagegroup[i] = @newpagegroup[j] elsif @newpagegroup[i] @newpagegroup[i] = nil end end @pages[topage] = tmppage @pagedim[topage] = tmppagedim @pagelen[topage] = tmppagelen @intmrk[topage] = tmpintmrk if tmpfooterpos @footerpos[topage] = tmpfooterpos elsif @footerpos[topage] @footerpos[topage] = nil end if tmpfooterlen @footerlen[topage] = tmpfooterlen elsif @footerlen[topage] @footerlen[topage] = nil end if tmptransfmrk @transfmrk[topage] = tmptransfmrk elsif @transfmrk[topage] @transfmrk[topage] = nil end if tmpannots @page_annots[topage] = tmpannots elsif @page_annots[topage] @page_annots[topage] = nil end if tmpnewpagegroup @newpagegroup[topage] = tmpnewpagegroup elsif @newpagegroup[topage] @newpagegroup[topage] = nil end # adjust outlines tmpoutlines = @outlines tmpoutlines.each_with_index do |outline, key| if (outline[:p] >= topage) and (outline[:p] < frompage) @outlines[key][:p] = outline[:p] + 1 elsif outline[:p] == frompage @outlines[key][:p] = topage end end # adjust links tmplinks = @links tmplinks.each_with_index do |link, key| next if link.nil? if (link[0] >= topage) and (link[0] < frompage) @links[key][0] = link[0] + 1 elsif link[0] == frompage @links[key][0] = topage end end ### T.B.D ### TCPDF 5.0.001 ### # adjust javascript #tmpjavascript = @javascript #global jfrompage, jtopage #jfrompage = frompage #jtopage = topage #@javascript = preg_replace_callback('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', #create_function('$matches', 'global $jfrompage, $jtopage; # pagenum = matches[3].to_i + 1 # if (pagenum >= jtopage) and (pagenum < jfrompage) # newpage = pagenum + 1 # elsif pagenum == jfrompage # newpage = jtopage # else # newpage = pagenum # end # newpage -= 1 # return "this.addField(\'".$matches[1]."\',\'".$matches[2]."\',".$newpage."";'), $tmpjavascript); # return to last page lastPage(true) return true end alias_method :move_page, :movePage # # Remove the specified page. # [@param int :page] page to remove # [@return] true in case of success, false in case of error. # [@access public] # [@since 4.6.004 (2009-04-23)] # def deletePage(page) if page > @numpages return false end # delete current page @pages[page] = nil @pagedim[page] = nil @pagelen[page] = nil @intmrk[page] = nil if @footerpos[page] @footerpos[page] = nil end if @footerlen[page] @footerlen[page] = nil end if @transfmrk[page] @transfmrk[page] = nil end if @page_annots[page] @page_annots[page] = nil end if @newpagegroup[page] @newpagegroup[page] = nil end if @pageopen[page] @pageopen[page] = nil end # update remaining pages page.upto(@numpages - 1) do |i| j = i + 1 # shift pages @pages[i] = @pages[j] @pagedim[i] = @pagedim[j] @pagelen[i] = @pagelen[j] @intmrk[i] = @intmrk[j] if @footerpos[j] @footerpos[i] = @footerpos[j] elsif @footerpos[i] @footerpos[i] = nil end if @footerlen[j] @footerlen[i] = @footerlen[j] elsif @footerlen[i] @footerlen[i] = nil end if @transfmrk[j] @transfmrk[i] = @transfmrk[j] elsif @transfmrk[i] @transfmrk[i] = nil end if @page_annots[j] @page_annots[i] = page_annots[j] elsif @page_annots[i] @page_annots[i] = nil end if @newpagegroup[j] @newpagegroup[i] = @newpagegroup[j] elsif @newpagegroup[i] @newpagegroup[i] = nil end if @pageopen[j] @pageopen[i] = @pageopen[j] elsif @pageopen[i] @pageopen[i] = nil end end # remove last page @pages[@numpages] = nil @pagedim[@numpages] = nil @pagelen[@numpages] = nil @intmrk[@numpages] = nil if @footerpos[@numpages] @footerpos[@numpages] = nil end if @footerlen[@numpages] @footerlen[@numpages] = nil end if @transfmrk[@numpages] @transfmrk[@numpages] = nil end if @page_annots[@numpages] @page_annots[@numpages] = nil end if @newpagegroup[@numpages] @newpagegroup[@numpages] = nil end if @pageopen[@numpages] @pageopen[@numpages] = nil end @numpages -= 1 @page = @numpages # adjust outlines tmpoutlines = @outlines tmpoutlines.each_with_index do |outline, key| if outline[:p] > page @outlines[key][:p] = outline[:p] - 1 elsif outline[:p] == page @outlines[key] = nil end end # adjust links tmplinks = @links tmplinks.each_with_index do |link, key| next if link.nil? if link[0] > page @links[key][0] = link[0] - 1 elsif link[0] == page @links[key] = nil end end #### PDF javascript code does not implement, yet. ### # adjust javascript #tmpjavascript = @javascript #jpage = page #tmpjavascript =~ /this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/ #pagenum = $3.to_i + 1 #if pagenum >= jpage # newpage = pagenum - 1 #elsif pagenum == jpage # newpage = 1 #else # newpage = pagenum #end #newpage -= 1 #@javascript = "this.addField(\'" + $1 + "\',\'" + $2 + "\'," + newpage + "" # return to last page lastPage(true) return true end alias_method :delete_page, :deletePage # # Output a Table of Content Index (TOC). # Before calling this method you have to open the page using the addTOCPage() method. # After calling this method you have to call endTOCPage() to close the TOC page. # You can override this method to achieve different styles. # [@param int :page] page number where this TOC should be inserted (leave empty for current page). # [@param string :numbersfont] set the font for page numbers (please use monospaced font for better alignment). # [@param string :filler] string used to fill the space between text and page number. # [@param string :toc_name] name to use for TOC bookmark. # [@access public] # [@author] Nicola Asuni # [@since 4.5.000 (2009-01-02)] # [@see] addTOCPage(), endTOCPage(), addHTMLTOC() # def addTOC(page='', numbersfont='', filler='.', toc_name='TOC') fontsize = @font_size_pt fontfamily = @font_family fontstyle = @font_style w = @w - @l_margin - @r_margin spacer = GetStringWidth(32.chr) * 4 page_first = getPage() lmargin = @l_margin rmargin = @r_margin x_start = GetX() if empty_string(numbersfont) numbersfont = @default_monospaced_font end if empty_string(filler) filler = ' ' end if empty_string(page) gap = ' ' else gap = '' end @outlines.each_with_index do |outline, key| if @rtl aligntext = 'R' alignnum = 'L' else aligntext = 'L' alignnum = 'R' end if outline[:l] == 0 SetFont(fontfamily, fontstyle + 'B', fontsize) else SetFont(fontfamily, fontstyle, fontsize - outline[:l]) end indent = spacer * outline[:l] if @rtl @r_margin += indent @x -= indent else @l_margin += indent @x += indent end link = AddLink() SetLink(link, 0, outline[:p]) # write the text Write(0, outline[:t], link, 0, aligntext, false, 0, false, false, 0) SetFont(numbersfont, fontstyle, fontsize) if empty_string(page) pagenum = outline[:p] else # placemark to be replaced with the correct number pagenum = '{#' + outline[:p].to_s + '}' if (@current_font['type'] == 'TrueTypeUnicode') or (@current_font['type'] == 'cidfont0') pagenum = '{' + pagenum + '}' end end numwidth = GetStringWidth(pagenum) if @rtl tw = @x - @l_margin else tw = @w - @r_margin - @x end fw = tw - numwidth - GetStringWidth(32.chr) numfills = (fw / GetStringWidth(filler)).floor if numfills > 0 rowfill = filler * numfills else rowfill = '' end if @rtl pagenum = pagenum + gap + rowfill + ' ' else pagenum = ' ' + rowfill + gap + pagenum end # write the number Cell(tw, 0, pagenum, 0, 1, alignnum, 0, link, 0) SetX(x_start) @l_margin = lmargin @r_margin = rmargin end page_last = getPage() numpages = page_last - page_first + 1 if !empty_string(page) page_first.upto(page_last) do |p| # get page data temppage = getPageBuffer(p) 1.upto(@numpages) do |n| # update page numbers k = '{#' + n.to_s + '}' ku = '{' + k + '}' alias_a = escape(k) alias_au = escape('{' + k + '}') if @is_unicode alias_b = escape(UTF8ToLatin1(k)) alias_bu = escape(UTF8ToLatin1(ku)) alias_c = escape(utf8StrRev(k, false, rtl_text_dir)) alias_cu = escape(utf8StrRev(ku, false, rtl_text_dir)) end if n >= page np = n + numpages else np = n end ns = formatTOCPageNumber(np) nu = ns sdiff = k.length - ns.length - 1 sdiffu = ku.length - ns.length - 1 sfill = filler * sdiff sfillu = filler * sdiffu if @rtl ns = ns + ' ' + sfill nu = nu + ' ' + sfillu else ns = sfill + ' ' + ns nu = sfillu + ' ' + nu end nu = UTF8ToUTF16BE(nu, false) temppage = temppage.gsub(alias_au, nu) if @is_unicode temppage = temppage.gsub(alias_bu, nu) temppage = temppage.gsub(alias_cu, nu) temppage = temppage.gsub(alias_b, ns) temppage = temppage.gsub(alias_c, ns) end temppage = temppage.gsub(alias_a, ns) end # save changes setPageBuffer(p, temppage) end # move pages Bookmark(toc_name, 0, 0, page_first) 0.upto(numpages - 1) do |i| movePage(page_last, page) end end rescue => err Error('addTOC Error.', err) end alias_method :add_toc, :addTOC # # Output a Table Of Content Index (TOC) using HTML templates. # Before calling this method you have to open the page using the addTOCPage() method. # After calling this method you have to call endTOCPage() to close the TOC page. # [@param int :page] page number where this TOC should be inserted (leave empty for current page). # [@param string :toc_name] name to use for TOC bookmark. # [@param array :templates] array of html templates. Use: #TOC_DESCRIPTION# for bookmark title, #TOC_PAGE_NUMBER# for page number. # [@parma boolean :correct_align] if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL) # [@access public] # [@author] Nicola Asuni # [@since 5.0.001 (2010-05-06)] # [@see] addTOCPage(), endTOCPage(), addTOC() # def addHTMLTOC(page='', toc_name='TOC', templates=[], correct_align=true) prev_htmlLinkColorArray = @html_link_color_array prev_htmlLinkFontStyle = @html_link_font_style # set new style for link @html_link_color_array = [] @html_link_font_style = '' page_first = getPage() @outlines.each_with_index do |outline, key| if empty_string(page) pagenum = outline[:p].to_s else # placemark to be replaced with the correct number pagenum = '{#' + outline[:p].to_s + '}' if (@current_font['type'] == 'TrueTypeUnicode') or (@current_font['type'] == 'cidfont0') pagenum = '{' + pagenum + '}' end end # get HTML template row = templates[outline[:l]] # replace templates with current values row = row.gsub('#TOC_DESCRIPTION#', outline[:t]) row = row.gsub('#TOC_PAGE_NUMBER#', pagenum) # add link to page row = '<a href="#' + outline[:p].to_s + '">' + row + '</a>' # write bookmark entry writeHTML(row, false, false, true, false, '') end # restore link styles @html_link_color_array = prev_htmlLinkColorArray @html_link_font_style = prev_htmlLinkFontStyle # move TOC page and replace numbers page_last = getPage() numpages = page_last - page_first + 1 if !empty_string(page) page_first.upto(page_last) do |p| # get page data temppage = getPageBuffer(p) 1.upto(@numpages) do |n| # update page numbers k = '{#' + n.to_s + '}' ku = '{' + k + '}' alias_a = escape(k) alias_au = escape('{' + k + '}') if @is_unicode alias_b = escape(UTF8ToLatin1(k)) alias_bu = escape(UTF8ToLatin1(ku)) alias_c = escape(utf8StrRev(k, false, rtl_text_dir)) alias_cu = escape(utf8StrRev(ku, false, rtl_text_dir)) end if n >= page np = n + numpages else np = n end ns = formatTOCPageNumber(np) nu = ns if correct_align sdiff = k.length - ns.length sdiffu = ku.length - ns.length sfill = ' ' * sdiff sfillu = ' ' * sdiffu if @rtl ns = ns + sfill nu = nu + sfillu else ns = sfill + ns nu = sfillu + nu end end nu = UTF8ToUTF16BE(nu, false) temppage = temppage.gsub(alias_au, nu) if @is_unicode temppage = temppage.gsub(alias_bu, nu) temppage = temppage.gsub(alias_cu, nu) temppage = temppage.gsub(alias_b, ns) temppage = temppage.gsub(alias_c, ns) end temppage = temppage.gsub(alias_a, ns) end # save changes setPageBuffer(p, temppage) end # move pages Bookmark(toc_name, 0, 0, page_first) 0.upto(numpages - 1) do |i| movePage(page_last, page) end end rescue => err Error('addHTMLTOC Error.', err) end alias_method :add_html_toc, :addHTMLTOC # # Stores a copy of the current RBPDF object used for undo operation. # [@access public] # [@since 4.5.029 (2009-03-19)] # def startTransaction() if @objcopy # remove previous copy commitTransaction() end # record current page number and Y position @start_transaction_page = @page @start_transaction_y = @y # clone current object @objcopy = objclone(self) end alias_method :start_transaction, :startTransaction # # Delete the copy of the current RBPDF object used for undo operation. # [@access public] # [@since 4.5.029 (2009-03-19)] # def commitTransaction() if @objcopy if @objcopy.diskcache @prev_pages.compact.each do |file| File.delete(file.path) end @prev_pages = [] end @objcopy.destroy(true, true) @objcopy = nil end end alias_method :commit_transaction, :commitTransaction # # This method allows to undo the latest transaction by returning the latest saved RBPDF object with startTransaction(). # [@param boolean :this_self] if true restores current class object to previous state without the need of reassignment via the returned value. # [@return] RBPDF object. # [@access public] # [@since 4.5.029 (2009-03-19)] # def rollbackTransaction(this_self=false) if @objcopy if @objcopy.diskcache # truncate files to previous values @objcopy.cache_file_length.each { |file, length| File.truncate(file, length) if File.exist?(file) } @pages.each_with_index do |file, i| if @prev_pages[i] and File.exist?(@prev_pages[i].path) File.delete(file.path) end end end destroy(true, true) if this_self objvars = @objcopy.instance_variables objvars.each {|key| eval("#{key} = @objcopy.instance_variable_get(key)") if key.to_s != '@objcopy' } end return @objcopy end return self end alias_method :rollback_transaction, :rollbackTransaction # # Creates a copy of a class object # [@param object :object] class object to be cloned # [@return] cloned object # [@access public] # [@since 4.5.029 (2009-03-19)] # def objclone(object) if @diskcache newobj = object.dup newobj.cache_file_length = object.cache_file_length.dup newobj.prev_pages = object.prev_pages.dup newobj.pages = object.pages.dup return newobj else return Marshal.load(Marshal.dump(object)) end end # --- END OF PAGE OPERATION FUNCTIONS --- # --- STRING FUNCTIONS --- # # Determine whether a string is empty. # [@param string :str] string to be checked # [@return boolean] true if string is empty # [@access public] # [@since 4.5.044 (2009-04-16)] # def empty_string(str) return (str.nil? or (str.is_a?(String) and (str.length == 0))) end # # Find position of last occurrence of a substring in a string # [@param string :haystack] The string to search in. # [@param string :needle] substring to search. # [@param int :offset] May be specified to begin searching an arbitrary number of characters into the string. # [@return] Returns the position where the needle exists. Returns FALSE if the needle was not found. # [@access public] # [@since 4.8.038 (2010-03-13)] # def revstrpos(haystack, needle, offset = 0) length = haystack.length offset = (offset > 0) ? (length - offset) : offset.abs pos = haystack.reverse.index(needle.reverse, offset) return (pos.nil? ? nil : length - pos - needle.length) end # --- END OF STRING FUNCTIONS --- # # Set Text rendering mode. # [@param int :stroke] outline size in user units (0 = disable). # [@param boolean :fill] if true fills the text (default). # [@param boolean :clip] if true activate clipping mode # [@access public] # [@since 4.9.008 (2009-04-02)] # def setTextRenderingMode(stroke=0, fill=true, clip=false) # Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode # convert text rendering parameters if stroke < 0 stroke = 0 end if fill == true if stroke > 0 if clip == true # Fill, then stroke text and add to path for clipping textrendermode = 6 else # Fill, then stroke text textrendermode = 2 end textstrokewidth = stroke else if clip == true # Fill text and add to path for clipping textrendermode = 4 else # Fill text textrendermode = 0 end end else if stroke > 0 if clip == true # Stroke text and add to path for clipping textrendermode = 5 else # Stroke text textrendermode = 1 end textstrokewidth = stroke else if clip == true # Add text to path for clipping textrendermode = 7 else # Neither fill nor stroke text (invisible) textrendermode = 3 end end end @textrendermode = textrendermode @textstrokewidth = stroke * @k end alias_method :set_text_rendering_mode, :setTextRenderingMode # # Get the Path-Painting Operators. # [@param string :style] # Style of rendering. Possible values are: # * S or D: Stroke the path. # * s or d: Close and stroke the path. # * f or F: Fill the path, using the nonzero winding number rule to determine the region to fill. # * f* or F*: Fill the path, using the even-odd rule to determine the region to fill. # * B or FD or DF: Fill and then stroke the path, using the nonzero winding number rule to determine the region to fill. # * B* or F*D or DF*: Fill and then stroke the path, using the even-odd rule to determine the region to fill. # * b or fd or df: Close, fill, and then stroke the path, using the nonzero winding number rule to determine the region to fill. # * b or f*d or df*: Close, fill, and then stroke the path, using the even-odd rule to determine the region to fill. # * CNZ: Clipping mode using the even-odd rule to determine which regions lie inside the clipping path. # * CEO: Clipping mode using the nonzero winding number rule to determine which regions lie inside the clipping path # * n: End the path object without filling or stroking it. # [@param string :default] default style # [@param boolean :mode] if true enable rasterization, false otherwise. # [@author] Nicola Asuni # [@access protected: # [@since 5.0.000 (2010-04-30)] # def getPathPaintOperator(style, default='S') op = '' case style when 'S', 'D' op = 'S' when 's', 'd' op = 's' when 'f', 'F' op = 'f' when 'f*', 'F*' op = 'f*' when 'B', 'FD', 'DF' op = 'B' when 'B*', 'F*D', 'DF*' op = 'B*' when 'b', 'fd', 'df' op = 'b' when 'b*', 'f*d', 'df*' op = 'b*' when 'CNZ' op = 'W n' when 'CEO' op = 'W* n' when 'n' op = 'n' else if !default.empty? op = getPathPaintOperator(default, '') else op = '' end end return op end protected :getPathPaintOperator # -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- # SVG METHODS (not implement, yet.) # -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- end # END OF RBPDF CLASS #TODO 2007-05-25 (EJM) Level=0 - #Handle special IE contype request # if (!_SERVER['HTTP_USER_AGENT'].nil? and (_SERVER['HTTP_USER_AGENT']=='contype')) # header('Content-Type: application/pdf'); # exit; # }