def rotate_image(self): pm = self.label.pixmap() t = QTransform() t.rotate(90) pm = pm.transformed(t) self.label.setPixmap(pm) self.label.adjustSize()
def __init__(self, file_object, page_width, page_height, left_margin, top_margin, right_margin, bottom_margin, width, height, errors=print, debug=print, compress=True, mark_links=False): QPaintEngine.__init__(self, self.FEATURES) self.file_object = file_object self.compress, self.mark_links = compress, mark_links self.page_height, self.page_width = page_height, page_width self.left_margin, self.top_margin = left_margin, top_margin self.right_margin, self.bottom_margin = right_margin, bottom_margin self.pixel_width, self.pixel_height = width, height # Setup a co-ordinate transform that allows us to use co-ords # from Qt's pixel based co-ordinate system with its origin at the top # left corner. PDF's co-ordinate system is based on pts and has its # origin in the bottom left corner. We also have to implement the page # margins. Therefore, we need to translate, scale and reflect about the # x-axis. dy = self.page_height - self.top_margin dx = self.left_margin sx = (self.page_width - self.left_margin - self.right_margin) / self.pixel_width sy = (self.page_height - self.top_margin - self.bottom_margin) / self.pixel_height self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy) self.graphics = Graphics(self.pixel_width, self.pixel_height) self.errors_occurred = False self.errors, self.debug = errors, debug self.fonts = {} self.current_page_num = 1 self.current_page_inited = False self.qt_hack, err = plugins['qt_hack'] if err: raise RuntimeError('Failed to load qt_hack with err: %s'%err)
def resolve_fill(self, rect, pdf_system, qt_system): ''' Qt's paint system does not update brushOrigin when using TexturePatterns and it also uses TexturePatterns to emulate gradients, leading to brokenness. So this method allows the paint engine to update the brush origin before painting an object. While not perfect, this is better than nothing. The problem is that if the rect being filled has a border, then QtWebKit generates an image of the rect size - border but fills the full rect, and there's no way for the paint engine to know that and adjust the brush origin. ''' if not hasattr(self, 'last_fill') or not self.current_state.do_fill: return if isinstance(self.last_fill.brush, TexturePattern): tl = rect.topLeft() if tl == self.last_fill.origin: return matrix = (QTransform.fromTranslate(tl.x(), tl.y()) * pdf_system * qt_system.inverted()[0]) pat = TexturePattern(None, matrix, self.pdf, clone=self.last_fill.brush) pattern = self.pdf.add_pattern(pat) self.pdf.apply_fill(self.last_fill.color, pattern)
def copy(self): ans = GraphicsState() ans.fill = QBrush(self.fill) ans.stroke = QPen(self.stroke) ans.opacity = self.opacity ans.transform = self.transform * QTransform() ans.brush_origin = QPointF(self.brush_origin) ans.clip_updated = self.clip_updated ans.do_fill, ans.do_stroke = self.do_fill, self.do_stroke return ans
def __init__(self): self.fill = QBrush(Qt.white) self.stroke = QPen() self.opacity = 1.0 self.transform = QTransform() self.brush_origin = QPointF() self.clip_updated = False self.do_fill = False self.do_stroke = True self.qt_pattern_cache = {}
def convert_brush(self, brush, brush_origin, global_opacity, pdf_system, qt_system): # Convert a QBrush to PDF operators style = brush.style() pdf = self.pdf pattern = color = pat = None opacity = global_opacity do_fill = True matrix = ( QTransform.fromTranslate(brush_origin.x(), brush_origin.y()) * pdf_system * qt_system.inverted()[0]) vals = list(brush.color().getRgbF()) self.brushobj = None if style <= Qt.DiagCrossPattern: opacity *= vals[-1] color = vals[:3] if style > Qt.SolidPattern: pat = QtPattern(style, matrix) elif style == Qt.TexturePattern: pat = TexturePattern(brush.texture(), matrix, pdf) if pat.paint_type == 2: opacity *= vals[-1] color = vals[:3] elif style == Qt.LinearGradientPattern: pat = LinearGradientPattern(brush, matrix, pdf, self.page_width_px, self.page_height_px) opacity *= pat.const_opacity # TODO: Add support for radial/conical gradient fills if opacity < 1e-4 or style == Qt.NoBrush: do_fill = False self.brushobj = Brush(brush_origin, pat, color) if pat is not None: pattern = pdf.add_pattern(pat) return color, opacity, pattern, do_fill
def convert_brush(self, brush, brush_origin, global_opacity, pdf_system, qt_system): # Convert a QBrush to PDF operators style = brush.style() pdf = self.pdf pattern = color = pat = None opacity = global_opacity do_fill = True matrix = (QTransform.fromTranslate(brush_origin.x(), brush_origin.y()) * pdf_system * qt_system.inverted()[0]) vals = list(brush.color().getRgbF()) self.brushobj = None if style <= Qt.DiagCrossPattern: opacity *= vals[-1] color = vals[:3] if style > Qt.SolidPattern: pat = QtPattern(style, matrix) elif style == Qt.TexturePattern: pat = TexturePattern(brush.texture(), matrix, pdf) if pat.paint_type == 2: opacity *= vals[-1] color = vals[:3] elif style == Qt.LinearGradientPattern: pat = LinearGradientPattern(brush, matrix, pdf, self.page_width_px, self.page_height_px) opacity *= pat.const_opacity # TODO: Add support for radial/conical gradient fills if opacity < 1e-4 or style == Qt.NoBrush: do_fill = False self.brushobj = Brush(brush_origin, pat, color) if pat is not None: pattern = pdf.add_pattern(pat) return color, opacity, pattern, do_fill
class PdfEngine(QPaintEngine): FEATURES = QPaintEngine.AllFeatures & ~( QPaintEngine.PorterDuff | QPaintEngine.PerspectiveTransform | QPaintEngine.ObjectBoundingModeGradients | QPaintEngine.RadialGradientFill | QPaintEngine.ConicalGradientFill ) def __init__(self, file_object, page_width, page_height, left_margin, top_margin, right_margin, bottom_margin, width, height, errors=print, debug=print, compress=True, mark_links=False): QPaintEngine.__init__(self, self.FEATURES) self.file_object = file_object self.compress, self.mark_links = compress, mark_links self.page_height, self.page_width = page_height, page_width self.left_margin, self.top_margin = left_margin, top_margin self.right_margin, self.bottom_margin = right_margin, bottom_margin self.pixel_width, self.pixel_height = width, height # Setup a co-ordinate transform that allows us to use co-ords # from Qt's pixel based co-ordinate system with its origin at the top # left corner. PDF's co-ordinate system is based on pts and has its # origin in the bottom left corner. We also have to implement the page # margins. Therefore, we need to translate, scale and reflect about the # x-axis. dy = self.page_height - self.top_margin dx = self.left_margin sx = (self.page_width - self.left_margin - self.right_margin) / self.pixel_width sy = (self.page_height - self.top_margin - self.bottom_margin) / self.pixel_height self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy) self.graphics = Graphics(self.pixel_width, self.pixel_height) self.errors_occurred = False self.errors, self.debug = errors, debug self.fonts = {} self.current_page_num = 1 self.current_page_inited = False self.qt_hack, err = plugins['qt_hack'] if err: raise RuntimeError('Failed to load qt_hack with err: %s'%err) def apply_graphics_state(self): self.graphics(self.pdf_system, self.painter()) def resolve_fill(self, rect): self.graphics.resolve_fill(rect, self.pdf_system, self.painter().transform()) @property def do_fill(self): return self.graphics.current_state.do_fill @property def do_stroke(self): return self.graphics.current_state.do_stroke def init_page(self): self.pdf.transform(self.pdf_system) self.pdf.apply_fill(color=(1, 1, 1)) # QPainter has a default background brush of white self.graphics.reset() self.pdf.save_stack() self.current_page_inited = True def begin(self, device): if not hasattr(self, 'pdf'): try: self.pdf = PDFStream(self.file_object, (self.page_width, self.page_height), compress=self.compress, mark_links=self.mark_links, debug=self.debug) self.graphics.begin(self.pdf) except: self.errors(traceback.format_exc()) self.errors_occurred = True return False return True def end_page(self): if self.current_page_inited: self.pdf.restore_stack() self.pdf.end_page() self.current_page_inited = False self.current_page_num += 1 def end(self): try: self.end_page() self.pdf.end() except: self.errors(traceback.format_exc()) self.errors_occurred = True return False finally: self.pdf = self.file_object = None return True def type(self): return QPaintEngine.Pdf def add_image(self, img, cache_key): if img.isNull(): return return self.pdf.add_image(img, cache_key) @store_error def drawTiledPixmap(self, rect, pixmap, point): self.apply_graphics_state() brush = QBrush(pixmap) bl = rect.topLeft() color, opacity, pattern, do_fill = self.graphics.convert_brush( brush, bl-point, 1.0, self.pdf_system, self.painter().transform()) self.pdf.save_stack() self.pdf.apply_fill(color, pattern) self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(), stroke=False, fill=True) self.pdf.restore_stack() @store_error def drawPixmap(self, rect, pixmap, source_rect): self.apply_graphics_state() source_rect = source_rect.toRect() pixmap = (pixmap if source_rect == pixmap.rect() else pixmap.copy(source_rect)) image = pixmap.toImage() ref = self.add_image(image, pixmap.cacheKey()) if ref is not None: self.pdf.draw_image(rect.x(), rect.y(), rect.width(), rect.height(), ref) @store_error def drawImage(self, rect, image, source_rect, flags=Qt.AutoColor): self.apply_graphics_state() source_rect = source_rect.toRect() image = (image if source_rect == image.rect() else image.copy(source_rect)) ref = self.add_image(image, image.cacheKey()) if ref is not None: self.pdf.draw_image(rect.x(), rect.y(), rect.width(), rect.height(), ref) @store_error def updateState(self, state): self.graphics.update_state(state, self.painter()) @store_error def drawPath(self, path): self.apply_graphics_state() p = convert_path(path) fill_rule = {Qt.OddEvenFill:'evenodd', Qt.WindingFill:'winding'}[path.fillRule()] self.pdf.draw_path(p, stroke=self.do_stroke, fill=self.do_fill, fill_rule=fill_rule) @store_error def drawPoints(self, points): self.apply_graphics_state() p = Path() for point in points: p.move_to(point.x(), point.y()) p.line_to(point.x(), point.y() + 0.001) self.pdf.draw_path(p, stroke=self.do_stroke, fill=False) @store_error def drawRects(self, rects): self.apply_graphics_state() with self.graphics: for rect in rects: self.resolve_fill(rect) bl = rect.topLeft() self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(), stroke=self.do_stroke, fill=self.do_fill) def create_sfnt(self, text_item): get_table = partial(self.qt_hack.get_sfnt_table, text_item) try: ans = Font(Sfnt(get_table)) except UnsupportedFont as e: raise UnsupportedFont('The font %s is not a valid sfnt. Error: %s'%( text_item.font().family(), e)) glyph_map = self.qt_hack.get_glyph_map(text_item) gm = {} for uc, glyph_id in enumerate(glyph_map): if glyph_id not in gm: gm[glyph_id] = unichr(uc) ans.full_glyph_map = gm return ans @store_error def drawTextItem(self, point, text_item): # return super(PdfEngine, self).drawTextItem(point, text_item) self.apply_graphics_state() gi = self.qt_hack.get_glyphs(point, text_item) if not gi.indices: sip.delete(gi) return name = hash(bytes(gi.name)) if name not in self.fonts: try: self.fonts[name] = self.create_sfnt(text_item) except UnsupportedFont: return super(PdfEngine, self).drawTextItem(point, text_item) metrics = self.fonts[name] for glyph_id in gi.indices: try: metrics.glyph_map[glyph_id] = metrics.full_glyph_map[glyph_id] except (KeyError, ValueError): pass glyphs = [] last_x = last_y = 0 for i, pos in enumerate(gi.positions): x, y = pos.x(), pos.y() glyphs.append((x-last_x, last_y - y, gi.indices[i])) last_x, last_y = x, y self.pdf.draw_glyph_run([gi.stretch, 0, 0, -1, 0, 0], gi.size, metrics, glyphs) sip.delete(gi) @store_error def drawPolygon(self, points, mode): self.apply_graphics_state() if not points: return p = Path() p.move_to(points[0].x(), points[0].y()) for point in points[1:]: p.line_to(point.x(), point.y()) p.close() fill_rule = {self.OddEvenMode:'evenodd', self.WindingMode:'winding'}.get(mode, 'evenodd') self.pdf.draw_path(p, stroke=True, fill_rule=fill_rule, fill=(mode in (self.OddEvenMode, self.WindingMode, self.ConvexMode))) def set_metadata(self, *args, **kwargs): self.pdf.set_metadata(*args, **kwargs) def add_outline(self, toc): self.pdf.links.add_outline(toc) def add_links(self, current_item, start_page, links, anchors): for pos in anchors.itervalues(): pos['left'], pos['top'] = self.pdf_system.map(pos['left'], pos['top']) for link in links: pos = link[1] llx = pos['left'] lly = pos['top'] + pos['height'] urx = pos['left'] + pos['width'] ury = pos['top'] llx, lly = self.pdf_system.map(llx, lly) urx, ury = self.pdf_system.map(urx, ury) link[1] = pos['column'] + start_page link.append((llx, lly, urx, ury)) self.pdf.links.add(current_item, start_page, links, anchors)
class PdfEngine(QPaintEngine): FEATURES = QPaintEngine.AllFeatures & ~( QPaintEngine.PorterDuff | QPaintEngine.PerspectiveTransform | QPaintEngine.ObjectBoundingModeGradients | QPaintEngine.RadialGradientFill | QPaintEngine.ConicalGradientFill) def __init__(self, file_object, page_width, page_height, left_margin, top_margin, right_margin, bottom_margin, width, height, errors=print, debug=print, compress=True, mark_links=False): QPaintEngine.__init__(self, self.FEATURES) self.file_object = file_object self.compress, self.mark_links = compress, mark_links self.page_height, self.page_width = page_height, page_width self.left_margin, self.top_margin = left_margin, top_margin self.right_margin, self.bottom_margin = right_margin, bottom_margin self.pixel_width, self.pixel_height = width, height # Setup a co-ordinate transform that allows us to use co-ords # from Qt's pixel based co-ordinate system with its origin at the top # left corner. PDF's co-ordinate system is based on pts and has its # origin in the bottom left corner. We also have to implement the page # margins. Therefore, we need to translate, scale and reflect about the # x-axis. dy = self.page_height - self.top_margin dx = self.left_margin sx = (self.page_width - self.left_margin - self.right_margin) / self.pixel_width sy = (self.page_height - self.top_margin - self.bottom_margin) / self.pixel_height self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy) self.graphics = Graphics(self.pixel_width, self.pixel_height) self.errors_occurred = False self.errors, self.debug = errors, debug self.fonts = {} self.current_page_num = 1 self.current_page_inited = False self.qt_hack, err = plugins['qt_hack'] if err: raise RuntimeError('Failed to load qt_hack with err: %s' % err) def apply_graphics_state(self): self.graphics(self.pdf_system, self.painter()) def resolve_fill(self, rect): self.graphics.resolve_fill(rect, self.pdf_system, self.painter().transform()) @property def do_fill(self): return self.graphics.current_state.do_fill @property def do_stroke(self): return self.graphics.current_state.do_stroke def init_page(self): self.pdf.transform(self.pdf_system) self.pdf.apply_fill( color=(1, 1, 1)) # QPainter has a default background brush of white self.graphics.reset() self.pdf.save_stack() self.current_page_inited = True def begin(self, device): if not hasattr(self, 'pdf'): try: self.pdf = PDFStream(self.file_object, (self.page_width, self.page_height), compress=self.compress, mark_links=self.mark_links, debug=self.debug) self.graphics.begin(self.pdf) except: self.errors(traceback.format_exc()) self.errors_occurred = True return False return True def end_page(self): if self.current_page_inited: self.pdf.restore_stack() self.pdf.end_page() self.current_page_inited = False self.current_page_num += 1 def end(self): try: self.end_page() self.pdf.end() except: self.errors(traceback.format_exc()) self.errors_occurred = True return False finally: self.pdf = self.file_object = None return True def type(self): return QPaintEngine.Pdf def add_image(self, img, cache_key): if img.isNull(): return return self.pdf.add_image(img, cache_key) @store_error def drawTiledPixmap(self, rect, pixmap, point): self.apply_graphics_state() brush = QBrush(pixmap) bl = rect.topLeft() color, opacity, pattern, do_fill = self.graphics.convert_brush( brush, bl - point, 1.0, self.pdf_system, self.painter().transform()) self.pdf.save_stack() self.pdf.apply_fill(color, pattern) self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(), stroke=False, fill=True) self.pdf.restore_stack() @store_error def drawPixmap(self, rect, pixmap, source_rect): self.apply_graphics_state() source_rect = source_rect.toRect() pixmap = (pixmap if source_rect == pixmap.rect() else pixmap.copy(source_rect)) image = pixmap.toImage() ref = self.add_image(image, pixmap.cacheKey()) if ref is not None: self.pdf.draw_image(rect.x(), rect.y(), rect.width(), rect.height(), ref) @store_error def drawImage(self, rect, image, source_rect, flags=Qt.AutoColor): self.apply_graphics_state() source_rect = source_rect.toRect() image = (image if source_rect == image.rect() else image.copy(source_rect)) ref = self.add_image(image, image.cacheKey()) if ref is not None: self.pdf.draw_image(rect.x(), rect.y(), rect.width(), rect.height(), ref) @store_error def updateState(self, state): self.graphics.update_state(state, self.painter()) @store_error def drawPath(self, path): self.apply_graphics_state() p = convert_path(path) fill_rule = { Qt.OddEvenFill: 'evenodd', Qt.WindingFill: 'winding' }[path.fillRule()] self.pdf.draw_path(p, stroke=self.do_stroke, fill=self.do_fill, fill_rule=fill_rule) @store_error def drawPoints(self, points): self.apply_graphics_state() p = Path() for point in points: p.move_to(point.x(), point.y()) p.line_to(point.x(), point.y() + 0.001) self.pdf.draw_path(p, stroke=self.do_stroke, fill=False) @store_error def drawRects(self, rects): self.apply_graphics_state() with self.graphics: for rect in rects: self.resolve_fill(rect) bl = rect.topLeft() self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(), stroke=self.do_stroke, fill=self.do_fill) def create_sfnt(self, text_item): get_table = partial(self.qt_hack.get_sfnt_table, text_item) try: ans = Font(Sfnt(get_table)) except UnsupportedFont as e: raise UnsupportedFont( 'The font %s is not a valid sfnt. Error: %s' % (text_item.font().family(), e)) glyph_map = self.qt_hack.get_glyph_map(text_item) gm = {} for uc, glyph_id in enumerate(glyph_map): if glyph_id not in gm: gm[glyph_id] = unichr(uc) ans.full_glyph_map = gm return ans @store_error def drawTextItem(self, point, text_item): # return super(PdfEngine, self).drawTextItem(point, text_item) self.apply_graphics_state() gi = self.qt_hack.get_glyphs(point, text_item) if not gi.indices: sip.delete(gi) return name = hash(bytes(gi.name)) if name not in self.fonts: try: self.fonts[name] = self.create_sfnt(text_item) except UnsupportedFont: return super(PdfEngine, self).drawTextItem(point, text_item) metrics = self.fonts[name] for glyph_id in gi.indices: try: metrics.glyph_map[glyph_id] = metrics.full_glyph_map[glyph_id] except (KeyError, ValueError): pass glyphs = [] last_x = last_y = 0 for i, pos in enumerate(gi.positions): x, y = pos.x(), pos.y() glyphs.append((x - last_x, last_y - y, gi.indices[i])) last_x, last_y = x, y self.pdf.draw_glyph_run([gi.stretch, 0, 0, -1, 0, 0], gi.size, metrics, glyphs) sip.delete(gi) @store_error def drawPolygon(self, points, mode): self.apply_graphics_state() if not points: return p = Path() p.move_to(points[0].x(), points[0].y()) for point in points[1:]: p.line_to(point.x(), point.y()) p.close() fill_rule = { self.OddEvenMode: 'evenodd', self.WindingMode: 'winding' }.get(mode, 'evenodd') self.pdf.draw_path(p, stroke=True, fill_rule=fill_rule, fill=(mode in (self.OddEvenMode, self.WindingMode, self.ConvexMode))) def set_metadata(self, *args, **kwargs): self.pdf.set_metadata(*args, **kwargs) def add_outline(self, toc): self.pdf.links.add_outline(toc) def add_links(self, current_item, start_page, links, anchors): for pos in anchors.itervalues(): pos['left'], pos['top'] = self.pdf_system.map( pos['left'], pos['top']) for link in links: pos = link[1] llx = pos['left'] lly = pos['top'] + pos['height'] urx = pos['left'] + pos['width'] ury = pos['top'] llx, lly = self.pdf_system.map(llx, lly) urx, ury = self.pdf_system.map(urx, ury) link[1] = pos['column'] + start_page link.append((llx, lly, urx, ury)) self.pdf.links.add(current_item, start_page, links, anchors)