示例#1
0
 def __init__(self,
              stream,
              page_size,
              compress=False,
              mark_links=False,
              debug=print):
     self.stream = HashingStream(stream)
     self.compress = compress
     self.write_line(PDFVER)
     self.write_line(u'%íì¦"'.encode('utf-8'))
     creator = ('%s %s [https://calibre-ebook.com]' %
                (__appname__, __version__))
     self.write_line('%% Created by %s' % creator)
     self.objects = IndirectObjects()
     self.objects.add(PageTree(page_size))
     self.objects.add(Catalog(self.page_tree))
     self.current_page = Page(self.page_tree, compress=self.compress)
     self.info = Dictionary({
         'Creator': String(creator),
         'Producer': String(creator),
         'CreationDate': utcnow(),
     })
     self.stroke_opacities, self.fill_opacities = {}, {}
     self.font_manager = FontManager(self.objects, self.compress)
     self.image_cache = {}
     self.pattern_cache, self.shader_cache = {}, {}
     self.debug = debug
     self.links = Links(self, mark_links, page_size)
     i = QImage(1, 1, QImage.Format_ARGB32)
     i.fill(qRgba(0, 0, 0, 255))
     self.alpha_bit = i.constBits().asstring(4).find(b'\xff')
示例#2
0
 def __init__(self, stream, page_size, compress=False, mark_links=False,
              debug=print):
     self.stream = HashingStream(stream)
     self.compress = compress
     self.write_line(PDFVER)
     self.write_line(b'%íì¦"')
     creator = ('%s %s [http://calibre-ebook.com]'%(__appname__,
                                 __version__))
     self.write_line('%% Created by %s'%creator)
     self.objects = IndirectObjects()
     self.objects.add(PageTree(page_size))
     self.objects.add(Catalog(self.page_tree))
     self.current_page = Page(self.page_tree, compress=self.compress)
     self.info = Dictionary({
         'Creator':String(creator),
         'Producer':String(creator),
         'CreationDate': utcnow(),
                             })
     self.stroke_opacities, self.fill_opacities = {}, {}
     self.font_manager = FontManager(self.objects, self.compress)
     self.image_cache = {}
     self.pattern_cache, self.shader_cache = {}, {}
     self.debug = debug
     self.links = Links(self, mark_links, page_size)
     i = QImage(1, 1, QImage.Format_ARGB32)
     i.fill(qRgba(0, 0, 0, 255))
     self.alpha_bit = i.constBits().asstring(4).find(b'\xff')
