mirror of
https://github.com/wxWidgets/Phoenix.git
synced 2026-01-05 11:30:06 +01:00
dcgraphics.py, bezier.py and vec2d.py no longer needed for rendering to printer DC
wx.GraphicsContext, Cairo.GraphicsContext and MuPDF page-Pixmap can now be created directly in a printer DC in pdfviewer.py (unlike originally in 2009). Cacheing of page bitmaps now removed - jerky scrolling no longer observed. SetZoom modified to stay on the same page after scale changed.
This commit is contained in:
@@ -1,71 +0,0 @@
|
||||
# Name: bezier.py
|
||||
# Package: wx.lib.pdfviewer
|
||||
#
|
||||
# Purpose: Compute Bezier curves for PDF rendered using wx.DC
|
||||
# Adapted from the original source code, see below.
|
||||
#
|
||||
# Author: David Hughes dfh@forestfield.co.uk
|
||||
# Copyright: Forestfield Software Ltd
|
||||
# Licence: Public domain
|
||||
|
||||
# History: Created 17 Jun 2009
|
||||
#
|
||||
# Tags: phoenix-port, documented
|
||||
#
|
||||
#----------------------------------------------------------------------------
|
||||
"""
|
||||
This module is used to compute Bezier curves for PDF rendering.
|
||||
"""
|
||||
|
||||
import wx
|
||||
from .vec2d import *
|
||||
|
||||
def calculate_bezier(p, steps = 30):
|
||||
"""
|
||||
Calculate a bezier curve from 4 control points and return a
|
||||
list of the resulting points.
|
||||
Depends on the 2d vector class from http://www.pygame.org/wiki/2DVectorClass
|
||||
|
||||
2007 Victor Blomqvist
|
||||
Released to the Public Domain
|
||||
The function uses the forward differencing algorithm described at
|
||||
http://www.niksula.cs.hut.fi/~hkankaan/Homepages/bezierfast.html
|
||||
"""
|
||||
|
||||
t = 1.0 / steps
|
||||
temp = t*t
|
||||
|
||||
f = p[0]
|
||||
fd = 3 * (p[1] - p[0]) * t
|
||||
fdd_per_2 = 3 * (p[0] - 2 * p[1] + p[2]) * temp
|
||||
fddd_per_2 = 3 * (3 * (p[1] - p[2]) + p[3] - p[0]) * temp * t
|
||||
|
||||
fddd = fddd_per_2 + fddd_per_2
|
||||
fdd = fdd_per_2 + fdd_per_2
|
||||
fddd_per_6 = fddd_per_2 * (1.0 / 3)
|
||||
|
||||
points = []
|
||||
for x in range(steps):
|
||||
points.append(f)
|
||||
f = f + fd + fdd_per_2 + fddd_per_6
|
||||
fd = fd + fdd + fddd_per_2
|
||||
fdd = fdd + fddd
|
||||
fdd_per_2 = fdd_per_2 + fddd_per_2
|
||||
points.append(f)
|
||||
return points
|
||||
|
||||
def compute_points(controlpoints, nsteps=30):
|
||||
"""
|
||||
Input 4 control points as :class:`wx.RealPoint` and convert to vec2d instances.
|
||||
compute the nsteps points on the resulting curve and return them
|
||||
as a list of :class:`wx.Point`
|
||||
"""
|
||||
controlvectors = []
|
||||
for p in controlpoints:
|
||||
controlvectors.append(vec2d(p.x, p.y))
|
||||
pointvectors = calculate_bezier(controlvectors, nsteps)
|
||||
curvepoints = []
|
||||
for v in pointvectors:
|
||||
curvepoints.append(wx.Point(v[0], v[1]))
|
||||
return curvepoints
|
||||
|
||||
@@ -1,474 +0,0 @@
|
||||
# Name: dcgraphics.py
|
||||
# Package: wx.lib.pdfviewer
|
||||
#
|
||||
# Purpose: A wx.GraphicsContext-like API implemented using wx.DC
|
||||
# based on wx.lib.graphics by Robin Dunn
|
||||
#
|
||||
# Author: David Hughes dfh@forestfield.co.uk
|
||||
# Copyright: Forestfield Software Ltd
|
||||
# Licence: Same as wxPython host
|
||||
|
||||
# History: 8 Aug 2009 - created
|
||||
# 12 Dec 2011 - amended DrawText
|
||||
#
|
||||
# Tags: phoenix-port, unittest, documented
|
||||
#
|
||||
#----------------------------------------------------------------------------
|
||||
"""
|
||||
This module implements an API similar to :class:`wx.GraphicsContext` and the
|
||||
related classes. The implementation is done using :class:`wx.DC`
|
||||
|
||||
Why do this? Neither :class:`wx.GraphicsContext` nor the Cairo-based
|
||||
GraphicsContext API provided by wx.lib.graphics can be written
|
||||
directly to a :class:`wx.PrinterDC`. It can be done via an intermediate bitmap in
|
||||
a :class:`wx.MemoryDC` but transferring this to a :class:`wx.PrinterDC` is an order of
|
||||
magnitude slower than writing directly.
|
||||
|
||||
Why not just use :class:`wx.PrinterDC` directly? There may be times when you do want
|
||||
to use :class:`wx.GraphicsContext` for its displayed appearance and for its
|
||||
clean(er) API, so being able to use the same code for printing as well is nice.
|
||||
|
||||
It started out with the intention of being a full implementation of the
|
||||
GraphicsContext API but so far only contains the sub-set required to render PDF.
|
||||
It also contains the co-ordinate twiddles for the PDF origin, which would need
|
||||
to be separated out if this was ever developed to be more general purpose.
|
||||
"""
|
||||
import copy
|
||||
from math import asin, pi
|
||||
from . import bezier
|
||||
import wx
|
||||
|
||||
class dcGraphicsState:
|
||||
"""
|
||||
Each instance holds the current graphics state. It can be
|
||||
saved (pushed) and restored (popped) by the owning parent.
|
||||
"""
|
||||
def __init__ (self):
|
||||
"""
|
||||
Default constructor, creates an instance with default values.
|
||||
"""
|
||||
self.Yoffset = 0.0
|
||||
self.Xtrans = 0.0
|
||||
self.Ytrans = 0.0
|
||||
self.Xscale = 1.0
|
||||
self.Yscale = 1.0
|
||||
self.sinA = 0.0
|
||||
self.cosA = 1.0
|
||||
self.tanAlpha = 0.0
|
||||
self.tanBeta = 0.0
|
||||
self.rotDegrees = 0
|
||||
|
||||
def Ytop(self, y):
|
||||
"""
|
||||
Return y co-ordinate wrt top of current page
|
||||
|
||||
:param integer `y`: y co-ordinate **TBW** (?)
|
||||
"""
|
||||
return self.Yoffset + y
|
||||
|
||||
def Translate(self, dx, dy):
|
||||
"""
|
||||
Move the origin from the current point to (dx, dy).
|
||||
|
||||
:param `dx`: x co-ordinate to move to **TBW** (?)
|
||||
:param `dy`: y co-ordinate to move to **TBW** (?)
|
||||
|
||||
"""
|
||||
self.Xtrans += (dx * self.Xscale)
|
||||
self.Ytrans += (dy * self.Yscale)
|
||||
|
||||
def Scale(self, sx, sy):
|
||||
"""
|
||||
Scale the current co-ordinates.
|
||||
|
||||
:param `sx`: x co-ordinate to scale to **TBW** (?)
|
||||
:param `sy`: y co-ordinate to scale to **TBW** (?)
|
||||
|
||||
"""
|
||||
self.Xscale *= sx
|
||||
self.Yscale *= sy
|
||||
|
||||
def Rotate(self, cosA, sinA):
|
||||
"""
|
||||
Compute the (text only) rotation angle
|
||||
sinA is inverted to cancel the original inversion in
|
||||
pdfviewer.drawfile that was introduced because of the difference
|
||||
in y direction between pdf and GraphicsContext co-ordinates.
|
||||
|
||||
:param `cosA`: **TBW** (?)
|
||||
:param `sinA`: **TBW** (?)
|
||||
|
||||
"""
|
||||
self.cosA = cosA
|
||||
self.sinA = sinA
|
||||
self.rotDegrees += asin(-self.sinA) * 180 / pi
|
||||
|
||||
def Skew(self, tanAlpha, tanBeta):
|
||||
"""
|
||||
**TBW** (?)
|
||||
|
||||
:param `tanAlpha`: **TBW** (?)
|
||||
:param `tanBeta`: **TBW** (?)
|
||||
"""
|
||||
self.tanAlpha = tanAlpha
|
||||
self.tanBeta = tanBeta
|
||||
|
||||
def Get_x(self, x=0, y=0):
|
||||
"""
|
||||
Return x co-ordinate using graphic states and transforms
|
||||
|
||||
:param `x`: current x co-ordinats
|
||||
:param `y`: current y co-ordinats
|
||||
|
||||
"""
|
||||
return ((x*self.cosA*self.Xscale - y*self.sinA*self.Yscale) + self.Xtrans)
|
||||
|
||||
def Get_y(self, x=0, y=0):
|
||||
"""
|
||||
Return y co-ordinate using graphic states and transforms
|
||||
|
||||
:param `x`: current x co-ordinats
|
||||
:param `y`: current y co-ordinats
|
||||
|
||||
"""
|
||||
return self.Ytop((x*self.sinA*self.Xscale + y*self.cosA*self.Yscale) + self.Ytrans)
|
||||
|
||||
def Get_angle(self):
|
||||
"""
|
||||
Return rotation angle in degrees.
|
||||
"""
|
||||
return self.rotDegrees
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
|
||||
class dcGraphicsContext(object):
|
||||
|
||||
def __init__(self, context=None, yoffset=0, have_cairo=False):
|
||||
"""
|
||||
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:`wx.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
|
||||
already scaled.
|
||||
|
||||
:param `context`: **TBW** (?)
|
||||
:param integer `yoffset`: informs us of the page height
|
||||
:param boolean `have_cairo`: is Cairo used
|
||||
|
||||
"""
|
||||
self._context = context
|
||||
self.gstate = dcGraphicsState()
|
||||
self.saved_state = []
|
||||
self.gstate.Yoffset = yoffset
|
||||
self.fontscale = 1.0
|
||||
if have_cairo and wx.PlatformInfo[1] == 'wxMSW':
|
||||
self.fontscale = 72.0 / 96.0
|
||||
|
||||
|
||||
@staticmethod
|
||||
def Create(dc, yoffset, have_cairo):
|
||||
"""
|
||||
The created dcGraphicsContext instance uses the dc itself.
|
||||
"""
|
||||
assert isinstance(dc, wx.DC)
|
||||
return dcGraphicsContext(dc, yoffset, have_cairo)
|
||||
|
||||
def CreateMatrix(self, a=1.0, b=0, c=0, d=1.0, tx=0, ty=0):
|
||||
"""
|
||||
Create a new matrix object.
|
||||
"""
|
||||
m = dcGraphicsMatrix()
|
||||
m.Set(a, b, c, d, tx, ty)
|
||||
return m
|
||||
|
||||
def CreatePath(self):
|
||||
"""
|
||||
Create a new path obejct.
|
||||
"""
|
||||
return dcGraphicsPath(parent=self)
|
||||
|
||||
def PushState(self):
|
||||
"""
|
||||
Makes a copy of the current state of the context and saves it
|
||||
on an internal stack of saved states. The saved state will be
|
||||
restored when PopState is called.
|
||||
"""
|
||||
self.saved_state.append(copy.deepcopy(self.gstate))
|
||||
|
||||
def PopState(self):
|
||||
"""
|
||||
Restore the most recently saved state which was saved with PushState.
|
||||
"""
|
||||
self.gstate = self.saved_state.pop()
|
||||
|
||||
def Scale(self, xScale, yScale):
|
||||
"""
|
||||
Sets the dc userscale factor.
|
||||
|
||||
:param `xScale`: **TBW** (?)
|
||||
:param `yScale`: **TBW** (?)
|
||||
|
||||
"""
|
||||
self._context.SetUserScale(xScale, yScale)
|
||||
|
||||
def ConcatTransform(self, matrix):
|
||||
"""
|
||||
Modifies the current transformation matrix by applying matrix
|
||||
as an additional transformation.
|
||||
"""
|
||||
g = self.gstate
|
||||
a, b, c, d, e, f = map(float, matrix.Get())
|
||||
g.Translate(e, f)
|
||||
if d == a and c == -b and b != 0:
|
||||
g.Rotate(a, b)
|
||||
else:
|
||||
g.Scale(a, d)
|
||||
g.Skew(b,c)
|
||||
|
||||
def SetPen(self, pen):
|
||||
"""
|
||||
Set the :class:`wx.Pen` to be used for stroking lines in future drawing
|
||||
operations.
|
||||
|
||||
:param `pen`: the :class:`wx.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
|
||||
operations.
|
||||
|
||||
:param `brush`: the :class:`wx.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.
|
||||
Don't set the dc font yet as it may need to be scaled
|
||||
|
||||
:param `font`: the :class:`wx.Font` for drawing text
|
||||
:param `colour`: the colour to be used
|
||||
|
||||
"""
|
||||
self._font = font
|
||||
if colour is not None:
|
||||
self._context.SetTextForeground(colour)
|
||||
|
||||
def StrokePath(self, path):
|
||||
"""
|
||||
Strokes the path (draws the lines) using the current pen.
|
||||
|
||||
:param `path`: path to draw line on
|
||||
"""
|
||||
raise NotImplementedError("TODO")
|
||||
|
||||
|
||||
def FillPath(self, path, fillStyle=wx.ODDEVEN_RULE):
|
||||
"""
|
||||
Fills the path using the current brush.
|
||||
|
||||
:param `path`: path to draw line on
|
||||
:param `fillStyle`: the fill style to use
|
||||
"""
|
||||
raise NotImplementedError("TODO")
|
||||
|
||||
|
||||
def DrawPath(self, path, fillStyle=wx.ODDEVEN_RULE):
|
||||
"""
|
||||
Draws the path using current pen and brush.
|
||||
|
||||
:param `path`: path to draw line on
|
||||
:param `fillStyle`: the fill style to use
|
||||
|
||||
"""
|
||||
pathdict = {'SetPen': self._context.SetPen,
|
||||
'DrawLine': self._context.DrawLine,
|
||||
'DrawRectangle': self._context.DrawRectangle,
|
||||
'DrawSpline': self._context.DrawSpline}
|
||||
for pathcmd, args, kwargs in path.commands:
|
||||
pathdict[pathcmd](*args, **kwargs)
|
||||
if path.allpoints:
|
||||
self._context.DrawPolygon(path.allpoints, 0, 0, fillStyle)
|
||||
|
||||
|
||||
def DrawText(self, text, x, y, backgroundBrush=None):
|
||||
"""
|
||||
Set the dc font at the required size.
|
||||
Ensure original font is not altered
|
||||
Draw the text at (x, y) using the current font.
|
||||
|
||||
:param `text`: the text to draw
|
||||
:param `x`: x co-ordinates for text
|
||||
:param `y`: y co-ordinates for text
|
||||
:param `backgroundBrush`: curently ignored
|
||||
|
||||
"""
|
||||
g = self.gstate
|
||||
orgsize = self._font.GetPointSize()
|
||||
newsize = orgsize * (g.Xscale * self.fontscale)
|
||||
|
||||
self._font.SetPointSize(newsize)
|
||||
self._context.SetFont(self._font)
|
||||
self._context.DrawRotatedText(text, g.Get_x(x, y), g.Get_y(x, y), g.Get_angle())
|
||||
self._font.SetPointSize(orgsize)
|
||||
|
||||
|
||||
def DrawBitmap(self, bmp, x, y, w=-1, h=-1):
|
||||
"""
|
||||
Draw the bitmap
|
||||
|
||||
:param `bmp`: the bitmap to draw
|
||||
:param `x`: the x co-ordinate for the bitmap
|
||||
:param `y`: the y co-ordinate for the bitmap
|
||||
:param `w`: currently ignored
|
||||
:param `h`: currently ignored
|
||||
|
||||
"""
|
||||
g = self.gstate
|
||||
self._context.DrawBitmap(bmp, g.Get_x(x, y), g.Get_y(x, y))
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
class dcGraphicsMatrix(object):
|
||||
"""
|
||||
A matrix holds an affine transformations, such as a scale,
|
||||
rotation, shear, or a combination of these, and is used to convert
|
||||
between different coordinante spaces.
|
||||
"""
|
||||
def __init__(self):
|
||||
"""
|
||||
The default constructor
|
||||
"""
|
||||
self._matrix = ()
|
||||
|
||||
|
||||
def Set(self, a=1.0, b=0.0, c=0.0, d=1.0, tx=0.0, ty=0.0):
|
||||
"""
|
||||
Set the componenets of the matrix by value, default values
|
||||
are the identity matrix.
|
||||
|
||||
:param `a`: **TBW** (?)
|
||||
:param `b`: **TBW** (?)
|
||||
:param `c`: **TBW** (?)
|
||||
:param `d`: **TBW** (?)
|
||||
:param `tx`: **TBW** (?)
|
||||
:param `ty`: **TBW** (?)
|
||||
|
||||
|
||||
"""
|
||||
self._matrix = (a, b, c, d, tx, ty)
|
||||
|
||||
|
||||
def Get(self):
|
||||
"""
|
||||
Return the component values of the matrix as a tuple.
|
||||
"""
|
||||
return tuple(self._matrix)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
class dcGraphicsPath(object):
|
||||
"""
|
||||
A GraphicsPath is a representaion of a geometric path, essentially
|
||||
a collection of lines and curves. Paths can be used to define
|
||||
areas to be stroked and filled on a GraphicsContext.
|
||||
"""
|
||||
def __init__(self, parent=None):
|
||||
"""
|
||||
A path is essentially an object that we use just for
|
||||
collecting path moves, lines, and curves in order to apply
|
||||
them to the real context using DrawPath.
|
||||
"""
|
||||
self.commands = []
|
||||
self.allpoints = []
|
||||
if parent:
|
||||
self.gstate = parent.gstate
|
||||
self.fillcolour = parent._context.GetBrush().GetColour()
|
||||
self.isfilled = parent._context.GetBrush().GetStyle() != wx.BRUSHSTYLE_TRANSPARENT
|
||||
|
||||
def AddCurveToPoint(self, cx1, cy1, cx2, cy2, x, y):
|
||||
"""
|
||||
Adds a cubic Bezier curve from the current point, using two
|
||||
control points and an end point.
|
||||
|
||||
:param `cx1`: **TBW** (?)
|
||||
:param `cy1`: **TBW** (?)
|
||||
:param `cx2`: **TBW** (?)
|
||||
:param `cy2`: **TBW** (?)
|
||||
:param `x`: **TBW** (?)
|
||||
:param `y`: **TBW** (?)
|
||||
|
||||
"""
|
||||
g = self.gstate
|
||||
clist = []
|
||||
clist.append(wx.RealPoint(self.xc, self.yc))
|
||||
clist.append(wx.RealPoint(g.Get_x(cx1, cy1), g.Get_y(cx1, cy1)))
|
||||
clist.append(wx.RealPoint(g.Get_x(cx2, cy2), g.Get_y(cx2, cy2)))
|
||||
clist.append(wx.RealPoint(g.Get_x(x, y), g.Get_y(x, y)))
|
||||
self.xc, self.yc = clist[-1]
|
||||
plist = bezier.compute_points(clist, 64)
|
||||
if self.isfilled:
|
||||
self.allpoints.extend(plist)
|
||||
else:
|
||||
self.commands.append(['DrawSpline', (plist,), {}])
|
||||
|
||||
def AddLineToPoint(self, x, y):
|
||||
"""
|
||||
Adds a straight line from the current point to (x, y)
|
||||
|
||||
:param `x`: **TBW** (?)
|
||||
:param `y`: **TBW** (?)
|
||||
|
||||
"""
|
||||
x2 = self.gstate.Get_x(x, y)
|
||||
y2 = self.gstate.Get_y(x, y)
|
||||
if self.isfilled:
|
||||
self.allpoints.extend([wx.Point(self.xc, self.yc), wx.Point(x2, y2)])
|
||||
else:
|
||||
self.commands.append(['DrawLine', (self.xc, self.yc, x2, y2), {}])
|
||||
self.xc = x2
|
||||
self.yc = y2
|
||||
|
||||
def AddRectangle(self, x, y, w, h):
|
||||
"""
|
||||
Adds a new rectangle as a closed sub-path.
|
||||
|
||||
:param `x`: **TBW** (?)
|
||||
:param `y`: **TBW** (?)
|
||||
:param `w`: **TBW** (?)
|
||||
:param `h`: **TBW** (?)
|
||||
|
||||
"""
|
||||
g = self.gstate
|
||||
xr = g.Get_x(x, y)
|
||||
yr = g.Get_y(x, y)
|
||||
wr = w*g.Xscale*g.cosA - h*g.Yscale*g.sinA
|
||||
hr = w*g.Xscale*g.sinA + h*g.Yscale*g.cosA
|
||||
if round(wr) == 1 or round(hr) == 1: # draw thin rectangles as lines
|
||||
self.commands.append(['SetPen', (wx.Pen(self.fillcolour, 1.0),), {}])
|
||||
self.commands.append(['DrawLine', (xr, yr, xr+wr-1, yr+hr), {}])
|
||||
else:
|
||||
self.commands.append(['DrawRectangle', (xr, yr, wr, hr), {}])
|
||||
|
||||
def CloseSubpath(self):
|
||||
"""
|
||||
Adds a line segment to the path from the current point to the
|
||||
beginning of the current sub-path, and closes this sub-path.
|
||||
"""
|
||||
if self.isfilled:
|
||||
self.allpoints.extend([wx.Point(self.xc, self.yc), wx.Point(self.x0, self.y0)])
|
||||
|
||||
def MoveToPoint(self, x, y):
|
||||
"""
|
||||
Begins a new sub-path at (x,y) by moving the "current point" there.
|
||||
"""
|
||||
self.x0 = self.xc = self.gstate.Get_x(x, y)
|
||||
self.y0 = self.yc = self.gstate.Get_y(x, y)
|
||||
|
||||
|
||||
@@ -1,352 +0,0 @@
|
||||
# Name: vec2d.py
|
||||
# Package: wx.lib.pdfviewer
|
||||
#
|
||||
# Purpose: 2D vector class. Used for computing Bezier curves
|
||||
#
|
||||
# Author:
|
||||
# Copyright:
|
||||
# Licence: LGPL - from http://www.pygame.org/wiki/2DVectorClass
|
||||
|
||||
# History: Created 17 Jun 2009
|
||||
#
|
||||
# Tags: phoenix-port, documented
|
||||
#----------------------------------------------------------------------------
|
||||
"""
|
||||
This module is used to compute Bezier curves.
|
||||
"""
|
||||
import operator
|
||||
import math
|
||||
|
||||
class vec2d(object):
|
||||
"""
|
||||
2d vector class, supports vector and scalar operators,
|
||||
and also provides a bunch of high level functions.
|
||||
"""
|
||||
__slots__ = ['x', 'y']
|
||||
|
||||
def __init__(self, x_or_pair, y = None):
|
||||
if y == None:
|
||||
self.x = x_or_pair[0]
|
||||
self.y = x_or_pair[1]
|
||||
else:
|
||||
self.x = x_or_pair
|
||||
self.y = y
|
||||
|
||||
def __len__(self):
|
||||
return 2
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key == 0:
|
||||
return self.x
|
||||
elif key == 1:
|
||||
return self.y
|
||||
else:
|
||||
raise IndexError("Invalid subscript "+str(key)+" to vec2d")
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key == 0:
|
||||
self.x = value
|
||||
elif key == 1:
|
||||
self.y = value
|
||||
else:
|
||||
raise IndexError("Invalid subscript "+str(key)+" to vec2d")
|
||||
|
||||
# String representaion (for debugging)
|
||||
def __repr__(self):
|
||||
return 'vec2d(%s, %s)' % (self.x, self.y)
|
||||
|
||||
# Comparison
|
||||
def __eq__(self, other):
|
||||
if hasattr(other, "__getitem__") and len(other) == 2:
|
||||
return self.x == other[0] and self.y == other[1]
|
||||
else:
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
if hasattr(other, "__getitem__") and len(other) == 2:
|
||||
return self.x != other[0] or self.y != other[1]
|
||||
else:
|
||||
return True
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self.x or self.y)
|
||||
|
||||
# Generic operator handlers
|
||||
def _o2(self, other, f):
|
||||
"""
|
||||
Any two-operator operation where the left operand is a vec2d.
|
||||
"""
|
||||
if isinstance(other, vec2d):
|
||||
return vec2d(f(self.x, other.x),
|
||||
f(self.y, other.y))
|
||||
elif (hasattr(other, "__getitem__")):
|
||||
return vec2d(f(self.x, other[0]),
|
||||
f(self.y, other[1]))
|
||||
else:
|
||||
return vec2d(f(self.x, other),
|
||||
f(self.y, other))
|
||||
|
||||
def _r_o2(self, other, f):
|
||||
"""
|
||||
Any two-operator operation where the right operand is a vec2d.
|
||||
"""
|
||||
if (hasattr(other, "__getitem__")):
|
||||
return vec2d(f(other[0], self.x),
|
||||
f(other[1], self.y))
|
||||
else:
|
||||
return vec2d(f(other, self.x),
|
||||
f(other, self.y))
|
||||
|
||||
def _io(self, other, f):
|
||||
"""
|
||||
inplace operator
|
||||
"""
|
||||
if (hasattr(other, "__getitem__")):
|
||||
self.x = f(self.x, other[0])
|
||||
self.y = f(self.y, other[1])
|
||||
else:
|
||||
self.x = f(self.x, other)
|
||||
self.y = f(self.y, other)
|
||||
return self
|
||||
|
||||
# Addition
|
||||
def __add__(self, other):
|
||||
if isinstance(other, vec2d):
|
||||
return vec2d(self.x + other.x, self.y + other.y)
|
||||
elif hasattr(other, "__getitem__"):
|
||||
return vec2d(self.x + other[0], self.y + other[1])
|
||||
else:
|
||||
return vec2d(self.x + other, self.y + other)
|
||||
__radd__ = __add__
|
||||
|
||||
def __iadd__(self, other):
|
||||
if isinstance(other, vec2d):
|
||||
self.x += other.x
|
||||
self.y += other.y
|
||||
elif hasattr(other, "__getitem__"):
|
||||
self.x += other[0]
|
||||
self.y += other[1]
|
||||
else:
|
||||
self.x += other
|
||||
self.y += other
|
||||
return self
|
||||
|
||||
# Subtraction
|
||||
def __sub__(self, other):
|
||||
if isinstance(other, vec2d):
|
||||
return vec2d(self.x - other.x, self.y - other.y)
|
||||
elif (hasattr(other, "__getitem__")):
|
||||
return vec2d(self.x - other[0], self.y - other[1])
|
||||
else:
|
||||
return vec2d(self.x - other, self.y - other)
|
||||
def __rsub__(self, other):
|
||||
if isinstance(other, vec2d):
|
||||
return vec2d(other.x - self.x, other.y - self.y)
|
||||
if (hasattr(other, "__getitem__")):
|
||||
return vec2d(other[0] - self.x, other[1] - self.y)
|
||||
else:
|
||||
return vec2d(other - self.x, other - self.y)
|
||||
def __isub__(self, other):
|
||||
if isinstance(other, vec2d):
|
||||
self.x -= other.x
|
||||
self.y -= other.y
|
||||
elif (hasattr(other, "__getitem__")):
|
||||
self.x -= other[0]
|
||||
self.y -= other[1]
|
||||
else:
|
||||
self.x -= other
|
||||
self.y -= other
|
||||
return self
|
||||
|
||||
# Multiplication
|
||||
def __mul__(self, other):
|
||||
if isinstance(other, vec2d):
|
||||
return vec2d(self.x*other.x, self.y*other.y)
|
||||
if (hasattr(other, "__getitem__")):
|
||||
return vec2d(self.x*other[0], self.y*other[1])
|
||||
else:
|
||||
return vec2d(self.x*other, self.y*other)
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __imul__(self, other):
|
||||
if isinstance(other, vec2d):
|
||||
self.x *= other.x
|
||||
self.y *= other.y
|
||||
elif (hasattr(other, "__getitem__")):
|
||||
self.x *= other[0]
|
||||
self.y *= other[1]
|
||||
else:
|
||||
self.x *= other
|
||||
self.y *= other
|
||||
return self
|
||||
|
||||
# Division
|
||||
def __div__(self, other):
|
||||
return self._o2(other, operator.div)
|
||||
def __rdiv__(self, other):
|
||||
return self._r_o2(other, operator.div)
|
||||
def __idiv__(self, other):
|
||||
return self._io(other, operator.div)
|
||||
|
||||
def __floordiv__(self, other):
|
||||
return self._o2(other, operator.floordiv)
|
||||
def __rfloordiv__(self, other):
|
||||
return self._r_o2(other, operator.floordiv)
|
||||
def __ifloordiv__(self, other):
|
||||
return self._io(other, operator.floordiv)
|
||||
|
||||
def __truediv__(self, other):
|
||||
return self._o2(other, operator.truediv)
|
||||
def __rtruediv__(self, other):
|
||||
return self._r_o2(other, operator.truediv)
|
||||
def __itruediv__(self, other):
|
||||
return self._io(other, operator.floordiv)
|
||||
|
||||
# Modulo
|
||||
def __mod__(self, other):
|
||||
return self._o2(other, operator.mod)
|
||||
def __rmod__(self, other):
|
||||
return self._r_o2(other, operator.mod)
|
||||
|
||||
def __divmod__(self, other):
|
||||
return self._o2(other, operator.divmod)
|
||||
def __rdivmod__(self, other):
|
||||
return self._r_o2(other, operator.divmod)
|
||||
|
||||
# Exponentation
|
||||
def __pow__(self, other):
|
||||
return self._o2(other, operator.pow)
|
||||
def __rpow__(self, other):
|
||||
return self._r_o2(other, operator.pow)
|
||||
|
||||
# Bitwise operators
|
||||
def __lshift__(self, other):
|
||||
return self._o2(other, operator.lshift)
|
||||
def __rlshift__(self, other):
|
||||
return self._r_o2(other, operator.lshift)
|
||||
|
||||
def __rshift__(self, other):
|
||||
return self._o2(other, operator.rshift)
|
||||
def __rrshift__(self, other):
|
||||
return self._r_o2(other, operator.rshift)
|
||||
|
||||
def __and__(self, other):
|
||||
return self._o2(other, operator.and_)
|
||||
__rand__ = __and__
|
||||
|
||||
def __or__(self, other):
|
||||
return self._o2(other, operator.or_)
|
||||
__ror__ = __or__
|
||||
|
||||
def __xor__(self, other):
|
||||
return self._o2(other, operator.xor)
|
||||
__rxor__ = __xor__
|
||||
|
||||
# Unary operations
|
||||
def __neg__(self):
|
||||
return vec2d(operator.neg(self.x), operator.neg(self.y))
|
||||
|
||||
def __pos__(self):
|
||||
return vec2d(operator.pos(self.x), operator.pos(self.y))
|
||||
|
||||
def __abs__(self):
|
||||
return vec2d(abs(self.x), abs(self.y))
|
||||
|
||||
def __invert__(self):
|
||||
return vec2d(-self.x, -self.y)
|
||||
|
||||
# vectory functions
|
||||
def get_length_sqrd(self):
|
||||
return self.x**2 + self.y**2
|
||||
|
||||
def get_length(self):
|
||||
return math.sqrt(self.x**2 + self.y**2)
|
||||
def __setlength(self, value):
|
||||
length = self.get_length()
|
||||
self.x *= value/length
|
||||
self.y *= value/length
|
||||
length = property(get_length, __setlength, None, "gets or sets the magnitude of the vector")
|
||||
|
||||
def rotate(self, angle_degrees):
|
||||
radians = math.radians(angle_degrees)
|
||||
cos = math.cos(radians)
|
||||
sin = math.sin(radians)
|
||||
x = self.x*cos - self.y*sin
|
||||
y = self.x*sin + self.y*cos
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def rotated(self, angle_degrees):
|
||||
radians = math.radians(angle_degrees)
|
||||
cos = math.cos(radians)
|
||||
sin = math.sin(radians)
|
||||
x = self.x*cos - self.y*sin
|
||||
y = self.x*sin + self.y*cos
|
||||
return vec2d(x, y)
|
||||
|
||||
def get_angle(self):
|
||||
if (self.get_length_sqrd() == 0):
|
||||
return 0
|
||||
return math.degrees(math.atan2(self.y, self.x))
|
||||
def __setangle(self, angle_degrees):
|
||||
self.x = self.length
|
||||
self.y = 0
|
||||
self.rotate(angle_degrees)
|
||||
angle = property(get_angle, __setangle, None, "gets or sets the angle of a vector")
|
||||
|
||||
def get_angle_between(self, other):
|
||||
cross = self.x*other[1] - self.y*other[0]
|
||||
dot = self.x*other[0] + self.y*other[1]
|
||||
return math.degrees(math.atan2(cross, dot))
|
||||
|
||||
def normalized(self):
|
||||
length = self.length
|
||||
if length != 0:
|
||||
return self/length
|
||||
return vec2d(self)
|
||||
|
||||
def normalize_return_length(self):
|
||||
length = self.length
|
||||
if length != 0:
|
||||
self.x /= length
|
||||
self.y /= length
|
||||
return length
|
||||
|
||||
def perpendicular(self):
|
||||
return vec2d(-self.y, self.x)
|
||||
|
||||
def perpendicular_normal(self):
|
||||
length = self.length
|
||||
if length != 0:
|
||||
return vec2d(-self.y/length, self.x/length)
|
||||
return vec2d(self)
|
||||
|
||||
def dot(self, other):
|
||||
return float(self.x*other[0] + self.y*other[1])
|
||||
|
||||
def get_distance(self, other):
|
||||
return math.sqrt((self.x - other[0])**2 + (self.y - other[1])**2)
|
||||
|
||||
def get_dist_sqrd(self, other):
|
||||
return (self.x - other[0])**2 + (self.y - other[1])**2
|
||||
|
||||
def projection(self, other):
|
||||
other_length_sqrd = other[0]*other[0] + other[1]*other[1]
|
||||
projected_length_times_other_length = self.dot(other)
|
||||
return other*(projected_length_times_other_length/other_length_sqrd)
|
||||
|
||||
def cross(self, other):
|
||||
return self.x*other[1] - self.y*other[0]
|
||||
|
||||
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())
|
||||
|
||||
def __getstate__(self):
|
||||
return [self.x, self.y]
|
||||
|
||||
def __setstate__(self, adict):
|
||||
self.x, self.y = adict
|
||||
@@ -34,10 +34,7 @@ from six import BytesIO, string_types
|
||||
|
||||
import wx
|
||||
|
||||
CACHE_LATE_PAGES = False # TODO Cacheing of page bitmaps does not currently
|
||||
# work when using cairocffi and Python 3.4 (MSW)
|
||||
LATE_THRESHOLD = 200 # Time to render (ttr), milliseconds
|
||||
VERBOSE = False
|
||||
VERBOSE = True
|
||||
|
||||
try:
|
||||
# see http://pythonhosted.org/PyMuPDF - documentation & installation
|
||||
@@ -59,7 +56,7 @@ except ImportError:
|
||||
|
||||
GraphicsContext = wx.GraphicsContext
|
||||
have_cairo = False
|
||||
if not mupdf:
|
||||
if not mupdf:
|
||||
try:
|
||||
import wx.lib.wxcairo as wxcairo
|
||||
import cairo
|
||||
@@ -69,8 +66,6 @@ if not mupdf:
|
||||
except ImportError:
|
||||
if VERBOSE: print('pdfviewer using wx.GraphicsContext')
|
||||
|
||||
from .dcgraphics import dcGraphicsContext
|
||||
|
||||
# New PageObject method added by Forestfield Software
|
||||
def extractOperators(self):
|
||||
"""
|
||||
@@ -130,7 +125,6 @@ class pdfViewer(wx.ScrolledWindow):
|
||||
self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) # recommended in wxWidgets docs
|
||||
self.buttonpanel = None # reference to panel is set by their common parent
|
||||
self._showLoadProgress = (not mupdf)
|
||||
self._usePrintDirect = (not mupdf)
|
||||
|
||||
self.Bind(wx.EVT_PAINT, self.OnPaint)
|
||||
self.Bind(wx.EVT_SIZE, self.OnResize)
|
||||
@@ -143,6 +137,7 @@ class pdfViewer(wx.ScrolledWindow):
|
||||
self.nom_page_gap = 20 # nominal inter-page gap (points)
|
||||
self.scrollrate = 20 # pixels per scrollbar increment
|
||||
self.page_buffer_valid = False
|
||||
self.page_after_zoom_change = None
|
||||
self.ClearBackground()
|
||||
|
||||
def OnIdle(self, event):
|
||||
@@ -159,8 +154,6 @@ class pdfViewer(wx.ScrolledWindow):
|
||||
Buffer size change due to client area resize.
|
||||
"""
|
||||
self.resizing = True
|
||||
if hasattr(self, 'cachedpages'):
|
||||
self.cachedpages = {}
|
||||
event.Skip()
|
||||
|
||||
def OnScroll(self, event):
|
||||
@@ -185,8 +178,8 @@ class pdfViewer(wx.ScrolledWindow):
|
||||
|
||||
# This section defines the externally callable methods:
|
||||
# LoadFile, Save, Print, SetZoom, and GoPage
|
||||
# also the getters and setters for ShowLoadProgress and UsePrintDirect
|
||||
# that are only applicable if using PyPDF2
|
||||
# also the getter and setter for ShowLoadProgress
|
||||
# that is only applicable if using PyPDF2
|
||||
|
||||
def LoadFile(self, pdf_file):
|
||||
"""
|
||||
@@ -211,10 +204,9 @@ class pdfViewer(wx.ScrolledWindow):
|
||||
# remove comment from next line to test using a file-like object
|
||||
# pdf_file = create_fileobject(pdf_file)
|
||||
if mupdf:
|
||||
self.pdfdoc = mupdfProcessor(self, pdf_file)
|
||||
self.pdfdoc = mupdfProcessor(self, pdf_file)
|
||||
else:
|
||||
self.pdfdoc = pypdfProcessor(self, pdf_file, self.ShowLoadProgress)
|
||||
self.cachedpages = {}
|
||||
|
||||
self.numpages = self.pdfdoc.numpages
|
||||
self.pagewidth = self.pdfdoc.pagewidth
|
||||
@@ -288,9 +280,11 @@ class pdfViewer(wx.ScrolledWindow):
|
||||
"""
|
||||
pagenow = self.frompage
|
||||
self.zoomscale = zoomscale
|
||||
if hasattr(self, 'cachedpages'):
|
||||
self.cachedpages = {}
|
||||
self.page_buffer_valid = False
|
||||
# calling GoPage now will trigger rendering at the new size but the page location
|
||||
# will be calculated based on the old zoom scale - so save the required page number
|
||||
# and call GoPage again *after* rendering at the new size
|
||||
self.page_after_zoom_change = pagenow
|
||||
self.GoPage(pagenow)
|
||||
|
||||
def GoPage(self, pagenum):
|
||||
@@ -317,19 +311,6 @@ class pdfViewer(wx.ScrolledWindow):
|
||||
"""Setter for showLoadProgress."""
|
||||
self._showLoadProgress = flag
|
||||
|
||||
@property
|
||||
def UsePrintDirect(self):
|
||||
"""
|
||||
Property to control whether prining is done via a page buffer or
|
||||
directly using dcGraphicsContext (PyPDF2 only)
|
||||
"""
|
||||
return self._usePrintDirect
|
||||
|
||||
@UsePrintDirect.setter
|
||||
def UsePrintDirect(self, flag):
|
||||
"""Setter for usePrintDirect."""
|
||||
self._usePrintDirect = flag
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
|
||||
# This section is concerned with rendering a sub-set of drawing commands on demand
|
||||
@@ -342,24 +323,23 @@ class pdfViewer(wx.ScrolledWindow):
|
||||
"""
|
||||
self.frompage = 0
|
||||
self.topage = 0
|
||||
self.clientdc = dc = wx.ClientDC(self) # dc for device scaling
|
||||
self.device_scale = dc.GetPPI()[0]/72.0 # pixels per inch / points per inch
|
||||
device_scale = wx.ClientDC(self).GetPPI()[0]/72.0 # pixels per inch/points per inch
|
||||
self.font_scale_metrics = 1.0
|
||||
self.font_scale_size = 1.0
|
||||
# for Windows only with wx.GraphicsContext the rendered font size is too big
|
||||
# in the ratio of screen pixels per inch to points per inch
|
||||
# and font metrics are too big in the same ratio for both for Cairo and wx.GC
|
||||
if wx.PlatformInfo[1] == 'wxMSW':
|
||||
self.font_scale_metrics = 1.0 / self.device_scale
|
||||
self.font_scale_metrics = 1.0 / device_scale
|
||||
if not have_cairo:
|
||||
self.font_scale_size = 1.0 / self.device_scale
|
||||
self.font_scale_size = 1.0 / device_scale
|
||||
|
||||
self.winwidth, self.winheight = self.GetClientSize()
|
||||
if self.winheight < 100:
|
||||
return
|
||||
self.Ypage = self.pageheight + self.nom_page_gap
|
||||
if self.zoomscale > 0.0:
|
||||
self.scale = self.zoomscale * self.device_scale
|
||||
self.scale = self.zoomscale * device_scale
|
||||
else:
|
||||
if int(self.zoomscale) == -1: # fit width
|
||||
self.scale = self.winwidth / self.pagewidth
|
||||
@@ -395,7 +375,7 @@ class pdfViewer(wx.ScrolledWindow):
|
||||
# Inform buttonpanel controls of any changes
|
||||
if self.buttonpanel:
|
||||
self.buttonpanel.Update(self.frompage, self.numpages,
|
||||
self.scale/self.device_scale)
|
||||
self.scale/device_scale)
|
||||
|
||||
self.page_y0 = self.frompage * self.Ypagepixels
|
||||
self.page_x0 = 0
|
||||
@@ -428,7 +408,9 @@ class pdfViewer(wx.ScrolledWindow):
|
||||
# Initialize the buffer bitmap.
|
||||
self.pagebuffer = wx.Bitmap(self.pagebufferwidth, self.pagebufferheight)
|
||||
self.pdc = wx.MemoryDC(self.pagebuffer) # must persist
|
||||
|
||||
gc = GraphicsContext.Create(self.pdc) # Cairo/wx.GraphicsContext API
|
||||
|
||||
# white background
|
||||
path = gc.CreatePath()
|
||||
path.AddRectangle(0, 0,
|
||||
@@ -439,34 +421,25 @@ class pdfViewer(wx.ScrolledWindow):
|
||||
for pageno in range(self.frompage, self.topage+1):
|
||||
self.xpageoffset = 0 - self.x0
|
||||
self.ypageoffset = pageno*self.Ypagepixels - self.page_y0
|
||||
if not mupdf and pageno in self.cachedpages:
|
||||
self.pdc.Blit(self.xpageoffset, self.ypageoffset,
|
||||
self.Xpagepixels, self.Ypagepixels,
|
||||
self.cachedpages[pageno], 0, 0)
|
||||
gc.PushState()
|
||||
if mupdf:
|
||||
gc.Translate(self.xpageoffset, self.ypageoffset)
|
||||
# scaling is done inside RenderPage
|
||||
else:
|
||||
t1 = time.time()
|
||||
gc.PushState()
|
||||
if mupdf:
|
||||
gc.Translate(self.xpageoffset, self.ypageoffset)
|
||||
# scaling is done inside RenderPage
|
||||
else:
|
||||
|
||||
gc.Translate(self.xpageoffset, self.ypageoffset +
|
||||
self.pageheight*self.scale)
|
||||
gc.Scale(self.scale, self.scale)
|
||||
self.pdfdoc.RenderPage(gc, pageno, self.scale)
|
||||
# Show inter-page gap
|
||||
gc.SetBrush(wx.Brush(wx.Colour(180, 180, 180))) #mid grey
|
||||
gc.SetPen(wx.TRANSPARENT_PEN)
|
||||
if mupdf:
|
||||
gc.DrawRectangle(0, self.pageheight*self.scale,
|
||||
self.pagewidth*self.scale, self.page_gap*self.scale)
|
||||
else:
|
||||
gc.DrawRectangle(0, 0, self.pagewidth, self.page_gap)
|
||||
gc.PopState()
|
||||
ttr = time.time()-t1
|
||||
if not mupdf and CACHE_LATE_PAGES and ttr * 1000 > LATE_THRESHOLD:
|
||||
self.CachePage(pageno) # save page out of buffer
|
||||
gc.Translate(self.xpageoffset, self.ypageoffset +
|
||||
self.pageheight*self.scale)
|
||||
gc.Scale(self.scale, self.scale)
|
||||
self.pdfdoc.RenderPage(gc, pageno, scale=self.scale)
|
||||
# Show inter-page gap
|
||||
gc.SetBrush(wx.Brush(wx.Colour(180, 180, 180))) #mid grey
|
||||
gc.SetPen(wx.TRANSPARENT_PEN)
|
||||
if mupdf:
|
||||
gc.DrawRectangle(0, self.pageheight*self.scale,
|
||||
self.pagewidth*self.scale, self.page_gap*self.scale)
|
||||
else:
|
||||
gc.DrawRectangle(0, 0, self.pagewidth, self.page_gap)
|
||||
gc.PopState()
|
||||
gc.PushState()
|
||||
gc.Translate(0-self.x0, 0-self.page_y0)
|
||||
self.RenderPageBoundaries(gc)
|
||||
@@ -475,6 +448,11 @@ class pdfViewer(wx.ScrolledWindow):
|
||||
self.page_buffer_valid = True
|
||||
self.Refresh(0) # Blit appropriate area of new or existing page buffer to screen
|
||||
|
||||
# ensure we stay on the same page after zoom scale is changed
|
||||
if self.page_after_zoom_change:
|
||||
self.GoPage(self.page_after_zoom_change)
|
||||
self.page_after_zoom_change = None
|
||||
|
||||
def RenderPageBoundaries(self, gc):
|
||||
"""
|
||||
Show non-page areas in grey.
|
||||
@@ -489,17 +467,6 @@ class pdfViewer(wx.ScrolledWindow):
|
||||
if extraheight > 0:
|
||||
gc.DrawRectangle(0, self.winheight-extraheight, self.maxwidth, extraheight)
|
||||
|
||||
def CachePage(self, pageno):
|
||||
"""
|
||||
When page takes a 'long' time to render, save its contents out of
|
||||
self.pdc and re-use it to minimise jerky scrolling
|
||||
"""
|
||||
cachebuffer = wx.Bitmap(self.Xpagepixels, self.Ypagepixels)
|
||||
cdc = wx.MemoryDC(cachebuffer)
|
||||
cdc.Blit(0, 0, self.Xpagepixels, self.Ypagepixels,
|
||||
self.pdc, self.xpageoffset, self.ypageoffset)
|
||||
self.cachedpages[pageno] = cdc
|
||||
|
||||
#============================================================================
|
||||
|
||||
class mupdfProcessor(object):
|
||||
@@ -641,6 +608,9 @@ class pypdfProcessor(object):
|
||||
'CreatePath': gc.CreatePath,
|
||||
'DrawPath': gc.DrawPath }
|
||||
for drawcmd, args, kwargs in self.pagedrawings[pageno]:
|
||||
# scale font if requested by printer DC
|
||||
if drawcmd == 'SetFont' and hasattr(gc, 'font_scale'):
|
||||
args[0].Scale(gc.font_scale)
|
||||
if drawcmd == 'ConcatTransform':
|
||||
cm = gc.CreateMatrix(*args, **kwargs)
|
||||
args = (cm,)
|
||||
@@ -651,6 +621,9 @@ class pypdfProcessor(object):
|
||||
args = (gp, args[1])
|
||||
if drawcmd in drawdict:
|
||||
drawdict[drawcmd](*args, **kwargs)
|
||||
# reset font scaling in case RenderPage call is repeated
|
||||
if drawcmd == 'SetFont' and hasattr(gc, 'font_scale'):
|
||||
args[0].Scale(1.0/gc.font_scale)
|
||||
else:
|
||||
pathdict = {'MoveToPoint': gp.MoveToPoint,
|
||||
'AddLineToPoint': gp.AddLineToPoint,
|
||||
@@ -824,8 +797,10 @@ class pypdfProcessor(object):
|
||||
"""
|
||||
dlist = []
|
||||
g = self.gstate
|
||||
f0 = self.SetFont(g.font, g.fontSize*self.parent.font_scale_metrics)
|
||||
f1 = self.SetFont(g.font, g.fontSize*self.parent.font_scale_size)
|
||||
f0 = self.SetFont(g.font, g.fontSize)
|
||||
f0.Scale(self.parent.font_scale_metrics)
|
||||
f1 = self.SetFont(g.font, g.fontSize)
|
||||
f1.Scale(self.parent.font_scale_size)
|
||||
dlist.append(['SetFont', (f1, g.fillRGB), {}])
|
||||
if g.wordSpacing > 0:
|
||||
textlist = text.split(' ')
|
||||
@@ -1078,54 +1053,25 @@ class pdfPrintout(wx.Printout):
|
||||
def OnPrintPage(self, page):
|
||||
"""
|
||||
Provide the data for page by rendering the drawing commands
|
||||
to the printer DC either via a page buffer or directly using a
|
||||
dcGraphicsContext depending on self.view.usePrintDirect
|
||||
"""
|
||||
if not mupdf and self.view.UsePrintDirect:
|
||||
self.PrintDirect(page)
|
||||
else:
|
||||
self.PrintViaBuffer(page)
|
||||
return True
|
||||
|
||||
def PrintDirect(self, page):
|
||||
"""
|
||||
Provide the data for page by rendering the drawing commands
|
||||
to the printer DC using :class:`~wx.lib.pdfviewer.dcgraphics.dcGraphicsContext`.
|
||||
to the printer DC, MuPDF returns the page content from an internally
|
||||
generated bitmap and sfac sets it to a high enough resolution that
|
||||
reduces anti-aliasing blur but keeps it small to minimise printing time
|
||||
"""
|
||||
sfac = 1.0
|
||||
if mupdf:
|
||||
sfac = 4.0
|
||||
pageno = page - 1 # zero based
|
||||
width = self.view.pagewidth
|
||||
height = self.view.pageheight
|
||||
self.FitThisSizeToPage(wx.Size(width, height))
|
||||
self.FitThisSizeToPage(wx.Size(width*sfac, height*sfac))
|
||||
dc = self.GetDC()
|
||||
gc = dcGraphicsContext.Create(dc, height, have_cairo)
|
||||
self.view.pdfdoc.RenderPage(gc, pageno)
|
||||
|
||||
def PrintViaBuffer(self, page):
|
||||
"""
|
||||
Provide the data for page by drawing it as a bitmap to the printer DC
|
||||
sfac needs to provide a high enough resolution bitmap for printing that
|
||||
reduces anti-aliasing blur but be kept small to minimise printing time
|
||||
"""
|
||||
sfac = 4.0
|
||||
pageno = page - 1 # zero based
|
||||
dc = self.GetDC()
|
||||
width = self.view.pagewidth*sfac
|
||||
height = self.view.pageheight*sfac
|
||||
self.FitThisSizeToPage(wx.Size(width, height))
|
||||
# Initialize the buffer bitmap.
|
||||
abuffer = wx.Bitmap(width, height)
|
||||
mdc = wx.MemoryDC(abuffer)
|
||||
gc = GraphicsContext.Create(mdc)
|
||||
# white background
|
||||
path = gc.CreatePath()
|
||||
path.AddRectangle(0, 0, width, height)
|
||||
gc.SetBrush(wx.WHITE_BRUSH)
|
||||
gc.FillPath(path)
|
||||
if mupdf:
|
||||
self.view.pdfdoc.RenderPage(gc, pageno, sfac)
|
||||
else:
|
||||
gc = wx.GraphicsContext.Create(dc)
|
||||
if not mupdf:
|
||||
gc.Translate(0, height)
|
||||
gc.Scale(sfac, sfac)
|
||||
self.view.pdfdoc.RenderPage(gc, pageno)
|
||||
dc.DrawBitmap(abuffer, 0, 0)
|
||||
if wx.PlatformInfo[1] == 'wxMSW' and have_cairo:
|
||||
device_scale = wx.ClientDC(self.view).GetPPI()[0]/72.0 # pixels per inch/ppi
|
||||
gc.font_scale = 1.0 / device_scale
|
||||
|
||||
self.view.pdfdoc.RenderPage(gc, pageno, sfac)
|
||||
return True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user