#!/usr/bin/env python

###
### Python Browser
### Student assignment for the python course 2003
### June 3 - July 1 2003
### Class given by Raimar Falke and Bernhard Kauer
###
### Josef Spillner
### Dresden University of Technology
### <js177634@inf.tu-dresden.de>
### http://mindx.dyndns.org/uni/python/
###

### External python modules ============================================== ###
###
###

import formatter
import htmllib
import urllib
import urlparse
import re
import md5
import os
import sys

import pygame

### Global variables ===================================================== ###
###
###

progresscount = 0
progresstotal = 0

zoomfactor = 100

globalx = 0
globaly = 0
globalpage = None

### Table container class ================================================ ###
###
###

class table:

	### Initialize a table with inheritance

	def __init__(self, ancestor, name):
		self.name = name
		self.children = []
		self.x = 0
		self.y = 0
		self.w = 0
		self.h = 0
		self.content = None
		if ancestor is not None:
			self.parent = ancestor
			if ancestor.align == "horizontal":
				self.align = "vertical"
			else:
				self.align = "horizontal"
			ancestor.children.append(self)
		else:
			self.parent = None
			self.align = "vertical"

	### Dump a table structure

	def dump(self, x = 0):
		p = ""
		for i in range(0, x):
			p += "  "
		p += self.name + " "
		p += str(self.x) + "/" + str(self.y) + " - " + str(self.w) + "/" + str(self.h)
		p += " " + self.align
		print p
		for c in self.children:
			c.dump(x + 1)

	### Fix all absolute size values

	def cons(self):
		givenw = 0
		givenh = 0
		count = 0
		for c in self.children:
			if self.align == "horizontal":
				if c.w > 0:
					givenw += c.w
					count += 1
				c.h = self.h
			if self.align == "vertical":
				if c.h > 0:
					givenh += c.h
					count += 1
				c.w = self.w
		if len(self.children) <= count:
			return
		if self.align == "horizontal":
			n = (self.w - givenw) / (len(self.children) - count)
			for c in self.children:
				if c.w == 0:
					c.w = n
		else:
			n = (self.h - givenh) / (len(self.children) - count)
			for c in self.children:
				if c.h == 0:
					c.h = n
		for c in self.children:
			c.cons()

	### Normalize widths or heights

	def normalize(self, direction):
		if direction == "vertical":
			if len(self.children) == 0:
				print "##### weird!"
				return
			l = len(self.children[0].children)
			max = 0
			for d in range(0, l):
				max = 0
				for c in self.children:
					if d < len(c.children):
						if c.children[d].w > max:
							max = c.children[d].w
				for c in self.children:
					if d < len(c.children):
						c.children[d].w = max
			if self.w == 0:
				self.w = max * l
		elif direction == "horizontal":
			for c in self.children:
				max = 0
				for d in c.children:
					if d.h > max:
						max = d.h
				for d in c.children:
					d.h = max
				c.h = max

	### Finish a table by converting all sizes

	def finish(self):
		y = 0
		for c in self.children:
			x = 0
			for d in c.children:
				d.x = x
				d.w += x
				x = d.w
			c.y = y
			c.h += y
			y = c.h

### Helper classes ======================================================= ###
###
###

### Surface data without content allocation

class dummypage:
	def __init__(self, w):
		self.w = w

	def get_width(self):
		return self.w

### Stylesheet top class

class stylesheet:
	def __init__(self):
		self.ident = None
		self.element = None
		self.styles = {}

### Surface enhancements with absolute positions

class surface:
	def __init__(self, surface):
		self.surface = surface
		self.original = None
		self.x = -1
		self.y = -1

### Render part

class renderpart:
	def __init__(self):
		self.x = -1
		self.y = -1
		self.surfaces = []
		self.content = []
		self.attributes = {}
		self.children = []
		self.parent = None
		self.activated = 0
		self.container = None
		self.hyperlink = None

### Main render object containing the renderpart tree

class renderobject:
	def __init__(self):
		self.title = ""
		self.root = renderpart()
		self.part = self.root
		self.bodypart = None
		self.stylepart = None
		self.resources = []
		self.containers = []

### HTML validation actions

def validator(str):
	print str

### Return a string backwards

def reverse(s):
	rs = ""
	for x in s:
		rs = x + rs
	return rs

### Return the int representation of a color component

def hex2bin(hex):
	number = 0
	factor = 1
	for x in reverse(hex):
		n = x
		if n == 'a' or n == "A":
			n = '10'
		if n == 'b' or n == "B":
			n = '11'
		if n == 'c' or n == "C":
			n = '12'
		if n == 'd' or n == "D":
			n = '13'
		if n == 'e' or n == "E":
			n = '14'
		if n == 'f' or n == "F":
			n = '15'
		number = number + int(n) * factor
		factor = factor * 16
	return number

### Return a color tuple

def getcolor(str):
	map = {
		"blue": "#0000ff",
		"yellow": "#ffff00",
		"teal": "#207090",
		"white": "#ffffff",
		"red": "#0000ff"
	}

	if str.lower() in map.keys():
		rgbcolor = map[str.lower()]
	else:
		if str[0:1] != "#":
			str = "#" + str
		rgbcolor = str
	colors = (hex2bin(rgbcolor[1:3]), hex2bin(rgbcolor[3:5]), hex2bin(rgbcolor[5:7]))
	return colors

### Parser class ========================================================= ###
###
###

