mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Auto convert SVG to PNG icons when creating icon theme from folder. Also allow specifying a license for the icon theme
This commit is contained in:
parent
a12182694a
commit
4484c2eb58
@ -11,7 +11,7 @@ from io import BytesIO
|
|||||||
|
|
||||||
from PyQt5.Qt import (
|
from PyQt5.Qt import (
|
||||||
QImageReader, QFormLayout, QVBoxLayout, QSplitter, QGroupBox, QListWidget,
|
QImageReader, QFormLayout, QVBoxLayout, QSplitter, QGroupBox, QListWidget,
|
||||||
QLineEdit, QSpinBox, QTextEdit, QSize, QListWidgetItem, QIcon
|
QLineEdit, QSpinBox, QTextEdit, QSize, QListWidgetItem, QIcon, QImage,
|
||||||
)
|
)
|
||||||
|
|
||||||
from calibre import walk, fit_image
|
from calibre import walk, fit_image
|
||||||
@ -28,21 +28,35 @@ IMAGE_EXTENSIONS = {'png', 'jpg', 'jpeg'}
|
|||||||
THEME_COVER = 'icon-theme-cover.jpg'
|
THEME_COVER = 'icon-theme-cover.jpg'
|
||||||
THEME_METADATA = 'metadata.json'
|
THEME_METADATA = 'metadata.json'
|
||||||
|
|
||||||
|
def render_svg(filepath):
|
||||||
|
must_use_qt(headless=False)
|
||||||
|
pngpath = filepath[:-4] + '.png'
|
||||||
|
i = QImage(filepath)
|
||||||
|
i.save(pngpath)
|
||||||
|
|
||||||
def read_images_from_folder(path):
|
def read_images_from_folder(path):
|
||||||
name_map = {}
|
name_map = {}
|
||||||
path = os.path.abspath(path)
|
path = os.path.abspath(path)
|
||||||
for filepath in walk(path):
|
for filepath in walk(path):
|
||||||
name = os.path.relpath(filepath, path).replace(os.sep, '/').lower()
|
name = os.path.relpath(filepath, path).replace(os.sep, '/').lower()
|
||||||
ext = name.rpartition('.')[-1]
|
ext = name.rpartition('.')[-1]
|
||||||
|
bname = os.path.basename(name)
|
||||||
|
if bname.startswith('.') or bname.startswith('_'):
|
||||||
|
continue
|
||||||
|
if ext == 'svg':
|
||||||
|
render_svg(filepath)
|
||||||
|
ext = 'png'
|
||||||
|
filepath = filepath[:-4] + '.png'
|
||||||
|
name = name[:-4] + '.png'
|
||||||
if ext in IMAGE_EXTENSIONS:
|
if ext in IMAGE_EXTENSIONS:
|
||||||
name_map[name] = filepath
|
name_map[name] = filepath
|
||||||
return name_map
|
return name_map
|
||||||
|
|
||||||
class Theme(object):
|
class Theme(object):
|
||||||
|
|
||||||
def __init__(self, title='', author='', version=-1, description='', cover=None):
|
def __init__(self, title='', author='', version=-1, description='', license='Unknown', cover=None):
|
||||||
self.title, self.author, self.version, self.description = title, author, version, description
|
self.title, self.author, self.version, self.description = title, author, version, description
|
||||||
self.cover = cover
|
self.license, self.cover = license, cover
|
||||||
|
|
||||||
class Report(object):
|
class Report(object):
|
||||||
|
|
||||||
@ -50,6 +64,10 @@ class Report(object):
|
|||||||
self.path, self.name_map, self.extra, self.missing, self.theme = path, name_map, extra, missing, theme
|
self.path, self.name_map, self.extra, self.missing, self.theme = path, name_map, extra, missing, theme
|
||||||
self.bad = {}
|
self.bad = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return ascii_filename(self.theme.title).replace(' ', '_').lower()
|
||||||
|
|
||||||
def read_theme_from_folder(path):
|
def read_theme_from_folder(path):
|
||||||
path = os.path.abspath(path)
|
path = os.path.abspath(path)
|
||||||
current_image_map = read_images_from_folder(P('images', allow_user_override=False))
|
current_image_map = read_images_from_folder(P('images', allow_user_override=False))
|
||||||
@ -74,7 +92,8 @@ def read_theme_from_folder(path):
|
|||||||
return int(x)
|
return int(x)
|
||||||
except Exception:
|
except Exception:
|
||||||
return -1
|
return -1
|
||||||
theme = Theme(metadata.get('title', ''), metadata.get('author', ''), safe_int(metadata.get('version', -1)), metadata.get('description', ''))
|
g = lambda x, defval='': metadata.get(x, defval)
|
||||||
|
theme = Theme(g('title'), g('author'), safe_int(g('version', -1)), g('description'), g('license', 'Unknown'))
|
||||||
|
|
||||||
ans = Report(path, name_map, extra, missing, theme)
|
ans = Report(path, name_map, extra, missing, theme)
|
||||||
try:
|
try:
|
||||||
@ -176,6 +195,11 @@ class ThemeCreateDialog(Dialog):
|
|||||||
self.version = v = QSpinBox(self)
|
self.version = v = QSpinBox(self)
|
||||||
v.setMinimum(1), v.setMaximum(1000000)
|
v.setMinimum(1), v.setMaximum(1000000)
|
||||||
l.addRow(_('&Version:'), v)
|
l.addRow(_('&Version:'), v)
|
||||||
|
self.license = lc = QLineEdit(self)
|
||||||
|
l.addRow(_('&License:'), lc)
|
||||||
|
lc.setText(_(
|
||||||
|
'The license for the icons in this theme. Common choices are'
|
||||||
|
' Creative Commons or Public Domain.'))
|
||||||
self.description = QTextEdit(self)
|
self.description = QTextEdit(self)
|
||||||
l.addRow(self.description)
|
l.addRow(self.description)
|
||||||
self.refresh_button = rb = self.bb.addButton(_('&Refresh'), self.bb.ActionRole)
|
self.refresh_button = rb = self.bb.addButton(_('&Refresh'), self.bb.ActionRole)
|
||||||
@ -189,6 +213,7 @@ class ThemeCreateDialog(Dialog):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def metadata(self):
|
def metadata(self):
|
||||||
|
self.report.theme.title = self.title.text().strip() # Needed for report.name to work
|
||||||
return {
|
return {
|
||||||
'title': self.title.text().strip(),
|
'title': self.title.text().strip(),
|
||||||
'author': self.author.text().strip(),
|
'author': self.author.text().strip(),
|
||||||
@ -196,6 +221,8 @@ class ThemeCreateDialog(Dialog):
|
|||||||
'description': self.description.toPlainText().strip(),
|
'description': self.description.toPlainText().strip(),
|
||||||
'number': len(self.report.name_map) - len(self.report.extra),
|
'number': len(self.report.name_map) - len(self.report.extra),
|
||||||
'date': utcnow().date().isoformat(),
|
'date': utcnow().date().isoformat(),
|
||||||
|
'name': self.report.name,
|
||||||
|
'license': self.license.text().strip() or 'Unknown',
|
||||||
}
|
}
|
||||||
|
|
||||||
def save_metadata(self):
|
def save_metadata(self):
|
||||||
@ -213,6 +240,7 @@ class ThemeCreateDialog(Dialog):
|
|||||||
self.author.setText((theme.author or '').strip())
|
self.author.setText((theme.author or '').strip())
|
||||||
self.version.setValue(theme.version or 1)
|
self.version.setValue(theme.version or 1)
|
||||||
self.description.setText((theme.description or '').strip())
|
self.description.setText((theme.description or '').strip())
|
||||||
|
self.license.setText((theme.license or 'Unknown').strip())
|
||||||
if self.report.missing:
|
if self.report.missing:
|
||||||
title = _('%d icons missing in this theme') % len(self.report.missing)
|
title = _('%d icons missing in this theme') % len(self.report.missing)
|
||||||
else:
|
else:
|
||||||
@ -244,13 +272,13 @@ def create_themeball(report):
|
|||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
compress(buf, out, level=9)
|
compress(buf, out, level=9)
|
||||||
buf = BytesIO()
|
buf = BytesIO()
|
||||||
prefix = ascii_filename(report.theme.title).replace(' ', '_').lower()
|
prefix = report.name
|
||||||
with ZipFile(buf, 'w') as zf:
|
with ZipFile(buf, 'w') as zf:
|
||||||
with open(os.path.join(report.path, THEME_METADATA), 'rb') as f:
|
with open(os.path.join(report.path, THEME_METADATA), 'rb') as f:
|
||||||
zf.writestr(prefix + '/' + THEME_METADATA, f.read())
|
zf.writestr(prefix + '/' + THEME_METADATA, f.read())
|
||||||
zf.writestr(prefix + '/' + THEME_COVER, create_cover(report))
|
zf.writestr(prefix + '/' + THEME_COVER, create_cover(report))
|
||||||
zf.writestr(prefix + '/' + 'icons.zip.xz', out.getvalue(), compression=ZIP_STORED)
|
zf.writestr(prefix + '/' + 'icons.zip.xz', out.getvalue(), compression=ZIP_STORED)
|
||||||
return buf.getvalue()
|
return buf.getvalue(), prefix
|
||||||
|
|
||||||
|
|
||||||
def create_theme(folder=None, parent=None):
|
def create_theme(folder=None, parent=None):
|
||||||
@ -264,10 +292,10 @@ def create_theme(folder=None, parent=None):
|
|||||||
if d.exec_() != d.Accepted:
|
if d.exec_() != d.Accepted:
|
||||||
return
|
return
|
||||||
d.save_metadata()
|
d.save_metadata()
|
||||||
raw = create_themeball(d.report)
|
raw, prefix = create_themeball(d.report)
|
||||||
dest = choose_save_file(parent, 'create-icon-theme-dest', _(
|
dest = choose_save_file(parent, 'create-icon-theme-dest', _(
|
||||||
'Choose destination for icon theme'),
|
'Choose destination for icon theme'),
|
||||||
[(_('ZIP files'), ['zip'])], initial_filename='my-calibre-icon-theme.zip')
|
[(_('ZIP files'), ['zip'])], initial_filename=prefix + '.zip')
|
||||||
if dest:
|
if dest:
|
||||||
with open(dest, 'wb') as f:
|
with open(dest, 'wb') as f:
|
||||||
f.write(raw)
|
f.write(raw)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user