mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Merge from trunk
This commit is contained in:
commit
8e3ba79234
@ -6,7 +6,6 @@ www.nsfwcorp.com
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
import urllib
|
import urllib
|
||||||
from calibre import strftime
|
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
class NotSafeForWork(BasicNewsRecipe):
|
class NotSafeForWork(BasicNewsRecipe):
|
||||||
@ -21,8 +20,9 @@ class NotSafeForWork(BasicNewsRecipe):
|
|||||||
needs_subscription = True
|
needs_subscription = True
|
||||||
auto_cleanup = False
|
auto_cleanup = False
|
||||||
INDEX = 'https://www.nsfwcorp.com'
|
INDEX = 'https://www.nsfwcorp.com'
|
||||||
LOGIN = INDEX + '/login'
|
LOGIN = INDEX + '/login/target/'
|
||||||
use_embedded_content = False
|
SETTINGS = INDEX + '/settings/'
|
||||||
|
use_embedded_content = True
|
||||||
language = 'en'
|
language = 'en'
|
||||||
publication_type = 'magazine'
|
publication_type = 'magazine'
|
||||||
masthead_url = 'http://assets.nsfwcorp.com/media/headers/nsfw_banner.jpg'
|
masthead_url = 'http://assets.nsfwcorp.com/media/headers/nsfw_banner.jpg'
|
||||||
@ -46,15 +46,6 @@ class NotSafeForWork(BasicNewsRecipe):
|
|||||||
, 'language' : language
|
, '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):
|
def get_browser(self):
|
||||||
br = BasicNewsRecipe.get_browser()
|
br = BasicNewsRecipe.get_browser()
|
||||||
br.open(self.LOGIN)
|
br.open(self.LOGIN)
|
||||||
@ -65,30 +56,12 @@ class NotSafeForWork(BasicNewsRecipe):
|
|||||||
br.open(self.LOGIN, data)
|
br.open(self.LOGIN, data)
|
||||||
return br
|
return br
|
||||||
|
|
||||||
def parse_index(self):
|
def get_feeds(self):
|
||||||
articles = []
|
self.feeds = []
|
||||||
soup = self.index_to_soup(self.INDEX)
|
soup = self.index_to_soup(self.SETTINGS)
|
||||||
dispatches = soup.find(attrs={'id':'dispatches'})
|
for item in soup.findAll('input', attrs={'type':'text'}):
|
||||||
if dispatches:
|
if item.has_key('value') and item['value'].startswith('http://www.nsfwcorp.com/feed/'):
|
||||||
for item in dispatches.findAll('h3'):
|
self.feeds.append(item['value'])
|
||||||
description = u''
|
return self.feeds
|
||||||
title_link = item.find('span', attrs={'class':'dispatchTitle'})
|
return self.feeds
|
||||||
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 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.web.feeds.news import BasicNewsRecipe
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
|
||||||
|
|
||||||
class PajamasMedia(BasicNewsRecipe):
|
class PajamasMedia(BasicNewsRecipe):
|
||||||
title = u'Pajamas Media'
|
title = u'Pajamas Media'
|
||||||
description = u'Provides exclusive news and opinion for forty countries.'
|
description = u'Provides exclusive news and opinion for forty countries.'
|
||||||
language = 'en'
|
language = 'en'
|
||||||
__author__ = 'Krittika Goyal'
|
__author__ = 'Krittika Goyal'
|
||||||
oldest_article = 1 #days
|
oldest_article = 2 #days
|
||||||
max_articles_per_feed = 25
|
max_articles_per_feed = 25
|
||||||
recursions = 1
|
recursions = 1
|
||||||
match_regexps = [r'http://pajamasmedia.com/blog/.*/2/$']
|
match_regexps = [r'http://pajamasmedia.com/blog/.*/2/$']
|
||||||
#encoding = 'latin1'
|
#encoding = 'latin1'
|
||||||
|
|
||||||
remove_stylesheets = True
|
remove_stylesheets = True
|
||||||
#remove_tags_before = dict(name='h1', attrs={'class':'heading'})
|
auto_cleanup = True
|
||||||
remove_tags_after = dict(name='div', attrs={'class':'paged-nav'})
|
##remove_tags_before = dict(name='h1', attrs={'class':'heading'})
|
||||||
remove_tags = [
|
#remove_tags_after = dict(name='div', attrs={'class':'paged-nav'})
|
||||||
dict(name='iframe'),
|
#remove_tags = [
|
||||||
dict(name='div', attrs={'class':['pages']}),
|
#dict(name='iframe'),
|
||||||
#dict(name='div', attrs={'id':['bookmark']}),
|
#dict(name='div', attrs={'class':['pages']}),
|
||||||
#dict(name='span', attrs={'class':['related_link', 'slideshowcontrols']}),
|
##dict(name='div', attrs={'id':['bookmark']}),
|
||||||
#dict(name='ul', attrs={'class':'articleTools'}),
|
##dict(name='span', attrs={'class':['related_link', 'slideshowcontrols']}),
|
||||||
]
|
##dict(name='ul', attrs={'class':'articleTools'}),
|
||||||
|
#]
|
||||||
|
|
||||||
feeds = [
|
feeds = [
|
||||||
('pajamas Media',
|
('pajamas Media',
|
||||||
@ -29,20 +29,20 @@ class PajamasMedia(BasicNewsRecipe):
|
|||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def preprocess_html(self, soup):
|
#def preprocess_html(self, soup):
|
||||||
story = soup.find(name='div', attrs={'id':'innerpage-content'})
|
#story = soup.find(name='div', attrs={'id':'innerpage-content'})
|
||||||
#td = heading.findParent(name='td')
|
##td = heading.findParent(name='td')
|
||||||
#td.extract()
|
##td.extract()
|
||||||
|
|
||||||
soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
|
#soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
|
||||||
body = soup.find(name='body')
|
#body = soup.find(name='body')
|
||||||
body.insert(0, story)
|
#body.insert(0, story)
|
||||||
return soup
|
#return soup
|
||||||
|
|
||||||
def postprocess_html(self, soup, first):
|
#def postprocess_html(self, soup, first):
|
||||||
if not first:
|
#if not first:
|
||||||
h = soup.find(attrs={'class':'innerpage-header'})
|
#h = soup.find(attrs={'class':'innerpage-header'})
|
||||||
if h: h.extract()
|
#if h: h.extract()
|
||||||
auth = soup.find(attrs={'class':'author'})
|
#auth = soup.find(attrs={'class':'author'})
|
||||||
if auth: auth.extract()
|
#if auth: auth.extract()
|
||||||
return soup
|
#return soup
|
||||||
|
@ -14,50 +14,32 @@ import os
|
|||||||
from calibre.customize.conversion import OutputFormatPlugin, \
|
from calibre.customize.conversion import OutputFormatPlugin, \
|
||||||
OptionRecommendation
|
OptionRecommendation
|
||||||
from calibre.ptempfile import TemporaryDirectory
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
from calibre.constants import iswindows
|
|
||||||
|
|
||||||
UNITS = [
|
UNITS = ['millimeter', 'centimeter', 'point', 'inch' , 'pica' , 'didot',
|
||||||
'millimeter',
|
'cicero', 'devicepixel']
|
||||||
'point',
|
|
||||||
'inch' ,
|
|
||||||
'pica' ,
|
|
||||||
'didot',
|
|
||||||
'cicero',
|
|
||||||
'devicepixel',
|
|
||||||
]
|
|
||||||
|
|
||||||
PAPER_SIZES = ['b2',
|
PAPER_SIZES = ['b2', 'b4', 'b5', 'b6', 'b0', 'b1', 'letter', 'b3', 'a3', 'a1',
|
||||||
'a9',
|
'a0', 'legal', 'a6', 'a2', 'a5', 'a4']
|
||||||
'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']
|
|
||||||
|
|
||||||
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):
|
class PDFOutput(OutputFormatPlugin):
|
||||||
|
|
||||||
@ -66,9 +48,14 @@ class PDFOutput(OutputFormatPlugin):
|
|||||||
file_type = 'pdf'
|
file_type = 'pdf'
|
||||||
|
|
||||||
options = set([
|
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',
|
OptionRecommendation(name='unit', recommended_value='inch',
|
||||||
level=OptionRecommendation.LOW, short_switch='u', choices=UNITS,
|
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 '
|
'are %s '
|
||||||
'Note: This does not override the unit for margins!') % UNITS),
|
'Note: This does not override the unit for margins!') % UNITS),
|
||||||
OptionRecommendation(name='paper_size', recommended_value='letter',
|
OptionRecommendation(name='paper_size', recommended_value='letter',
|
||||||
@ -80,10 +67,6 @@ class PDFOutput(OutputFormatPlugin):
|
|||||||
help=_('Custom size of the document. Use the form widthxheight '
|
help=_('Custom size of the document. Use the form widthxheight '
|
||||||
'EG. `123x321` to specify the width and height. '
|
'EG. `123x321` to specify the width and height. '
|
||||||
'This overrides any specified paper-size.')),
|
'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',
|
OptionRecommendation(name='preserve_cover_aspect_ratio',
|
||||||
recommended_value=False,
|
recommended_value=False,
|
||||||
help=_('Preserve the aspect ratio of the cover, instead'
|
help=_('Preserve the aspect ratio of the cover, instead'
|
||||||
@ -108,6 +91,11 @@ class PDFOutput(OutputFormatPlugin):
|
|||||||
OptionRecommendation(name='pdf_mono_font_size',
|
OptionRecommendation(name='pdf_mono_font_size',
|
||||||
recommended_value=16, help=_(
|
recommended_value=16, help=_(
|
||||||
'The default font size for monospaced text')),
|
'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):
|
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||||
@ -200,33 +188,18 @@ class PDFOutput(OutputFormatPlugin):
|
|||||||
if k in family_map:
|
if k in family_map:
|
||||||
val[i].value = family_map[k]
|
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):
|
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
|
from calibre.ebooks.metadata.opf2 import OPF
|
||||||
|
|
||||||
self.log.debug('Serializing oeb input to disk for processing...')
|
self.log.debug('Serializing oeb input to disk for processing...')
|
||||||
self.get_cover_data()
|
self.get_cover_data()
|
||||||
|
|
||||||
if iswindows:
|
self.handle_embedded_fonts()
|
||||||
self.remove_font_specification()
|
|
||||||
else:
|
|
||||||
self.handle_embedded_fonts()
|
|
||||||
|
|
||||||
with TemporaryDirectory('_pdf_out') as oeb_dir:
|
with TemporaryDirectory('_pdf_out') as oeb_dir:
|
||||||
from calibre.customize.ui import plugin_for_output_format
|
from calibre.customize.ui import plugin_for_output_format
|
||||||
@ -240,9 +213,9 @@ class PDFOutput(OutputFormatPlugin):
|
|||||||
'toc', None))
|
'toc', None))
|
||||||
|
|
||||||
def write(self, Writer, items, toc):
|
def write(self, Writer, items, toc):
|
||||||
from calibre.ebooks.pdf.writer import PDFMetadata
|
|
||||||
writer = Writer(self.opts, self.log, cover_data=self.cover_data,
|
writer = Writer(self.opts, self.log, cover_data=self.cover_data,
|
||||||
toc=toc)
|
toc=toc)
|
||||||
|
writer.report_progress = self.report_progress
|
||||||
|
|
||||||
close = False
|
close = False
|
||||||
if not hasattr(self.output_path, 'write'):
|
if not hasattr(self.output_path, 'write'):
|
||||||
|
@ -18,6 +18,8 @@ inch = 72.0
|
|||||||
cm = inch / 2.54
|
cm = inch / 2.54
|
||||||
mm = cm * 0.1
|
mm = cm * 0.1
|
||||||
pica = 12.0
|
pica = 12.0
|
||||||
|
didot = 0.375 * mm
|
||||||
|
cicero = 12 * didot
|
||||||
|
|
||||||
_W, _H = (21*cm, 29.7*cm)
|
_W, _H = (21*cm, 29.7*cm)
|
||||||
|
|
||||||
@ -41,6 +43,10 @@ B3 = (_BH*2, _BW)
|
|||||||
B2 = (_BW*2, _BH*2)
|
B2 = (_BW*2, _BH*2)
|
||||||
B1 = (_BH*4, _BW*2)
|
B1 = (_BH*4, _BW*2)
|
||||||
B0 = (_BW*4, _BH*4)
|
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 {{{
|
# Basic PDF datatypes {{{
|
||||||
@ -79,19 +85,12 @@ class String(unicode):
|
|||||||
raw = codecs.BOM_UTF16_BE + s.encode('utf-16-be')
|
raw = codecs.BOM_UTF16_BE + s.encode('utf-16-be')
|
||||||
stream.write(b'('+raw+b')')
|
stream.write(b'('+raw+b')')
|
||||||
|
|
||||||
class GlyphIndex(object):
|
class GlyphIndex(int):
|
||||||
|
|
||||||
def __init__(self, code, compress):
|
|
||||||
self.code = code
|
|
||||||
self.compress = compress
|
|
||||||
|
|
||||||
def pdf_serialize(self, stream):
|
def pdf_serialize(self, stream):
|
||||||
if self.compress:
|
byts = bytearray(pack(b'>H', self))
|
||||||
stream.write(pack(b'>sHs', b'(', self.code, b')'))
|
stream.write('<%s>'%''.join(map(
|
||||||
else:
|
lambda x: bytes(hex(x)[2:]).rjust(2, b'0'), byts)))
|
||||||
byts = bytearray(pack(b'>H', self.code))
|
|
||||||
stream.write('<%s>'%''.join(map(
|
|
||||||
lambda x: bytes(hex(int(x))[2:]).rjust(2, b'0'), byts)))
|
|
||||||
|
|
||||||
class Dictionary(dict):
|
class Dictionary(dict):
|
||||||
|
|
||||||
|
@ -16,15 +16,11 @@ from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter,
|
|||||||
QTransform, QPainterPath, QTextOption, QTextLayout,
|
QTransform, QPainterPath, QTextOption, QTextLayout,
|
||||||
QImage, QByteArray, QBuffer, qRgba)
|
QImage, QByteArray, QBuffer, qRgba)
|
||||||
|
|
||||||
from calibre.constants import DEBUG
|
|
||||||
from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path)
|
from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path)
|
||||||
from calibre.ebooks.pdf.render.common import inch, A4
|
from calibre.ebooks.pdf.render.common import inch, A4
|
||||||
from calibre.utils.fonts.sfnt.container import Sfnt
|
from calibre.utils.fonts.sfnt.container import Sfnt
|
||||||
from calibre.utils.fonts.sfnt.metrics import FontMetrics
|
from calibre.utils.fonts.sfnt.metrics import FontMetrics
|
||||||
|
|
||||||
XDPI = 1200
|
|
||||||
YDPI = 1200
|
|
||||||
|
|
||||||
Point = namedtuple('Point', 'x y')
|
Point = namedtuple('Point', 'x y')
|
||||||
ColorState = namedtuple('ColorState', 'color opacity do')
|
ColorState = namedtuple('ColorState', 'color opacity do')
|
||||||
|
|
||||||
@ -35,7 +31,8 @@ def store_error(func):
|
|||||||
try:
|
try:
|
||||||
func(self, *args, **kwargs)
|
func(self, *args, **kwargs)
|
||||||
except:
|
except:
|
||||||
self.errors.append(traceback.format_exc())
|
self.errors_occurred = True
|
||||||
|
self.errors(traceback.format_exc())
|
||||||
|
|
||||||
return errh
|
return errh
|
||||||
|
|
||||||
@ -43,7 +40,7 @@ class GraphicsState(object): # {{{
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.ops = {}
|
self.ops = {}
|
||||||
self.current_state = self.initial_state = {
|
self.initial_state = {
|
||||||
'fill': ColorState(Color(0., 0., 0., 1.), 1.0, False),
|
'fill': ColorState(Color(0., 0., 0., 1.), 1.0, False),
|
||||||
'transform': QTransform(),
|
'transform': QTransform(),
|
||||||
'dash': [],
|
'dash': [],
|
||||||
@ -53,9 +50,10 @@ class GraphicsState(object): # {{{
|
|||||||
'line_join': 'miter',
|
'line_join': 'miter',
|
||||||
'clip': (Qt.NoClip, QPainterPath()),
|
'clip': (Qt.NoClip, QPainterPath()),
|
||||||
}
|
}
|
||||||
|
self.current_state = self.initial_state.copy()
|
||||||
|
|
||||||
def reset(self):
|
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,
|
def update_color_state(self, which, color=None, opacity=None,
|
||||||
brush_style=None, pen_style=None):
|
brush_style=None, pen_style=None):
|
||||||
@ -78,7 +76,6 @@ class GraphicsState(object): # {{{
|
|||||||
self.ops[which] = n
|
self.ops[which] = n
|
||||||
|
|
||||||
def read(self, state):
|
def read(self, state):
|
||||||
self.ops = {}
|
|
||||||
flags = state.state()
|
flags = state.state()
|
||||||
|
|
||||||
if flags & QPaintEngine.DirtyTransform:
|
if flags & QPaintEngine.DirtyTransform:
|
||||||
@ -110,15 +107,12 @@ class GraphicsState(object): # {{{
|
|||||||
self.update_color_state('fill', opacity=state.opacity())
|
self.update_color_state('fill', opacity=state.opacity())
|
||||||
self.update_color_state('stroke', opacity=state.opacity())
|
self.update_color_state('stroke', opacity=state.opacity())
|
||||||
|
|
||||||
if flags & QPaintEngine.DirtyClipPath:
|
if flags & QPaintEngine.DirtyClipPath or flags & QPaintEngine.DirtyClipRegion:
|
||||||
self.ops['clip'] = (state.clipOperation(), state.clipPath())
|
self.ops['clip'] = True
|
||||||
elif flags & QPaintEngine.DirtyClipRegion:
|
|
||||||
path = QPainterPath()
|
|
||||||
for rect in state.clipRegion().rects():
|
|
||||||
path.addRect(rect)
|
|
||||||
self.ops['clip'] = (state.clipOperation(), path)
|
|
||||||
|
|
||||||
def __call__(self, engine):
|
def __call__(self, engine):
|
||||||
|
if not self.ops:
|
||||||
|
return
|
||||||
pdf = engine.pdf
|
pdf = engine.pdf
|
||||||
ops = self.ops
|
ops = self.ops
|
||||||
current_transform = self.current_state['transform']
|
current_transform = self.current_state['transform']
|
||||||
@ -128,58 +122,34 @@ class GraphicsState(object): # {{{
|
|||||||
if reset_stack:
|
if reset_stack:
|
||||||
pdf.restore_stack()
|
pdf.restore_stack()
|
||||||
pdf.save_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
|
# Since we have reset the stack we need to re-apply all previous
|
||||||
# operations, that are different from the default value (clip is
|
# operations, that are different from the default value (clip is
|
||||||
# handled separately).
|
# handled separately).
|
||||||
for op in set(self.current_state) - (set(ops)|{'clip'}):
|
for op in set(self.initial_state) - {'clip'}:
|
||||||
if self.current_state[op] != self.initial_state[op]:
|
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)
|
self.apply(op, self.current_state[op], engine, pdf)
|
||||||
|
|
||||||
# Now apply the new operations
|
# Now apply the new operations
|
||||||
for op, val in ops.iteritems():
|
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.apply(op, val, engine, pdf)
|
||||||
self.current_state[op] = val
|
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):
|
def apply(self, op, val, engine, pdf):
|
||||||
getattr(self, 'apply_'+op)(val, engine, pdf)
|
getattr(self, 'apply_'+op)(val, engine, pdf)
|
||||||
|
|
||||||
def apply_transform(self, val, engine, pdf):
|
def apply_transform(self, val, engine, pdf):
|
||||||
engine.qt_system = val
|
if not val.isIdentity():
|
||||||
pdf.transform(val)
|
pdf.transform(val)
|
||||||
|
|
||||||
def apply_stroke(self, val, engine, pdf):
|
def apply_stroke(self, val, engine, pdf):
|
||||||
self.apply_color_state('stroke', val, engine, pdf)
|
self.apply_color_state('stroke', val, engine, pdf)
|
||||||
@ -215,9 +185,11 @@ class Font(FontMetrics):
|
|||||||
class PdfEngine(QPaintEngine):
|
class PdfEngine(QPaintEngine):
|
||||||
|
|
||||||
def __init__(self, file_object, page_width, page_height, left_margin,
|
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)
|
QPaintEngine.__init__(self, self.features)
|
||||||
self.file_object = file_object
|
self.file_object = file_object
|
||||||
|
self.compress = compress
|
||||||
self.page_height, self.page_width = page_height, page_width
|
self.page_height, self.page_width = page_height, page_width
|
||||||
self.left_margin, self.top_margin = left_margin, top_margin
|
self.left_margin, self.top_margin = left_margin, top_margin
|
||||||
self.right_margin, self.bottom_margin = right_margin, bottom_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.bottom_margin) / self.pixel_height
|
||||||
|
|
||||||
self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy)
|
self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy)
|
||||||
self.qt_system = QTransform()
|
|
||||||
self.do_stroke = True
|
self.do_stroke = True
|
||||||
self.do_fill = False
|
self.do_fill = False
|
||||||
self.scale = sqrt(sy**2 + sx**2)
|
self.scale = sqrt(sy**2 + sx**2)
|
||||||
self.xscale, self.yscale = sx, sy
|
self.xscale, self.yscale = sx, sy
|
||||||
self.graphics_state = GraphicsState()
|
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 = QTextOption()
|
||||||
self.text_option.setWrapMode(QTextOption.NoWrap)
|
self.text_option.setWrapMode(QTextOption.NoWrap)
|
||||||
self.fonts = {}
|
self.fonts = {}
|
||||||
i = QImage(1, 1, QImage.Format_ARGB32)
|
i = QImage(1, 1, QImage.Format_ARGB32)
|
||||||
i.fill(qRgba(0, 0, 0, 255))
|
i.fill(qRgba(0, 0, 0, 255))
|
||||||
self.alpha_bit = i.constBits().asstring(4).find(b'\xff')
|
self.alpha_bit = i.constBits().asstring(4).find(b'\xff')
|
||||||
|
self.current_page_num = 1
|
||||||
|
self.current_page_inited = False
|
||||||
|
|
||||||
def init_page(self):
|
def init_page(self):
|
||||||
self.pdf.transform(self.pdf_system)
|
self.pdf.transform(self.pdf_system)
|
||||||
self.pdf.set_rgb_colorspace()
|
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.pdf.set_line_width(width)
|
||||||
self.do_stroke = True
|
self.do_stroke = True
|
||||||
self.do_fill = False
|
self.do_fill = False
|
||||||
self.graphics_state.reset()
|
self.graphics_state.reset()
|
||||||
self.pdf.save_stack()
|
self.pdf.save_stack()
|
||||||
|
self.current_page_inited = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def features(self):
|
def features(self):
|
||||||
@ -268,25 +243,26 @@ class PdfEngine(QPaintEngine):
|
|||||||
QPaintEngine.PrimitiveTransform)
|
QPaintEngine.PrimitiveTransform)
|
||||||
|
|
||||||
def begin(self, device):
|
def begin(self, device):
|
||||||
try:
|
if not hasattr(self, 'pdf'):
|
||||||
self.pdf = PDFStream(self.file_object, (self.page_width,
|
try:
|
||||||
self.page_height),
|
self.pdf = PDFStream(self.file_object, (self.page_width,
|
||||||
compress=not DEBUG)
|
self.page_height),
|
||||||
self.init_page()
|
compress=self.compress)
|
||||||
except:
|
except:
|
||||||
self.errors.append(traceback.format_exc())
|
self.errors.append(traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def end_page(self, start_new=True):
|
def end_page(self):
|
||||||
self.pdf.restore_stack()
|
if self.current_page_inited:
|
||||||
self.pdf.end_page()
|
self.pdf.restore_stack()
|
||||||
if start_new:
|
self.pdf.end_page()
|
||||||
self.init_page()
|
self.current_page_inited = False
|
||||||
|
self.current_page_num += 1
|
||||||
|
|
||||||
def end(self):
|
def end(self):
|
||||||
try:
|
try:
|
||||||
self.end_page(start_new=False)
|
self.end_page()
|
||||||
self.pdf.end()
|
self.pdf.end()
|
||||||
except:
|
except:
|
||||||
self.errors.append(traceback.format_exc())
|
self.errors.append(traceback.format_exc())
|
||||||
@ -300,6 +276,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawPixmap(self, rect, pixmap, source_rect):
|
def drawPixmap(self, rect, pixmap, source_rect):
|
||||||
|
self.graphics_state(self)
|
||||||
source_rect = source_rect.toRect()
|
source_rect = source_rect.toRect()
|
||||||
pixmap = (pixmap if source_rect == pixmap.rect() else
|
pixmap = (pixmap if source_rect == pixmap.rect() else
|
||||||
pixmap.copy(source_rect))
|
pixmap.copy(source_rect))
|
||||||
@ -311,6 +288,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawImage(self, rect, image, source_rect, flags=Qt.AutoColor):
|
def drawImage(self, rect, image, source_rect, flags=Qt.AutoColor):
|
||||||
|
self.graphics_state(self)
|
||||||
source_rect = source_rect.toRect()
|
source_rect = source_rect.toRect()
|
||||||
image = (image if source_rect == image.rect() else
|
image = (image if source_rect == image.rect() else
|
||||||
image.copy(source_rect))
|
image.copy(source_rect))
|
||||||
@ -386,7 +364,6 @@ class PdfEngine(QPaintEngine):
|
|||||||
@store_error
|
@store_error
|
||||||
def updateState(self, state):
|
def updateState(self, state):
|
||||||
self.graphics_state.read(state)
|
self.graphics_state.read(state)
|
||||||
self.graphics_state(self)
|
|
||||||
|
|
||||||
def convert_path(self, path):
|
def convert_path(self, path):
|
||||||
p = Path()
|
p = Path()
|
||||||
@ -414,6 +391,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawPath(self, path):
|
def drawPath(self, path):
|
||||||
|
self.graphics_state(self)
|
||||||
p = self.convert_path(path)
|
p = self.convert_path(path)
|
||||||
fill_rule = {Qt.OddEvenFill:'evenodd',
|
fill_rule = {Qt.OddEvenFill:'evenodd',
|
||||||
Qt.WindingFill:'winding'}[path.fillRule()]
|
Qt.WindingFill:'winding'}[path.fillRule()]
|
||||||
@ -428,6 +406,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawPoints(self, points):
|
def drawPoints(self, points):
|
||||||
|
self.graphics_state(self)
|
||||||
p = Path()
|
p = Path()
|
||||||
for point in points:
|
for point in points:
|
||||||
p.move_to(point.x(), point.y())
|
p.move_to(point.x(), point.y())
|
||||||
@ -436,6 +415,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawRects(self, rects):
|
def drawRects(self, rects):
|
||||||
|
self.graphics_state(self)
|
||||||
for rect in rects:
|
for rect in rects:
|
||||||
bl = rect.topLeft()
|
bl = rect.topLeft()
|
||||||
self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(),
|
self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(),
|
||||||
@ -488,7 +468,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
glyph_map[g[0]] = string
|
glyph_map[g[0]] = string
|
||||||
break
|
break
|
||||||
if not found:
|
if not found:
|
||||||
self.debug.append(
|
self.debug(
|
||||||
'Failed to find glyph->unicode mapping for text: %s'%text)
|
'Failed to find glyph->unicode mapping for text: %s'%text)
|
||||||
break
|
break
|
||||||
ipos += 1
|
ipos += 1
|
||||||
@ -498,7 +478,8 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawTextItem(self, point, text_item):
|
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 = type(u'')(text_item.text()).replace('\n', ' ')
|
||||||
text = unicodedata.normalize('NFKC', text)
|
text = unicodedata.normalize('NFKC', text)
|
||||||
tl = self.get_text_layout(text_item, text)
|
tl = self.get_text_layout(text_item, text)
|
||||||
@ -535,6 +516,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawPolygon(self, points, mode):
|
def drawPolygon(self, points, mode):
|
||||||
|
self.graphics_state(self)
|
||||||
if not points: return
|
if not points: return
|
||||||
p = Path()
|
p = Path()
|
||||||
p.move_to(points[0].x(), points[0].y())
|
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,
|
self.pdf.draw_path(p, stroke=True, fill_rule=fill_rule,
|
||||||
fill=(mode in (self.OddEvenMode, self.WindingMode, self.ConvexMode)))
|
fill=(mode in (self.OddEvenMode, self.WindingMode, self.ConvexMode)))
|
||||||
|
|
||||||
|
def set_metadata(self, *args, **kwargs):
|
||||||
|
self.pdf.set_metadata(*args, **kwargs)
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.pdf.save_stack()
|
self.pdf.save_stack()
|
||||||
self.saved_ps = (self.do_stroke, self.do_fill)
|
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,
|
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)
|
QPaintDevice.__init__(self)
|
||||||
|
self.xdpi, self.ydpi = xdpi, ydpi
|
||||||
self.page_width, self.page_height = page_size
|
self.page_width, self.page_height = page_size
|
||||||
self.body_width = self.page_width - left_margin - right_margin
|
self.body_width = self.page_width - left_margin - right_margin
|
||||||
self.body_height = self.page_height - top_margin - bottom_margin
|
self.body_height = self.page_height - top_margin - bottom_margin
|
||||||
self.engine = PdfEngine(file_object, self.page_width, self.page_height,
|
self.engine = PdfEngine(file_object, self.page_width, self.page_height,
|
||||||
left_margin, top_margin, right_margin,
|
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):
|
def paintEngine(self):
|
||||||
return self.engine
|
return self.engine
|
||||||
|
|
||||||
def metric(self, m):
|
def metric(self, m):
|
||||||
if m in (self.PdmDpiX, self.PdmPhysicalDpiX):
|
if m in (self.PdmDpiX, self.PdmPhysicalDpiX):
|
||||||
return XDPI
|
return self.xdpi
|
||||||
if m in (self.PdmDpiY, self.PdmPhysicalDpiY):
|
if m in (self.PdmDpiY, self.PdmPhysicalDpiY):
|
||||||
return YDPI
|
return self.ydpi
|
||||||
if m == self.PdmDepth:
|
if m == self.PdmDepth:
|
||||||
return 32
|
return 32
|
||||||
if m == self.PdmNumColors:
|
if m == self.PdmNumColors:
|
||||||
@ -584,10 +572,32 @@ class PdfDevice(QPaintDevice): # {{{
|
|||||||
if m == self.PdmHeightMM:
|
if m == self.PdmHeightMM:
|
||||||
return int(round(self.body_height * 0.35277777777778))
|
return int(round(self.body_height * 0.35277777777778))
|
||||||
if m == self.PdmWidth:
|
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:
|
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
|
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__':
|
if __name__ == '__main__':
|
||||||
@ -596,8 +606,9 @@ if __name__ == '__main__':
|
|||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
p = QPainter()
|
p = QPainter()
|
||||||
with open('/tmp/painter.pdf', 'wb') as f:
|
with open('/tmp/painter.pdf', 'wb') as f:
|
||||||
dev = PdfDevice(f)
|
dev = PdfDevice(f, compress=False)
|
||||||
p.begin(dev)
|
p.begin(dev)
|
||||||
|
dev.init_page()
|
||||||
xmax, ymax = p.viewport().width(), p.viewport().height()
|
xmax, ymax = p.viewport().width(), p.viewport().height()
|
||||||
try:
|
try:
|
||||||
p.drawRect(0, 0, xmax, ymax)
|
p.drawRect(0, 0, xmax, ymax)
|
||||||
@ -606,21 +617,21 @@ if __name__ == '__main__':
|
|||||||
# pp = QPainterPath()
|
# pp = QPainterPath()
|
||||||
# pp.addRect(0, 0, xmax, ymax)
|
# pp.addRect(0, 0, xmax, ymax)
|
||||||
# p.drawPath(pp)
|
# p.drawPath(pp)
|
||||||
p.save()
|
# p.save()
|
||||||
for i in xrange(3):
|
# for i in xrange(3):
|
||||||
col = [0, 0, 0, 200]
|
# col = [0, 0, 0, 200]
|
||||||
col[i] = 255
|
# col[i] = 255
|
||||||
p.setOpacity(0.3)
|
# p.setOpacity(0.3)
|
||||||
p.setBrush(QBrush(QColor(*col)))
|
# p.setBrush(QBrush(QColor(*col)))
|
||||||
p.drawRect(0, 0, xmax/10, xmax/10)
|
# p.drawRect(0, 0, xmax/10, xmax/10)
|
||||||
p.translate(xmax/10, xmax/10)
|
# p.translate(xmax/10, xmax/10)
|
||||||
p.scale(1, 1.5)
|
# p.scale(1, 1.5)
|
||||||
p.restore()
|
# p.restore()
|
||||||
|
|
||||||
# p.scale(2, 2)
|
# # p.scale(2, 2)
|
||||||
# p.rotate(45)
|
# # p.rotate(45)
|
||||||
p.drawPixmap(0, 0, 2048, 2048, QPixmap(I('library.png')))
|
# p.drawPixmap(0, 0, 2048, 2048, QPixmap(I('library.png')))
|
||||||
p.drawRect(0, 0, 2048, 2048)
|
# p.drawRect(0, 0, 2048, 2048)
|
||||||
|
|
||||||
# p.save()
|
# p.save()
|
||||||
# p.drawLine(0, 0, 5000, 0)
|
# p.drawLine(0, 0, 5000, 0)
|
||||||
@ -628,23 +639,20 @@ if __name__ == '__main__':
|
|||||||
# p.drawLine(0, 0, 5000, 0)
|
# p.drawLine(0, 0, 5000, 0)
|
||||||
# p.restore()
|
# p.restore()
|
||||||
|
|
||||||
# f = p.font()
|
f = p.font()
|
||||||
# f.setPointSize(24)
|
f.setPointSize(20)
|
||||||
# f.setLetterSpacing(f.PercentageSpacing, 200)
|
# f.setLetterSpacing(f.PercentageSpacing, 200)
|
||||||
# f.setUnderline(True)
|
# f.setUnderline(True)
|
||||||
# f.setOverline(True)
|
# f.setOverline(True)
|
||||||
# f.setStrikeOut(True)
|
# f.setStrikeOut(True)
|
||||||
# f.setFamily('Calibri')
|
f.setFamily('DejaVu Sans')
|
||||||
# p.setFont(f)
|
p.setFont(f)
|
||||||
# p.setPen(QColor(0, 0, 255))
|
# p.setPen(QColor(0, 0, 255))
|
||||||
# p.scale(2, 2)
|
# p.scale(2, 2)
|
||||||
# p.rotate(45)
|
# 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:
|
finally:
|
||||||
p.end()
|
p.end()
|
||||||
for line in dev.engine.debug:
|
if dev.engine.errors_occurred:
|
||||||
print (line)
|
|
||||||
if dev.engine.errors:
|
|
||||||
for err in dev.engine.errors: print (err)
|
|
||||||
raise SystemExit(1)
|
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):
|
def catalog(self):
|
||||||
return self.objects[1]
|
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''):
|
def write_line(self, byts=b''):
|
||||||
byts = byts if isinstance(byts, bytes) else byts.encode('ascii')
|
byts = byts if isinstance(byts, bytes) else byts.encode('ascii')
|
||||||
self.stream.write(byts + EOL)
|
self.stream.write(byts + EOL)
|
||||||
@ -409,7 +417,7 @@ class PDFStream(object):
|
|||||||
self.current_page.write('%s Tm '%' '.join(map(type(u''), transform)))
|
self.current_page.write('%s Tm '%' '.join(map(type(u''), transform)))
|
||||||
for x, y, glyph_id in glyphs:
|
for x, y, glyph_id in glyphs:
|
||||||
self.current_page.write('%g %g Td '%(x, y))
|
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(' Tj ')
|
||||||
self.current_page.write_line(b' ET')
|
self.current_page.write_line(b' ET')
|
||||||
|
|
||||||
|
@ -9,18 +9,16 @@ Write content to PDF.
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
import os, shutil, json
|
import os, shutil, json
|
||||||
from future_builtins import map
|
|
||||||
|
|
||||||
from PyQt4.Qt import (QEventLoop, QObject, QPrinter, QSizeF, Qt, QPainter,
|
from PyQt4.Qt import (QEventLoop, QObject, QPrinter, QSizeF, Qt, QPainter,
|
||||||
QPixmap, QTimer, pyqtProperty, QString, QSize)
|
QPixmap, QTimer, pyqtProperty, QString, QSize)
|
||||||
from PyQt4.QtWebKit import QWebView, QWebPage, QWebSettings
|
from PyQt4.QtWebKit import QWebView, QWebPage, QWebSettings
|
||||||
|
|
||||||
from calibre.ptempfile import PersistentTemporaryDirectory
|
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.pdf.outline_writer import Outline
|
||||||
from calibre.ebooks.metadata import authors_to_string
|
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
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
|
from calibre.ebooks.oeb.display.webview import load_html
|
||||||
|
|
||||||
def get_custom_size(opts):
|
def get_custom_size(opts):
|
||||||
@ -72,7 +70,6 @@ def get_pdf_printer(opts, for_comic=False, output_file_name=None): # {{{
|
|||||||
else:
|
else:
|
||||||
printer.setPageMargins(opts.margin_left, opts.margin_top,
|
printer.setPageMargins(opts.margin_left, opts.margin_top,
|
||||||
opts.margin_right, opts.margin_bottom, QPrinter.Point)
|
opts.margin_right, opts.margin_bottom, QPrinter.Point)
|
||||||
printer.setOrientation(orientation(opts.orientation))
|
|
||||||
printer.setOutputFormat(QPrinter.PdfFormat)
|
printer.setOutputFormat(QPrinter.PdfFormat)
|
||||||
printer.setFullPage(for_comic)
|
printer.setFullPage(for_comic)
|
||||||
if output_file_name:
|
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())
|
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): # {{{
|
class Page(QWebPage): # {{{
|
||||||
|
|
||||||
def __init__(self, opts, log):
|
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
|
Add an empty book item to the library. This does not import any formats
|
||||||
from a book file.
|
from a book file.
|
||||||
'''
|
'''
|
||||||
author = None
|
author = series = None
|
||||||
index = self.gui.library_view.currentIndex()
|
index = self.gui.library_view.currentIndex()
|
||||||
if index.isValid():
|
if index.isValid():
|
||||||
raw = index.model().db.authors(index.row())
|
raw = index.model().db.authors(index.row())
|
||||||
@ -159,16 +159,27 @@ class AddAction(InterfaceAction):
|
|||||||
authors = [a.strip().replace('|', ',') for a in raw.split(',')]
|
authors = [a.strip().replace('|', ',') for a in raw.split(',')]
|
||||||
if authors:
|
if authors:
|
||||||
author = authors[0]
|
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:
|
if dlg.exec_() == dlg.Accepted:
|
||||||
num = dlg.qty_to_add
|
num = dlg.qty_to_add
|
||||||
|
series = dlg.selected_series
|
||||||
|
db = self.gui.library_view.model().db
|
||||||
|
ids = []
|
||||||
for x in xrange(num):
|
for x in xrange(num):
|
||||||
mi = MetaInformation(_('Unknown'), dlg.selected_authors)
|
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)
|
self.gui.library_view.model().books_added(num)
|
||||||
if hasattr(self.gui, 'db_images'):
|
if hasattr(self.gui, 'db_images'):
|
||||||
self.gui.db_images.reset()
|
self.gui.db_images.reset()
|
||||||
self.gui.tags_view.recount()
|
self.gui.tags_view.recount()
|
||||||
|
if ids:
|
||||||
|
ids.reverse()
|
||||||
|
self.gui.library_view.select_rows(ids)
|
||||||
|
|
||||||
def add_isbns(self, books, add_tags=[]):
|
def add_isbns(self, books, add_tags=[]):
|
||||||
self.isbn_books = list(books)
|
self.isbn_books = list(books)
|
||||||
|
@ -18,16 +18,17 @@ class PluginWidget(Widget, Ui_Form):
|
|||||||
ICON = I('mimetypes/pdf.png')
|
ICON = I('mimetypes/pdf.png')
|
||||||
|
|
||||||
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
|
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
|
||||||
Widget.__init__(self, parent, ['paper_size', 'custom_size',
|
Widget.__init__(self, parent, [
|
||||||
'orientation', 'preserve_cover_aspect_ratio', 'pdf_serif_family',
|
'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_sans_family', 'pdf_mono_family', 'pdf_standard_font',
|
||||||
'pdf_default_font_size', 'pdf_mono_font_size'])
|
'pdf_default_font_size', 'pdf_mono_font_size'])
|
||||||
self.db, self.book_id = db, book_id
|
self.db, self.book_id = db, book_id
|
||||||
|
|
||||||
for x in get_option('paper_size').option.choices:
|
for x in get_option('paper_size').option.choices:
|
||||||
self.opt_paper_size.addItem(x)
|
self.opt_paper_size.addItem(x)
|
||||||
for x in get_option('orientation').option.choices:
|
for x in get_option('unit').option.choices:
|
||||||
self.opt_orientation.addItem(x)
|
self.opt_unit.addItem(x)
|
||||||
for x in get_option('pdf_standard_font').option.choices:
|
for x in get_option('pdf_standard_font').option.choices:
|
||||||
self.opt_pdf_standard_font.addItem(x)
|
self.opt_pdf_standard_font.addItem(x)
|
||||||
|
|
||||||
|
@ -14,7 +14,27 @@
|
|||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QFormLayout" name="formLayout">
|
<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">
|
<widget class="QLabel" name="label">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Paper Size:</string>
|
<string>&Paper Size:</string>
|
||||||
@ -24,21 +44,8 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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">
|
<item row="2" column="1">
|
||||||
<widget class="QComboBox" name="opt_orientation"/>
|
<widget class="QComboBox" name="opt_paper_size"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
<item row="3" column="0">
|
||||||
<widget class="QLabel" name="label_3">
|
<widget class="QLabel" name="label_3">
|
||||||
@ -51,7 +58,24 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1">
|
<item row="3" column="1">
|
||||||
<widget class="QLineEdit" name="opt_custom_size"/>
|
<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>
|
||||||
<item row="4" column="0" colspan="2">
|
<item row="4" column="0" colspan="2">
|
||||||
<widget class="QCheckBox" name="opt_preserve_cover_aspect_ratio">
|
<widget class="QCheckBox" name="opt_preserve_cover_aspect_ratio">
|
||||||
@ -60,19 +84,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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">
|
<item row="5" column="0">
|
||||||
<widget class="QLabel" name="label_4">
|
<widget class="QLabel" name="label_4">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -159,15 +170,18 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="0" colspan="2">
|
<item row="11" column="0">
|
||||||
<widget class="QLabel" name="label_10">
|
<spacer name="verticalSpacer">
|
||||||
<property name="text">
|
<property name="orientation">
|
||||||
<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>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="wordWrap">
|
<property name="sizeHint" stdset="0">
|
||||||
<bool>true</bool>
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>213</height>
|
||||||
|
</size>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -12,7 +12,7 @@ from calibre.utils.config import tweaks
|
|||||||
|
|
||||||
class AddEmptyBookDialog(QDialog):
|
class AddEmptyBookDialog(QDialog):
|
||||||
|
|
||||||
def __init__(self, parent, db, author):
|
def __init__(self, parent, db, author, series=None):
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
@ -45,6 +45,22 @@ class AddEmptyBookDialog(QDialog):
|
|||||||
self.clear_button.clicked.connect(self.reset_author)
|
self.clear_button.clicked.connect(self.reset_author)
|
||||||
self._layout.addWidget(self.clear_button, 3, 1, 1, 1)
|
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 = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
button_box.accepted.connect(self.accept)
|
button_box.accepted.connect(self.accept)
|
||||||
button_box.rejected.connect(self.reject)
|
button_box.rejected.connect(self.reject)
|
||||||
@ -54,6 +70,9 @@ class AddEmptyBookDialog(QDialog):
|
|||||||
def reset_author(self, *args):
|
def reset_author(self, *args):
|
||||||
self.authors_combo.setEditText(_('Unknown'))
|
self.authors_combo.setEditText(_('Unknown'))
|
||||||
|
|
||||||
|
def reset_series(self):
|
||||||
|
self.series_combo.setEditText('')
|
||||||
|
|
||||||
def initialize_authors(self, db, author):
|
def initialize_authors(self, db, author):
|
||||||
au = author
|
au = author
|
||||||
if not au:
|
if not au:
|
||||||
@ -65,6 +84,11 @@ class AddEmptyBookDialog(QDialog):
|
|||||||
self.authors_combo.set_add_separator(tweaks['authors_completer_append_separator'])
|
self.authors_combo.set_add_separator(tweaks['authors_completer_append_separator'])
|
||||||
self.authors_combo.update_items_cache(db.all_author_names())
|
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
|
@property
|
||||||
def qty_to_add(self):
|
def qty_to_add(self):
|
||||||
return self.qty_spinbox.value()
|
return self.qty_spinbox.value()
|
||||||
@ -73,6 +97,10 @@ class AddEmptyBookDialog(QDialog):
|
|||||||
def selected_authors(self):
|
def selected_authors(self):
|
||||||
return string_to_authors(unicode(self.authors_combo.text()))
|
return string_to_authors(unicode(self.authors_combo.text()))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def selected_series(self):
|
||||||
|
return unicode(self.series_combo.text())
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
d = AddEmptyBookDialog()
|
d = AddEmptyBookDialog()
|
||||||
|
@ -52,10 +52,9 @@ class ColumnColor(object):
|
|||||||
self.mi = None
|
self.mi = None
|
||||||
|
|
||||||
def __call__(self, id_, key, fmt, db, formatter, color_cache, colors):
|
def __call__(self, id_, key, fmt, db, formatter, color_cache, colors):
|
||||||
if id_ in color_cache:
|
if id_ in color_cache and key in color_cache[id_]:
|
||||||
if key in color_cache[id_]:
|
self.mi = None
|
||||||
self.mi = None
|
return color_cache[id_][key]
|
||||||
return color_cache[id_][key]
|
|
||||||
try:
|
try:
|
||||||
if self.mi is None:
|
if self.mi is None:
|
||||||
self.mi = db.get_metadata(id_, index_is_id=True)
|
self.mi = db.get_metadata(id_, index_is_id=True)
|
||||||
@ -763,9 +762,8 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
self.column_color.mi = None
|
self.column_color.mi = None
|
||||||
|
|
||||||
if self.color_row_fmt_cache is None:
|
if self.color_row_fmt_cache is None:
|
||||||
d = dict(self.db.prefs['column_color_rules'])
|
self.color_row_fmt_cache = tuple(fmt for key, fmt in
|
||||||
self.color_row_fmt_cache = d.get(color_row_key, '')
|
self.db.prefs['column_color_rules'] if key == color_row_key)
|
||||||
|
|
||||||
|
|
||||||
for k, fmt in self.db.prefs['column_color_rules']:
|
for k, fmt in self.db.prefs['column_color_rules']:
|
||||||
if k == key:
|
if k == key:
|
||||||
@ -789,10 +787,9 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if self.color_row_fmt_cache:
|
for fmt in self.color_row_fmt_cache:
|
||||||
key = color_row_key
|
ccol = self.column_color(id_, color_row_key, fmt, self.db,
|
||||||
ccol = self.column_color(id_, key, self.color_row_fmt_cache,
|
self.formatter, self.color_cache, self.colors)
|
||||||
self.db, self.formatter, self.color_cache, self.colors)
|
|
||||||
if ccol is not None:
|
if ccol is not None:
|
||||||
return ccol
|
return ccol
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user