diff --git a/recipes/nsfw_corp.recipe b/recipes/nsfw_corp.recipe
index c88bdd705e..0ed40ade3a 100644
--- a/recipes/nsfw_corp.recipe
+++ b/recipes/nsfw_corp.recipe
@@ -6,7 +6,6 @@ www.nsfwcorp.com
'''
import urllib
-from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
class NotSafeForWork(BasicNewsRecipe):
@@ -21,8 +20,9 @@ class NotSafeForWork(BasicNewsRecipe):
needs_subscription = True
auto_cleanup = False
INDEX = 'https://www.nsfwcorp.com'
- LOGIN = INDEX + '/login'
- use_embedded_content = False
+ LOGIN = INDEX + '/login/target/'
+ SETTINGS = INDEX + '/settings/'
+ use_embedded_content = True
language = 'en'
publication_type = 'magazine'
masthead_url = 'http://assets.nsfwcorp.com/media/headers/nsfw_banner.jpg'
@@ -46,15 +46,6 @@ class NotSafeForWork(BasicNewsRecipe):
, 'language' : language
}
- remove_tags_before = dict(attrs={'id':'fromToLine'})
- remove_tags_after = dict(attrs={'id':'unlockButtonDiv'})
- remove_tags=[
- dict(name=['meta', 'link', 'iframe', 'embed', 'object'])
- ,dict(name='a', attrs={'class':'switchToDeskNotes'})
- ,dict(attrs={'id':'unlockButtonDiv'})
- ]
- remove_attributes = ['lang']
-
def get_browser(self):
br = BasicNewsRecipe.get_browser()
br.open(self.LOGIN)
@@ -65,30 +56,12 @@ class NotSafeForWork(BasicNewsRecipe):
br.open(self.LOGIN, data)
return br
- def parse_index(self):
- articles = []
- soup = self.index_to_soup(self.INDEX)
- dispatches = soup.find(attrs={'id':'dispatches'})
- if dispatches:
- for item in dispatches.findAll('h3'):
- description = u''
- title_link = item.find('span', attrs={'class':'dispatchTitle'})
- description_link = item.find('span', attrs={'class':'dispatchSubtitle'})
- feed_link = item.find('a', href=True)
- if feed_link:
- url = self.INDEX + feed_link['href']
- title = self.tag_to_string(title_link)
- description = self.tag_to_string(description_link)
- date = strftime(self.timefmt)
- articles.append({
- 'title' :title
- ,'date' :date
- ,'url' :url
- ,'description':description
- })
- return [('Dispatches', articles)]
+ def get_feeds(self):
+ self.feeds = []
+ soup = self.index_to_soup(self.SETTINGS)
+ for item in soup.findAll('input', attrs={'type':'text'}):
+ if item.has_key('value') and item['value'].startswith('http://www.nsfwcorp.com/feed/'):
+ self.feeds.append(item['value'])
+ return self.feeds
+ return self.feeds
- def preprocess_html(self, soup):
- for item in soup.findAll(style=True):
- del item['style']
- return soup
diff --git a/recipes/pajama.recipe b/recipes/pajama.recipe
index 8c5ba74317..9b474b6e65 100644
--- a/recipes/pajama.recipe
+++ b/recipes/pajama.recipe
@@ -1,27 +1,27 @@
from calibre.web.feeds.news import BasicNewsRecipe
-from calibre.ebooks.BeautifulSoup import BeautifulSoup
class PajamasMedia(BasicNewsRecipe):
title = u'Pajamas Media'
description = u'Provides exclusive news and opinion for forty countries.'
language = 'en'
__author__ = 'Krittika Goyal'
- oldest_article = 1 #days
+ oldest_article = 2 #days
max_articles_per_feed = 25
recursions = 1
match_regexps = [r'http://pajamasmedia.com/blog/.*/2/$']
#encoding = 'latin1'
remove_stylesheets = True
- #remove_tags_before = dict(name='h1', attrs={'class':'heading'})
- remove_tags_after = dict(name='div', attrs={'class':'paged-nav'})
- remove_tags = [
- dict(name='iframe'),
- dict(name='div', attrs={'class':['pages']}),
- #dict(name='div', attrs={'id':['bookmark']}),
- #dict(name='span', attrs={'class':['related_link', 'slideshowcontrols']}),
- #dict(name='ul', attrs={'class':'articleTools'}),
- ]
+ auto_cleanup = True
+ ##remove_tags_before = dict(name='h1', attrs={'class':'heading'})
+ #remove_tags_after = dict(name='div', attrs={'class':'paged-nav'})
+ #remove_tags = [
+ #dict(name='iframe'),
+ #dict(name='div', attrs={'class':['pages']}),
+ ##dict(name='div', attrs={'id':['bookmark']}),
+ ##dict(name='span', attrs={'class':['related_link', 'slideshowcontrols']}),
+ ##dict(name='ul', attrs={'class':'articleTools'}),
+ #]
feeds = [
('pajamas Media',
@@ -29,20 +29,20 @@ class PajamasMedia(BasicNewsRecipe):
]
- def preprocess_html(self, soup):
- story = soup.find(name='div', attrs={'id':'innerpage-content'})
- #td = heading.findParent(name='td')
- #td.extract()
+ #def preprocess_html(self, soup):
+ #story = soup.find(name='div', attrs={'id':'innerpage-content'})
+ ##td = heading.findParent(name='td')
+ ##td.extract()
- soup = BeautifulSoup('
t')
- body = soup.find(name='body')
- body.insert(0, story)
- return soup
+ #soup = BeautifulSoup('t')
+ #body = soup.find(name='body')
+ #body.insert(0, story)
+ #return soup
- def postprocess_html(self, soup, first):
- if not first:
- h = soup.find(attrs={'class':'innerpage-header'})
- if h: h.extract()
- auth = soup.find(attrs={'class':'author'})
- if auth: auth.extract()
- return soup
+ #def postprocess_html(self, soup, first):
+ #if not first:
+ #h = soup.find(attrs={'class':'innerpage-header'})
+ #if h: h.extract()
+ #auth = soup.find(attrs={'class':'author'})
+ #if auth: auth.extract()
+ #return soup
diff --git a/src/calibre/ebooks/conversion/plugins/pdf_output.py b/src/calibre/ebooks/conversion/plugins/pdf_output.py
index da66a9be0d..972631b5bd 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,9 +213,9 @@ 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)
+ writer.report_progress = self.report_progress
close = False
if not hasattr(self.output_path, 'write'):
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..723cff7a89 100644
--- a/src/calibre/ebooks/pdf/render/engine.py
+++ b/src/calibre/ebooks/pdf/render/engine.py
@@ -16,15 +16,11 @@ from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter,
QTransform, QPainterPath, QTextOption, QTextLayout,
QImage, QByteArray, QBuffer, qRgba)
-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
@@ -43,7 +40,7 @@ class GraphicsState(object): # {{{
def __init__(self):
self.ops = {}
- self.current_state = self.initial_state = {
+ self.initial_state = {
'fill': ColorState(Color(0., 0., 0., 1.), 1.0, False),
'transform': QTransform(),
'dash': [],
@@ -53,9 +50,10 @@ class GraphicsState(object): # {{{
'line_join': 'miter',
'clip': (Qt.NoClip, QPainterPath()),
}
+ self.current_state = self.initial_state.copy()
def reset(self):
- self.current_state = self.initial_state
+ self.current_state = self.initial_state.copy()
def update_color_state(self, which, color=None, opacity=None,
brush_style=None, pen_style=None):
@@ -78,7 +76,6 @@ class GraphicsState(object): # {{{
self.ops[which] = n
def read(self, state):
- self.ops = {}
flags = state.state()
if flags & QPaintEngine.DirtyTransform:
@@ -110,15 +107,12 @@ class GraphicsState(object): # {{{
self.update_color_state('fill', opacity=state.opacity())
self.update_color_state('stroke', opacity=state.opacity())
- if flags & QPaintEngine.DirtyClipPath:
- self.ops['clip'] = (state.clipOperation(), state.clipPath())
- elif flags & QPaintEngine.DirtyClipRegion:
- path = QPainterPath()
- for rect in state.clipRegion().rects():
- path.addRect(rect)
- self.ops['clip'] = (state.clipOperation(), path)
+ if flags & QPaintEngine.DirtyClipPath or flags & QPaintEngine.DirtyClipRegion:
+ self.ops['clip'] = True
def __call__(self, engine):
+ if not self.ops:
+ return
pdf = engine.pdf
ops = self.ops
current_transform = self.current_state['transform']
@@ -128,58 +122,34 @@ class GraphicsState(object): # {{{
if reset_stack:
pdf.restore_stack()
pdf.save_stack()
-
- # We apply clip before transform as the clip may have to be merged with
- # the previous clip path so it is easiest to work with clips that are
- # pre-transformed
- prev_op, prev_clip_path = self.current_state['clip']
- if 'clip' in ops:
- op, path = ops['clip']
- self.current_state['clip'] = (op, path)
- transform = ops.get('transform', QTransform())
- if not transform.isIdentity() and path is not None:
- # Pre transform the clip path
- path = current_transform.map(path)
- self.current_state['clip'] = (op, path)
-
- if op == Qt.ReplaceClip:
- pass
- elif op == Qt.IntersectClip:
- if prev_op != Qt.NoClip:
- self.current_state['clip'] = (op, path.intersected(prev_clip_path))
- elif op == Qt.UniteClip:
- if prev_clip_path is not None:
- path.addPath(prev_clip_path)
- else:
- self.current_state['clip'] = (Qt.NoClip, QPainterPath())
- op, path = self.current_state['clip']
- if op != Qt.NoClip:
- engine.add_clip(path)
- elif reset_stack and prev_op != Qt.NoClip:
- # Re-apply the previous clip path since no clipping operation was
- # specified
- engine.add_clip(prev_clip_path)
-
- if reset_stack:
# Since we have reset the stack we need to re-apply all previous
# operations, that are different from the default value (clip is
# handled separately).
- for op in set(self.current_state) - (set(ops)|{'clip'}):
- if self.current_state[op] != self.initial_state[op]:
+ for op in set(self.initial_state) - {'clip'}:
+ if op in ops: # These will be applied below
+ self.current_state[op] = self.initial_state[op]
+ elif self.current_state[op] != self.initial_state[op]:
self.apply(op, self.current_state[op], engine, pdf)
# Now apply the new operations
for op, val in ops.iteritems():
- if op != 'clip':
+ if op != 'clip' and self.current_state[op] != val:
self.apply(op, val, engine, pdf)
self.current_state[op] = val
+ if 'clip' in ops:
+ # Get the current clip
+ path = engine.painter().clipPath()
+ if not path.isEmpty():
+ engine.add_clip(path)
+ self.ops = {}
+
def apply(self, op, val, engine, pdf):
getattr(self, 'apply_'+op)(val, engine, pdf)
def apply_transform(self, val, engine, pdf):
- engine.qt_system = val
- pdf.transform(val)
+ if not val.isIdentity():
+ pdf.transform(val)
def apply_stroke(self, val, engine, pdf):
self.apply_color_state('stroke', val, engine, pdf)
@@ -215,9 +185,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
@@ -236,29 +208,32 @@ class PdfEngine(QPaintEngine):
self.bottom_margin) / self.pixel_height
self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy)
- self.qt_system = QTransform()
self.do_stroke = True
self.do_fill = False
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
+ self.current_page_inited = False
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
self.graphics_state.reset()
self.pdf.save_stack()
+ self.current_page_inited = True
@property
def features(self):
@@ -268,25 +243,26 @@ class PdfEngine(QPaintEngine):
QPaintEngine.PrimitiveTransform)
def begin(self, device):
- try:
- self.pdf = PDFStream(self.file_object, (self.page_width,
- self.page_height),
- compress=not DEBUG)
- self.init_page()
- except:
- self.errors.append(traceback.format_exc())
- return False
+ if not hasattr(self, 'pdf'):
+ try:
+ self.pdf = PDFStream(self.file_object, (self.page_width,
+ self.page_height),
+ compress=self.compress)
+ except:
+ self.errors.append(traceback.format_exc())
+ return False
return True
- def end_page(self, start_new=True):
- self.pdf.restore_stack()
- self.pdf.end_page()
- if start_new:
- self.init_page()
+ def end_page(self):
+ if self.current_page_inited:
+ self.pdf.restore_stack()
+ self.pdf.end_page()
+ self.current_page_inited = False
+ self.current_page_num += 1
def end(self):
try:
- self.end_page(start_new=False)
+ self.end_page()
self.pdf.end()
except:
self.errors.append(traceback.format_exc())
@@ -300,6 +276,7 @@ class PdfEngine(QPaintEngine):
@store_error
def drawPixmap(self, rect, pixmap, source_rect):
+ self.graphics_state(self)
source_rect = source_rect.toRect()
pixmap = (pixmap if source_rect == pixmap.rect() else
pixmap.copy(source_rect))
@@ -311,6 +288,7 @@ class PdfEngine(QPaintEngine):
@store_error
def drawImage(self, rect, image, source_rect, flags=Qt.AutoColor):
+ self.graphics_state(self)
source_rect = source_rect.toRect()
image = (image if source_rect == image.rect() else
image.copy(source_rect))
@@ -386,7 +364,6 @@ class PdfEngine(QPaintEngine):
@store_error
def updateState(self, state):
self.graphics_state.read(state)
- self.graphics_state(self)
def convert_path(self, path):
p = Path()
@@ -414,6 +391,7 @@ class PdfEngine(QPaintEngine):
@store_error
def drawPath(self, path):
+ self.graphics_state(self)
p = self.convert_path(path)
fill_rule = {Qt.OddEvenFill:'evenodd',
Qt.WindingFill:'winding'}[path.fillRule()]
@@ -428,6 +406,7 @@ class PdfEngine(QPaintEngine):
@store_error
def drawPoints(self, points):
+ self.graphics_state(self)
p = Path()
for point in points:
p.move_to(point.x(), point.y())
@@ -436,6 +415,7 @@ class PdfEngine(QPaintEngine):
@store_error
def drawRects(self, rects):
+ self.graphics_state(self)
for rect in rects:
bl = rect.topLeft()
self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(),
@@ -488,7 +468,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
@@ -498,7 +478,8 @@ class PdfEngine(QPaintEngine):
@store_error
def drawTextItem(self, point, text_item):
- # super(PdfEngine, self).drawTextItem(point+QPoint(0, 0), text_item)
+ # super(PdfEngine, self).drawTextItem(point, text_item)
+ self.graphics_state(self)
text = type(u'')(text_item.text()).replace('\n', ' ')
text = unicodedata.normalize('NFKC', text)
tl = self.get_text_layout(text_item, text)
@@ -535,6 +516,7 @@ class PdfEngine(QPaintEngine):
@store_error
def drawPolygon(self, points, mode):
+ self.graphics_state(self)
if not points: return
p = Path()
p.move_to(points[0].x(), points[0].y())
@@ -546,6 +528,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 +543,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 +572,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):
+ self.engine.end_page()
+
+ 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,8 +606,9 @@ 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)
+ dev.init_page()
xmax, ymax = p.viewport().width(), p.viewport().height()
try:
p.drawRect(0, 0, xmax, ymax)
@@ -606,21 +617,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.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)
@@ -628,23 +639,20 @@ if __name__ == '__main__':
# p.drawLine(0, 0, 5000, 0)
# p.restore()
- # f = p.font()
- # f.setPointSize(24)
+ f = p.font()
+ f.setPointSize(20)
# f.setLetterSpacing(f.PercentageSpacing, 200)
# f.setUnderline(True)
# f.setOverline(True)
# f.setStrikeOut(True)
- # f.setFamily('Calibri')
- # p.setFont(f)
+ f.setFamily('DejaVu Sans')
+ p.setFont(f)
# p.setPen(QColor(0, 0, 255))
# p.scale(2, 2)
# p.rotate(45)
- # p.drawText(QPoint(100, 300), 'Some text ū --- Д AV ff ff')
+ p.drawText(QPoint(0, 300), 'Some—text not By’s ū --- Д 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..cdc5a96c61
--- /dev/null
+++ b/src/calibre/ebooks/pdf/render/from_html.py
@@ -0,0 +1,286 @@
+#!/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, os
+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)
+ self.report_progress = lambda x, y: x
+
+ def dump(self, items, out_stream, pdf_metadata):
+ opts = self.opts
+ self.outline = Outline(self.toc, items)
+ page_size = get_page_size(self.opts)
+ xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY()
+ 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=xdpi, ydpi=ydpi, 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.total_items = len(items)
+
+ # 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
+ done = self.total_items - len(self.render_queue)
+ self.report_progress(done/self.total_items,
+ _('Rendered %s'%os.path.basename(self.current_item)))
+ 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
+ dx = 0
+ while True:
+ self.doc.init_page()
+ self.painter.save()
+ mf.render(self.painter)
+ self.painter.restore()
+ nsl = evaljs('paged_display.next_screen_location()').toInt()
+ self.doc.end_page()
+ if not nsl[1] or nsl[0] <= 0:
+ break
+ dx = nsl[0]
+ evaljs('window.scrollTo(%d, 0)'%dx)
+ 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/actions/add.py b/src/calibre/gui2/actions/add.py
index 4f3e9fc066..e18f5fe77c 100644
--- a/src/calibre/gui2/actions/add.py
+++ b/src/calibre/gui2/actions/add.py
@@ -151,7 +151,7 @@ class AddAction(InterfaceAction):
Add an empty book item to the library. This does not import any formats
from a book file.
'''
- author = None
+ author = series = None
index = self.gui.library_view.currentIndex()
if index.isValid():
raw = index.model().db.authors(index.row())
@@ -159,16 +159,27 @@ class AddAction(InterfaceAction):
authors = [a.strip().replace('|', ',') for a in raw.split(',')]
if authors:
author = authors[0]
- dlg = AddEmptyBookDialog(self.gui, self.gui.library_view.model().db, author)
+ series = index.model().db.series(index.row())
+ dlg = AddEmptyBookDialog(self.gui, self.gui.library_view.model().db,
+ author, series)
if dlg.exec_() == dlg.Accepted:
num = dlg.qty_to_add
+ series = dlg.selected_series
+ db = self.gui.library_view.model().db
+ ids = []
for x in xrange(num):
mi = MetaInformation(_('Unknown'), dlg.selected_authors)
- self.gui.library_view.model().db.import_book(mi, [])
+ if series:
+ mi.series = series
+ mi.series_index = db.get_next_series_num_for(series)
+ ids.append(db.import_book(mi, []))
self.gui.library_view.model().books_added(num)
if hasattr(self.gui, 'db_images'):
self.gui.db_images.reset()
self.gui.tags_view.recount()
+ if ids:
+ ids.reverse()
+ self.gui.library_view.select_rows(ids)
def add_isbns(self, books, add_tags=[]):
self.isbn_books = list(books)
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
+
-
+
diff --git a/src/calibre/gui2/dialogs/add_empty_book.py b/src/calibre/gui2/dialogs/add_empty_book.py
index 98992f85bb..185a3699e7 100644
--- a/src/calibre/gui2/dialogs/add_empty_book.py
+++ b/src/calibre/gui2/dialogs/add_empty_book.py
@@ -12,7 +12,7 @@ from calibre.utils.config import tweaks
class AddEmptyBookDialog(QDialog):
- def __init__(self, parent, db, author):
+ def __init__(self, parent, db, author, series=None):
QDialog.__init__(self, parent)
self.db = db
@@ -45,6 +45,22 @@ class AddEmptyBookDialog(QDialog):
self.clear_button.clicked.connect(self.reset_author)
self._layout.addWidget(self.clear_button, 3, 1, 1, 1)
+ self.series_label = QLabel(_('Set the series of the new books to:'))
+ self._layout.addWidget(self.series_label, 4, 0, 1, 2)
+
+ self.series_combo = EditWithComplete(self)
+ self.authors_combo.setSizeAdjustPolicy(
+ self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
+ self.series_combo.setEditable(True)
+ self._layout.addWidget(self.series_combo, 5, 0, 1, 1)
+ self.initialize_series(db, series)
+
+ self.sclear_button = QToolButton(self)
+ self.sclear_button.setIcon(QIcon(I('trash.png')))
+ self.sclear_button.setToolTip(_('Reset series'))
+ self.sclear_button.clicked.connect(self.reset_series)
+ self._layout.addWidget(self.sclear_button, 5, 1, 1, 1)
+
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
@@ -54,6 +70,9 @@ class AddEmptyBookDialog(QDialog):
def reset_author(self, *args):
self.authors_combo.setEditText(_('Unknown'))
+ def reset_series(self):
+ self.series_combo.setEditText('')
+
def initialize_authors(self, db, author):
au = author
if not au:
@@ -65,6 +84,11 @@ class AddEmptyBookDialog(QDialog):
self.authors_combo.set_add_separator(tweaks['authors_completer_append_separator'])
self.authors_combo.update_items_cache(db.all_author_names())
+ def initialize_series(self, db, series):
+ self.series_combo.show_initial_value(series or '')
+ self.series_combo.update_items_cache(db.all_series_names())
+ self.series_combo.set_separator(None)
+
@property
def qty_to_add(self):
return self.qty_spinbox.value()
@@ -73,6 +97,10 @@ class AddEmptyBookDialog(QDialog):
def selected_authors(self):
return string_to_authors(unicode(self.authors_combo.text()))
+ @property
+ def selected_series(self):
+ return unicode(self.series_combo.text())
+
if __name__ == '__main__':
app = QApplication([])
d = AddEmptyBookDialog()
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 063149c62e..8cd84bdafc 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -52,10 +52,9 @@ class ColumnColor(object):
self.mi = None
def __call__(self, id_, key, fmt, db, formatter, color_cache, colors):
- if id_ in color_cache:
- if key in color_cache[id_]:
- self.mi = None
- return color_cache[id_][key]
+ if id_ in color_cache and key in color_cache[id_]:
+ self.mi = None
+ return color_cache[id_][key]
try:
if self.mi is None:
self.mi = db.get_metadata(id_, index_is_id=True)
@@ -763,9 +762,8 @@ class BooksModel(QAbstractTableModel): # {{{
self.column_color.mi = None
if self.color_row_fmt_cache is None:
- d = dict(self.db.prefs['column_color_rules'])
- self.color_row_fmt_cache = d.get(color_row_key, '')
-
+ self.color_row_fmt_cache = tuple(fmt for key, fmt in
+ self.db.prefs['column_color_rules'] if key == color_row_key)
for k, fmt in self.db.prefs['column_color_rules']:
if k == key:
@@ -789,10 +787,9 @@ class BooksModel(QAbstractTableModel): # {{{
except:
pass
- if self.color_row_fmt_cache:
- key = color_row_key
- ccol = self.column_color(id_, key, self.color_row_fmt_cache,
- self.db, self.formatter, self.color_cache, self.colors)
+ for fmt in self.color_row_fmt_cache:
+ ccol = self.column_color(id_, color_row_key, fmt, self.db,
+ self.formatter, self.color_cache, self.colors)
if ccol is not None:
return ccol