示例#3
0
class PDFStream(object):

    PATH_OPS = {
        # stroke fill   fill-rule
        (False, False, 'winding'): 'n',
        (False, False, 'evenodd'): 'n',
        (False, True, 'winding'): 'f',
        (False, True, 'evenodd'): 'f*',
        (True, False, 'winding'): 'S',
        (True, False, 'evenodd'): 'S',
        (True, True, 'winding'): 'B',
        (True, True, 'evenodd'): 'B*',
    }

    def __init__(self,
                 stream,
                 page_size,
                 compress=False,
                 mark_links=False,
                 debug=print):
        self.stream = HashingStream(stream)
        self.compress = compress
        self.write_line(PDFVER)
        self.write_line(u'%íì¦"'.encode('utf-8'))
        creator = ('%s %s [https://calibre-ebook.com]' %
                   (__appname__, __version__))
        self.write_line('%% Created by %s' % creator)
        self.objects = IndirectObjects()
        self.objects.add(PageTree(page_size))
        self.objects.add(Catalog(self.page_tree))
        self.current_page = Page(self.page_tree, compress=self.compress)
        self.info = Dictionary({
            'Creator': String(creator),
            'Producer': String(creator),
            'CreationDate': utcnow(),
        })
        self.stroke_opacities, self.fill_opacities = {}, {}
        self.font_manager = FontManager(self.objects, self.compress)
        self.image_cache = {}
        self.pattern_cache, self.shader_cache = {}, {}
        self.debug = debug
        self.links = Links(self, mark_links, page_size)
        i = QImage(1, 1, QImage.Format_ARGB32)
        i.fill(qRgba(0, 0, 0, 255))
        self.alpha_bit = i.constBits().asstring(4).find(b'\xff')

    @property
    def page_tree(self):
        return self.objects[0]

    @property
    def catalog(self):
        return self.objects[1]

    def get_pageref(self, pagenum):
        return self.page_tree.obj.get_ref(pagenum)

    def set_metadata(self, title=None, author=None, tags=None, mi=None):
        if title:
            self.info['Title'] = String(title)
        if author:
            self.info['Author'] = String(author)
        if tags:
            self.info['Keywords'] = String(tags)
        if mi is not None:
            self.metadata = self.objects.add(Metadata(mi))
            self.catalog.obj['Metadata'] = self.metadata

    def write_line(self, byts=b''):
        byts = byts if isinstance(byts, bytes) else byts.encode('ascii')
        self.stream.write(byts + EOL)

    def transform(self, *args):
        if len(args) == 1:
            m = args[0]
            vals = [m.m11(), m.m12(), m.m21(), m.m22(), m.dx(), m.dy()]
        else:
            vals = args
        cm = ' '.join(map(fmtnum, vals))
        self.current_page.write_line(cm + ' cm')

    def save_stack(self):
        self.current_page.write_line('q')

    def restore_stack(self):
        self.current_page.write_line('Q')

    def reset_stack(self):
        self.current_page.write_line('Q q')

    def draw_rect(self, x, y, width, height, stroke=True, fill=False):
        self.current_page.write('%s re ' %
                                ' '.join(map(fmtnum, (x, y, width, height))))
        self.current_page.write_line(self.PATH_OPS[(stroke, fill, 'winding')])

    def write_path(self, path):
        for i, op in enumerate(path.ops):
            if i != 0:
                self.current_page.write_line()
            for x in op:
                self.current_page.write(
                    (fmtnum(x) if isinstance(x, numbers.Number) else x) + ' ')

    def draw_path(self, path, stroke=True, fill=False, fill_rule='winding'):
        if not path.ops:
            return
        self.write_path(path)
        self.current_page.write_line(self.PATH_OPS[(stroke, fill, fill_rule)])

    def add_clip(self, path, fill_rule='winding'):
        if not path.ops:
            return
        self.write_path(path)
        op = 'W' if fill_rule == 'winding' else 'W*'
        self.current_page.write_line(op + ' ' + 'n')

    def serialize(self, o):
        serialize(o, self.current_page)

    def set_stroke_opacity(self, opacity):
        if opacity not in self.stroke_opacities:
            op = Dictionary({'Type': Name('ExtGState'), 'CA': opacity})
            self.stroke_opacities[opacity] = self.objects.add(op)
        self.current_page.set_opacity(self.stroke_opacities[opacity])

    def set_fill_opacity(self, opacity):
        opacity = float(opacity)
        if opacity not in self.fill_opacities:
            op = Dictionary({'Type': Name('ExtGState'), 'ca': opacity})
            self.fill_opacities[opacity] = self.objects.add(op)
        self.current_page.set_opacity(self.fill_opacities[opacity])

    def end_page(self, drop_page=False):
        if not drop_page:
            pageref = self.current_page.end(self.objects, self.stream)
            self.page_tree.obj.add_page(pageref)
        self.current_page = Page(self.page_tree, compress=self.compress)

    def draw_glyph_run(self, transform, size, font_metrics, glyphs):
        glyph_ids = {x[-1] for x in glyphs}
        fontref = self.font_manager.add_font(font_metrics, glyph_ids)
        name = self.current_page.add_font(fontref)
        self.current_page.write(b'BT ')
        serialize(Name(name), self.current_page)
        self.current_page.write(' %s Tf ' % fmtnum(size))
        self.current_page.write('%s Tm ' % ' '.join(map(fmtnum, transform)))
        for x, y, glyph_id in glyphs:
            self.current_page.write_raw(
                ('%s %s Td <%04X> Tj ' %
                 (fmtnum(x), fmtnum(y), glyph_id)).encode('ascii'))
        self.current_page.write_line(b' ET')

    def get_image(self, cache_key):
        return self.image_cache.get(cache_key, None)

    def write_image(self,
                    data,
                    w,
                    h,
                    depth,
                    dct=False,
                    mask=None,
                    soft_mask=None,
                    cache_key=None):
        imgobj = Image(data, w, h, depth, mask, soft_mask, dct)
        self.image_cache[cache_key] = r = self.objects.add(imgobj)
        self.objects.commit(r, self.stream)
        return r

    def add_image(self, img, cache_key):
        ref = self.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.write_image(data, w, h, d, cache_key=cache_key)

        has_alpha = False
        soft_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)  # discard opaque pixels
            has_alpha = bool(vals)
            if has_alpha:
                # Blend image onto a white background as otherwise Qt will render
                # transparent pixels as black
                background = QImage(image.size(),
                                    QImage.Format_ARGB32_Premultiplied)
                background.fill(Qt.white)
                painter = QPainter(background)
                painter.drawImage(0, 0, image)
                painter.end()
                image = background

        ba = QByteArray()
        buf = QBuffer(ba)
        image.save(buf, 'jpeg', 94)
        data = ba.data()

        if has_alpha:
            soft_mask = self.write_image(tmask, w, h, 8)

        return self.write_image(data,
                                w,
                                h,
                                32,
                                dct=True,
                                soft_mask=soft_mask,
                                cache_key=cache_key)

    def add_pattern(self, pattern):
        if pattern.cache_key not in self.pattern_cache:
            self.pattern_cache[pattern.cache_key] = self.objects.add(pattern)
        return self.current_page.add_pattern(
            self.pattern_cache[pattern.cache_key])

    def add_shader(self, shader):
        if shader.cache_key not in self.shader_cache:
            self.shader_cache[shader.cache_key] = self.objects.add(shader)
        return self.shader_cache[shader.cache_key]

    def draw_image(self, x, y, width, height, imgref):
        name = self.current_page.add_image(imgref)
        self.current_page.write(
            'q %s 0 0 %s %s %s cm ' %
            (fmtnum(width), fmtnum(-height), fmtnum(x), fmtnum(y + height)))
        serialize(Name(name), self.current_page)
        self.current_page.write_line(' Do Q')

    def apply_color_space(self, color, pattern, stroke=False):
        wl = self.current_page.write_line
        if color is not None and pattern is None:
            wl(' '.join(map(fmtnum, color)) + (' RG' if stroke else ' rg'))
        elif color is None and pattern is not None:
            wl('/Pattern %s /%s %s' %
               ('CS' if stroke else 'cs', pattern, 'SCN' if stroke else 'scn'))
        elif color is not None and pattern is not None:
            col = ' '.join(map(fmtnum, color))
            wl('/PCSp %s %s /%s %s' % ('CS' if stroke else 'cs', col, pattern,
                                       'SCN' if stroke else 'scn'))

    def apply_fill(self, color=None, pattern=None, opacity=None):
        if opacity is not None:
            self.set_fill_opacity(opacity)
        self.apply_color_space(color, pattern)

    def apply_stroke(self, color=None, pattern=None, opacity=None):
        if opacity is not None:
            self.set_stroke_opacity(opacity)
        self.apply_color_space(color, pattern, stroke=True)

    def end(self):
        if self.current_page.getvalue():
            self.end_page()
        self.font_manager.embed_fonts(self.debug)
        inforef = self.objects.add(self.info)
        self.links.add_links()
        self.objects.pdf_serialize(self.stream)
        self.write_line()
        startxref = self.objects.write_xref(self.stream)
        file_id = String(as_unicode(self.stream.hashobj.hexdigest()))
        self.write_line('trailer')
        trailer = Dictionary({
            'Root': self.catalog,
            'Size': len(self.objects) + 1,
            'ID': Array([file_id, file_id]),
            'Info': inforef
        })
        serialize(trailer, self.stream)
        self.write_line('startxref')
        self.write_line('%d' % startxref)
        self.stream.write('%%EOF')
