e6d431c9e3
- added a try - except clause for the situations when for a font can't be determined the family and name - moved font parsing to the Geometry Editor: it is done everytime the Text tool is invoked - made sure that the HPGL postprocessor is not populated in the Excellon postprocessors in Preferences as it make no sense (HPGL is useful only for Geometries)
340 lines
12 KiB
Python
340 lines
12 KiB
Python
#########################################################################
|
|
### Borrowed code from 'https://github.com/gddc/ttfquery/blob/master/ ###
|
|
### and made it work with Python 3 #############
|
|
#########################################################################
|
|
|
|
import re, os, sys, glob
|
|
import itertools
|
|
|
|
from shapely.geometry import Point, Polygon
|
|
from shapely.affinity import translate, scale, rotate
|
|
from shapely.geometry import MultiPolygon
|
|
from shapely.geometry.base import BaseGeometry
|
|
|
|
import freetype as ft
|
|
from fontTools import ttLib
|
|
|
|
import logging
|
|
|
|
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:
|
|
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] 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:
|
|
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)
|