class myparser(htmllib.HTMLParser):

	### Initialize parser

	def __init__(self):
		f = formatter.NullFormatter()
		htmllib.HTMLParser.__init__(self, f)
		self.statelist = []
		self.statestack = []
		self.object = None
		self.container = None
		self.baseurl = ""

		self.tempdir = "/tmp/pyromaniac/"

	### Invoke end tag handler

	def __getattr__(self, name):
		if name[0:3] == "end":
			tag = name[4:]
			self.handle_endtag(tag, None)

	### Always return the absolute URL

	def correcturl(self, url):
		return urlparse.urljoin(self.baseurl, url)

	### Download an external resource (may be cached)

	def download(self, url):
		url = self.correcturl(url)
		localfile = self.tempdir + md5.md5(url).hexdigest()
		try:
			file = open(localfile, "r")
			self.debug("DOWNLOADING (cached): " + url)
		except:
			self.debug("DOWNLOADING (live): " + url)
			try:
				file = urllib.urlopen(url)
			except:
				file = None
				validator("Warning! Resource " + url + " not found!")
		progress(-1)
		if file is not None:
			contents = file.read()
			file.close()
			try:
				os.mkdir(self.tempdir)
			except:
				pass
			local = open(localfile, "w")
			local.writelines(contents)
			local.close()
			return localfile
		return None

	### Parse a file or URL

	def parse(self, filename, object):
		self.baseurl = filename
		if filename[0:7] == "http://":
			f = self.download(filename)
			filename = f
		try:
			contents = open(filename).read()
		except:
			return
		contents = re.sub("\\n", " ", contents) #### FIXME! TODO!
		contents = re.sub("\\t", " ", contents) #### FIXME! TODO!

		while 1:
			match = re.search("\\ \\ ", contents)
			if match:
				contents = re.sub("\\ \\ ", " ", contents) #### FIXME! TODO!
			else:
				break

		self.object = object

		self.feed(contents)
		self.close()
		print "!!!! linklist: ", self.object.resources

	### Debug function

	def debug(self, str):
		#print str
		pass

	### Handler for HTML tag data

	def handle_data(self, data):
		if len(self.statelist) > 0:
			state = self.statelist[len(self.statelist) - 1]
			self.object.part = self.statestack[len(self.statestack) - 1]
		else:
			state = "[invalid]"
			stack = None
		self.debug("DATA " + data + " in " + state)

		if state == "html":
			return
		if state == "title":
			self.object.title = data
			self.object.part.attributes['content'] = [data]
			return
		if state == "style":
			self.object.part.attributes['content'] = [data]
			return

		if state == "table" or state == "tr":
			return

		tags = ["body", "h1", "h2", "h3", "h4", "h5", "h6", "h7", "li", "hr"]
		tags += ["td", "a", "b", "i", "p", "font", "tt", "sub", "sup", "s"]
		tags += ["strike", "big", "small", "u", "center", "th", "form", "div"]
		tags += ["span"]
		if state in tags:
			if len(self.object.part.children) > 0:
				child = self.object.part.children[-1]
				if child.attributes['__type'] == "__text":
					child.attributes['content'] = [child.attributes['content'][0] + data]
					return

			if data == " ":
				return

			child = renderpart()
			child.parent = self.object.part
			self.object.part.children.append(child)
			child.attributes['__type'] = "__text"
			child.attributes['content'] = [data]
			if state == "span":
				child.attributes['css::color'] = "#106f10"
		else:
			child = renderpart()
			child.parent = self.object.part
			self.object.part.children.append(child)
			child.attributes['__type'] = "__text"
			child.attributes['css::color'] = "#c0c0c0"
			child.attributes['css::strikethrough'] = 1
			child.attributes['content'] = [data]

	### Handler for HTML tag begin

	def handle_starttag(self, tag, data, attributes):
		self.debug("<" + tag + ">")
		child = renderpart()
		child.parent = self.object.part
		self.object.part.children.append(child)
		child.attributes['__type'] = tag
		for attribute in attributes:
			child.attributes[attribute[0]] = attribute[1]

		single = ["br", "img", "input", "hr"]
		if tag not in single:
			self.object.part = child
			self.statelist.append(tag)
			self.statestack.append(child)

		state = tag

		self.debug(">> new state could be: " + state)

		resource = None
		if tag == "img":
			if child.attributes.has_key('src'):
				resource = child.attributes['src']
		if tag == "td" or tag == "th" or tag == "body":
			if child.attributes.has_key('background'):
				resource = child.attributes['background']
		if resource is not None:
			self.object.resources.append(resource)

		if tag == "body":
			self.object.bodypart = self.object.part
		if tag == "style":
			self.object.stylepart = self.object.part

		### Container handling

		if tag == "table" or tag == "tr" or tag == "td" or tag == "th":
			self.container = table(self.container, tag)
			a = self.object.part.attributes
			if a.has_key('width'):
				w = a['width']
				if w[-1:] == "%":
					w = 800
				self.container.w = int(w)
			if a.has_key('height'):
				h = a['height']
				if h[-1:] == "%":
					h = 800
				self.container.h = int(h)
			if tag == "td" or tag == "th":
				self.container.content = self.object.part
				self.object.part.container = self.container
			print "##### TABLE #####", tag, "for", self.object.part.attributes['__type']

		self.debug(">> new state is: " + state)

	### Handler for HTML tag end

	def handle_endtag(self, tag, foo):
		single = ["br", "img", "input", "hr"]
		if tag in single:
			self.debug("** circumvent " + tag)
			return

		self.debug("</" + tag + ">")
		self.statelist = self.statelist[:-1]
		self.statestack = self.statestack[:-1]

		### Container handling

		if self.container is not None:
			if self.container.name == tag:
				old = self.container
				self.container = self.container.parent
				if self.container == None:
					print "##### TABLE FINISHED #####", tag
					self.object.containers.append(old)

		if len(self.statelist) > 0:
			state = self.statelist[len(self.statelist) - 1]
			self.object.part = self.statestack[len(self.statestack) - 1]
		else:
			state = "[invalid]"
			stack = None
		self.debug(">> new state is now again: " + state)

	### Optimize all headings (h1-h7) to __h

	def optimizer_headings(self, part):
		a = part.attributes
		if a.has_key('__type'):
			if a['__type'] == "h1":
				a['__type'] = "__h"
				a['__headingsize'] = 1
			if a['__type'] == "h2":
				a['__type'] = "__h"
				a['__headingsize'] = 2
			if a['__type'] == "h3":
				a['__type'] = "__h"
				a['__headingsize'] = 3
			if a['__type'] == "h4":
				a['__type'] = "__h"
				a['__headingsize'] = 4
			if a['__type'] == "h5":
				a['__type'] = "__h"
				a['__headingsize'] = 5
			if a['__type'] == "h6":
				a['__type'] = "__h"
				a['__headingsize'] = 6
			if a['__type'] == "h7":
				a['__type'] = "__h"
				a['__headingsize'] = 7

		for part in part.children:
			self.optimizer_headings(part)

	### Generic tree walker method

	def optimizer_treewalk(self, part, attribute, value):
		part.attributes[attribute] = value
		for part in part.children:
			self.optimizer_treewalk(part, attribute, value)

	### Generic additive tree walker method

	def optimizer_treewalk_add(self, part, attribute, value):
		if part.attributes.has_key(attribute):
			part.attributes[attribute] += value
		else:
			part.attributes[attribute] = 0
		for part in part.children:
			self.optimizer_treewalk_add(part, attribute, value)

	### Optimize all attributes (except CSS)

	def optimizer_attributes(self, part):
		a = part.attributes
		if part.parent != None:
			clist = part.parent.children
		if a.has_key('__type'):
			if a['__type'] == "font":
				for childpart in part.children:
					if a.has_key('size'):
						size = a['size']
						if size == "small":
							size = "-1"
						if size[0:1] == "+" or size[0:1] == "-":
							rel = size[0:1] + str(int(size) * 5)
							self.optimizer_treewalk(childpart, "css::fontsizerel", rel)
						elif size != "":
							self.optimizer_treewalk(childpart, "css::font-size", int(size) * 5)
					if a.has_key('color'):
						self.optimizer_treewalk(childpart, "css::color", a['color'])
					childpart.parent = part.parent
					clist.insert(clist.index(part), childpart)
				part.parent.children.remove(part)
				part = part.parent
			if a['__type'] == "__h":
				size = 48
				if a['__headingsize'] == 1:
					size = "35"
				if a['__headingsize'] == 2:
					size = "30"
				if a['__headingsize'] == 3:
					size = "25"
				if a['__headingsize'] == 4:
					size = "20"
				if a['__headingsize'] == 5:
					size = "15"
				if a['__headingsize'] == 6:
					size = "10"
				if a['__headingsize'] == 7:
					#size = "5" ### FIXME: pygame bug
					size = "10"
				childpart = part.children[0]
				if childpart is not None:
					childpart.attributes['__prebreak'] = 1
				childpart = None
				for childpart in part.children:
					self.optimizer_treewalk(childpart, "css::font-size", size)
					childpart.parent = part.parent
					clist.insert(clist.index(part), childpart)
				newpart = renderpart()
				newpart.attributes['__type'] = "br"
				clist.insert(clist.index(part), newpart)

				part.parent.children.remove(part)
				part = part.parent
			if a['__type'] == "big":
				for childpart in part.children:
					self.optimizer_treewalk(childpart, "css::sizerel", 1);
					clist.insert(clist.index(part), childpart)
					childpart.parent = part.parent
				part.parent.children.remove(part)
				part = part.parent
			if a['__type'] == "small":
				for childpart in part.children:
					self.optimizer_treewalk(childpart, "css::sizerel", -1);
					clist.insert(clist.index(part), childpart)
					childpart.parent = part.parent
				part.parent.children.remove(part)
				part = part.parent

			if a['__type'] == "form":
				for childpart in part.children:
					self.optimizer_treewalk(childpart, "__form", 1);
					clist.insert(clist.index(part), childpart)
					childpart.parent = part.parent
				part.parent.children.remove(part)
				part = part.parent

			css = {
				"i": "css::italic",
				"b": "css::font-weight",
				"tt": "css::fixedwidth",
				"u": "css::underline",
				"s": "css::strikethrough",
				"strike": "css::strikethrough",
				"sup": "css::superscript",
				"sub": "css::subscript",
				"pre": "css:formatted",
				"center": "css::centered"
			}
			if css.has_key(a['__type']):
				for childpart in part.children:
					self.optimizer_treewalk(childpart, css[a['__type']], 1);
					clist.insert(clist.index(part), childpart)
					childpart.parent = part.parent
				clist.remove(part)
				part = part.parent

			if a['__type'] == "ol" or a['__type'] == "dl":
				i = 0
				for childpart in part.children:
					if childpart.attributes['__type'] == "li":
						i += 1
						self.optimizer_treewalk(childpart, "css::ordered", i);
						self.optimizer_treewalk_add(childpart, "css::leftmargin", 20);
						clist.insert(clist.index(part), childpart)
						childpart.parent = part.parent
				part.parent.children.remove(part)
				part = part.parent
			if a['__type'] == "ul":
				for childpart in part.children:
					self.optimizer_treewalk(childpart, "css::unordered", 1);
					self.optimizer_treewalk_add(childpart, "css::leftmargin", 20);
					clist.insert(clist.index(part), childpart)
					childpart.parent = part.parent
				part.parent.children.remove(part)
				part = part.parent

			if a['__type'] == "a":
				if a.has_key('href'):
					for childpart in part.children:
						self.optimizer_treewalk(childpart, "href", a['href']);
						clist.insert(clist.index(part), childpart)
						childpart.parent = part.parent
				elif a.has_key('name'):
					for childpart in part.children:
						self.optimizer_treewalk(childpart, "name", a['name']);
						clist.insert(clist.index(part), childpart)
						childpart.parent = part.parent
				else:
					validator("A is missing HREF or NAME")
				part.parent.children.remove(part)
				part = part.parent

			if a['__type'] == "tr":
				a['__break'] = 1

			if a['__type'] == "li" or a['__type'] == "dd" or a['__type'] == "dt":
				childpart = renderpart()
				if a.has_key('css::unordered'):
					childpart.attributes['__type'] = "bullet"
					childpart.attributes['css::bullettype'] = "round"
				elif a.has_key('css::ordered'):
					childpart.attributes['__type'] = "__text"
					childpart.attributes['content'] = [str(a['css::ordered']) + "."]
				else:
					validator("LI is not enclosed by OL or UL")
				clist.insert(clist.index(part), childpart)
				childpart.attributes['__prebreak'] = 1
				if a.has_key('css::leftmargin'):
					childpart.attributes['css::leftmargin'] = a['css::leftmargin']
				childpart.parent = part.parent
				childpart = None
				for childpart in part.children:
					clist.insert(clist.index(part), childpart)
					childpart.parent = part.parent
				clist.remove(part)
				part = part.parent
				if childpart != None:
					childpart.attributes['__break'] = 1

		for childpart in part.children:
			self.optimizer_attributes(childpart)

	### Dump the optimized HTML tree

	def dump(self, part, level):
		a = part.attributes
		if a.has_key('__type'):
			tag = a['__type']
		else:
			tag = "(unknown)"
		tagline = ""
		for i in range(0, level):
			tagline += " "
		tagline += "<" + tag
		for key in a:
			if key != "__type":
				value = a[key]
				if type(value) == type([]):
					value = "[list:" + value[0] + "]"
				if type(value) == type(0):
					value = str(value)
				tagline += " " + key + "=\"" + value + "\""
		tagline += ">"
		print tagline
		if len(part.surfaces) > 0:
			self.debug("# surfaces" + str(part.surfaces))
		if part.container is not None:
			self.debug("() container" + str(part.container))
		for childpart in part.children:
			self.dump(childpart, level + 1)
		if len(part.children) > 0:
			tagline = ""
			for i in range(0, level):
				tagline += " "
			tagline += "</" + tag + ">"
			print tagline

	### Apply a CSS style to a parse tree

	def optimizer_styleapply(self, part, style, active):
		a = part.attributes
		if a.has_key('__type'):
			self.debug("CSS DETECTION: " + part.attributes['__type'])
			if a.has_key('class'):
				if a['class'] == style.ident:
					active = 1
					self.debug("CSS IS ACTIVE!")

			if active:
				if style.element == None or style.element == a['__type']:
					for s in style.styles.keys():
						self.debug("CSS APPLY!")
						a["css::" + s] = style.styles[s]

		for childpart in part.children:
			self.optimizer_styleapply(childpart, style, active)

	### Apply a list of CSS styles to a parse tree

	def optimizer_style(self, part, stylelist):
		if stylelist == None:
			return
		print "*** CASCADING STYLE SHEETS ***"
		for s in stylelist:
			print "NAME", s.ident, "ELEM", s.element, "STYLES", s.styles
			self.optimizer_styleapply(part, s, 0)

	### Parse document style sheet (CSS) definitions

	def stylesheetparser(self, part):
		if part == None:
			return
		if not part.attributes.has_key('content'):
			return

		definition = part.attributes['content']
		tokens = definition[0].split(" ")
		state = "ident"
		stylelist = []
		style = None
		attribute = None
		for t in tokens:
			if t == "":
				continue

			if state == "ident":
				state = "element-or-brace"
				style = stylesheet()
				ident = t
				if ident[0] == ".":
					ident = ident[1:]
				style.ident = ident
				stylelist.append(style)
			elif state == "element-or-brace":
				if t == "{":
					state = "attribute"
				else:
					state = "brace"
					style.element = t
			elif state == "brace":
				state = "attribute"
			elif state == "attribute":
				if t == "}":
					state = "ident"
				else:
					state = "value"
					attribute = t[:-1]
			elif state == "value":
				state = "attribute"
				style.styles[attribute] = t[:-1]
			else:
				print "Invalid state! " + state

		return stylelist

