Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New, more modern KivaFont trait #929

Merged
merged 10 commits into from
Apr 28, 2022
2 changes: 1 addition & 1 deletion docs/source/enable/traits.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ the form of an HTML color name ("blue" or "#0000FF").

font_trait
----------
:class:`~.font_trait` is a synonym for :class:`kiva.trait_defs.api.KivaFont`.
:class:`~.font_trait` is a synonym for :class:`enable.trait_defs.kiva_font_trait.KivaFont`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nits:

  • Rewrap to avoid long lines? The rest of the file seems to be limited to width 80.
  • The statement seems misleading: font_trait is an instance of KivaFont, rather than a synonym for it. Worth rephrasing?

The trait maps a font-description string to a valid :class:`kiva.fonttools.Font`
instance which can be passed to :py:meth:`AbstractGraphicsContext.set_font`

Expand Down
6 changes: 3 additions & 3 deletions docs/source/kiva/drawing_details.rst
Original file line number Diff line number Diff line change
Expand Up @@ -280,17 +280,17 @@ The ``KivaFont`` trait and ``set_font``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you're already doing your drawing within an application using traits, you can
use the :class:`~kiva.trait_defs.kiva_font_trait.KivaFont` trait.
use the :class:`~enable.trait_defs.kiva_font_trait.KivaFont` trait.

:class:`~kiva.trait_defs.kiva_font_trait.KivaFont` traits are initialized with
:class:`~enable.trait_defs.kiva_font_trait.KivaFont` traits are initialized with
a string which describes the font: "Times Italic 18", "Courier Bold 10", etc.
The *value* of the trait is a :class:`~kiva.fonttools.font.Font` instance which
can be passed to the :py:meth:`~.AbstractGraphicsContext.set_font` method.

*Supported backends*: all backends

.. note::
The :class:`~kiva.trait_defs.kiva_font_trait.KivaFont` parser is very
The :class:`~enable.trait_defs.kiva_font_trait.KivaFont` parser is very
simplistic and special-cases some words. For example "roman" means a
generic serif-style font family, so for example a face name of "Times New
Roman" will not resolve as expected. In these cases, use a
Expand Down
2 changes: 1 addition & 1 deletion enable/drawing/drawing_canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# Thanks for using Enthought open source!
from enable.api import Container, Component, ColorTrait
from kiva.api import FILL, FILL_STROKE
from kiva.trait_defs.api import KivaFont
from enable.trait_defs.kiva_font_trait import KivaFont
from traits.api import Any, Bool, Delegate, Enum, Instance, Int, List, Str


Expand Down
2 changes: 1 addition & 1 deletion enable/enable_traits.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from numpy import array, ndarray

# Enthought library imports
from kiva.trait_defs.api import KivaFont
from enable.trait_defs.kiva_font_trait import KivaFont
from traits.api import (
BaseFloat, List, Map, PrefixList, PrefixMap, Range, TraitType, Union,
)
Expand Down
2 changes: 1 addition & 1 deletion enable/examples/demo/enable/container_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from enable.api import ColorTrait
from enable.examples._example_support import DemoFrame, demo_main
from enable.tools.api import DragTool
from kiva.trait_defs.api import KivaFont
from enable.trait_defs.api import KivaFont


class Region(PlotComponent, DragTool):
Expand Down
14 changes: 7 additions & 7 deletions enable/examples/demo/enable/editors/font_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
from traits.api import HasStrictTraits
from traitsui.api import View, Item

from enable.api import Container, TextField, font_trait
from enable.api import Container, TextField
mdickinson marked this conversation as resolved.
Show resolved Hide resolved
mdickinson marked this conversation as resolved.
Show resolved Hide resolved
from enable.trait_defs.api import KivaFont
from enable.trait_defs.ui.api import KivaFontEditor
from enable.examples._example_support import demo_main

