Commit before breaking everything
[matches/honours.git] / research / transmission_spectroscopy / simulator / pgu-0.18 / pgu / html.py
1 """Code for html rendering
2 """
3
4 import sys
5
6 # Import the html parser code, maintaing compatibility with older versions of python
7 if (sys.version_info[0] < 3):
8     # Import the old style htmllib parser
9     import htmllib
10     from htmllib import HTMLParser
11 else:
12     # Import the new html.parser module
13     from html.parser import HTMLParser
14     htmllib = None
15
16 import re
17 import pygame
18 from pygame.locals import *
19
20 from pgu import gui
21
22 _amap = {'left':-1,'right':1,'center':0,None:None,'':None,}
23 _vamap = {'top':-1,'bottom':1,'center':0,'middle':0,None:None,'':None,}
24
25 # Used by the HTML parser to load external resources (like images). This 
26 # class loads content from the local file system. But you can pass your own 
27 # resource loader to the HTML parser to find images by other means.
28 class ResourceLoader(object):
29     # Loads an image and returns it as a pygame image
30     def load_image(this, path):
31         return pygame.image.load(path)
32
33 class _dummy:
34     pass
35
36 class _flush:
37     def __init__(self):
38         self.style = _dummy()
39         self.style.font = None
40         self.style.color = None
41         self.cls = None
42     def add(self,w): pass
43     def space(self,v): pass
44     
45 class _hr(gui.Color):
46     def __init__(self,**params):
47         gui.Color.__init__(self,(0,0,0),**params)
48     def resize(self,width=None,height=None):
49         w,h = self.style.width,self.style.height
50         #if width != None: self.rect.w = width
51         #else: self.rect.w = 1
52         
53         #xt,xr,xb,xl = self.getspacing()
54
55         if width != None: w = max(w,width)
56         if height != None: h = max(h,height)        
57         w = max(w,1)
58         h = max(h,1)
59         
60         return w,h #self.container.rect.w,h
61         
62         #self.rect.w = max(1,width,self.container.rect.w-(xl+xr))
63         
64         #print self.rect
65         #self.rect.w = 1
66
67 class _html(HTMLParser):
68     def init(self,doc,font,color,_globals,_locals,loader=None):
69         self.mystack = []
70         self.document = doc
71         if (loader):
72             self.loader = loader
73         else:
74             # Use the default resource loader
75             self.loader = ResourceLoader()
76         self.myopen('document',self.document)
77         
78         self.myfont = self.font = font
79         self.mycolor = self.color = color
80         
81         self.form = None
82         
83         self._globals = _globals
84         self._locals = _locals
85         
86     def myopen(self,type_,w):
87     
88         self.mystack.append((type_,w))
89         self.type,self.item = type_,w
90         
91         self.font = self.item.style.font
92         self.color = self.item.style.color
93         
94         if not self.font: self.font = self.myfont
95         if not self.color: self.color = self.mycolor
96         
97     def myclose(self, tag):
98         self.mydone()
99         n = len(self.mystack)-1
100         while (n >= 0):
101             (t, w) = self.mystack[n]
102             if (t == tag):
103                 # Found the tag in the stack. Drop everything from that tag onwards
104                 # from the stack.
105                 self.mystack = self.mystack[0:n]
106                 # Pop off the parent element, then add it back on to set the
107                 # font, color, etc.
108                 # TODO - tacky
109                 t,w = self.mystack.pop()
110                 self.myopen(t,w)
111                 break
112             n -= 1
113
114 #        t = None
115 #        while t != type_:
116 #            if len(self.mystack)==0:
117 #                # Closing a tag that was never opened
118 #                break
119 #            t,w = self.mystack.pop()
120 #        t,w = self.mystack.pop()
121 #        self.myopen(t,w)
122         
123     def myback(self,type_):
124         if type(type_) == str: type_ = [type_,]
125         self.mydone()
126         #print 'myback',type_
127         t = None
128         while t not in type_:
129             #if len(self.mystack)==0: return
130             t,w = self.mystack.pop()
131         self.myopen(t,w)
132         
133     def mydone(self):
134         #clearing out the last </p>
135         if not hasattr(self.item,'layout'): return 
136         if len(self.item.layout._widgets) == 0: return 
137         w = self.item.layout._widgets[-1]
138         if type(w) == tuple:
139             del self.item.layout._widgets[-1]
140
141         
142     def start_b(self,attrs): self.font.set_bold(1)
143     def end_b(self): self.font.set_bold(0)
144     def start_i(self,attrs): self.font.set_italic(1)
145     def end_i(self): self.font.set_italic(0)
146     def start_u(self,attrs): self.font.set_underline(1)
147     def end_u(self): self.font.set_underline(0)
148     def start_br(self,attrs): self.do_br(attrs)
149     def do_br(self,attrs): self.item.br(self.font.size(" ")[1])
150     def attrs_to_map(self,attrs):
151         k = None
152         r = {}
153         for k,v in attrs: r[k] = v
154         return r
155         
156     def map_to_params(self,r):
157         anum = re.compile("\D")
158         
159         params = {'style':{}}
160         style = params['style']
161
162         if 'bgcolor' in r: 
163             style['background'] = gui.parse_color(r['bgcolor'])
164         if 'background' in r: 
165             style['background'] = self.loader.load_image(r['background'])
166         if 'border' in r: style['border'] = int(r['border'])
167             
168         for k in ['width','height','colspan','rowspan','size','min','max']:
169             if k in r: params[k] = int(anum.sub("",r[k]))
170             
171         for k in ['name','value']:
172             if k in r: params[k] = r[k]
173         
174         if 'class' in r: params['cls'] = r['class']
175         
176         if 'align' in r: 
177             params['align'] = _amap[r['align']]
178         if 'valign' in r:
179             params['valign'] = _vamap[r['valign']]
180
181         if 'style' in r:
182             for st in r['style'].split(";"):
183                 #print st
184                 if ":" in st:
185                     #print st.split(":")
186                     k,v = st.split(":")
187                     k = k.replace("-","_")
188                     k = k.replace(" ","")
189                     v = v.replace(" ","")
190                     if k == 'color' or k == 'border_color' or k == 'background':
191                         v = gui.parse_color(v)
192                     else:
193                         v = int(anum.sub("",v))
194                     style[k] = v
195         return params
196         
197     def map_to_connects(self,e,r):
198         for k,evt in [('onclick',gui.CLICK),('onchange',gui.CHANGE)]: #blah blah blah
199             
200             if k in r:
201                 #print k,r[k]
202                 e.connect(evt,self.myexec,(e,r[k]))
203
204     def start_p(self,attrs):
205         r = self.attrs_to_map(attrs)
206         align = r.get("align","left")
207         
208         self.check_p()
209         self.item.block(_amap[align])
210         
211     def check_p(self):
212         if len(self.item.layout._widgets) == 0: return
213         if type(self.item.layout._widgets[-1]) == tuple:
214             w,h = self.item.layout._widgets[-1]
215             if w == 0: return
216         self.do_br(None)
217         
218     def end_p(self):
219         #print 'end p'
220         self.check_p()
221         
222         
223     def start_block(self,t,attrs,align=-1):
224         r = self.attrs_to_map(attrs)
225         params = self.map_to_params(r)
226         if 'cls' in params: params['cls'] = t+"."+params['cls']
227         else: params['cls'] = t
228         b = gui.Document(**params)
229         b.style.font = self.item.style.font
230         if 'align' in params:
231             align = params['align']
232         self.item.block(align)
233         self.item.add(b)
234         self.myopen(t,b)
235
236         
237                 
238     def end_block(self,t):
239         self.myclose(t)
240         self.item.block(-1)
241         
242     def start_div(self,attrs): self.start_block('div',attrs)
243     def end_div(self): self.end_block('div')
244     def start_center(self,attrs): self.start_block('div',attrs,0)
245     def end_center(self): self.end_block('div')
246     
247     def start_h1(self,attrs): self.start_block('h1',attrs)
248     def end_h1(self): self.end_block('h1')
249     def start_h2(self,attrs): self.start_block('h2',attrs)
250     def end_h2(self): self.end_block('h2')
251     def start_h3(self,attrs): self.start_block('h3',attrs)
252     def end_h3(self): self.end_block('h3')
253     def start_h4(self,attrs): self.start_block('h4',attrs)
254     def end_h4(self): self.end_block('h4')
255     def start_h5(self,attrs): self.start_block('h5',attrs)
256     def end_h5(self): self.end_block('h5')
257     def start_h6(self,attrs): self.start_block('h6',attrs)
258     def end_h6(self): self.end_block('h6')
259
260     def start_ul(self,attrs): self.start_block('ul',attrs)
261     def end_ul(self): self.end_block('ul')
262     def start_ol(self,attrs): 
263         self.start_block('ol',attrs)
264         self.item.counter = 0
265     def end_ol(self): self.end_block('ol')
266     def start_li(self,attrs): 
267         self.myback(['ul','ol'])
268         cur = self.item
269         self.start_block('li',attrs)
270         if hasattr(cur,'counter'):
271             cur.counter += 1
272             self.handle_data("%d. "%cur.counter)
273         else:
274             self.handle_data("- ")
275     #def end_li(self): self.end_block('li') #this isn't needed because of how the parser works
276
277     def start_pre(self,attrs): self.start_block('pre',attrs)
278     def end_pre(self): self.end_block('pre')
279     def start_code(self,attrs): self.start_block('code',attrs)
280     def end_code(self): self.end_block('code')
281             
282     def start_table(self,attrs):
283         r = self.attrs_to_map(attrs)
284         params = self.map_to_params(r)
285         
286         align = r.get("align","left")
287         self.item.block(_amap[align])
288
289         t = gui.Table(**params)
290         self.item.add(t)
291         
292         self.myopen('table',t)
293         
294     def start_tr(self,attrs):
295         self.myback('table')
296         self.item.tr()
297         
298     def _start_td(self,t,attrs):
299         r = self.attrs_to_map(attrs)
300         params = self.map_to_params(r)
301         if 'cls' in params: params['cls'] = t+"."+params['cls']
302         else: params['cls'] = t
303         b = gui.Document(cls=t)
304         
305         self.myback('table')
306         self.item.td(b,**params)
307         self.myopen(t,b)
308     
309         self.font = self.item.style.font
310         self.color = self.item.style.color
311         
312     def start_td(self,attrs):
313         self._start_td('td',attrs)
314     
315     def start_th(self,attrs):
316         self._start_td('th',attrs)
317         
318     def end_table(self):
319         self.myclose('table')
320         self.item.block(-1)
321         
322     def start_form(self,attrs):
323         r = self.attrs_to_map(attrs)
324         e = self.form = gui.Form()
325         e.groups = {}
326         
327         self._locals[r.get('id',None)] = e
328         
329     def start_input(self,attrs):
330         r = self.attrs_to_map(attrs)
331         params = self.map_to_params(r) #why bother
332         #params = {}
333         
334         type_,name,value = r.get('type','text'),r.get('name',None),r.get('value',None)
335         f = self.form
336         if type_ == 'text':
337             e = gui.Input(**params)
338             self.map_to_connects(e,r)
339             self.item.add(e)
340         elif type_ == 'radio':
341             if name not in f.groups:
342                 f.groups[name] = gui.Group(name=name)
343             g = f.groups[name]
344             del params['name']
345             e = gui.Radio(group=g,**params)
346             self.map_to_connects(e,r)
347             self.item.add(e)
348             if 'checked' in r: g.value = value
349         elif type_ == 'checkbox':
350             if name not in f.groups:
351                 f.groups[name] = gui.Group(name=name)
352             g = f.groups[name]
353             del params['name']
354             e = gui.Checkbox(group=g,**params)
355             self.map_to_connects(e,r)
356             self.item.add(e)
357             if 'checked' in r: g.value = value
358
359         elif type_ == 'button':
360             e = gui.Button(**params)
361             self.map_to_connects(e,r)
362             self.item.add(e)
363         elif type_ == 'submit':
364             e = gui.Button(**params)
365             self.map_to_connects(e,r)
366             self.item.add(e)
367         elif type_ == 'file':
368             e = gui.Input(**params)
369             self.map_to_connects(e,r)
370             self.item.add(e)
371             b = gui.Button(value='Browse...')
372             self.item.add(b)
373             def _browse(value):
374                 d = gui.FileDialog();
375                 d.connect(gui.CHANGE,gui.action_setvalue,(d,e))
376                 d.open();
377             b.connect(gui.CLICK,_browse,None)
378
379         self._locals[r.get('id',None)] = e
380
381     def start_object(self,attrs):
382         r = self.attrs_to_map(attrs)
383         params = self.map_to_params(r)
384         # Use eval to automagically get the class being refered
385         cls = eval(r["type"])
386         e = cls(**params)
387         self.map_to_connects(e,r)
388         self.item.add(e)
389         self._locals[r.get('id',None)] = e
390     
391     def start_select(self,attrs):
392         r = self.attrs_to_map(attrs)
393         params = {}
394         
395         name,value = r.get('name',None),r.get('value',None)
396         e = gui.Select(name=name,value=value,**params)
397         self.map_to_connects(e,r)
398         self.item.add(e)
399         self.myopen('select',e)
400         
401     def start_option(self,attrs):
402         r = self.attrs_to_map(attrs)
403         params = {} #style = self.map_to_style(r)
404         
405         self.myback('select')
406         e = gui.Document(**params)
407         self.item.add(e,value=r.get('value',None))
408         self.myopen('option',e)
409         
410         
411     def end_select(self):
412         self.myclose('select')
413         
414     def start_hr(self,attrs):
415         self.do_hr(attrs)
416     def do_hr(self,attrs):
417         h = self.font.size(" ")[1]/2
418         
419         r = self.attrs_to_map(attrs)
420         params = self.map_to_params(r)
421         params['style']['padding'] = h
422
423         self.item.block(0)
424         self.item.add(_hr(**params))
425         self.item.block(-1)
426         
427     def anchor_begin(self,href,name,type_):
428         pass
429
430     def anchor_end(self):
431         pass
432         
433     def start_title(self,attrs): self.myopen('title',_flush())
434     def end_title(self): self.myclose('title')
435             
436     def myexec(self,value):
437         w,code = value
438         g = self._globals
439         l = self._locals
440         l['self'] = w
441         exec(code,g,l)
442         
443     def handle_image(self,src,alt,ismap,align,width,height):
444         try:
445             w = gui.Image(self.loader.load_image(src))
446             if align != '':
447                 self.item.add(w,_amap[align])
448             else:
449                 self.item.add(w)
450         except:
451             print('handle_image: missing %s'%src)
452                 
453     def handle_data(self,txt):
454         if self.type == 'table': return 
455         elif self.type in ('pre','code'): 
456             txt = txt.replace("\t","        ")
457             ss = txt.split("\n")
458             if ss[-1] == "": del ss[-1]
459             for sentence in ss:
460                 img = self.font.render(sentence,1,self.color)
461                 w = gui.Image(img)
462                 self.item.add(w)
463                 self.item.block(-1)
464             return
465
466         txt = re.compile("^[\t\r\n]+").sub("",txt)
467         txt = re.compile("[\t\r\n]+$").sub("",txt)
468         
469         tst = re.compile("[\t\r\n]+").sub("",txt)
470         if tst == "": return
471         
472         txt = re.compile("\s+").sub(" ",txt)
473         if txt == "": return
474         
475         if txt == " ":
476             self.item.space(self.font.size(" "))
477             return
478         
479         for word in txt.split(" "):
480             word = word.replace(chr(160)," ") #&nbsp;
481             #print self.item.cls
482             w = gui.Image(self.font.render(word,1,self.color))
483             self.item.add(w)
484             self.item.space(self.font.size(" "))
485
486 if (sys.version_info[0] >= 3):
487     # These functions are for compatibility with python 3, where it seems that HTMLParser 
488     # was rewritten to be more general. There is a problem though, since python pre 3 
489     # defines these same functions with an extra argument. So we have to include them
490     # conditionally, depending on whether we're using python 2 or 3. Ugh.
491     def handle_starttag(this, tag, attrs):
492         func = getattr(this, "start_" + tag, None)
493         if (not func):
494             print("ERROR - unrecognized tag %s" % tag)
495             return
496         func(attrs)
497
498     def handle_endtag(this, tag):
499         func = getattr(this, "end_" + tag, None)
500         if (func):
501             func()
502
503     def start_img(this, attrs):
504 #        src = ""
505 #        align = ""
506 #        for (key, value) in attrs:
507 #            if (key == "src"): src = value
508 #            elif (key == "align"): align = value
509         args = this.attrs_to_map(attrs)
510         src = args.get("src", "")
511         align = args.get("align", "")
512         this.handle_image(src, "", "", align, "", "")
513
514     _html.handle_starttag = handle_starttag
515     _html.handle_endtag = handle_endtag
516     _html.start_img = start_img
517
518
519 class HTML(gui.Document):
520     """A gui HTML object
521
522     Arguments:
523         data -- html data
524         globals -- global variables (for scripting)
525         locals -- local variables (for scripting)
526         loader -- the resource loader
527     
528     You may access html elements that have an id via widget[id]
529
530     """
531     def __init__(self,data,globals=None,locals=None,loader=None,**params):
532         gui.Document.__init__(self,**params)
533         # This ensures that the whole HTML document is left-aligned within
534         # the rendered surface.
535         self.style.align = -1
536         
537         _globals,_locals = globals,locals
538         
539         if _globals == None: _globals = {}
540         if _locals == None: _locals = {}
541         self._globals = _globals
542         self._locals = _locals
543         
544         #font = gui.theme.get("label","","font")
545         if (htmllib):
546             # The constructor is slightly different
547             p = _html(None, 0)
548         else:
549             p = _html()
550         p.init(self,self.style.font,self.style.color,_globals,_locals,
551                loader=loader)
552         p.feed(data) 
553         p.close() 
554         p.mydone()
555         
556         
557     def __getitem__(self,k):
558         return self._locals[k]
559
560     # Returns a box (pygame rectangle) surrounding all widgets in this document
561     def get_bounding_box(this):
562         minx = miny = sys.maxint
563         maxx = maxy = -sys.maxint
564         for e in this.layout.widgets:
565             minx = min(minx, e.rect.left)
566             miny = min(miny, e.rect.top)
567             maxx = max(maxx, e.rect.right+1)
568             maxy = max(maxy, e.rect.bottom+1)
569         return pygame.Rect(minx, miny, maxx-minx, maxy-miny)
570
571
572 def render_ext(font, rect, text, aa, color, bgcolor=(0,0,0,0), **params):
573     """Renders some html and returns the rendered surface, plus the
574     HTML instance that produced it.
575     """
576
577     htm = HTML(text, font=font, color=color, **params)
578
579     if (rect == -1):
580         # Make the surface large enough to fit the rendered text
581         htm.resize(width=sys.maxint)
582         (width, height) = htm.get_bounding_box().size
583         # Now set the proper document width (computed from the bounding box)
584         htm.resize(width=width)
585     elif (type(rect) == int):
586         # Fix the width of the document, while the height is variable
587         width = rect
588         height = htm.resize(width=width)[1]
589     else:
590         # Otherwise the width and height of the document is fixed
591         (width, height) = rect.size
592         htm.resize(width=width)
593
594     # Now construct a surface and paint to it
595     surf = pygame.Surface((width, height)).convert_alpha()
596     surf.fill(bgcolor)
597     htm.paint(surf)
598     return (surf, htm)
599
600 def render(font, rect, text, aa, color, bgcolor=(0,0,0,0), **params):
601     """Renders some html"""
602     return render_ext(font, rect, text, aa, color, bgcolor, **params)[0]
603
604 def rendertrim(font,rect,text,aa,color,bgcolor=(0,0,0,0),**params):
605     """Render html, and make sure to trim the size."""
606     # Render the HTML
607     (surf, htm) = render_ext(font, rect, text, aa, color, bgcolor, **params)
608     return surf.subsurface(htm.get_bounding_box())
609
610     
611 def write(s,font,rect,text,aa=0,color=(0,0,0), **params):
612     """Write html to a surface."""
613     htm = HTML(text, font=font, color=color, **params)
614     htm.resize(width=rect.w)
615     s = s.subsurface(rect)
616     htm.paint(s)
617     
618

UCC git Repository :: git.ucc.asn.au