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 convert(images, output_path, opts, metadata, report_progress): with open(output_path, 'wb') as buf: page_layout = get_page_layout(opts, for_comic=True) page_size = page_layout.fullRectPoints().size() writer = PDFStream(buf, (page_size.width(), page_size.height()), compress=True) writer.apply_fill(color=(1, 1, 1)) pdf_metadata = PDFMetadata(metadata) writer.set_metadata(pdf_metadata.title, pdf_metadata.author, pdf_metadata.tags, pdf_metadata.mi) for i, path in enumerate(images): img = Image(as_unicode(path, filesystem_encoding)) draw_image_page(writer, img) writer.end_page() report_progress((i + 1) / len(images), _('Rendered {0} of {1} pages').format(i + 1, len(images))) writer.end()
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) except: self.errors.append(traceback.format_exc()) return False return True
def add_cover(pdf_doc, cover_data, page_layout, opts): buf = BytesIO() page_size = page_layout.fullRectPoints().size() img = Image(cover_data) writer = PDFStream(buf, (page_size.width(), page_size.height()), compress=True) writer.apply_fill(color=(1, 1, 1)) draw_image_page(writer, img, preserve_aspect_ratio=opts.preserve_cover_aspect_ratio) writer.end() cover_pdf_doc = data_as_pdf_doc(buf.getvalue()) pdf_doc.insert_existing_page(cover_pdf_doc)
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): 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, ): QPaintEngine.__init__(self, self.features) self.file_object = file_object self.compress = compress 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.do_stroke = True self.do_fill = False self.scale = sqrt(sy ** 2 + sx ** 2) self.xscale, self.yscale = sx, sy self.graphics_state = GraphicsState() self.errors_occurred = False self.errors, self.debug = errors, debug self.text_option = QTextOption() self.text_option.setWrapMode(QTextOption.NoWrap) self.fonts = {} i = QImage(1, 1, QImage.Format_ARGB32) i.fill(qRgba(0, 0, 0, 255)) self.alpha_bit = i.constBits().asstring(4).find(b"\xff") self.current_page_num = 1 self.current_page_inited = False def init_page(self): self.pdf.transform(self.pdf_system) self.pdf.set_rgb_colorspace() width = self.painter().pen().widthF() if self.isActive() else 0 self.pdf.set_line_width(width) self.do_stroke = True self.do_fill = False self.graphics_state.reset() self.pdf.save_stack() self.current_page_inited = True @property def features(self): return ( QPaintEngine.Antialiasing | QPaintEngine.AlphaBlend | QPaintEngine.ConstantOpacity | QPaintEngine.PainterPaths | QPaintEngine.PaintOutsidePaintEvent | QPaintEngine.PrimitiveTransform ) 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) except: self.errors.append(traceback.format_exc()) 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.append(traceback.format_exc()) return False finally: self.pdf = self.file_object = None return True def type(self): return QPaintEngine.Pdf @store_error def drawPixmap(self, rect, pixmap, source_rect): self.graphics_state(self) 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.height() + rect.y(), rect.width(), -rect.height(), ref) @store_error def drawImage(self, rect, image, source_rect, flags=Qt.AutoColor): self.graphics_state(self) 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.height() + rect.y(), rect.width(), -rect.height(), ref) def add_image(self, img, cache_key): if img.isNull(): return ref = self.pdf.get_image(cache_key) if ref is not None: return ref fmt = img.format() image = QImage(img) if ( image.depth() == 1 and img.colorTable().size() == 2 and img.colorTable().at(0) == QColor(Qt.black).rgba() and img.colorTable().at(1) == QColor(Qt.white).rgba() ): if fmt == QImage.Format_MonoLSB: image = image.convertToFormat(QImage.Format_Mono) fmt = QImage.Format_Mono else: if fmt != QImage.Format_RGB32 and fmt != QImage.Format_ARGB32: image = image.convertToFormat(QImage.Format_ARGB32) fmt = QImage.Format_ARGB32 w = image.width() h = image.height() d = image.depth() if fmt == QImage.Format_Mono: bytes_per_line = (w + 7) >> 3 data = image.constBits().asstring(bytes_per_line * h) return self.pdf.write_image(data, w, h, d, cache_key=cache_key) ba = QByteArray() buf = QBuffer(ba) image.save(buf, "jpeg", 94) data = bytes(ba.data()) has_alpha = has_mask = False soft_mask = mask = None if fmt == QImage.Format_ARGB32: tmask = image.constBits().asstring(4 * w * h)[self.alpha_bit :: 4] sdata = bytearray(tmask) vals = set(sdata) vals.discard(255) has_mask = bool(vals) vals.discard(0) has_alpha = bool(vals) if has_alpha: soft_mask = self.pdf.write_image(tmask, w, h, 8) elif has_mask: # dither the soft mask to 1bit and add it. This also helps PDF # viewers without transparency support bytes_per_line = (w + 7) >> 3 mdata = bytearray(0 for i in xrange(bytes_per_line * h)) spos = mpos = 0 for y in xrange(h): for x in xrange(w): if sdata[spos]: mdata[mpos + x >> 3] |= 0x80 >> (x & 7) spos += 1 mpos += bytes_per_line mdata = bytes(mdata) mask = self.pdf.write_image(mdata, w, h, 1) return self.pdf.write_image(data, w, h, 32, mask=mask, dct=True, soft_mask=soft_mask, cache_key=cache_key) @store_error def updateState(self, state): self.graphics_state.read(state) def convert_path(self, path): p = Path() i = 0 while i < path.elementCount(): elem = path.elementAt(i) em = (elem.x, elem.y) i += 1 if elem.isMoveTo(): p.move_to(*em) elif elem.isLineTo(): p.line_to(*em) elif elem.isCurveTo(): added = False if path.elementCount() > i + 1: c1, c2 = path.elementAt(i), path.elementAt(i + 1) if c1.type == path.CurveToDataElement and c2.type == path.CurveToDataElement: i += 2 p.curve_to(em[0], em[1], c1.x, c1.y, c2.x, c2.y) added = True if not added: raise ValueError("Invalid curve to operation") return p @store_error def drawPath(self, path): self.graphics_state(self) p = self.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) def add_clip(self, path): p = self.convert_path(path) fill_rule = {Qt.OddEvenFill: "evenodd", Qt.WindingFill: "winding"}[path.fillRule()] self.pdf.add_clip(p, fill_rule=fill_rule) @store_error def drawPoints(self, points): self.graphics_state(self) 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.graphics_state(self) for rect in rects: bl = rect.topLeft() self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(), stroke=self.do_stroke, fill=self.do_fill) def get_text_layout(self, text_item, text): tl = QTextLayout(text, text_item.font(), self.paintDevice()) self.text_option.setTextDirection( Qt.RightToLeft if text_item.renderFlags() & text_item.RightToLeft else Qt.LeftToRight ) tl.setTextOption(self.text_option) return tl def update_glyph_map(self, text, indices, text_item, glyph_map): """ Map glyphs back to the unicode text they represent. """ pos = 0 tl = self.get_text_layout(text_item, "") indices = list(indices) def get_glyphs(string): tl.setText(string) tl.beginLayout() line = tl.createLine() if not line.isValid(): tl.endLayout() return [] line.setLineWidth(int(1e12)) tl.endLayout() ans = [] for run in tl.glyphRuns(): ans.extend(run.glyphIndexes()) return ans ipos = 0 while ipos < len(indices): if indices[ipos] in glyph_map: t = glyph_map[indices[ipos]] if t == text[pos : pos + len(t)]: pos += len(t) ipos += 1 continue found = False for l in xrange(1, 10): string = text[pos : pos + l] g = get_glyphs(string) if g and g[0] == indices[ipos]: found = True glyph_map[g[0]] = string break if not found: self.debug("Failed to find glyph->unicode mapping for text: %s" % text) break ipos += 1 pos += l return text[pos:] @store_error def drawTextItem(self, point, text_item): # super(PdfEngine, self).drawTextItem(point, text_item) self.graphics_state(self) text = type("")(text_item.text()).replace("\n", " ") text = unicodedata.normalize("NFKC", text) tl = self.get_text_layout(text_item, text) tl.setPosition(point) tl.beginLayout() line = tl.createLine() if not line.isValid(): tl.endLayout() return line.setLineWidth(int(1e12)) tl.endLayout() for run in tl.glyphRuns(): rf = run.rawFont() name = hash(bytes(rf.fontTable("name"))) if name not in self.fonts: self.fonts[name] = Font(Sfnt(rf)) metrics = self.fonts[name] indices = run.glyphIndexes() text = self.update_glyph_map(text, indices, text_item, metrics.glyph_map) glyphs = [] pdf_pos = point first_baseline = None for i, pos in enumerate(run.positions()): if first_baseline is None: first_baseline = pos.y() glyph_pos = point + pos delta = glyph_pos - pdf_pos glyphs.append((delta.x(), pos.y() - first_baseline, indices[i])) pdf_pos = glyph_pos self.pdf.draw_glyph_run([1, 0, 0, -1, point.x(), point.y()], rf.pixelSize(), metrics, glyphs) @store_error def drawPolygon(self, points, mode): self.graphics_state(self) 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 __enter__(self): self.pdf.save_stack() self.saved_ps = (self.do_stroke, self.do_fill) def __exit__(self, *args): self.do_stroke, self.do_fill = self.saved_ps self.pdf.restore_stack()
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)