### More helper classes and methods ====================================== ###
###
###

### Display download progress

def progress(p):
	global navpage
	global progresscount
	global progresstotal

	if p < 0:
		progresscount -= 1
	else:
		progresscount = p
		progresstotal = p
	print "-- progress --", progresscount

	if progresstotal > 0:
		bar = pygame.Surface((800, 10))
		bar.fill((0, 0, 140))
		bar.fill((0, 0, 200), (0, 0, 800 - 800 * progresscount / progresstotal, 10))

		#navpage.blit(bar, (0, 0))
		#screen.blit(navpage, (0, 10))
		screen.blit(bar, (0, 0))
		pygame.display.update()

### Return a container background part

def containerrender(part):
	a = part.attributes

	colors = None
	img = None
	if a.has_key('bgcolor'):
		rgbcolor = a['bgcolor']
		colors = getcolor(rgbcolor)
	elif a.has_key('css::background'):
		rgbcolor = a['css::background']
		colors = getcolor(rgbcolor)
	else:
		colors = (255, 255, 255)
	if a.has_key('background'):
		bg = parser.download(a['background'])
		img = pygame.image.load(bg)
		img = pygame.transform.scale(img, (800, 600))

	x = None
	if a['__type'] == "th" or a['__type'] == "td" or a['__type'] == "body":
		w = 100
		h = 100
		if a['__type'] == "body":
			w = 800
			h = 6000
		if a.has_key('width'):
			wx = a['width']
			if type(wx) == type(""):
				if wx[-1:] == "%":
					wx = "200" ### FIXME!
			w = int(wx)
		if a.has_key('height'):
			hx = a['height']
			if type(hx) == type(""):
				if hx[-1:] == "%":
					hx = "200" ### FIXME!
			h = int(hx)
		if w > 0 and h > 0:
			x = pygame.Surface((w, h))
			x.fill(colors)

			for j in range(0, x.get_height()):
				x.set_at((0, j), (0, 0, 0))
				x.set_at((x.get_width() - 1, j), (0, 0, 0))
			for i in range(0, x.get_width()):
				x.set_at((i, 0), (0, 0, 0))
				x.set_at((i, x.get_height() - 1), (0, 0, 0))

	return x

