Skip to content

Commit

Permalink
Add face_index support to celiagg and kiva.agg backends.
Browse files Browse the repository at this point in the history
  • Loading branch information
jwiggins committed Feb 22, 2021
1 parent 56ddd33 commit 48ac3da
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 45 deletions.
18 changes: 10 additions & 8 deletions kiva/agg/src/font_type.i
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ namespace kiva
std::string name;
int family;
int style;
int encoding;
int encoding;
int face_index;
std::string filename;

// constructor
Expand All @@ -35,6 +36,7 @@ namespace kiva
int _family=0,
int _style=0,
int _encoding=0,
int _face_index=0,
bool validate=true);

int change_filename(std::string _filename);
Expand All @@ -47,12 +49,11 @@ namespace kiva
char *__repr__()
{
static char tmp[1024];
// Write out elements of trans_affine in a,b,c,d,tx,ty order
// !! We should work to make output formatting conform to
// !! whatever it Numeric does (which needs to be cleaned up also).
sprintf(tmp,"Font(%s,%d,%d,%d,%d)", self->name.c_str(), self->family,
self->size, self->style,
self->encoding);
sprintf(tmp,"Font(%s,%d,%d,%d,%d,%d)",
self->name.c_str(), self->family, self->size, self->style,
self->encoding, self->face_index);
return tmp;
}
int __eq__(kiva::font_type& other)
Expand All @@ -61,14 +62,15 @@ namespace kiva
self->family == other.family &&
self->size == other.size &&
self->style == other.style &&
self->encoding == other.encoding);
self->encoding == other.encoding &&
self->face_index == other.face_index);
}
}

%pythoncode
%{
def unicode_safe_init(self, _name="Arial", _size=12, _family=0, _style=0,
_encoding=0, validate=True):
_encoding=0, _face_index=0, validate=True):
### HACK: C++ stuff expects a string (not unicode) for the face_name, so fix
### if needed.
### Only for python < 3
Expand All @@ -79,7 +81,7 @@ def unicode_safe_init(self, _name="Arial", _size=12, _family=0, _style=0,
if isinstance(_name, bytes):
_name = _name.decode()
obj = _agg.new_AggFontType(_name, _size, _family, _style,
_encoding, validate)
_encoding, _face_index, validate)
_swig_setattr(self, AggFontType, "this", obj)
_swig_setattr(self, AggFontType, "thisown", 1)

Expand Down
5 changes: 3 additions & 2 deletions kiva/agg/src/graphics_context.i
Original file line number Diff line number Diff line change
Expand Up @@ -560,11 +560,12 @@ namespace kiva {
and (font.encoding == cur_font.encoding):
return
else:
newfilename = font.findfont()
newfilename, face_index = font.findfont()
agg_font = AggFontType(font.face_name, font.size, font.family, font.style,
font.encoding, False)
font.encoding, face_index, False)
agg_font.filename = newfilename
else:
# XXX: What are we expecting here?
agg_font = AggFontType(font.face_name, font.size, font.family, font.style, font.encoding)
try:
retval = _agg.GraphicsContextArray_set_font(self, agg_font)
Expand Down
11 changes: 6 additions & 5 deletions kiva/agg/src/kiva_font_type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ const char* freetype_suffixes[] = { ".ttf", ".pfa", ".pfb" };
// Therefore this simple function is left in.

