4 August 2016 Phoenix updates for wx.lib.pdfviewer and demo/PDFViewer

demo/viewer.py and viewer_basics.py removed - was demo of obsolete vtk
This commit is contained in:
David Hughes
2016-08-04 11:26:08 +01:00
parent 9cb833a2f1
commit 56cad152d9
9 changed files with 544 additions and 819 deletions

View File

@@ -3,7 +3,6 @@
import wx
try:
import pyPdf
from wx.lib.pdfviewer import pdfViewer, pdfButtonPanel
havePyPdf = True
except ImportError:
@@ -51,10 +50,13 @@ def runTest(frame, nb, log):
win = TestPanel(nb, log)
return win
else:
from Main import MessagePanel
from wx.lib.msgpanel import MessagePanel
win = MessagePanel(nb,
'This demo requires the pyPdf package to be installed.\n'
'See: http://pybrary.net/pyPdf/',
'This demo requires either the\n'
'PyMuPDF see http://pythonhosted.org/PyMuPDF\n'
'or\n'
'PyPDF2 see http://pythonhosted.org/PyPDF2\n'
'package installed.\n',
'Sorry', wx.ICON_WARNING)
return win
@@ -66,9 +68,22 @@ The wx.lib.pdfviewer.pdfViewer class is derived from wx.ScrolledWindow
and can display and print PDF files. The whole file can be scrolled from
end to end at whatever magnification (zoom-level) is specified.
<p> The viewer uses <b>pyPdf</b> to parse the pdf file so it is a requirement that
this must be installed. The pyPdf home page is http://pybrary.net/pyPdf/
and the library can also be downloaded from http://pypi.python.org/pypi/pyPdf/1.12
<p> The viewer checks for the <b>PyMuPDF</b> then the <b>PyPDF2</b> package.
If neither are installed an import error exception will be raised.
<p>PyMuPDF contains the Python bindings for the underlying MuPDF library, a cross platform,
complete PDF rendering library that is GPL licenced. PyMuPDF version 1.9.2 or later is required.
<p>Further details on PyMuPDF can be found via http://pythonhosted.org/PyMuPDF
<p>PyPDF2 provides a PdfFileReader class that is used to read the content stream of a PDF
file which is subsequently rendered by the viewer itself.
Please note that this is not a complete implementation of the pdf specification and
will probably fail to render any random PDF file you supply. However it does seem to
behave correctly with files that have been produced by ReportLab using Western languages.
The main limitation is that it doesn't currently support embedded fonts.
<p>Additional details on PyPDF2 can be found via http://pythonhosted.org/PyPDF2
<p> There is an optional pdfButtonPanel class, derived from wx.lib.agw.buttonpanel,
that can be placed, for example, at the top of the scrolled viewer window,
@@ -82,12 +97,6 @@ Externally callable methods are: LoadFile, Save, Print, SetZoom, and GoPage.
otherwise wx.GraphicsContext is used. Printing is achieved by writing
directly to a wx.PrintDC and using wx.Printer.
<p> Please note that pdfviewer is a far from complete implementation of the pdf
specification and will probably fail to display any random file you supply.
However it does seem to be OK with the sort of files produced by ReportLab that
use Western languages. The biggest limitation is probably that it doesn't (yet?)
support embedded fonts and will substitute one of the standard fonts instead.
</body></html>
"""

View File

@@ -1,100 +0,0 @@
#!/usr/bin/env python
"""
Run wxPython in a second thread.
Overview:
Importing this module creates a second thread and starts
wxPython in that thread. Its single method,
add_cone(), sends an event to the second thread
telling it to create a VTK viewer window with a cone in
it.
This module is meant to be imported into the standard
Python interpreter. It also works with Pythonwin.
It doesn't seem to work with IDLE (on NT anyways).
It should also work in a wxPython application.
Applications already running a wxPython app do not
need to start a second thread. In these cases,
viewer creates the cone windows in the current
thread. You can test this by running shell.py
that comes with wxPython, importing viewer and
calling add_cone.
Usage:
[user]$ python
Python 1.5.2 (#1, Sep 17 1999, 20:15:36) ...
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import viewer
>>> viewer.add_cone() # pop up a cone window
>>> a = 1
1
>>> viewer.add_cone() # create another cone window
Why would anyone do this?:
When using wxPython, the call to app.Mainloop() takes over
the thread from which it is called. This presents a
problem for applications that want to use the standard
Python command line user interface, while occasionally
creating a GUI window for viewing an image, plot, etc.
One solution is to manage the GUI in a second thread.
wxPython does not behave well if windows are created in
a thread other than the one where wxPython was originally
imported. ( I assume importing wxPython initializes some
info in the thread). The current solution is to make the
original import of wxPython in the second thread and then
create all windows in that second thread.
Methods in the main thread can create a new window by issuing
events to a "catcher" window in the second thread. This
catcher window has event handlers that actually create the
new window.
"""
class viewer_thread:
def start(self):
""" start the GUI thread
"""
import time
import thread
thread.start_new_thread(self.run, ())
def run(self):
"""
Note that viewer_basices is first imported ***here***.
This is the second thread. viewer_basics imports
wxPython. if we imported it at
the module level instead of in this function,
the import would occur in the main thread and
wxPython wouldn't run correctly in the second thread.
"""
import viewer_basics
try:
self.app = viewer_basics.SecondThreadApp(0)
self.app.MainLoop()
except TypeError:
self.app = None
def add_cone(self):
"""
send an event to the catcher window in the
other thread and tell it to create a cone window.
"""
import viewer_basics
if self.app:
evt = viewer_basics.AddCone()
viewer_basics.wxPostEvent(self.app.catcher, evt)
else:
viewer_basics.add_cone()
viewer = viewer_thread()
viewer.start()
def add_cone():
viewer.add_cone()

View File

@@ -1,75 +0,0 @@
#!/usr/bin/env python
# 11/15/2003 - Jeff Grimmett (grimmtooth@softhome.net)
#
# o Updated for wx namespace
# o No idea what this does.
#
import wx
import wx.lib.vtk as vtk
#---------------------------------------------------------------------------
class VtkFrame(wx.Frame):
"""
Simple example VTK window that contains a cone.
"""
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(450, 300))
win = vtk.VTKRenderWindow(self, -1)
renWin = win.GetRenderWindow()
ren = vtk.vtkRenderer()
renWin.AddRenderer(ren)
cone = vtk.vtkConeSource()
coneMapper = vtk.vtkPolyDataMapper()
coneMapper.SetInput(cone.GetOutput())
coneActor = vtk.vtkActor()
coneActor.SetMapper(coneMapper)
ren.AddActor(coneActor)
#---------------------------------------------------------------------------
# Using new event binder
wx_EVT_ADD_CONE = wx.NewEventType()
EVT_ADD_CONE = wx.PyEventBinder(wx_EVT_ADD_CONE, 1)
class AddCone(wx.PyEvent):
def __init__(self):
wx.PyEvent.__init__(self)
self.SetEventType(wx_EVT_ADD_CONE)
class HiddenCatcher(wx.Frame):
"""
The "catcher" frame in the second thread.
It is invisible. It's only job is to receive
Events from the main thread, and create
the appropriate windows.
"""
def __init__(self):
wx.Frame.__init__(self, None, -1, '')
self.Bind(EVT_ADD_CONE, self.AddCone)
def AddCone(self,evt):
add_cone()
#---------------------------------------------------------------------------
class SecondThreadApp(wx.App):
"""
wxApp that lives in the second thread.
"""
def OnInit(self):
catcher = HiddenCatcher()
#self.SetTopWindow(catcher)
self.catcher = catcher
return True
#---------------------------------------------------------------------------
def add_cone():
frame = VtkFrame(None, -1, "Cone")
frame.Show(True)

View File

@@ -23,18 +23,22 @@ The :class:`~lib.pdfviewer.viewer.pdfViewer` class is derived from :class:`Scro
and can display and print PDF files. The whole file can be scrolled from
end to end at whatever magnification (zoom-level) is specified.
The viewer uses pyPDF2 or pyPdf, if neither of them are installed an
import error exception will be thrown.
The viewer uses PyMuPDF (version 1.9.2 or later) or PyPDF2.
If neither of them are installed an import error exception will be raised.
Additional details on pyPdf can be found:
PyMuPDF contains the Python bindings for the underlying MuPDF library, a cross platform,
complete PDF rendering library that is GPL licenced.
- home page: http://pybrary.net/pyPdf/
- download: https://pypi.python.org/pypi/pyPdf
Further details on PyMuPDF can be found via http://pythonhosted.org/PyMuPDF
Additional details on pyPDF2 can be found:
PyPDF2 provides a PdfFileReader class that is used to read the content stream of a PDF
file which is subsequently rendered by :class:`~lib.pdfviewer.viewer.pdfViewer` itself.
Please note that this is not a complete implementation of the pdf specification and
will probably fail to display any random file you supply. However it does seem to
satisfactorily render files typically produced by ReportLab using Western languages.
The main limitation is that it doesn't currently support embedded fonts.
- home page: http://knowah.github.com/PyPDF2/
- download: https://github.com/knowah/PyPDF2/
Additional details on PyPDF2 can be found via http://pythonhosted.org/PyPDF2
There is an optional :class:`~lib.pdfviewer.buttonpanel.pdfButtonPanel` class, derived from
:class:`~lib.agw.buttonpanel`, that can be placed, for example, at the top of the
@@ -102,22 +106,10 @@ The viewer renders the pdf file content using Cairo if installed,
otherwise :class:`GraphicsContext` is used. Printing is achieved by writing
directly to a :class:`PrinterDC` and using :class:`Printer`.
Please note that :class:`~lib.pdfviewer.viewer.pdfViewer` is a far from complete
implementation of the pdf specification and will probably fail to display any
random file you supply. However it does seem to be OK with the sort of files
produced by ReportLab that use Western languages. The biggest limitation is
probably that it doesn't (yet?) support embedded fonts and will substitute one
of the standard fonts instead.
The icons used in :class:`~lib.pdfviewer.buttonpanel.pdfButtonPanel` are Free Icons
by Axialis Software: http://www.axialis.com
You can freely use them in any project or website, commercially or not.
TERMS OF USE:
You must keep the credits of the authors: "Axialis Team", even if you modify them.
See ./bitmaps/ReadMe.txt for further details
by Axialis Software: http://www.axialis.com. You can freely use them in any project,
commercially or not, but you must keep the credits of the authors:
"Axialis Team", even if you modify them. See ./bitmaps/ReadMe.txt for further details.
"""

View File

@@ -58,7 +58,7 @@ def compute_points(controlpoints, nsteps=30):
"""
Input 4 control points as :class:`RealPoint` and convert to vec2d instances.
compute the nsteps points on the resulting curve and return them
as a list of :class:`wx.Point`
as a list of :class:`Point`
"""
controlvectors = []
for p in controlpoints:

View File

@@ -28,24 +28,24 @@ class pdfButtonPanel(bp.ButtonPanel):
from wx.lib.agw.buttonpanel and provides buttons to manipulate the viewed
PDF, e.g. zoom, save, print etc.
"""
def __init__(self, parent, id, pos, size, style):
def __init__(self, parent, nid, pos, size, style):
"""
Default class constructor.
:param wx.Window `parent`: parent window. Must not be ``None``;
:param integer `id`: window identifier. A value of -1 indicates a default value;
:param Window `parent`: parent window. Must not be ``None``;
:param integer `nid`: window identifier. A value of -1 indicates a default value;
:param `pos`: the control position. A value of (-1, -1) indicates a default position,
chosen by either the windowing system or wxPython, depending on platform;
:type `pos`: tuple or :class:`wx.Point`
:type `pos`: tuple or :class:`Point`
:param `size`: the control size. A value of (-1, -1) indicates a default size,
chosen by either the windowing system or wxPython, depending on platform;
:type `size`: tuple or :class:`wx.Size`
:type `size`: tuple or :class:`Size`
:param integer `style`: the button style (unused);
"""
self.viewer = None # reference to viewer is set by their common parent
self.numpages = None
bp.ButtonPanel.__init__(self, parent, id, "",
bp.ButtonPanel.__init__(self, parent, nid, "",
agwStyle=bp.BP_USE_GRADIENT, alignment=bp.BP_ALIGN_LEFT)
self.SetProperties()
self.CreateButtons()
@@ -92,7 +92,7 @@ class pdfButtonPanel(bp.ButtonPanel):
self.Freeze()
for item in panelitems:
if item[0].lower() == 'btn':
type, image, kind, popup, handler = item
x_type, image, kind, popup, handler = item
btn = bp.ButtonInfo(self, wx.NewId(),image, kind=kind,
shortHelp=popup, longHelp='')
self.AddButton(btn)
@@ -219,7 +219,7 @@ class pdfButtonPanel(bp.ButtonPanel):
def OnZoomSet(self, event):
"""
The zoom set handler, either a list selection of a value entered.
The zoom set handler, either a list selection or a value entered.
"""
MINZ = 0
MAXZ = 1000

View File

@@ -16,7 +16,7 @@
#----------------------------------------------------------------------------
"""
This module implements an API similar to :class:`GraphicsContext` and the
related classes. The implementation is done using :class:`wx.DC`
related classes. The implementation is done using :class:`DC`
Why do this? Neither :class:`GraphicsContext` nor the Cairo-based
GraphicsContext API provided by wx.lib.graphics can be written
@@ -148,9 +148,9 @@ class dcGraphicsContext(object):
The incoming co-ordinates have a bottom left origin with increasing
y downwards (so y values are all negative). The DC origin is top left
also with increasing y down.
:class:`wx.DC` and :class:`GraphicsContext` fonts are too big in the ratio
:class:`DC` and :class:`GraphicsContext` fonts are too big in the ratio
of pixels per inch to points per inch. If screen rendering used Cairo,
printed fonts need to be scaled but if :class:`wx.GCDC` was used, they are
printed fonts need to be scaled but if :class:`GCDC` was used, they are
already scaled.
:param `context`: **TBW** (?)
@@ -229,30 +229,30 @@ class dcGraphicsContext(object):
def SetPen(self, pen):
"""
Set the :class:`wx.Pen` to be used for stroking lines in future drawing
Set the :class:`Pen` to be used for stroking lines in future drawing
operations.
:param `pen`: the :class:`wx.Pen` to be used from now on.
:param `pen`: the :class:`Pen` to be used from now on.
"""
self._context.SetPen(pen)
def SetBrush(self, brush):
"""
Set the :class:`wx.Brush` to be used for filling shapes in future drawing
Set the :class:`Brush` to be used for filling shapes in future drawing
operations.
:param `brush`: the :class:`wx.Brush` to be used from now on.
:param `brush`: the :class:`Brush` to be used from now on.
"""
self._context.SetBrush(brush)
def SetFont(self, font, colour=None):
"""
Sets the :class:`wx.Font` to be used for drawing text.
Sets the :class:`Font` to be used for drawing text.
Don't set the dc font yet as it may need to be scaled
:param `font`: the :class:`wx.Font` for drawing text
:param `font`: the :class:`Font` for drawing text
:param `colour`: the colour to be used
"""

View File

@@ -9,8 +9,7 @@
# History: Created 17 Jun 2009
#
# Tags: phoenix-port, documented, unittest
#
# Tags: phoenix-port, documented
#----------------------------------------------------------------------------
"""
This module is used to compute Bezier curves.
@@ -339,141 +338,15 @@ class vec2d(object):
def cross(self, other):
return self.x*other[1] - self.y*other[0]
def interpolate_to(self, other, range):
return vec2d(self.x + (other[0] - self.x)*range, self.y + (other[1] - self.y)*range)
def interpolate_to(self, other, arange):
return vec2d(self.x + (other[0] - self.x)*arange, self.y + (other[1] - self.y)*arange)
def convert_to_basis(self, x_vector, y_vector):
return vec2d(self.dot(x_vector)/x_vector.get_length_sqrd(), self.dot(y_vector)/y_vector.get_length_sqrd())
return vec2d(self.dot(x_vector)/x_vector.get_length_sqrd(),
self.dot(y_vector)/y_vector.get_length_sqrd())
def __getstate__(self):
return [self.x, self.y]
def __setstate__(self, dict):
self.x, self.y = dict
########################################################################
## Unit Testing ##
########################################################################
if __name__ == "__main__":
import unittest
import pickle
####################################################################
class UnitTestVec2D(unittest.TestCase):
def setUp(self):
pass
def testCreationAndAccess(self):
v = vec2d(111,222)
self.assert_(v.x == 111 and v.y == 222)
v.x = 333
v[1] = 444
self.assert_(v[0] == 333 and v[1] == 444)
def testMath(self):
v = vec2d(111,222)
self.assertEqual(v + 1, vec2d(112,223))
self.assert_(v - 2 == [109,220])
self.assert_(v * 3 == (333,666))
self.assert_(v / 2.0 == vec2d(55.5, 111))
self.assert_(v / 2 == (55, 111))
self.assert_(v ** vec2d(2,3) == [12321, 10941048])
self.assert_(v + [-11, 78] == vec2d(100, 300))
self.assert_(v / [11,2] == [10,111])
def testReverseMath(self):
v = vec2d(111,222)
self.assert_(1 + v == vec2d(112,223))
self.assert_(2 - v == [-109,-220])
self.assert_(3 * v == (333,666))
self.assert_([222,999] / v == [2,4])
self.assert_([111,222] ** vec2d(2,3) == [12321, 10941048])
self.assert_([-11, 78] + v == vec2d(100, 300))
def testUnary(self):
v = vec2d(111,222)
v = -v
self.assert_(v == [-111,-222])
v = abs(v)
self.assert_(v == [111,222])
def testLength(self):
v = vec2d(3,4)
self.assert_(v.length == 5)
self.assert_(v.get_length_sqrd() == 25)
self.assert_(v.normalize_return_length() == 5)
self.assert_(v.length == 1)
v.length = 5
self.assert_(v == vec2d(3,4))
v2 = vec2d(10, -2)
self.assert_(v.get_distance(v2) == (v - v2).get_length())
def testAngles(self):
v = vec2d(0, 3)
self.assertEquals(v.angle, 90)
v2 = vec2d(v)
v.rotate(-90)
self.assertEqual(v.get_angle_between(v2), 90)
v2.angle -= 90
self.assertEqual(v.length, v2.length)
self.assertEquals(v2.angle, 0)
self.assertEqual(v2, [3, 0])
self.assert_((v - v2).length < .00001)
self.assertEqual(v.length, v2.length)
v2.rotate(300)
self.assertAlmostEquals(v.get_angle_between(v2), -60)
v2.rotate(v2.get_angle_between(v))
angle = v.get_angle_between(v2)
self.assertAlmostEquals(v.get_angle_between(v2), 0)
def testHighLevel(self):
basis0 = vec2d(5.0, 0)
basis1 = vec2d(0, .5)
v = vec2d(10, 1)
self.assert_(v.convert_to_basis(basis0, basis1) == [2, 2])
self.assert_(v.projection(basis0) == (10, 0))
self.assert_(basis0.dot(basis1) == 0)
def testCross(self):
lhs = vec2d(1, .5)
rhs = vec2d(4,6)
self.assert_(lhs.cross(rhs) == 4)
def testComparison(self):
int_vec = vec2d(3, -2)
flt_vec = vec2d(3.0, -2.0)
zero_vec = vec2d(0, 0)
self.assert_(int_vec == flt_vec)
self.assert_(int_vec != zero_vec)
self.assert_((flt_vec == zero_vec) == False)
self.assert_((flt_vec != int_vec) == False)
self.assert_(int_vec == (3, -2))
self.assert_(int_vec != [0, 0])
self.assert_(int_vec != 5)
self.assert_(int_vec != [3, -2, -5])
def testInplace(self):
inplace_vec = vec2d(5, 13)
inplace_ref = inplace_vec
inplace_src = vec2d(inplace_vec)
inplace_vec *= .5
inplace_vec += .5
inplace_vec /= (3, 6)
inplace_vec += vec2d(-1, -1)
alternate = (inplace_src*.5 + .5)/vec2d(3,6) + [-1, -1]
self.assertEquals(inplace_vec, inplace_ref)
self.assertEquals(inplace_vec, alternate)
def testPickle(self):
testvec = vec2d(5, .3)
testvec_str = pickle.dumps(testvec)
loaded_vec = pickle.loads(testvec_str)
self.assertEquals(testvec, loaded_vec)
####################################################################
unittest.main()
########################################################################
def __setstate__(self, adict):
self.x, self.y = adict

File diff suppressed because it is too large Load Diff