diff vendor/plugins/rfpdf/lib/tcpdf.rb @ 909:cbb26bc654de redmine-1.3

Update to Redmine 1.3-stable branch (Redmine SVN rev 8964)
author Chris Cannam
date Fri, 24 Feb 2012 19:09:32 +0000
parents cbce1fd3b1b7
children 5f33065ddc4b
line wrap: on
line diff
--- a/vendor/plugins/rfpdf/lib/tcpdf.rb	Fri Feb 24 18:36:29 2012 +0000
+++ b/vendor/plugins/rfpdf/lib/tcpdf.rb	Fri Feb 24 19:09:32 2012 +0000
@@ -72,6 +72,10 @@
   include Core::RFPDF
   include RFPDF::Math
   
+  def logger
+    Rails.logger
+  end
+
   cattr_accessor :k_cell_height_ratio
   @@k_cell_height_ratio = 1.25
 
@@ -82,10 +86,10 @@
   @@k_small_ratio = 2/3.0
   
   cattr_accessor :k_path_cache
-  @@k_path_cache = File.join(RAILS_ROOT, 'tmp')
+  @@k_path_cache = Rails.root.join('tmp')
   
   cattr_accessor :k_path_url_cache
-  @@k_path_url_cache = File.join(RAILS_ROOT, 'tmp')
+  @@k_path_url_cache = Rails.root.join('tmp')
   
   cattr_accessor :decoder
 		
@@ -98,6 +102,8 @@
 	attr_accessor :color_flag
 	
 	attr_accessor :default_table_columns
+
+	attr_accessor :max_table_columns
 	
 	attr_accessor :default_font
 
@@ -141,11 +147,11 @@
 	
 	attr_accessor :links
 	
-	attr_accessor :listordered
+	attr_accessor :list_ordered
 	
-	attr_accessor :listcount
+	attr_accessor :list_count
 	
-	attr_accessor :lispacer
+	attr_accessor :li_spacer
 	
 	attr_accessor :n
 	
@@ -226,6 +232,12 @@
 		@diffs ||= []
 		@color_flag ||= false
   	@default_table_columns ||= 4
+  	@table_columns ||= 0
+  	@max_table_columns ||= []
+  	@tr_id ||= 0
+  	@max_td_page ||= []
+  	@max_td_y ||= []
+  	@t_columns ||= 0
   	@default_font ||= "FreeSans" if unicode
   	@default_font ||= "Helvetica"
 		@draw_color ||= '0 G'
@@ -248,9 +260,15 @@
 		@is_unicode = unicode
 		@lasth ||= 0
 		@links ||= []
-  	@listordered ||= false
-  	@listcount ||= 0
-  	@lispacer ||= ""
+  	@list_ordered ||= []
+  	@list_count ||= []
+  	@li_spacer ||= ""
+  	@li_count ||= 0
+  	@spacer ||= ""
+  	@quote_count ||= 0
+  	@prevquote_count ||= 0
+  	@quote_top ||= []
+  	@quote_page ||= []
 		@n ||= 2
 		@offsets ||= []
 		@orientation_changes ||= []
@@ -272,6 +290,7 @@
   	@tempfontsize ||= 10
 		@text_color ||= '0 g'
 		@underline ||= false
+		@deleted ||= false
 		@ws ||= 0
 		
 		#Standard Unicode fonts
@@ -737,7 +756,7 @@
 			Open();
 		end
 		family=@font_family;
-		style=@font_style + (@underline ? 'U' : '');
+		style=@font_style + (@underline ? 'U' : '') + (@deleted ? 'D' : '');
 		size=@font_size_pt;
 		lw=@line_width;
 		dc=@draw_color;
@@ -1312,6 +1331,7 @@
 
 		style=style.upcase
 		style=style.gsub('U','');
+		style=style.gsub('D','');
 		if (style == 'IB')
 			style = 'BI';
 		end
@@ -1394,7 +1414,7 @@
 	# #Removes bold
 	# :pdf->SetFont('');
 	# #Times bold, italic and underlined 14
-	# :pdf->SetFont('Times','BIU');
+	# :pdf->SetFont('Times','BIUD');
 	# </pre><br />
 	# If the file corresponding to the requested font is not found, the error "Could not include font metric file" is generated.
 	# @param string :family Family font. It can be either a name defined by AddFont() or one of the standard families (case insensitive):<ul><li>Courier (fixed-width)</li><li>Helvetica or Arial (synonymous; sans serif)</li><li>Times (serif)</li><li>Symbol (symbolic)</li><li>ZapfDingbats (symbolic)</li></ul>It is also possible to pass an empty string. In that case, the current family is retained.
@@ -1426,6 +1446,12 @@
 		else
 			@underline=false;
 		end
+		if (style.include?('D'))
+			@deleted=true;
+			style= style.gsub('D','');
+		else
+			@deleted=false;
+		end
 		if (style=='IB')
 			style='BI';
 		end
@@ -1573,7 +1599,7 @@
 		#Output a string
 		s=sprintf('BT %.2f %.2f Td (%s) Tj ET', x * @k, (@h-y) * @k, escapetext(txt));
 		if (@underline and (txt!=''))
