#
# $Id: renderer.py,v 1.17 2004/03/08 23:30:05 mrnolta Exp $
#
# Copyright (C) 2000-2001 Mike Nolta <mrnolta@users.sourceforge.net>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA  02111-1307, USA.
#

import libplot, math
raw = libplot

from tex2libplot import tex2libplot

## polygon clipping

def sh_inside( p, dim, boundary, side ):
	return side*p[dim] >= side*boundary

def sh_intersection( s, p, dim, boundary ):
	mid = not dim
	g = 0.
	if p[dim] != s[dim]:
		g = (boundary - s[dim])/(p[dim] - s[dim])
	q = [0,0]
	q[dim] = boundary
	q[mid] = s[mid] + g*(p[mid] - s[mid])
	return q[0], q[1]

def sutherland_hodgman( polygon, dim, boundary, side ):
	out = []
	s = polygon[-1]
	s_inside = sh_inside( s, dim, boundary, side )
	for p in polygon:
		p_inside = sh_inside( p, dim, boundary, side )

		crosses = (p_inside and not s_inside) or \
		          (not p_inside and s_inside)
		if crosses:
			out.append( sh_intersection(s, p, dim, boundary) )

		if p_inside:
			out.append( p )

		s = p
		s_inside = p_inside

	return out

class RendererState:

	def __init__( self ):
		self.current = {}
		self.saved = []

	def set( self, name, value ):
		self.current[name] = value

	def get( self, name, notfound=None ):
		if self.current.has_key(name):
			return self.current[name]
		for i in range(len(self.saved)):
			d = self.saved[i]
			if d.has_key(name):
				return d[name]
		return notfound

	def save( self ):
		self.saved.insert( 0, self.current )
		self.current = {}

	def restore( self ):
		self.current = self.saved.pop(0)

def _hexcolor( hextriplet, scale=1 ):
	s = float(scale) / 0xff
	r = s * ((hextriplet >> 16) & 0xff) 
	g = s * ((hextriplet >>  8) & 0xff)
	b = s * ((hextriplet >>  0) & 0xff)
	return r, g, b

def _set_color( pl, color ):
	if type(color) == type(''):
		raw.set_colorname_fg( pl, color )
	else:
		r,g,b = _hexcolor( color )
		raw.set_color_fg( pl, r, g, b )

def _set_pen_color( pl, color ):
	if type(color) == type(''):
		raw.set_colorname_pen( pl, color )
	else:
		r,g,b = _hexcolor( color )
		raw.set_color_pen( pl, r, g, b )

def _set_fill_color( pl, color ):
	if type(color) == type(''):
		raw.set_colorname_fill( pl, color )
	else:
		r,g,b = _hexcolor( color )
		raw.set_color_fill( pl, r, g, b )

_pl_line_type = {
	"dot"		: "dotted",
	"dash"		: "shortdashed",
	"dashed"	: "shortdashed",
}

def _set_line_type( pl, type ):
	pl_type = _pl_line_type.get( type, type )
	raw.set_line_type( pl, pl_type )

