script.module.iptvlib/lib/skinutils/fonts.py

211 lines
6.9 KiB
Python

"""
Created on 09/08/2011
@author: mikel
"""
import os
import shutil
import xml.etree.ElementTree as ET
import xbmc
from . import SkinUtilsError, check_skin_writability, reload_skin, try_remove_file, case_file_exists, \
DocumentCache, get_logger
class FontXmlError(SkinUtilsError):
pass
class FontManager:
__installed_names = None
__installed_fonts = None
__doc_cache = None
def _list_skin_font_files(self):
font_xml_list = []
skin_path = xbmc.translatePath("special://skin/")
# Go into each dir. Could be 720, 1080...
for dir_item in os.listdir(skin_path):
dir_path = os.path.join(skin_path, dir_item)
if os.path.isdir(dir_path):
# Try with font.xml
file = os.path.join(dir_path, "font.xml")
if case_file_exists(file):
font_xml_list.append(file)
# Don't try the next step on windows, wasted time
file = os.path.join(dir_path, "Font.xml")
if case_file_exists(file):
font_xml_list.append(file)
return font_xml_list
def __init__(self):
self.__installed_names = []
self.__installed_fonts = []
self.__doc_cache = DocumentCache()
# Check if the environment is sane
check_skin_writability()
# Initialize the doc cache with the skin's files
for file in self._list_skin_font_files():
self.__doc_cache.add(file)
def is_name_installed(self, name):
return name in self.__installed_names
def is_font_installed(self, file):
return file in self.__installed_fonts
def _get_font_attr(self, node, name):
attrnode = node.find(name)
if attrnode is not None:
return attrnode.text
def _copy_font_file(self, file):
skin_font_path = xbmc.translatePath("special://skin/fonts/")
file_name = os.path.basename(file)
dest_file = os.path.join(skin_font_path, file_name)
# TODO: Unix systems could use symlinks
# Check if it's already there
if dest_file not in self.__installed_fonts:
self.__installed_fonts.append(dest_file)
# Overwrite if file exists
shutil.copyfile(file, dest_file)
def _add_font_attr(self, fontdef, name, value):
attr = ET.SubElement(fontdef, name)
attr.text = value
attr.tail = "\n\t\t\t"
return attr
def _install_font_def(self, skin_file, name, filename, size, style="", aspect="", linespacing=""):
# Add it to the registry
self.__installed_names.append(name)
# Get the parsed skin font file
font_doc = self.__doc_cache.read(skin_file)
# Iterate over all the fontsets on the file
for fontset in font_doc.getroot().findall("fontset"):
fontset.findall("font")[-1].tail = "\n\t\t"
fontdef = ET.SubElement(fontset, "font")
fontdef.text, fontdef.tail = "\n\t\t\t", "\n\t"
self._add_font_attr(fontdef, "name", name)
# We get the full file path to the font, so let's basename
self._add_font_attr(fontdef, "filename", os.path.basename(filename))
self._copy_font_file(filename)
last = self._add_font_attr(fontdef, "size", size)
if style:
if style in ["normal", "bold", "italics", "bolditalics", "lighten"]:
last = self._add_font_attr(fontdef, "style", style)
else:
raise FontXmlError(
"Font '%s' has an invalid style definition: %s"
% (name, style)
)
if aspect:
last = self._add_font_attr(fontdef, "aspect", aspect)
if linespacing:
last = self._add_font_attr(fontdef, "linespacing", linespacing)
last.tail = "\n\t\t"
def _install_file(self, doc_cache, user_file, skin_file, font_path):
user_doc = doc_cache.read(user_file)
# Handle only the first fontset
fontset = user_doc.getroot().find("fontset")
if len(fontset):
# Every font definition inside it
for item in fontset.findall("font"):
name = self._get_font_attr(item, "name")
# Basic check for malformed defs.
if name is None:
raise FontXmlError("Malformed XML: No name for font definition.")
# Omit already defined fonts
elif not self.is_name_installed(name):
font_file_path = os.path.join(
font_path, self._get_font_attr(item, "filename")
)
self._install_font_def(
skin_file,
name,
font_file_path,
self._get_font_attr(item, "size"),
self._get_font_attr(item, "style"),
self._get_font_attr(item, "aspect"),
self._get_font_attr(item, "linespacing")
)
def _get_res_folder(self, path):
return os.path.basename(os.path.dirname(path))
def _get_res_filename(self, res_folder, user_file):
path, ext = os.path.splitext(user_file)
return path + '-' + res_folder + ext
def install_file(self, user_file, font_path, commit=True, clear=True):
doc_cache = DocumentCache()
# If the file does not exist the following will fail
doc_cache.add(user_file)
# Install the file into every cached skin font file
for skin_file in self.__doc_cache.list_files():
res_folder = self._get_res_folder(skin_file)
res_file = self._get_res_filename(res_folder, user_file)
# If an specific res file exists...
if os.path.isfile(res_file):
self._install_file(doc_cache, res_file, skin_file, font_path)
# Otherwise use the dafault fallback
else:
self._install_file(doc_cache, user_file, skin_file, font_path)
# If save was requested
if commit:
self.__doc_cache.write_all()
# Clear cached docs after write (if requested)
if clear:
self.__doc_cache.clear_all()
def remove_font(self, name):
pass
def remove_installed_names(self):
self.__doc_cache.rollback_all()
def remove_installed_fonts(self):
for item in self.__installed_fonts:
if not try_remove_file(item):
get_logger().error(
'Failed removing font file "%s". XBMC may still be using it.' % item
)
def cleanup(self):
self.remove_installed_names()
# Reload skin so font files are no longer in use, and then delete them
reload_skin()
self.remove_installed_fonts()
def __del__(self):
self.cleanup()