fixed leaking file descriptors
[swftools.git] / wx / pdf2swf.py
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-15 -*-
3 #
4 # pdf2swf.py
5 # graphical user interface for pdf2swf
6 #
7 # Part of the swftools package.
8
9 # Copyright (c) 2008,2009 Matthias Kramm <kramm@quiss.org> 
10 #
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with this program; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
24
25
26 import sys
27 import wx
28 import os
29 sys.path+=["../lib/python"]
30 import gfx
31 import images
32 import stat
33
34 basedir = os.getcwd()
35
36 gfx.verbose(3)
37
38 #try:
39 #    gfx.setparameter("wxwindowparams", "1")
40 #except:
41 #    gfx.setoption("wxwindowparams", "1")
42
43 class StaticData:
44     def __init__(self):
45         self.simpleviewer_bitmap = wx.BitmapFromImage(wx.ImageFromData(images.simpleviewer_width,images.simpleviewer_height,images.simpleviewer_data))
46         self.raw_bitmap = wx.BitmapFromImage(wx.ImageFromData(images.raw_width,images.raw_height,images.raw_data))
47         self.motionpaper_bitmap = wx.BitmapFromImage(wx.ImageFromData(images.motionpaper_width,images.motionpaper_height,images.motionpaper_data))
48         self.rfxview_bitmap = wx.BitmapFromImage(wx.ImageFromData(images.rfxview_width,images.rfxview_height,images.rfxview_data))
49 staticdata = None
50
51 HTMLTEMPLATE = """<html>
52 <body style="padding: 0px; margin: 0px">
53 <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
54         width="%(width)s"
55         height="%(height)s"
56         codebase="http://active.macromedia.com/flash5/cabs/swflash.cab#version=%(version)d,0,0,0">
57         <param name="MOVIE" value="%(swffilename)s">
58         <param name="PLAY" value="true">
59         <param name="LOOP" value="true">
60         <param name="QUALITY" value="high">
61         <param name="FLASHVARS" value="%(flashvars)s">
62           <embed src="%(swffilename)s" width="%(width)s" height="%(height)s"
63                  play="true" ALIGN="" loop="true" quality="high"
64                  type="application/x-shockwave-flash"
65                  flashvars="%(flashvars)s"
66                  pluginspage="http://www.macromedia.com/go/getflashplayer">
67           </embed>
68 </object>
69 </body>
70 </html>
71 """
72
73 def error(msg):
74     dlg = wx.MessageDialog(None, msg, "Error", style=wx.OK, pos=wx.DefaultPosition)
75     dlg.ShowModal()
76     dlg.Destroy()
77
78 def savefilestatus(msg):
79     dlg = wx.MessageDialog(None, msg, "Save file status", style=wx.OK, pos=wx.DefaultPosition)
80     dlg.ShowModal()
81     dlg.Destroy()
82
83 def swfcombine(params):
84     exe = "swfcombine"
85     if os.path.sep == '/':
86         locations = [os.path.join(basedir, "swfcombine"), 
87                      "/usr/local/bin/swfcombine",
88                      "/usr/bin/swfcombine"
89                     ]
90     else:
91         locations = [os.path.join(basedir, "swfcombine.exe"), 
92                      "c:\\swftools\\swfcombine.exe"]
93         params = ['"'+p+'"' for p in params]
94
95     for e in locations:
96         if os.path.isfile(e):
97             exe = e
98             break
99
100     if hasattr(os,"spawnv"):
101         print "spawnv",exe,params
102         ret = -1
103         try:
104             ret = os.spawnv(os.P_WAIT, exe, ["swfcombine"]+params)
105         except:
106             ret = -1
107         if not ret:
108             return
109
110     cmd = '"' + exe + '"' + " " + (" ".join(params))
111     print "system",cmd
112     ret = os.system(cmd)
113     if ret&0xff00:
114         error("Couldn't execute swfcombine.exe- error code "+str(ret))
115
116 ICON_SIZE = 64
117
118 EVENT_PAGE_CHANGE = 1
119 EVENT_FILE_CHANGE = 2
120 EVENT_STATUS_TEXT = 4
121
122 class ProgressFrame(wx.Dialog):
123     def __init__(self, parent, message=""):
124         wx.Dialog.__init__(self, parent, -1, "Progress", size=(350, 150))
125         panel = wx.Panel(self, -1)
126         self.count = 0
127         
128         self.msg = wx.StaticText(panel, -1, message, (20,25))
129         self.gauge = wx.Gauge(panel, -1, 100, (20, 50), (250, 25))
130
131         self.gauge.SetBezelFace(3)
132         self.gauge.SetShadowWidth(3)
133
134         self.Bind(wx.EVT_WINDOW_DESTROY, self.close, id=wx.ID_CLOSE)
135
136     def setProgress(self, num):
137         self.gauge.SetValue(int(num))
138
139     def close(self, event):
140         print "close"
141
142
143 def swapextension(filename,newext):
144     basename,ext = os.path.splitext(filename)
145     return basename + "." + newext
146
147 def has_different_size_pages(doc):
148     width,height = 0,0
149     for i in range(1,doc.pages+1):
150         page = doc.getPage(i)
151         if i==1:
152             width,height = page.width,page.height
153         else:
154             if abs(width-page.width)>2 or \
155                abs(height-page.height)>2:
156                    return 1
157     return 0
158
159
160 options = []
161 gfx_options = {}
162
163 class Option:
164     def __init__(self, parameter, text, options, default, mapping=None):
165         self.parameter = parameter
166         self.text = text
167         self.options = options
168         self.default = default
169         self.mapping = mapping
170         self.control = None
171         self.enabled = 1
172         self.register()
173
174     def generateControl(self, panel):
175         if type(self.options) == type((0,)):
176             control = wx.Choice(panel, -1, choices=self.options)
177             control.SetSelection(self.default)
178         elif self.options == "slider":
179             control = wx.Slider(panel, -1, self.default, 0, 100, size=(100, -1), style=wx.SL_HORIZONTAL|wx.SL_LABELS|wx.SL_TOP)
180         elif self.options == "spinner":
181             control = wx.SpinCtrl(panel, -1, str(self.default))
182         else:
183             control = wx.Choice(panel, -1, choices=["broken"])
184             control.SetSelection(0)
185
186         self.control = control
187         return self.control
188
189     def getSettings(self):
190         value = ""
191         if type(self.options) == type((0,)):
192             value = self.options[self.control.GetCurrentSelection()]
193             if self.mapping and value in self.mapping:
194                 value = str(self.mapping[value])
195             if value == "yes": 
196                 value = "1"
197             elif value == "no":
198                 value = "0"
199             return {self.parameter:value}
200         elif self.options == "slider" or self.options == "spinner":
201             value = str(self.control.GetValue())
202             return {self.parameter:value}
203
204     def register(self):
205         global options
206         options += [self]
207
208 class Option2(Option):
209
210     def __init__(self, parameter, text, options, default, mapping=None):
211         Option.__init__(self, parameter, text, options, default, mapping)
212         self.enabled = 0
213
214     def generateControl(self, panel):
215         p = wx.Panel(panel, -1)
216         #p.SetOwnBackgroundColour('#ff0000')
217         h = wx.BoxSizer(wx.HORIZONTAL)
218         control = wx.Choice(p, -1, choices=self.options)
219         control.SetSelection(self.default)
220         text = wx.StaticText(p, -1, self.text)
221         h.Add(text,1,wx.EXPAND|wx.ALIGN_LEFT|wx.TOP, 5)
222         h.Add(control,1,wx.EXPAND|wx.ALIGN_RIGHT|wx.ALIGN_TOP)
223         self.control = control
224         if self.enabled:
225             control.Enable()
226         else:
227             control.Disable()
228         p.SetSizer(h)
229         p.Fit()
230         return p 
231     
232     def Disable(self):
233         self.enabled=0
234         if self.control:
235             self.control.Disable()
236
237     def Enable(self):
238         self.enabled=1
239         if self.control:
240             self.control.Enable()
241     
242     def getSettings(self):
243         if not self.enabled:
244             return {}
245         return Option.getSettings(self)
246
247 class ChooseAndText(Option):
248     def __init__(self, parameter, text, options, default, editselection, textvalue=""):
249         Option.__init__(self, parameter, text, options, default)
250         self.editselection = editselection
251         self.selection = default
252         self.textvalue = textvalue
253         self.enabled = 0
254         self.choice = None
255
256     def generateControl(self, panel):
257         p = wx.Panel(panel, -1)
258         h = wx.BoxSizer(wx.HORIZONTAL)
259         control = wx.Choice(p, -1, choices=self.options)
260         p.Bind(wx.EVT_CHOICE, self.OnChoice, control)
261         control.SetSelection(self.default)
262         text = wx.StaticText(p, -1, self.text)
263         if self.selection == self.editselection:
264             edittext = wx.TextCtrl(p, -1, self.textvalue)
265             self.textvalue = ""
266         else:
267             edittext = wx.TextCtrl(p, -1, "")
268             edittext.Disable()
269         p.Bind(wx.EVT_TEXT, self.OnText, edittext)
270         h.Add(text,1,wx.EXPAND|wx.ALIGN_LEFT|wx.TOP, 5)
271         h.Add(control,1,wx.EXPAND|wx.ALIGN_RIGHT)
272         h.Add(edittext,1,wx.EXPAND|wx.ALIGN_RIGHT)
273         self.choice = control
274         self.edittext = edittext
275         if self.enabled:
276             control.Enable()
277         else:
278             control.Disable()
279         p.SetSizer(h)
280         p.Fit()
281         return p 
282
283     def OnText(self, event):
284         text = self.edittext.GetValue()
285         text2 = "".join(c for c in text if c.isdigit())
286         if text2!=text:
287             self.edittext.SetValue(text2)
288
289     def OnChoice(self, event):
290         self.selection = self.choice.GetCurrentSelection()
291         if self.selection != self.editselection:
292             if not self.textvalue and self.edittext.GetValue():
293                 self.textvalue = self.edittext.GetValue()
294             self.edittext.SetValue("")
295             self.edittext.Disable()
296         else:
297             if self.textvalue and not self.edittext.GetValue():
298                 self.edittext.SetValue(self.textvalue)
299                 self.textvalue = ""
300             self.edittext.Enable()
301     
302     def Disable(self):
303         self.enabled=0
304         if not self.choice:
305             return
306         self.choice.Disable()
307         self.edittext.Disable()
308
309     def Enable(self):
310         self.enabled=1
311         if not self.choice:
312             return
313         self.choice.Enable()
314         if self.choice.GetCurrentSelection() == self.editselection:
315             if self.textvalue and not self.edittext.GetValue():
316                 self.edittext.SetValue(self.textvalue)
317                 self.textvalue = ""
318             self.edittext.Enable()
319         else:
320             self.edittext.Disable()
321     
322     def getSettings(self):
323         if not self.enabled:
324             return {}
325         if self.choice.GetCurrentSelection() != self.editselection:
326             value = self.options[self.choice.GetCurrentSelection()]
327         else:
328             value = self.edittext.GetValue().strip()
329         return {self.parameter:value}
330
331 class TextOption:
332     def __init__(self, parameter, label, default=""):
333         self.parameter = parameter
334         self.label = label
335         self.default = default
336         self.register()
337
338     def generateControl(self, panel):
339         v = wx.BoxSizer(wx.VERTICAL)
340         self.control = wx.TextCtrl(panel, -1, self.default, size=(250, -1))
341         self.control.Fit()
342         return self.control
343
344     def getSettings(self):
345         settings = {}
346         for items in self.control.GetValue().split(" "):
347             if "=" in items:
348                 l = items.split("=")
349                 if len(l) == 2:
350                     settings[l[0]] = l[1]
351         return settings
352
353     def register(self):
354         global options
355         options += [self]
356
357 class RadioOption(Option):
358     def __init__(self, text, options):
359         self.text = text
360         self.options = options
361         self.selected = "==nothing=="
362         self.radios = []
363         self.register()
364         
365     def generateControl(self, panel):
366         control = wx.Panel(panel, -1)
367         vsplit = wx.BoxSizer(wx.VERTICAL)
368         for i in range(len(self.options)/2):
369             text = self.options[i*2]
370             if i == 0:
371                 c = wx.RadioButton(control, -1, text, style=wx.RB_GROUP)
372             else:
373                 c = wx.RadioButton(control, -1, text)
374             control.Bind(wx.EVT_RADIOBUTTON, self.OnRadio, c)
375             self.radios += [c]
376             vsplit.Add(c)
377         control.SetSizer(vsplit)
378         control.Fit()
379         self.control = control
380         return control
381
382     def OnRadio(self, event):
383         self.selected = event.GetEventObject().GetLabel()
384
385     def getSettings(self):
386         for i in range(len(self.options)/2):
387             if self.options[i*2] == self.selected:
388                 return self.options[i*2+1]
389         return self.options[1]
390
391 class BitmapWindow(wx.Window):
392     def __init__(self, parent, image):
393         wx.Window.__init__(self, parent, -1)
394         self.image = image
395         self.SetMinSize((image.GetWidth()+2, image.GetHeight()+2))
396         self.SetMaxSize((image.GetWidth()+2, image.GetHeight()+2))
397         self.SetSize((image.GetWidth()+2, image.GetHeight()+2))
398         self.Bind(wx.EVT_PAINT, self.OnPaint)
399         self.Update()
400     def OnPaint(self, event):
401         dc = wx.PaintDC(self)
402         self.Draw(dc)
403     def Draw(self,dc=None):
404         if not dc:
405             dc = wx.ClientDC(self)
406         dc.DrawRectangleRect((0, 0, self.image.GetWidth()+2, self.image.GetHeight()+2))
407         dc.DrawBitmap(self.image, 1, 1, False)
408
409 class ImageRadioOption(Option):
410     def __init__(self, text, options):
411         self.text = text
412         self.options = options
413         self.selected = "==nothing=="
414         self.radios = []
415         self.register()
416         self.ids = []
417         
418     def generateControl(self, panel):
419         control = wx.Panel(panel, -1)
420         vsplit = wx.BoxSizer(wx.VERTICAL)
421         first = 1
422         for image,text,params,selected,extraoptions in self.options:
423             hsplit = wx.BoxSizer(wx.HORIZONTAL)
424
425             v = wx.BoxSizer(wx.VERTICAL)
426
427             name,text = text.split("- ")
428
429             c = wx.CheckBox(control, -1, name)
430             control.Bind(wx.EVT_CHECKBOX, self.OnRadio, c)
431
432             # radio buttons crash windows when clicked on- even without event bindings.
433             # This is caused by the subpanel which is created for extra options
434             # (I tried this with a empty Panel(), and even that crashed)
435             #if first:
436             #    c = wx.RadioButton(control, -1, name, style=wx.RB_GROUP)
437             #else:
438             #    c = wx.RadioButton(control, -1, name)
439             #control.Bind(wx.EVT_RADIOBUTTON, self.OnRadio, c)
440
441             self.ids += [c.GetId()]
442
443             first = 0
444
445             if "disable" in text:
446                 c.Enable(False)
447             if selected:
448                 self.selected = c.GetId()
449                 c.SetValue(True)
450             else:
451                 c.SetValue(False)
452             self.radios += [c]
453
454             bitmap = BitmapWindow(control, image)
455             t = wx.StaticText(control, -1, text, size=(400,50))
456             
457             v.Add(c, 0, wx.EXPAND)
458             v.Add(t, 0, wx.EXPAND|wx.LEFT, 20)
459            
460             for o in extraoptions:
461                 cx = o.generateControl(control)
462                 if selected:
463                     o.Enable()
464                 else:
465                     o.Disable()
466                 v.Add(cx, 0, wx.EXPAND|wx.LEFT, 20)
467             
468             v.SetMinSize((330,170))
469             
470             hsplit.Add(bitmap, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.ALIGN_TOP, 5)
471             hsplit.Add(v, 0, wx.EXPAND)
472             vsplit.Add(hsplit, 0, wx.EXPAND)
473
474         control.SetSizer(vsplit)
475         control.Fit()
476         self.control = control
477         return vsplit
478
479     def OnRadio(self, event):
480         self.selected = event.GetEventObject().GetId()
481         for c in self.radios:
482             if c.GetId() == self.selected:
483                 c.SetValue(1)
484             else:
485                 c.SetValue(0)
486         i = 0
487         for image,text,params,selected,extraoptions in self.options:
488             if self.ids[i] == self.selected:
489                 for xo in extraoptions:
490                     xo.Enable()
491                 pass
492             else:
493                 for xo in extraoptions:
494                     xo.Disable()
495                 pass
496             i = i + 1
497         event.ResumePropagation(0)
498
499     def getSettings(self):
500         i = 0
501         for image,text,params,s,extraoptions in self.options:
502             id = self.ids[i]
503             i = i + 1
504             if id == self.selected:
505                 return params
506         return {}
507
508
509 class OptionFrame(wx.Dialog):
510
511     def __init__(self, parent):
512         wx.Dialog.__init__(self, parent, -1, "Options")
513
514         #self.nb = wx.Notebook(self, -1)#, wx.Point(0,0), wx.Size(0,0), wxNB_FIXEDWIDTH)
515         self.nb = wx.Notebook(self, -1)
516
517         self.needreload = 0
518         
519         options0 = [RadioOption('Rendering mode', 
520                         ["Convert polygons to polygons and fonts to fonts", {},
521                          "Convert fonts to fonts, everything else to bitmaps", {"poly2bitmap":"1"},
522                          "Convert everthing to bitmaps", {"poly2bitmap":"1", "bitmapfonts":"1"}
523                         ])]
524
525         mp_options = []
526         sv_options = [Option2('flashversion', 'Flash version:', ('4','5','6','7','8'), 2),
527                       Option2('transparent', 'Make SWF file transparent:', ('no','yes'), 0),
528                      ]
529
530         raw_options = [Option2('flashversion', 'Flash version:', ('4','5','6','7','8','9'), 2),
531                        Option2('insertstop', 'Insert stop after each frame:', ('no','yes'), 0),
532                        Option2('transparent', 'Make SWF file transparent:', ('no','yes'), 0),
533                       ]
534         rfxview_options = [ChooseAndText('rfxwidth', 'Width:', ('same as PDF','fullscreen','custom'),1,2,"600"),
535                            ChooseAndText('rfxheight', 'Height:', ('same as PDF','fullscreen','custom'),1,2,"800"),
536                            Option2('rfxzoomtype', 'Initial zoom level:', ('Original resolution','Show all','Maximum width/height'),2),
537                           ]
538
539         options4 = [ImageRadioOption('Select Paging GUI', 
540                         [(staticdata.raw_bitmap, "No Viewer- The SWF will be in \"raw\" format, with each page a seperate frame. Use this if you want to add a viewer yourself afterwards.", {}, 0, raw_options),
541                          (staticdata.simpleviewer_bitmap, "SimpleViewer- A tiny viewer, which attaches directly to the SWF, and provides small previous/next buttons in the upper left corner", {"simpleviewer":"1", "insertstop":"1"}, 0, sv_options),
542                          (staticdata.rfxview_bitmap, "rfxView- A more sophisticated viewer with zooming and scrolling.", {"rfxview":"1", "flashversion":"8"}, 1, rfxview_options),
543                          #(staticdata.motionpaper_bitmap, "MotionPaper- A highly sophisticated viewer with page flipping. (disabled in this evaluation version)", {}, 0, mp_options),
544                          #(staticdata.motionpaper_bitmap, "Your advertisement here- Are you are company who developed a viewer for pdf2swf, or who offers commercial PDF hosting service? Place your advertisement or demo viewer here, or allow pdf2swf to upload SWFs directly to your site! contact sales@swftools.org for details.", {}, 0, mp_options),
545                         ])]
546
547         options1 = [Option('zoom', 'Resolution (in dpi):', "spinner", 72),
548                     Option('fontquality', 'Font quality:', "slider", 20),
549                     Option('storeallcharacters', 'Insert full fonts in SWF file:', ('no','yes'), 0),
550                     Option('splinequality', 'Polygon quality:', "slider", 100),
551                     Option('jpegquality', 'JPEG quality:', "slider", 75),
552                     Option('jpegsubpixels', 'JPEG image resolution:', ('same as in PDF', '1x', '2x', '4x'), 0, {"same as in PDF": 0, "1x": 1, "2x": 2, "3x": 3}),
553                     Option('ppmsubpixels', 'non-JPEG image resolution:', ('same as in PDF', '1x', '2x', '4x'), 0, {"same as in PDF": 0, "1x": 1, "2x": 2, "3x": 3}),
554                    ]
555         
556         
557         options3 = [TextOption('_additional_', 'Additional options')]
558
559         panel1 = [('Rendering options', options0,''),
560                   ('Quality',options1,'v')]
561         panel3 = [('Select paging GUI', options4,'')]
562         panel4 = [('Additional options', options3,'')]
563
564         panels = [('Quality', panel1),
565                   ('Viewer', panel3),
566                   ('Advanced', panel4)]
567
568         for name,poptions in panels:
569             panel = wx.Panel(self.nb, -1)
570             self.nb.AddPage(panel, name)
571         
572             vsplit = wx.BoxSizer(wx.VERTICAL)
573       
574             for name,options,align in poptions:
575                 optiongroup = wx.StaticBox(panel, -1, name)
576                 optiongroupsizer= wx.StaticBoxSizer(optiongroup, wx.VERTICAL)
577                 optiongroup.SetSizer(optiongroupsizer)
578
579                 if align == 'v':
580                     grid = wx.GridSizer(rows=len(options), cols=2, hgap=3, vgap=3)
581                     optiongroupsizer.Add(grid, 1, wx.EXPAND, 0)
582                 else:
583                     grid = wx.GridSizer(rows=len(options), cols=1, hgap=3, vgap=3)
584                     optiongroupsizer.Add(grid, 1, wx.EXPAND, 0)
585
586                 for option in options:
587                     if align=='v':
588                         t = wx.StaticText(panel, -1, option.text)
589                         grid.Add(t, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
590                     optionbox = option.generateControl(panel)
591                     grid.Add(optionbox, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
592
593                 vsplit.Add(optiongroupsizer, 0, wx.EXPAND, 0)
594
595             #hs = wx.BoxSizer(wx.HORIZONTAL)
596             #hs.Add(gobutton, 0, wx.ALIGN_CENTER, 0)
597             gobutton = wx.Button(panel, -1, "Apply")
598             self.Bind(wx.EVT_BUTTON, self.Apply, gobutton)
599
600             vsplit.Add(gobutton, 0, wx.ALIGN_CENTER|wx.ALL, 0)
601             
602             panel.SetSizer(vsplit)
603             panel.Fit()
604
605         self.nb.Fit()
606
607         self.Fit()
608         
609
610     def updateOptions(self):
611         global options,gfx_options
612         a = []
613
614         # FIXME: we clear *our* options- but gfx will still have
615         #        stored the old ones. Critical for options in the "imageradio" section.
616         gfx_options.clear()
617         i = 0
618         print "----- options ------"
619         for option in options:
620             for k,v in option.getSettings().items():
621                 gfx_options[k] = v
622                 gfx.setparameter(k,v)
623                 print k,v
624             i = i + 1
625
626         # TODO: filter out "global" options, and do this only if
627         # pdf layer is affected
628     
629     def Apply(self, event):
630         self.updateOptions()
631         self.Hide()
632         self.needreload = 1
633
634
635 class State:
636     def __init__(self):
637         self.pdf = None
638         self.page = None
639         self.pagenr = 1
640         self.pagebitmap = None
641         self.bitmap_width = 0
642         self.bitmap_height = 0
643         self.bitmap_page = 0
644         self.filename = None
645         self.status_text = None
646         self.lastsavefile = "output.swf"
647         self.lasthtmlfile = "index.html"
648
649         self.listeners = []
650
651     def onEvent(self,event_type, function):
652         self.listeners += [(event_type,function)]
653     def loadPDF(self,filename):
654         self.filename = filename
655         self.lastsavefile = swapextension(filename,"swf")
656         self.lasthtmlfile = swapextension(filename,"html")
657
658         self.pdf = gfx.open("pdf",filename)
659         if(has_different_size_pages(self.pdf)):
660             # just let the user know- for now, we can't handle this properly
661             dlg = wx.MessageDialog(app.frame, """In this PDF, width or height are not the same for each page. This might cause problems if you export pages of different dimensions into the same SWF file.""", "Notice", style=wx.OK, pos=wx.DefaultPosition)
662             dlg.ShowModal()
663             dlg.Destroy()
664
665         self.changePage(1)
666
667         for type,f in self.listeners:
668             if type&EVENT_PAGE_CHANGE or type&EVENT_FILE_CHANGE:
669                 f()
670         self.setStatus("File loaded successfully.")
671
672     def saveSWF(self, filename, progress, pages=None, html=0):
673         if html:
674             basename,ext = os.path.splitext(filename)
675             if not ext:
676                 html = basename + ".html"
677                 filename = basename + ".swf"
678             elif ext.lower() != ".swf":
679                 html = filename
680                 filename = basename + ".swf"
681             else:
682                 html = basename + ".html"
683                 filename = filename
684
685         steps = 100.0 / (self.pdf.pages*2 + 3)
686         pos = [0]
687         
688         self.lastsavefile = filename
689         if html:
690             self.lasthtmlfile = html
691
692         swf = gfx.SWF()
693         for k,v in gfx_options.items():
694             swf.setparameter(k,v)
695         if pages is None:
696             pages = range(1,self.pdf.pages+1)
697         pdfwidth,pdfheight=0,0
698         for pagenr in pages:
699             page = self.pdf.getPage(pagenr)
700             pdfwidth = page.width
701             pdfheight = page.height
702             swf.startpage(page.width, page.height)
703             page.render(swf)
704             swf.endpage()
705         swf.save(filename)
706         if not os.path.isfile(filename):
707             error("Couldn't create file "+filename)
708
709         if gfx_options.get("rfxview",None):
710             rfxview = os.path.join(basedir, "rfxview.swf")
711             if not os.path.isfile(rfxview):
712                 error("File rfxview.swf not found in working directory")
713             else:
714                 size1 = os.stat(filename)[stat.ST_SIZE]
715                 swfcombine([rfxview,"viewport="+filename,"-o",filename])
716                 size2 = os.stat(filename)[stat.ST_SIZE]
717                 if size1 == size2:
718                     error("Couldn't add viewer to file "+filename)
719         
720         if html:
721             version = int(gfx_options.get("flashversion", "8"))
722             swf = gfx.open("swf", filename)
723             page1 = swf.getPage(1)
724
725             width,height = str(page1.width),str(page1.height)
726
727
728             w = gfx_options.get("rfxwidth","")
729             if w == "fullscreen":    width = "100%"
730             elif w == "same as PDF": width = pdfwidth+40
731             elif w.isdigit():        width = w
732             else:                    width = pdfwidth
733             
734             h = gfx_options.get("rfxheight","")
735             if h == "fullscreen":    height = "100%"
736             elif h == "same as PDF": height = pdfheight+70
737             elif h.isdigit():        height = h
738             else:                    height = pdfwidth
739
740             flashvars = ""
741             zoomtype = gfx_options.get("rfxzoomtype","")
742             if zoomtype=="Original resolution":
743                 flashvars = "zoomtype=1"
744             elif zoomtype=="Show all":
745                 flashvars = "zoomtype=2"
746             elif zoomtype=="Maximum width/height":
747                 flashvars = "zoomtype=3"
748
749             swffilename = os.path.basename(filename)
750             fi = open(html, "wb")
751             fi.write(HTMLTEMPLATE % locals())
752             fi.close()
753
754
755     def changePage(self,page):
756         self.pagenr = page
757         self.page = self.pdf.getPage(self.pagenr)
758         for type,f in self.listeners:
759             if type&EVENT_PAGE_CHANGE:
760                 f()
761
762     def getPageIcon(self,pagenr):
763         page = self.pdf.getPage(pagenr)
764         return wx.BitmapFromImage(wx.ImageFromData(ICON_SIZE,ICON_SIZE,page.asImage(ICON_SIZE,ICON_SIZE)))
765         #return wx.BitmapFromImage(wx.ImageFromData(8,8,"0"*(64*3)))
766
767     def getPageImage(self, width, height):
768         if self.bitmap_width == width and self.bitmap_height == height and self.bitmap_page == self.pagenr:
769             return self.pagebitmap
770         else:
771             self.bitmap_width = width
772             self.bitmap_height = height
773             self.bitmap_page = self.pagenr
774             self.pagebitmap = wx.BitmapFromImage(wx.ImageFromData(width,height,self.page.asImage(width,height)))
775             #self.pagebitmap = wx.BitmapFromImage(wx.ImageFromData(8,8,"0"*(64*3)))
776             return self.pagebitmap
777
778     def setStatus(self,text):
779         self.status_text = text
780         for type,f in self.listeners:
781             if type&EVENT_STATUS_TEXT:
782                 f()
783
784 state = State()
785
786 class PageListWidget(wx.ListCtrl):
787     def __init__(self,parent):
788         wx.ListCtrl.__init__(self,parent,style=wx.LC_ICON|wx.LC_AUTOARRANGE)
789         #self.SetMinSize((ICON_SIZE+8,-1))
790         #self.SetMaxSize((ICON_SIZE+8,-1))
791         #self.SetSize((ICON_SIZE+8,-1))
792         self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.SelectItem)
793         state.onEvent(EVENT_FILE_CHANGE, self.reload)
794         state.onEvent(EVENT_PAGE_CHANGE, self.switchPage)
795         self.reload()
796         self.dontcare = 0
797         #self.Bind(wx.EVT_IDLE, self.OnIdle)
798         #print dir(self)
799
800     def processFiles(self):
801         if self.filepos >= 0 and self.filepos < state.pdf.pages:
802             icon = state.getPageIcon(self.filepos+1)
803             self.imglist.Add(icon)
804             self.InsertImageStringItem(self.filepos, str(self.filepos+1), self.filepos)
805             self.filepos = self.filepos + 1
806         self.Update()
807
808     def OnIdle(self,event):
809         self.processFiles()
810         event.ResumePropagation(0)
811
812     def reload(self):
813         self.filepos = -1
814         self.DeleteAllItems()
815         self.imglist = wx.ImageList(ICON_SIZE,ICON_SIZE,mask=False)
816         self.AssignImageList(self.imglist,wx.IMAGE_LIST_NORMAL)
817         self.filepos = 0
818         while state.pdf and self.filepos < state.pdf.pages:
819             self.processFiles()
820
821     def switchPage(self):
822         if self.dontcare:
823             self.dontcare = 0
824             return
825         for i in range(0,self.GetItemCount()):
826             self.Select(i, False)
827         self.Select(state.pagenr-1, True)
828         self.Focus(state.pagenr-1)
829         self.Update()
830
831     def SelectItem(self,event):
832         self.dontcare = 1 #ignore next change event
833         state.changePage(event.GetIndex()+1)
834
835
836 helptxt = """
837 This is the SWF preview window.
838 Here, you will see how the SWF file generated from
839 the PDF file will look like. Changing parameters in
840 the configuration which affect the appeareance of
841 the final SWF will affect this preview, too, so you
842 can always evaluate the final output beforehand.
843 """
844
845         
846 class OnePageWidget(wx.Window):
847     def __init__(self,parent):
848         wx.Window.__init__(self, parent)
849         self.SetSize((160,100))
850         self.SetMinSize((160,100))
851         self.Fit()
852         self.Bind(wx.EVT_PAINT, self.OnPaint)
853         self.Bind(wx.EVT_SIZE, self.OnSize)
854         self.Bind(wx.EVT_KEY_DOWN, self.key_down)
855         state.onEvent(EVENT_PAGE_CHANGE, self.OnPageChange)
856     
857     def key_down(self, event):
858         if state.pdf:
859             if event.GetKeyCode() == 312 and state.pagenr>1:
860                 state.changePage(state.pagenr-1)
861             elif event.GetKeyCode() == 313 and state.pagenr<state.pdf.pages:
862                 state.changePage(state.pagenr+1)
863
864     def OnPageChange(self):
865         self.Refresh()
866
867     def OnSize(self, event):
868         self.Refresh()
869
870     def Draw(self,dc=None):
871         global bitmap
872         if not dc:
873             dc = wx.ClientDC(self)
874         posx = 0 
875         posy = 0
876         window_width,window_height = self.GetSize()
877         dc.Clear()
878
879         if not state.pdf or not state.page:
880             return
881
882         if state.page.width * window_height > state.page.height * window_width:
883             width = window_width
884             height = window_width * state.page.height / state.page.width
885             posy = (window_height - height) / 2
886         else:
887             width = window_height * state.page.width / state.page.height
888             height = window_height
889             posx = (window_width - width) / 2
890
891         dc.DrawBitmap(state.getPageImage(width,height), posx,posy, False)
892         #state.getPageImage(
893
894     def OnPaint(self, event):
895         dc = wx.PaintDC(self)
896         self.Draw(dc)
897
898 class Pdf2swfFrame(wx.Frame):
899     #def __init__(self):
900         #wx.Window.__init__(self, None, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize)
901     def __init__(self,application):
902         wx.Frame.__init__(self, None, -1, style = wx.DEFAULT_FRAME_STYLE)
903         self.application = application
904         
905         self.SetTitle("pdf2swf")
906         self.createMenu()
907         self.createToolbar()
908         self.createStatusBar()
909         self.createMainFrame()
910         
911         self.SetSize((800,600))
912
913         self.options = OptionFrame(None)
914         self.options.Show(False)
915         self.options.updateOptions()
916
917         state.onEvent(EVENT_STATUS_TEXT, self.status_change)
918         self.html = 0
919        
920         #self.table = wx.AcceleratorTable([(wx.ACCEL_ALT,  ord('X'), 333),])
921         #self.SetAcceleratorTable(self.table)
922
923         self.Bind(wx.EVT_IDLE, self.OnIdle)
924         self.Bind(wx.EVT_CLOSE, self.menu_exit)
925         return
926
927     def menu_open(self,event):
928         global state
929         if state.filename:
930             dlg = wx.FileDialog(self, "Choose PDF File:", style = wx.DD_DEFAULT_STYLE, defaultFile = state.filename, wildcard = "PDF files (*.pdf)|*.pdf|all files (*.*)|*.*")
931         else:
932             dlg = wx.FileDialog(self, "Choose PDF File:", style = wx.DD_DEFAULT_STYLE, wildcard = "PDF files (*.pdf)|*.pdf|all files (*.*)|*.*")
933
934         if dlg.ShowModal() == wx.ID_OK:
935             self.filename = dlg.GetFilename() 
936             state.loadPDF(self.filename)
937
938     def menu_save(self,event,pages=None):
939         html,self.html = self.html,0
940         global state
941         if not state.pdf:
942             return
943         print "html",html
944         if not html:
945             defaultFile = state.lastsavefile
946         else:
947             defaultFile = state.lasthtmlfile
948         dlg = wx.FileDialog(self, "Choose Save Filename:", style = wx.SAVE | wx.OVERWRITE_PROMPT, defaultFile = defaultFile, wildcard = "all files (*.*)|*.*|SWF files (*.swf)|*.swf|HTML template (*.html)|*.html")
949         
950         if dlg.ShowModal() == wx.ID_OK:
951             filename = os.path.join(dlg.GetDirectory(),dlg.GetFilename())
952         
953             #progress = ProgressFrame(self, "Saving %s File '%s'..." % (html and "HTML" or "SWF", filename))
954             #progress.Show(True)
955             progress = None
956             state.saveSWF(filename, progress, pages, html)
957             #progress.Destroy()
958     
959     def menu_save_selected(self,event):
960         if not state.pdf:
961             return
962         p = []
963         for i in range(0,self.pagelist.GetItemCount()):
964             if self.pagelist.IsSelected(i):
965                 p += [i+1]
966         self.menu_save(event, pages=p)
967
968     def menu_save_html(self,event):
969         self.html = 1
970         return self.menu_save(event)
971
972     def menu_save_selected_html(self,event):
973         self.html = 1
974         return self.menu_save_selected(event)
975
976     def menu_exit(self,event):
977         self.application.Exit()
978
979     def menu_selectall(self,event):
980         for i in range(0,self.pagelist.GetItemCount()):
981             self.pagelist.Select(i, True)
982     def menu_options(self,event):
983         self.options.Show(True)
984
985     def status_change(self):
986         self.statusbar.SetStatusText(state.status_text)
987
988     def OnIdle(self,event):
989         if self.options.needreload:
990             self.options.needreload = 0
991             if state.pdf:
992                 # reload
993                 state.loadPDF(state.filename)
994
995     def createMenu(self):
996         menubar = wx.MenuBar()
997
998         menu = wx.Menu();menubar.Append(menu, "&File")
999         menu.Append(wx.ID_OPEN, "Open PDF\tCTRL-O");self.Bind(wx.EVT_MENU, self.menu_open, id=wx.ID_OPEN)
1000         menu.AppendSeparator()
1001         menu.Append(wx.ID_SAVE, "Save SWF (all pages)\tCTRL-W");self.Bind(wx.EVT_MENU, self.menu_save, id=wx.ID_SAVE)
1002         menu.Append(wx.ID_SAVEAS, "Save SWF (selected pages)\tCTRL-S");self.Bind(wx.EVT_MENU, self.menu_save_selected, id=wx.ID_SAVEAS)
1003         menu.AppendSeparator()
1004         menu.Append(2001, "Save HTML template (all pages)\tCTRL-H");self.Bind(wx.EVT_MENU, self.menu_save_html, id=2001)
1005         menu.Append(2002, "Save HTML template (selected pages)");self.Bind(wx.EVT_MENU, self.menu_save_selected_html, id=2002)
1006         menu.AppendSeparator()
1007         menu.Append(wx.ID_EXIT, "Exit\tCTRL-Q");self.Bind(wx.EVT_MENU, self.menu_exit, id=wx.ID_EXIT)
1008
1009         menu = wx.Menu();menubar.Append(menu, "&Edit")
1010         menu.Append(wx.ID_SELECTALL, "Select All\tCTRL-A");self.Bind(wx.EVT_MENU, self.menu_selectall, id=wx.ID_SELECTALL)
1011         menu.AppendSeparator()
1012         menu.Append(wx.ID_PREFERENCES, "Options\tCTRL-R");self.Bind(wx.EVT_MENU, self.menu_options, id=wx.ID_PREFERENCES)
1013
1014         menu = wx.Menu();menubar.Append(menu, "&Help")
1015
1016         self.SetMenuBar(menubar)
1017
1018
1019     def createToolbar(self):
1020
1021         tsize = (16,16)
1022         self.toolbar = self.CreateToolBar(wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT)
1023
1024         self.toolbar.AddSimpleTool(wx.ID_OPEN,
1025                                    wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, tsize),
1026                                    "Open")
1027         self.toolbar.AddSimpleTool(wx.ID_SAVE,
1028                                    wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE, wx.ART_TOOLBAR, tsize),
1029                                    "Save selected pages")
1030         self.toolbar.AddSimpleTool(wx.ID_PREFERENCES,
1031                                    wx.ArtProvider.GetBitmap(wx.ART_LIST_VIEW, wx.ART_TOOLBAR, tsize),
1032                                    "Options")
1033         #self.toolbar.AddSeparator()
1034         self.toolbar.Realize()
1035
1036     def createStatusBar(self):
1037         self.statusbar = self.CreateStatusBar(1)
1038
1039     def createMainFrame(self):
1040         
1041         if 0:
1042             self.pagelist = PageListWidget(self)
1043             self.onepage = OnePageWidget(self)
1044             hsplit = wx.BoxSizer(wx.HORIZONTAL)
1045             pagelistbox = wx.StaticBox(self, -1, "Pages")
1046             pagelistboxsizer= wx.StaticBoxSizer(pagelistbox, wx.VERTICAL)
1047             pagelistboxsizer.Add(self.pagelist, proportion=1, flag=wx.EXPAND)
1048             onepagebox = wx.StaticBox(self, -1, "Page 1")
1049             onepageboxsizer= wx.StaticBoxSizer(onepagebox, wx.VERTICAL)
1050             onepageboxsizer.Add(self.onepage, proportion=1, flag=wx.EXPAND)
1051             hsplit.Add(pagelistboxsizer, 0, wx.EXPAND, 0)
1052             hsplit.Add(onepageboxsizer, 1, wx.EXPAND, 0)
1053             self.SetAutoLayout(True)
1054             self.SetSizer(hsplit)
1055             hsplit.Fit(self)
1056             hsplit.SetSizeHints(self)
1057         else:
1058             hsplit = wx.SplitterWindow(self, style=wx.SP_3D|wx.SP_LIVE_UPDATE)
1059             #p1 = wx.Panel(hsplit,-1, style=wx.SUNKEN_BORDER)
1060             #p2 = wx.Panel(hsplit,-1, style=wx.SUNKEN_BORDER)
1061             self.pagelist = PageListWidget(hsplit)
1062             self.onepage = OnePageWidget(hsplit)
1063             #hsplit.SplitVertically(p1,p2, sashPosition=64)
1064             hsplit.SplitVertically(self.pagelist, self.onepage, sashPosition=ICON_SIZE*3/2)
1065             hsplit.SetMinimumPaneSize(10)
1066
1067 class MyApp(wx.App):
1068     def __init__(self):
1069         wx.App.__init__(self, redirect=False, filename=None, useBestVisual=False)
1070         
1071         #state.loadPDF("sitis2007.pdf")
1072         #state.loadPDF("wxPython-Advanced-OSCON2004.pdf")
1073         global staticdata
1074         staticdata = StaticData()
1075
1076         self.frame = Pdf2swfFrame(self)
1077         self.SetTopWindow(self.frame)
1078         self.frame.Show(True)
1079
1080         #self.frame = TestFrame(self)
1081         #self.frame.Show(True)
1082
1083     def OnInit(self):
1084         return True
1085
1086 app = MyApp()
1087 app.MainLoop()
1088