### Font enhancement class

class fontobject:
	def __init__(self, font, rfontsize):
		self.font = font
		self.rfontsize = rfontsize

### Return an appropriate font

def getfont(part):
	a = part.attributes

	fontsize = 15
	if a.has_key('css::font-size'):
		param = str(a['css::font-size'])
		if len(param) > 2 and param[-2:] == "pt":
			size = param[:-2]
			fontsize = int(size) * 2
		else:
			fontsize = int(param)
	if a.has_key('css::fontsizerel'):
		relsize = str(a['css::fontsizerel'])
		sign = relsize[0:1]
		amount = relsize[1:]
		if sign == "-":
			fontsize -= int(amount)
		elif sign == "+":
			fontsize += int(amount)
	rfontsize = fontsize
	if a.has_key('css::superscript'):
		fontsize = int(fontsize * 0.7)
	if a.has_key('css::subscript'):
		fontsize = int(fontsize * 0.7)
	if fontsize < 10:
		fontsize = 10 # FIXME! pygame bug
	font = pygame.font.Font(None, fontsize)

	if a.has_key('css::font-weight'):
		font.set_bold(1)
	if a.has_key('css::italic'):
		font.set_italic(1)
	if a.has_key('css::underline'):
		font.set_underline(1)

	return fontobject(font, rfontsize)

