mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 18:24:30 -04:00
Images work with the pdf backend
This commit is contained in:
parent
8a6f1b11ba
commit
3538bbe4dc
@ -132,6 +132,7 @@ class Stream(BytesIO):
|
|||||||
def __init__(self, compress=False):
|
def __init__(self, compress=False):
|
||||||
BytesIO.__init__(self)
|
BytesIO.__init__(self)
|
||||||
self.compress = compress
|
self.compress = compress
|
||||||
|
self.filters = Array()
|
||||||
|
|
||||||
def add_extra_keys(self, d):
|
def add_extra_keys(self, d):
|
||||||
pass
|
pass
|
||||||
@ -139,7 +140,7 @@ class Stream(BytesIO):
|
|||||||
def pdf_serialize(self, stream):
|
def pdf_serialize(self, stream):
|
||||||
raw = self.getvalue()
|
raw = self.getvalue()
|
||||||
dl = len(raw)
|
dl = len(raw)
|
||||||
filters = Array()
|
filters = self.filters
|
||||||
if self.compress:
|
if self.compress:
|
||||||
filters.append(Name('FlateDecode'))
|
filters.append(Name('FlateDecode'))
|
||||||
raw = zlib.compress(raw)
|
raw = zlib.compress(raw)
|
||||||
|
@ -13,7 +13,8 @@ from collections import namedtuple
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter,
|
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.constants import DEBUG
|
||||||
from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path)
|
from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path)
|
||||||
@ -245,6 +246,9 @@ class PdfEngine(QPaintEngine):
|
|||||||
self.text_option = QTextOption()
|
self.text_option = QTextOption()
|
||||||
self.text_option.setWrapMode(QTextOption.NoWrap)
|
self.text_option.setWrapMode(QTextOption.NoWrap)
|
||||||
self.fonts = {}
|
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):
|
def init_page(self):
|
||||||
self.pdf.transform(self.pdf_system)
|
self.pdf.transform(self.pdf_system)
|
||||||
@ -296,11 +300,88 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawPixmap(self, rect, pixmap, source_rect):
|
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
|
@store_error
|
||||||
def drawImage(self, rect, image, source_rect, flags=Qt.AutoColor):
|
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
|
@store_error
|
||||||
def updateState(self, state):
|
def updateState(self, state):
|
||||||
@ -510,8 +591,8 @@ class PdfDevice(QPaintDevice): # {{{
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from PyQt4.Qt import (QBrush, QColor, QPoint)
|
from PyQt4.Qt import (QBrush, QColor, QPoint, QPixmap)
|
||||||
QBrush, QColor, QPoint
|
QBrush, QColor, QPoint, QPixmap
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
p = QPainter()
|
p = QPainter()
|
||||||
with open('/tmp/painter.pdf', 'wb') as f:
|
with open('/tmp/painter.pdf', 'wb') as f:
|
||||||
@ -525,16 +606,21 @@ if __name__ == '__main__':
|
|||||||
# pp = QPainterPath()
|
# pp = QPainterPath()
|
||||||
# pp.addRect(0, 0, xmax, ymax)
|
# pp.addRect(0, 0, xmax, ymax)
|
||||||
# p.drawPath(pp)
|
# p.drawPath(pp)
|
||||||
# p.save()
|
p.save()
|
||||||
# for i in xrange(3):
|
for i in xrange(3):
|
||||||
# col = [0, 0, 0, 200]
|
col = [0, 0, 0, 200]
|
||||||
# col[i] = 255
|
col[i] = 255
|
||||||
# p.setOpacity(0.3)
|
p.setOpacity(0.3)
|
||||||
# p.setBrush(QBrush(QColor(*col)))
|
p.setBrush(QBrush(QColor(*col)))
|
||||||
# p.drawRect(0, 0, xmax/10, xmax/10)
|
p.drawRect(0, 0, xmax/10, xmax/10)
|
||||||
# p.translate(xmax/10, xmax/10)
|
p.translate(xmax/10, xmax/10)
|
||||||
# p.scale(1, 1.5)
|
p.scale(1, 1.5)
|
||||||
# p.restore()
|
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.save()
|
||||||
# p.drawLine(0, 0, 5000, 0)
|
# p.drawLine(0, 0, 5000, 0)
|
||||||
@ -542,18 +628,18 @@ if __name__ == '__main__':
|
|||||||
# p.drawLine(0, 0, 5000, 0)
|
# p.drawLine(0, 0, 5000, 0)
|
||||||
# p.restore()
|
# p.restore()
|
||||||
|
|
||||||
f = p.font()
|
# f = p.font()
|
||||||
f.setPointSize(24)
|
# f.setPointSize(24)
|
||||||
# f.setLetterSpacing(f.PercentageSpacing, 200)
|
# f.setLetterSpacing(f.PercentageSpacing, 200)
|
||||||
# f.setUnderline(True)
|
# f.setUnderline(True)
|
||||||
# f.setOverline(True)
|
# f.setOverline(True)
|
||||||
# f.setStrikeOut(True)
|
# f.setStrikeOut(True)
|
||||||
f.setFamily('Calibri')
|
# f.setFamily('Calibri')
|
||||||
p.setFont(f)
|
# p.setFont(f)
|
||||||
|
# p.setPen(QColor(0, 0, 255))
|
||||||
# p.scale(2, 2)
|
# p.scale(2, 2)
|
||||||
# p.rotate(45)
|
# 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:
|
finally:
|
||||||
p.end()
|
p.end()
|
||||||
for line in dev.engine.debug:
|
for line in dev.engine.debug:
|
||||||
|
@ -88,6 +88,7 @@ class Page(Stream):
|
|||||||
})
|
})
|
||||||
self.opacities = {}
|
self.opacities = {}
|
||||||
self.fonts = {}
|
self.fonts = {}
|
||||||
|
self.xobjects = {}
|
||||||
|
|
||||||
def set_opacity(self, opref):
|
def set_opacity(self, opref):
|
||||||
if opref not in self.opacities:
|
if opref not in self.opacities:
|
||||||
@ -101,6 +102,11 @@ class Page(Stream):
|
|||||||
self.fonts[fontref] = 'F%d'%len(self.fonts)
|
self.fonts[fontref] = 'F%d'%len(self.fonts)
|
||||||
return self.fonts[fontref]
|
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):
|
def add_resources(self):
|
||||||
r = Dictionary()
|
r = Dictionary()
|
||||||
if self.opacities:
|
if self.opacities:
|
||||||
@ -113,6 +119,11 @@ class Page(Stream):
|
|||||||
for ref, name in self.fonts.iteritems():
|
for ref, name in self.fonts.iteritems():
|
||||||
fonts[name] = ref
|
fonts[name] = ref
|
||||||
r['Font'] = fonts
|
r['Font'] = fonts
|
||||||
|
if self.xobjects:
|
||||||
|
xobjects = Dictionary()
|
||||||
|
for ref, name in self.xobjects.iteritems():
|
||||||
|
xobjects[name] = ref
|
||||||
|
r['XObject'] = xobjects
|
||||||
if r:
|
if r:
|
||||||
self.page_dict['Resources'] = r
|
self.page_dict['Resources'] = r
|
||||||
|
|
||||||
@ -223,6 +234,35 @@ class HashingStream(object):
|
|||||||
if raw:
|
if raw:
|
||||||
self.last_char = raw[-1]
|
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):
|
class PDFStream(object):
|
||||||
|
|
||||||
PATH_OPS = {
|
PATH_OPS = {
|
||||||
@ -253,6 +293,7 @@ class PDFStream(object):
|
|||||||
'Producer':String(creator)})
|
'Producer':String(creator)})
|
||||||
self.stroke_opacities, self.fill_opacities = {}, {}
|
self.stroke_opacities, self.fill_opacities = {}, {}
|
||||||
self.font_manager = FontManager(self.objects, self.compress)
|
self.font_manager = FontManager(self.objects, self.compress)
|
||||||
|
self.image_cache = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def page_tree(self):
|
def page_tree(self):
|
||||||
@ -372,6 +413,22 @@ class PDFStream(object):
|
|||||||
self.current_page.write(' Tj ')
|
self.current_page.write(' Tj ')
|
||||||
self.current_page.write_line(b' ET')
|
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):
|
def end(self):
|
||||||
if self.current_page.getvalue():
|
if self.current_page.getvalue():
|
||||||
self.end_page()
|
self.end_page()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user