mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Use Qt to draw catalog covers and mastheads as well
This commit is contained in:
parent
9963e4263e
commit
88df6cbb3d
@ -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.
|
||||||
|
@ -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')
|
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
@ -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))
|
||||||
|
@ -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')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user