-			s += ' ' + dounderline(x, y, txt);
+			s += ' ' + dolinetxt(x, y, txt);
 		end
 		if (@color_flag)
 			s='q ' + @text_color + ' ' + s + ' Q';
@@ -1661,19 +1687,25 @@
 		k=@k;
 		if ((@y + h) > @page_break_trigger and !@in_footer and AcceptPageBreak())
 			#Automatic page break
-			x = @x;
-			ws = @ws;
-			if (ws > 0)
-				@ws = 0;
-				out('0 Tw');
-			end
-			AddPage(@cur_orientation);
-			@x = x;
-			if (ws > 0)
-				@ws = ws;
-				out(sprintf('%.3f Tw', ws * k));
+			if @pages[@page+1].nil?
+				x = @x;
+				ws = @ws;
+				if (ws > 0)
+					@ws = 0;
+					out('0 Tw');
+				end
+				AddPage(@cur_orientation);
+				@x = x;
+				if (ws > 0)
+					@ws = ws;
+					out(sprintf('%.3f Tw', ws * k));
+				end
+			else
+				@page += 1;
+				@y=@t_margin;
 			end
 		end
+
 		if (w == 0)
 			w = @w - @r_margin - @x;
 		end
@@ -1717,7 +1749,10 @@
 			txt2 = escapetext(txt);
 			s<<sprintf('BT %.2f %.2f Td (%s) Tj ET', (@x + dx) * k, (@h - (@y + 0.5 * h + 0.3 * @font_size)) * k, txt2);
 			if (@underline)
-				s<<' ' + dounderline(@x + dx, @y + 0.5 * h + 0.3 * @font_size, txt);
+				s<<' ' + dolinetxt(@x + dx, @y + 0.5 * h + 0.3 * @font_size, txt);
+			end
+			if (@deleted)
+				s<<' ' + dolinetxt(@x + dx, @y + 0.3 * h + 0.2 * @font_size, txt);
 			end
 			if (@color_flag)
 				s<<' Q';
@@ -1760,6 +1795,7 @@
 		# save current position
 		prevx = @x;
 		prevy = @y;
+		prevpage = @page;
 		 
 		#Output text with automatic or explicit line breaks
 
@@ -1876,6 +1912,7 @@
 			@x = @l_margin;
 		elsif (ln == 0)
 			# go to the top-right of the cell
+			@page = prevpage;
 			@y = prevy;
 			@x = prevx + w;
 		elsif (ln == 2)
@@ -2012,17 +2049,27 @@
 		if (@images[file].nil?)
 			#First use of image, get info
 			if (type == '')
-				pos = file.rindex('.');
-				if (pos == 0)
+				pos = File::basename(file).rindex('.');
+				if (pos.nil? or pos == 0)
 					Error('Image file has no extension and no type was specified: ' + file);
 				end
+				pos = file.rindex('.');
 				type = file[pos+1..-1];
 			end
 			type.downcase!
 			if (type == 'jpg' or type == 'jpeg')
 				info=parsejpg(file);
-			elsif (type == 'png')
-				info=parsepng(file);
+			elsif (type == 'png' or type == 'gif')
+				img = Magick::ImageList.new(file)
+				img.format = "PNG"     # convert to PNG from gif 
+				img.opacity = 0        # PNG alpha channel delete
+				File.open( @@k_path_cache + File::basename(file), 'w'){|f|
+					f.binmode
+					f.print img.to_blob
+					f.close
+				}
+				info=parsepng( @@k_path_cache + File::basename(file));
+				File.delete( @@k_path_cache + File::basename(file))
 			else
 				#Allow for additional formats
 				mtd='parse' + type;
@@ -2038,15 +2085,37 @@
 		end
 		#Automatic width and height calculation if needed
 		if ((w == 0) and (h == 0))
+			rescale_x = (@w - @r_margin - x) / (info['w'] / (@img_scale * @k)) 
+			rescale_x = 1 if rescale_x >= 1 
+			if (y + info['h'] * rescale_x / (@img_scale * @k) > @page_break_trigger and !@in_footer and AcceptPageBreak())
+				#Automatic page break
+				if @pages[@page+1].nil?
+					ws = @ws;
+					if (ws > 0)
+						@ws = 0;
+						out('0 Tw');
+					end
+					AddPage(@cur_orientation);
+					if (ws > 0)
+						@ws = ws;
+						out(sprintf('%.3f Tw', ws * @k));
+					end
+				else
+					@page += 1;
+				end
+				y=@t_margin;
+			end
+			rescale_y = (@page_break_trigger - y) / (info['h'] / (@img_scale * @k)) 
+			rescale_y = 1 if rescale_y >= 1 
+			rescale = rescale_y >= rescale_x ? rescale_x : rescale_y
+
 			#Put image at 72 dpi
 			# 2004-06-14 :: Nicola Asuni, scale factor where added
-			w = info['w'] / (@img_scale * @k);
-			h = info['h'] / (@img_scale * @k);
-		end
-		if (w == 0)
+			w = info['w'] * rescale / (@img_scale * @k);
+			h = info['h'] * rescale / (@img_scale * @k);
+		elsif (w == 0)
 			w = h * info['w'] / info['h'];
