1
2
3
4
5
6
7
8
9
10
11 from __future__ import with_statement
12 import copy
13 import webbrowser
14 import re
15 import htmlentitydefs
16 import HTMLParser
17 import StringIO
18
19
20 import wx
21 import wx.html
22
23
24 import guihelper
25 import fixedwxpTag
26 import common
27
28
29
30
31
32 -class HTMLWindow(wx.html.HtmlWindow):
33 """BitPim customised HTML Window
34
35 Some extras on this:
36
37 - You can press Ctrl-Alt-S to get a source view
38 - Clicking on a link opens a window in your browser
39 - Shift-clicking on a link copies it to the clipboard
40 """
41 - def __init__(self, parent, id, relsize=0.7):
42 wx.html.HtmlWindow.__init__(self, parent, id)
43 wx.EVT_KEY_UP(self, self.OnKeyUp)
44 self.thetext=""
45 self._normal_font_size=max(wx.NORMAL_FONT.GetPointSize(), 10)
46 self.SetFontScale(relsize)
47
49 self.SetStandardFonts(int(scale*self._normal_font_size), '', '')
50
51 if len(self.thetext):
52 wx.html.HtmlWindow.SetPage(self,self.thetext)
53
54
55
56
57
58
60
61
62
63 if event.GetEvent().ShiftDown():
64 wx.TheClipboard.Open()
65 wx.TheClipboard.SetData(event.GetHref())
66 wx.TheClipboard.Close()
67 else:
68 webbrowser.open(event.GetHref())
69
70 - def SetPage(self, text):
71 self.thetext=text
72 wx.html.HtmlWindow.SetPage(self,text)
73
75 keycode=evt.GetKeyCode()
76 if keycode==ord('S') and evt.ControlDown() and evt.AltDown():
77 vs=ViewSourceFrame(None, self.thetext)
78 vs.Show(True)
79 evt.Skip()
80
86 - def __init__(self, parent, text, id=-1):
87 wx.Frame.__init__(self, parent, id, "HTML Source")
88 stc=wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE)
89 stc.AppendText(text)
90
91
92
93
94
95 -class TreeParser(HTMLParser.HTMLParser):
96 """Turns the HTML data into a tree structure
97
98 Note that the HTML needs to be well formed (ie closing tags must be present)"""
99
101
103 self.tag=""
104 self.attrs=[]
105 self.children=[]
106 self.data=""
107 self.styles=[]
108
110 HTMLParser.HTMLParser.__init__(self)
111 self.rootnode=self.TreeNode()
112 self.nodestack=[self.rootnode]
113 self.feed(data)
114 self.close()
115 assert len(self.rootnode.children)==1
116 self.rootnode=self.rootnode.children[0]
117 assert self.rootnode.tag=="html"
118
119
121
122 node=self.TreeNode()
123 node.tag=tag
124 node.attrs=attrs
125 self.nodestack[-1].children.append(node)
126 self.nodestack.append(node)
127
129
130 if tag==self.nodestack[-1].tag:
131 self.nodestack=self.nodestack[:-1]
132 else:
133 print tag,"doesn't match tos",self.nodestack[-1].tag
134 self.printtree()
135 assert False, "HTML is not well formed"
136
137
146
154
156 if node is None:
157 node=self.rootnode
158 ins=" "*indent
159 if len(node.data):
160 print ins+`node.data`
161 assert len(node.children)==0
162 assert len(node.attrs)==0
163 else:
164 print ins+"<"+node.tag+"> "+`node.attrs`
165 for c in node.children:
166 self.printtree(c, indent+1)
167
169 io=StringIO.StringIO()
170 self._flatten(self.rootnode, io)
171 return io.getvalue()
172
173 _nltags=("p", "head", "title", "h1", "h2", "h3", "h4", "h5", "table", "tr")
175 if len(node.data):
176 io.write(node.data)
177 return
178
179 if node.tag in self._nltags:
180 io.write("\n")
181
182 io.write("<%s" % (node.tag,))
183 for a,v in node.styles:
184 io.write(' %s="%s"' % (a,v))
185 for a,v in node.attrs:
186 io.write(' %s="%s"' % (a,v))
187 io.write(">")
188
189 for child in node.children:
190 self._flatten(child,io)
191
192 io.write("</%s>" % (node.tag,))
193
194 if node.tag in self._nltags:
195 io.write("\n")
196
208
210 for a,_ in node.attrs:
211 if a=="class":
212 return True
213 return False
214
216 if len(node.data):
217 return
218
219
220 if node.tag=="wxp":
221 return
222
223 if _hasclass(node):
224 newattrs=[]
225 for a,v in node.attrs:
226 if a!="class":
227 newattrs.append( (a,v) )
228 continue
229 c=styles.get(v,None)
230 if c is None:
231 continue
232 _applystyle(node, c)
233 node.attrs=newattrs
234
235 for c in node.children:
236 _applystyles(c, styles)
237
239 if len(style)==0: return
240 if len(node.data): return
241 s=style.get('', None)
242 if s is not None:
243 assert len(s)&1==0
244 for i in range(len(s)/2):
245 node.styles.append( (s[i*2], s[i*2+1]) )
246 style=style.copy()
247 del style['']
248
249 if len([k for k in style if k[0]=='+']):
250 newstyle={}
251 for k in style:
252 if k[0]!='+':
253 newstyle[k]=style[k]
254 continue
255
256 kid=TreeParser.TreeNode()
257 kid.tag=k[1:]
258 kid.children=node.children
259 node.children=[kid]
260
261 s=style[k]
262 assert len(s)&1==0
263 for i in range(len(s)/2):
264 kid.styles.append( (s[i*2], s[i*2+1]) )
265 style=newstyle
266 if len(style)==0: return
267
268 if node.tag in style:
269 s=style[node.tag]
270 assert len(s)&1==0
271 for i in range(len(s)/2):
272 node.styles.append( (s[i*2], s[i*2+1]) )
273 for i in node.children:
274 _applystyle(i, style)
275
277 """ Similar to wx.PrintData except this one includes copy ctor and
278 copy 'operator'
279 """
280
281 _attr_names=("Collate", "Colour", "Duplex", "NoCopies",
282 "Orientation", "PaperId", "PrinterName",
283 "PaperSize")
288
289 - def _copy(self, src, dest):
290 for attr in self._attr_names:
291 getattr(dest, 'Set'+attr)(getattr(src, 'Get'+attr)())
292
295
297 """Similar to wxHtmlEasyPrinting, except this is actually useful.
298
299 The following additions are present:
300
301 - The various settings are saved in a supplied config object
302 - You can set the scale for the fonts, otherwise the default
303 is way too large (you would get about 80 chars across)
304 """
305 import guiwidgets
306
307 - def __init__(self, parent=None, config=None, configstr=None):
315
318
320 with guihelper.WXDialogWrapper(wx.PrintDialog(self.parent)) as printerDialog:
321 if self.printData.Ok():
322 printerDialog.GetPrintDialogData().SetPrintData(self.printData.copy())
323 printerDialog.GetPrintDialogData().SetSetupDialog(True)
324 if printerDialog.ShowModal()==wx.ID_OK:
325 self.printData = PrintData(printerDialog.GetPrintDialogData().GetPrintData())
326 self._printdatatoconfig(self.printData)
327
328 - def PageSetup(self):
329 psdd=wx.PageSetupDialogData()
330 if self.printData.Ok():
331 psdd.SetPrintData(self.printData.copy())
332 self._configtopagesetupdata(psdd)
333 with guihelper.WXDialogWrapper(wx.PageSetupDialog(self.parent, psdd)) as pageDialog:
334 if pageDialog.ShowModal()==wx.ID_OK and \
335 pageDialog.GetPageSetupData().Ok() and \
336 pageDialog.GetPageSetupData().GetPrintData().Ok():
337 self.printData=PrintData(pageDialog.GetPageSetupData().GetPrintData())
338 self._printdatatoconfig(self.printData)
339 self._pagesetupdatatoconfig(pageDialog.GetPageSetupData())
340
341 - def PreviewText(self, htmltext, basepath="", scale=1.0):
342 printout1=self._getprintout(htmltext, basepath, scale)
343 printout2=self._getprintout(htmltext, basepath, scale)
344 if self.printData.Ok():
345 pd=self.printData.copy()
346 else:
347 pd=PrintData()
348 if not pd.Orientation:
349 pd.SetOrientation(wx.PORTRAIT)
350 preview=wx.PrintPreview(printout1, printout2, pd)
351 if not preview.Ok():
352 print "preview problem"
353 assert False, "preview problem"
354 return
355 self.frame=wx.PreviewFrame(preview, self.parent, "Print Preview")
356 self.guiwidgets.set_size("PrintPreview", self.frame, screenpct=90, aspect=0.58)
357 wx.EVT_CLOSE(self.frame, self.OnPreviewClose)
358 self.frame.Initialize()
359
360
361 self.frame.Show(True)
362
363 - def PrintText(self, htmltext, basepath="", scale=1.0):
364 pdd=wx.PrintDialogData()
365 if self.printData.Ok():
366 pdd.SetPrintData(self.printData.copy())
367 if not pdd.GetPrintData().Orientation:
368 pdd.GetPrintData().SetOrientation(wx.PORTRAIT)
369 printer=wx.Printer(pdd)
370 with guihelper.WXDialogWrapper(self._getprintout(htmltext, basepath, scale)) \
371 as printout:
372 printer.Print(self.parent, printout)
373
375 printout=wx.html.HtmlPrintout()
376 basesizes=[7,8,10,12,16,22,30]
377 printout.SetFonts("", "", [int(sz*scale) for sz in basesizes])
378 printout.SetMargins(*self.margins)
379 printout.SetHtmlText(htmltext, basepath)
380 return printout
381
383 if self.config is None or self.configstr is None or not pd.Ok():
384 print '_printdatatoconfig: bad printData'
385 return
386 c=self.config
387 cstr=self.configstr
388
389 for key,func in [ ("collate", pd.GetCollate),
390 ("colour", pd.GetColour),
391 ("duplex", pd.GetDuplex),
392 ("nocopies", pd.GetNoCopies),
393 ("orientation", pd.GetOrientation),
394 ("paperid", pd.GetPaperId),
395 ]:
396 c.WriteInt(cstr+"/"+key, func())
397 c.Write(cstr+"/printer", pd.GetPrinterName())
398 c.Flush()
399
401 if self.config is None or self.configstr is None:
402 return
403 c=self.config
404 cstr=self.configstr
405 if not pd.Ok():
406 print '_configtoprintdata: bad printData'
407 return
408 for key,func in [ ("collate", pd.SetCollate),
409 ("colour", pd.SetColour),
410 ("duplex", pd.SetDuplex),
411 ("nocopies", pd.SetNoCopies),
412 ("orientation", pd.SetOrientation),
413 ("paperid", pd.SetPaperId),
414 ]:
415 if c.HasEntry(cstr+"/"+key):
416 func(c.ReadInt(cstr+"/"+key))
417
418 if not c.HasEntry(cstr+"/paperid"):
419 pd.SetPaperId(wx.PAPER_LETTER)
420
421 pd.SetPrinterName(c.Read(cstr+"/printer", ""))
422
423 - def _configtopagesetupdata(self, psdd=None):
424 v=self.config.Read(self.configstr+"/margins", "")
425 if len(v):
426 l=[int(x) for x in v.split(',')]
427 else:
428 l=[15,15,15,15]
429 if psdd is not None:
430 psdd.SetMarginTopLeft( (l[2], l[0]) )
431 psdd.SetMarginBottomRight( (l[3], l[1]) )
432 self.margins=l
433
435 tl=psdd.GetMarginTopLeft()
436 br=psdd.GetMarginBottomRight()
437 v="%d,%d,%d,%d" % (tl[1], br[1], tl[0], br[0])
438 self.config.Write(self.configstr+"/margins", v)
439 self.margins=[tl[1],br[1],tl[0],br[0]]
440
444
445
446 _scales=[0.7, 0.9, 1, 1.1, 1.2, 1.4, 1.6]
447
448
449
450 -class Renderer:
451
452 _lastsize=None
453 _lastfont=None
454 _mdc=None
455 _bmp=None
456 _hdc=None
457
458 @classmethod
460 if Renderer._bmp is None:
461 Renderer._bmp=wx.EmptyBitmap(1,1)
462 Renderer._mdc=wx.MemoryDC()
463 Renderer._mdc.SelectObject(Renderer._bmp)
464 Renderer._hdc=wx.html.HtmlDCRenderer()
465 Renderer._hdc.SetDC(Renderer._mdc, 1)
466 Renderer._hdc.SetSize(99999,99999)
467 Renderer._mdc.ResetBoundingBox()
468 if Renderer._lastsize!=size or Renderer._lastfont!=font:
469 Renderer._hdc.SetFonts(font, "", [int(x*size) for x in _scales])
470 Renderer._hdc.SetHtmlText(html, basepath)
471 Renderer._hdc.Render(0, 0, (), 0, False)
472 return (Renderer._mdc.MaxX(), Renderer._mdc.MaxY())
473
474 -def getbestsize(dc, html, basepath="", font="", size=10):
476
477 -def drawhtml(dc, rect, html, basepath="", font="", size=10):
478 """Draw html into supplied dc and rect"""
479 if html is None or html=="":
480 return
481 origscale=dc.GetUserScale()
482 hdc=wx.html.HtmlDCRenderer()
483 hdc.SetFonts(font, "", [int(x*size) for x in _scales])
484 hdc.SetDC(dc, 1)
485 hdc.SetSize(rect.width, rect.height)
486 hdc.SetHtmlText(html, basepath)
487 hdc.Render(rect.x, rect.y, (), 0, False)
488 dc.SetUserScale(*origscale)
489
490
491
492
493
494
495 if __name__=='__main__':
496 src="""
497 <HTML>
498 <head><title>A title</title></head>
499
500 <body>
501 <h1 cLaSs="gaudy">Heading 1</h1>
502
503 <p>This is my sentence <span class=hilite>with some hilite</span></p>
504
505 <p><table><tr><th>one</th><th>two</th></tr>
506 <tr><td class="orange">orange</td><td>Normal</td></tr>
507 <tr class="orange"><td>whole row is</td><td>orange</td></tr>
508 </table></p>
509 </body>
510 </html>
511 """
512 styles={
513 'gaudy':
514 {
515 '+font': ('color', '#123456'),
516 '': ('bgcolor', 'grey'),
517 },
518
519 'orange':
520 {
521 '+font': ('color', '#001122'),
522 'tr': ('bgcolor', '#cc1122'),
523 'td': ('bgcolor', '#cc1122'),
524 },
525
526 'hilite':
527 {
528 '+b': (),
529 '+font': ('color', '#564bef'),
530 }
531
532 }
533
534 tp=TreeParser(src)
535 _applystyles(tp.rootnode, styles)
536 tp.printtree()
537 print tp.flatten()
538
539 app=wx.PySimpleApp()
540
541 f=wx.Frame(None, -1, "HTML Test")
542 h=HTMLWindow(f, -1)
543 f.Show(True)
544 h.SetPage(tp.flatten())
545 app.MainLoop()
546 sys.exit(0)
547
548
549 if __name__=='__main__':
550 import sys
553 global scale
554 scale=szlist[evt.GetPosition()]
555
556 f=common.opentextfile(sys.argv[1])
557 html=f.read()
558 f.close()
559
560 app=wx.PySimpleApp()
561
562 f=wx.Frame(None, -1, "Print Test")
563 butsetup=wx.Button(f, wx.NewId(), "Setup")
564 butpreview=wx.Button(f, wx.NewId(), "Preview")
565 butprint=wx.Button(f, wx.NewId(), "Print")
566
567 slider=wx.Slider(f, wx.NewId(), 5, 0, 10, style=wx.SL_HORIZONTAL)
568
569 szlist=[0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4]
570
571 scale=1.0
572
573 bs=wx.BoxSizer(wx.HORIZONTAL)
574 bs.Add(slider, 1, wx.EXPAND|wx.ALL, 5)
575 for i in (butsetup,butpreview, butprint):
576 bs.Add(i, 0, wx.EXPAND|wx.ALL, 5)
577 f.SetSizer(bs)
578 bs.Fit(f)
579
580 hep=HtmlEasyPrinting(f, None, None)
581
582 wx.EVT_BUTTON(f, butsetup.GetId(), lambda _: hep.PrinterSetup())
583 wx.EVT_BUTTON(f, butpreview.GetId(), lambda _: hep.PreviewText(html, scale=scale))
584 wx.EVT_BUTTON(f, butprint.GetId(), lambda _: hep.PrintText(html, scale=scale))
585 wx.EVT_COMMAND_SCROLL(f, slider.GetId(), OnSlider)
586
587 f.Show(True)
588 app.MainLoop()
589