Esempio n. 1
0
 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
Esempio n. 2
0
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()
Esempio n. 3
0
 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
Esempio n. 4
0
 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
Esempio n. 5
0
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)
Esempio n. 6
0
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)
Esempio n. 7
0
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)
Esempio n. 8
0
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)
Esempio n. 9
0
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()
Esempio n. 10
0
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)