### Return appropriate font colors

def getfontcolors(part):
	a = part.attributes

	if a.has_key('css::color'):
		rgbcolor = a['css::color']
		colors = getcolor(rgbcolor)
	else:
		colors = (0, 0, 0)
	if a.has_key('href'):
		colors = (0, 0, 255)

	return colors

### Render a text using a given font

def fontrender(part, text, fontobject = None, colors = None):
	a = part.attributes

	if fontobject == None:
		fontobject = getfont(part)
	if colors == None:
		colors = getfontcolors(part)

	font = fontobject.font
	rfontsize = fontobject.rfontsize
	fontsize = font.get_height()

	textline = font.render(text, 1, colors)
	if a.has_key('css::strikethrough'):
		j = textline.get_height() / 2
		for i in range(0, textline.get_width()):
			textline.set_at((i, j), colors)
	if a.has_key('css::superscript') or a.has_key('css::subscript'):
		tmp = pygame.Surface((textline.get_width(), rfontsize))
		tmp.fill((255, 255, 255)) ### ALPHA
		if a.has_key('css::superscript'):
			tmp.blit(textline, (0, 0))
		else:
			tmp.blit(textline, (0, rfontsize - fontsize))
		textline = tmp
	if a.has_key('css::centered'): ### FIXME! centering
		tmp = pygame.Surface((textline.get_width(), rfontsize))
		tmp.fill((255, 255, 255)) ### ALPHA
		tmp.blit(textline, (tmp.get_width() / 2 - textline.get_width() / 2, 0))
		textline = tmp

	return textline

### Render each element of a parse tree

def render(page, part):
	if part == None:
		return
	a = part.attributes
	if a.has_key('__type'):
		if a['__type'] == "img":
			if a.has_key('src'):
				src = parser.download(a['src'])
				try:
					img = pygame.image.load(src)
					width = img.get_width()
					height = img.get_height()
					if a.has_key('width'):
						width = int(a['width'])
					if a.has_key('height'):
						height = int(a['height'])
					img = pygame.transform.scale(img, (width, height))
				except:
					width = 20
					height = 20
					if a.has_key('width'):
						width = int(a['width'])
					if a.has_key('height'):
						width = int(a['height'])
					img = pygame.Surface((width, height))
					img.fill((255, 0, 0))
				s = surface(img)
				part.surfaces.append(s)
			else:
				validator("IMG is missing SRC")
		elif a['__type'] == "bullet":
			if a.has_key('css::color'):
				rgbcolor = a['css::color']
				colors = getcolor(rgbcolor)
			else:
				colors = (0, 0, 0)
			img = pygame.Surface((10, 10))
			img.fill(colors)
			s = surface(img)
			part.surfaces.append(s)
		elif a['__type'] == "hr":
			img = pygame.Surface((page.get_width() - 20, 5))
			img.fill((255, 200, 200))
			s = surface(img)
			part.surfaces.append(s)
		elif a['__type'] == "input":
			type = "text"
			if a.has_key('type'):
				type = a['type']
			if type == "text":
				img = pygame.Surface((300, 20))
				img.fill((255, 200, 200))
			elif type == "checkbox":
				img = pygame.Surface((20, 20))
				img.fill((255, 200, 200))
				if a.has_key('checked'):
					if a['checked'] == "CHECKED":
						img.fill((0, 0, 0), (5, 5, 10, 10))
			elif type == "submit":
				title = "Submit"
				if a.has_key('value'):
					title = a['value']
				fontsize = 20
				font = pygame.font.Font(None, fontsize)
				text = font.render(title, 1, (0, 0, 0))
				img = pygame.Surface((text.get_width(), text.get_height()))
				img.fill((255, 200, 200))
				img.blit(text, (0, 0))
			else:
				img = pygame.Surface((20, 20))
				img.fill((255, 200, 200))
			s = surface(img)
			part.surfaces.append(s)
		elif a['__type'] == "body":
			img = containerrender(part)
			font = pygame.font.Font(None, 20)
			for text in part.content:
				textline = font.render(text, 1, (0, 0, 0), colors)
				s = surface(textline)
				part.surfaces.append(s)

			if a['__type'] == "body":
				if img is not None:
					page.blit(img, (0, 0))
				elif colors is not None:
					page.fill(colors)
			else:
				w = 100
				h = 100
				if a.has_key('width'):
					wx = a['width']
					if wx[-1:] == "%":
						wx = "200" ### FIXME!
					w = int(wx)
				if a.has_key('height'):
					hx = int(a['height'])
					if hx[-1:] == "%":
						hx = "200" ### FIXME!
					h = int(hx)
				x = pygame.Surface((w, h))
				x.fill(colors)
				s = surface(x)
				part.surfaces.append(s)
		elif a['__type'] == "__text":
			fontobject = getfont(part)
			colors = getfontcolors(part)
			font = fontobject.font
			rfontsize = fontobject.rfontsize

			width = page.get_width() - 100 ### FIXME: containers
			textstrategy = "words" ### words or width
			for text in a['content']:
				(x, y) = font.size(text)
				txt = text
				while len(txt):
					if textstrategy == "words":
						txt2 = ""
						ready = 0
						while not ready:
							txt2 += txt[0:1]
							txt = txt[1:]
							if txt2[-1:] == " ":
								ready = 1
							if len(txt) == 0:
								ready = 1
					elif textstrategy == "width":
						txt2 = txt
						while font.size(txt2)[0] > width and len(txt2):
							txt2 = txt2[:-1]
						txt = txt[len(txt2):]
						if len(txt2) == 0:
							txt2 = txt
							txt = ""
					else:
						print "Error! Unknown text strategy"
					a['content'].insert(a['content'].index(text), txt2)
				a['content'].remove(text)

			for text in a['content']:
				if text != " ":
					textline = fontrender(part, text, fontobject, colors)
					s = surface(textline)
					part.surfaces.append(s)
	for part in part.children:
		render(page, part)

