| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- # ##########################################################
- # FlatCAM: 2D Post-processing for Manufacturing #
- # File Author: Marius Adrian Stanciu (c) #
- # Date: 3/10/2019 #
- # MIT Licence #
- # ##########################################################
- # ######################################################################
- # ## Borrowed code from 'https://github.com/gddc/ttfquery/blob/master/ #
- # ## and made it work with Python 3 #
- # ######################################################################
- import re, os, sys, glob
- from shapely.geometry import Point, Polygon
- from shapely.affinity import translate, scale
- from shapely.geometry import MultiPolygon
- import freetype as ft
- from fontTools import ttLib
- import logging
- import gettext
- import appTranslation as fcTranslate
- import builtins
- fcTranslate.apply_language('strings')
- if '_' not in builtins.__dict__:
- _ = gettext.gettext
- log = logging.getLogger('base2')
- class ParseFont():
- FONT_SPECIFIER_NAME_ID = 4
- FONT_SPECIFIER_FAMILY_ID = 1
- @staticmethod
- def get_win32_font_path():
- """Get User-specific font directory on Win32"""
- try:
- import winreg
- except ImportError:
- return os.path.join(os.environ['WINDIR'], 'Fonts')
- else:
- k = winreg.OpenKey(
- winreg.HKEY_CURRENT_USER,
- r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
- try:
- # should check that k is valid? How?
- return winreg.QueryValueEx(k, "Fonts")[0]
- finally:
- winreg.CloseKey(k)
- @staticmethod
- def get_linux_font_paths():
- """Get system font directories on Linux/Unix
- Uses /usr/sbin/chkfontpath to get the list
- of system-font directories, note that many
- of these will *not* be truetype font directories.
- If /usr/sbin/chkfontpath isn't available, uses
- returns a set of common Linux/Unix paths
- """
- executable = '/usr/sbin/chkfontpath'
- if os.path.isfile(executable):
- data = os.popen(executable).readlines()
- match = re.compile('\d+: (.+)')
- set = []
- for line in data:
- result = match.match(line)
- if result:
- set.append(result.group(1))
- return set
- else:
- directories = [
- # what seems to be the standard installation point
- "/usr/X11R6/lib/X11/fonts/TTF/",
- # common application, not really useful
- "/usr/lib/openoffice/share/fonts/truetype/",
- # documented as a good place to install new fonts...
- "/usr/share/fonts",
- "/usr/local/share/fonts",
- # seems to be where fonts are installed for an individual user?
- "~/.fonts",
- ]
- dir_set = []
- for directory in directories:
- directory = directory = os.path.expanduser(os.path.expandvars(directory))
- try:
- if os.path.isdir(directory):
- for path, children, files in os.walk(directory):
- dir_set.append(path)
- except (IOError, OSError, TypeError, ValueError):
- pass
- return dir_set
- @staticmethod
- def get_mac_font_paths():
- """Get system font directories on MacOS
- """
- directories = [
- # okay, now the OS X variants...
- "~/Library/Fonts/",
- "/Library/Fonts/",
- "/Network/Library/Fonts/",
- "/System/Library/Fonts/",
- "System Folder:Fonts:",
- ]
- dir_set = []
- for directory in directories:
- directory = directory = os.path.expanduser(os.path.expandvars(directory))
- try:
- if os.path.isdir(directory):
- for path, children, files in os.walk(directory):
- dir_set.append(path)
- except (IOError, OSError, TypeError, ValueError):
- pass
- return dir_set
- @staticmethod
- def get_win32_fonts(font_directory=None):
- """Get list of explicitly *installed* font names"""
- import winreg
- if font_directory is None:
- font_directory = ParseFont.get_win32_font_path()
- k = None
- items = {}
- for keyName in (
- r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts",
- r"SOFTWARE\Microsoft\Windows\CurrentVersion\Fonts",
- ):
- try:
- k = winreg.OpenKey(
- winreg.HKEY_LOCAL_MACHINE,
- keyName
- )
- except OSError as err:
- pass
- if not k:
- # couldn't open either WinNT or Win98 key???
- return glob.glob(os.path.join(font_directory, '*.ttf'))
- try:
- # should check that k is valid? How?
- for index in range(winreg.QueryInfoKey(k)[1]):
- key, value, _ = winreg.EnumValue(k, index)
- if not os.path.dirname(value):
- value = os.path.join(font_directory, value)
- value = os.path.abspath(value).lower()
- if value[-4:] == '.ttf':
- items[value] = 1
- return list(items.keys())
- finally:
- winreg.CloseKey(k)
- @staticmethod
- def get_font_name(font_path):
- """
- Get the short name from the font's names table
- From 'https://github.com/gddc/ttfquery/blob/master/ttfquery/describe.py'
- and
- http://www.starrhorne.com/2012/01/18/
- how-to-extract-font-names-from-ttf-files-using-python-and-our-old-friend-the-command-line.html
- ported to Python 3 here: https://gist.github.com/pklaus/dce37521579513c574d0
- """
- name = ""
- family = ""
- font = ttLib.TTFont(font_path)
- for record in font['name'].names:
- if b'\x00' in record.string:
- name_str = record.string.decode('utf-16-be')
- else:
- # name_str = record.string.decode('utf-8')
- name_str = record.string.decode('latin-1')
- if record.nameID == ParseFont.FONT_SPECIFIER_NAME_ID and not name:
- name = name_str
- elif record.nameID == ParseFont.FONT_SPECIFIER_FAMILY_ID and not family:
- family = name_str
- if name and family:
- break
- return name, family
- def __init__(self, app, parent=None):
- super(ParseFont, self).__init__()
- self.app = app
- # regular fonts
- self.regular_f = {}
- # bold fonts
- self.bold_f = {}
- # italic fonts
- self.italic_f = {}
- # bold and italic fonts
- self.bold_italic_f = {}
- def get_fonts(self, paths=None):
- """
- Find fonts in paths, or the system paths if not given
- """
- files = {}
- if paths is None:
- if sys.platform == 'win32':
- font_directory = ParseFont.get_win32_font_path()
- paths = [font_directory,]
- # now get all installed fonts directly...
- for f in self.get_win32_fonts(font_directory):
- files[f] = 1
- elif sys.platform == 'linux':
- paths = ParseFont.get_linux_font_paths()
- else:
- paths = ParseFont.get_mac_font_paths()
- elif isinstance(paths, str):
- paths = [paths]
- for path in paths:
- for file in glob.glob(os.path.join(path, '*.ttf')):
- files[os.path.abspath(file)] = 1
- return list(files.keys())
- def get_fonts_by_types(self):
- system_fonts = self.get_fonts()
- # split the installed fonts by type: regular, bold, italic (oblique), bold-italic and
- # store them in separate dictionaries {name: file_path/filename.ttf}
- for font in system_fonts:
- try:
- name, family = ParseFont.get_font_name(font)
- except Exception as e:
- log.debug("ParseFont.get_fonts_by_types() --> Could not get the font name. %s" % str(e))
- continue
- if 'Bold' in name and 'Italic' in name:
- name = name.replace(" Bold Italic", '')
- self.bold_italic_f.update({name: font})
- elif 'Bold' in name and 'Oblique' in name:
- name = name.replace(" Bold Oblique", '')
- self.bold_italic_f.update({name: font})
- elif 'Bold' in name:
- name = name.replace(" Bold", '')
- self.bold_f.update({name: font})
- elif 'SemiBold' in name:
- name = name.replace(" SemiBold", '')
- self.bold_f.update({name: font})
- elif 'DemiBold' in name:
- name = name.replace(" DemiBold", '')
- self.bold_f.update({name: font})
- elif 'Demi' in name:
- name = name.replace(" Demi", '')
- self.bold_f.update({name: font})
- elif 'Italic' in name:
- name = name.replace(" Italic", '')
- self.italic_f.update({name: font})
- elif 'Oblique' in name:
- name = name.replace(" Italic", '')
- self.italic_f.update({name: font})
- else:
- try:
- name = name.replace(" Regular", '')
- except Exception as e:
- pass
- self.regular_f.update({name: font})
- log.debug("Font parsing is finished.")
- def font_to_geometry(self, char_string, font_name, font_type, font_size, units='MM', coordx=0, coordy=0):
- path = []
- scaled_path = []
- path_filename = ""
- regular_dict = self.regular_f
- bold_dict = self.bold_f
- italic_dict = self.italic_f
- bold_italic_dict = self.bold_italic_f
- try:
- if font_type == 'bi':
- path_filename = bold_italic_dict[font_name]
- elif font_type == 'bold':
- path_filename = bold_dict[font_name]
- elif font_type == 'italic':
- path_filename = italic_dict[font_name]
- elif font_type == 'regular':
- path_filename = regular_dict[font_name]
- except Exception as e:
- self.app.inform.emit('[ERROR_NOTCL] %s' % _("Font not supported, try another one."))
- log.debug("[ERROR_NOTCL] Font Loading: %s" % str(e))
- return "flatcam font parse failed"
- face = ft.Face(path_filename)
- face.set_char_size(int(font_size) * 64)
- pen_x = coordx
- previous = 0
- # done as here: https://www.freetype.org/freetype2/docs/tutorial/step2.html
- for char in char_string:
- glyph_index = face.get_char_index(char)
- try:
- if previous > 0 and glyph_index > 0:
- delta = face.get_kerning(previous, glyph_index)
- pen_x += delta.x
- except Exception as e:
- pass
- face.load_glyph(glyph_index)
- # face.load_char(char, flags=8)
- slot = face.glyph
- outline = slot.outline
- start, end = 0, 0
- for i in range(len(outline.contours)):
- end = outline.contours[i]
- points = outline.points[start:end + 1]
- points.append(points[0])
- char_geo = Polygon(points)
- char_geo = translate(char_geo, xoff=pen_x, yoff=coordy)
- path.append(char_geo)
- start = end + 1
- pen_x += slot.advance.x
- previous = glyph_index
- for item in path:
- if units == 'MM':
- scaled_path.append(scale(item, 0.0080187969924812, 0.0080187969924812, origin=(coordx, coordy)))
- else:
- scaled_path.append(scale(item, 0.00031570066, 0.00031570066, origin=(coordx, coordy)))
- return MultiPolygon(scaled_path)
|