kiva::font_type::font_type(std::string _name, int _size, int _family,
int _style, int _encoding, bool validate):
int _style, int _encoding, int _face_index, bool validate):
name(_name), size(_size), family(_family), style(_style),
encoding(_encoding), _is_loaded(false)
encoding(_encoding), face_index(_face_index),
_is_loaded(false)
{
std::string full_file_name;
if (validate)
Expand Down Expand Up @@ -83,10 +84,9 @@ kiva::font_type::font_type(std::string _name, int _size, int _family,

kiva::font_type::font_type(const kiva::font_type &font) :
name(font.name), filename(font.filename), size(font.size),
_is_loaded(font.is_loaded())
family(font.family), style(font.style), encoding(font.encoding),
face_index(font.face_index), _is_loaded(font.is_loaded())
{
this->family = font.family;
this->style = font.style;
}

kiva::font_type &kiva::font_type::operator=(const kiva::font_type& font)
Expand All @@ -95,6 +95,7 @@ kiva::font_type &kiva::font_type::operator=(const kiva::font_type& font)
this->family = font.family;
this->style = font.style;
this->encoding = font.encoding;
this->face_index = font.face_index;
this->name = font.name;
this->filename = font.filename;
this->_is_loaded = font.is_loaded();
Expand Down
4 changes: 3 additions & 1 deletion kiva/agg/src/kiva_font_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ namespace kiva
int family;
int style;
int encoding;
int face_index;

// Constructors

Expand All @@ -39,6 +40,7 @@ namespace kiva
int _family=0,
int _style=0,
int _encoding=0,
int _face_index=0,
bool validate=true);

font_type(const font_type &font);
Expand All @@ -57,7 +59,7 @@ namespace kiva
{
return (a.size == b.size) && (a.name == b.name) &&
(a.style == b.style) && (a.encoding == b.encoding) &&
(a.family == b.family);
(a.family == b.family) && (a.face_index == b.face_index);
}


Expand Down
4 changes: 2 additions & 2 deletions kiva/agg/src/kiva_graphics_context_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -668,12 +668,12 @@ void graphics_context_base::_grab_font_manager()
#ifdef KIVA_USE_FREETYPE
if (font->filename != "")
{
font_engine->load_font(font->filename.c_str(), 0,
font_engine->load_font(font->filename.c_str(), font->face_index,
agg24::glyph_ren_agg_gray8);
}
else
{
font_engine->load_font(font->name.c_str(), 0,
font_engine->load_font(font->name.c_str(), font->face_index,
agg24::glyph_ren_agg_gray8);
}
#endif
Expand Down
17 changes: 11 additions & 6 deletions kiva/celiagg.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
import celiagg as agg
import numpy as np

from .abstract_graphics_context import AbstractGraphicsContext
from .fonttools import Font
from kiva.abstract_graphics_context import AbstractGraphicsContext
import kiva.constants as constants
from kiva.fonttools import Font

# These are the symbols that a backend has to define.
__all__ = ["CompiledPath", "Font", "font_metrics_provider", "GraphicsContext"]
Expand Down Expand Up @@ -608,15 +608,18 @@ def normalize_image(img):
# Drawing Text
# ----------------------------------------------------------------

def select_font(self, name, size, textEncoding):
def select_font(self, face_name, size=12, style='regular', encoding=None):
""" Set the font for the current graphics context.
"""
self.font = agg.Font(name, size, agg.FontCacheType.RasterFontCache)
self.set_font(Font(face_name, size=size, style=style))

def set_font(self, font):
""" Set the font for the current graphics context.
"""
self.select_font(font.findfont(), font.size, None)
filename, face_index = font.findfont()
self.font = agg.Font(
filename, font.size, agg.FontCacheType.RasterFontCache, face_index
)

def set_font_size(self, size):
""" Set the font size for the current graphics context.
Expand All @@ -625,7 +628,9 @@ def set_font_size(self, size):
return

font = self.font
self.select_font(font.filepath, size, font.cache_type)
self.font = agg.Font(
font.filepath, size, font.cache_type, font.face_index
)

def set_character_spacing(self, spacing):
msg = "set_character_spacing not implemented on celiagg yet."
Expand Down
9 changes: 5 additions & 4 deletions kiva/fonttools/font.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,12 @@ def __init__(self, face_name="", size=12, family=SWISS, weight=NORMAL,
self.encoding = encoding

def findfont(self):
""" Returns the file name containing the font that most closely matches
our font properties.
""" Returns the file name and face index of the font that most closely
matches our font properties.
"""
fp = self._make_font_props()
return str(default_font_manager().findfont(fp))
filename, face_index = default_font_manager().findfont(fp)
return str(filename), face_index

def findfontname(self):
""" Returns the name of the font that most closely matches our font
Expand Down Expand Up @@ -181,7 +182,7 @@ def __ne__(self, other):
def __repr__(self):
fmt = (
"Font(size=%d,family=%d,weight=%d, style=%d, face_name='%s', "
+ "encoding=%d)"
"encoding=%d)"
)
return fmt % (
self.size,
Expand Down
38 changes: 23 additions & 15 deletions kiva/fonttools/font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,30 +545,33 @@ class FontEntry(object):
"""

def __init__(self, fname="", name="", style="normal", variant="normal",
weight="normal", stretch="normal", size="medium"):
weight="normal", stretch="normal", size="medium",
face_index=0):
self.fname = fname
self.name = name
self.style = style
self.variant = variant
self.weight = weight
self.stretch = stretch
self.face_index = face_index
try:
self.size = str(float(size))
except ValueError:
self.size = size

def __repr__(self):
return "<Font '%s' (%s) %s %s %s %s>" % (
return "<Font '%s' (%s[%d]) %s %s %s %s>" % (
self.name,
os.path.basename(self.fname),
self.face_index,
self.style,
self.variant,
self.weight,
self.stretch,
)


def ttfFontProperty(fpath, font):
def ttfFontProperty(fpath, font, index=0):
"""
A function for populating the :class:`FontKey` by extracting
information from the TrueType font file.
Expand Down Expand Up @@ -639,7 +642,7 @@ def ttfFontProperty(fpath, font):
# !!!! Incomplete
size = "scalable"

return FontEntry(fpath, name, style, variant, weight, stretch, size)
return FontEntry(fpath, name, style, variant, weight, stretch, size, index)


def afmFontProperty(fontpath, font):
Expand Down Expand Up @@ -758,8 +761,8 @@ def createFontList(fontfiles, fontext="ttf"):
collection = TTCollection(f)
try:
props = []
for font in collection.fonts:
props.append(ttfFontProperty(fpath, font))
for idx, font in enumerate(collection.fonts):
props.append(ttfFontProperty(fpath, font, idx))
fontlist.extend(props)
continue
except Exception:
Expand Down Expand Up @@ -878,12 +881,13 @@ def get_name(self):
Return the name of the font that best matches the font
properties.
"""
filename = str(default_font_manager().findfont(self))
filename, face_index = default_font_manager().findfont(self)
filename = str(filename)
if filename.endswith(".afm"):
return afm.AFM(open(filename)).get_familyname()

font = default_font_manager().findfont(self)
prop_dict = getPropDict(TTFont(str(font)))
font, face_index = default_font_manager().findfont(self)
prop_dict = getPropDict(TTFont(str(font), fontNumber=face_index))
return prop_dict["name"]

def get_style(self):
Expand Down Expand Up @@ -1091,7 +1095,7 @@ class FontManager:
# Increment this version number whenever the font cache data
# format or behavior has changed and requires a existing font
# cache files to be rebuilt.
__version__ = 7
__version__ = 8

def __init__(self, size=None, weight="normal"):
self._version = self.__version__
Expand Down Expand Up @@ -1314,7 +1318,9 @@ def findfont(self, prop, fontext="ttf", directory=None,
fname = prop.get_file()
if fname is not None:
logger.debug("findfont returning %s", fname)
return fname
# It's not at all clear where a `FontProperties` instance with
# `fname` already set would come from. Assume face_index == 0.
return (fname, 0)

if fontext == "afm":
font_cache = self.afm_lookup_cache
Expand Down Expand Up @@ -1369,18 +1375,20 @@ def findfont(self, prop, fontext="ttf", directory=None,
% (prop, self.defaultFont[fontext]),
UserWarning,
)
result = self.defaultFont[fontext]
# Assume this is never a .ttc font, so 0 is ok for face index.
result = (self.defaultFont[fontext], 0)
else:
logger.debug(
"findfont: Matching %s to %s (%s) with score of %f",
"findfont: Matching %s to %s (%s[%d]) with score of %f",
prop,
best_font.name,
best_font.fname,
best_font.face_index,
best_score,
)
result = best_font.fname
result = (best_font.fname, best_font.face_index)

if not os.path.isfile(result):
if not os.path.isfile(result[0]):
if rebuild_if_missing:
logger.debug(
"findfont: Found a missing font file. Rebuilding cache."
Expand Down
6 changes: 4 additions & 2 deletions kiva/fonttools/tests/test_font.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,19 @@ def test_find_font_empty_name(self):
# the path from which the font manager loads font files, then this test
# can be less fragile.
font = Font(face_name="")
font_file_path = font.findfont()
font_file_path, face_index = font.findfont()
self.assertTrue(os.path.exists(font_file_path))
self.assertEqual(face_index, 0)

def test_find_font_some_face_name(self):
font = Font(face_name="ProbablyNotFound")

# There will be warnings as there will be no match for the requested
# face name.
with self.assertWarns(UserWarning):
font_file_path = font.findfont()
font_file_path, face_index = font.findfont()
self.assertTrue(os.path.exists(font_file_path))
self.assertEqual(face_index, 0)

def test_find_font_name(self):
font = Font(face_name="ProbablyNotFound")
Expand Down

0 comments on commit 48ac3da

Please sign in to comment.