### Class representing a hyperlink

class hyperlink:
	def __init__(self, surface, x, y, part, xoff, yoff, page):
		self.surface = surface
		self.x = x
		self.y = y
		self.part = part
		self.xoff = xoff
		self.yoff = yoff
		self.page = page

### Return user input from a text input field

def getinput(h):
	url = ""
	shadow = {}
	shift = 0
	while 1:
		pygame.event.pump()
		keystates = pygame.key.get_pressed()
		for keycode in range(0, len(keystates)):
			key = pygame.key.name(keycode)
			if not shadow.has_key(keycode):
				shadow[keycode] = 0
			if keystates[keycode] and not shadow[keycode]:
				shadow[keycode] = 1
				if key == "escape":
					return ""
				elif key == "return":
					return url
				elif key == "backspace":
					url = url[:-1]
				elif key == "right shift":
					shift = 1
				elif key == "left shift":
					shift = 1
				else:
					if shift == 1:
						if key == ".":
							key = ":"
						if key == "7":
							key = "/"
					url = url + key
			if not keystates[keycode] and shadow[keycode]:
				shadow[keycode] = 0
				if key == "right shift":
					shift = 0
				if key == "left shift":
					shift = 0

		h.page.blit(h.part.surfaces[0].surface, (h.part.x, h.part.y))
		if url is not "":
			font = pygame.font.Font(None, 20)
			textline = font.render(url, 1, (0, 0, 255))
			h.page.blit(textline, (h.part.x, h.part.y))
		screen.blit(h.page, (h.xoff, h.yoff))
		pygame.display.update()

### Displayer class ====================================================== ###
###
###