class LibplotRenderer:

	def __init__( self, ll, ur, type='X', parameters=None, file=None ):
		self.lowerleft = ll
		self.upperright = ur
		self.pl = raw.new( type, parameters, file )

	def open( self ):
		self.state = RendererState()
		raw.begin_page( self.pl )
		apply( raw.space, \
			(self.pl,) + self.lowerleft + self.upperright )
		raw.clear( self.pl )

	def clear( self ):
		raw.clear( self.pl )

	def close( self ):
		if self.pl is not None:
			raw.end_page( self.pl )

	def delete( self ):
		if self.pl is not None:
			raw.delete( self.pl )
			self.pl = None

	def __del__( self ):
		self.delete()

	## state commands

	__pl_style_func = {
		"color"		: _set_color,
		"linecolor"	: _set_pen_color,
		"fillcolor"	: _set_fill_color,
		"linetype"	: _set_line_type,
		"linewidth"	: raw.set_line_size,
		"filltype"	: raw.set_fill_level,
		"fillmode"	: raw.set_fill_type,
		"fontface"	: raw.set_font_type,
		"fontsize"	: raw.set_font_size,
		"textangle"	: raw.set_string_angle,
	}

	def set( self, key, value ):
		self.state.set( key, value )
		if LibplotRenderer.__pl_style_func.has_key(key):
			method = LibplotRenderer.__pl_style_func[key]
			apply( method, (self.pl, value) )

	def get( self, parameter, notfound=None ):
		return self.state.get( parameter, notfound )

	def save_state( self ):
		self.state.save()
		raw.gsave( self.pl )

	def restore_state( self ):
		self.state.restore()
		raw.grestore( self.pl )

	## drawing commands

	def move( self, p ):
		raw.move( self.pl, p[0], p[1] )

	def lineto( self, p ):
		raw.lineto( self.pl, p[0], p[1] )

	def linetorel( self, p ):
		raw.linetorel( self.pl, p[0], p[1] )

	def line( self, p, q ):
		cr = self.get( "cliprect" )
		if cr is None:
			raw.line( self.pl, p[0], p[1], q[0], q[1] )
		else:
			raw.clipped_line( self.pl, \
				cr[0], cr[1], cr[2], cr[3], \
				p[0], p[1], q[0], q[1] )

	def rect( self, p, q ):
		raw.rect( self.pl, p[0], p[1], q[0], q[1] )

	def circle( self, p, r ):
		raw.circle( self.pl, p[0], p[1], r )

	def ellipse( self, p, rx, ry, angle=0. ):
		raw.ellipse( self.pl, p[0], p[1], rx, ry, angle )

	def arc( self, c, p, q ):
		raw.arc( self.pl, c[0], c[1], p[0], p[1], q[0], q[1] )

	__pl_symbol_type = {
		"none"				: 0,
		"dot"				: 1,
		"plus"				: 2,
		"asterisk"			: 3, 
		"circle"			: 4,
		"cross"				: 5,
		"square"			: 6,
		"triangle"			: 7,
		"diamond"			: 8,
		"star"				: 9, 
		"inverted triangle"		: 10, 
		"starburst"			: 11,
		"fancy plus"			: 12,
		"fancy cross"			: 13, 
		"fancy square"			: 14, 
		"fancy diamond"			: 15,
		"filled circle"			: 16,
		"filled square"			: 17,
		"filled triangle"		: 18, 
		"filled diamond"		: 19,
		"filled inverted triangle"	: 20,
		"filled fancy square"		: 21,
		"filled fancy diamond"		: 22,
		"half filled circle"		: 23,
		"half filled square"		: 24,
		"half filled triangle"		: 25,
		"half filled diamond"		: 26,
		"half filled inverted triangle" : 27,
		"half filled fancy square"	: 28,
		"half filled fancy diamond"	: 29,
		"octagon"			: 30,
		"filled octagon"		: 31,
	}

	def symbol( self, p ):
		self.symbols( [p[0]], [p[1]] )

	def symbols( self, x, y ):
		DEFAULT_SYMBOL_TYPE = "square"
		DEFAULT_SYMBOL_SIZE = 0.01
		type_str = self.state.get( "symboltype", DEFAULT_SYMBOL_TYPE )
		size = self.state.get( "symbolsize", DEFAULT_SYMBOL_SIZE )
		if len(type_str) == 1:
			type = ord(type_str[0])
		else:
			type = LibplotRenderer.__pl_symbol_type.get( type_str )

		cr = self.get( "cliprect" )
		if cr is None:
			raw.symbols( self.pl, x, y, type, size )
		else:
			raw.clipped_symbols( self.pl, x, y, type, size,
				cr[0], cr[1], cr[2], cr[3] )

	def colored_symbols( self, x, y, c ):
		DEFAULT_SYMBOL_TYPE = "square"
		DEFAULT_SYMBOL_SIZE = 0.01
		type_str = self.state.get( "symboltype", DEFAULT_SYMBOL_TYPE )
		size = self.state.get( "symbolsize", DEFAULT_SYMBOL_SIZE )
		if len(type_str) == 1:
			type = ord(type_str[0])
		else:
			type = LibplotRenderer.__pl_symbol_type.get( type_str )

		cr = self.get( "cliprect" )
		if cr is None:
			# This will cause an error: not written yet
			raw.colored_symbols( self.pl, x, y, type, size )
		else:
			raw.clipped_colored_symbols( self.pl, x, y, c, type, size,
				cr[0], cr[1], cr[2], cr[3] )

	def density_plot( self, densgrid, ((xmin,ymin), (xmax,ymax)) ):
		raw.density_plot( self.pl, densgrid,
				  xmin, xmax, ymin, ymax )

	def color_density_plot( self, densgrid, ((xmin,ymin), (xmax,ymax)) ):
		raw.color_density_plot( self.pl, densgrid,
					xmin, xmax, ymin, ymax )


	def curve( self, x, y ):
		cr = self.get( "cliprect" )
		if cr is None:
			raw.curve( self.pl, x, y )
		else:
			raw.clipped_curve( self.pl, x, y,
				cr[0], cr[1], cr[2], cr[3] )

	def polygon( self, points ):
		pts = points
		cr = self.get( "cliprect" )
		if cr is not None:
			pts = sutherland_hodgman( pts, 0, cr[0], +1 )
			pts = sutherland_hodgman( pts, 0, cr[1], -1 )
			pts = sutherland_hodgman( pts, 1, cr[2], +1 )
			pts = sutherland_hodgman( pts, 1, cr[3], -1 )
		self.move( pts[0] )
		map( self.lineto, pts[1:] )

	## text commands

	__pl_text_align = {
		"center"	: ord('c'),
		"baseline"	: ord('x'),
		"left"		: ord('l'),
		"right"		: ord('r'),
		"top"		: ord('t'),
		"bottom"	: ord('b'),
	}

	def text( self, p, str ):
		plstr = tex2libplot( str )
		hstr = self.state.get( "texthalign", "center" )
		vstr = self.state.get( "textvalign", "center" )
		hnum = LibplotRenderer.__pl_text_align.get( hstr )
		vnum = LibplotRenderer.__pl_text_align.get( vstr )
		raw.move( self.pl, p[0], p[1] )
		raw.string( self.pl, hnum, vnum, plstr )

	def textwidth( self, str ):
		plstr = tex2libplot( str )
		return raw.get_string_width( self.pl, plstr )

	def textheight( self, str ):
		return self.state.get( "fontsize" )	## XXX: kludge?

