diff --git a/src/calibre/ebooks/conversion/plugins/pdf_output.py b/src/calibre/ebooks/conversion/plugins/pdf_output.py index da66a9be0d..2e4b370d50 100644 --- a/src/calibre/ebooks/conversion/plugins/pdf_output.py +++ b/src/calibre/ebooks/conversion/plugins/pdf_output.py @@ -14,50 +14,32 @@ import os from calibre.customize.conversion import OutputFormatPlugin, \ OptionRecommendation from calibre.ptempfile import TemporaryDirectory -from calibre.constants import iswindows -UNITS = [ - 'millimeter', - 'point', - 'inch' , - 'pica' , - 'didot', - 'cicero', - 'devicepixel', - ] +UNITS = ['millimeter', 'centimeter', 'point', 'inch' , 'pica' , 'didot', + 'cicero', 'devicepixel'] -PAPER_SIZES = ['b2', - 'a9', - 'executive', - 'tabloid', - 'b4', - 'b5', - 'b6', - 'b7', - 'b0', - 'b1', - 'letter', - 'b3', - 'a7', - 'a8', - 'b8', - 'b9', - 'a3', - 'a1', - 'folio', - 'c5e', - 'dle', - 'a0', - 'ledger', - 'legal', - 'a6', - 'a2', - 'b10', - 'a5', - 'comm10e', - 'a4'] +PAPER_SIZES = ['b2', 'b4', 'b5', 'b6', 'b0', 'b1', 'letter', 'b3', 'a3', 'a1', + 'a0', 'legal', 'a6', 'a2', 'a5', 'a4'] -ORIENTATIONS = ['portrait', 'landscape'] +class PDFMetadata(object): # {{{ + def __init__(self, oeb_metadata=None): + from calibre import force_unicode + from calibre.ebooks.metadata import authors_to_string + self.title = _(u'Unknown') + self.author = _(u'Unknown') + self.tags = u'' + + if oeb_metadata != None: + if len(oeb_metadata.title) >= 1: + self.title = oeb_metadata.title[0].value + if len(oeb_metadata.creator) >= 1: + self.author = authors_to_string([x.value for x in oeb_metadata.creator]) + if oeb_metadata.subject: + self.tags = u', '.join(map(unicode, oeb_metadata.subject)) + + self.title = force_unicode(self.title) + self.author = force_unicode(self.author) +# }}} class PDFOutput(OutputFormatPlugin): @@ -66,9 +48,14 @@ class PDFOutput(OutputFormatPlugin): file_type = 'pdf' options = set([ + OptionRecommendation(name='override_profile_size', recommended_value=False, + help=_('Normally, the PDF page size is set by the output profile' + ' chosen under page options. This option will cause the ' + ' page size settings under PDF Output to override the ' + ' size specified by the output profile.')), OptionRecommendation(name='unit', recommended_value='inch', level=OptionRecommendation.LOW, short_switch='u', choices=UNITS, - help=_('The unit of measure. Default is inch. Choices ' + help=_('The unit of measure for page sizes. Default is inch. Choices ' 'are %s ' 'Note: This does not override the unit for margins!') % UNITS), OptionRecommendation(name='paper_size', recommended_value='letter', @@ -80,10 +67,6 @@ class PDFOutput(OutputFormatPlugin): help=_('Custom size of the document. Use the form widthxheight ' 'EG. `123x321` to specify the width and height. ' 'This overrides any specified paper-size.')), - OptionRecommendation(name='orientation', recommended_value='portrait', - level=OptionRecommendation.LOW, choices=ORIENTATIONS, - help=_('The orientation of the page. Default is portrait. Choices ' - 'are %s') % ORIENTATIONS), OptionRecommendation(name='preserve_cover_aspect_ratio', recommended_value=False, help=_('Preserve the aspect ratio of the cover, instead' @@ -108,6 +91,11 @@ class PDFOutput(OutputFormatPlugin): OptionRecommendation(name='pdf_mono_font_size', recommended_value=16, help=_( 'The default font size for monospaced text')), + OptionRecommendation(name='uncompressed_pdf', + recommended_value=False, help=_( + 'Generate an uncompressed PDF (useful for debugging)')), + OptionRecommendation(name='old_pdf_engine', recommended_value=False, + help=_('Use the old, less capable engine to generate the PDF')), ]) def convert(self, oeb_book, output_path, input_plugin, opts, log): @@ -200,33 +188,18 @@ class PDFOutput(OutputFormatPlugin): if k in family_map: val[i].value = family_map[k] - def remove_font_specification(self): - # Qt produces image based pdfs on windows when non-generic fonts are specified - # This might change in Qt WebKit 2.3+ you will have to test. - for item in self.oeb.manifest: - if not hasattr(item.data, 'cssRules'): continue - for i, rule in enumerate(item.data.cssRules): - if rule.type != rule.STYLE_RULE: continue - ff = rule.style.getProperty('font-family') - if ff is None: continue - val = ff.propertyValue - for i in xrange(val.length): - k = icu_lower(val[i].value) - if k not in {'serif', 'sans', 'sans-serif', 'sansserif', - 'monospace', 'cursive', 'fantasy'}: - val[i].value = '' - def convert_text(self, oeb_book): - from calibre.ebooks.pdf.writer import PDFWriter + if self.opts.old_pdf_engine: + from calibre.ebooks.pdf.writer import PDFWriter + PDFWriter + else: + from calibre.ebooks.pdf.render.from_html import PDFWriter from calibre.ebooks.metadata.opf2 import OPF self.log.debug('Serializing oeb input to disk for processing...') self.get_cover_data() - if iswindows: - self.remove_font_specification() - else: - self.handle_embedded_fonts() + self.handle_embedded_fonts() with TemporaryDirectory('_pdf_out') as oeb_dir: from calibre.customize.ui import plugin_for_output_format @@ -240,7 +213,6 @@ class PDFOutput(OutputFormatPlugin): 'toc', None)) def write(self, Writer, items, toc): - from calibre.ebooks.pdf.writer import PDFMetadata writer = Writer(self.opts, self.log, cover_data=self.cover_data, toc=toc) diff --git a/src/calibre/ebooks/pdf/render/common.py b/src/calibre/ebooks/pdf/render/common.py index 297e614560..554d170656 100644 --- a/src/calibre/ebooks/pdf/render/common.py +++ b/src/calibre/ebooks/pdf/render/common.py @@ -18,6 +18,8 @@ inch = 72.0 cm = inch / 2.54 mm = cm * 0.1 pica = 12.0 +didot = 0.375 * mm +cicero = 12 * didot _W, _H = (21*cm, 29.7*cm) @@ -41,6 +43,10 @@ B3 = (_BH*2, _BW) B2 = (_BW*2, _BH*2) B1 = (_BH*4, _BW*2) B0 = (_BW*4, _BH*4) + +PAPER_SIZES = {k:globals()[k.upper()] for k in ('a0 a1 a2 a3 a4 a5 a6 b0 b1 b2' + ' b3 b4 b5 b6 letter legal').split()} + # }}} # Basic PDF datatypes {{{ @@ -79,19 +85,12 @@ class String(unicode): raw = codecs.BOM_UTF16_BE + s.encode('utf-16-be') stream.write(b'('+raw+b')') -class GlyphIndex(object): - - def __init__(self, code, compress): - self.code = code - self.compress = compress +class GlyphIndex(int): def pdf_serialize(self, stream): - if self.compress: - stream.write(pack(b'>sHs', b'(', self.code, b')')) - else: - byts = bytearray(pack(b'>H', self.code)) - stream.write('<%s>'%''.join(map( - lambda x: bytes(hex(int(x))[2:]).rjust(2, b'0'), byts))) + byts = bytearray(pack(b'>H', self)) + stream.write('<%s>'%''.join(map( + lambda x: bytes(hex(x)[2:]).rjust(2, b'0'), byts))) class Dictionary(dict): diff --git a/src/calibre/ebooks/pdf/render/engine.py b/src/calibre/ebooks/pdf/render/engine.py index 47ed68f60b..ea8a42dc8f 100644 --- a/src/calibre/ebooks/pdf/render/engine.py +++ b/src/calibre/ebooks/pdf/render/engine.py @@ -14,17 +14,13 @@ from functools import wraps from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter, QTransform, QPainterPath, QTextOption, QTextLayout, - QImage, QByteArray, QBuffer, qRgba) + QImage, QByteArray, QBuffer, qRgba, QRectF) -from calibre.constants import DEBUG from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path) from calibre.ebooks.pdf.render.common import inch, A4 from calibre.utils.fonts.sfnt.container import Sfnt from calibre.utils.fonts.sfnt.metrics import FontMetrics -XDPI = 1200 -YDPI = 1200 - Point = namedtuple('Point', 'x y') ColorState = namedtuple('ColorState', 'color opacity do') @@ -35,7 +31,8 @@ def store_error(func): try: func(self, *args, **kwargs) except: - self.errors.append(traceback.format_exc()) + self.errors_occurred = True + self.errors(traceback.format_exc()) return errh @@ -115,7 +112,7 @@ class GraphicsState(object): # {{{ elif flags & QPaintEngine.DirtyClipRegion: path = QPainterPath() for rect in state.clipRegion().rects(): - path.addRect(rect) + path.addRect(QRectF(rect)) self.ops['clip'] = (state.clipOperation(), path) def __call__(self, engine): @@ -215,9 +212,11 @@ class Font(FontMetrics): class PdfEngine(QPaintEngine): def __init__(self, file_object, page_width, page_height, left_margin, - top_margin, right_margin, bottom_margin, width, height): + 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 @@ -242,18 +241,20 @@ class PdfEngine(QPaintEngine): self.scale = sqrt(sy**2 + sx**2) self.xscale, self.yscale = sx, sy self.graphics_state = GraphicsState() - self.errors, self.debug = [], [] + 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 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 + width = self.painter().pen().widthF() if self.isActive() else 0 self.pdf.set_line_width(width) self.do_stroke = True self.do_fill = False @@ -271,7 +272,7 @@ class PdfEngine(QPaintEngine): try: self.pdf = PDFStream(self.file_object, (self.page_width, self.page_height), - compress=not DEBUG) + compress=self.compress) self.init_page() except: self.errors.append(traceback.format_exc()) @@ -281,6 +282,7 @@ class PdfEngine(QPaintEngine): def end_page(self, start_new=True): self.pdf.restore_stack() self.pdf.end_page() + self.current_page_num += 1 if start_new: self.init_page() @@ -488,7 +490,7 @@ class PdfEngine(QPaintEngine): glyph_map[g[0]] = string break if not found: - self.debug.append( + self.debug( 'Failed to find glyph->unicode mapping for text: %s'%text) break ipos += 1 @@ -546,6 +548,9 @@ class PdfEngine(QPaintEngine): 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) @@ -558,23 +563,26 @@ class PdfDevice(QPaintDevice): # {{{ def __init__(self, file_object, page_size=A4, left_margin=inch, - top_margin=inch, right_margin=inch, bottom_margin=inch): + top_margin=inch, right_margin=inch, bottom_margin=inch, + xdpi=1200, ydpi=1200, errors=print, debug=print, compress=True): QPaintDevice.__init__(self) + self.xdpi, self.ydpi = xdpi, ydpi self.page_width, self.page_height = page_size self.body_width = self.page_width - left_margin - right_margin self.body_height = self.page_height - top_margin - bottom_margin self.engine = PdfEngine(file_object, self.page_width, self.page_height, left_margin, top_margin, right_margin, - bottom_margin, self.width(), self.height()) + bottom_margin, self.width(), self.height(), + errors=errors, debug=debug, compress=compress) def paintEngine(self): return self.engine def metric(self, m): if m in (self.PdmDpiX, self.PdmPhysicalDpiX): - return XDPI + return self.xdpi if m in (self.PdmDpiY, self.PdmPhysicalDpiY): - return YDPI + return self.ydpi if m == self.PdmDepth: return 32 if m == self.PdmNumColors: @@ -584,10 +592,32 @@ class PdfDevice(QPaintDevice): # {{{ if m == self.PdmHeightMM: return int(round(self.body_height * 0.35277777777778)) if m == self.PdmWidth: - return int(round(self.body_width * XDPI / 72.0)) + return int(round(self.body_width * self.xdpi / 72.0)) if m == self.PdmHeight: - return int(round(self.body_height * YDPI / 72.0)) + return int(round(self.body_height * self.ydpi / 72.0)) return 0 + + def end_page(self, start_new=True): + self.engine.end_page(start_new=start_new) + + def init_page(self): + self.engine.init_page() + + @property + def current_page_num(self): + return self.engine.current_page_num + + @property + def errors_occurred(self): + return self.engine.errors_occurred + + def to_px(self, pt, vertical=True): + return pt * (self.height()/self.page_height if vertical else + self.width()/self.page_width) + + def set_metadata(self, *args, **kwargs): + self.engine.set_metadata(*args, **kwargs) + # }}} if __name__ == '__main__': @@ -596,7 +626,7 @@ if __name__ == '__main__': app = QApplication([]) p = QPainter() with open('/tmp/painter.pdf', 'wb') as f: - dev = PdfDevice(f) + dev = PdfDevice(f, compress=False) p.begin(dev) xmax, ymax = p.viewport().width(), p.viewport().height() try: @@ -642,9 +672,6 @@ if __name__ == '__main__': # p.drawText(QPoint(100, 300), 'Some text ū --- Д AV ff ff') finally: p.end() - for line in dev.engine.debug: - print (line) - if dev.engine.errors: - for err in dev.engine.errors: print (err) + if dev.engine.errors_occurred: raise SystemExit(1) diff --git a/src/calibre/ebooks/pdf/render/from_html.py b/src/calibre/ebooks/pdf/render/from_html.py new file mode 100644 index 0000000000..544eafe8a4 --- /dev/null +++ b/src/calibre/ebooks/pdf/render/from_html.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2012, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import json +from future_builtins import map +from math import floor + +from PyQt4.Qt import (QObject, QPainter, Qt, QSize, QString, QTimer, + pyqtProperty, QEventLoop, QPixmap, QRect) +from PyQt4.QtWebKit import QWebView, QWebPage, QWebSettings + +from calibre import fit_image +from calibre.ebooks.oeb.display.webview import load_html +from calibre.ebooks.pdf.render.engine import PdfDevice +from calibre.ebooks.pdf.render.common import (inch, cm, mm, pica, cicero, + didot, PAPER_SIZES) +from calibre.ebooks.pdf.outline_writer import Outline + +def get_page_size(opts, for_comic=False): # {{{ + use_profile = not (opts.override_profile_size or + opts.output_profile.short_name == 'default') + if use_profile: + w = (opts.output_profile.comic_screen_size[0] if for_comic else + opts.output_profile.width) + h = (opts.output_profile.comic_screen_size[1] if for_comic else + opts.output_profile.height) + dpi = opts.output_profile.dpi + factor = 72.0 / dpi + page_size = (factor * w, factor * h) + else: + page_size = None + if opts.custom_size != None: + width, sep, height = opts.custom_size.partition('x') + if height: + try: + width = float(width) + height = float(height) + except: + pass + else: + if opts.unit == 'devicepixel': + factor = 72.0 / opts.output_profile.dpi + else: + {'point':1.0, 'inch':inch, 'cicero':cicero, + 'didot':didot, 'pica':pica, 'millimeter':mm, + 'centimeter':cm}[opts.unit] + page_size = (factor*width, factor*height) + if page_size is None: + page_size = PAPER_SIZES[opts.paper_size] + return page_size +# }}} + +class Page(QWebPage): # {{{ + + def __init__(self, opts, log): + self.log = log + QWebPage.__init__(self) + settings = self.settings() + settings.setFontSize(QWebSettings.DefaultFontSize, + opts.pdf_default_font_size) + settings.setFontSize(QWebSettings.DefaultFixedFontSize, + opts.pdf_mono_font_size) + settings.setFontSize(QWebSettings.MinimumLogicalFontSize, 8) + settings.setFontSize(QWebSettings.MinimumFontSize, 8) + + std = {'serif':opts.pdf_serif_family, 'sans':opts.pdf_sans_family, + 'mono':opts.pdf_mono_family}.get(opts.pdf_standard_font, + opts.pdf_serif_family) + if std: + settings.setFontFamily(QWebSettings.StandardFont, std) + if opts.pdf_serif_family: + settings.setFontFamily(QWebSettings.SerifFont, opts.pdf_serif_family) + if opts.pdf_sans_family: + settings.setFontFamily(QWebSettings.SansSerifFont, + opts.pdf_sans_family) + if opts.pdf_mono_family: + settings.setFontFamily(QWebSettings.FixedFont, opts.pdf_mono_family) + + def javaScriptConsoleMessage(self, msg, lineno, msgid): + self.log.debug(u'JS:', unicode(msg)) + + def javaScriptAlert(self, frame, msg): + self.log(unicode(msg)) +# }}} + +def draw_image_page(page_rect, painter, p, preserve_aspect_ratio=True): + if preserve_aspect_ratio: + aspect_ratio = float(p.width())/p.height() + nw, nh = page_rect.width(), page_rect.height() + if aspect_ratio > 1: + nh = int(page_rect.width()/aspect_ratio) + else: # Width is smaller than height + nw = page_rect.height()*aspect_ratio + __, nnw, nnh = fit_image(nw, nh, page_rect.width(), + page_rect.height()) + dx = int((page_rect.width() - nnw)/2.) + dy = int((page_rect.height() - nnh)/2.) + page_rect.moveTo(dx, dy) + page_rect.setHeight(nnh) + page_rect.setWidth(nnw) + painter.drawPixmap(page_rect, p, p.rect()) + +class PDFWriter(QObject): + + def _pass_json_value_getter(self): + val = json.dumps(self.bridge_value) + return QString(val) + + def _pass_json_value_setter(self, value): + self.bridge_value = json.loads(unicode(value)) + + _pass_json_value = pyqtProperty(QString, fget=_pass_json_value_getter, + fset=_pass_json_value_setter) + + def __init__(self, opts, log, cover_data=None, toc=None): + from calibre.gui2 import is_ok_to_use_qt + if not is_ok_to_use_qt(): + raise Exception('Not OK to use Qt') + QObject.__init__(self) + + self.logger = self.log = log + self.opts = opts + self.cover_data = cover_data + self.paged_js = None + self.toc = toc + + self.loop = QEventLoop() + self.view = QWebView() + self.page = Page(opts, self.log) + self.view.setPage(self.page) + self.view.setRenderHints(QPainter.Antialiasing| + QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform) + self.view.loadFinished.connect(self.render_html, + type=Qt.QueuedConnection) + for x in (Qt.Horizontal, Qt.Vertical): + self.view.page().mainFrame().setScrollBarPolicy(x, + Qt.ScrollBarAlwaysOff) + + def dump(self, items, out_stream, pdf_metadata): + opts = self.opts + self.outline = Outline(self.toc, items) + page_size = get_page_size(self.opts) + dpi = min(self.opts.input_profile.dpi, 150) + ml, mr = opts.margin_left, opts.margin_right + margin_side = min(ml, mr) + ml, mr = ml - margin_side, mr - margin_side + self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml, + top_margin=0, right_margin=mr, bottom_margin=0, + xdpi=dpi, ydpi=dpi, errors=self.log.error, + debug=self.log.debug, compress=not + opts.uncompressed_pdf) + + self.page.setViewportSize(QSize(self.doc.width(), self.doc.height())) + self.render_queue = items + self.first_page = True + + # TODO: Test margins + mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom)) + ms = self.doc.to_px(margin_side, vertical=False) + self.margin_top, self.margin_size, self.margin_bottom = map( + lambda x:int(floor(x)), (mt, ms, mb)) + + self.painter = QPainter(self.doc) + self.doc.set_metadata(title=pdf_metadata.title, + author=pdf_metadata.author, + tags=pdf_metadata.tags) + self.painter.save() + try: + if self.cover_data is not None: + p = QPixmap() + p.loadFromData(self.cover_data) + if not p.isNull(): + draw_image_page(QRect(0, 0, self.doc.width(), self.doc.height()), + self.painter, p, + preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio) + self.doc.end_page() + finally: + self.painter.restore() + + QTimer.singleShot(0, self.render_book) + self.loop.exec_() + + # TODO: Outline and links + self.painter.end() + + if self.doc.errors_occurred: + raise Exception('PDF Output failed, see log for details') + + def render_book(self): + if self.doc.errors_occurred: + return self.loop.exit(1) + try: + if not self.render_queue: + self.loop.exit() + else: + self.render_next() + except: + self.logger.exception('Rendering failed') + self.loop.exit(1) + + def render_next(self): + item = unicode(self.render_queue.pop(0)) + + self.logger.debug('Processing %s...' % item) + self.current_item = item + load_html(item, self.view) + + def render_html(self, ok): + if ok: + try: + self.do_paged_render() + except: + self.log.exception('Rendering failed') + self.loop.exit(1) + else: + # The document is so corrupt that we can't render the page. + self.logger.error('Document cannot be rendered.') + self.loop.exit(1) + return + self.render_book() + + @property + def current_page_num(self): + return self.doc.current_page_num + + def do_paged_render(self): + if self.paged_js is None: + from calibre.utils.resources import compiled_coffeescript + self.paged_js = compiled_coffeescript('ebooks.oeb.display.utils') + self.paged_js += compiled_coffeescript('ebooks.oeb.display.indexing') + self.paged_js += compiled_coffeescript('ebooks.oeb.display.paged') + + self.view.page().mainFrame().addToJavaScriptWindowObject("py_bridge", self) + evaljs = self.view.page().mainFrame().evaluateJavaScript + evaljs(self.paged_js) + evaljs(''' + py_bridge.__defineGetter__('value', function() { + return JSON.parse(this._pass_json_value); + }); + py_bridge.__defineSetter__('value', function(val) { + this._pass_json_value = JSON.stringify(val); + }); + + document.body.style.backgroundColor = "white"; + paged_display.set_geometry(1, %d, %d, %d); + paged_display.layout(); + paged_display.fit_images(); + '''%(self.margin_top, self.margin_size, self.margin_bottom)) + + mf = self.view.page().mainFrame() + start_page = self.current_page_num + while True: + if not self.first_page: + self.doc.init_page() + self.first_page = False + self.painter.save() + try: + mf.render(self.painter) + nsl = evaljs('paged_display.next_screen_location()').toInt() + if not nsl[1] or nsl[0] <= 0: + break + evaljs('window.scrollTo(%d, 0)'%nsl[0]) + self.doc.end_page() + finally: + self.painter.restore() + if self.doc.errors_occurred: + break + + self.bridge_value = tuple(self.outline.anchor_map[self.current_item]) + evaljs('py_bridge.value = book_indexing.anchor_positions(py_bridge.value)') + amap = self.bridge_value + if not isinstance(amap, dict): + amap = {} # Some javascript error occurred + self.outline.set_pos(self.current_item, None, start_page, 0) + for anchor, x in amap.iteritems(): + pagenum, ypos = x + self.outline.set_pos(self.current_item, anchor, start_page + pagenum, ypos) + diff --git a/src/calibre/ebooks/pdf/render/serialize.py b/src/calibre/ebooks/pdf/render/serialize.py index 9fe89cfafb..51d81f1b91 100644 --- a/src/calibre/ebooks/pdf/render/serialize.py +++ b/src/calibre/ebooks/pdf/render/serialize.py @@ -303,6 +303,14 @@ class PDFStream(object): def catalog(self): return self.objects[1] + 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) @@ -409,7 +417,7 @@ class PDFStream(object): self.current_page.write('%s Tm '%' '.join(map(type(u''), transform))) for x, y, glyph_id in glyphs: self.current_page.write('%g %g Td '%(x, y)) - serialize(GlyphIndex(glyph_id, self.compress), self.current_page) + serialize(GlyphIndex(glyph_id), self.current_page) self.current_page.write(' Tj ') self.current_page.write_line(b' ET') diff --git a/src/calibre/ebooks/pdf/writer.py b/src/calibre/ebooks/pdf/writer.py index 76ab6b9096..46a3e92821 100644 --- a/src/calibre/ebooks/pdf/writer.py +++ b/src/calibre/ebooks/pdf/writer.py @@ -9,18 +9,16 @@ Write content to PDF. ''' import os, shutil, json -from future_builtins import map from PyQt4.Qt import (QEventLoop, QObject, QPrinter, QSizeF, Qt, QPainter, QPixmap, QTimer, pyqtProperty, QString, QSize) from PyQt4.QtWebKit import QWebView, QWebPage, QWebSettings from calibre.ptempfile import PersistentTemporaryDirectory -from calibre.ebooks.pdf.pageoptions import (unit, paper_size, orientation) +from calibre.ebooks.pdf.pageoptions import (unit, paper_size) from calibre.ebooks.pdf.outline_writer import Outline -from calibre.ebooks.metadata import authors_to_string from calibre.ptempfile import PersistentTemporaryFile -from calibre import (__appname__, __version__, fit_image, isosx, force_unicode) +from calibre import (__appname__, __version__, fit_image, isosx) from calibre.ebooks.oeb.display.webview import load_html def get_custom_size(opts): @@ -72,7 +70,6 @@ def get_pdf_printer(opts, for_comic=False, output_file_name=None): # {{{ else: printer.setPageMargins(opts.margin_left, opts.margin_top, opts.margin_right, opts.margin_bottom, QPrinter.Point) - printer.setOrientation(orientation(opts.orientation)) printer.setOutputFormat(QPrinter.PdfFormat) printer.setFullPage(for_comic) if output_file_name: @@ -103,24 +100,6 @@ def draw_image_page(printer, painter, p, preserve_aspect_ratio=True): painter.drawPixmap(page_rect, p, p.rect()) -class PDFMetadata(object): # {{{ - def __init__(self, oeb_metadata=None): - self.title = _(u'Unknown') - self.author = _(u'Unknown') - self.tags = u'' - - if oeb_metadata != None: - if len(oeb_metadata.title) >= 1: - self.title = oeb_metadata.title[0].value - if len(oeb_metadata.creator) >= 1: - self.author = authors_to_string([x.value for x in oeb_metadata.creator]) - if oeb_metadata.subject: - self.tags = u', '.join(map(unicode, oeb_metadata.subject)) - - self.title = force_unicode(self.title) - self.author = force_unicode(self.author) -# }}} - class Page(QWebPage): # {{{ def __init__(self, opts, log): diff --git a/src/calibre/gui2/convert/pdf_output.py b/src/calibre/gui2/convert/pdf_output.py index a2bfcc667f..e0674d066c 100644 --- a/src/calibre/gui2/convert/pdf_output.py +++ b/src/calibre/gui2/convert/pdf_output.py @@ -18,16 +18,17 @@ class PluginWidget(Widget, Ui_Form): ICON = I('mimetypes/pdf.png') def __init__(self, parent, get_option, get_help, db=None, book_id=None): - Widget.__init__(self, parent, ['paper_size', 'custom_size', - 'orientation', 'preserve_cover_aspect_ratio', 'pdf_serif_family', + Widget.__init__(self, parent, [ + 'override_profile_size', 'paper_size', 'custom_size', + 'preserve_cover_aspect_ratio', 'pdf_serif_family', 'unit', 'pdf_sans_family', 'pdf_mono_family', 'pdf_standard_font', 'pdf_default_font_size', 'pdf_mono_font_size']) self.db, self.book_id = db, book_id for x in get_option('paper_size').option.choices: self.opt_paper_size.addItem(x) - for x in get_option('orientation').option.choices: - self.opt_orientation.addItem(x) + for x in get_option('unit').option.choices: + self.opt_unit.addItem(x) for x in get_option('pdf_standard_font').option.choices: self.opt_pdf_standard_font.addItem(x) diff --git a/src/calibre/gui2/convert/pdf_output.ui b/src/calibre/gui2/convert/pdf_output.ui index 5f77f526d0..5e3c4c9137 100644 --- a/src/calibre/gui2/convert/pdf_output.ui +++ b/src/calibre/gui2/convert/pdf_output.ui @@ -14,7 +14,27 @@ Form - + + QFormLayout::ExpandingFieldsGrow + + + + + <b>Note:</b> The paper size settings below only take effect if you enable the "Override" checkbox below. Otherwise the size from the output profile will be used. + + + true + + + + + + + &Override paper size set in output profile + + + + &Paper Size: @@ -24,21 +44,8 @@ - - - - - - - &Orientation: - - - opt_orientation - - - - + @@ -51,7 +58,24 @@ - + + + + + + + + &Unit: + + + opt_unit + + + + + + + @@ -60,19 +84,6 @@ - - - - Qt::Vertical - - - - 20 - 213 - - - - @@ -159,15 +170,18 @@ - - - - <b>Note:</b> The paper size settings below only take effect if you have set the output profile to the default output profile. Otherwise the output profile will override these settings. + + + + Qt::Vertical - - true + + + 20 + 213 + - +