clone

a browser-first diagram editor with live svg previews, local drafts and encrypted, accountless sharing

main
diagramzip / renderers / python / blockdiag-family / src / blockdiag / utils / fontmap.py
fontmap.py 5.1K
1# -*- coding: utf-8 -*-2# Copyright 2011 Takeshi KOMIYA3#4# Licensed under the Apache License, Version 2.0 (the "License");5# you may not use this file except in compliance with the License.6# You may obtain a copy of the License at7#8# http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing, software11# distributed under the License is distributed on an "AS IS" BASIS,12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13# See the License for the specific language governing permissions and14# limitations under the License.1516import copy17import os18import re19from collections import namedtuple2021from blockdiag.utils.config import ConfigParser22from blockdiag.utils.logging import warning232425def parse_fontpath(path):26 if path is None:27 return (None, None)2829 match = re.search(r'^(.*):(\d)$', path)30 if match:31 return (match.group(1), int(match.group(2)))32 else:33 return (path, None)343536class FontInfo(object):37 def __init__(self, family, path, size):38 self.path = path39 self.size = int(size)4041 family = self._parse(family)42 self.name = family[0]43 self.generic_family = family[1]44 self.weight = family[2]45 self.style = family[3]4647 def __repr__(self):48 return ("<FontInfo familyname=%r size=%r>" %49 (self.familyname, self.size))5051 @property52 def familyname(self):53 if self.name:54 name = self.name + "-"55 else:56 name = ''5758 if self.generic_family == 'sans-serif':59 generic_family = 'sansserif'60 else:61 generic_family = self.generic_family6263 if self.weight == 'bold':64 return "%s%s-%s" % (name, generic_family, self.weight)65 else:66 return "%s%s-%s" % (name, generic_family, self.style)6768 def _parse(self, familyname):69 pattern = '^(?:(.*)-)?' + \70 '(serif|sansserif|monospace|fantasy|cursive)' + \71 '(?:-(normal|bold|italic|oblique))?$'7273 match = re.search(pattern, familyname or '')74 if match is None:75 msg = 'Unknown font family: %s' % familyname76 raise AttributeError(msg)7778 name = match.group(1) or ''79 generic_family = match.group(2)80 style = match.group(3) or ''8182 if generic_family == 'sansserif':83 generic_family = 'sans-serif'8485 if style == 'bold':86 weight = 'bold'87 style = 'normal'88 elif style in ('italic', 'oblique'):89 weight = 'normal'90 style = style91 else:92 weight = 'normal'93 style = 'normal'9495 return [name, generic_family, weight, style]9697 def duplicate(self):98 return copy.copy(self)99100101class FontMap(object):102 BASE_FONTSIZE = 11103 fontsize = BASE_FONTSIZE104 default_fontfamily = 'sansserif'105106 def __init__(self, filename=None):107 self.fonts = {}108 self.aliases = {}109110 if filename:111 self._parse_config(filename)112 self.set_default_font(None)113114 def set_default_fontfamily(self, fontfamily):115 self.default_fontfamily = fontfamily116 self.set_default_font(None)117118 def _parse_config(self, conffile):119 config = ConfigParser()120121 if hasattr(conffile, 'read'):122 config.read_file(conffile)123 elif os.path.isfile(conffile):124 config.read(conffile)125 else:126 msg = "fontmap file is not found: %s" % conffile127 raise RuntimeError(msg)128129 if config.has_section('fontmap'):130 for name, path in config.items('fontmap'):131 self.append_font(name, path)132133 if config.has_section('fontalias'):134 for name, family in config.items('fontalias'):135 self.aliases[name] = family136137 def set_default_font(self, path):138 if path is None and self.find() is not None:139 return140141 self.append_font(self.default_fontfamily, path)142143 def append_font(self, fontfamily, path):144 _path, _ = parse_fontpath(path)145 if path is None or os.path.isfile(_path):146 font = FontInfo(fontfamily, path, self.fontsize)147 self.fonts[font.familyname] = font148 else:149 warning('fontfile `%s` is not found: %s', fontfamily, path)150151 def _regulate_familyname(self, name):152 return FontInfo(name, None, self.BASE_FONTSIZE).familyname.lower()153154 def find(self, element=None):155 fontfamily = getattr(element, 'fontfamily', None) or \156 self.default_fontfamily157 fontfamily = self.aliases.get(fontfamily, fontfamily)158 fontsize = getattr(element, 'fontsize', None) or self.fontsize159160 name = self._regulate_familyname(fontfamily)161 if name in self.fonts:162 font = self.fonts[name].duplicate()163 font.size = fontsize164 elif element is not None:165 warning("Unknown fontfamily: %s", fontfamily)166 elem = namedtuple('Font', 'fontsize')(fontsize)167 font = self.find(elem)168 else:169 font = None170171 return font