diff --git a/src/calibre/ebooks/pdf/render/common.py b/src/calibre/ebooks/pdf/render/common.py index 66add0b64e..297e614560 100644 --- a/src/calibre/ebooks/pdf/render/common.py +++ b/src/calibre/ebooks/pdf/render/common.py @@ -132,6 +132,7 @@ class Stream(BytesIO): def __init__(self, compress=False): BytesIO.__init__(self) self.compress = compress + self.filters = Array() def add_extra_keys(self, d): pass @@ -139,7 +140,7 @@ class Stream(BytesIO): def pdf_serialize(self, stream): raw = self.getvalue() dl = len(raw) - filters = Array() + filters = self.filters if self.compress: filters.append(Name('FlateDecode')) raw = zlib.compress(raw) diff --git a/src/calibre/ebooks/pdf/render/engine.py b/src/calibre/ebooks/pdf/render/engine.py index 41737402ea..047084d9af 100644 --- a/src/calibre/ebooks/pdf/render/engine.py +++ b/src/calibre/ebooks/pdf/render/engine.py @@ -13,7 +13,8 @@ from collections import namedtuple from functools import wraps from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter, - QTransform, QPainterPath, QTextOption, QTextLayout) + QTransform, QPainterPath, QTextOption, QTextLayout, + QImage, QByteArray, QBuffer, qRgba) from calibre.constants import DEBUG from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path) @@ -245,6 +246,9 @@ class PdfEngine(QPaintEngine): 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') def init_page(self): self.pdf.transform(self.pdf_system) @@ -296,11 +300,88 @@ class PdfEngine(QPaintEngine): @store_error def drawPixmap(self, rect, pixmap, source_rect): - print ('TODO: drawPixmap() currently unimplemented') + 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): - print ('TODO: drawImage() currently unimplemented') + 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) + + 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): @@ -510,8 +591,8 @@ class PdfDevice(QPaintDevice): # {{{ # }}} if __name__ == '__main__': - from PyQt4.Qt import (QBrush, QColor, QPoint) - QBrush, QColor, QPoint + from PyQt4.Qt import (QBrush, QColor, QPoint, QPixmap) + QBrush, QColor, QPoint, QPixmap app = QApplication([]) p = QPainter() with open('/tmp/painter.pdf', 'wb') as f: @@ -525,16 +606,21 @@ if __name__ == '__main__': # pp = QPainterPath() # pp.addRect(0, 0, xmax, ymax) # p.drawPath(pp) - # p.save() - # for i in xrange(3): - # col = [0, 0, 0, 200] - # col[i] = 255 - # p.setOpacity(0.3) - # p.setBrush(QBrush(QColor(*col))) - # p.drawRect(0, 0, xmax/10, xmax/10) - # p.translate(xmax/10, xmax/10) - # p.scale(1, 1.5) - # p.restore() + p.save() + for i in xrange(3): + col = [0, 0, 0, 200] + col[i] = 255 + p.setOpacity(0.3) + p.setBrush(QBrush(QColor(*col))) + p.drawRect(0, 0, xmax/10, xmax/10) + p.translate(xmax/10, xmax/10) + p.scale(1, 1.5) + p.restore() + + # p.scale(2, 2) + # p.rotate(45) + p.drawPixmap(0, 0, 2048, 2048, QPixmap(I('library.png'))) + p.drawRect(0, 0, 2048, 2048) # p.save() # p.drawLine(0, 0, 5000, 0) @@ -542,18 +628,18 @@ if __name__ == '__main__': # p.drawLine(0, 0, 5000, 0) # p.restore() - f = p.font() - f.setPointSize(24) + # f = p.font() + # f.setPointSize(24) # f.setLetterSpacing(f.PercentageSpacing, 200) # f.setUnderline(True) # f.setOverline(True) # f.setStrikeOut(True) - f.setFamily('Calibri') - p.setFont(f) + # f.setFamily('Calibri') + # p.setFont(f) + # p.setPen(QColor(0, 0, 255)) # p.scale(2, 2) # p.rotate(45) - # p.setPen(QColor(0, 0, 255)) - p.drawText(QPoint(100, 300), 'Some text ū --- Д AV ff ff') + # p.drawText(QPoint(100, 300), 'Some text ū --- Д AV ff ff') finally: p.end() for line in dev.engine.debug: diff --git a/src/calibre/ebooks/pdf/render/serialize.py b/src/calibre/ebooks/pdf/render/serialize.py index 503c299dc5..6f5b5d7a71 100644 --- a/src/calibre/ebooks/pdf/render/serialize.py +++ b/src/calibre/ebooks/pdf/render/serialize.py @@ -88,6 +88,7 @@ class Page(Stream): }) self.opacities = {} self.fonts = {} + self.xobjects = {} def set_opacity(self, opref): if opref not in self.opacities: @@ -101,6 +102,11 @@ class Page(Stream): self.fonts[fontref] = 'F%d'%len(self.fonts) return self.fonts[fontref] + def add_image(self, imgref): + if imgref not in self.xobjects: + self.xobjects[imgref] = 'Image%d'%len(self.xobjects) + return self.xobjects[imgref] + def add_resources(self): r = Dictionary() if self.opacities: @@ -113,6 +119,11 @@ class Page(Stream): for ref, name in self.fonts.iteritems(): fonts[name] = ref r['Font'] = fonts + if self.xobjects: + xobjects = Dictionary() + for ref, name in self.xobjects.iteritems(): + xobjects[name] = ref + r['XObject'] = xobjects if r: self.page_dict['Resources'] = r @@ -223,6 +234,35 @@ class HashingStream(object): if raw: self.last_char = raw[-1] +class Image(Stream): + + def __init__(self, data, w, h, depth, mask, soft_mask, dct): + Stream.__init__(self) + self.width, self.height, self.depth = w, h, depth + self.mask, self.soft_mask = mask, soft_mask + if dct: + self.filters.append(Name('DCTDecode')) + else: + self.compress = True + self.write(data) + + def add_extra_keys(self, d): + d['Type'] = Name('XObject') + d['Subtype']= Name('Image') + d['Width'] = self.width + d['Height'] = self.height + if self.depth == 1: + d['ImageMask'] = True + d['Decode'] = Array([1, 0]) + else: + d['BitsPerComponent'] = 8 + d['ColorSpace'] = Name('Device' + ('RGB' if self.depth == 32 else + 'Gray')) + if self.mask is not None: + d['Mask'] = self.mask + if self.soft_mask is not None: + d['SMask'] = self.soft_mask + class PDFStream(object): PATH_OPS = { @@ -253,6 +293,7 @@ class PDFStream(object): 'Producer':String(creator)}) self.stroke_opacities, self.fill_opacities = {}, {} self.font_manager = FontManager(self.objects, self.compress) + self.image_cache = {} @property def page_tree(self): @@ -372,6 +413,22 @@ class PDFStream(object): self.current_page.write(' Tj ') 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] = self.objects.add(imgobj) + return self.image_cache[cache_key] + + def draw_image(self, x, y, w, h, imgref): + name = self.current_page.add_image(imgref) + sx, sy = w, h + self.current_page.write('q %g 0 0 %g %g %g cm '%(sx, -sy, x, y+h)) + serialize(Name(name), self.current_page) + self.current_page.write_line(' Do Q') + def end(self): if self.current_page.getvalue(): self.end_page()