#!/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