mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
MOBI Output: Make font used for generated masthead images user customizable. Various catalog fixes.
This commit is contained in:
commit
bae592ae3a
@ -42,6 +42,7 @@ def gui_catalog(fmt, title, dbspec, ids, out_file_name, sync, fmt_options,
|
|||||||
opts, args = parser.parse_args()
|
opts, args = parser.parse_args()
|
||||||
|
|
||||||
# Populate opts
|
# Populate opts
|
||||||
|
# opts.gui_search_text = something
|
||||||
opts.catalog_title = title
|
opts.catalog_title = title
|
||||||
opts.ids = ids
|
opts.ids = ids
|
||||||
opts.search_text = None
|
opts.search_text = None
|
||||||
|
@ -6,9 +6,14 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from PyQt4.Qt import Qt
|
||||||
|
|
||||||
from calibre.gui2.convert.mobi_output_ui import Ui_Form
|
from calibre.gui2.convert.mobi_output_ui import Ui_Form
|
||||||
from calibre.gui2.convert import Widget
|
from calibre.gui2.convert import Widget
|
||||||
|
from calibre.gui2.widgets import FontFamilyModel
|
||||||
|
from calibre.utils.fonts import fontconfig
|
||||||
|
|
||||||
|
font_family_model = None
|
||||||
|
|
||||||
class PluginWidget(Widget, Ui_Form):
|
class PluginWidget(Widget, Ui_Form):
|
||||||
|
|
||||||
@ -19,8 +24,35 @@ class PluginWidget(Widget, Ui_Form):
|
|||||||
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, 'mobi_output',
|
Widget.__init__(self, parent, 'mobi_output',
|
||||||
['prefer_author_sort', 'rescale_images', 'toc_title',
|
['prefer_author_sort', 'rescale_images', 'toc_title',
|
||||||
'dont_compress', 'no_inline_toc']
|
'dont_compress', 'no_inline_toc', 'masthead_font']
|
||||||
)
|
)
|
||||||
self.db, self.book_id = db, book_id
|
self.db, self.book_id = db, book_id
|
||||||
|
|
||||||
|
global font_family_model
|
||||||
|
if font_family_model is None:
|
||||||
|
font_family_model = FontFamilyModel()
|
||||||
|
try:
|
||||||
|
font_family_model.families = fontconfig.find_font_families(allowed_extensions=['ttf'])
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
font_family_model.families = []
|
||||||
|
print 'WARNING: Could not load fonts'
|
||||||
|
traceback.print_exc()
|
||||||
|
font_family_model.families.sort()
|
||||||
|
font_family_model.families[:0] = [_('Default')]
|
||||||
|
|
||||||
|
self.font_family_model = font_family_model
|
||||||
|
self.opt_masthead_font.setModel(self.font_family_model)
|
||||||
|
|
||||||
self.initialize_options(get_option, get_help, db, book_id)
|
self.initialize_options(get_option, get_help, db, book_id)
|
||||||
|
|
||||||
|
def set_value_handler(self, g, val):
|
||||||
|
if unicode(g.objectName()) in 'opt_masthead_font':
|
||||||
|
idx = -1
|
||||||
|
if val:
|
||||||
|
idx = g.findText(val, Qt.MatchFixedString)
|
||||||
|
if idx < 0:
|
||||||
|
idx = 0
|
||||||
|
g.setCurrentIndex(idx)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>400</width>
|
<width>421</width>
|
||||||
<height>300</height>
|
<height>300</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
@ -41,19 +41,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0">
|
|
||||||
<spacer name="verticalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>40</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="0">
|
<item row="4" column="0">
|
||||||
<widget class="QCheckBox" name="opt_dont_compress">
|
<widget class="QCheckBox" name="opt_dont_compress">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -68,6 +55,51 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="5" column="0" colspan="2">
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Kindle options</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Masthead font:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="opt_masthead_font"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>55</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<spacer name="verticalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
@ -403,10 +403,15 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
right = EPUB_MOBI.NumberToText(strings[1]).text
|
right = EPUB_MOBI.NumberToText(strings[1]).text
|
||||||
self.text = '%s-%s' % (left, right)
|
self.text = '%s-%s' % (left, right)
|
||||||
|
|
||||||
|
# Test for $xx,xxx
|
||||||
|
elif re.search('[$,]', self.number):
|
||||||
|
self.number_as_float = re.sub('[$,]','',self.number)
|
||||||
|
self.text = EPUB_MOBI.NumberToText(self.number_as_float).text
|
||||||
|
|
||||||
# Test for comma
|
# Test for comma
|
||||||
elif re.search(',', self.number):
|
elif re.search(',', self.number):
|
||||||
self.number_as_float = re.sub(',','',self.number)
|
self.number_as_float = re.sub(',','',self.number)
|
||||||
self.text = EPUB_MOBI.NumberToText(self.number.replace(',','')).text
|
self.text = EPUB_MOBI.NumberToText(self.number_as_float).text
|
||||||
|
|
||||||
# Test for hybrid e.g., 'K2'
|
# Test for hybrid e.g., 'K2'
|
||||||
elif re.search('[\D]+', self.number):
|
elif re.search('[\D]+', self.number):
|
||||||
@ -482,11 +487,6 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
catalog.createDirectoryStructure()
|
catalog.createDirectoryStructure()
|
||||||
catalog.copyResources()
|
catalog.copyResources()
|
||||||
catalog.buildSources()
|
catalog.buildSources()
|
||||||
|
|
||||||
- To do:
|
|
||||||
*** generateThumbnails() creates a default book image from book.svg, but the background
|
|
||||||
is black instead of white. This needs to be fixed (approx line #1418)
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# Number of discrete steps to catalog creation
|
# Number of discrete steps to catalog creation
|
||||||
@ -811,7 +811,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
# If failure, default mastheadImage.gif should still be in place
|
# If failure, default mastheadImage.gif should still be in place
|
||||||
if self.generateForKindle:
|
if self.generateForKindle:
|
||||||
try:
|
try:
|
||||||
self.generate_masthead_image(os.path.join(self.catalogPath,
|
self.generateMastheadImage(os.path.join(self.catalogPath,
|
||||||
'images/mastheadImage.gif'))
|
'images/mastheadImage.gif'))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@ -831,11 +831,14 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
# Merge opts.exclude_tag with opts.search_text
|
# Merge opts.exclude_tag with opts.search_text
|
||||||
|
|
||||||
# What if no exclude tags?
|
# What if no exclude tags?
|
||||||
exclude_tags = self.opts.exclude_tags.split(',')
|
empty_exclude_tags = False if len(self.opts.exclude_tags) else True
|
||||||
search_terms = []
|
search_phrase = ''
|
||||||
for tag in exclude_tags:
|
if not empty_exclude_tags:
|
||||||
search_terms.append("tag:%s" % tag)
|
exclude_tags = self.opts.exclude_tags.split(',')
|
||||||
search_phrase = "not (%s)" % " or ".join(search_terms)
|
search_terms = []
|
||||||
|
for tag in exclude_tags:
|
||||||
|
search_terms.append("tag:%s" % tag)
|
||||||
|
search_phrase = "not (%s)" % " or ".join(search_terms)
|
||||||
|
|
||||||
# If a list of ids are provided, don't use search_text
|
# If a list of ids are provided, don't use search_text
|
||||||
if self.opts.ids:
|
if self.opts.ids:
|
||||||
@ -846,6 +849,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
else:
|
else:
|
||||||
self.opts.search_text = search_phrase
|
self.opts.search_text = search_phrase
|
||||||
|
|
||||||
|
#print "fetchBooksByTitle(): opts.search_text: %s" % self.opts.search_text
|
||||||
# Fetch the database as a dictionary
|
# Fetch the database as a dictionary
|
||||||
data = self.plugin.search_sort_db(self.db, self.opts)
|
data = self.plugin.search_sort_db(self.db, self.opts)
|
||||||
|
|
||||||
@ -1455,7 +1459,6 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
|
|
||||||
self.updateProgressFullStep("'Genres'")
|
self.updateProgressFullStep("'Genres'")
|
||||||
|
|
||||||
# filtered_tags = {friendly:normalized, }
|
|
||||||
self.genre_tags_dict = self.filterDbTags(self.db.all_tags())
|
self.genre_tags_dict = self.filterDbTags(self.db.all_tags())
|
||||||
|
|
||||||
# Extract books matching filtered_tags
|
# Extract books matching filtered_tags
|
||||||
@ -2202,11 +2205,11 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
|
|
||||||
# GwR *** Can this be optimized?
|
# GwR *** Can this be optimized?
|
||||||
normalized_tag = None
|
normalized_tag = None
|
||||||
for genre_tag in self.genre_tags_dict:
|
for friendly_tag in self.genre_tags_dict:
|
||||||
if self.genre_tags_dict[genre_tag] == genre['tag']:
|
if self.genre_tags_dict[friendly_tag] == genre['tag']:
|
||||||
normalized_tag = self.genre_tags_dict[genre_tag]
|
normalized_tag = self.genre_tags_dict[friendly_tag]
|
||||||
break
|
break
|
||||||
textTag.insert(0, self.formatNCXText(NavigableString(genre_tag)))
|
textTag.insert(0, self.formatNCXText(NavigableString(friendly_tag)))
|
||||||
navLabelTag.insert(0,textTag)
|
navLabelTag.insert(0,textTag)
|
||||||
navPointVolumeTag.insert(0,navLabelTag)
|
navPointVolumeTag.insert(0,navLabelTag)
|
||||||
contentTag = Tag(ncx_soup, "content")
|
contentTag = Tag(ncx_soup, "content")
|
||||||
@ -2312,14 +2315,6 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
if not os.path.isdir(images_path):
|
if not os.path.isdir(images_path):
|
||||||
os.makedirs(images_path)
|
os.makedirs(images_path)
|
||||||
|
|
||||||
def getMarkerTags(self):
|
|
||||||
''' Return a list of special marker tags to be excluded from genre list '''
|
|
||||||
markerTags = []
|
|
||||||
markerTags.extend(self.opts.exclude_tags.split(','))
|
|
||||||
markerTags.extend(self.opts.note_tag.split(','))
|
|
||||||
markerTags.extend(self.opts.read_tag.split(','))
|
|
||||||
return markerTags
|
|
||||||
|
|
||||||
def filterDbTags(self, tags):
|
def filterDbTags(self, tags):
|
||||||
# Remove the special marker tags from the database's tag list,
|
# Remove the special marker tags from the database's tag list,
|
||||||
# return sorted list of normalized genre tags
|
# return sorted list of normalized genre tags
|
||||||
@ -2337,7 +2332,6 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
normalized_tags.append(re.sub('\W','',tag).lower())
|
normalized_tags.append(re.sub('\W','',tag).lower())
|
||||||
friendly_tags.append(tag)
|
friendly_tags.append(tag)
|
||||||
|
|
||||||
|
|
||||||
genre_tags_dict = dict(zip(friendly_tags,normalized_tags))
|
genre_tags_dict = dict(zip(friendly_tags,normalized_tags))
|
||||||
|
|
||||||
# Test for multiple genres resolving to same normalized form
|
# Test for multiple genres resolving to same normalized form
|
||||||
@ -2405,10 +2399,10 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
btc += 1
|
btc += 1
|
||||||
|
|
||||||
# Find the first instance of friendly_tag matching genre
|
# Find the first instance of friendly_tag matching genre
|
||||||
# GwR *** optimize
|
|
||||||
for friendly_tag in self.genre_tags_dict:
|
for friendly_tag in self.genre_tags_dict:
|
||||||
if self.genre_tags_dict[friendly_tag] == genre:
|
if self.genre_tags_dict[friendly_tag] == genre:
|
||||||
break
|
break
|
||||||
|
|
||||||
titleTag = body.find(attrs={'class':'title'})
|
titleTag = body.find(attrs={'class':'title'})
|
||||||
titleTag.insert(0,NavigableString('<b><i>%s</i></b>' % escape(friendly_tag)))
|
titleTag.insert(0,NavigableString('<b><i>%s</i></b>' % escape(friendly_tag)))
|
||||||
|
|
||||||
@ -2570,8 +2564,22 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
titleTag.insert(0,escape(NavigableString(title)))
|
titleTag.insert(0,escape(NavigableString(title)))
|
||||||
return soup
|
return soup
|
||||||
|
|
||||||
def generate_masthead_image(self, out_path):
|
def generateMastheadImage(self, out_path):
|
||||||
font_path = P('fonts/liberation/LiberationSerif-Bold.ttf')
|
from calibre.ebooks.conversion.config import load_defaults
|
||||||
|
from calibre.utils.fonts import fontconfig
|
||||||
|
font_path = default_font = P('fonts/liberation/LiberationSerif-Bold.ttf')
|
||||||
|
recs = load_defaults('mobi_output')
|
||||||
|
masthead_font_family = recs.get('masthead_font', 'Default')
|
||||||
|
|
||||||
|
if masthead_font_family != 'Default':
|
||||||
|
masthead_font = fontconfig.files_for_family(masthead_font_family)
|
||||||
|
# Assume 'normal' always in dict, else use default
|
||||||
|
# {'normal': (path_to_font, friendly name)}
|
||||||
|
if 'normal' in masthead_font:
|
||||||
|
font_path = masthead_font['normal'][0]
|
||||||
|
|
||||||
|
if not font_path or not os.access(font_path, os.R_OK):
|
||||||
|
font_path = default_font
|
||||||
|
|
||||||
MI_WIDTH = 600
|
MI_WIDTH = 600
|
||||||
MI_HEIGHT = 60
|
MI_HEIGHT = 60
|
||||||
@ -2584,7 +2592,11 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
|
|
||||||
img = Image.new('RGB', (MI_WIDTH, MI_HEIGHT), 'white')
|
img = Image.new('RGB', (MI_WIDTH, MI_HEIGHT), 'white')
|
||||||
draw = ImageDraw.Draw(img)
|
draw = ImageDraw.Draw(img)
|
||||||
font = ImageFont.truetype(font_path, 48)
|
try:
|
||||||
|
font = ImageFont.truetype(font_path, 48)
|
||||||
|
except:
|
||||||
|
self.opts.log.error(" Failed to load user-specifed font '%s'" % font_path)
|
||||||
|
font = ImageFont.truetype(default_font, 48)
|
||||||
text = self.title.encode('utf-8')
|
text = self.title.encode('utf-8')
|
||||||
width, height = draw.textsize(text, font=font)
|
width, height = draw.textsize(text, font=font)
|
||||||
left = max(int((MI_WIDTH - width)/2.), 0)
|
left = max(int((MI_WIDTH - width)/2.), 0)
|
||||||
@ -2682,6 +2694,14 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
else:
|
else:
|
||||||
return char
|
return char
|
||||||
|
|
||||||
|
def getMarkerTags(self):
|
||||||
|
''' Return a list of special marker tags to be excluded from genre list '''
|
||||||
|
markerTags = []
|
||||||
|
markerTags.extend(self.opts.exclude_tags.split(','))
|
||||||
|
markerTags.extend(self.opts.note_tag.split(','))
|
||||||
|
markerTags.extend(self.opts.read_tag.split(','))
|
||||||
|
return markerTags
|
||||||
|
|
||||||
def processSpecialTags(self, tags, this_title, opts):
|
def processSpecialTags(self, tags, this_title, opts):
|
||||||
tag_list = []
|
tag_list = []
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
|
@ -989,6 +989,22 @@ class BasicNewsRecipe(Recipe):
|
|||||||
MI_HEIGHT = 60
|
MI_HEIGHT = 60
|
||||||
|
|
||||||
def default_masthead_image(self, out_path):
|
def default_masthead_image(self, out_path):
|
||||||
|
from calibre.ebooks.conversion.config import load_defaults
|
||||||
|
from calibre.utils.fonts import fontconfig
|
||||||
|
font_path = default_font = P('fonts/liberation/LiberationSerif-Bold.ttf')
|
||||||
|
recs = load_defaults('mobi_output')
|
||||||
|
masthead_font_family = recs.get('masthead_font', 'Default')
|
||||||
|
|
||||||
|
if masthead_font_family != 'Default':
|
||||||
|
masthead_font = fontconfig.files_for_family(masthead_font_family)
|
||||||
|
# Assume 'normal' always in dict, else use default
|
||||||
|
# {'normal': (path_to_font, friendly name)}
|
||||||
|
if 'normal' in masthead_font:
|
||||||
|
font_path = masthead_font['normal'][0]
|
||||||
|
|
||||||
|
if not font_path or not os.access(font_path, os.R_OK):
|
||||||
|
font_path = default_font
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
Image, ImageDraw, ImageFont
|
Image, ImageDraw, ImageFont
|
||||||
@ -997,7 +1013,10 @@ class BasicNewsRecipe(Recipe):
|
|||||||
|
|
||||||
img = Image.new('RGB', (self.MI_WIDTH, self.MI_HEIGHT), 'white')
|
img = Image.new('RGB', (self.MI_WIDTH, self.MI_HEIGHT), 'white')
|
||||||
draw = ImageDraw.Draw(img)
|
draw = ImageDraw.Draw(img)
|
||||||
font = ImageFont.truetype(P('fonts/liberation/LiberationSerif-Bold.ttf'), 48)
|
try:
|
||||||
|
font = ImageFont.truetype(font_path, 48)
|
||||||
|
except:
|
||||||
|
font = ImageFont.truetype(default_font, 48)
|
||||||
text = self.get_masthead_title().encode('utf-8')
|
text = self.get_masthead_title().encode('utf-8')
|
||||||
width, height = draw.textsize(text, font=font)
|
width, height = draw.textsize(text, font=font)
|
||||||
left = max(int((self.MI_WIDTH - width)/2.), 0)
|
left = max(int((self.MI_WIDTH - width)/2.), 0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user