Hi all,
Robin wrote:
Jean-Michel Fauth wrote:
Hi all,
In a previous post -- point size in printout --, there
was some discussion about printing and scaling. I want
to add some comments.
Could you prepare a wiki page for this? You've got some
good info in this message and it would be nice to preserve
it in the wiki.
Preparing a true wiki page is asking too much for me,
but preparing something for a wiki page is ok.
···
-------------------------------------------------------
- title -
In the wxPython users list, there are regularly comments
and questions about printing in wxPython. In the present
page, I would like to add some comments. These are coming
from my personal experience as a wxPython user.
Printing or preparing the printing process under wxPython
is not an easy task. There are a lot of points to consider,
font size, dc size, preview, paper format, scaling... .
Each point, itself, is working fine, but the interaction
between them can lead to "unexpected" results.
Font size
The simplest way is certainly to set a font size in points
and to live with it. Unfortunately, all printers do not
"understand" the font size in the same manner. Beside this,
this method is not working for previewing.
One another approach is to set a font size proportional
to the size of the dc associated to the device. This dc
represents the printable area of a printer page or the
preview canvas in a preview window. The size of this
dc can be obtained via the wxPrintout object. The demo
uses this approach and it is globally working fine.
I propose here a small refinement to this approach. I do
not claim, this is the best solution, but I get satisfying
and practical results. When preparing the printing job, I
define my own font size unit, fsu, which is proportional to
the size of the dc.
fsu = dc.GetSizeTuple()[0] / 750.0
This is a float type. The magic 750.0 number is a trial
and error number, it corresponds more or less to the width
of the dc for a previewing of 100%. Later, I am using this
fsu to define the font size I want to use in my plot job.
fs = int(fsu * 10 + 0.5)
if fs < 1: fs = 1
dc.SetFont(wxFont(fs, wxSWISS, wxNORMAL, wxNORMAL))
Using Python 2.3, do not forget to use integer font size,
in order to prevent the "Deprecation warning: get float,
integer expected..." or something like this. This simple
approach is working fine for printing and for previewing
at all zoom levels. Note that I am using this to generate
one-page plots, not pages of long text.
You should be aware that this approach has some drawbacks.
- The printable area and therefore the dc size may vary for
different printers. A consequence is that the same document
printed on different printers may not show the same font
size.
- One more critical issue is printing a text of several lines.
In wxPython, the font size must be an integer. After having
calculated a theoretical font size and having truncated it,
you may not print the same amount of lines on two different
printers. This happens even if the size difference of the
printable area on two different printers is small. I give here
some numbers as example: the theoretical font size (based on
the dc size) may be 11.9 for printer A, while it is 12.03 for
printer B. In a real print, the printer A prints a document of
with a font size of 11 and printer B uses a font size of 12.
As consequence, each printer fill a page with a different
number of lines.
- The size may differ according to the orientation: portrait
or landscape.
Working with "print preview" is a good way to illustrate this
phenomenon. Prepare a text of, let's say, 60 lines and
define a font size proportional to the dc size. Now play with
different zoom values, you will notice the print preview
page/canvas is not always displaying the same amount of lines.
wxHTMLEasyPrinting is one another illustration, where previewing
and printing do no match.
A way to get around with this issue would be to be able to
use non integer font sizes. This is not possible on wxPython.
We have seen the printable area varies from one printer to the
other. One another and probably more elegant approach is to set
the font size proportional to the physical page size. This is
a way to set a font size a little bit more printer independent.
Of course, this depends on the page format, us-letter, A4,...
Finally, you should be aware that by defining a font size, you
set both the x- and y-dimension at the same time. If you scale
your font with the dc size, you have to choose between the height
or the width of the dc.
The size of the dc and printout
When printing, it is easy to get the size of the dc, but it is
impossible to know where this dc area is sitting on the physical
page. In others words, it is impossible to get the size of
the non printable margins. This information is not available
using wxPython. However, these sizes can be obtained by other
means. The simpliest way to get it is probably to use Word or
OpenOffice.org. If you have done this experience, you may have
notice that the size of the printable area returned by wxPython,
wxPrintout::GetPageSizeMM() is not exact. It is a rounded /
truncated(?) value.
A way to make wxPython users more happy could be to create
some kind of data base on a wiki page. After all, there are
not so many printers on the markets. I am thinking about a
python module holding a dictionary like this:
NonPrintableMargins = {'None': (,), \
'HP Deskjet 500': (3.2, 6.3, 3.6, 12.8), \
'printer A': (....) }
If you are artful enough, it is still possible to find
a acceptable solution, as shown in the MyPrintPreview7
application.
MyPrintPreview7.py
An application that illustrates the above discussion.
- It shows the font size issue.
- It proposes an home build preview window, which displays
the physical page and its printable area.
- It shows how to print a rectangle centered on the physical
page.
- It has a previewing and printingn, that match.
- User should adapt the constants to the used printer
That's all. My English is certainly not the best one, but
the spirit of the story should be intelligible.
Jean-Michel Fauth, Switzerland
22 October 2002
#-------------------------------------------------------------------
#-*- coding: iso-8859-1 -*-
#-------------------------------------------------------------------
# MyPrintPreview7.py
# wxPython 2.4.2.4, Python 2.3.0, win 98
# Jean-Michel Fauth, Switzerland
# 22 October 2003
#-------------------------------------------------------------------
from wxPython.wx import *
#-------------------------------------------------------------------
#constants
DlgHeightCorr = 25 #caption bar + borders for default style, pixels
DlgWidthCorr = 6 #borders for default style, pixels
PageA4PortraitWidth_mm = 210.0
PageA4PortraitHeight_mm = 297.0
PageA4LandscapeWidth_mm = 297.0
PageA4LandscapeHeight_mm = 210.0
#for my hp deskjet 500, numbers from wxPython/printout
PrintableAreaPortraitWidth_pix = 2400.0
PrintableAreaPortraitHeight_pix = 3282.0
PrintableAreaPortraitWidth_mm = 203.0
PrintableAreaPortraitHeight_mm = 278.0
PrintableAreaLandscapeWidth_pix = 3282.0
PrintableAreaLandscapeHeight_pix = 2400.0
PrintableAreaLandscapeWidth_mm = 278.0
PrintableAreaLandscapeHeight_mm = 203.0
#for my hp deskjet 500, numbers from other tools eg word
NonPrintableMarginPortraitLeft_mm = 3.2
NonPrintableMarginPortraitTop_mm = 6.3
NonPrintableMarginPortraitRight_mm = 3.6
NonPrintableMarginPortraitBottom_mm = 12.8
NonPrintableMarginLandscapeLeft_mm = 6.3
NonPrintableMarginLandscapeTop_mm = 3.6
NonPrintableMarginLandscapeRight_mm = 12.8
NonPrintableMarginLandscapeBottom_mm = 3.2
#-------------------------------------------------------------------
#convert mm into pixels (float)
def mm2pix(mm):
return mm / PrintableAreaPortraitWidth_mm * PrintableAreaPortraitWidth_pix
#-------------------------------------------------------------------
#a dc independent print job, used for printing and previewing
def PrepareReducedPageA4Printout(dc, q, orientation):
dcwi, dche = dc.GetSizeTuple()
dc.BeginDrawing()
#rectangle on dc limits
dc.SetBrush(wxBrush('#ffffff', wxTRANSPARENT ))
dc.SetPen(wxPen(wxRED))
dc.DrawRectangle(0, 0, dcwi, dche)
#a square centered on the page
#offsets
if orientation == 'portrait':
#margin right > margin left !
dhalf_mm = (NonPrintableMarginPortraitRight_mm - NonPrintableMarginPortraitLeft_mm) / 2.0
dhalf_pix = mm2pix(dhalf_mm)
xoff = int(dhalf_pix / q)
#margin bottom > margin top !
dhalf_mm = (NonPrintableMarginPortraitBottom_mm - NonPrintableMarginPortraitTop_mm) / 2.0
dhalf_pix = mm2pix(dhalf_mm)
yoff = int(dhalf_pix / q)
print 'q:', q
print 'x,y off:', xoff, yoff
else: #landscape
#margin right > margin left !
dhalf_mm = (NonPrintableMarginLandscapeRight_mm - NonPrintableMarginLandscapeLeft_mm) / 2.0
dhalf_pix = mm2pix(dhalf_mm)
xoff = int(dhalf_pix / q)
#margin top > margin bottom !
dhalf_mm = (NonPrintableMarginLandscapeTop_mm - NonPrintableMarginLandscapeBottom_mm) / 2.0
dhalf_pix = mm2pix(dhalf_mm)
yoff = int(dhalf_pix / q)
#a rectangle with a size proportional to the dc
#it will be previewed and printed in the center of the physical page
dc.SetBrush(wxBrush('#ffffff', wxTRANSPARENT))
dc.SetPen(wxPen(wxBLUE))
w, h = int(dcwi * 0.9), int(dche * 0.9)
dc.DrawRectangle((dcwi / 2) - (w / 2) + xoff, (dche / 2) - (h / 2) + yoff, w, h)
#some text, the font size is proportional to the physical page width
c = 1000.0
if orientation == 'portrait':
PageA4PortraitWidth_pix = mm2pix(PageA4PortraitWidth_mm)
fsu = PageA4PortraitWidth_pix / q / c
else: #landscape
PageA4LandscapeHeight_pix = mm2pix(PageA4LandscapeHeight_mm)
fsu = PageA4LandscapeHeight_pix / q / c
fs = int(fsu * 20 + 0.5)
if fs < 1.0: fs = 1.0
facename = 'Courier New'
dc.SetFont(wxFont(int(fs), wxMODERN, wxNORMAL, wxNORMAL, false, facename))
s = '0---:----1----:----2----:----3----:----4----:----5'
dc.DrawText(s, 0, 0)
dc.EndDrawing()
#-------------------------------------------------------------------
#a dialog window for previewing
#orientation : 'portrait' or 'landscape'
#q: reduction factor, for printer q=1.0
class ReducedPageA4(wxDialog):
def __init__(self, parent, id, q, orientation):
sty = wxDEFAULT_DIALOG_STYLE
tit = 'Preview - reduced page A4 - %2i%% - HP DeskJet 500' % (1. / q * 100)
wxDialog.__init__(self, parent, id, tit, wxPoint(0, 0), wxSize(10, 10), style=sty)
self.SetBackgroundColour('#dcdcdc')
self.orientation = orientation
self.q = q
#my page A4
if self.orientation == 'portrait':
PageA4PortraitWidth_pix = mm2pix(PageA4PortraitWidth_mm)
PageA4PortraitHeight_pix = mm2pix(PageA4PortraitHeight_mm)
w = PageA4PortraitWidth_pix / self.q + DlgWidthCorr
h = PageA4PortraitHeight_pix / self.q + DlgHeightCorr
else: #landscape
PageA4LandscapeWidth_pix = mm2pix(PageA4LandscapeWidth_mm)
PageA4LandscapeHeight_pix = mm2pix(PageA4LandscapeHeight_mm)
w = PageA4LandscapeWidth_pix / self.q + DlgWidthCorr
h = PageA4LandscapeHeight_pix / self.q + DlgHeightCorr
#set dialog sizes and center it
self.SetSize((w, h))
self.CenterOnScreen()
#create the printable area window
self.pa = wxWindow(self, id, wxPoint(0, 0), wxSize(100, 100))
self.pa.SetBackgroundColour(wxWHITE)
#size
if self.orientation == 'portrait':
w = PrintableAreaPortraitWidth_pix / self.q
h = PrintableAreaPortraitHeight_pix / self.q
else:
w = PrintableAreaLandscapeWidth_pix / self.q
h = PrintableAreaLandscapeHeight_pix / self.q
self.pa.SetSize((w, h))
self.pa.CenterOnParent()
#position
if self.orientation == 'portrait':
NonPrintableMarginPortraitLeft_pix = mm2pix(NonPrintableMarginPortraitLeft_mm)
NonPrintableMarginPortraitTop_pix = mm2pix(NonPrintableMarginPortraitTop_mm)
le = NonPrintableMarginPortraitLeft_pix / self.q
to = NonPrintableMarginPortraitTop_pix / self.q
else:
NonPrintableMarginLandscapeLeft_pix = mm2pix(NonPrintableMarginLandscapeLeft_mm)
NonPrintableMarginLandscapeTop_pix = mm2pix(NonPrintableMarginLandscapeTop_mm)
le = NonPrintableMarginLandscapeLeft_pix / self.q
to = NonPrintableMarginLandscapeTop_pix / self.q
self.pa.SetPosition((le, to))
EVT_PAINT(self.pa, self.OnPaint)
EVT_CHAR_HOOK(self, self.OnCharHook)
def OnPaint(self, event):
dc = wxPaintDC(self.pa)
PrepareReducedPageA4Printout(dc, self.q, self.orientation)
def OnCharHook(self, event):
if event.KeyCode() == WXK_ESCAPE or event.KeyCode() == WXK_SPACE:
self.EndModal(wxID_CANCEL)
else:
event.Skip()
#-------------------------------------------------------------------
#a printing matching the previewing
class ReducedPageA4Printout(wxPrintout):
def __init__(self, q, orientation):
wxPrintout.__init__(self)
self.orientation = orientation
self.q = q
def OnPrintPage(self, page):
dc = self.GetDC()
PrepareReducedPageA4Printout(dc, self.q, self.orientation)
return True
def GetPageInfo(self):
return (1, 1, 1, 1)
#-------------------------------------------------------------------
#-for zoom tests in preview-----------------------------------------
#-------------------------------------------------------------------
#a print job showing to be used in zoom/preview
def PrepareStdPrintout(dc):
dcwi, dche = dc.GetSizeTuple()
dc.BeginDrawing()
#a font propotional to the dc
dcwi, dche = dc.GetSizeTuple()
fsu = dcwi / 750.0
fs = int(fsu * 30 + 0.5)
if fs < 1: fs = 1
facename = 'Courier New'
dc.SetFont(wxFont(int(fs), wxMODERN, wxNORMAL, wxNORMAL, false, facename))
s = '0---:----1----:----2----:----3----:----4----:----5'
dc.DrawText(s, 0, 0)
r = dc.GetFullTextExtent('M')
for i in xrange(40):
px, py = 0, r[1] * i
dc.DrawText('line' + str(i), px, py)
dc.EndDrawing()
#-------------------------------------------------------------------
class StdPrintout(wxPrintout):
def __init__(self):
wxPrintout.__init__(self)
def OnPrintPage(self, page):
dc = self.GetDC()
PrepareStdPrintout(dc)
return True
def GetPageInfo(self):
return (1, 1, 1, 1)
#-------------------------------------------------------------------
#-Main application--------------------------------------------------
#-------------------------------------------------------------------
class MyPanel(wxPanel):
def __init__(self, parent):
wxPanel.__init__(self, parent, -1, wxDefaultPosition, wxDefaultSize)
self.parent = parent
le = 10
self.b1 = wxButton(self, 1001, 'Reduced page A4 (preview)', wxPoint(le, 10), wxDefaultSize)
EVT_BUTTON(self, 1001, self.OnBut1)
self.b2 = wxButton(self, 1002, 'Reduded page A4 (print)', wxPoint(le, 40), wxDefaultSize)
EVT_BUTTON(self, 1002, self.OnBut2)
self.b3 = wxButton(self, 1003, 'std wxPreview', wxPoint(le, 70), wxDefaultSize)
EVT_BUTTON(self, 1003, self.OnBut3)
self.b4 = wxButton(self, 1004, 'Quit', wxPoint(le, 100), wxDefaultSize)
EVT_BUTTON(self, 1004, self.OnBut4)
#a home build preview of the reduced page A4
def OnBut1(self, event):
dlg = ReducedPageA4(self, -1, 5, 'portrait')
#~ dlg = ReducedPageA4(self, -1, 5, 'landscape')
dlg.ShowModal()
dlg.Destroy()
#print the reduced page A4
def OnBut2(self, event):
pd = wxPrintData()
pd.SetPaperId(wxPAPER_A4)
pd.SetOrientation(wxPORTRAIT)
#~ pd.SetOrientation(wxLANDSCAPE)
pdd = wxPrintDialogData()
pdd.SetPrintData(pd)
printer = wxPrinter(pdd)
printout = ReducedPageA4Printout(1.0, 'portrait')
#~ printout = ReducedPageA4Printout(1.0, 'landscape')
ret = printer.Print(self.parent, printout, false)
if not ret:
print 'Printer problem...'
printout.Destroy()
#standard wxPreview
def OnBut3(self, event):
pd = wxPrintData()
pd.SetOrientation(wxPORTRAIT)
printout = StdPrintout()
printpreview = wxPrintPreview(printout, None, pd)
printpreview.SetZoom(50)
pos, size = wxPoint(0, 0), wxSize(700, 600)
previewframe = wxPreviewFrame(printpreview, None, 'Std preview', pos, size)
previewframe.Initialize()
previewframe.Show()
#do not destroy the printout object!
def OnBut4(self, event):
self.parent.Destroy()
#-------------------------------------------------------------------
class MyFrame(wxFrame):
def __init__(self, parent, id):
wxFrame.__init__(self, parent, id, "MyPrintPreview7", wxPoint(10,10), wxSize(400, 400))
self.panel = MyPanel(self)
EVT_CLOSE(self, self.OnCloseWindow)
def OnCloseWindow(self, event):
self.Destroy()
#-------------------------------------------------------------------
class MyApp(wxApp):
def OnInit(self):
frame = MyFrame(None, -1)
frame.Show(True)
self.SetTopWindow(frame)
return True
#-------------------------------------------------------------------
def main():
app = MyApp(0)
app.MainLoop()
#-------------------------------------------------------------------
if __name__ == "__main__" :
main()
#eof-------------------------------------------------------------------