Use Qt to draw catalog covers and mastheads as well

This commit is contained in:
Kovid Goyal 2014-09-24 09:46:23 +05:30
parent 9963e4263e
commit 88df6cbb3d
5 changed files with 77 additions and 66 deletions

View File

@ -384,14 +384,6 @@ maximum_resort_levels = 5
# the fields that are being displayed. # the fields that are being displayed.
sort_dates_using_visible_fields = False sort_dates_using_visible_fields = False
#: Specify which font to use when generating a default cover or masthead
# Absolute path to .ttf font files to use as the fonts for the title, author
# and footer when generating a default cover or masthead image. Useful if the
# default font (Liberation Serif) does not contain glyphs for the language of
# the books in your library.
generate_cover_title_font = None
generate_cover_foot_font = None
#: Fuzz value for trimming covers #: Fuzz value for trimming covers
# The value used for the fuzz distance when trimming a cover. # The value used for the fuzz distance when trimming a cover.
# Colors within this distance are considered equal. # Colors within this distance are considered equal.

View File

@ -8,8 +8,7 @@ from various formats.
''' '''
import traceback, os, re import traceback, os, re
from cStringIO import StringIO from calibre import CurrentDir
from calibre import CurrentDir, force_unicode
class ConversionError(Exception): class ConversionError(Exception):
@ -181,7 +180,6 @@ def normalize(x):
def calibre_cover(title, author_string, series_string=None, def calibre_cover(title, author_string, series_string=None,
output_format='jpg', title_size=46, author_size=36, logo_path=None): output_format='jpg', title_size=46, author_size=36, logo_path=None):
from calibre.utils.config_base import tweaks
title = normalize(title) title = normalize(title)
author_string = normalize(author_string) author_string = normalize(author_string)
series_string = normalize(series_string) series_string = normalize(series_string)
@ -189,8 +187,6 @@ def calibre_cover(title, author_string, series_string=None,
import regex import regex
pat = regex.compile(ur'\p{Cf}+', flags=regex.VERSION1) # remove non-printing chars like the soft hyphen pat = regex.compile(ur'\p{Cf}+', flags=regex.VERSION1) # remove non-printing chars like the soft hyphen
text = pat.sub(u'', title + author_string + (series_string or u'')) text = pat.sub(u'', title + author_string + (series_string or u''))
font_path = tweaks['generate_cover_title_font']
if font_path is None:
font_path = P('fonts/liberation/LiberationSerif-Bold.ttf') font_path = P('fonts/liberation/LiberationSerif-Bold.ttf')
from calibre.utils.fonts.utils import get_font_for_text from calibre.utils.fonts.utils import get_font_for_text
@ -261,48 +257,8 @@ def unit_convert(value, base, font, dpi, body_font_size=12):
def generate_masthead(title, output_path=None, width=600, height=60): def generate_masthead(title, output_path=None, width=600, height=60):
from calibre.ebooks.conversion.config import load_defaults from calibre.ebooks.conversion.config import load_defaults
from calibre.utils.config import tweaks
fp = tweaks['generate_cover_title_font']
if not fp:
fp = P('fonts/liberation/LiberationSerif-Bold.ttf')
font_path = default_font = fp
recs = load_defaults('mobi_output') recs = load_defaults('mobi_output')
masthead_font_family = recs.get('masthead_font', 'Default') masthead_font_family = recs.get('masthead_font', None)
from calibre.ebooks.covers import generate_masthead
if masthead_font_family != 'Default': return generate_masthead(title, output_path=output_path, width=width, height=height, font_family=masthead_font_family)
from calibre.utils.fonts.scanner import font_scanner, NoFonts
try:
faces = font_scanner.fonts_for_family(masthead_font_family)
except NoFonts:
faces = []
if faces:
font_path = faces[0]['path']
if not font_path or not os.access(font_path, os.R_OK):
font_path = default_font
try:
from PIL import Image, ImageDraw, ImageFont
Image, ImageDraw, ImageFont
except ImportError:
import Image, ImageDraw, ImageFont
img = Image.new('RGB', (width, height), 'white')
draw = ImageDraw.Draw(img)
try:
font = ImageFont.truetype(font_path, 48, encoding='unic')
except:
font = ImageFont.truetype(default_font, 48, encoding='unic')
text = force_unicode(title)
width, height = draw.textsize(text, font=font)
left = max(int((width - width)/2.), 0)
top = max(int((height - height)/2.), 0)
draw.text((left, top), text, fill=(0,0,0), font=font)
if output_path is None:
f = StringIO()
img.save(f, 'JPEG')
return f.getvalue()
else:
with open(output_path, 'wb') as f:
img.save(f, 'JPEG')

View File

@ -19,7 +19,7 @@ from PyQt5.Qt import (
QPainterPath, QPen, QRectF QPainterPath, QPen, QRectF
) )
from calibre import force_unicode from calibre import force_unicode, fit_image
from calibre.constants import __appname__, __version__ from calibre.constants import __appname__, __version__
from calibre.ebooks.metadata import fmt_sidx from calibre.ebooks.metadata import fmt_sidx
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
@ -517,10 +517,76 @@ def create_cover(title, authors, series=None, series_index=1, prefs=None, as_qim
prefs or cprefs, title_template=d['title_template'], subtitle_template=d['subtitle_template'], footer_template=d['footer_template']) prefs or cprefs, title_template=d['title_template'], subtitle_template=d['subtitle_template'], footer_template=d['footer_template'])
return generate_cover(mi, prefs=prefs, as_qimage=as_qimage) return generate_cover(mi, prefs=prefs, as_qimage=as_qimage)
def calibre_cover2(title, author_string='', series_string='', prefs=None, as_qimage=False):
init_environment()
title, subtitle, footer = '<b>' + escape_formatting(title), '<i>' + escape_formatting(series_string), '<b>' + escape_formatting(author_string)
prefs = prefs or cprefs
prefs = {k:prefs.get(k) for k in cprefs.defaults}
scale = 800. / prefs['cover_height']
scale_cover(prefs, scale)
prefs = Prefs(**prefs)
img = QImage(prefs.cover_width, prefs.cover_height, QImage.Format_ARGB32)
img.fill(Qt.white)
# colors = to_theme('ffffff ffffff 000000 000000')
color_theme = theme_to_colors(fallback_colors)
class CalibeLogoStyle(Style):
NAME = GUI_NAME = 'calibre'
def __call__(self, painter, rect, color_theme, title_block, subtitle_block, footer_block):
top = title_block.position.y + 10
extra_spacing = subtitle_block.line_spacing // 2 if subtitle_block.line_spacing else title_block.line_spacing // 3
height = title_block.height + subtitle_block.height + extra_spacing + title_block.leading
top += height + 25
bottom = footer_block.position.y - 50
logo = QImage(I('library.png'))
pwidth, pheight = rect.width(), bottom - top
scaled, width, height = fit_image(logo.width(), logo.height(), pwidth, pheight)
x, y = (pwidth - width) // 2, (pheight - height) // 2
rect = QRect(x, top + y, width, height)
painter.setRenderHint(QPainter.SmoothPixmapTransform)
painter.drawImage(rect, logo)
return self.ccolor1, self.ccolor1, self.ccolor1
style = CalibeLogoStyle(color_theme, prefs)
title_block, subtitle_block, footer_block = layout_text(
prefs, img, title, subtitle, footer, img.height() // 3, style)
p = QPainter(img)
rect = QRect(0, 0, img.width(), img.height())
colors = style(p, rect, color_theme, title_block, subtitle_block, footer_block)
for block, color in zip((title_block, subtitle_block, footer_block), colors):
p.setPen(color)
block.draw(p)
p.end()
img.setText('Generated cover', '%s %s' % (__appname__, __version__))
if as_qimage:
return img
return pixmap_to_data(img)
def scale_cover(prefs, scale):
for x in ('cover_width', 'cover_height', 'title_font_size', 'subtitle_font_size', 'footer_font_size'):
prefs[x] = int(scale * prefs[x])
def generate_masthead(title, output_path=None, width=600, height=60, as_qimage=False, font_family=None):
init_environment()
font_family = font_family or cprefs['title_font_family'] or 'Liberation Serif'
img = QImage(width, height, QImage.Format_ARGB32)
img.fill(Qt.white)
p = QPainter(img)
f = QFont(font_family)
f.setPixelSize((height * 3) // 4), f.setBold(True)
p.setFont(f)
p.drawText(img.rect(), Qt.AlignLeft | Qt.AlignVCenter, sanitize(title))
p.end()
if as_qimage:
return img
data = pixmap_to_data(img)
if output_path is None:
return data
with open(output_path, 'wb') as f:
f.write(data)
def test(scale=0.25): def test(scale=0.25):
from PyQt5.Qt import QLabel, QApplication, QPixmap, QMainWindow, QWidget, QScrollArea, QGridLayout from PyQt5.Qt import QLabel, QApplication, QPixmap, QMainWindow, QWidget, QScrollArea, QGridLayout
app = QApplication([]) app = QApplication([])
mi = Metadata('xxx', ['Kovid Goyal', 'John Q. Doe', 'Author']) mi = Metadata('xxx', ['Kovid Goyal', 'John & Doe', 'Author'])
mi.series = 'A series of styles' mi.series = 'A series of styles'
m = QMainWindow() m = QMainWindow()
sa = QScrollArea(m) sa = QScrollArea(m)
@ -534,8 +600,7 @@ def test(scale=0.25):
mi.series_index = c + 1 mi.series_index = c + 1
mi.title = 'An algorithmic cover [%s]' % color mi.title = 'An algorithmic cover [%s]' % color
prefs = override_prefs(cprefs, override_color_theme=color, override_style=style) prefs = override_prefs(cprefs, override_color_theme=color, override_style=style)
for x in ('cover_width', 'cover_height', 'title_font_size', 'subtitle_font_size', 'footer_font_size'): scale_cover(prefs, scale)
prefs[x] = int(scale * prefs[x])
img = generate_cover(mi, prefs=prefs, as_qimage=True) img = generate_cover(mi, prefs=prefs, as_qimage=True)
la = QLabel() la = QLabel()
la.setPixmap(QPixmap.fromImage(img)) la.setPixmap(QPixmap.fromImage(img))

View File

@ -13,7 +13,6 @@ from collections import namedtuple
from calibre import strftime from calibre import strftime
from calibre.customize import CatalogPlugin from calibre.customize import CatalogPlugin
from calibre.customize.conversion import OptionRecommendation, DummyReporter from calibre.customize.conversion import OptionRecommendation, DummyReporter
from calibre.ebooks import calibre_cover
from calibre.library import current_library_name from calibre.library import current_library_name
from calibre.library.catalogs import AuthorSortMismatchException, EmptyCatalogException from calibre.library.catalogs import AuthorSortMismatchException, EmptyCatalogException
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
@ -463,9 +462,10 @@ class EPUB_MOBI(CatalogPlugin):
recommendations.append(('cover', cpath, OptionRecommendation.HIGH)) recommendations.append(('cover', cpath, OptionRecommendation.HIGH))
log.info("using existing catalog cover") log.info("using existing catalog cover")
else: else:
from calibre.ebooks.covers import calibre_cover2
log.info("replacing catalog cover") log.info("replacing catalog cover")
new_cover_path = PersistentTemporaryFile(suffix='.jpg') new_cover_path = PersistentTemporaryFile(suffix='.jpg')
new_cover = calibre_cover(opts.catalog_title.replace('"', '\\"'), 'calibre') new_cover = calibre_cover2(opts.catalog_title, 'calibre')
new_cover_path.write(new_cover) new_cover_path.write(new_cover)
new_cover_path.close() new_cover_path.close()
recommendations.append(('cover', new_cover_path.name, OptionRecommendation.HIGH)) recommendations.append(('cover', new_cover_path.name, OptionRecommendation.HIGH))

View File

@ -273,8 +273,6 @@ def create_cover_page(top_lines, logo_path, width=590, height=750,
bottom += line.bottom_margin bottom += line.bottom_margin
bottom -= top_lines[-1].bottom_margin bottom -= top_lines[-1].bottom_margin
foot_font = tweaks['generate_cover_foot_font']
if not foot_font:
foot_font = P('fonts/liberation/LiberationMono-Regular.ttf') foot_font = P('fonts/liberation/LiberationMono-Regular.ttf')
vanity = create_text_arc(__appname__ + ' ' + __version__, 24, vanity = create_text_arc(__appname__ + ' ' + __version__, 24,
font=foot_font, bgcolor='#00000000') font=foot_font, bgcolor='#00000000')