示例#4
0
class PDFStream(object):

    PATH_OPS = {
        # stroke fill   fill-rule
        ( False, False, 'winding')  : 'n',
        ( False, False, 'evenodd')  : 'n',
        ( False, True,  'winding')  : 'f',
        ( False, True,  'evenodd')  : 'f*',
        ( True,  False, 'winding')  : 'S',
        ( True,  False, 'evenodd')  : 'S',
        ( True,  True,  'winding')  : 'B',
        ( True,  True,  'evenodd')  : 'B*',
    }

    def __init__(self, stream, page_size, compress=False, mark_links=False,
                 debug=print):
        self.stream = HashingStream(stream)
        self.compress = compress
        self.write_line(PDFVER)
        self.write_line(b'%íì¦"')
        creator = ('%s %s [http://calibre-ebook.com]'%(__appname__,
                                    __version__))
        self.write_line('%% Created by %s'%creator)
        self.objects = IndirectObjects()
        self.objects.add(PageTree(page_size))
        self.objects.add(Catalog(self.page_tree))
        self.current_page = Page(self.page_tree, compress=self.compress)
        self.info = Dictionary({
            'Creator':String(creator),
            'Producer':String(creator),
            'CreationDate': utcnow(),
                                })
        self.stroke_opacities, self.fill_opacities = {}, {}
        self.font_manager = FontManager(self.objects, self.compress)
        self.image_cache = {}
        self.pattern_cache, self.shader_cache = {}, {}
        self.debug = debug
        self.links = Links(self, mark_links, page_size)
        i = QImage(1, 1, QImage.Format_ARGB32)
        i.fill(qRgba(0, 0, 0, 255))
        self.alpha_bit = i.constBits().asstring(4).find(b'\xff')

    @property
    def page_tree(self):
        return self.objects[0]

    @property
    def catalog(self):
        return self.objects[1]

    def get_pageref(self, pagenum):
        return self.page_tree.obj.get_ref(pagenum)

    def set_metadata(self, title=None, author=None, tags=None):
        if title:
            self.info['Title'] = String(title)
        if author:
            self.info['Author'] = String(author)
        if tags:
            self.info['Keywords'] = String(tags)

    def write_line(self, byts=b''):
        byts = byts if isinstance(byts, bytes) else byts.encode('ascii')
        self.stream.write(byts + EOL)

    def transform(self, *args):
        if len(args) == 1:
            m = args[0]
            vals = [m.m11(), m.m12(), m.m21(), m.m22(), m.dx(), m.dy()]
        else:
            vals = args
        cm = ' '.join(map(fmtnum, vals))
        self.current_page.write_line(cm + ' cm')

    def save_stack(self):
        self.current_page.write_line('q')

    def restore_stack(self):
        self.current_page.write_line('Q')

    def reset_stack(self):
        self.current_page.write_line('Q q')

    def draw_rect(self, x, y, width, height, stroke=True, fill=False):
        self.current_page.write('%s re '%' '.join(map(fmtnum, (x, y, width, height))))
        self.current_page.write_line(self.PATH_OPS[(stroke, fill, 'winding')])

    def write_path(self, path):
        for i, op in enumerate(path.ops):
            if i != 0:
                self.current_page.write_line()
            for x in op:
                self.current_page.write(
                (fmtnum(x) if isinstance(x, (int, long, float)) else x) + ' ')

    def draw_path(self, path, stroke=True, fill=False, fill_rule='winding'):
        if not path.ops: return
        self.write_path(path)
        self.current_page.write_line(self.PATH_OPS[(stroke, fill, fill_rule)])

    def add_clip(self, path, fill_rule='winding'):
        if not path.ops: return
        self.write_path(path)
        op = 'W' if fill_rule == 'winding' else 'W*'
        self.current_page.write_line(op + ' ' + 'n')

    def serialize(self, o):
        serialize(o, self.current_page)

    def set_stroke_opacity(self, opacity):
        if opacity not in self.stroke_opacities:
            op = Dictionary({'Type':Name('ExtGState'), 'CA': opacity})
            self.stroke_opacities[opacity] = self.objects.add(op)
        self.current_page.set_opacity(self.stroke_opacities[opacity])

    def set_fill_opacity(self, opacity):
        opacity = float(opacity)
        if opacity not in self.fill_opacities:
            op = Dictionary({'Type':Name('ExtGState'), 'ca': opacity})
            self.fill_opacities[opacity] = self.objects.add(op)
        self.current_page.set_opacity(self.fill_opacities[opacity])

    def end_page(self):
        pageref = self.current_page.end(self.objects, self.stream)
        self.page_tree.obj.add_page(pageref)
        self.current_page = Page(self.page_tree, compress=self.compress)

    def draw_glyph_run(self, transform, size, font_metrics, glyphs):
        glyph_ids = {x[-1] for x in glyphs}
        fontref = self.font_manager.add_font(font_metrics, glyph_ids)
        name = self.current_page.add_font(fontref)
        self.current_page.write(b'BT ')
        serialize(Name(name), self.current_page)
        self.current_page.write(' %s Tf '%fmtnum(size))
        self.current_page.write('%s Tm '%' '.join(map(fmtnum, transform)))
        for x, y, glyph_id in glyphs:
            self.current_page.write_raw(('%s %s Td <%04X> Tj '%(
                fmtnum(x), fmtnum(y), glyph_id)).encode('ascii'))
        self.current_page.write_line(b' ET')

    def get_image(self, cache_key):
        return self.image_cache.get(cache_key, None)

    def write_image(self, data, w, h, depth, dct=False, mask=None,
                    soft_mask=None, cache_key=None):
        imgobj = Image(data, w, h, depth, mask, soft_mask, dct)
        self.image_cache[cache_key] = r = self.objects.add(imgobj)
        self.objects.commit(r, self.stream)
        return r

    def add_image(self, img, cache_key):
        ref = self.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.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.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.write_image(mdata, w, h, 1)

        return self.write_image(data, w, h, 32, mask=mask, dct=True,
                                    soft_mask=soft_mask, cache_key=cache_key)

    def add_pattern(self, pattern):
        if pattern.cache_key not in self.pattern_cache:
            self.pattern_cache[pattern.cache_key] = self.objects.add(pattern)
        return self.current_page.add_pattern(self.pattern_cache[pattern.cache_key])

    def add_shader(self, shader):
        if shader.cache_key not in self.shader_cache:
            self.shader_cache[shader.cache_key] = self.objects.add(shader)
        return self.shader_cache[shader.cache_key]

    def draw_image(self, x, y, width, height, imgref):
        name = self.current_page.add_image(imgref)
        self.current_page.write('q %s 0 0 %s %s %s cm '%(fmtnum(width),
                            fmtnum(-height), fmtnum(x), fmtnum(y+height)))
        serialize(Name(name), self.current_page)
        self.current_page.write_line(' Do Q')

    def apply_color_space(self, color, pattern, stroke=False):
        wl = self.current_page.write_line
        if color is not None and pattern is None:
            wl(' '.join(map(fmtnum, color)) + (' RG' if stroke else ' rg'))
        elif color is None and pattern is not None:
            wl('/Pattern %s /%s %s'%('CS' if stroke else 'cs', pattern,
                                     'SCN' if stroke else 'scn'))
        elif color is not None and pattern is not None:
            col = ' '.join(map(fmtnum, color))
            wl('/PCSp %s %s /%s %s'%('CS' if stroke else 'cs', col, pattern,
                                     'SCN' if stroke else 'scn'))

    def apply_fill(self, color=None, pattern=None, opacity=None):
        if opacity is not None:
            self.set_fill_opacity(opacity)
        self.apply_color_space(color, pattern)

    def apply_stroke(self, color=None, pattern=None, opacity=None):
        if opacity is not None:
            self.set_stroke_opacity(opacity)
        self.apply_color_space(color, pattern, stroke=True)

    def end(self):
        if self.current_page.getvalue():
            self.end_page()
        self.font_manager.embed_fonts()
        inforef = self.objects.add(self.info)
        self.links.add_links()
        self.objects.pdf_serialize(self.stream)
        self.write_line()
        startxref = self.objects.write_xref(self.stream)
        file_id = String(self.stream.hashobj.hexdigest().decode('ascii'))
        self.write_line('trailer')
        trailer = Dictionary({'Root':self.catalog, 'Size':len(self.objects)+1,
                              'ID':Array([file_id, file_id]), 'Info':inforef})
        serialize(trailer, self.stream)
        self.write_line('startxref')
        self.write_line('%d'%startxref)
        self.stream.write('%%EOF')