Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ recursive-include kiva/agg *.py *.i *.cpp *.h *.c
recursive-include kiva/agg/agg-24 *
recursive-include kiva/agg/freetype2 *
recursive-include kiva/agg/LICENSES *
recursive-include kiva/fonttools/tests/data *.ttc *.ttf
recursive-include kiva/fonttools/tests/data *.ttc *.ttf *.afm
recursive-include kiva/fonttools/LICENSES *
recursive-include kiva/gl *.h *.cpp *.i LICENSE_*
recursive-include kiva/quartz *.pyx *.pxi *.pxd mac_context*.*
21 changes: 21 additions & 0 deletions kiva/fonttools/LICENSES/LICENSE_fonttools
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2017 Just van Rossum

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
36 changes: 18 additions & 18 deletions kiva/fonttools/_scan_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
import logging
import os

from fontTools.afmLib import AFM
from fontTools.ttLib import TTCollection, TTFont, TTLibError

from kiva.fonttools import afm
from kiva.fonttools._constants import weight_dict
from kiva.fonttools._util import get_ttf_prop_dict, weight_as_number

Expand Down Expand Up @@ -89,18 +89,10 @@ def _build_afm_entries(fpath):
instance or an empty list if there was an error.
"""
try:
fh = open(fpath, "r")
except OSError:
logger.error(f"Could not open font file {fpath}", exc_info=True)
return []

try:
font = afm.AFM(fh)
except RuntimeError:
font = AFM(fpath)
except Exception:
logger.error(f"Could not parse font file {fpath}", exc_info=True)
return []
finally:
fh.close()

try:
return [_afm_font_property(fpath, font)]
Expand Down Expand Up @@ -140,17 +132,17 @@ def _build_ttf_entries(fpath):
return entries


def _afm_font_property(fontpath, font):
def _afm_font_property(fpath, font):
""" A function for populating a :class:`FontEntry` instance by
extracting information from the AFM font file.