class NonInteractiveScreenRenderer( LibplotRenderer ):

	def __init__( self, width, height ):
		ll = 0, 0
		ur = width, height
		parameters = {
			"BITMAPSIZE": "%dx%d" % (width, height),
			"VANISH_ON_DELETE": "no",
		}
		LibplotRenderer.__init__( self, ll, ur, "X", parameters )

class InteractiveScreenRenderer( LibplotRenderer ):

	def __init__( self, width, height ):
		ll = 0, 0
		ur = width, height
		parameters = {
			"BITMAPSIZE": "%dx%d" % (width, height),
			"VANISH_ON_DELETE": "yes",
		}
		LibplotRenderer.__init__( self, ll, ur, "X", parameters )

	def close( self ):
		raw.flush( self.pl )

	def delete( self ):
		raw.flush( self.pl )

_saved_screen_renderer = None

def ScreenRenderer( persistent=0, width=512, height=512 ):

	if persistent == 1:
		global _saved_screen_renderer
		if _saved_screen_renderer is None:
			_saved_screen_renderer \
				= InteractiveScreenRenderer( width, height )
		_saved_screen_renderer.clear()
		return _saved_screen_renderer
	else:
		return NonInteractiveScreenRenderer( width, height )

def _str_size_to_pts( str ):
	import re
	m = re.compile(r"([\d.]+)([^\s]+)").match(str)
	num_xx = float(m.group(1))
	units = m.group(2)
	# convert to postscipt pt = in/72
	xx2pt = { "in":72, "pt":1, "mm":2.835, "cm":28.35 }
	num_pt = int(num_xx*xx2pt[units])
	return num_pt

class PSRenderer( LibplotRenderer ):

	def __init__( self, file, paper="", width="", height="", **kw ):
		ll = 0, 0
		ur = _str_size_to_pts(width), _str_size_to_pts(height)
		pagesize = "%s,xsize=%s,ysize=%s" % (paper,width,height)
		for key,val in kw.items():
			pagesize = pagesize +","+ key +"="+ val
		parameters = { "PAGESIZE": pagesize }
		LibplotRenderer.__init__( self, ll, ur, "ps", parameters, file )

class ImageRenderer( LibplotRenderer ):

	def __init__( self, type, width, height, file ):
		ll = 0, 0
		ur = width, height
		parameters = { "BITMAPSIZE": "%dx%d" % (width, height) }
		LibplotRenderer.__init__( self, ll, ur, type, parameters, file )

