diff --git a/manual/conversion.rst b/manual/conversion.rst index bf451d0980..14710f3f6a 100644 --- a/manual/conversion.rst +++ b/manual/conversion.rst @@ -750,8 +750,61 @@ If this property is detected by |app|, the following custom properties are recog opf.series opf.seriesindex -In addition to this, you can specify the picture to use as the cover by naming it ``opf.cover`` (right click, Picture->Options->Name) in the ODT. If no picture with this name is found, the 'smart' method is used. -As the cover detection might result in double covers in certain output formats, the process will remove the paragraph (only if the only content is the cover!) from the document. But this works only with the named picture! +In addition to this, you can specify the picture to use as the cover by naming +it ``opf.cover`` (right click, Picture->Options->Name) in the ODT. If no +picture with this name is found, the 'smart' method is used. As the cover +detection might result in double covers in certain output formats, the process +will remove the paragraph (only if the only content is the cover!) from the +document. But this works only with the named picture! To disable cover detection you can set the custom property ``opf.nocover`` ('Yes or No' type) to Yes in advanced mode. +Converting to PDF +~~~~~~~~~~~~~~~~~~~ + +The first, most important, setting to decide on when converting to PDF is the page +size. By default, |app| uses a page size defined by the current +:guilabel:`Output profile`. So if your output profile is set to Kindle, |app| +will create a PDF with page size suitable for viewing on the small kindle +screen. However, if you view this PDF file on a computer screen, then it will +appear to have too large fonts. To create "normal" sized PDFs, use the override +page size option under :guilabel:`PDF Output` in the conversion dialog. + +You can insert arbitrary headers and footers on each page of the PDF by +specifying header and footer templates. Templates are just snippets of HTML +code that get rendered in the header and footer locations. For example, to +display page numbers centered at the bottom of every page, in green, use the following +footer template:: + +
Page _PAGENUM_
+ +|app| will automatically replace _PAGENUM_ with the current page number. You +can even put different content on even and odd pages, for example the following +header template will show the title on odd pages and the author on even pages:: + +_AUTHOR__TITLE_
+ +|app| will automatically replace _TITLE_ and _AUTHOR_ with the title and author +of the document being converted. You can also display text at the left and +right edges and change the font size, as demonstrated with this header +template:: + +_TITLE_
_AUTHOR_
_SECTION_
+ +_SECTION_ is replaced by whatever the name of the current section is. These +names are taken from the metadata Table of Contents in the document (the PDF +Outline). If the document has no table of contents then it will be replaced by +empty text. If a single PDF page has multiple sections, the first section on +the page will be used. + +.. note:: When adding headers and footers make sure you set the page top and + bottom margins to large enough values, under the Page Setup section of the + conversion dialog. + diff --git a/resources/compiled_coffeescript.zip b/resources/compiled_coffeescript.zip index 782a26a0c2..d5b7086600 100644 Binary files a/resources/compiled_coffeescript.zip and b/resources/compiled_coffeescript.zip differ diff --git a/src/calibre/ebooks/conversion/plugins/pdf_output.py b/src/calibre/ebooks/conversion/plugins/pdf_output.py index c473931aef..dc943eec30 100644 --- a/src/calibre/ebooks/conversion/plugins/pdf_output.py +++ b/src/calibre/ebooks/conversion/plugins/pdf_output.py @@ -104,13 +104,11 @@ class PDFOutput(OutputFormatPlugin): 'specify a footer template, it will take precedence ' 'over this option.')), OptionRecommendation(name='pdf_footer_template', recommended_value=None, - help=_('An HTML template used to generate footers on every page.' - ' The string _PAGENUM_ will be replaced by the current page' - ' number.')), + help=_('An HTML template used to generate %s on every page.' + ' The strings _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_ will be replaced by their current values.')%_('footers')), OptionRecommendation(name='pdf_header_template', recommended_value=None, - help=_('An HTML template used to generate headers on every page.' - ' The string _PAGENUM_ will be replaced by the current page' - ' number.')), + help=_('An HTML template used to generate %s on every page.' + ' The strings _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_ will be replaced by their current values.')%_('headers')), ]) def convert(self, oeb_book, output_path, input_plugin, opts, log): diff --git a/src/calibre/ebooks/oeb/display/paged.coffee b/src/calibre/ebooks/oeb/display/paged.coffee index 405b26a9f6..f97f1b3cf8 100644 --- a/src/calibre/ebooks/oeb/display/paged.coffee +++ b/src/calibre/ebooks/oeb/display/paged.coffee @@ -33,6 +33,7 @@ class PagedDisplay this.header_template = null this.header = null this.footer = null + this.hf_style = null read_document_margins: () -> # Read page margins from the document. First checks for an @page rule. @@ -184,15 +185,22 @@ class PagedDisplay # log('Time to layout:', new Date().getTime() - start_time) return sm - create_header_footer: () -> + create_header_footer: (uuid) -> if this.header_template != null this.header = document.createElement('div') this.header.setAttribute('style', "overflow:hidden; display:block; position:absolute; left:#{ this.side_margin }px; top: 0px; height: #{ this.margin_top }px; width: #{ this.col_width }px; margin: 0; padding: 0") + this.header.setAttribute('id', 'pdf_page_header_'+uuid) document.body.appendChild(this.header) if this.footer_template != null this.footer = document.createElement('div') this.footer.setAttribute('style', "overflow:hidden; display:block; position:absolute; left:#{ this.side_margin }px; top: #{ window.innerHeight - this.margin_bottom }px; height: #{ this.margin_bottom }px; width: #{ this.col_width }px; margin: 0; padding: 0") + this.footer.setAttribute('id', 'pdf_page_footer_'+uuid) document.body.appendChild(this.footer) + if this.header != null or this.footer != null + this.hf_uuid = uuid + this.hf_style = document.createElement('style') + this.hf_style.setAttribute('type', 'text/css') + document.head.appendChild(this.hf_style) this.update_header_footer(1) position_header_footer: () -> @@ -203,10 +211,16 @@ class PagedDisplay this.footer.style.setProperty('left', left+'px') update_header_footer: (pagenum) -> + if this.hf_style != null + if pagenum%2 == 1 then cls = "even_page" else cls = "odd_page" + this.hf_style.innerHTML = "#pdf_page_header_#{ this.hf_uuid } .#{ cls }, #pdf_page_footer_#{ this.hf_uuid } .#{ cls } { display: none }" + title = py_bridge.title() + author = py_bridge.author() + section = py_bridge.section() if this.header != null - this.header.innerHTML = this.header_template.replace(/_PAGENUM_/g, pagenum+"") + this.header.innerHTML = this.header_template.replace(/_PAGENUM_/g, pagenum+"").replace(/_TITLE_/g, title+"").replace(/_AUTHOR_/g, author+"").replace(/_SECTION_/g, section+"") if this.footer != null - this.footer.innerHTML = this.footer_template.replace(/_PAGENUM_/g, pagenum+"") + this.footer.innerHTML = this.footer_template.replace(/_PAGENUM_/g, pagenum+"").replace(/_TITLE_/g, title+"").replace(/_AUTHOR_/g, author+"").replace(/_SECTION_/g, section+"") fit_images: () -> # Ensure no images are wider than the available width in a column. Note diff --git a/src/calibre/ebooks/pdf/render/from_html.py b/src/calibre/ebooks/pdf/render/from_html.py index 525bed16a3..ae1083bb56 100644 --- a/src/calibre/ebooks/pdf/render/from_html.py +++ b/src/calibre/ebooks/pdf/render/from_html.py @@ -130,6 +130,18 @@ class PDFWriter(QObject): _pass_json_value = pyqtProperty(QString, fget=_pass_json_value_getter, fset=_pass_json_value_setter) + @pyqtSlot(result=unicode) + def title(self): + return self.doc_title + + @pyqtSlot(result=unicode) + def author(self): + return self.doc_author + + @pyqtSlot(result=unicode) + def section(self): + return self.current_section + 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(): @@ -154,6 +166,7 @@ class PDFWriter(QObject): self.view.page().mainFrame().setScrollBarPolicy(x, Qt.ScrollBarAlwaysOff) self.report_progress = lambda x, y: x + self.current_section = '' def dump(self, items, out_stream, pdf_metadata): opts = self.opts @@ -170,9 +183,13 @@ class PDFWriter(QObject): opts.uncompressed_pdf, mark_links=opts.pdf_mark_links) self.footer = opts.pdf_footer_template - if self.footer is None and opts.pdf_page_numbers: + if self.footer: + self.footer = self.footer.strip() + if not self.footer and opts.pdf_page_numbers: self.footer = '_PAGENUM_
' self.header = opts.pdf_header_template + if self.header: + self.header = self.header.strip() min_margin = 36 if self.footer and opts.margin_bottom < min_margin: self.log.warn('Bottom margin is too small for footer, increasing it.') @@ -192,6 +209,8 @@ class PDFWriter(QObject): self.doc.set_metadata(title=pdf_metadata.title, author=pdf_metadata.author, tags=pdf_metadata.tags) + self.doc_title = pdf_metadata.title + self.doc_author = pdf_metadata.author self.painter.save() try: if self.cover_data is not None: @@ -273,13 +292,34 @@ class PDFWriter(QObject): self.loop.processEvents(self.loop.ExcludeUserInputEvents) evaljs('document.getElementById("MathJax_Message").style.display="none";') + def get_sections(self, anchor_map): + sections = {} + ci = os.path.abspath(os.path.normcase(self.current_item)) + if self.toc is not None: + for toc in self.toc.flat(): + path = toc.abspath or None + frag = toc.fragment or None + if path is None: + continue + path = os.path.abspath(os.path.normcase(path)) + if path == ci: + col = 0 + if frag and frag in anchor_map: + col = anchor_map[frag]['column'] + if col not in sections: + sections[col] = toc.text or _('Untitled') + + return sections + def do_paged_render(self): if self.paged_js is None: + import uuid from calibre.utils.resources import compiled_coffeescript as cc self.paged_js = cc('ebooks.oeb.display.utils') self.paged_js += cc('ebooks.oeb.display.indexing') self.paged_js += cc('ebooks.oeb.display.paged') self.paged_js += cc('ebooks.oeb.display.mathjax') + self.hf_uuid = str(uuid.uuid4()).replace('-', '') self.view.page().mainFrame().addToJavaScriptWindowObject("py_bridge", self) self.view.page().longjs_counter = 0 @@ -305,6 +345,8 @@ class PDFWriter(QObject): amap = self.bridge_value if not isinstance(amap, dict): amap = {'links':[], 'anchors':{}} # Some javascript error occurred + sections = self.get_sections(amap['anchors']) + col = 0 if self.header: self.bridge_value = self.header @@ -313,12 +355,14 @@ class PDFWriter(QObject): self.bridge_value = self.footer evaljs('paged_display.footer_template = py_bridge.value') if self.header or self.footer: - evaljs('paged_display.create_header_footer();') + evaljs('paged_display.create_header_footer("%s");'%self.hf_uuid) start_page = self.current_page_num mf = self.view.page().mainFrame() while True: + if col in sections: + self.current_section = sections[col] self.doc.init_page() if self.header or self.footer: evaljs('paged_display.update_header_footer(%d)'%self.current_page_num) @@ -332,8 +376,10 @@ class PDFWriter(QObject): evaljs('window.scrollTo(%d, 0); paged_display.position_header_footer();'%nsl[0]) if self.doc.errors_occurred: break + col += 1 if not self.doc.errors_occurred: self.doc.add_links(self.current_item, start_page, amap['links'], amap['anchors']) + diff --git a/src/calibre/gui2/convert/pdf_output.py b/src/calibre/gui2/convert/pdf_output.py index 98334d1709..889a99a66a 100644 --- a/src/calibre/gui2/convert/pdf_output.py +++ b/src/calibre/gui2/convert/pdf_output.py @@ -22,7 +22,9 @@ class PluginWidget(Widget, Ui_Form): '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', 'pdf_page_numbers']) + 'pdf_default_font_size', 'pdf_mono_font_size', 'pdf_page_numbers', + 'pdf_footer_template', 'pdf_header_template', + ]) self.db, self.book_id = db, book_id for x in get_option('paper_size').option.choices: diff --git a/src/calibre/gui2/convert/pdf_output.ui b/src/calibre/gui2/convert/pdf_output.ui index a4d184d6bc..a3cd131ba3 100644 --- a/src/calibre/gui2/convert/pdf_output.ui +++ b/src/calibre/gui2/convert/pdf_output.ui @@ -6,8 +6,8 @@