class displayer:

	### Initialize class, including reset

	def __init__(self):
		self.edgex = 10
		self.edgey = 10
		self.depth = 0
		self.fontstrategy = "passive" # overlay or passive

		self.reset()

	### Reset all values

	def reset(self):
		self.posx = self.edgex
		self.posy = self.edgey
		self.oldmaxy = 0
		self.leftmargin = 0
		self.totalheight = 0

	### Assign absolute positions to all renderparts

	def display(self, page, part, construction, reset = 1):
		global globalx, globaly
		global zoomfactor

		if part == None:
			return

		if reset == 1:
			self.reset()
			print "reset image:", page.get_width()

		a = part.attributes

		dobreak = 0

		types = ["ol", "br", "p", "table"]
		if a.has_key('__type'):
			if a['__type'] in types:
				dobreak = 1

		if a.has_key('__prebreak'):
			if a['__prebreak'] == 1:
				dobreak = 1

		if a.has_key('css::leftmargin'):
			self.leftmargin = a['css::leftmargin']
		else:
			self.leftmargin = 0

		if dobreak:
			self.posx = self.edgex
			self.posy += self.oldmaxy + 2
			self.oldmaxy = 0

		if reset == 0:
			if part.container is not None:
				d = displayer()
				rpage = containerrender(part)
				if rpage is not None:
					d.blitter(rpage, part, 1, 1)
					part.surfaces = [surface(rpage)]

		maxx = 0
		lastx = 0
		maxy = 0
		for s in part.surfaces:
			if lastx > 0:
				self.posx += lastx
			if s.original == None:
				o = pygame.Surface((s.surface.get_width(), s.surface.get_height()))
				o.fill((255, 255, 255)) ### ALPHA
				o.blit(s.surface, (0, 0))
				s.original = o ### FIXME: s.original = s.surface
			if zoomfactor != 100:
				zoomx = (s.original.get_width() * zoomfactor) / 100
				zoomy = (s.original.get_height() * zoomfactor) / 100
				print "zoom", s.original.get_width(), s.original.get_height(), "=>", zoomx, zoomy, zoomfactor
				s.surface = pygame.transform.scale(s.original, (zoomx, zoomy))
			else:
				o = pygame.Surface((s.original.get_width(), s.original.get_height()))
				o.fill((255, 255, 255)) ### ALPHA
				o.blit(s.original, (0, 0))
				s.surface = o ### FIXME: s.surface = s.original

			if self.posx + s.surface.get_width() > page.get_width() - self.leftmargin + 2: ### FIXME: containers
				self.posx = self.edgex
				self.posy += self.oldmaxy + 2
				self.oldmaxy = 0

			rposx = self.posx + self.leftmargin
			part.x = rposx
			part.y = self.posy
			s.x = rposx
			s.y = self.posy

			if construction == 1:
				if a.has_key('href'):
					h = hyperlink(s, rposx, self.posy, part, globalx, globaly, globalpage)
					links.append(h)
					part.hyperlink = h
				if a.has_key('__type'):
					if a['__type'] == "input":
						part.attributes['href'] = "internal:input"
						h = hyperlink(s, rposx, self.posy, part, globalx, globaly, globalpage)
						links.append(h)
						part.hyperlink = h

			if s.surface.get_width() > maxx:
				maxx = s.surface.get_width() + 1
			lastx = s.surface.get_width() + 1
			if s.surface.get_height() > maxy:
				maxy = s.surface.get_height() + 1

			if self.posy + s.surface.get_height() > self.totalheight:
				self.totalheight = self.posy + s.surface.get_height()

			if maxy > 0 and maxy > self.oldmaxy:
				self.oldmaxy = maxy

		dobreak = 0

		types = ["p", "br"]
		if a.has_key('__type'):
			if a['__type'] in types:
				dobreak = 1

		if a.has_key('__break'):
			if a['__break'] == 1:
				dobreak = 1

		subrender = 1
		if reset == 0:
			if part.container is not None:
				subrender = 0

		if subrender:
			for childpart in part.children:
				self.depth += 1
				maxy = self.display(page, childpart, construction, 0)
				self.depth -= 1

		if dobreak:
			self.posx = self.edgex
			self.posy += maxy + 2
		else:
			self.posx += lastx + 2

		return maxy

	### Blits renderparts with fixed positions onto a surface

	def blitter(self, page, part, construction, override):
		if part == None:
			return

		a = part.attributes

		### Blit each surface of the renderpart

		counter = 0
		for s in part.surfaces:
			rposx = s.x
			posy = s.y
			if a['__type'] == "__text" and self.fontstrategy == "overlay":
				tmp = fontrender(part, a['content'][counter])
				page.blit(tmp, (rposx, posy))
			else:
				page.blit(s.surface, (rposx, posy))
			counter += 1

			### Some special effects

			if construction == 0:
				if part.activated == 1:
					for i in range(0, s.surface.get_width()):
						try:
							page.set_at((rposx + i, posy + s.surface.get_height() - 1), (0, 0, 0))
						except:
							pass
			if construction == 1:
				if a.has_key('__type'):
					if a['__type'] == 'input':
						try:
							for j in range(0, s.surface.get_height()):
								page.set_at((rposx, posy + j), (0, 0, 0))
								page.set_at((rposx + s.surface.get_width() - 1, posy + j), (0, 0, 0))
							for i in range(0, s.surface.get_width()):
								page.set_at((rposx + i, posy), (0, 0, 0))
								page.set_at((rposx + i, posy + s.surface.get_height() - 1), (0, 0, 0))
						except:
							pass

		### Also blit the renderpart's children

		if part.container == None or override == 1:
			for c in part.children:
				self.blitter(page, c, construction, override)

### Main program ========================================================= ###
###
###

### Read command line parameters

url = None

for i in range(1, len(sys.argv)):
	if sys.argv[i] == "--help":
		print sys.argv[0] + " [--help] [<url>]"
		sys.exit()
	elif i < len(sys.argv) - 1:
		if sys.argv[i] == "--foo":
			myarg = sys.argv[i + 1]
		else:
			url = sys.argv[i]
	else:
		url = sys.argv[i]

### Initialization

parser = myparser()

pygame.init()
screen = pygame.display.set_mode((800, 600))
page = pygame.Surface((800, 6000))

font = pygame.font.Font(None, 48)

links = []
history = []

### Navigation panel

navigation = """
<html>
<body bgcolor='#ffffff'>
<a href='internal:back'>Zurück</a>
<b> | </b>
<a href='internal:forward'>Vor</a>
<b> | </b>
URL: <input type='text' name='internal:url'>
<b> | </b>
<a href='internal:clear'>Cache leeren</a>
</body>
</html>
"""
#navigation = "" ##### temp! FIXME TODO
navigation = re.sub("\\n", " ", navigation)
navpage = pygame.Surface((800, 40))
globalpage = navpage
object = renderobject()
parser.object = object
parser.feed(navigation)
parser.close()
parser.optimizer_attributes(object.root)
globaly = 10
render(navpage, object.bodypart)
d = displayer()
d.display(navpage, object.bodypart, 1)
d.blitter(navpage, object.bodypart, 1, 0)
screen.blit(navpage, (0, 10))
pygame.display.update()

### Setup navigation

navlinks = []
for l in links:
	navlinks.append(l)
globalpage = page

if url is not None:
	history.append(url)
historyposition = 0

### Main loop