-		end
-		if (h == 0)
+		elsif (h == 0)
 			h = w * info['h'] / info['w'];
 		end
 		out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q', w*@k, h*@k, x*@k, (@h-(y+h))*@k, info['i']));
@@ -2075,6 +2144,29 @@
 		else
 			@y += h;
 		end
+
+		k=@k;
+		if (@y > @page_break_trigger and !@in_footer and AcceptPageBreak())
+			#Automatic page break
+			if @pages[@page+1].nil?
+				x = @x;
+				ws = @ws;
+				if (ws > 0)
+					@ws = 0;
+					out('0 Tw');
+				end
+				AddPage(@cur_orientation);
+				@x = x;
+				if (ws > 0)
+					@ws = ws;
+					out(sprintf('%.3f Tw', ws * k));
+				end
+			else
+				@page += 1;
+				@y=@t_margin;
+			end
+		end
+
 	end
   alias_method :ln, :Ln
 
@@ -2473,6 +2565,7 @@
 
   def putType0(font)
   	#Type0
+		newobj();
 		out('<</Type /Font')
   	out('/Subtype /Type0')
   	out('/BaseFont /'+font['name']+'-'+font['cMap'])
@@ -2525,7 +2618,7 @@
 			out('/Width ' + info['w'].to_s);
 			out('/Height ' + info['h'].to_s);
 			if (info['cs']=='Indexed')
-				out('/ColorSpace [/Indexed /DeviceRGB ' + (info['pal'].length/3-1) + ' ' + (@n+1) + ' 0 R]');
+				out('/ColorSpace [/Indexed /DeviceRGB ' + (info['pal'].length/3-1).to_s + ' ' + (@n+1).to_s + ' 0 R]');
 			else
 				out('/ColorSpace /' + info['cs']);
 				if (info['cs']=='DeviceCMYK')
@@ -2553,7 +2646,7 @@
 			#Palette
 			if (info['cs']=='Indexed')
 				newobj();
-				pal=(@compress) ? gzcompress(info['pal']) : :info['pal'];
+				pal=(@compress) ? gzcompress(info['pal']) : info['pal'];
 				out('<<' + filter + '/Length ' + pal.length.to_s + '>>');
 				putstream(pal);
 				out('endobj');
@@ -2766,10 +2859,10 @@
 	end
 
 	#
-	# Underline text
+	# Underline and Deleted text
 	# @access protected
 	#
-	def dounderline(x, y, txt)
+	def dolinetxt(x, y, txt)
 		up = @current_font['up'];
 		ut = @current_font['ut'];
 		w = GetStringWidth(txt) + @ws * txt.count(' ');
@@ -2785,7 +2878,7 @@
 		if (a.empty?)
 			Error('Missing or incorrect image file: ' + file);
 		end
-		if (a[2]!='JPEG')
+		if (!a[2].nil? and a[2]!='JPEG')
 			Error('Not a JPEG file: ' + file);
 		end
 		if (a['channels'].nil? or a['channels']==3)
@@ -2798,9 +2891,12 @@
 		bpc=!a['bits'].nil? ? a['bits'] : 8;
 		#Read whole file
 		data='';
-	  open(file,'rb') do |f|
+
+		open( @@k_path_cache + File::basename(file),'rb') do |f|
 			data<<f.read();
 		end
+		File.delete( @@k_path_cache + File::basename(file))
+
 		return {'w' => a[0],'h' => a[1],'cs' => colspace,'bpc' => bpc,'f'=>'DCTDecode','data' => data}
 	end
 
@@ -2821,11 +2917,11 @@
 		end
 		w=freadint(f);
 		h=freadint(f);
-		bpc=f.read(1)[0];
+		bpc=f.read(1).unpack('C')[0];
 		if (bpc>8)
 			Error('16-bit depth not supported: ' + file);
 		end
-		ct=f.read(1)[0];
+		ct=f.read(1).unpack('C')[0];
 		if (ct==0)
 			colspace='DeviceGray';
 		elsif (ct==2)
@@ -2835,13 +2931,13 @@
 		else
 			Error('Alpha channel not supported: ' + file);
 		end
-		if (f.read(1)[0] != 0)
+		if (f.read(1).unpack('C')[0] != 0)
 			Error('Unknown compression method: ' + file);
 		end
-		if (f.read(1)[0]!=0)
+		if (f.read(1).unpack('C')[0] != 0)
 			Error('Unknown filter method: ' + file);
 		end
-		if (f.read(1)[0]!=0)
+		if (f.read(1).unpack('C')[0] != 0)
 			Error('Interlacing not supported: ' + file);
 		end
 		f.read(4);
@@ -2861,9 +2957,9 @@
 				#Read transparency info
 				t=f.read( n);
 				if (ct==0)
-					trns = t[1][0]
+					trns = t[1].unpack('C')[0]
 				elsif (ct==2)
-					trns = t[[1][0], t[3][0], t[5][0]]
+					trns = t[[1].unpack('C')[0], t[3].unpack('C')[0], t[5].unpack('C')[0]]
 				else
 					pos=t.include?(0.chr);
 					if (pos!=false)
@@ -3350,7 +3446,7 @@
 	
 	#
 	# Allows to preserve some HTML formatting.<br />
