mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 02:34:06 -04:00
Merge from trunk
This commit is contained in:
commit
8e3ba79234
@ -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
|
||||
|
@ -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('<html><head><title>t</title></head><body></body></html>')
|
||||
body = soup.find(name='body')
|
||||
body.insert(0, story)
|
||||
return soup
|
||||
#soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
|
||||
#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
|
||||
|
@ -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,32 +188,17 @@ 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):
|
||||
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()
|
||||
|
||||
with TemporaryDirectory('_pdf_out') as oeb_dir:
|
||||
@ -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'):
|
||||
|
@ -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))
|
||||
byts = bytearray(pack(b'>H', self))
|
||||
stream.write('<%s>'%''.join(map(
|
||||
lambda x: bytes(hex(int(x))[2:]).rjust(2, b'0'), byts)))
|
||||
lambda x: bytes(hex(x)[2:]).rjust(2, b'0'), byts)))
|
||||
|
||||
class Dictionary(dict):
|
||||
|
||||
|
@ -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,57 +122,33 @@ 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
|
||||
if not val.isIdentity():
|
||||
pdf.transform(val)
|
||||
|
||||
def apply_stroke(self, 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):
|
||||
if not hasattr(self, 'pdf'):
|
||||
try:
|
||||
self.pdf = PDFStream(self.file_object, (self.page_width,
|
||||
self.page_height),
|
||||
compress=not DEBUG)
|
||||
self.init_page()
|
||||
compress=self.compress)
|
||||
except:
|
||||
self.errors.append(traceback.format_exc())
|
||||
return False
|
||||
return True
|
||||
|
||||
def end_page(self, start_new=True):
|
||||
def end_page(self):
|
||||
if self.current_page_inited:
|
||||
self.pdf.restore_stack()
|
||||
self.pdf.end_page()
|
||||
if start_new:
|
||||
self.init_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)
|
||||
|
||||
|
286
src/calibre/ebooks/pdf/render/from_html.py
Normal file
286
src/calibre/ebooks/pdf/render/from_html.py
Normal file
@ -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 <kovid at kovidgoyal.net>'
|
||||
__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)
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -14,7 +14,27 @@
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="1" column="0">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string><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.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_override_profile_size">
|
||||
<property name="text">
|
||||
<string>&Override paper size set in output profile</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>&Paper Size:</string>
|
||||
@ -24,21 +44,8 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="opt_paper_size"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>&Orientation:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_orientation</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="opt_orientation"/>
|
||||
<widget class="QComboBox" name="opt_paper_size"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
@ -51,8 +58,25 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="opt_custom_size"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>&Unit:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_unit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="opt_unit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_preserve_cover_aspect_ratio">
|
||||
<property name="text">
|
||||
@ -60,19 +84,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>213</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
@ -159,15 +170,18 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string><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.</string>
|
||||
<item row="11" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>213</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
@ -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()
|
||||
|
@ -52,8 +52,7 @@ 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_]:
|
||||
if id_ in color_cache and key in color_cache[id_]:
|
||||
self.mi = None
|
||||
return color_cache[id_][key]
|
||||
try:
|
||||
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user