Handle formatting tags in metadata when geenrating covers -- they must not be interpreted as formatting instructions

This commit is contained in:
Kovid Goyal 2014-09-23 23:40:33 +05:30
parent e20672cf45
commit 9963e4263e

View File

@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
import re, random import re, random, unicodedata
from collections import namedtuple from collections import namedtuple
from contextlib import contextmanager from contextlib import contextmanager
from math import ceil, sqrt, cos, sin, atan2 from math import ceil, sqrt, cos, sin, atan2
@ -46,11 +46,11 @@ cprefs.defaults['subtitle_template'] = '''{series:'test($, strcat("<i>", $, "</i
cprefs.defaults['footer_template'] = r'''program: cprefs.defaults['footer_template'] = r'''program:
# Show at most two authors, on separate lines. # Show at most two authors, on separate lines.
authors = field('authors'); authors = field('authors');
num = count(authors, ' & '); num = count(authors, ' &amp; ');
authors = sublist(authors, 0, 2, ' & '); authors = sublist(authors, 0, 2, ' &amp; ');
authors = list_re(authors, ' & ', '(.+)', '<b>\1'); authors = list_re(authors, ' &amp; ', '(.+)', '<b>\1');
authors = re(authors, ' & ', '<br>'); authors = re(authors, ' &amp; ', '<br>');
re(authors, '&&', '&') re(authors, '&amp;&amp;', '&amp;')
''' '''
Prefs = namedtuple('Prefs', ' '.join(sorted(cprefs.defaults))) Prefs = namedtuple('Prefs', ' '.join(sorted(cprefs.defaults)))
# }}} # }}}
@ -121,8 +121,8 @@ class Block(object):
self.leading = fm.leading() self.leading = fm.leading()
self.line_spacing = fm.lineSpacing() self.line_spacing = fm.lineSpacing()
for text in text.split('<br>') if text else (): for text in text.split('<br>') if text else ():
text, formats = parse_text_formatting(text) text, formats = parse_text_formatting(sanitize(text))
l = QTextLayout(text, font, img) l = QTextLayout(unescape_formatting(text), font, img)
l.setAdditionalFormats(formats) l.setAdditionalFormats(formats)
to = QTextOption(align) to = QTextOption(align)
to.setWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere) to.setWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)
@ -181,7 +181,7 @@ class Block(object):
def layout_text(prefs, img, title, subtitle, footer, max_height, style): def layout_text(prefs, img, title, subtitle, footer, max_height, style):
width = img.width() - 2 * style.hmargin width = img.width() - 2 * style.hmargin
title, subtitle, footer = map(normalize, (title, subtitle, footer)) title, subtitle, footer = title, subtitle, footer
title_font = QFont(prefs.title_font_family or 'Liberation Serif') title_font = QFont(prefs.title_font_family or 'Liberation Serif')
title_font.setPixelSize(prefs.title_font_size) title_font.setPixelSize(prefs.title_font_size)
title_block = Block(title, width, title_font, img, max_height, style.TITLE_ALIGN) title_block = Block(title, width, title_font, img, max_height, style.TITLE_ALIGN)
@ -206,23 +206,35 @@ def layout_text(prefs, img, title, subtitle, footer, max_height, style):
# Format text using templates {{{ # Format text using templates {{{
def sanitize(s): def sanitize(s):
return clean_xml_chars(clean_ascii_chars(force_unicode(s or ''))) return unicodedata.normalize('NFC', clean_xml_chars(clean_ascii_chars(force_unicode(s or ''))))
_formatter = None _formatter = None
_template_cache = {} _template_cache = {}
def escape_formatting(val):
return val.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
def unescape_formatting(val):
return val.replace('&lt;', '<').replace('&gt;', '>').replace('&amp;', '&')
class Formatter(SafeFormat):
def get_value(self, orig_key, args, kwargs):
ans = SafeFormat.get_value(self, orig_key, args, kwargs)
return escape_formatting(ans)
def formatter(): def formatter():
global _formatter global _formatter
if _formatter is None: if _formatter is None:
_formatter = SafeFormat() _formatter = Formatter()
return _formatter return _formatter
def format_fields(mi, prefs): def format_fields(mi, prefs):
f = formatter() f = formatter()
def safe_format(field): def safe_format(field):
return sanitize(f.safe_format( return f.safe_format(
getattr(prefs, field), mi, _('Template error'), mi, template_cache=_template_cache getattr(prefs, field), mi, _('Template error'), mi, template_cache=_template_cache
)) )
return map(safe_format, ('title_template', 'subtitle_template', 'footer_template')) return map(safe_format, ('title_template', 'subtitle_template', 'footer_template'))
@contextmanager @contextmanager
@ -240,12 +252,6 @@ def preserve_fields(obj, fields):
else: else:
setattr(obj, f, val) setattr(obj, f, val)
def normalize(x):
if isinstance(x, unicode):
import unicodedata
x = unicodedata.normalize('NFC', x)
return x
def format_text(mi, prefs): def format_text(mi, prefs):
with preserve_fields(mi, 'authors formatted_series_index'): with preserve_fields(mi, 'authors formatted_series_index'):
mi.authors = [a for a in mi.authors if a != _('Unknown')] mi.authors = [a for a in mi.authors if a != _('Unknown')]
@ -456,9 +462,12 @@ def load_styles(prefs, respect_disabled=True):
# }}} # }}}
def generate_cover(mi, prefs=None, as_qimage=False): def init_environment():
ensure_app() ensure_app()
load_builtin_fonts() load_builtin_fonts()
def generate_cover(mi, prefs=None, as_qimage=False):
init_environment()
prefs = prefs or cprefs prefs = prefs or cprefs
prefs = {k:prefs.get(k) for k in cprefs.defaults} prefs = {k:prefs.get(k) for k in cprefs.defaults}
prefs = Prefs(**prefs) prefs = Prefs(**prefs)