*font* is a class:`AFM` instance.
"""
family = font.get_familyname()
fontname = font.get_fontname().lower()
family = font.FamilyName
fontname = font.FullName.lower()

# Styles are: italic, oblique, and normal (default)
if font.get_angle() != 0 or family.lower().find("italic") >= 0:
if float(font.ItalicAngle) != 0.0 or family.lower().find("italic") >= 0:
style = "italic"
elif family.lower().find("oblique") >= 0:
style = "oblique"
Expand All @@ -160,15 +152,15 @@ def _afm_font_property(fontpath, font):
# Variants are: small-caps and normal (default)
# NOTE: Not sure how many fonts actually have these strings in their family
variant = "normal"
for value in ("capitals", "small-caps"):
for value in ("capitals", "small-caps", "smallcaps"):
if value in family.lower():
variant = "small-caps"
break

# Weights are: 100, 200, 300, 400 (normal: default), 500 (medium),
# 600 (semibold, demibold), 700 (bold), 800 (heavy), 900 (black)
# lighter and bolder are also allowed.
weight = weight_as_number(font.get_weight().lower())
weight = weight_as_number(font.Weight.lower())

# Stretch can be absolute and relative
# Absolute stretches are: ultra-condensed, extra-condensed, condensed,
Expand Down Expand Up @@ -196,7 +188,15 @@ def _afm_font_property(fontpath, font):

# All AFM fonts are apparently scalable.
size = "scalable"
return FontEntry(fontpath, family, style, variant, weight, stretch, size)
return FontEntry(
fname=fpath,
family=family,
style=style,
variant=variant,
weight=weight,
stretch=stretch,
size=size,
)


def _ttf_font_property(fpath, font, face_index=0):
Expand Down
37 changes: 37 additions & 0 deletions kiva/fonttools/tests/data/TestAFM.afm
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
StartFontMetrics 2.0
Comment UniqueID 2123703
Comment Panose 2 0 6 3 3 0 0 2 0 4
FontName TestFont-Regular
FullName TestFont-Regular
FamilyName TestFont
Weight Regular
ItalicAngle 0.00
IsFixedPitch false
FontBBox -94 -317 1316 1009
UnderlinePosition -296
UnderlineThickness 111
Version 001.000
Notice [c] Copyright 2017. All Rights Reserved.
EncodingScheme FontSpecific
CapHeight 700
XHeight 500
Ascender 750
Descender -250
StdHW 181
StdVW 194
StartCharMetrics 4
C 32 ; WX 200 ; N space ; B 0 0 0 0 ;
C 65 ; WX 668 ; N A ; B 8 -25 660 666 ;
C 66 ; WX 543 ; N B ; B 36 0 522 666 ;
C 67 ; WX 582 ; N C ; B 24 -21 564 687 ;
EndCharMetrics
StartKernData
StartKernPairs 5
KPX T c 30
KPX T comma -100
KPX T period -100
KPX V A -60
KPX V d 30
EndKernPairs
EndKernData
EndFontMetrics
55 changes: 36 additions & 19 deletions kiva/fonttools/tests/test_scan_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import unittest
from unittest import mock

from fontTools.afmLib import AFM
from fontTools.ttLib import TTFont
from pkg_resources import resource_filename

Expand Down Expand Up @@ -76,31 +77,47 @@ def test_afm_font_failure(self):
entries = _build_afm_entries("nonexistant.path")
self.assertListEqual([], entries)

# XXX: Once AFM code has been converted to expect bytestrings:
# Add a test which passes an existing file (non-afm) to
# _build_afm_entries.
# Add a test which passes an existing file (non-afm)
ttf_fontpath = os.path.join(data_dir, "TestTTF.ttf")
with self.assertLogs("kiva"):
entries = _build_afm_entries(ttf_fontpath)
self.assertListEqual([], entries)

def test_property_branches(self):
fake_path = os.path.join(data_dir, "TestAFM.afm")
def test_afm_font_success(self):
afm_fontpath = os.path.join(data_dir, "TestAFM.afm")
entries = _build_afm_entries(afm_fontpath)
self.assertEqual(len(entries), 1)

class FakeAFM:
def __init__(self, name, family, angle, weight):
self.name = name
self.family = family
self.angle = angle
self.weight = weight
def test_afm_font_parse(self):
# Given
test_font = os.path.join(data_dir, "TestAFM.afm")
exp_family = "TestFont"
exp_style = "normal"
exp_variant = "normal"
exp_weight = 400
exp_stretch = "normal"
exp_size = "scalable"

def get_angle(self):
return self.angle
# When
entry = _afm_font_property(test_font, AFM(test_font))

def get_familyname(self):
return self.family
# Then
self.assertEqual(entry.family, exp_family)
self.assertEqual(entry.style, exp_style)
self.assertEqual(entry.variant, exp_variant)
self.assertEqual(entry.weight, exp_weight)
self.assertEqual(entry.stretch, exp_stretch)
self.assertEqual(entry.size, exp_size)

def get_fontname(self):
return self.name
def test_property_branches(self):
fake_path = os.path.join(data_dir, "FakeAFM.afm")

def get_weight(self):
return self.weight
class FakeAFM:
def __init__(self, name, family, angle, weight):
self.FullName = name
self.FamilyName = family
self.ItalicAngle = str(angle)
self.Weight = weight

# Given
fake_font = FakeAFM("TestyFont", "Testy", 0, "Bold")
Expand Down
10 changes: 8 additions & 2 deletions kiva/fonttools/tests/test_scan_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,24 @@ def test_directory_scanning(self):
expected = [
os.path.join(data_dir, fname)
for fname in os.listdir(data_dir)
if os.path.splitext(fname)[-1] in (".ttf", ".ttc")
]
fonts = scan_system_fonts(data_dir, fontext="ttf")
self.assertListEqual(sorted(expected), sorted(fonts))

# There are no AFM fonts in the test data
expected = [
os.path.join(data_dir, fname)
for fname in os.listdir(data_dir)
if os.path.splitext(fname)[-1] == ".afm"
]
fonts = scan_system_fonts(data_dir, fontext="afm")
self.assertListEqual([], fonts)
self.assertListEqual(sorted(expected), sorted(fonts))

def test_directories_scanning(self):
expected = sorted([
os.path.join(data_dir, fname)
for fname in os.listdir(data_dir)
if os.path.splitext(fname)[-1] in (".ttf", ".ttc")
])
# Pass a list of directories instead of a single path string
fonts = scan_system_fonts([data_dir], fontext="ttf")
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ def macos_extensions():
'demo/*/*/*/*/*'],
'enable.savage.trait_defs.ui.wx': ['data/*.svg'],
'kiva': ['tests/agg/doubleprom_soho_full.jpg',
'fonttools/tests/data/*.afm',
'fonttools/tests/data/*.ttc',
'fonttools/tests/data/*.ttf',
'fonttools/tests/data/*.txt'],
Expand Down