-	# Supports: h1, h2, h3, h4, h5, h6, b, u, i, a, img, p, br, strong, em, font, blockquote, li, ul, ol, hr, td, th, tr, table, sup, sub, small
+	# Supports: h1, h2, h3, h4, h5, h6, b, u, i, a, img, p, br, strong, em, ins, del, font, blockquote, li, ul, ol, hr, td, th, tr, table, sup, sub, small
 	# @param string :html text to display
 	# @param boolean :ln if true add a new line after text (default = true)
 	# @param int :fill Indicates if the background must be painted (1) or transparent (0). Default value: 0.
@@ -3364,12 +3460,38 @@
 		end
 		
     @href = nil
-    @style = {}
-    html.gsub!(/[\t\r\n\f]/, "")#\0\x0B
+    @style = "";
+    @t_cells =  [[]];
+    @table_id = 0;
+
+    # pre calculate
     html.split(/(<[^>]+>)/).each do |element|
       if "<" == element[0,1]
         #Tag
         if (element[1, 1] == '/')
+					closedHTMLTagCalc(element[2..-2].downcase);
+        else
+					#Extract attributes
+					# get tag name
+					tag = element.scan(/([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0}
+					tag = tag[0].downcase;
+					
+					# get attributes
+					attr_array = element.scan(/([^=\s]*)=["\']?([^"\']*)["\']?/)
+          attrs = {}
+          attr_array.each do |name, value|
+    			  attrs[name.downcase] = value;
+    		  end
+					openHTMLTagCalc(tag, attrs);
+				end
+			end
+		end
+		@table_id = 0;
+				
+    html.split(/(<[A-Za-z!?\/][^>]*?>)/).each do |element|
+      if "<" == element[0,1]
+        #Tag
+        if (element[1, 1] == '/')
 					closedHTMLTagHandler(element[2..-2].downcase);
         else
 					#Extract attributes
@@ -3389,14 +3511,32 @@
       else
         #Text
 				if (@href)
+					element.gsub!(/[\t\r\n\f]/, "");
 					addHtmlLink(@href, element, fill);
 				elsif (@tdbegin)
-					if ((element.strip.length > 0) and (element != "&nbsp;"))
-						Cell(@tdwidth, @tdheight, unhtmlentities(element.strip), @tableborder, 0, @tdalign, @tdfill);
-					elsif (element == "&nbsp;")
-						Cell(@tdwidth, @tdheight, '', @tableborder, 0, @tdalign, @tdfill);
+					element.gsub!(/[\t\r\n\f]/, "");
+					element.gsub!(/&nbsp;/, " ");
+					base_page = @page;
+					base_x = @x;
+					base_y = @y;
+
+					MultiCell(@tdwidth, @tdheight, unhtmlentities(element.strip), @tableborder, @tdalign, @tdfill, 1);
+					tr_end = @t_cells[@table_id][@tr_id][@td_id]['j1'] + 1;
+					if @max_td_page[tr_end].nil?  or (@max_td_page[tr_end] < @page)
+						@max_td_page[tr_end] = @page
+						@max_td_y[tr_end] = @y
+					elsif (@max_td_page[tr_end] == @page)
+						@max_td_y[tr_end] = @y if @max_td_y[tr_end].nil? or (@max_td_y[tr_end] < @y) 
 					end
-				elsif ((element.strip.length > 0) and (element != "&nbsp;"))
+
+					@page = base_page;
+					@x = base_x + @tdwidth;
+					@y = base_y;
+				elsif (@pre_state == true and element.length > 0)
+					Write(@lasth, unhtmlentities(element), '', fill);
+				elsif (element.strip.length > 0)
+					element.gsub!(/[\t\r\n\f]/, "");
+					element.gsub!(/&nbsp;/, " ");
 					Write(@lasth, unhtmlentities(element), '', fill);
 				end
       end
@@ -3422,7 +3562,7 @@
 	# @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
 	# @see Cell()
 	#
-	def writeHTMLCell(w, h, x, y, html='', border=0, ln=0, fill=0)
+	def writeHTMLCell(w, h, x, y, html='', border=0, ln=1, fill=0)
 		
 		if (@lasth == 0)
 			#set row height
@@ -3446,6 +3586,24 @@
 			w = @fw - x - @r_margin;
 		end
 		
+		b=0;
+		if (border)
+			if (border==1)
+				border='LTRB';
+				b='LRT';
+				b2='LR';
+			elsif border.is_a?(String)
+				b2='';
+				if (border.include?('L'))
+					b2<<'L';
+				end
+				if (border.include?('R'))
+					b2<<'R';
+				end
+				b=(border.include?('T')) ? b2 + 'T' : b2;
+			end
+		end
+		
 		# store original margin values
 		l_margin = @l_margin;
 		r_margin = @r_margin;
@@ -3461,37 +3619,196 @@
 		
 		currentY =  GetY();
 		
+		@auto_page_break = false;
 		# check if a new page has been created
 		if (@page > pagenum)
 			# design a cell around the text on first page
 			currentpage = @page;
 			@page = pagenum;
 			SetY(GetPageHeight() - restspace - GetBreakMargin());
-			h = restspace - 1;
-			Cell(w, h, "", border, ln, 'L', 0);
+			Cell(w, restspace - 1, "", b, 0, 'L', 0);
+			b = b2;
+			@page += 1;
+			while @page < currentpage
+				SetY(@t_margin); # put cursor at the beginning of text
+				Cell(w, @page_break_trigger - @t_margin, "", b, 0, 'L', 0);
+				@page += 1;
+			end
+			if (border.is_a?(String) and border.include?('B'))
+				b<<'B';
+			end
 			# design a cell around the text on last page
-			@page = currentpage;
-			h = currentY - @t_margin;
 			SetY(@t_margin); # put cursor at the beginning of text
-			Cell(w, h, "", border, ln, 'L', 0);
+			Cell(w, currentY - @t_margin, "", b, 0, 'L', 0);
 		else
-			h = [h, (currentY - y)].max;
 			SetY(y); # put cursor at the beginning of text
 			# design a cell around the text
-			Cell(w, h, "", border, ln, 'L', 0);
+			Cell(w, [h, (currentY - y)].max, "", border, 0, 'L', 0);
 		end
+		@auto_page_break = true;
 		
 		# restore original margin values
 		SetLeftMargin(l_margin);
 		SetRightMargin(r_margin);
 		
-		if (ln)
-			Ln(0);
+		@lasth = h
+
+		# move cursor to specified position
+		if (ln == 0)
+			# go to the top-right of the cell
+			@x = x + w;
+			@y = y;
+		elsif (ln == 1)
+			# go to the beginning of the next line
+			@x = @l_margin;
+			@y = currentY;
+		elsif (ln == 2)
+			# go to the bottom-left of the cell (below)
+			@x = x;
+			@y = currentY;
 		end
 	end
   alias_method :write_html_cell, :writeHTMLCell
 
 	#
+	# Check html table tag position.
+	#
+	# @param array :table potision array
+	# @param int :current tr tag id number
+	# @param int :current td tag id number
+	# @access private
+	# @return int : next td_id position.
+	#               value 0 mean that can use position.
+	#
+	def checkTableBlockingCellPosition(table, tr_id, td_id )
+		0.upto(tr_id) do |j|
+			0.upto(@t_cells[table][j].size - 1) do |i|
+				if @t_cells[table][j][i]['i0'] <= td_id and td_id <= @t_cells[table][j][i]['i1']
+					if @t_cells[table][j][i]['j0'] <= tr_id and tr_id <= @t_cells[table][j][i]['j1']
+						return @t_cells[table][j][i]['i1'] - td_id + 1;
+					end
+				end
+			end
+		end
+		return 0;
+	end
+
+	#
+	# Calculate opening tags.
+	#
+	# html table cell array : @t_cells
+	#
+	#  i0: table cell start position
+	#  i1: table cell end position
+	#  j0: table row start position
+	#  j1: table row end position
+	#
+	#  +------+
+	#  |i0,j0 |
+	#  | i1,j1|
+	#  +------+
+	#
+	#  example html:
+	#  <table>
+	#    <tr><td></td><td></td><td></td></tr>
+	#    <tr><td colspan=2></td><td></td></tr>
+	#    <tr><td rowspan=2></td><td></td><td></td></tr>
+	#    <tr><td></td><td></td></tr>
+	#  </table>
+	#
+	#   i: 0    1    2
+	#  j+----+----+----+
+	#  :|0,0 |1,0 |2,0 |
+	#  0| 0,0| 1,0| 2,0|
+	#   +----+----+----+
+	#   |0,1      |2,1 |
+	#  1|      1,1| 2,1|
+	#   +----+----+----+
+	#   |0,2 |1,2 |2,2 |
+	#  2|    | 1,2| 2,2|
+	#   +    +----+----+
+	#   |    |1,3 |2,3 |
+	#  3| 0,3| 1,3| 2,3|
+	#   +----+----+----+
+	#
+	#  html table cell array :
+	#  [[[i0=>0,j0=>0,i1=>0,j1=>0],[i0=>1,j0=>0,i1=>1,j1=>0],[i0=>2,j0=>0,i1=>2,j1=>0]],
+	#   [[i0=>0,j0=>1,i1=>1,j1=>1],[i0=>2,j0=>1,i1=>2,j1=>1]],
+	#   [[i0=>0,j0=>2,i1=>0,j1=>3],[i0=>1,j0=>2,i1=>1,j1=>2],[i0=>2,j0=>2,i1=>2,j1=>2]]
+	#   [[i0=>1,j0=>3,i1=>1,j1=>3],[i0=>2,j0=>3,i1=>2,j1=>3]]]
+	#
+	# @param string :tag tag name (in upcase)
+	# @param string :attr tag attribute (in upcase)
+	# @access private
+	#
+	def openHTMLTagCalc(tag, attrs)
+		#Opening tag
+		case (tag)
+			when 'table'
+				@max_table_columns[@table_id] = 0;
+				@t_columns = 0;
+				@tr_id = -1;
+			when 'tr'
+				if @max_table_columns[@table_id] < @t_columns
+					@max_table_columns[@table_id] = @t_columns;
+				end
+				@t_columns = 0;
+				@tr_id +=  1;
+				@td_id =  -1;
+				@t_cells[@table_id].push []
+			when 'td', 'th'
+				@td_id +=  1;
+				if attrs['colspan'].nil? or attrs['colspan'] == ''
+					colspan = 1;
+				else
+					colspan = attrs['colspan'].to_i;
+				end
+				if attrs['rowspan'].nil? or attrs['rowspan'] == ''
+					rowspan = 1;
+				else
+					rowspan = attrs['rowspan'].to_i;
+				end
+
+				i = 0;
+				while true
+					next_i_distance = checkTableBlockingCellPosition(@table_id, @tr_id, @td_id + i);
+					if next_i_distance == 0
+						@t_cells[@table_id][@tr_id].push "i0"=>@td_id + i, "j0"=>@tr_id, "i1"=>(@td_id + i + colspan - 1), "j1"=>@tr_id + rowspan - 1
+						break;
+					end
+					i += next_i_distance;
+				end
+
+				@t_columns += colspan;
+		end
+	end
+
+	#
+	# Calculate closing tags.
+	# @param string :tag tag name (in upcase)
+	# @access private
+	#
+	def closedHTMLTagCalc(tag)
+		#Closing tag
+		case (tag)
+			when 'table'
+				if @max_table_columns[@table_id] < @t_columns
+					@max_table_columns[@table_id] = @t_columns;
+				end
+				@table_id += 1;
+				@t_cells.push []
+		end
+	end
+
+	#
+	# Convert to accessible file path
+	# @param string :attrname image file name
+	#
+	def getImageFilename( attrname )
+		nil
+	end
+
+	#
 	# Process opening tags.
 	# @param string :tag tag name (in upcase)
 	# @param string :attr tag attribute (in upcase)
@@ -3501,20 +3818,56 @@
 	def openHTMLTagHandler(tag, attrs, fill=0)
 		#Opening tag
 		case (tag)
+			when 'pre'
+				@pre_state = true;
+				@l_margin += 5;	
+				@r_margin += 5;	
+				@x += 5;	
+
 			when 'table'
+				if @default_table_columns < @max_table_columns[@table_id]
+					@table_columns = @max_table_columns[@table_id];
+				else
+					@table_columns = @default_table_columns;
+				end
+				@l_margin += 5;	
+				@r_margin += 5;	
+				@x += 5;	
+
 				if attrs['border'].nil? or attrs['border'] == ''
 					@tableborder = 0;
 				else
 					@tableborder = attrs['border'];
 				end
+				@tr_id        = -1;
+				@max_td_page[0] = @page;
+				@max_td_y[0]    = @y;
+
 			when 'tr', 'td', 'th'
-        # SetStyle('b', true) if tag == 'th'
-				
+				if tag == 'th'
+					SetStyle('b', true);
+					@tdalign = "C";
+				end
 				if ((!attrs['width'].nil?) and (attrs['width'] != ''))
 					@tdwidth = (attrs['width'].to_i/4);
 				else
-					@tdwidth = ((@w - @l_margin - @r_margin) / @default_table_columns);
+					@tdwidth = ((@w - @l_margin - @r_margin) / @table_columns);
 				end
+
+				if tag == 'tr'
+					@tr_id +=  1;
+					@td_id  = -1;
+				else
+					@td_id +=  1;
+					@x =  @l_margin + @tdwidth * @t_cells[@table_id][@tr_id][@td_id]['i0'];
+				end
+
+				if attrs['colspan'].nil? or attrs['border'] == ''
+					@colspan = 1;
+				else
+					@colspan = attrs['colspan'].to_i;
+				end
+				@tdwidth *= @colspan;
 				if ((!attrs['height'].nil?) and (attrs['height'] != ''))
 					@tdheight=(attrs['height'].to_i / @k);
 				else
@@ -3538,17 +3891,14 @@
 				@tdbegin=true;
 				
 			when 'hr'
-				Ln();
+				margin = 1;
 				if ((!attrs['width'].nil?) and (attrs['width'] != ''))
 					hrWidth = attrs['width'];
 				else
-					hrWidth = @w - @l_margin - @r_margin;
+					hrWidth = @w - @l_margin - @r_margin - margin;
 				end
-				x = GetX();
-				y = GetY();
 				SetLineWidth(0.2);
-				Line(x, y, x + hrWidth, y);
-				SetLineWidth(0.2);
+				Line(@x + margin, @y, @x + hrWidth, @y);
 				Ln();
 				
 			when 'strong'
@@ -3557,6 +3907,12 @@
 			when 'em'
 				SetStyle('i', true);
 				
+			when 'ins'
+				SetStyle('u', true);
+				
+			when 'del'
+				SetStyle('d', true);
+				
 			when 'b', 'i', 'u'
 				SetStyle(tag, true);
 				
@@ -3565,8 +3921,17 @@
 				
 			when 'img'
 				if (!attrs['src'].nil?)
-					# replace relative path with real server path
-					attrs['src'] = attrs['src'].gsub(@@k_path_url_cache, @@k_path_cache);
+					# Only generates image include a pdf if RMagick is avalaible
+					unless Object.const_defined?(:Magick)
+						Write(@lasth, attrs['src'], '', fill);
+						return
+					end
+					file = getImageFilename(attrs['src'])
+					if (file.nil?)
+						Write(@lasth, attrs['src'], '', fill);
+						return
+					end
+
 					if (attrs['width'].nil?)
 						attrs['width'] = 0;
 					end
@@ -3574,40 +3939,80 @@
 						attrs['height'] = 0;
 					end
 					
-					Image(attrs['src'], GetX(),GetY(), pixelsToMillimeters(attrs['width']), pixelsToMillimeters(attrs['height']));
-					#SetX(@img_rb_x);
-					SetY(@img_rb_y);
-					
+					begin
+						Image(file, GetX(),GetY(), pixelsToMillimeters(attrs['width']), pixelsToMillimeters(attrs['height']));
+						#SetX(@img_rb_x);
+						SetY(@img_rb_y);
+					rescue => err
+						logger.error "pdf: Image: error: #{err.message}"
+						Write(@lasth, attrs['src'], '', fill);
+						if File.file?( @@k_path_cache + File::basename(file))
+							File.delete( @@k_path_cache + File::basename(file))
+						end
+					end
 				end
 				
-			when 'ul'
-				@listordered = false;
-				@listcount = 0;
-				
-			when 'ol'
-				@listordered = true;
-				@listcount = 0;
+			when 'ul', 'ol'
+				if @li_count == 0
+					Ln() if @prevquote_count == @quote_count; # insert Ln for keeping quote lines
+					@prevquote_count = @quote_count;
+				end
+				if @li_state == true
+					Ln();
+			  	@li_state = false;
+				end
+				if tag == 'ul'
+					@list_ordered[@li_count] = false;
+				else
+					@list_ordered[@li_count] = true;
+				end
+				@list_count[@li_count] = 0;
+				@li_count += 1
 				
 			when 'li'
-				Ln();
-				if (@listordered)
-				  @listcount += 1
-					@lispacer = "    " + (@listcount).to_s + ". ";
+				Ln() if @li_state == true
+				if (@list_ordered[@li_count - 1])
+					@list_count[@li_count - 1] += 1;
+					@li_spacer = "    " * @li_count + (@list_count[@li_count - 1]).to_s + ". ";
 				else
 					#unordered list simbol
-					@lispacer = "    -  ";
+					@li_spacer = "    " * @li_count + "-  ";
 				end
-				Write(@lasth, @lispacer, '', fill);
+				Write(@lasth, @spacer + @li_spacer, '', fill);
+				@li_state = true;
 
-			when 'blockquote', 'br'
+			when 'blockquote'
+				if (@quote_count == 0)
+					SetStyle('i', true);
+					@l_margin += 5;	
+				else
+					@l_margin += 5 / 2;	
+				end
+				@x = @l_margin;	
+				@quote_top[@quote_count]  = @y;
+				@quote_page[@quote_count] = @page;
+				@quote_count += 1
+			when 'br'
 				Ln();
-				if (@lispacer.length > 0)
-					@x += GetStringWidth(@lispacer);
+
+				if (@li_spacer.length > 0)
+					@x += GetStringWidth(@li_spacer);
 				end
 				
 			when 'p'
 				Ln();
-				Ln();
+				0.upto(@quote_count - 1) do |i|
+			  	if @quote_page[i] == @page;
+			  		if @quote_top[i] == @y - @lasth; # fix start line
+			  			@quote_top[i] = @y;
+						end
+					else
+			  		if @quote_page[i] == @page - 1;
+			  			@quote_page[i] = @page; # fix start line
+			  			@quote_top[i] = @t_margin;
+						end
+					end
+				end
 				
 			when 'sup'
 				currentfont_size = @font_size;
@@ -3648,6 +4053,7 @@
 				@lasth = @font_size * @@k_cell_height_ratio;
 				
 			when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
+				Ln();
 				headsize = (4 - tag[1,1].to_f) * 2
 				@tempfontsize = @font_size_pt;
 				SetFontSize(@font_size_pt + headsize);
@@ -3665,19 +4071,63 @@
 	def closedHTMLTagHandler(tag)
 		#Closing tag
 		case (tag)
+			when 'pre'
+				@pre_state = false;
+				@l_margin -= 5;
+				@r_margin -= 5;
+				@x = @l_margin;
+				Ln();
+
 			when 'td','th'
 				@tdbegin = false;
 				@tdwidth = 0;
 				@tdheight = 0;
 				@tdalign = "L";
+				SetStyle('b', false);
 				@tdfill = 0;
 				SetFillColor(@prevfill_color[0], @prevfill_color[1], @prevfill_color[2]);
 				
 			when 'tr'
-				Ln();
+				@y = @max_td_y[@tr_id + 1];
+				@x = @l_margin;
+				@page = @max_td_page[@tr_id + 1];
 				
 			when 'table'
+				# Write Table Line
+				width = (@w - @l_margin - @r_margin) / @table_columns;
+				0.upto(@t_cells[@table_id].size - 1) do |j|
+					0.upto(@t_cells[@table_id][j].size - 1) do |i|
+						@page = @max_td_page[j]
+						i0=@t_cells[@table_id][j][i]['i0'];
+						j0=@t_cells[@table_id][j][i]['j0'];
+						i1=@t_cells[@table_id][j][i]['i1'];
+						j1=@t_cells[@table_id][j][i]['j1'];
+
+						Line(@l_margin + width * i0,     @max_td_y[j0],   @l_margin + width * (i1+1), @max_td_y[j0])   # top
+						if ( @page == @max_td_page[j1 + 1])
+							Line(@l_margin + width * i0,     @max_td_y[j0],   @l_margin + width * i0,     @max_td_y[j1+1]) # left
+							Line(@l_margin + width * (i1+1), @max_td_y[j0],   @l_margin + width * (i1+1), @max_td_y[j1+1]) # right
+						else
+							Line(@l_margin + width * i0,     @max_td_y[j0],   @l_margin + width * i0,     @page_break_trigger) # left
+							Line(@l_margin + width * (i1+1), @max_td_y[j0],   @l_margin + width * (i1+1), @page_break_trigger) # right
+							@page += 1;
+							while @page < @max_td_page[j1 + 1]
+								Line(@l_margin + width * i0,     @t_margin,   @l_margin + width * i0,     @page_break_trigger) # left
+								Line(@l_margin + width * (i1+1), @t_margin,   @l_margin + width * (i1+1), @page_break_trigger) # right
+								@page += 1;
+							end
+							Line(@l_margin + width * i0,     @t_margin,   @l_margin + width * i0,     @max_td_y[j1+1]) # left
+							Line(@l_margin + width * (i1+1), @t_margin,   @l_margin + width * (i1+1), @max_td_y[j1+1]) # right
+						end
+						Line(@l_margin + width * i0,     @max_td_y[j1+1], @l_margin + width * (i1+1), @max_td_y[j1+1]) # bottom
+					end
+				end
+
+				@l_margin -= 5;
+				@r_margin -= 5;
 				@tableborder=0;
+				Ln();
+				@table_id += 1;
 				
 			when 'strong'
 				SetStyle('b', false);
@@ -3685,12 +4135,21 @@
 			when 'em'
 				SetStyle('i', false);
 				
+			when 'ins'
+				SetStyle('u', false);
+				
+			when 'del'
+				SetStyle('d', false);
+				
 			when 'b', 'i', 'u'
 				SetStyle(tag, false);
 				
 			when 'a'
 				@href = nil;
 				
+			when 'p'
+				Ln();
+				
 			when 'sup'
 				currentfont_size = @font_size;
 				SetFontSize(@tempfontsize);
@@ -3725,14 +4184,47 @@
 				#@text_color = @prevtext_color;
 				@lasth = @font_size * @@k_cell_height_ratio;
 				
-			when 'ul'
-				Ln();
-				
-			when 'ol'
-				Ln();
+			when 'blockquote'
+			  @quote_count -= 1
+				if (@quote_page[@quote_count] == @page)
+					Line(@l_margin - 1, @quote_top[@quote_count], @l_margin - 1, @y)  # quoto line
+				else
+					cur_page = @page;
+					cur_y = @y;
+					@page = @quote_page[@quote_count];
+					if (@quote_top[@quote_count] < @page_break_trigger)
+						Line(@l_margin - 1, @quote_top[@quote_count], @l_margin - 1, @page_break_trigger)  # quoto line
+					end
+					@page += 1;
+					while @page < cur_page
+						Line(@l_margin - 1, @t_margin, @l_margin - 1, @page_break_trigger)  # quoto line
+						@page += 1;
+					end
+					@y = cur_y;
+					Line(@l_margin - 1, @t_margin, @l_margin - 1, @y)  # quoto line
+				end
+				if (@quote_count <= 0)
+					SetStyle('i', false);
+					@l_margin -= 5;
+				else
+					@l_margin -= 5 / 2;
+				end
+				@x = @l_margin;
+				Ln() if @quote_count == 0
+
+			when 'ul', 'ol'
+			  @li_count -= 1
+				if @li_state == true
+					Ln();
+			  	@li_state = false;
+				end
 				
 			when 'li'
-				@lispacer = "";
+				@li_spacer = "";
+				if @li_state == true
+					Ln();
+			  	@li_state = false;
+				end
 				
 			when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
 				SetFontSize(@tempfontsize);
@@ -3740,7 +4232,17 @@
 				SetStyle('b', false);
 				Ln();
 				@lasth = @font_size * @@k_cell_height_ratio;
-								
+				
+				if tag == 'h1' or tag == 'h2' or tag == 'h3' or tag == 'h4'
+					margin = 1;
+					hrWidth = @w - @l_margin - @r_margin - margin;
+					if tag == 'h1' or tag == 'h2'
+						SetLineWidth(0.2);
+					else
+						SetLineWidth(0.1);
+					end
+					Line(@x + margin, @y, @x + hrWidth, @y);
+				end
 		end
 	end
 	
@@ -3752,11 +4254,16 @@
 	#
 	def SetStyle(tag, enable)
 		#Modify style and select corresponding font
-		style='';
-		['b', 'i', 'u'].each do |s|
-				style << s if tag.downcase == s and enable
+		['b', 'i', 'u', 'd'].each do |s|
+			if tag.downcase == s
+				if enable
+					@style << s if ! @style.include?(s)
+				else
+					@style = @style.gsub(s,'')
+				end
+			end
 		end
-		SetFont('', style);
+		SetFont('', @style);
 	end
 	
 	#