diff --git a/README.md b/README.md index 099fac6..bfcad39 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ A template plugin allowing the inclusion of ERB-enabled RBPDF template files. * Image - 8bit PNG image support without MiniMagick/RMagick library. - PNG(with alpha channel)/JPEG/GIF/WebP image support. (use MiniMagick or RMagick library) + - SVG image support. (use REXML for XML parsing.) ## diff --git a/example/rails/app/controllers/example006_controller.rb b/example/rails/app/controllers/example006_controller.rb index 8ece7e5..4cf9bee 100644 --- a/example/rails/app/controllers/example006_controller.rb +++ b/example/rails/app/controllers/example006_controller.rb @@ -95,7 +95,7 @@ def index
White cold drink
IMAGES
- test alt attributetest alt attributetest alt attribute + test alt attributetest alt attributetest alt attribute
' # output the HTML content diff --git a/example/rails/app/controllers/example012_controller.rb b/example/rails/app/controllers/example012_controller.rb index bcf44c3..9a36bad 100644 --- a/example/rails/app/controllers/example012_controller.rb +++ b/example/rails/app/controllers/example012_controller.rb @@ -54,7 +54,7 @@ def index style = {'width' => 0.5, 'cap' => 'butt', 'join' => 'miter', 'dash' => '10,20,5,10', 'phase' => 10, 'color' => [255, 0, 0]} style2 = {'width' => 0.5, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => [255, 0, 0]} style3 = {'width' => 1, 'cap' => 'round', 'join' => 'round', 'dash' => '2,10', 'color' => [255, 0, 0]} - style4 = {'L' => 0, + style4 = {'L' => nil, 'T' => {'width' => 0.25, 'cap' => 'butt', 'join' => 'miter', 'dash' => '20,10', 'phase' => 10, 'color' => [100, 100, 255]}, 'R' => {'width' => 0.50, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => [50, 50, 127]}, 'B' => {'width' => 0.75, 'cap' => 'square', 'join' => 'miter', 'dash' => '30,10,5,10'}} @@ -107,7 +107,7 @@ def index # Polygonal Line pdf.set_line_style({'width' => 0.5, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => [0, 0, 164]}) - pdf.poly_line([80,165,90,160,100,165,110,160,120,165,130,160,140,165], 'D', nil, nil) + pdf.poly_line([80,165,90,160,100,165,110,160,120,165,130,160,140,165], 'D') # Regular polygon pdf.text(5, 169, 'Regular polygon examples') @@ -124,8 +124,8 @@ def index pdf.set_line_style(style5) pdf.star_polygon(20, 230, 15, 20, 3, 0, 1, 'F') pdf.star_polygon(55, 230, 15, 12, 5) - pdf.star_polygon(55, 230, 7, 12, 5, 45, 0, 'DF', {'all' => style7}, [220, 220, 200], 'F', [255, 200, 200]) - pdf.star_polygon(90, 230, 15, 20, 6, 0, 1, 'DF', {'all' => style5}, [220, 220, 200], 'F', [255, 200, 200]) + pdf.star_polygon(55, 230, 7, 12, 5, 45, 0, 'DF', {'all' => style7}, [220, 220, 200], 'F', nil, [255, 200, 100]) + pdf.star_polygon(90, 230, 15, 20, 6, 0, 1, 'DF', {'all' => style5}, [220, 220, 200], 'F', nil, [255, 200, 100]) pdf.star_polygon(125, 230, 15, 5, 2, 30, 1, nil, {'all' => style5}, nil, nil, style6) pdf.star_polygon(160, 230, 15, 10, 3) pdf.star_polygon(160, 230, 7, 50, 26) diff --git a/example/rails/app/controllers/example039_controller.rb b/example/rails/app/controllers/example039_controller.rb index e574226..25113b6 100644 --- a/example/rails/app/controllers/example039_controller.rb +++ b/example/rails/app/controllers/example039_controller.rb @@ -59,7 +59,7 @@ def index pdf.write(0, 'Example of HTML Justification', '', 0, 'L', true, 0, false, false, 0) # create some HTML content - html = 'a abc abcdefghijkl abcdef abcdefg abcdefghi a abc abcd test alt attribute abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg start a abc before yellow color after a abc abcd abcdef abcdefg abcdefghi a abc abcd end abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi
abcd abcdef abcdefg abcdefghi
abcd abcde abcdef
' + html = 'a abc abcdefghijkl abcdef abcdefg abcdefghi a abc abcd test alt attribute abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg start a abc before yellow color after a abc abcd abcdef abcdefg abcdefghi a abc abcd end abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi a abc abcd abcdef abcdefg abcdefghi
abcd abcdef abcdefg abcdefghi
abcd abcde abcdef
' # set core font pdf.set_font('helvetica', '', 10) diff --git a/example/rails/app/controllers/example058_controller.rb b/example/rails/app/controllers/example058_controller.rb new file mode 100644 index 0000000..7baa3f3 --- /dev/null +++ b/example/rails/app/controllers/example058_controller.rb @@ -0,0 +1,80 @@ +# coding: UTF-8 +#============================================================+ +# Begin : 2010-04-22 +# Last Update : 2010-05-20 +# +# Description : Example 058 for RBPDF class +# SVG Image +# +# Author: Jun NAITOH +# License: LGPL 2.1 or later +#============================================================+ + +require("example_common.rb") + +class Example058Controller < ApplicationController + def index + # create new PDF document + pdf = RBPDF.new(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false) + + # set document information + pdf.set_creator(PDF_CREATOR) + pdf.set_author(PDF_AUTHOR) + pdf.set_title('RBPDF Example 058') + pdf.set_subject('RBPDF Tutorial') + pdf.set_keywords('RBPDF, PDF, example, test, guide') + + # set default header data + pdf.set_header_data(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, PDF_HEADER_TITLE + ' 058', PDF_HEADER_STRING) + + # set header and footer fonts + pdf.set_header_font([PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN]) + pdf.set_footer_font([PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA]) + + # set default monospaced font + pdf.set_default_monospaced_font(PDF_FONT_MONOSPACED) + + # set margins + pdf.set_margins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT) + pdf.set_header_margin(PDF_MARGIN_HEADER) + pdf.set_footer_margin(PDF_MARGIN_FOOTER) + + # set auto page breaks + pdf.set_auto_page_break(true, PDF_MARGIN_BOTTOM) + + # set image scale factor + pdf.set_image_scale(PDF_IMAGE_SCALE_RATIO) + + # set some language-dependent strings + pdf.set_language_array($l) + + # --------------------------------------------------------- + + # set font + pdf.set_font('helvetica', '', 20) + + # add a page + pdf.add_page() + + # NOTE: Uncomment the following line to rasterize SVG image using the ImageMagick library. + # pdf.setRasterizeVectorImages(true); + + pdf.image_svg(PDF_SVG_TEST_TESTSVG, 15, 30, 0, 0, 'https://github.com/naitoh/rbpdf', '', '', 1, false) + + pdf.image_svg(PDF_SVG_TEST_TUX, 30, 100, 0, 100, '', '', '', 0, false) + + pdf.set_font('helvetica', '', 8) + pdf.set_y(195) + txt = '© The copyright holder of the above Tux image is Larry Ewing, allows anyone to use it for any purpose, provided that the copyright holder is properly attributed. Redistribution, derivative work, commercial use, and all other use is permitted.' + pdf.write(0, txt, '', 0, 'L', true, 0, false, false, 0) + + # --------------------------------------------------------- + + # Close and output PDF document + send_data pdf.output(), :type => "application/pdf", :disposition => "inline" + end +end + +#============================================================+ +# END OF FILE +#============================================================+ diff --git a/example/rails/app/controllers/example_common.rb b/example/rails/app/controllers/example_common.rb index ddb130a..6747dbd 100644 --- a/example/rails/app/controllers/example_common.rb +++ b/example/rails/app/controllers/example_common.rb @@ -27,7 +27,6 @@ PDF_IMAGE_DEMO_WEBP="#{PUBLIC}/image_demo.webp" PDF_UTF8TEST_TXT="#{PUBLIC}/utf8test.txt" PDF_TABLE_DATA_DEMO_TXT="#{PUBLIC}/table_data_demo.txt" -PDF_TIGER_AI="#{PUBLIC}/tiger.ai" PDF_PNG_TEST_ALPHA_PNG="#{PUBLIC}/png_test_alpha.png" PDF_PNG_TEST_MSK_ALPHA_PNG="#{PUBLIC}/png_test_msk_alpha.png" PDF_PNG_TEST_NON_ALPHA_PNG="#{PUBLIC}/png_test_non_alpha.png" @@ -35,6 +34,8 @@ PDF_GIF_TEST_NON_ALPHA_PNG="#{PUBLIC}/gif_test_non_alpha.png" PDF_GIF_TEST_ALPHA_GIF="#{PUBLIC}/gif_test_alpha.gif" PDF_WEBP_TEST_ALPHA_PNG="#{PUBLIC}/webp_test_alpha.webp" +PDF_SVG_TEST_TESTSVG="#{PUBLIC}/testsvg.svg" +PDF_SVG_TEST_TUX="#{PUBLIC}/tux.svg" if !defined? send_data def send_data(data, options = {}) diff --git a/example/rails/public/testsvg.svg b/example/rails/public/testsvg.svg new file mode 100644 index 0000000..fe3ece3 --- /dev/null +++ b/example/rails/public/testsvg.svg @@ -0,0 +1,328 @@ + + + + + TCPDF SVG EXAMPLE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + github.com + + + + + + + + SVG + diff --git a/example/rails/public/tux.svg b/example/rails/public/tux.svg new file mode 100644 index 0000000..de8c869 --- /dev/null +++ b/example/rails/public/tux.svg @@ -0,0 +1,1487 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/rbpdf.rb b/lib/rbpdf.rb index 061d079..14e4f2d 100755 --- a/lib/rbpdf.rb +++ b/lib/rbpdf.rb @@ -428,6 +428,84 @@ def initialize(orientation = 'P', unit = 'mm', format = 'A4', unicode = true, e @pdfunit ||= 'mm' @tocpage ||= false + @rasterize_vector_images = false + @svgdir = '' + @svgunit = 'px' + @svggradients = {} + @svggradientid = 0 + @svgdefsmode = false + @svgdefs = {} + @svgclipmode = false + @svgclippaths = {} + @svgclipid = 0 + @svgtext = '' + @svginheritprop = ['clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'fill', 'fill-opacity', 'fill-rule', 'font', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'marker', 'marker-end', 'marker-mid', 'marker-start', 'pointer-events', 'shape-rendering', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-rendering', 'visibility', 'word-spacing', 'writing-mode'] + @svgstyles = [{ + 'alignment-baseline' => 'auto', + 'baseline-shift' => 'baseline', + 'clip' => 'auto', + 'clip-path' => 'none', + 'clip-rule' => 'nonzero', + 'color' => 'black', + 'color-interpolation' => 'sRGB', + 'color-interpolation-filters' => 'linearRGB', + 'color-profile' => 'auto', + 'color-rendering' => 'auto', + 'cursor' => 'auto', + 'direction' => 'ltr', + 'display' => 'inline', + 'dominant-baseline' => 'auto', + 'enable-background' => 'accumulate', + 'fill' => 'black', + 'fill-opacity' => 1, + 'fill-rule' => 'nonzero', + 'filter' => 'none', + 'flood-color' => 'black', + 'flood-opacity' => 1, + 'font' => '', + 'font-family' => 'helvetica', + 'font-size' => 'medium', + 'font-size-adjust' => 'none', + 'font-stretch' => 'normal', + 'font-style' => 'normal', + 'font-variant' => 'normal', + 'font-weight' => 'normal', + 'glyph-orientation-horizontal' => '0deg', + 'glyph-orientation-vertical' => 'auto', + 'image-rendering' => 'auto', + 'kerning' => 'auto', + 'letter-spacing' => 'normal', + 'lighting-color' => 'white', + 'marker' => '', + 'marker-end' => 'none', + 'marker-mid' => 'none', + 'marker-start' => 'none', + 'mask' => 'none', + 'opacity' => 1, + 'overflow' => 'auto', + 'pointer-events' => 'visiblePainted', + 'shape-rendering' => 'auto', + 'stop-color' => 'black', + 'stop-opacity' => 1, + 'stroke' => 'none', + 'stroke-dasharray' => 'none', + 'stroke-dashoffset' => 0, + 'stroke-linecap' => 'butt', + 'stroke-linejoin' => 'miter', + 'stroke-miterlimit' => 4, + 'stroke-opacity' => 1, + 'stroke-width' => 1, + 'text-anchor' => 'start', + 'text-decoration' => 'none', + 'text-rendering' => 'auto', + 'unicode-bidi' => 'normal', + 'visibility' => 'visible', + 'word-spacing' => 'normal', + 'writing-mode' => 'lr-tb', + 'text-color' => 'black', + 'transfmatrix' => [1, 0, 0, 1, 0, 0] + }] + ####################### #Some checks @@ -3373,11 +3451,17 @@ def removeSHY(txt='') # * T : top # * M : middle # * B : bottom + # [@param string :anchor] + # The text-anchor attribute is used to align (start-, middle- or end-alignment) a string. + # * empty string: no anchor mode (default value) + # * start: The rendered characters are aligned such that the start of the text string is at the initial current text position. + # * middle: The rendered characters are aligned such that the middle of the text string is at the current text position. + # * end: The rendered characters are shifted such that the end of the resulting rendered text. # [@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') + 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', anchor='') if !ignore_min_height min_cell_height = @font_size * @cell_height_ratio if h < min_cell_height @@ -3385,7 +3469,7 @@ def Cell(w, h=0, txt='', border=0, ln=0, align='', fill=0, link=nil, stretch=0, end end checkPageBreak(h) - out(getCellCode(w, h, txt, border, ln, align, fill, link, stretch, ignore_min_height, calign, valign)) + out(getCellCode(w, h, txt, border, ln, align, fill, link, stretch, ignore_min_height, calign, valign, anchor)) rescue => err Error('Cell Error.', err) end @@ -3441,11 +3525,17 @@ def Cell(w, h=0, txt='', border=0, ln=0, align='', fill=0, link=nil, stretch=0, # * T : top # * M : middle # * B : bottom + # [@param string :anchor] + # The text-anchor attribute is used to align (start-, middle- or end-alignment) a string. + # * empty string: no anchor mode (default value) + # * start: The rendered characters are aligned such that the start of the text string is at the initial current text position. + # * middle: The rendered characters are aligned such that the middle of the text string is at the current text position. + # * end: The rendered characters are shifted such that the end of the resulting rendered text. # [@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') + 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', anchor='') txt = '' if txt.nil? rs = "" # string to be returned txt = removeSHY(txt) @@ -3703,6 +3793,16 @@ def getCellCode(w, h=0, txt='', border=0, ln=0, align='', fill=0, link=nil, stre dx = @c_margin end + # [note] text-anchor takes precedence over align. + case anchor + when 'start' + dx = @c_margin + when 'middle' + dx = -width / 2.0 + when 'end' + dx = - width - @c_margin + end + if @rtl xdx = @x - dx - width else @@ -9090,31 +9190,28 @@ def Line(x1, y1, x2, y2, style={}) # [@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. + # [@param string :style] Style of rendering. See the getPathPaintOperator() function for more information. + # [@param hash :border_style] Border style of rectangle. Hash with keys among the following: # * 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). + # If a key is not present or is null, not draws the border. Default value: default line style (empty Hash). # [@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? + def Rect(x, y, w, h, style='', border_style={}, fill_color=[]) + if !style.index('F').nil? && fill_color && !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 + if border_style && border_style['all'] + SetLineStyle(border_style['all']) + border_style = {} end outRect(x, y, w, h, op) - if border_style + if border_style && !border_style.empty? border_style2 = {} border_style.each { |line, value| length = line.length @@ -9152,18 +9249,18 @@ def Rect(x, y, w, h, style='', border_style={}, fill_color={}) # [@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 hash :line_style] Line style of curve. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty Hash). # [@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 + def Curve(x0, y0, x1, y1, x2, y2, x3, y3, style='', line_style={}, fill_color=[]) + if style && !style.index('F').nil? && fill_color && !fill_color.empty? SetFillColorArray(fill_color) end op = getPathPaintOperator(style) - if line_style + if line_style && !line_style.empty? SetLineStyle(line_style) end outPoint(x0, y0) @@ -9183,19 +9280,19 @@ def Curve(x0, y0, x1, y1, x2, y2, x3, y3, style='', line_style=nil, fill_color=n # [@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 hash :line_style] Line style of ellipse. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty Hash). # [@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) + def Ellipse(x0, y0, rx, ry='', angle=0, astart=0, afinish=360, style='', line_style={}, fill_color=[], nc=2) style = '' if style.nil? if empty_string(ry) or (ry == 0) ry = rx end - if (nil != style.index('F')) and fill_color + if !style.index('F').nil? && fill_color && !fill_color.empty? SetFillColorArray(fill_color) end op = getPathPaintOperator(style) @@ -9320,13 +9417,13 @@ def outellipticalarc(xc, yc, rx, ry, xang=0, angs=0, angf=360, pie=false, nc=2) # [@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 hash :line_style] Line style of circle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty Hash). # [@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) + def Circle(x0, y0, r, angstr=0, angend=360, style='', line_style={}, fill_color=[], nc=2) Ellipse(x0, y0, r, r, 0, angstr, angend, style, line_style, fill_color, nc) end alias_method :circle, :Circle @@ -9335,17 +9432,17 @@ def Circle(x0, y0, r, angstr=0, angend=360, style='', line_style=nil, fill_color # 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}. + # [@param hash or array :line_style] + # Line style of polygon. Hash with keys among the following: + # * all: Line style of all lines. Hash 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). + # If a key is not present or is null, not draws the line. Default value is default line style (empty Hash). # [@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) + def PolyLine(p, style='', line_style={}, fill_color=[]) Polygon(p, style, line_style, fill_color, false) end alias_method :poly_line, :PolyLine @@ -9354,17 +9451,17 @@ def PolyLine(p, style='', line_style=nil, fill_color=nil) # 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}. + # [@param hash or array :line_style] + # Line style of polygon. Hash with keys among the following: + # * all: Line style of all lines. Hash 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). + # If a key is not present or is null, not draws the line. Default value is default line style (empty Hash). # [@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) + def Polygon(p, style='', line_style={}, fill_color=[], closed=true) style = '' if style.nil? nc = p.length # number of coordinates np = nc / 2 # number of points @@ -9383,15 +9480,15 @@ def Polygon(p, style='', line_style=nil, fill_color=nil, closed=true) end nc += 4 end - if (nil != style.index('F')) and fill_color + if !style.index('F').nil? && fill_color && !fill_color.empty? SetFillColorArray(fill_color) end op = getPathPaintOperator(style) if op == 'f' - line_style = [] + line_style = {} end draw = true - if line_style + if line_style && !line_style.empty? if line_style.is_a? Hash and line_style['all'] SetLineStyle(line_style['all']) else @@ -9448,11 +9545,11 @@ def Polygon(p, style='', line_style=nil, fill_color=nil, closed=true) # [@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: + # [@param hash :line_style] + # Line style of polygon sides. Hash 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). + # If a key is not present or is null, not draws the side. Default value is default line style (empty Hash). # [@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: @@ -9461,12 +9558,12 @@ def Polygon(p, style='', line_style=nil, fill_color=nil, closed=true) # * 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 hash :circle_outLine_style] Line style of inscribed circle (if draws). Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty Hash). # [@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) + def RegularPolygon(x0, y0, r, ns, angle=0, draw_circle=false, style='', line_style={}, fill_color=[], circle_style='', circle_outLine_style={}, circle_fill_color=[]) draw_circle = false if draw_circle == 0 if 3 > ns ns = 3 @@ -9495,11 +9592,11 @@ def RegularPolygon(x0, y0, r, ns, angle=0, draw_circle=false, style='', line_sty # [@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: + # [@param hash :line_style] + # Line style of polygon sides. Hash 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). + # If a key is not present or is null, not draws the side. Default value is default line style (empty Hash). # [@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: @@ -9508,12 +9605,12 @@ def RegularPolygon(x0, y0, r, ns, angle=0, draw_circle=false, style='', line_sty # * 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 hash :circle_outLine_style] Line style of inscribed circle (if draws). Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty Hash). # [@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) + def StarPolygon(x0, y0, r, nv, ng, angle=0, draw_circle=false, style='', line_style={}, fill_color=[], circle_style='', circle_outLine_style={}, circle_fill_color=[]) draw_circle = false if draw_circle == 0 if nv < 2 nv = 2 @@ -9553,12 +9650,12 @@ def StarPolygon(x0, y0, r, nv, ng, angle=0, draw_circle=false, style='', line_st # [@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 hash :border_style] Border style of rectangle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty hash). # [@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) + def RoundedRect(x, y, w, h, r, round_corner='1111', style='', border_style={}, fill_color=[]) RoundedRectXY(x, y, w, h, r, r, round_corner, style, border_style, fill_color) end alias_method :rounded_rect, :RoundedRect @@ -9573,12 +9670,12 @@ def RoundedRect(x, y, w, h, r, round_corner='1111', style='', border_style=nil, # [@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 hash :border_style] Border style of rectangle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty hash). # [@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) + def RoundedRectXY(x, y, w, h, rx, ry, round_corner='1111', style='', border_style={}, fill_color=[]) style = '' if style.nil? if (round_corner == '0000') or ((rx == ry) and (rx == 0)) # Not rounded @@ -9586,14 +9683,14 @@ def RoundedRectXY(x, y, w, h, rx, ry, round_corner='1111', style='', border_styl return end # Rounded - if (nil != style.index('F')) and fill_color + if !style.index('F').nil? && fill_color && !fill_color.empty? SetFillColorArray(fill_color) end op = getPathPaintOperator(style) if op == 'f' - border_style = [] + border_style = {} end - if border_style + unless border_style.empty? SetLineStyle(border_style) end myArc = 4 / 3.0 * (::Math.sqrt(2) - 1) @@ -14740,7 +14837,6 @@ def openHTMLTagHandler(dom, key, cell) 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 @@ -14815,7 +14911,12 @@ def openHTMLTagHandler(dom, key, cell) result_img = proc_image_file(tag['attribute']['src']) do |img_file| - Image(img_file, xpos, @y, iw, ih, '', imglink, align, false, 300, '', false, false, border, false, false, true) + type = getImageFileType(tag['attribute']['src']) + if type == 'svg' + image_svg(img_file, xpos, @y, iw, ih, imglink, align, '', border, true) + else + image(img_file, xpos, @y, iw, ih, '', imglink, align, false, 300, '', false, false, border, false, false, true) + end end if result_img or ih != 0 @@ -16958,8 +17059,1410 @@ def getPathPaintOperator(style, default='S') protected :getPathPaintOperator # -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- - # SVG METHODS (not implement, yet.) + # SVG METHODS # -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- + require 'rexml/parsers/pullparser' + # + # Embedd a Scalable Vector Graphics (SVG) image. + # NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of SVG images using ImageMagick library. + # [@param string :file] Name of the SVG file. + # [@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 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: + # [@param string :palign] Allows to center or align the image on the current line. Possible values are: + # [@param mixed :border] Indicates if borders must be drawn around the image. The value can be either a number:or a string containing some or all of the following characters (in any order): + # [@param boolean :fitonpage] if true the image is resized to not exceed page dimensions. + # [@since 5.0.000 (2010-05-02)] + # [@access public] + # + def image_svg(file, x='', y='', w=0, h=0, link='', align='', palign='', border=0, fitonpage=false) + error("SVG file not found: #{file}") unless File.exist?(file) + + if @rasterize_vector_images + # convert SVG to raster image using GD or ImageMagick libraries + return Image(file, x, y, w, h, 'SVG', link, align, true, 300, palign, false, false, border, false, false, false) + end + + @svgdir = File.dirname(file) + x = @x if x == '' + y = @y if y == '' + k = @k + + # start parsing an XML SVG header + width, height, ox, oy, ow, oh, aspect_ratio_align, aspect_ratio_ms = parse_svg_tag_attributes(file, w, h) + + # calculate image width and height on document + if (w <= 0) && (h <= 0) + # convert image size to document unit + w = width + h = height + elsif w <= 0 + w = h * width / height + elsif h <= 0 + h = w * height / width + end + # Check whether we need a new page first as this does not fit + prev_x = @x + if checkPageBreak(h, y) + y = @y + 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 && ((x + w) > (@w - @r_margin)) + w = @w - @r_margin - x + h = w / ratio_wh + elsif @rtl && ((x - w) < (@l_margin)) + w = x - @l_margin + h = w / ratio_wh + end + end + # set alignment + @img_rb_y = y + h + # set alignment + if @rtl + case palign + when 'L' + ximg = @l_margin + when 'C' + ximg = (@w + @l_margin - @r_margin - w) / 2 + when 'R' + ximg = @w - @r_margin - w + else + ximg = x - w + end + @img_rb_x = ximg + else + case palign + when 'L' + ximg = @l_margin + when 'C' + ximg = (@w + @l_margin - @r_margin - w) / 2 + when 'R' + ximg = @w - @r_margin - w + else + ximg = x + end + @img_rb_x = ximg + w + end + # store current graphic vars + gvars = getGraphicVars() + # store SVG position and scale factors + svgoffset_x = (ximg - ox) * @k + svgoffset_y = -(y - oy) * @k + + if ow.nil? + ow = w + svgscale_x = 1 + else + svgscale_x = w / ow + end + if oh.nil? + oh = h + svgscale_y = 1 + else + svgscale_y = h / oh + end + # scaling and alignment + if aspect_ratio_align != 'none' + # store current scaling values + svgscale_old_x = svgscale_x + svgscale_old_y = svgscale_y + # force uniform scaling + if aspect_ratio_ms == 'slice' + # the entire viewport is covered by the viewBox + if svgscale_x > svgscale_y + svgscale_y = svgscale_x + elsif svgscale_x < svgscale_y + svgscale_x = svgscale_y + end + else # meet + # the entire viewBox is visible within the viewport + if svgscale_x < svgscale_y + svgscale_y = svgscale_x + elsif svgscale_x > svgscale_y + svgscale_x = svgscale_y + end + end + # correct X alignment + case aspect_ratio_align[1, 3] + when 'Min' + # do nothing + when 'Max' + svgoffset_x += ((w * @k) - (ow * @k * svgscale_x)) + else # 'Mid' + svgoffset_x += (((w * @k) - (ow * @k * svgscale_x)) / 2) + end + # correct Y alignment + case aspect_ratio_align[5..-1] + when 'Min' + # do nothing + when 'Max' + svgoffset_y -= ((h * @k) - (oh * @k * svgscale_y)) + else # 'Mid' + svgoffset_y -= (((h * @k) - (oh * @k * svgscale_y)) / 2) + end + end + # save the current graphic state + out("q#{@epsmarker}") + # set initial clipping mask + rect(x, y, w, h, 'CNZ', {}, {}) + # scale and translate + e = ox * @k * (1 - svgscale_x) + f = (@h - oy) * @k * (1 - svgscale_y) + out(sprintf('%.3f %.3f %.3f %.3f %.3f %.3f cm', svgscale_x, 0, 0, svgscale_y, e + svgoffset_x, f + svgoffset_y)) + + # start parsing an XML document + open(file,'rb') do |f| + parser = REXML::Parsers::PullParser.new(f) + while parser.has_next? + res = parser.pull + case res.event_type + when :start_element # sets the element handler method + name = res[0] + attribs = res[1] + startSVGElementHandler(name, attribs) + when :end_element + name = res[0] + endSVGElementHandler(name) + when :cdata # sets the character data handler method + data = res[0] + segSVGContentHandler(data) + when :text # sets the character data handler method + data = res[1] + segSVGContentHandler(data) + end + end + end + + # restore previous graphic state + out("#{@epsmarker}Q") + # restore graphic vars + setGraphicVars(gvars) + unless 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 + link(ximg, y, w, h, link, 0) + end + # set pointer to align the successive text/objects + case align + when 'T' + @y = y + @x = @img_rb_x + when 'M' + @y = y + round(h/2) + @x = @img_rb_x + when 'B' + @y = @img_rb_y + @x = @img_rb_x + when 'N' + set_y(@img_rb_y) + end + @endlinex = @img_rb_x + end + + def parse_svg_tag_attributes(file, w, h) + ox = 0 + oy = 0 + ow = nil + oh = nil + aspect_ratio_align = 'xMidYMid' + aspect_ratio_ms = 'meet' + svg_check = false + open(file,'rb') do |f| + parser = REXML::Parsers::PullParser.new(f) + + while parser.has_next? + res = parser.pull + if (res.event_type == :start_element) && (res[0] == 'svg') + svg_check = true + attribs = res[1] + + if attribs['viewBox'] && !attribs['viewBox'].empty? + tmp = attribs['viewBox'].strip.split(/[,\s]+/) + view_box = [] + if tmp.size == 4 + tmp.each_with_index {|val, key| + view_box[key] = getHTMLUnitToUnits(val, 0, @svgunit, false) + } + ox = view_box[0] + oy = view_box[1] + end + if (view_box[2]&.> 0) && (view_box[3]&.> 0) + ow = view_box[2] + oh = view_box[3] + end + # get aspect ratio + if attribs['preserveAspectRatio'] && attribs['preserveAspectRatio'].empty? + aspect_ratio = attribs['preserveAspectRatio'].split + case aspect_ratio.size + when 3 + aspect_ratio_align = aspect_ratio[1] + aspect_ratio_ms = aspect_ratio[2] + when 2 + aspect_ratio_align = aspect_ratio[0] + aspect_ratio_ms = aspect_ratio[1] + when 1 + aspect_ratio_align = aspect_ratio[0] + aspect_ratio_ms = 'meet' + end + end + end + + if attribs['x'] && !attribs['x'].empty? + ox = getHTMLUnitToUnits(attribs['x'], 0, @svgunit, false) + end + if attribs['y'] && !attribs['y'].empty? + oy = getHTMLUnitToUnits(attribs['y'], 0, @svgunit, false) + end + if attribs['width'] && !attribs['width'].empty? + refsize = (w == 0) ? (ow.nil? ? 1 : ow) : w + w = getHTMLUnitToUnits(attribs['width'], refsize, @svgunit, false) + ow ||= w + elsif !ow.nil? + w = ow + end + if attribs['height'] && !attribs['height'].empty? + refsize = (h == 0) ? (oh.nil? ? 1 : oh) : h + h = getHTMLUnitToUnits(attribs['height'], refsize, @svgunit, false) + oh ||= h + elsif !oh.nil? + h = oh + end + break + end + end + end + + error("SVG tag not found in SVG file: #{file}") unless svg_check + + [w, h, ox, oy, ow, oh, aspect_ratio_align, aspect_ratio_ms] + end + private :parse_svg_tag_attributes + +protected + + # + # Get the tranformation matrix from SVG transform attribute + # [@param string :transformation] + # [@return array of transformations] + # [@since 5.0.000 (2010-05-02)] + # [@access protected] + # + def getSVGTransformMatrix(attribute) + # identity matrix + tm = [1, 0, 0, 1, 0, 0] + continue = true + while continue + continue = false + # matrix + if attribute =~ /matrix\(([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)\)/mi + a = Regexp.last_match[1].to_f + b = Regexp.last_match[2].to_f + c = Regexp.last_match[3].to_f + d = Regexp.last_match[4].to_f + e = Regexp.last_match[5].to_f + f = Regexp.last_match[6].to_f + attribute = attribute.gsub(Regexp.last_match[0], '') + continue = true + tm = getTransformationMatrixProduct(tm, [a, b, c, d, e, f]) + end + # translate x + if attribute =~ /translate\(([-a-z0-9.]+)\)/mi + attribute = attribute.gsub(Regexp.last_match[0], "translate(#{Regexp.last_match[1]},0)") + continue = true + end + # translate x,y + if attribute =~ /translate\(([-a-z0-9.]+)[,\s]+([-a-z0-9.]+)\)/mi + e = Regexp.last_match[1].to_f + f = Regexp.last_match[2].to_f + attribute = attribute.gsub(Regexp.last_match[0], '') + continue = true + tm = getTransformationMatrixProduct(tm, [1, 0, 0, 1, e, f]) + end + # scale x + if attribute =~ /scale\(([-a-z0-9.]+)\)/mi + attribute = attribute.gsub(Regexp.last_match[0], "scale(#{Regexp.last_match[1]},#{Regexp.last_match[1]})") + continue = true + end + # scale x,y + if attribute =~ /scale\(([-a-z0-9.]+)[,\s]+([-a-z0-9.]+)\)/mi + a = Regexp.last_match[1].to_f + if Regexp.last_match[2] && (Regexp.last_match[2].strip.size > 0) + d = Regexp.last_match[2].to_f + else + d = a + end + attribute = attribute.gsub(Regexp.last_match[0], '') + continue = true + tm = getTransformationMatrixProduct(tm, [a, 0, 0, d, 0, 0]) + end + # rotate ang + if attribute =~ /rotate\(([-a-z0-9.]+)\)/mi + attribute = attribute.gsub(Regexp.last_match[0], "rotate(#{Regexp.last_match[1]},0,0)") + continue = true + end + # rotate ang,x,y + if attribute =~ /rotate\(([-0-9.]+)[,\s]+([-a-z0-9.]+)[,\s]+([-a-z0-9.]+)\)/mi + ang = Regexp.last_match[1].to_f * ::Math::PI / 180 # deg2rad + a = ::Math::cos(ang) + b = ::Math::sin(ang) + c = -b + d = a + x = Regexp.last_match[2].to_f + y = Regexp.last_match[3].to_f + e = (x * (1 - a)) - (y * c) + f = (y * (1 - d)) - (x * b) + attribute = attribute.gsub(Regexp.last_match[0], '') + continue = true + tm = getTransformationMatrixProduct(tm, [a, b, c, d, e, f]) + end + # skewX + if attribute =~ /skewX\(([-0-9.]+)\)/mi + c = ::Math::tan(Regexp.last_match[1].to_f * ::Math::PI / 180 ) # deg2rad + attribute = attribute.gsub(Regexp.last_match[0], '') + continue = true + tm = getTransformationMatrixProduct(tm, [1, 0, c, 1, 0, 0]) + end + # skewY + if attribute =~ /skewY\(([-0-9.]+)\)/mi + b = ::Math::tan(Regexp.last_match[1].to_f * ::Math::PI / 180 ) # deg2rad + attribute = attribute.gsub(Regexp.last_match[0], '') + continue = true + tm = getTransformationMatrixProduct(tm, [1, b, 0, 1, 0, 0]) + end + end + tm + end + + # + # Get the product of two SVG tranformation matrices + # [@param array :ta] first SVG tranformation matrix + # [@param array :tb] second SVG tranformation matrix + # [@return transformation array] + # [@since 5.0.000 (2010-05-02)] + # [@access protected] + # + def getTransformationMatrixProduct(ta, tb) + tm = [] + tm[0] = (ta[0] * tb[0]) + (ta[2] * tb[1]) + tm[1] = (ta[1] * tb[0]) + (ta[3] * tb[1]) + tm[2] = (ta[0] * tb[2]) + (ta[2] * tb[3]) + tm[3] = (ta[1] * tb[2]) + (ta[3] * tb[3]) + tm[4] = (ta[0] * tb[4]) + (ta[2] * tb[5]) + ta[4] + tm[5] = (ta[1] * tb[4]) + (ta[3] * tb[5]) + ta[5] + tm + end + + # + # Convert SVG transformation matrix to PDF. + # [@param array :tm] original SVG transformation matrix + # [@return array] transformation matrix + # [@access protected] + # [@since 5.0.000 (2010-05-02)] + # + def convertSVGtMatrix(tm) + a = tm[0] + b = -tm[1] + c = -tm[2] + d = tm[3] + e = getHTMLUnitToUnits(tm[4], 1, @svgunit, false) * @k + f = -getHTMLUnitToUnits(tm[5], 1, @svgunit, false) * @k + x = 0 + y = @h * @k + e = (x * (1 - a)) - (y * c) + e + f = (y * (1 - d)) - (x * b) + f + [a, b, c, d, e, f] + end + + # + # Apply SVG graphic transformation matrix. + # [@param array :tm] original SVG transformation matrix + # [@access protected] + # [@since 5.0.000 (2010-05-02)] + # + def svg_transform(tm) + transform(convertSVGtMatrix(tm)) + end + + # + # Apply the requested SVG styles (*** TO BE COMPLETED ***) + # [@param array :svgstyle] array of SVG styles to apply + # [@param array :prevsvgstyle] array of previous SVG style + # [@param int :x] X origin of the bounding box + # [@param int :y] Y origin of the bounding box + # [@param int :w] width of the bounding box + # [@param int :h] height of the bounding box + # [@param string :clip_function] clip function + # [@param array :clip_params] array of parameters for clipping function + # [@return object style] + # [@since 5.0.000 (2010-05-02)] + # [@access protected] + # + def setSVGStyles(svgstyle, prevsvgstyle, x=0, y=0, w=1, h=1, clip_function='', clip_params=[]) + objstyle = '' + return objstyle if svgstyle['opacity'].nil? + + # clip-path + if svgstyle['clip-path'] =~ /url\([\s]*\#([^\)]*)\)/mi + clip_path = @svgclippaths[Regexp.last_match[1]] + clip_path.each{ |cp| startSVGElementHandler(cp['name'], cp['attribs'], true) } + end + #setAlpha(1); # reset alpha + # opacity + setAlpha(svgstyle['opacity']) if svgstyle['opacity'] != 1 + + # color + fill_color = convert_html_color_to_dec_array(svgstyle['color']) + set_fill_color_array(fill_color) + # text color + text_color = convert_html_color_to_dec_array(svgstyle['text-color']) + set_text_color_array(text_color) + # clip + if svgstyle['clip'] =~ /rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/mi + top = Regexp.last_match[1] ? getHTMLUnitToUnits(Regexp.last_match[1], 0, @svgunit, false) : 0 + right = Regexp.last_match[2] ? getHTMLUnitToUnits(Regexp.last_match[2], 0, @svgunit, false) : 0 + bottom = Regexp.last_match[3] ? getHTMLUnitToUnits(Regexp.last_match[3], 0, @svgunit, false) : 0 + left = Regexp.last_match[4] ? getHTMLUnitToUnits(Regexp.last_match[4], 0, @svgunit, false) : 0 + cx = x + left + cy = y + top + cw = w - left - right + ch = h - top - bottom + if svgstyle['clip-rule'] == 'evenodd' + clip_rule = 'CNZ' + else + clip_rule = 'CEO' + end + rect(cx, cy, cw, ch, clip_rule, {}, {}) + end + # fill + if svgstyle['fill'] =~ /url\([\s]*\#([^\)]*)\)/mi + # gradient + gradient = @svggradients[Regexp.last_match[1]] + if gradient['xref'] + # reference to another gradient definition + newgradient = @svggradients[gradient['xref']].dup + newgradient['coords'] = gradient['coords'] + newgradient['mode'] = gradient['mode'] + newgradient['gradientUnits'] = gradient['gradientUnits'] + if gradient['gradientTransform'] + newgradient['gradientTransform'] = gradient['gradientTransform'] + end + gradient = newgradient + end + #save current Graphic State + out('q') + #set clipping area + if !clip_function.empty? && self.respond_to?(clip_function, true) + bbox = self.send(clip_function, *clip_params) + if bbox.is_a?(Array) && (bbox.size == 4) + x, y, w, h = bbox + end + end + if gradient['mode'] == 'measure' + if gradient['gradientTransform'] && !gradient['gradientTransform'].empty? + gtm = gradient['gradientTransform'] + # apply transformation matrix + xa = (gtm[0] * gradient['coords'][0]) + (gtm[2] * gradient['coords'][1]) + gtm[4] + ya = (gtm[1] * gradient['coords'][0]) + (gtm[3] * gradient['coords'][1]) + gtm[5] + xb = (gtm[0] * gradient['coords'][2]) + (gtm[2] * gradient['coords'][3]) + gtm[4] + yb = (gtm[1] * gradient['coords'][2]) + (gtm[3] * gradient['coords'][3]) + gtm[5] + if gradient['coords'][4] + gradient['coords'][4] = ::Math.sqrt(((gtm[0] * gradient['coords'][4]) ** 2) + ((gtm[1] * gradient['coords'][4]) ** 2)) + end + gradient['coords'][0] = xa + gradient['coords'][1] = ya + gradient['coords'][2] = xb + gradient['coords'][3] = yb + end + # convert SVG coordinates to user units + gradient['coords'][0] = getHTMLUnitToUnits(gradient['coords'][0], 0, @svgunit, false) + gradient['coords'][1] = getHTMLUnitToUnits(gradient['coords'][1], 0, @svgunit, false) + gradient['coords'][2] = getHTMLUnitToUnits(gradient['coords'][2], 0, @svgunit, false) + gradient['coords'][3] = getHTMLUnitToUnits(gradient['coords'][3], 0, @svgunit, false) + if gradient['coords'][4] + gradient['coords'][4] = getHTMLUnitToUnits(gradient['coords'][4], 0, @svgunit, false) + end + # shift units + if gradient['gradientUnits'] == 'objectBoundingBox' + # convert to SVG coordinate system + gradient['coords'][0] += x + gradient['coords'][1] += y + gradient['coords'][2] += x + gradient['coords'][3] += y + end + # calculate percentages + gradient['coords'][0] = (gradient['coords'][0] - x) / w + gradient['coords'][1] = (gradient['coords'][1] - y) / h + gradient['coords'][2] = (gradient['coords'][2] - x) / w + gradient['coords'][3] = (gradient['coords'][3] - y) / h + if gradient['coords'][4] + gradient['coords'][4] /= w + end + # fix values + gradient['coords'].each_with_index do |val, key| + if val < 0 + gradient['coords'][key] = 0 + elsif val > 1 + gradient['coords'][key] = 1 + end + end + if (gradient['type'] == 2) && (gradient['coords'][0] == gradient['coords'][2]) && (gradient['coords'][1] == gradient['coords'][3]) + # single color (no shading) + gradient['coords'][0] = 1 + gradient['coords'][1] = 0 + gradient['coords'][2] = 0.999 + gradient['coords'][3] = 0 + end + end + # swap Y coordinates + tmp = gradient['coords'][1] + gradient['coords'][1] = gradient['coords'][3] + gradient['coords'][3] = tmp + # set transformation map for gradient + if (gradient['type'] == 3) && (gradient['mode'] == 'measure') + # gradient is always circular + cy = @h - y - (gradient['coords'][1] * (w + h)) + out(sprintf('%.3f 0 0 %.3f %.3f %.3f cm', w * @k, w * @k, x * @k, cy * @k)) + else + out(sprintf('%.3f 0 0 %.3f %.3f %.3f cm', w * @k, h * @k, x * @k, (@h - (y + h)) * @k)) + end + + if gradient['stops'].size > 1 + gradient(gradient['type'], gradient['coords'], gradient['stops'], [], false) + end + elsif svgstyle['fill'] != 'none' + fill_color = convert_html_color_to_dec_array(svgstyle['fill']) + setAlpha(svgstyle['fill-opacity']) if svgstyle['fill-opacity'] != 1 + + set_fill_color_array(fill_color) + if svgstyle['fill-rule'] == 'evenodd' + objstyle << 'F*' + else + objstyle << 'F' + end + end + # stroke + if svgstyle['stroke'] != 'none' + stroke_style = { + 'color' => convert_html_color_to_dec_array(svgstyle['stroke']), + 'width' => getHTMLUnitToUnits(svgstyle['stroke-width'], 0, @svgunit, false), + 'cap' => svgstyle['stroke-linecap'], + 'join' => svgstyle['stroke-linejoin'] + } + if svgstyle['stroke-dasharray'] && !svgstyle['stroke-dasharray'].empty? && (svgstyle['stroke-dasharray'] != 'none') + stroke_style['dash'] = svgstyle['stroke-dasharray'] + end + set_line_style(stroke_style) + objstyle << 'D' + end + # font + unless svgstyle['font'].empty? + if svgstyle['font'] =~ /font-family[\s]*:[\s]*([^\s;"]*)/mi + font_family = Regexp.last_match[1].strip + else + font_family = svgstyle['font-family'] + end + if svgstyle['font'] =~ /font-size[\s]*:[\s]*([^\s;"]*)/mi + font_size = Regexp.last_match[1].strip + else + font_size = svgstyle['font-size'] + end + if svgstyle['font'] =~ /font-style[\s]*:[\s]*([^\s;"]*)/mi + font_style = Regexp.last_match[1].strip + else + font_style = svgstyle['font-style'] + end + if svgstyle['font'] =~ /font-weight[\s]*:[\s]*([^\s;"]*)/mi + font_weight = Regexp.last_match[1].strip + else + font_weight = svgstyle['font-weight'] + end + else + font_family = svgstyle['font-family'] + font_size = svgstyle['font-size'] + font_style = svgstyle['font-style'] + font_weight = svgstyle['font-weight'] + end + font_size = getHTMLUnitToUnits(font_size, prevsvgstyle['font-size'], @svgunit, false) * @k + case font_style + when 'italic' + font_style = 'I' + when 'oblique' + font_style = 'I' + else # 'normal' + font_style = '' + end + case font_weight + when 'bold', 'bolder' + font_style << 'B' + end + case svgstyle['text-decoration'] + when 'underline' + font_style << 'U' + when 'overline' + font_style << 'O' + when 'line-through' + font_style << 'D' + else # 'none' + end + + fontname = nil + fontslist = font_family.downcase.split(',') + fontslist.each {|font| + font = font.downcase.strip + if @fontlist.include?(font) or @fontkeys.include?(font) + fontname = font + break + end + } + + set_font(fontname || 'helvetica', font_style, font_size) + objstyle + end + + # + # Draws an SVG path + # [@param string :d] attribute d of the path SVG element + # [@param string :style] Style of rendering. Possible values are: + # + # [@return array of container box] measures (x, y, w, h) + # [@since 5.0.000 (2010-05-02)] + # [@access protected] + # + def svg_path(d, style='') + # set fill/stroke style + op = getPathPaintOperator(style, '') + return if op.empty? + + paths = d.scan(/([a-zA-Z])[\s]*([^a-zA-Z\"]*)/mi) + x = 0 + y = 0 + x1 = 0 + y1 = 0 + x2 = 0 + y2 = 0 + xmin = 2147483647 + xmax = 0 + ymin = 2147483647 + ymax = 0 + relcoord = false + # draw curve pieces + paths.each_with_index do |val, key| + # get curve type + cmd = val[0].strip + if cmd.downcase == cmd + # use relative coordinated instead of absolute + relcoord = true + xoffset = x + yoffset = y + else + relcoord = false + xoffset = 0 + yoffset = 0 + end + params = [] + if val[1] + # get curve parameters + rawparams = val[1].strip.scan(/-?[.\d]+/) + rawparams.each_with_index do |cp, ck| + params[ck] = getHTMLUnitToUnits(cp, 0, @svgunit, false) + end + end + case cmd.upcase + when 'M' # moveto + params.each_with_index do |cp, ck| + if (ck % 2) == 0 + x = cp + xoffset + else + y = cp + yoffset + if ck == 1 + outPoint(x, y) + else + outLine(x, y) + end + xmin = [xmin, x].min + ymin = [ymin, y].min + xmax = [xmax, x].max + ymax = [ymax, y].max + if relcoord + xoffset = x + yoffset = y + end + end + end + when 'L' # lineto + params.each_with_index do |cp, ck| + if (ck % 2) == 0 + x = cp + xoffset + else + y = cp + yoffset + outLine(x, y) + xmin = [xmin, x].min + ymin = [ymin, y].min + xmax = [xmax, x].max + ymax = [ymax, y].max + if relcoord + xoffset = x + yoffset = y + end + end + end + when 'H' # horizontal lineto + params.each_with_index do |cp, ck| + x = cp + xoffset + outLine(x, y) + xmin = [xmin, x].min + xmax = [xmax, x].max + if relcoord + xoffset = x + end + end + when 'V' # vertical lineto + params.each_with_index do |cp, ck| + y = cp + yoffset + outLine(x, y) + ymin = [ymin, y].min + ymax = [ymax, y].max + if relcoord + yoffset = y + end + end + when 'C' # curveto + params.each_with_index do |cp, ck| + params[ck] = cp + if ((ck + 1) % 6) == 0 + x1 = params[ck - 5] + xoffset + y1 = params[ck - 4] + yoffset + x2 = params[ck - 3] + xoffset + y2 = params[ck - 2] + yoffset + x = params[ck - 1] + xoffset + y = params[ck] + yoffset + outCurve(x1, y1, x2, y2, x, y) + xmin = [xmin, x, x1, x2].min + ymin = [ymin, y, y1, y2].min + xmax = [xmax, x, x1, x2].max + ymax = [ymax, y, y1, y2].max + if relcoord + xoffset = x + yoffset = y + end + end + end + when 'S' # shorthand/smooth curveto + params.each_with_index do |cp, ck| + params[ck] = cp + if ((ck + 1) % 4) == 0 + if (key > 0) && ((paths[key - 1][0].upcase == 'C') || (paths[key - 1][0].upcase == 'S')) + x1 = (2 * x) - x2 + y1 = (2 * y) - y2 + else + x1 = x + y1 = y + end + x2 = params[ck - 3] + xoffset + y2 = params[ck - 2] + yoffset + x = params[ck - 1] + xoffset + y = params[ck] + yoffset + outCurve(x1, y1, x2, y2, x, y) + xmin = [xmin, x, x1, x2].min + ymin = [ymin, y, y1, y2].min + xmax = [xmax, x, x1, x2].max + ymax = [ymax, y, y1, y2].max + if relcoord + xoffset = x + yoffset = y + end + end + end + when 'Q' # quadratic B�zier curveto + params.each_with_index do |cp, ck| + params[ck] = cp + if ((ck + 1) % 4) == 0 + # convert quadratic points to cubic points + x1 = params[ck - 3] + xoffset + y1 = params[ck - 2] + yoffset + xa = (x + (2 * x1)) / 3 + ya = (y + (2 * y1)) / 3 + x = params[ck - 1] + xoffset + y = params[ck] + yoffset + xb = (x + (2 * x1)) / 3 + yb = (y + (2 * y1)) / 3 + outCurve(xa, ya, xb, yb, x, y) + xmin = [xmin, x, xa, xb].min + ymin = [ymin, y, ya, yb].min + xmax = [xmax, x, xa, xb].max + ymax = [ymax, y, ya, yb].max + if relcoord + xoffset = x + yoffset = y + end + end + end + when 'T' # shorthand/smooth quadratic bezier curve + params.each_with_index do |cp, ck| + params[ck] = cp + if (ck % 2) != 0 + if (key > 0) && ((paths[key - 1][0].upcase == 'Q') || (paths[key - 1][0].upcase == 'T')) + x1 = (2 * x) - x1 + y1 = (2 * y) - y1 + else + x1 = x + y1 = y + end + # convert quadratic points to cubic points + xa = (x + (2 * x1)) / 3 + ya = (y + (2 * y1)) / 3 + x = params[ck - 1] + xoffset + y = params[ck] + yoffset + xb = (x + (2 * x1)) / 3 + yb = (y + (2 * y1)) / 3 + outCurve(xa, ya, xb, yb, x, y) + xmin = [xmin, x, x1, x2].min + ymin = [ymin, y, y1, y2].min + xmax = [xmax, x, x1, x2].max + ymax = [ymax, y, y1, y2].max + if relcoord + xoffset = x + yoffset = y + end + end + end + when 'A' # elliptical arc + params.each_with_index do |cp, ck| + params[ck] = cp + if ((ck + 1) % 7) == 0 + x0 = x + y0 = y + rx = params[ck - 6].abs + ry = params[ck - 5].abs + ang = -rawparams[ck - 4].to_f + angle = ang * ::Math::PI / 180 # deg2rad + fa = rawparams[ck - 3].to_i # large-arc-flag + fs = rawparams[ck - 2].to_i # sweep-flag + x = params[ck - 1] + xoffset + y = params[ck] + yoffset + cos_ang = ::Math::cos(angle) + sin_ang = ::Math::sin(angle) + a = (x0 - x) / 2 + b = (y0 - y) / 2 + xa = (a * cos_ang) - (b * sin_ang) + ya = (a * sin_ang) + (b * cos_ang) + rx2 = rx * rx + ry2 = ry * ry + xa2 = xa * xa + ya2 = ya * ya + delta = (xa2 / rx2) + (ya2 / ry2) + if delta > 1 + rx *= ::Math.sqrt(delta) + ry *= ::Math.sqrt(delta) + rx2 = rx * rx + ry2 = ry * ry + end + numerator = (rx2 * ry2) - (rx2 * ya2) - (ry2 * xa2) + if numerator < 0 + root = 0 + else + root = ::Math.sqrt(numerator / ((rx2 * ya2) + (ry2 * xa2))) + end + if fa == fs + root *= -1 + end + cax = root * ((rx * ya) / ry) + cay = -root * ((ry * xa) / rx) + # coordinates of ellipse center + cx = (cax * cos_ang) - (cay * sin_ang) + ((x0 + x) / 2) + cy = (cax * sin_ang) + (cay * cos_ang) + ((y0 + y) / 2) + # get angles + angs = getVectorsAngle(1, 0, ((xa - cax) / rx), ((cay - ya) / ry)) + dang = getVectorsAngle(((xa - cax) / rx), ((ya - cay) / ry), ((-xa - cax) / rx), ((-ya - cay) / ry)) + if (fs == 0) && (dang > 0) + dang -= (2 * ::Math::PI) + elsif (fs == 1) && (dang < 0) + dang += (2 * ::Math::PI) + end + angf = angs - dang + if (fs == 1) && (angs > angf) + tmp = angs + angs = angf + angf = tmp + end + angs = angs * 180 / Math::PI # rad2deg + angf = angf * 180 / Math::PI # rad2deg + pie = false + if (key + 1 < paths.size) && paths[key + 1][0] && (paths[key + 1][0].strip == 'z') + pie = true + end + outellipticalarc(cx, cy, rx, ry, ang, angs, angf, pie, 2) + outPoint(x, y) + xmin = [xmin, x].min + ymin = [ymin, y].min + xmax = [xmax, x].max + ymax = [ymax, y].max + if relcoord + xoffset = x + yoffset = y + end + end + end + when 'Z' + out('h') + end + end # end paths.each_with_index + + out(op) unless op.empty? + + [xmin, ymin, xmax - xmin, ymax - ymin] + end + + # + # Returns the angle in radiants between two vectors + # [@param int :x1] X coordiante of first vector point + # [@param int :y1] Y coordiante of first vector point + # [@param int :x2] X coordiante of second vector point + # [@param int :y2] Y coordiante of second vector point + # [@since 5.0.000 (2010-05-04)] + # [@access protected] + # + def getVectorsAngle(x1, y1, x2, y2) + dprod = (x1 * x2) + (y1 * y2) + dist1 = ::Math.sqrt((x1 * x1) + (y1 * y1)) + dist2 = ::Math.sqrt((x2 * x2) + (y2 * y2)) + angle = ::Math.acos((dprod / (dist1 * dist2)).round(3)) + if angle.nan? + angle = ::Math::PI + end + if ((x1 * y2) - (x2 * y1)) < 0 + angle *= -1 + end + angle + end + + # + # Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***) + # [@param string :name] The first parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters. + # [@param array :attribs] The second parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on. + # [@param bool :clipping] The third parameter, set clipping mode. + # [@since 5.0.000 (2010-05-02)] + # [@access protected] + # + def startSVGElementHandler(name, attribs, clipping = false) + # check if we are in clip mode + if @svgclipmode + @svgclippaths[@svgclipid] << {'name' => name, 'attribs' => attribs} + return + end + unless ['clipPath', 'linearGradient', 'radialGradient', 'stop'].include?(name) + @svgdefs[attribs['id']] = {'name' => name, 'attribs' => attribs} + return if @svgdefsmode + end + + prev_svgstyle = @svgstyles[@svgstyles.size - 1] # previous style + svgstyle = get_styling_properties(prev_svgstyle, attribs) # get styling properties + + # transformation matrix + tm = @svgstyles[@svgstyles.size - 1]['transfmatrix'] + if attribs['transform'] && !attribs['transform'].empty? + tm = getTransformationMatrixProduct(tm, getSVGTransformMatrix(attribs['transform'])) + end + svgstyle['transfmatrix'] = tm + # process tag + case name + when 'defs' + @svgdefsmode = true + when 'clipPath' # clipPath + @svgclipmode = true + @svgclipid = attribs['id'] + @svgclippaths[@svgclipid] = [] + when 'svg' # start of SVG object + when 'g' # group together related graphics elements + @svgstyles.push(svgstyle) + start_transform() + setSVGStyles(svgstyle, prev_svgstyle) + when 'linearGradient' + @svggradientid = attribs['id'] + @svggradients[@svggradientid] = {} + @svggradients[@svggradientid]['type'] = 2 + @svggradients[@svggradientid]['stops'] = [] + if attribs['gradientUnits'] + @svggradients[@svggradientid]['gradientUnits'] = attribs['gradientUnits'] + else + @svggradients[@svggradientid]['gradientUnits'] = 'objectBoundingBox' + end + #attribs['spreadMethod'] + x1 = attribs['x1'] ? attribs['x1'].to_f : 0 + y1 = attribs['y1'] ? attribs['y1'].to_f : 0 + x2 = attribs['x2'] ? attribs['x2'].to_f : 1 + y2 = attribs['y2'] ? attribs['y2'].to_f : 0 + if attribs['x1'] && (attribs['x1'][-1] != '%') + @svggradients[@svggradientid]['mode'] = 'measure' + else + @svggradients[@svggradientid]['mode'] = 'percentage' + end + if attribs['gradientTransform'] + @svggradients[@svggradientid]['gradientTransform'] = getSVGTransformMatrix(attribs['gradientTransform']) + end + @svggradients[@svggradientid]['coords'] = [x1, y1, x2, y2] + if (attribs['href'] && !attribs['href'].empty?) || (attribs['xlink:href'] && !attribs['xlink:href'].empty?) + href = attribs['href'] ? 'href' : 'xlink:href' + # gradient is defined on another place + @svggradients[@svggradientid]['xref'] = attribs[href][1..-1] + end + when 'radialGradient' + @svggradientid = attribs['id'] + @svggradients[@svggradientid] = {} + @svggradients[@svggradientid]['type'] = 3 + @svggradients[@svggradientid]['stops'] = [] + if attribs['gradientUnits'] + @svggradients[@svggradientid]['gradientUnits'] = attribs['gradientUnits'] + else + @svggradients[@svggradientid]['gradientUnits'] = 'objectBoundingBox' + end + #attribs['spreadMethod'] + cx = attribs['cx'] ? attribs['cx'].to_f : 0.5 + cy = attribs['cy'] ? attribs['cy'].to_f : 0.5 + fx = attribs['fx'] ? attribs['fx'].to_f : cx + fy = attribs['fy'] ? attribs['fy'].to_f : cy + r = attribs['r'] ? attribs['r'].to_f : 0.5 + if attribs['cx'] && (attribs['cx'][-1] != '%') + @svggradients[@svggradientid]['mode'] = 'measure' + else + @svggradients[@svggradientid]['mode'] = 'percentage' + end + if attribs['gradientTransform'] + @svggradients[@svggradientid]['gradientTransform'] = getSVGTransformMatrix(attribs['gradientTransform']) + end + @svggradients[@svggradientid]['coords'] = [cx, cy, fx, fy, r] + if (attribs['href'] && !attribs['href'].empty?) || (attribs['xlink:href'] && !attribs['xlink:href'].empty?) + href = attribs['href'] ? 'href' : 'xlink:href' + # gradient is defined on another place + @svggradients[@svggradientid]['xref'] = attribs[href][1..-1] + end + when 'stop' + # gradient stops + if attribs['offset'][-1] == '%' + offset = attribs['offset'][0...-1].to_f / 100 + else + offset = attribs['offset'].to_f + if offset > 1 + offset /= 100 + end + end + stop_color = svgstyle['stop-color'] ? convert_html_color_to_dec_array(svgstyle['stop-color']) : 'black' + opacity = svgstyle['stop-opacity'] ? svgstyle['stop-opacity'].to_f : 1 + @svggradients[@svggradientid]['stops'] << {'offset' => offset, 'color' => stop_color, 'opacity' => opacity} + when 'path' # paths + d = attribs['d'].to_s.strip + if clipping + svg_transform(tm) + svg_path(d, 'CNZ') + else + start_transform() + svg_transform(tm) + obstyle = setSVGStyles(svgstyle, prev_svgstyle, 0, 0, 1, 1, 'svg_path', [d, 'CNZ']) + unless obstyle.empty? + svg_path(d, obstyle) + end + stop_transform() + end + # shapes + when 'rect' + x = attribs['x'] ? getHTMLUnitToUnits(attribs['x'], 0, @svgunit, false) : 0 + y = attribs['y'] ? getHTMLUnitToUnits(attribs['y'], 0, @svgunit, false) : 0 + w = attribs['width'] ? getHTMLUnitToUnits(attribs['width'], 0, @svgunit, false) : 0 + h = attribs['height'] ? getHTMLUnitToUnits(attribs['height'], 0, @svgunit, false) : 0 + rx = attribs['rx'] ? getHTMLUnitToUnits(attribs['rx'], 0, @svgunit, false) : 0 + ry = attribs['ry'] ? getHTMLUnitToUnits(attribs['ry'], 0, @svgunit, false) : rx + # svgstyle['stroke'] + # svgstyle['fill'] + if clipping + svg_transform(tm) + rounded_rect_xy(x, y, w, h, rx, ry, '1111', 'CNZ') + else + start_transform() + svg_transform(tm) + obstyle = setSVGStyles(svgstyle, prev_svgstyle, x, y, w, h, 'rounded_rect_xy', [x, y, w, h, rx, ry, '1111', 'CNZ']) + unless obstyle.empty? + # obstyle = D + rounded_rect_xy(x, y, w, h, rx, ry, '1111', obstyle) + end + stop_transform() + end + when 'circle' + cx = attribs['cx'] ? getHTMLUnitToUnits(attribs['cx'], 0, @svgunit, false) : 0 + cy = attribs['cy'] ? getHTMLUnitToUnits(attribs['cy'], 0, @svgunit, false) : 0 + r = attribs['r'] ? getHTMLUnitToUnits(attribs['r'], 0, @svgunit, false) : 0 + x = cx - r + y = cy - r + w = 2 * r + h = w + if clipping + svg_transform(tm) + circle(cx, cy, r, 0, 360, 'CNZ', nil, nil, 8) + else + start_transform() + svg_transform(tm) + obstyle = setSVGStyles(svgstyle, prev_svgstyle, x, y, w, h, 'circle', [cx, cy, r, 0, 360, 'CNZ']) + unless obstyle.empty? + circle(cx, cy, r, 0, 360, obstyle, nil, nil, 8) + end + stop_transform() + end + when 'ellipse' + cx = attribs['cx'] ? getHTMLUnitToUnits(attribs['cx'], 0, @svgunit, false) : 0 + cy = attribs['cy'] ? getHTMLUnitToUnits(attribs['cy'], 0, @svgunit, false) : 0 + rx = attribs['rx'] ? getHTMLUnitToUnits(attribs['rx'], 0, @svgunit, false) : 0 + ry = attribs['ry'] ? getHTMLUnitToUnits(attribs['ry'], 0, @svgunit, false) : 0 + x = cx - rx + y = cy - ry + w = 2 * rx + h = 2 * ry + if clipping + svg_transform(tm) + ellipse(cx, cy, rx, ry, 0, 0, 360, 'CNZ', nil, nil, 8) + else + start_transform() + svg_transform(tm) + obstyle = setSVGStyles(svgstyle, prev_svgstyle, x, y, w, h, 'ellipse', [cx, cy, rx, ry, 0, 0, 360, 'CNZ']) + unless obstyle.empty? + ellipse(cx, cy, rx, ry, 0, 0, 360, obstyle, nil, nil, 8) + end + stop_transform() + end + when 'line' + x1 = attribs['x1'] ? getHTMLUnitToUnits(attribs['x1'], 0, @svgunit, false) : 0 + y1 = attribs['y1'] ? getHTMLUnitToUnits(attribs['y1'], 0, @svgunit, false) : 0 + x2 = attribs['x2'] ? getHTMLUnitToUnits(attribs['x2'], 0, @svgunit, false) : 0 + y2 = attribs['y2'] ? getHTMLUnitToUnits(attribs['y2'], 0, @svgunit, false) : 0 + x = x1 + y = y1 + w = (x2 - x1).abs + h = (y2 - y1).abs + unless clipping + start_transform() + svg_transform(tm) + obstyle = setSVGStyles(svgstyle, prev_svgstyle, x, y, w, h, 'line', [x1, y1, x2, y2]) + line(x1, y1, x2, y2) + stop_transform() + end + when 'polyline', 'polygon' + points = attribs['points'] ? attribs['points'] : '0 0' + points = points.strip + # note that point may use a complex syntax not covered here + points = points.split(/[\,\s]+/mi) + if points.size >= 4 + p = [] + xmin = 2147483647 + xmax = 0 + ymin = 2147483647 + ymax = 0 + points.each_with_index do |val, key| + p[key] = getHTMLUnitToUnits(val, 0, @svgunit, false) + if (key % 2) == 0 + # X coordinate + xmin = [xmin, p[key]].min + xmax = [xmax, p[key]].max + else + # Y coordinate + ymin = [ymin, p[key]].min + ymax = [ymax, p[key]].max + end + end + x = xmin + y = ymin + w = xmax - xmin + h = ymax - ymin + if name == 'polyline' + start_transform() + svg_transform(tm) + obstyle = setSVGStyles(svgstyle, prev_svgstyle, x, y, w, h, 'poly_line', [p, 'CNZ']) + poly_line(p, obstyle) + stop_transform() + else # polygon + if clipping + svg_transform(tm) + polygon(p, 'CNZ', nil, nil, true) + else + start_transform() + svg_transform(tm) + obstyle = setSVGStyles(svgstyle, prev_svgstyle, x, y, w, h, 'polygon', [p, 'CNZ']) + unless obstyle.empty? + polygon(p, obstyle, nil, nil, true) + end + stop_transform() + end + end + end + when 'image' # image + if (attribs['href'] && !attribs['href'].empty?) || (attribs['xlink:href'] && !attribs['xlink:href'].empty?) + href = attribs['href'] ? 'href' : 'xlink:href' + x = attribs['x'] ? getHTMLUnitToUnits(attribs['x'], 0, @svgunit, false) : 0 + y = attribs['y'] ? getHTMLUnitToUnits(attribs['y'], 0, @svgunit, false) : 0 + w = attribs['width'] ? getHTMLUnitToUnits(attribs['width'], 0, @svgunit, false) : 0 + h = attribs['height'] ? getHTMLUnitToUnits(attribs['height'], 0, @svgunit, false) : 0 + img = attribs[href] + unless clipping + start_transform() + svg_transform(tm) + obstyle = setSVGStyles(svgstyle, prev_svgstyle, x, y, w, h) + # fix image path + if !empty_string(@svgdir) && ((img[0, 1] == '.') || (File.basename(img) == img)) + # replace relative path with full server path + img = @svgdir + '/' + img + end + #if (img[0, 1] == '/') && (_SERVER['DOCUMENT_ROOT'] != '/') + # findroot = strpos(img, _SERVER['DOCUMENT_ROOT']) + # if (findroot === false) || (findroot > 1) + # # replace relative path with full server path + # img = _SERVER['DOCUMENT_ROOT'].img + # end + #end + #img = urldecode(img) + img.gsub!(/%([0-9a-fA-F]{2})/){$1.hex.chr} + + #testscrtype = parse_url(img) + #if testscrtype['query'].nil? || testscrtype['query'].empty? + # convert URL to server path + # img = img.gsub(@@k_path_url, @@k_path_main) + #end + image(img, x, y, w, h) + stop_transform() + end + end + when 'text', 'tspan' # text + # only basic support - advanced features must be implemented + x = attribs['x'] ? getHTMLUnitToUnits(attribs['x'], 0, @svgunit, false) : 0 + y = attribs['y'] ? getHTMLUnitToUnits(attribs['y'], 0, @svgunit, false) : 0 + svgstyle['text-color'] = svgstyle['fill'] + @svgtext = '' + start_transform() + svg_transform(tm) + obstyle = setSVGStyles(svgstyle, prev_svgstyle, x, y, 1, 1) + set_xy(x, y, true) + @svgstyles.push(svgstyle) + when 'use' # use + if attribs['href'] || attribs['xlink:href'] + href = attribs['href'] ? 'href' : 'xlink:href' + use = @svgdefs[attribs[href][1..-1]] + # Most attributes (except for x, y, width, height and (xlink:)href) do not override those set in the ancestor. + ['x', 'y', 'width', 'height', 'hred', 'xlink:href'].each do |attr| + use['attribs'].delete(attr) if attribs[attr] + end + attribs = attribs.merge(use['attribs']) + startSVGElementHandler(use['name'], use['attribs']) + end + end + end + + def get_styling_properties(prev_svgstyle, attribs) + svgstyle = {} # current style + if attribs['style'] + attribs['style'] = ';' + attribs['style'] + end + prev_svgstyle.each do |key, val| + if attribs[key] + if attribs[key] == 'inherit' + svgstyle[key] = val + else + svgstyle[key] = attribs[key] + end + elsif attribs['style'] + # CSS style syntax + if attribs['style'] =~ /[;\s]#{key}\s*:\s*([^;]*)/mi + if Regexp.last_match[1] == 'inherit' + svgstyle[key] = val + else + svgstyle[key] = Regexp.last_match[1] + end + else + # default value + svgstyle[key] = @svgstyles[0][key] + end + else + if @svginheritprop.include? key + # inherit previous value + svgstyle[key] = val + else + # default value + svgstyle[key] = @svgstyles[0][key] + end + end + end + svgstyle + end + private :get_styling_properties + + # + # Sets the closing SVG element handler function for the XML parser. + # [@param string :name] The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters. + # [@since 5.0.000 (2010-05-02)] + # [@access protected] + # + def endSVGElementHandler(name) + case name + when 'defs' + @svgdefsmode = false + when 'clipPath' # clipPath + @svgclipmode = false + when 'g' + return if @svgdefsmode + + # ungroup: remove last style from array + @svgstyles.pop + stop_transform() + when 'text', 'tspan' + return if @svgdefsmode + + anchor = @svgstyles.last['text-anchor'] + # print text + cell(0, 0, @svgtext.strip, 0, 0, '', 0, '', 0, false, 'L', 'T', anchor) + @svgstyles.pop + stop_transform() + end + end + + # + # Sets the character data handler function for the XML parser. + # [@param string :data] The second parameter, data, contains the character data as a string. + # [@since 5.0.000 (2010-05-02)] + # [@access protected] + # + def segSVGContentHandler(data) + @svgtext << data + end + + # --- END SVG METHODS ----------------------------- + end # END OF RBPDF CLASS #TODO 2007-05-25 (EJM) Level=0 - diff --git a/rbpdf.gemspec b/rbpdf.gemspec index 45a5f9b..003074f 100644 --- a/rbpdf.gemspec +++ b/rbpdf.gemspec @@ -33,6 +33,7 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency "htmlentities" spec.add_runtime_dependency "rbpdf-font", "~> 1.19.0" + spec.add_runtime_dependency "rexml" spec.required_ruby_version = '>= 2.3.0' spec.add_development_dependency "bundler" diff --git a/test/rbpdf_cell_test.rb b/test/rbpdf_cell_test.rb index 5a11407..cacecaa 100644 --- a/test/rbpdf_cell_test.rb +++ b/test/rbpdf_cell_test.rb @@ -1,4 +1,4 @@ -# Copyright (c) 2011-2018 NAITOH Jun +# Copyright (c) 2011-2023 NAITOH Jun # Released under the MIT license # http://www.opensource.org/licenses/MIT @@ -71,6 +71,60 @@ class RbpdfTest < Test::Unit::TestCase # ET end + test "getCellCode text-anchor start test" do + pdf = RBPDF.new('P', 'mm', 'A4', true, "UTF-8", true) + pdf.add_page() + content = [] + + contents = pdf.send(:getCellCode, 10, 10, 'abc', 'LTRB',0 , '', 0, nil, 0, false, '', '', 'start') + contents.each_line {|line| content.push line.chomp } + + assert_equal 2, content.length + assert_equal "0.57 w 0 J 0 j [] 0 d 0 G 0 g", content[0] + assert_match(/BT 31.1[89] 795.17 Td 0 Tr 0.00 w \[\(abc\)\] TJ ET/, content[1]) + # BT + # 31.19 795.17 Td + # 0 Tr 0.00 w + # [(abc)] TJ + # ET + end + + test "getCellCode text-anchor middle test" do + pdf = RBPDF.new('P', 'mm', 'A4', true, "UTF-8", true) + pdf.add_page() + content = [] + + contents = pdf.send(:getCellCode, 10, 10, 'abc', 'LTRB',0 , '', 0, nil, 0, false, '', '', 'middle') + contents.each_line {|line| content.push line.chomp } + + assert_equal 2, content.length + assert_equal "0.57 w 0 J 0 j [] 0 d 0 G 0 g", content[0] + assert_match(/BT 18.68 795.17 Td 0 Tr 0.00 w \[\(abc\)\] TJ ET/, content[1]) + # BT + # 18.68 795.17 Td + # 0 Tr 0.00 w + # [(abc)] TJ + # ET + end + + test "getCellCode text-anchor end test" do + pdf = RBPDF.new('P', 'mm', 'A4', true, "UTF-8", true) + pdf.add_page() + content = [] + + contents = pdf.send(:getCellCode, 10, 10, 'abc', 'LTRB',0 , '', 0, nil, 0, false, '', '', 'end') + contents.each_line {|line| content.push line.chomp } + + assert_equal 2, content.length + assert_equal "0.57 w 0 J 0 j [] 0 d 0 G 0 g", content[0] + assert_match(/BT 6.17 795.17 Td 0 Tr 0.00 w \[\(abc\)\] TJ ET/, content[1]) + # BT + # 6.17 795.17 Td + # 0 Tr 0.00 w + # [(abc)] TJ + # ET + end + test "getCellCode link url test" do pdf = RBPDF.new('P', 'mm', 'A4', true, "UTF-8", true) pdf.add_page() diff --git a/test/rbpdf_examples_test.rb b/test/rbpdf_examples_test.rb index 0777d93..4397337 100644 --- a/test/rbpdf_examples_test.rb +++ b/test/rbpdf_examples_test.rb @@ -60,6 +60,7 @@ class RbpdfTest < Test::Unit::TestCase '054 : XHTML Form' => '054', '055 : Display all characters available on core fonts.' => '055', '057 : Cell vertical alignments' => '057', + '058 : SVG Image' => '058', '059 : Table Of Content using HTML templates.' => '059', '060 : Advanced page settings.' => '060', '061 : XHTML + CSS' => '061', diff --git a/test/rbpdf_graphic_func_test.rb b/test/rbpdf_graphic_func_test.rb index 3e81bd7..58b5fd3 100644 --- a/test/rbpdf_graphic_func_test.rb +++ b/test/rbpdf_graphic_func_test.rb @@ -77,4 +77,186 @@ def getPageBuffer(page) assert_equal "226.77 756.85 l", content[before_size + 6] # outLine(x2, y2) assert_equal "S", content[before_size + 7] end + + test "rect basic test" do + pdf = MYPDF.new + pdf.add_page() + page = pdf.get_page + contents = pdf.getPageBuffer(page) + before_size = contents.split("\n").size + pdf.putshaders + style = { 'L' => nil, 'T' => nil, 'R' => nil, 'B' => nil } + pdf.rect(30, 40, 60, 60, '', style) + + content = [] + contents = pdf.getPageBuffer(page) + contents.each_line {|line| content.push line.chomp } + + assert_equal "85.04 728.50 170.08 -170.08 re S", content[before_size] # outRect(x, y, w, h, op) + end + + test "rect with style & fill_color test" do + pdf = MYPDF.new + pdf.add_page() + page = pdf.get_page + contents = pdf.getPageBuffer(page) + before_size = contents.split("\n").size + pdf.putshaders + pdf.rect(30, 40, 60, 60, 'F', {}, [220, 220, 200]) + + content = [] + contents = pdf.getPageBuffer(page) + contents.each_line {|line| content.push line.chomp } + assert_equal "0.863 0.863 0.784 rg", content[before_size] # SetFillColorArray(fill_color) + assert_equal "85.04 728.50 170.08 -170.08 re f", content[before_size + 1] # outRect(x, y, w, h, op) + end + + test "rect with border_style 'all' test" do + pdf = MYPDF.new + pdf.add_page() + page = pdf.get_page + contents = pdf.getPageBuffer(page) + before_size = contents.split("\n").size + pdf.putshaders + + style = {'width' => 0.1, 'cap' => 'square', 'join' => 'round', 'dash' => 0, 'color' => [0, 64, 128]} + pdf.rect(30, 40, 60, 60, '', { 'all' => style }) + + content = [] + contents = pdf.getPageBuffer(page) + contents.each_line {|line| content.push line.chomp } + + # set_line_style start + assert_equal "0.28 w", content[before_size] # 'width' => 0.1 + assert_equal "2 J", content[before_size + 1] # 'cap' => 'square' + assert_equal "1 j", content[before_size + 2] # 'join' => 'round' + assert_equal "[] 0.00 d", content[before_size + 3] # 'dash' => 0 + assert_equal "0.000 0.251 0.502 RG ", content[before_size + 4] # 'color' => [0, 255, 0] + # set_line_style end + + assert_equal "85.04 728.50 170.08 -170.08 re S", content[before_size + 5] # outRect(x, y, w, h, op) + end + + test "rect with border_style 'LTRB' test" do + pdf = MYPDF.new + pdf.add_page() + page = pdf.get_page + contents = pdf.getPageBuffer(page) + before_size = contents.split("\n").size + pdf.putshaders + + style = { 'L' => {'width' => 0.1}, 'T' => {'width' => 0.1}, 'R' => {'width' => 0.1}, 'B' => {'width' => 0.1} } + pdf.rect(30, 40, 60, 60, '', style) + + content = [] + contents = pdf.getPageBuffer(page) + contents.each_line {|line| content.push line.chomp } + + assert_equal "85.04 728.50 170.08 -170.08 re S", content[before_size] # outRect(x, y, w, h, op) + + # line start + assert_equal "0.28 w", content[before_size + 1] # 'width' => 0.1 + assert_equal "85.04 728.50 m", content[before_size + 2] # outPoint(x1, y1) + assert_equal "85.04 558.43 l", content[before_size + 3] # outLine(x2, y2) + assert_equal "S", content[before_size + 4] + + assert_equal "0.28 w", content[before_size + 5] # 'width' => 0.1 + assert_equal "85.04 728.50 m", content[before_size + 6] # outPoint(x1, y1) + assert_equal "255.12 728.50 l", content[before_size + 7] # outLine(x2, y2) + assert_equal "S", content[before_size + 8] + + assert_equal "0.28 w", content[before_size + 9] # 'width' => 0.1 + assert_equal "255.12 728.50 m", content[before_size + 10] # outPoint(x1, y1) + assert_equal "255.12 558.43 l", content[before_size + 11] # outLine(x2, y2) + assert_equal "S", content[before_size + 12] + + assert_equal "0.28 w", content[before_size + 13] # 'width' => 0.1 + assert_equal "85.04 558.43 m", content[before_size + 14] # outPoint(x1, y1) + assert_equal "255.12 558.43 l", content[before_size + 15] # outLine(x2, y2) + assert_equal "S", content[before_size + 16] + # line end + end + + test "rounded_rect basic test" do + pdf = MYPDF.new + pdf.add_page() + page = pdf.get_page + contents = pdf.getPageBuffer(page) + before_size = contents.split("\n").size + pdf.putshaders + style = { 'L' => nil, 'T' => nil, 'R' => nil, 'B' => nil } + pdf.rounded_rect(50, 255, 40, 30, 6.50) + + content = [] + contents = pdf.getPageBuffer(page) + contents.each_line {|line| content.push line.chomp } + + assert_equal "160.16 119.06 m", content[before_size] # outPoint(x + rx, y) + assert_equal "236.69 119.06 l", content[before_size + 1] # outLine(xc, y) + assert_equal "246.87 119.06 255.12 110.81 255.12 100.63 c", content[before_size + 2] # outCurve(xc + (rx * myArc), yc - ry, xc + rx, yc - (ry * myArc), xc + rx, yc) + assert_equal "255.12 52.44 l", content[before_size + 3] # outLine(x + w, yc) + assert_equal "255.12 42.27 246.87 34.02 236.69 34.02 c", content[before_size + 4] # outCurve(xc + rx, yc + (ry * myArc), xc + (rx * myArc), yc + ry, xc, yc + ry) + assert_equal "160.16 34.02 l", content[before_size + 5] # outLine(xc, y + h) + assert_equal "149.98 34.02 141.73 42.27 141.73 52.44 c", content[before_size + 6] # outCurve(xc - (rx * myArc), yc + ry, xc - rx, yc + (ry * myArc), xc - rx, yc) + assert_equal "141.73 100.63 l", content[before_size + 7] # outLine(x, yc) + assert_equal "141.73 110.81 149.98 119.06 160.16 119.06 c", content[before_size + 8] # outCurve(xc - rx, yc - (ry * myArc), xc - (rx * myArc), yc - ry, xc, yc - ry) + assert_equal "S", content[before_size + 9] + end + + test "polygon basic test" do + pdf = MYPDF.new + pdf.add_page() + page = pdf.get_page + contents = pdf.getPageBuffer(page) + before_size = contents.split("\n").size + pdf.putshaders + style = { 'L' => nil, 'T' => nil, 'R' => nil, 'B' => nil } + pdf.polygon([5,135,45,135,15,165]) + + content = [] + contents = pdf.getPageBuffer(page) + contents.each_line {|line| content.push line.chomp } + + # draw = true + assert_equal "14.17 459.21 m", content[before_size] # outPoint(p[0], p[1]) + assert_equal "127.56 459.21 l", content[before_size + 1] # outLine(p[i], p[i + 1]) + assert_equal "42.52 374.17 l", content[before_size + 2] # outLine(p[i], p[i + 1]) + assert_equal "14.17 459.21 l", content[before_size + 3] # outLine(p[i], p[i + 1]) + assert_equal "127.56 459.21 l", content[before_size + 4] # outLine(p[i], p[i + 1]) + assert_equal "S", content[before_size + 5] + end + + test "polygon line_style test" do + pdf = MYPDF.new + pdf.add_page() + page = pdf.get_page + contents = pdf.getPageBuffer(page) + before_size = contents.split("\n").size + pdf.putshaders + style = { 'L' => nil, 'T' => nil, 'R' => nil, 'B' => nil } + style6 = {'width' => 0.5} + pdf.polygon([5,135,45,135,15,165], 'D', [style6, 0, 0]) + + content = [] + contents = pdf.getPageBuffer(page) + contents.each_line {|line| content.push line.chomp } + # draw = true + assert_equal "14.17 459.21 m", content[before_size] # outPoint(p[0], p[1]) + assert_equal "S", content[before_size + 1] + + assert_equal "1.42 w", content[before_size + 2] + assert_equal "14.17 459.21 m", content[before_size + 3] # outLine(p[i], p[i + 1]) + assert_equal "127.56 459.21 l", content[before_size + 4] # outLine(p[i], p[i + 1]) + assert_equal "S", content[before_size + 5] + assert_equal "127.56 459.21 m", content[before_size + 6] # outLine(p[i], p[i + 1]) + assert_equal "S", content[before_size + 7] + + assert_equal "1.42 w", content[before_size + 8] + assert_equal "14.17 459.21 m", content[before_size + 9] # outLine(p[i], p[i + 1]) + assert_equal "127.56 459.21 l", content[before_size + 10] # outLine(p[i], p[i + 1]) + assert_equal "S", content[before_size + 11] + + assert_equal "127.56 459.21 m", content[before_size + 12] # outLine(p[i], p[i + 1]) + assert_equal "S", content[before_size + 13] + end end diff --git a/test/rbpdf_image_svg_func_test.rb b/test/rbpdf_image_svg_func_test.rb new file mode 100644 index 0000000..69868cc --- /dev/null +++ b/test/rbpdf_image_svg_func_test.rb @@ -0,0 +1,157 @@ +# coding: ASCII-8BIT +# Copyright (c) 2011-2023 NAITOH Jun +# Released under the MIT license +# http://www.opensource.org/licenses/MIT + +require 'test_helper' + +class RbpdfTest < Test::Unit::TestCase + class MYPDF < RBPDF + def parse_svg_tag_attributes(file, w, h) + super + end + + def get_styling_properties(prev_svgstyle, attribs) + super + end + + def svg_path(d, style='') + super + end + end + + test "SVG parse_svg_tag_attributes empty file test" do + tf = Tempfile.open(['test', '.svg']) do |fp| + fp.puts '' + fp + end + + pdf = MYPDF.new + assert_raise(RBPDFError) {pdf.parse_svg_tag_attributes(tf.path, 0, 0)} + end + + test "SVG parse_svg_tag_attributes (no x y width height viewBox) test" do + tf = Tempfile.open(['test', '.svg']) do |fp| + fp.puts '' + fp + end + + pdf = MYPDF.new + w, h, ox, oy, ow, oh, aspect_ratio_align, aspect_ratio_ms = pdf.parse_svg_tag_attributes(tf.path, 0, 0) + assert_equal 0, w + assert_equal 0, h + assert_equal 0, ox + assert_equal 0, oy + assert_equal nil, ow + assert_equal nil, oh + assert_equal "xMidYMid", aspect_ratio_align + assert_equal "meet", aspect_ratio_ms + end + + test "SVG parse_svg_tag_attributes x y (no width height) test" do + tf = Tempfile.open(['test', '.svg']) do |fp| + fp.puts '' + fp + end + + pdf = MYPDF.new + w, h, ox, oy, ow, oh, aspect_ratio_align, aspect_ratio_ms = pdf.parse_svg_tag_attributes(tf.path, 0, 0) + assert_equal 0, w + assert_equal 0, h + assert_equal 3.5277777777777772, ox + assert_equal 7.0555555555555545, oy + assert_equal nil, ow + assert_equal nil, oh + assert_equal "xMidYMid", aspect_ratio_align + assert_equal "meet", aspect_ratio_ms + end + + test "SVG parse_svg_tag_attributes x y (width height 100%) test" do + tf = Tempfile.open(['test', '.svg']) do |fp| + fp.puts '' + fp + end + + pdf = MYPDF.new + w, h, ox, oy, ow, oh, aspect_ratio_align, aspect_ratio_ms = pdf.parse_svg_tag_attributes(tf.path, 10, 20) + assert_equal 10.0, w + assert_equal 20.0, h + assert_equal 3.5277777777777772, ox + assert_equal 7.0555555555555545, oy + assert_equal 10.0, ow + assert_equal 20.0, oh + assert_equal "xMidYMid", aspect_ratio_align + assert_equal "meet", aspect_ratio_ms + end + + test "SVG parse_svg_tag_attributes x y width height test" do + tf = Tempfile.open(['test', '.svg']) do |fp| + fp.puts '' + fp + end + + pdf = MYPDF.new + w, h, ox, oy, ow, oh, aspect_ratio_align, aspect_ratio_ms = pdf.parse_svg_tag_attributes(tf.path, 0, 0) + assert_equal 70.55555555555554, w + assert_equal 70.55555555555554, h + assert_equal 3.5277777777777772, ox + assert_equal 7.0555555555555545, oy + assert_equal 70.55555555555554, ow + assert_equal 70.55555555555554, oh + assert_equal "xMidYMid", aspect_ratio_align + assert_equal "meet", aspect_ratio_ms + end + + test "SVG parse_svg_tag_attributes viewBox test" do + datas = [ + '', + '', + ] + + datas.each do |data| + tf = Tempfile.open(['test', '.svg']) do |fp| + fp.puts data + fp + end + pdf = MYPDF.new + w, h, ox, oy, ow, oh, aspect_ratio_align, aspect_ratio_ms = pdf.parse_svg_tag_attributes(tf.path, 0, 0) + assert_equal 22.577777777777776, w + assert_equal 28.222222222222218, h + assert_equal 0, ox + assert_equal 0, oy + assert_equal 22.577777777777776, ow + assert_equal 28.222222222222218, oh + assert_equal "xMidYMid", aspect_ratio_align + assert_equal "meet", aspect_ratio_ms + end + end + + test "SVG get_styling_properties style test" do + datas = [ + {input: {"style"=>"stroke: black; fill: none"}, + output: {"fill"=>"none", "stroke"=>"black"}}, + {input: {"style"=>"stop-color: rgb(83, 25, 113); stop-opacity: 1;"}, + output: {"stop-color"=>"rgb(83, 25, 113)", "stop-opacity"=>"1"}}, + ] + + datas.each do |data| + pdf = MYPDF.new + prev_svgstyle = pdf.instance_variable_get('@svgstyles').last + attrs = data[:input] + svgstyle = pdf.get_styling_properties(prev_svgstyle, attrs) + + diff = svgstyle.to_a - prev_svgstyle.to_a + assert_equal(data[:output], Hash[*diff.flatten]) + end + end + + test "SVG svg_path test" do + pdf = MYPDF.new + x, y, w, h = pdf.svg_path("M30,1h40l29,29v40l-29,29h-40l-29.0-29v-40z", "D") + + assert_equal 0.35277777777777963, x + assert_equal 0.35277777777777775, y + assert_equal 34.572222222222216, w + assert_equal 34.57222222222222, h + end +end diff --git a/test/rbpdf_image_svg_test.rb b/test/rbpdf_image_svg_test.rb new file mode 100644 index 0000000..f93fb2d --- /dev/null +++ b/test/rbpdf_image_svg_test.rb @@ -0,0 +1,61 @@ +# coding: ASCII-8BIT +# Copyright (c) 2011-2023 NAITOH Jun +# Released under the MIT license +# http://www.opensource.org/licenses/MIT + +require 'test_helper' + +class RbpdfTest < Test::Unit::TestCase + class MYPDF < RBPDF + def startSVGElementHandler(name, attribs, clipping = false) + super + end + end + + test "SVG basic test" do + pdf = RBPDF.new + pdf.add_page + img_file = File.join(File.dirname(__FILE__), 'testsvg.svg') + + pdf.image_svg(img_file, 15, 30, 0, 0, '', '', '', 1, false) + + no = pdf.get_num_pages + assert_equal 1, no + end + + test "SVG basic rect test" do + tf = Tempfile.open(['test', '.svg']) do |fp| + fp.puts ' + + ' + fp + end + + pdf = RBPDF.new + pdf.image_svg(tf.path, 15, 30, 0, 0, '', '', '', 1, false) + end + + test "SVG basic polyline & viewBox test" do + tf = Tempfile.open(['test', '.svg']) do |fp| + fp.puts ' + + ' + fp + end + + pdf = RBPDF.new + pdf.image_svg(tf.path, 15, 30, 0, 0, '', '', '', 1, false) + end + + test "SVG basic circle & viewBox test" do + tf = Tempfile.open(['test', '.svg']) do |fp| + fp.puts ' + + ' + fp + end + + pdf = RBPDF.new + pdf.image_svg(tf.path, 15, 30, 0, 0, '', '', '', 1, false) + end +end diff --git a/test/testsvg.svg b/test/testsvg.svg new file mode 100644 index 0000000..fe3ece3 --- /dev/null +++ b/test/testsvg.svg @@ -0,0 +1,328 @@ + + + + + TCPDF SVG EXAMPLE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + github.com + + + + + + + + SVG +