from kiva.api import Font
from kiva.constants import ITALIC, SWISS, WEIGHT_BOLD
from kiva.trait_defs.api import KivaFont

size = (500, 200)

Expand All @@ -26,13 +26,13 @@
class Demo(HasStrictTraits):
""" An example which shows the KivaFontEditor's variations. """

font = font_trait(Font("Times", 24, SWISS, WEIGHT_BOLD, ITALIC))
font = KivaFont(Font("Times", 24, SWISS, WEIGHT_BOLD, ITALIC))

view = View(
Item('font', editor=KivaFontEditor(), style='simple', label="Simple"),
Item('font', editor=KivaFontEditor(), style='custom', label="Custom"),
Item('font', editor=KivaFontEditor(), style='text', label="Text"),
Item('font', editor=KivaFontEditor(), style='readonly', label="Readonly"),
jwiggins marked this conversation as resolved.
Show resolved Hide resolved
Item('font', style='simple', label="Simple"),
Item('font', style='custom', label="Custom"),
Item('font', style='text', label="Text"),
Item('font', style='readonly', label="Readonly"),
Item('font', editor=KivaFontEditor(sample_text=sample_text), style='readonly', label="sample text"),
resizable=True,
width=size[0],
Expand Down
2 changes: 1 addition & 1 deletion enable/gadgets/vu_meter.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from traits.api import Float, Property, List, Str, Range
from enable.api import Component
from kiva.trait_defs.api import KivaFont
from enable.trait_defs.kiva_font_trait import KivaFont
from kiva import affine


Expand Down
2 changes: 1 addition & 1 deletion enable/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

# Enthought library imports
from kiva.api import FILL, STROKE
from kiva.trait_defs.api import KivaFont
from enable.trait_defs.kiva_font_trait import KivaFont
mdickinson marked this conversation as resolved.
Show resolved Hide resolved
from traits.api import Bool, Enum, Float, HasTraits, Int, List, Str, observe

# Local, relative imports
Expand Down
92 changes: 92 additions & 0 deletions enable/tests/trait_defs/test_kiva_font_trait.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# (C) Copyright 2008-2022 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!

import unittest

from traits.api import HasTraits, TraitError

from kiva.fonttools.font import Font, FAMILIES
from kiva import constants
from enable.trait_defs.kiva_font_trait import (
KivaFont, font_families, font_styles, font_weights
mdickinson marked this conversation as resolved.
Show resolved Hide resolved
mdickinson marked this conversation as resolved.
Show resolved Hide resolved
)


class FontExample(HasTraits):

font = KivaFont()


class TestKivaFont(unittest.TestCase):

def test_validate_str(self):
expected_outcomes = {}
expected_outcomes[""] = Font(size=10, family=constants.DEFAULT)

for weight, kiva_weight in font_weights.items():
expected_outcomes[weight] = Font(weight=kiva_weight, size=10, family=constants.DEFAULT)
jwiggins marked this conversation as resolved.
Show resolved Hide resolved

for style, kiva_style in font_styles.items():
expected_outcomes[style] = Font(style=kiva_style, size=10, family=constants.DEFAULT)

expected_outcomes["underline"] = Font(underline=True, size=10, family=constants.DEFAULT)

expected_outcomes["18"] = Font(size=18, family=constants.DEFAULT)
expected_outcomes["18 pt"] = Font(size=18, family=constants.DEFAULT)
expected_outcomes["18 point"] = Font(size=18, family=constants.DEFAULT)

for family, kiva_family in FAMILIES.items():
expected_outcomes[family] = Font(family=kiva_family, size=10)

expected_outcomes["Courier"] = Font("Courier", size=10, family=constants.DEFAULT)
expected_outcomes["Comic Sans"] = Font("Comic Sans", size=10, family=constants.DEFAULT)
expected_outcomes["18 pt Bold Italic Underline Comic Sans script"] = Font(
"Comic Sans", 18, constants.SCRIPT, weight=constants.WEIGHT_BOLD,
style=constants.ITALIC, underline=True,
)

for name, expected in expected_outcomes.items():
with self.subTest(name=name):
example = FontExample(font=name)
result = example.font

# test we get expected font
self.assertIsInstance(result, Font)
self.assertEqual(result, expected)

def test_validate_font(self):
font = Font("Comic Sans", 18)
example = FontExample(font=font)

result = example.font

# test we get expected font
self.assertIsInstance(result, Font)
self.assertIs(result, font)

def test_validate_pyface_font(self):
font = Font("Comic Sans", 18)
example = FontExample(font=font)

result = example.font

# test we get expected font
self.assertIsInstance(result, Font)
self.assertIs(result, font)
jwiggins marked this conversation as resolved.
Show resolved Hide resolved

def test_font_trait_default(self):
example = FontExample()

self.assertIsInstance(example.font, Font)
self.assertEqual(example.font, Font(size=12, family=constants.MODERN))

def test_font_trait_none(self):
with self.assertRaises(TraitError):
FontExample(font=None)
2 changes: 1 addition & 1 deletion enable/text_field_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

# Enthought library imports
from traits.api import HasTraits, Int, Bool
from kiva.trait_defs.api import KivaFont
from enable.trait_defs.kiva_font_trait import KivaFont
from enable.colors import ColorTrait


Expand Down
2 changes: 1 addition & 1 deletion enable/text_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from traits.api import (
Any, Array, Bool, Int, List, Property, Tuple, observe,
)
from kiva.trait_defs.api import KivaFont
from enable.trait_defs.kiva_font_trait import KivaFont

# Relative imports
from .component import Component
Expand Down
2 changes: 1 addition & 1 deletion enable/tools/toolbars/toolbar_buttons.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# Enthought library imports
from enable.api import ColorTrait, Component
from enable.font_metrics_provider import font_metrics_provider
from kiva.trait_defs.api import KivaFont
from enable.trait_defs.kiva_font_trait import KivaFont
from traits.api import Bool, Enum, Int, Str, Tuple


Expand Down
1 change: 1 addition & 0 deletions enable/trait_defs/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
"""

from .rgba_color_trait import RGBAColor
from .kiva_font_trait import KivaFont
mdickinson marked this conversation as resolved.
Show resolved Hide resolved
131 changes: 131 additions & 0 deletions enable/trait_defs/kiva_font_trait.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
""" Trait definition for a wxPython-based Kiva font.
"""

from pyface.font import Font as PyfaceFont
from traits.api import DefaultValue, TraitError, TraitType, NoDefaultSpecified

import kiva.constants as kc
from kiva.fonttools.font import Font, simple_parser
corranwebster marked this conversation as resolved.
Show resolved Hide resolved


font_attrs = [
'face_name', 'size', 'family', 'weight', 'style', 'underline', 'encoding',
]

pyface_family_to_kiva_family = {
mdickinson marked this conversation as resolved.
Show resolved Hide resolved
'default': kc.DEFAULT,
'fantasy': kc.DECORATIVE,
'decorative': kc.DECORATIVE,
'serif': kc.ROMAN,
'roman': kc.ROMAN,
'cursive': kc.SCRIPT,
'script': kc.SCRIPT,
'sans-serif': kc.SWISS,
'swiss': kc.SWISS,
'monospace': kc.MODERN,
'modern': kc.MODERN,
'typewriter': kc.TELETYPE,
'teletype': kc.TELETYPE,
}


def pyface_font_to_font(font):
"""Convert a Pyface font to an equivalent Kiva Font.

This ignores stretch and some options like small caps and strikethrough
as the Kiva font object can't represent these at the moment.

Parameters
----------
font : Pyface Font instance
The font to convert.

Returns
-------
font : Kiva Font instance
The resulting Kiva Font object.
"""
face_name = font.family[0]
for face in font.family:
if face in pyface_family_to_kiva_family:
family = pyface_family_to_kiva_family[face]
break
else:
family = kc.DEFAULT
size = int(font.size)
weight = font.weight_
style = kc.NORMAL if font.style is 'normal' else kc.ITALIC
corranwebster marked this conversation as resolved.
Show resolved Hide resolved
underline = 'underline' in font.decorations
return Font(face_name, size, family, weight, style, underline)


class KivaFont(TraitType):
""" A Trait which casts strings to a Kiva Font value.
"""

#: The default value should be a tuple (factory, args, kwargs)
default_value_type = DefaultValue.callable_and_args

#: The parser to use when converting text to keyword args. This should
#: accept a string and return a dictionary of Font class trait values (ie.
#: "family", "size", "weight", etc.).
mdickinson marked this conversation as resolved.
Show resolved Hide resolved
parser = None

def __init__(self, default_value=None, *, parser=simple_parser, **metadata):
self.parser = parser
default_value = self._get_default_value(default_value)
super().__init__(default_value, **metadata)

def validate(self, object, name, value):
if isinstance(value, Font):
return value
if isinstance(value, PyfaceFont):
return pyface_font_to_font(value)
if isinstance(value, str):
try:
return Font(**self.parser(value))
except FontParseError:
self.error(object, name, value)
mdickinson marked this conversation as resolved.
Show resolved Hide resolved

self.error(object, name, value)

def info(self):
return (
"a Kiva Font, a Pyface Font, or a string describing a font"
)

def get_editor(self, trait):
from enable.trait_defs.ui.kiva_font_editor import KivaFontEditor
return KivaFontEditor()

def clone(self, default_value=NoDefaultSpecified, **metadata):
mdickinson marked this conversation as resolved.
Show resolved Hide resolved
new = super().clone(NoDefaultSpecified, **metadata)
if default_value is not NoDefaultSpecified:
new.default_value = self._get_default_value(default_value)
mdickinson marked this conversation as resolved.
Show resolved Hide resolved
return new

def _get_default_value(self, default_value):
"""Construct a default value suitable for callable_and_args."""
if default_value is not None:
try:
font = self.validate(None, None, default_value)
except TraitError:
raise ValueError(
"expected " + self.info()
+ f", but got {default_value!r}"
corranwebster marked this conversation as resolved.
Show resolved Hide resolved
)
klass = font.__class__
kwargs = {attr: getattr(font, attr) for attr in font_attrs}
else:
klass = Font
kwargs = {}
return (klass, (), kwargs)
16 changes: 15 additions & 1 deletion enable/trait_defs/ui/kiva_font_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@
from .editor_with_component import EditorWithLabelComponent


WEIGHTS = {
mdickinson marked this conversation as resolved.
Show resolved Hide resolved
kc.WEIGHT_THIN: ' Thin',
kc.WEIGHT_EXTRALIGHT: ' Extra-light',
kc.WEIGHT_LIGHT: ' Light',
kc.WEIGHT_NORMAL: '',
kc.WEIGHT_MEDIUM: ' Medium',
kc.WEIGHT_SEMIBOLD: ' Demi-bold',
corranwebster marked this conversation as resolved.
Show resolved Hide resolved
kc.WEIGHT_BOLD: ' Bold',
kc.WEIGHT_EXTRABOLD: ' Extra-bold',
kc.WEIGHT_HEAVY: ' Heavy',
kc.WEIGHT_EXTRAHEAVY: ' Extra-heavy',
}


def face_name(font):
""" Returns a Font's typeface name.
"""
Expand All @@ -37,7 +51,7 @@ def str_font(font):
""" Returns the text representation of the specified font trait value
"""

weight = " Bold" if font.is_bold() else ""
weight = WEIGHTS[font.weight]
style = " Italic" if font.style in kc.italic_styles else ""
underline = " Underline" if font.underline else ""

Expand Down
Loading