url = ""
while url is not None:

	### Reset navigation

	links = []
	for l in navlinks:
		links.append(l)

	### Parse document

	object = renderobject()
	parser.parse(url, object)
	progress(len(object.resources))

	### Prepare container documents

	for c in object.containers:
		c.w = 600 ### FIXME
		c.dump()
		c.normalize("vertical")
		c.cons()

	### Optimizations

	style = parser.stylesheetparser(object.stylepart)
	parser.optimizer_style(object.root, style)
	parser.optimizer_headings(object.root)
	parser.optimizer_attributes(object.root)
	parser.dump(object.root, 0)
	pygame.display.set_caption(object.title)

	# Rendering

	globalx = 0
	globaly = 50
	render(page, object.bodypart)

	### Apply containers

	for c in object.containers:
		for d in c.children:
			for e in d.children:
				part = e.content
				dd = displayer()
				w = part.container.w - part.container.x
				rpage = dummypage(w) # FIXME: relative values
				dd.display(rpage, part, 1)
				e.h = dd.totalheight

	for c in object.containers:
		c.normalize("horizontal")

	for c in object.containers:
		for d in c.children:
			for e in d.children:
				part = e.content
				w = part.container.w - part.container.x
				h = part.container.h
				w += 20
				h += 20
				part.attributes['width'] = w
				part.attributes['height'] = h

	for c in object.containers:
		c.finish()
		c.dump()

	### Display page off-screen

	d = displayer()
	d.display(page, object.bodypart, 1)
	d.blitter(page, object.bodypart, 1, 0)

	### Display on screen

	scrollx = 0
	scrolly = 0
	scrolled = 0
	zoomed = 0

	screen.blit(page, (0, 50), (scrollx, scrolly, scrollx + 800, scrolly + 550))

	pygame.display.update()

	### Page event loop

	loop = 1
	activelink = None
	oldbutton = 0
	while loop:

		### Check for keyboard events

		link = None

		pygame.event.pump()
		key = pygame.key.get_pressed()
		mods = pygame.key.get_mods()
		if key[pygame.K_ESCAPE]:
			loop = 0
			url = None
		if key[pygame.K_PAGEDOWN]:
			scrolly += 50
			scrolled = 1
		if key[pygame.K_PAGEUP]:
			scrolly -= 50
			scrolled = 1
		if key[pygame.K_DOWN]:
			scrolly += 3
			scrolled = 1
		if key[pygame.K_UP]:
			scrolly -= 3
			scrolled = 1
		if key[pygame.K_RIGHT]:
			if mods & (pygame.KMOD_LALT | pygame.KMOD_RALT):
				link = "internal:forward"
			else:
				scrollx += 3
				scrolled = 1
		if key[pygame.K_LEFT]:
			if mods & (pygame.KMOD_LALT | pygame.KMOD_RALT):
				link = "internal:back"
			else:
				scrollx -= 3
				scrolled = 1
		if key[pygame.K_MINUS] or key[pygame.K_KP_MINUS]:
			if zoomfactor > 10:
				zoomfactor -= 10
				zoomed = 1
		if key[pygame.K_PLUS] or key[pygame.K_KP_PLUS]:
			if zoomfactor < 400:
				zoomfactor += 10
				zoomed = 1

		### Execute keyboard events

		if zoomed:
			page = containerrender(object.bodypart)
			d = displayer()
			d.display(page, object.bodypart, 1)
			d.blitter(page, object.bodypart, 1, 0)
			screen.blit(page, (0, 50), (scrollx, scrolly, scrollx + 800, scrolly + 550))
			pygame.display.update()
			zoomed = 0

		if scrolled:
			xscrollx = scrollx
			xscrolly = scrolly
			if xscrollx >= 800 - 800:
				xscrollx = 800 - 800
			if xscrolly >= 6000 - 550:
				xscrolly = 6000 - 550
			if xscrollx < 0:
				xscrollx = 0
			if xscrolly < 0:
				xscrolly = 0
			screen.fill((0, 0, 0), (0, 50, 800, 550))
			screen.blit(page, (0, 50), (scrollx, scrolly, xscrollx + 800, xscrolly + 550))
			pygame.display.update()

		### Check and execute mouse move events

		(mx, my) = pygame.mouse.get_rel()
		if mx != 0 or my != 0:
			(mx, my) = pygame.mouse.get_pos()
			if my > 50:
				mx = mx + scrollx
				my = my + scrolly
			if activelink is not None:
				activelink.part.activated = 0
				#posx = activelink.x # FIXME!
				#posy = activelink.y
				#d = displayer()
				#d.display(activelink.page, activelink.part, 0)

				activelink = None
				print "mouse reset"
			for h in links:
				s = h.surface.surface
				x = h.x + h.xoff
				y = h.y + h.yoff
				if mx < x + s.get_width() and my < y + s.get_height():
					if mx >= x and my >= y:
						print "-->", s, "at", x, y
						h.part.activated = 1
						#posx = h.x
						#posy = h.y # FIXME!
						#d = displayer()
						#d.display(h.page, h.part, 0)
						activelink = h

		### Check and execute mouse press events

		(button1, button2, button3) = pygame.mouse.get_pressed()
		if button1 is not oldbutton:
			oldbutton = button1
			if button1 == 1:
				if activelink is not None:
					link = activelink.part.attributes['href']
					print "HYPERREF", link

		### Navigation

		if link is not None:
			if link == "internal:back":
				if historyposition > 0:
					historyposition -= 1
					url = history[historyposition]
					loop = 0
					print "*** GET", url, "FROM HISTORY"
			elif link == "internal:forward":
				if historyposition < len(history) - 1:
					historyposition += 1
					url = history[historyposition]
					loop = 0
					print "*** GET", url, "FROM HISTORY (forward)"
			elif link == "internal:clear":
				pass
			elif link == "internal:input":
				newurl = getinput(activelink)
				if newurl is not "":
					url = newurl
					loop = 0
					history.append(url)
			else:
				url = parser.correcturl(link)
				loop = 0
				historyposition += 1
				history = history[0:historyposition]
				history.append(url)

### Program end ========================================================== ###
###
###



syntax highlighted by Code2HTML, v. 0.9.1