mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 18:24:30 -04:00
Fix graphics state handling
This commit is contained in:
parent
8b7eda245e
commit
a34799a284
@ -14,7 +14,7 @@ from functools import wraps
|
|||||||
|
|
||||||
from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter,
|
from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter,
|
||||||
QTransform, QPainterPath, QTextOption, QTextLayout,
|
QTransform, QPainterPath, QTextOption, QTextLayout,
|
||||||
QImage, QByteArray, QBuffer, qRgba, QRectF)
|
QImage, QByteArray, QBuffer, qRgba)
|
||||||
|
|
||||||
from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path)
|
from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path)
|
||||||
from calibre.ebooks.pdf.render.common import inch, A4
|
from calibre.ebooks.pdf.render.common import inch, A4
|
||||||
@ -40,7 +40,7 @@ class GraphicsState(object): # {{{
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.ops = {}
|
self.ops = {}
|
||||||
self.current_state = self.initial_state = {
|
self.initial_state = {
|
||||||
'fill': ColorState(Color(0., 0., 0., 1.), 1.0, False),
|
'fill': ColorState(Color(0., 0., 0., 1.), 1.0, False),
|
||||||
'transform': QTransform(),
|
'transform': QTransform(),
|
||||||
'dash': [],
|
'dash': [],
|
||||||
@ -50,9 +50,10 @@ class GraphicsState(object): # {{{
|
|||||||
'line_join': 'miter',
|
'line_join': 'miter',
|
||||||
'clip': (Qt.NoClip, QPainterPath()),
|
'clip': (Qt.NoClip, QPainterPath()),
|
||||||
}
|
}
|
||||||
|
self.current_state = self.initial_state.copy()
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.current_state = self.initial_state
|
self.current_state = self.initial_state.copy()
|
||||||
|
|
||||||
def update_color_state(self, which, color=None, opacity=None,
|
def update_color_state(self, which, color=None, opacity=None,
|
||||||
brush_style=None, pen_style=None):
|
brush_style=None, pen_style=None):
|
||||||
@ -75,7 +76,6 @@ class GraphicsState(object): # {{{
|
|||||||
self.ops[which] = n
|
self.ops[which] = n
|
||||||
|
|
||||||
def read(self, state):
|
def read(self, state):
|
||||||
self.ops = {}
|
|
||||||
flags = state.state()
|
flags = state.state()
|
||||||
|
|
||||||
if flags & QPaintEngine.DirtyTransform:
|
if flags & QPaintEngine.DirtyTransform:
|
||||||
@ -107,15 +107,12 @@ class GraphicsState(object): # {{{
|
|||||||
self.update_color_state('fill', opacity=state.opacity())
|
self.update_color_state('fill', opacity=state.opacity())
|
||||||
self.update_color_state('stroke', opacity=state.opacity())
|
self.update_color_state('stroke', opacity=state.opacity())
|
||||||
|
|
||||||
if flags & QPaintEngine.DirtyClipPath:
|
if flags & QPaintEngine.DirtyClipPath or flags & QPaintEngine.DirtyClipRegion:
|
||||||
self.ops['clip'] = (state.clipOperation(), state.clipPath())
|
self.ops['clip'] = True
|
||||||
elif flags & QPaintEngine.DirtyClipRegion:
|
|
||||||
path = QPainterPath()
|
|
||||||
for rect in state.clipRegion().rects():
|
|
||||||
path.addRect(QRectF(rect))
|
|
||||||
self.ops['clip'] = (state.clipOperation(), path)
|
|
||||||
|
|
||||||
def __call__(self, engine):
|
def __call__(self, engine):
|
||||||
|
if not self.ops:
|
||||||
|
return
|
||||||
pdf = engine.pdf
|
pdf = engine.pdf
|
||||||
ops = self.ops
|
ops = self.ops
|
||||||
current_transform = self.current_state['transform']
|
current_transform = self.current_state['transform']
|
||||||
@ -125,58 +122,34 @@ class GraphicsState(object): # {{{
|
|||||||
if reset_stack:
|
if reset_stack:
|
||||||
pdf.restore_stack()
|
pdf.restore_stack()
|
||||||
pdf.save_stack()
|
pdf.save_stack()
|
||||||
|
|
||||||
# We apply clip before transform as the clip may have to be merged with
|
|
||||||
# the previous clip path so it is easiest to work with clips that are
|
|
||||||
# pre-transformed
|
|
||||||
prev_op, prev_clip_path = self.current_state['clip']
|
|
||||||
if 'clip' in ops:
|
|
||||||
op, path = ops['clip']
|
|
||||||
self.current_state['clip'] = (op, path)
|
|
||||||
transform = ops.get('transform', QTransform())
|
|
||||||
if not transform.isIdentity() and path is not None:
|
|
||||||
# Pre transform the clip path
|
|
||||||
path = current_transform.map(path)
|
|
||||||
self.current_state['clip'] = (op, path)
|
|
||||||
|
|
||||||
if op == Qt.ReplaceClip:
|
|
||||||
pass
|
|
||||||
elif op == Qt.IntersectClip:
|
|
||||||
if prev_op != Qt.NoClip:
|
|
||||||
self.current_state['clip'] = (op, path.intersected(prev_clip_path))
|
|
||||||
elif op == Qt.UniteClip:
|
|
||||||
if prev_clip_path is not None:
|
|
||||||
path.addPath(prev_clip_path)
|
|
||||||
else:
|
|
||||||
self.current_state['clip'] = (Qt.NoClip, QPainterPath())
|
|
||||||
op, path = self.current_state['clip']
|
|
||||||
if op != Qt.NoClip:
|
|
||||||
engine.add_clip(path)
|
|
||||||
elif reset_stack and prev_op != Qt.NoClip:
|
|
||||||
# Re-apply the previous clip path since no clipping operation was
|
|
||||||
# specified
|
|
||||||
engine.add_clip(prev_clip_path)
|
|
||||||
|
|
||||||
if reset_stack:
|
|
||||||
# Since we have reset the stack we need to re-apply all previous
|
# Since we have reset the stack we need to re-apply all previous
|
||||||
# operations, that are different from the default value (clip is
|
# operations, that are different from the default value (clip is
|
||||||
# handled separately).
|
# handled separately).
|
||||||
for op in set(self.current_state) - (set(ops)|{'clip'}):
|
for op in set(self.initial_state) - {'clip'}:
|
||||||
if self.current_state[op] != self.initial_state[op]:
|
if op in ops: # These will be applied below
|
||||||
|
self.current_state[op] = self.initial_state[op]
|
||||||
|
elif self.current_state[op] != self.initial_state[op]:
|
||||||
self.apply(op, self.current_state[op], engine, pdf)
|
self.apply(op, self.current_state[op], engine, pdf)
|
||||||
|
|
||||||
# Now apply the new operations
|
# Now apply the new operations
|
||||||
for op, val in ops.iteritems():
|
for op, val in ops.iteritems():
|
||||||
if op != 'clip':
|
if op != 'clip' and self.current_state[op] != val:
|
||||||
self.apply(op, val, engine, pdf)
|
self.apply(op, val, engine, pdf)
|
||||||
self.current_state[op] = val
|
self.current_state[op] = val
|
||||||
|
|
||||||
|
if 'clip' in ops:
|
||||||
|
# Get the current clip
|
||||||
|
path = engine.painter().clipPath()
|
||||||
|
if not path.isEmpty():
|
||||||
|
engine.add_clip(path)
|
||||||
|
self.ops = {}
|
||||||
|
|
||||||
def apply(self, op, val, engine, pdf):
|
def apply(self, op, val, engine, pdf):
|
||||||
getattr(self, 'apply_'+op)(val, engine, pdf)
|
getattr(self, 'apply_'+op)(val, engine, pdf)
|
||||||
|
|
||||||
def apply_transform(self, val, engine, pdf):
|
def apply_transform(self, val, engine, pdf):
|
||||||
engine.qt_system = val
|
if not val.isIdentity():
|
||||||
pdf.transform(val)
|
pdf.transform(val)
|
||||||
|
|
||||||
def apply_stroke(self, val, engine, pdf):
|
def apply_stroke(self, val, engine, pdf):
|
||||||
self.apply_color_state('stroke', val, engine, pdf)
|
self.apply_color_state('stroke', val, engine, pdf)
|
||||||
@ -235,7 +208,6 @@ class PdfEngine(QPaintEngine):
|
|||||||
self.bottom_margin) / self.pixel_height
|
self.bottom_margin) / self.pixel_height
|
||||||
|
|
||||||
self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy)
|
self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy)
|
||||||
self.qt_system = QTransform()
|
|
||||||
self.do_stroke = True
|
self.do_stroke = True
|
||||||
self.do_fill = False
|
self.do_fill = False
|
||||||
self.scale = sqrt(sy**2 + sx**2)
|
self.scale = sqrt(sy**2 + sx**2)
|
||||||
@ -250,6 +222,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
i.fill(qRgba(0, 0, 0, 255))
|
i.fill(qRgba(0, 0, 0, 255))
|
||||||
self.alpha_bit = i.constBits().asstring(4).find(b'\xff')
|
self.alpha_bit = i.constBits().asstring(4).find(b'\xff')
|
||||||
self.current_page_num = 1
|
self.current_page_num = 1
|
||||||
|
self.current_page_inited = False
|
||||||
|
|
||||||
def init_page(self):
|
def init_page(self):
|
||||||
self.pdf.transform(self.pdf_system)
|
self.pdf.transform(self.pdf_system)
|
||||||
@ -260,6 +233,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
self.do_fill = False
|
self.do_fill = False
|
||||||
self.graphics_state.reset()
|
self.graphics_state.reset()
|
||||||
self.pdf.save_stack()
|
self.pdf.save_stack()
|
||||||
|
self.current_page_inited = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def features(self):
|
def features(self):
|
||||||
@ -269,26 +243,26 @@ class PdfEngine(QPaintEngine):
|
|||||||
QPaintEngine.PrimitiveTransform)
|
QPaintEngine.PrimitiveTransform)
|
||||||
|
|
||||||
def begin(self, device):
|
def begin(self, device):
|
||||||
try:
|
if not hasattr(self, 'pdf'):
|
||||||
self.pdf = PDFStream(self.file_object, (self.page_width,
|
try:
|
||||||
self.page_height),
|
self.pdf = PDFStream(self.file_object, (self.page_width,
|
||||||
compress=self.compress)
|
self.page_height),
|
||||||
self.init_page()
|
compress=self.compress)
|
||||||
except:
|
except:
|
||||||
self.errors.append(traceback.format_exc())
|
self.errors.append(traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def end_page(self, start_new=True):
|
def end_page(self):
|
||||||
self.pdf.restore_stack()
|
if self.current_page_inited:
|
||||||
self.pdf.end_page()
|
self.pdf.restore_stack()
|
||||||
self.current_page_num += 1
|
self.pdf.end_page()
|
||||||
if start_new:
|
self.current_page_inited = False
|
||||||
self.init_page()
|
self.current_page_num += 1
|
||||||
|
|
||||||
def end(self):
|
def end(self):
|
||||||
try:
|
try:
|
||||||
self.end_page(start_new=False)
|
self.end_page()
|
||||||
self.pdf.end()
|
self.pdf.end()
|
||||||
except:
|
except:
|
||||||
self.errors.append(traceback.format_exc())
|
self.errors.append(traceback.format_exc())
|
||||||
@ -302,6 +276,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawPixmap(self, rect, pixmap, source_rect):
|
def drawPixmap(self, rect, pixmap, source_rect):
|
||||||
|
self.graphics_state(self)
|
||||||
source_rect = source_rect.toRect()
|
source_rect = source_rect.toRect()
|
||||||
pixmap = (pixmap if source_rect == pixmap.rect() else
|
pixmap = (pixmap if source_rect == pixmap.rect() else
|
||||||
pixmap.copy(source_rect))
|
pixmap.copy(source_rect))
|
||||||
@ -313,6 +288,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawImage(self, rect, image, source_rect, flags=Qt.AutoColor):
|
def drawImage(self, rect, image, source_rect, flags=Qt.AutoColor):
|
||||||
|
self.graphics_state(self)
|
||||||
source_rect = source_rect.toRect()
|
source_rect = source_rect.toRect()
|
||||||
image = (image if source_rect == image.rect() else
|
image = (image if source_rect == image.rect() else
|
||||||
image.copy(source_rect))
|
image.copy(source_rect))
|
||||||
@ -388,7 +364,6 @@ class PdfEngine(QPaintEngine):
|
|||||||
@store_error
|
@store_error
|
||||||
def updateState(self, state):
|
def updateState(self, state):
|
||||||
self.graphics_state.read(state)
|
self.graphics_state.read(state)
|
||||||
self.graphics_state(self)
|
|
||||||
|
|
||||||
def convert_path(self, path):
|
def convert_path(self, path):
|
||||||
p = Path()
|
p = Path()
|
||||||
@ -416,6 +391,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawPath(self, path):
|
def drawPath(self, path):
|
||||||
|
self.graphics_state(self)
|
||||||
p = self.convert_path(path)
|
p = self.convert_path(path)
|
||||||
fill_rule = {Qt.OddEvenFill:'evenodd',
|
fill_rule = {Qt.OddEvenFill:'evenodd',
|
||||||
Qt.WindingFill:'winding'}[path.fillRule()]
|
Qt.WindingFill:'winding'}[path.fillRule()]
|
||||||
@ -430,6 +406,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawPoints(self, points):
|
def drawPoints(self, points):
|
||||||
|
self.graphics_state(self)
|
||||||
p = Path()
|
p = Path()
|
||||||
for point in points:
|
for point in points:
|
||||||
p.move_to(point.x(), point.y())
|
p.move_to(point.x(), point.y())
|
||||||
@ -438,6 +415,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawRects(self, rects):
|
def drawRects(self, rects):
|
||||||
|
self.graphics_state(self)
|
||||||
for rect in rects:
|
for rect in rects:
|
||||||
bl = rect.topLeft()
|
bl = rect.topLeft()
|
||||||
self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(),
|
self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(),
|
||||||
@ -500,7 +478,8 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawTextItem(self, point, text_item):
|
def drawTextItem(self, point, text_item):
|
||||||
# super(PdfEngine, self).drawTextItem(point+QPoint(0, 0), text_item)
|
# super(PdfEngine, self).drawTextItem(point, text_item)
|
||||||
|
self.graphics_state(self)
|
||||||
text = type(u'')(text_item.text()).replace('\n', ' ')
|
text = type(u'')(text_item.text()).replace('\n', ' ')
|
||||||
text = unicodedata.normalize('NFKC', text)
|
text = unicodedata.normalize('NFKC', text)
|
||||||
tl = self.get_text_layout(text_item, text)
|
tl = self.get_text_layout(text_item, text)
|
||||||
@ -537,6 +516,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawPolygon(self, points, mode):
|
def drawPolygon(self, points, mode):
|
||||||
|
self.graphics_state(self)
|
||||||
if not points: return
|
if not points: return
|
||||||
p = Path()
|
p = Path()
|
||||||
p.move_to(points[0].x(), points[0].y())
|
p.move_to(points[0].x(), points[0].y())
|
||||||
@ -597,8 +577,8 @@ class PdfDevice(QPaintDevice): # {{{
|
|||||||
return int(round(self.body_height * self.ydpi / 72.0))
|
return int(round(self.body_height * self.ydpi / 72.0))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def end_page(self, start_new=True):
|
def end_page(self):
|
||||||
self.engine.end_page(start_new=start_new)
|
self.engine.end_page()
|
||||||
|
|
||||||
def init_page(self):
|
def init_page(self):
|
||||||
self.engine.init_page()
|
self.engine.init_page()
|
||||||
@ -628,6 +608,7 @@ if __name__ == '__main__':
|
|||||||
with open('/tmp/painter.pdf', 'wb') as f:
|
with open('/tmp/painter.pdf', 'wb') as f:
|
||||||
dev = PdfDevice(f, compress=False)
|
dev = PdfDevice(f, compress=False)
|
||||||
p.begin(dev)
|
p.begin(dev)
|
||||||
|
dev.init_page()
|
||||||
xmax, ymax = p.viewport().width(), p.viewport().height()
|
xmax, ymax = p.viewport().width(), p.viewport().height()
|
||||||
try:
|
try:
|
||||||
p.drawRect(0, 0, xmax, ymax)
|
p.drawRect(0, 0, xmax, ymax)
|
||||||
@ -636,21 +617,21 @@ if __name__ == '__main__':
|
|||||||
# pp = QPainterPath()
|
# pp = QPainterPath()
|
||||||
# pp.addRect(0, 0, xmax, ymax)
|
# pp.addRect(0, 0, xmax, ymax)
|
||||||
# p.drawPath(pp)
|
# p.drawPath(pp)
|
||||||
p.save()
|
# p.save()
|
||||||
for i in xrange(3):
|
# for i in xrange(3):
|
||||||
col = [0, 0, 0, 200]
|
# col = [0, 0, 0, 200]
|
||||||
col[i] = 255
|
# col[i] = 255
|
||||||
p.setOpacity(0.3)
|
# p.setOpacity(0.3)
|
||||||
p.setBrush(QBrush(QColor(*col)))
|
# p.setBrush(QBrush(QColor(*col)))
|
||||||
p.drawRect(0, 0, xmax/10, xmax/10)
|
# p.drawRect(0, 0, xmax/10, xmax/10)
|
||||||
p.translate(xmax/10, xmax/10)
|
# p.translate(xmax/10, xmax/10)
|
||||||
p.scale(1, 1.5)
|
# p.scale(1, 1.5)
|
||||||
p.restore()
|
# p.restore()
|
||||||
|
|
||||||
# p.scale(2, 2)
|
# # p.scale(2, 2)
|
||||||
# p.rotate(45)
|
# # p.rotate(45)
|
||||||
p.drawPixmap(0, 0, 2048, 2048, QPixmap(I('library.png')))
|
# p.drawPixmap(0, 0, 2048, 2048, QPixmap(I('library.png')))
|
||||||
p.drawRect(0, 0, 2048, 2048)
|
# p.drawRect(0, 0, 2048, 2048)
|
||||||
|
|
||||||
# p.save()
|
# p.save()
|
||||||
# p.drawLine(0, 0, 5000, 0)
|
# p.drawLine(0, 0, 5000, 0)
|
||||||
@ -658,18 +639,18 @@ if __name__ == '__main__':
|
|||||||
# p.drawLine(0, 0, 5000, 0)
|
# p.drawLine(0, 0, 5000, 0)
|
||||||
# p.restore()
|
# p.restore()
|
||||||
|
|
||||||
# f = p.font()
|
f = p.font()
|
||||||
# f.setPointSize(24)
|
f.setPointSize(20)
|
||||||
# f.setLetterSpacing(f.PercentageSpacing, 200)
|
# f.setLetterSpacing(f.PercentageSpacing, 200)
|
||||||
# f.setUnderline(True)
|
# f.setUnderline(True)
|
||||||
# f.setOverline(True)
|
# f.setOverline(True)
|
||||||
# f.setStrikeOut(True)
|
# f.setStrikeOut(True)
|
||||||
# f.setFamily('Calibri')
|
f.setFamily('DejaVu Sans')
|
||||||
# p.setFont(f)
|
p.setFont(f)
|
||||||
# p.setPen(QColor(0, 0, 255))
|
# p.setPen(QColor(0, 0, 255))
|
||||||
# p.scale(2, 2)
|
# p.scale(2, 2)
|
||||||
# p.rotate(45)
|
# p.rotate(45)
|
||||||
# p.drawText(QPoint(100, 300), 'Some text ū --- Д AV ff ff')
|
p.drawText(QPoint(0, 300), 'Some—text not By’s ū --- Д AV ff ff')
|
||||||
finally:
|
finally:
|
||||||
p.end()
|
p.end()
|
||||||
if dev.engine.errors_occurred:
|
if dev.engine.errors_occurred:
|
||||||
|
@ -160,7 +160,6 @@ class PDFWriter(QObject):
|
|||||||
self.page.setViewportSize(QSize(self.doc.width(), self.doc.height()))
|
self.page.setViewportSize(QSize(self.doc.width(), self.doc.height()))
|
||||||
self.render_queue = items
|
self.render_queue = items
|
||||||
self.total_items = len(items)
|
self.total_items = len(items)
|
||||||
self.first_page = True
|
|
||||||
|
|
||||||
# TODO: Test margins
|
# TODO: Test margins
|
||||||
mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom))
|
mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom))
|
||||||
@ -260,20 +259,18 @@ class PDFWriter(QObject):
|
|||||||
|
|
||||||
mf = self.view.page().mainFrame()
|
mf = self.view.page().mainFrame()
|
||||||
start_page = self.current_page_num
|
start_page = self.current_page_num
|
||||||
|
dx = 0
|
||||||
while True:
|
while True:
|
||||||
if not self.first_page:
|
self.doc.init_page()
|
||||||
self.doc.init_page()
|
|
||||||
self.first_page = False
|
|
||||||
self.painter.save()
|
self.painter.save()
|
||||||
try:
|
mf.render(self.painter)
|
||||||
mf.render(self.painter)
|
self.painter.restore()
|
||||||
nsl = evaljs('paged_display.next_screen_location()').toInt()
|
nsl = evaljs('paged_display.next_screen_location()').toInt()
|
||||||
if not nsl[1] or nsl[0] <= 0:
|
self.doc.end_page()
|
||||||
break
|
if not nsl[1] or nsl[0] <= 0:
|
||||||
evaljs('window.scrollTo(%d, 0)'%nsl[0])
|
break
|
||||||
self.doc.end_page()
|
dx = nsl[0]
|
||||||
finally:
|
evaljs('window.scrollTo(%d, 0)'%dx)
|
||||||
self.painter.restore()
|
|
||||||
if self.doc.errors_occurred:
|
if self.doc.errors_occurred:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user