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 __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, opts=None, page_margins=(0, 0, 0, 0)): 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 self.pdf_system = self.create_transform() 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.content_written_to_current_page = False self.qt_hack, err = plugins['qt_hack'] self.has_footers = opts is not None and (opts.pdf_page_numbers or opts.pdf_footer_template is not None) self.has_headers = opts is not None and opts.pdf_header_template is not None ml, mr, mt, mb = page_margins self.header_height = mt self.footer_height = mb if err: raise RuntimeError('Failed to load qt_hack with err: %s'%err)
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.content_written_to_current_page = False self.qt_hack, err = plugins['qt_hack'] if err: raise RuntimeError('Failed to load qt_hack with err: %s'%err)
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, opts=None, page_margins=(0, 0, 0, 0)): 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 self.pdf_system = self.create_transform() 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.content_written_to_current_page = False self.qt_hack, err = plugins['qt_hack'] self.has_footers = opts is not None and ( opts.pdf_page_numbers or opts.pdf_footer_template is not None) self.has_headers = opts is not None and opts.pdf_header_template is not None ml, mr, mt, mb = page_margins self.header_height = mt self.footer_height = mb if err: raise RuntimeError('Failed to load qt_hack with err: %s' % err)
class PdfEngine(QPaintEngine): FEATURES = QPaintEngine.AllFeatures & ~( QPaintEngine.PorterDuff | QPaintEngine.PerspectiveTransform | QPaintEngine.ObjectBoundingModeGradients | QPaintEngine.RadialGradientFill | QPaintEngine.ConicalGradientFill ) # noqa 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, opts=None, page_margins=(0, 0, 0, 0)): 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 self.pdf_system = self.create_transform() 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.content_written_to_current_page = False self.qt_hack, err = plugins['qt_hack'] self.has_footers = opts is not None and ( opts.pdf_page_numbers or opts.pdf_footer_template is not None) self.has_headers = opts is not None and opts.pdf_header_template is not None ml, mr, mt, mb = page_margins self.header_height = mt self.footer_height = mb if err: raise RuntimeError('Failed to load qt_hack with err: %s' % err) def create_transform(self, left_margin=None, top_margin=None, right_margin=None, bottom_margin=None): # 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. left_margin = self.left_margin if left_margin is None else left_margin top_margin = self.top_margin if top_margin is None else top_margin right_margin = self.right_margin if right_margin is None else right_margin bottom_margin = self.bottom_margin if bottom_margin is None else bottom_margin dy = self.page_height - top_margin dx = left_margin sx = (self.page_width - left_margin - right_margin) / self.pixel_width sy = (self.page_height - top_margin - bottom_margin) / self.pixel_height return QTransform(sx, 0, 0, -sy, dx, dy) 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, custom_margins=None): self.content_written_to_current_page = False if custom_margins is None: self.pdf.transform(self.pdf_system) else: self.pdf.transform(self.create_transform(*custom_margins)) 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, is_last_page=False): if self.current_page_inited: self.pdf.restore_stack() drop_page = is_last_page and not self.content_written_to_current_page self.pdf.end_page(drop_page=drop_page) self.current_page_inited = False self.current_page_num += 0 if drop_page else 1 return self.content_written_to_current_page 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.content_written_to_current_page = 'drawTiledPixmap' 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.content_written_to_current_page = 'drawPixmap' 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.content_written_to_current_page = 'drawImage' 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.content_written_to_current_page = 'drawPath' 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.content_written_to_current_page = 'drawPoints' 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() if self.do_stroke or self.do_fill: self.content_written_to_current_page = 'drawRects' 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 = {} ans.ignore_glyphs = set() for uc, glyph_id in enumerate(glyph_map): if glyph_id not in gm: gm[glyph_id] = unichr(uc) if uc in (0xad, 0x200b): ans.ignore_glyphs.add(glyph_id) 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 = GlyphInfo(*self.qt_hack.get_glyphs(point, text_item)) if not gi.indices: return metrics = self.fonts.get(gi.name) if metrics is None: from calibre.utils.fonts.utils import get_all_font_names try: names = get_all_font_names(gi.name, True) names = ' '.join('%s=%s' % (k, names[k]) for k in sorted(names)) except Exception: names = 'Unknown' self.debug('Loading font: %s' % names) try: self.fonts[gi.name] = metrics = self.create_sfnt(text_item) except UnsupportedFont: self.debug( 'Failed to load font: %s, drawing text as outlines...' % names) return super(PdfEngine, self).drawTextItem(point, text_item) indices, positions = [], [] ignore_glyphs = metrics.ignore_glyphs for glyph_id, gpos in zip(gi.indices, gi.positions): if glyph_id not in ignore_glyphs: indices.append(glyph_id), positions.append(gpos) for glyph_id in indices: try: metrics.glyph_map[glyph_id] = metrics.full_glyph_map[glyph_id] except (KeyError, ValueError): pass glyphs = [] last_x = last_y = 0 for glyph_index, (x, y) in zip(indices, positions): glyphs.append((x - last_x, last_y - y, glyph_index)) last_x, last_y = x, y if not self.content_written_to_current_page: dy = self.graphics.current_state.transform.dy() ypositions = [y + dy for x, y in positions] miny = min(ypositions or (0, )) maxy = max(ypositions or (self.pixel_height, )) page_top = self.header_height if self.has_headers else 0 page_bottom = self.pixel_height - (self.footer_height if self.has_footers else 0) if page_top <= miny <= page_bottom or page_top <= maxy <= page_bottom: self.content_written_to_current_page = 'drawTextItem' else: self.debug( 'Text in header/footer: miny=%s maxy=%s page_top=%s page_bottom=%s' % (miny, maxy, page_top, page_bottom)) self.pdf.draw_glyph_run([gi.stretch, 0, 0, -1, 0, 0], gi.size, metrics, glyphs) @store_error def drawPolygon(self, points, mode): self.content_written_to_current_page = 'drawPolygon' 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, opts=None): 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.content_written_to_current_page = False self.qt_hack, err = plugins['qt_hack'] self.has_footers = opts is not None and (opts.pdf_page_numbers or opts.pdf_footer_template is not None) self.has_headers = opts is not None and opts.pdf_header_template is not None self.header_height = (opts.margin_top or 0) if opts else 0 self.footer_height = (opts.margin_bottom) or 0 if opts else 0 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.content_written_to_current_page = False 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, is_last_page=False): if self.current_page_inited: self.pdf.restore_stack() drop_page = is_last_page and not self.content_written_to_current_page self.pdf.end_page(drop_page=drop_page) self.current_page_inited = False self.current_page_num += 0 if drop_page else 1 return self.content_written_to_current_page 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.content_written_to_current_page = 'drawTiledPixmap' 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.content_written_to_current_page = 'drawPixmap' 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.content_written_to_current_page = 'drawImage' 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.content_written_to_current_page = 'drawPath' 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.content_written_to_current_page = 'drawPoints' 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() if self.do_stroke or self.do_fill: self.content_written_to_current_page = 'drawRects' 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 = GlyphInfo(*self.qt_hack.get_glyphs(point, text_item)) if not gi.indices: return metrics = self.fonts.get(gi.name) if metrics is None: from calibre.utils.fonts.utils import get_all_font_names try: names = get_all_font_names(gi.name, True) names = ' '.join('%s=%s'%(k, names[k]) for k in sorted(names)) except Exception: names = 'Unknown' self.debug('Loading font: %s' % names) try: self.fonts[gi.name] = metrics = self.create_sfnt(text_item) except UnsupportedFont: return super(PdfEngine, self).drawTextItem(point, text_item) 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 glyph_index, (x, y) in zip(gi.indices, gi.positions): glyphs.append((x-last_x, last_y - y, glyph_index)) last_x, last_y = x, y if not self.content_written_to_current_page: dy = self.graphics.current_state.transform.dy() ypositions = [y + dy for x, y in gi.positions] miny = min(ypositions or (0,)) maxy = max(ypositions or (self.pixel_height,)) page_top = self.header_height if self.has_headers else 0 page_bottom = self.pixel_height - (self.footer_height if self.has_footers else 0) if page_top <= miny <= page_bottom or page_top <= maxy <= page_bottom: self.content_written_to_current_page = 'drawTextItem' else: self.debug('Text in header/footer: miny=%s maxy=%s page_top=%s page_bottom=%s'% ( miny, maxy, page_top, page_bottom)) self.pdf.draw_glyph_run([gi.stretch, 0, 0, -1, 0, 0], gi.size, metrics, glyphs) @store_error def drawPolygon(self, points, mode): self.content_written_to_current_page = 'drawPolygon' 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)
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)