flatcam/appParsers/ParseFont.py

355 lines
12 KiB
Python
Raw Normal View History

# ##########################################################
# 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 #
# ######################################################################
2019-01-03 19:25:08 +00:00
import re
import os
import sys
import glob
2019-01-03 19:25:08 +00:00
from shapely.geometry import Polygon
from shapely.affinity import translate, scale
2019-01-03 19:25:08 +00:00
from shapely.geometry import MultiPolygon
2019-01-03 19:25:08 +00:00
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
2019-01-03 19:25:08 +00:00
log = logging.getLogger('base2')
class ParseFont:
2019-01-03 19:25:08 +00:00
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_lst = []
2019-01-03 19:25:08 +00:00
for line in data:
result = match.match(line)
if result:
set_lst.append(result.group(1))
return set_lst
2019-01-03 19:25:08 +00:00
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 = os.path.expanduser(os.path.expandvars(directory))
2019-01-03 19:25:08 +00:00
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 = os.path.expanduser(os.path.expandvars(directory))
2019-01-03 19:25:08 +00:00
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:
2019-01-03 19:25:08 +00:00
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):
2019-01-03 19:25:08 +00:00
super(ParseFont, self).__init__()
self.app = app
2019-01-03 19:25:08 +00:00
# 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, ]
2019-01-03 19:25:08 +00:00
# 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
2019-01-03 19:25:08 +00:00
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:
2019-01-03 19:25:08 +00:00
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"
2019-01-03 19:25:08 +00:00
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:
2019-01-03 19:25:08 +00:00
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)