classes.c12_glyph

  1from functools import cached_property
  2from loguru import logger
  3import drawBot
  4import colorama
  5from icecream import ic
  6
  7from lib import fonts, helpers, glyphs
  8
  9
 10class KGlyph:
 11    def __init__(self, source: str) -> None:
 12        """Initialize a KGlyph instance."""
 13        self.source: str = source
 14        """The glyph source identifier. Can be character, name, or unicode."""
 15
 16    @cached_property
 17    def name(self) -> str:
 18        """
 19        Returns:
 20            The glyph name, see `lib.glyphs.toName`.
 21
 22        Example:
 23            ccaron
 24        """
 25        return glyphs.toName(self.source)
 26
 27    @cached_property
 28    def char(self) -> str:
 29        """
 30        Returns:
 31            The corresponding character, see `lib.glyphs.toChar`.
 32
 33        Example:
 34            č
 35        """
 36        return glyphs.toChar(self.source)
 37
 38    @cached_property
 39    def charBase(self) -> str:
 40        """
 41        Returns:
 42            The base character of the glyph, see `lib.glyphs.toCharBase`.
 43
 44        Example:
 45            Á.ss01 => Á
 46            S.ss01.cv01 => S
 47        """
 48        return glyphs.toCharBase(self.source)
 49
 50    @cached_property
 51    def unicode(self) -> str:
 52        """
 53        Returns:
 54            The corresponding unicode string, see `lib.glyphs.toUni`.
 55
 56        Example:
 57            uni010D
 58        """
 59        return glyphs.toUni(self.source, "full")
 60
 61    @cached_property
 62    def category(self) -> str:
 63        """Returns the glyph category, see `lib.glyphs.getCategory`."""
 64        return glyphs.getCategory(self.source)
 65
 66    @cached_property
 67    def canLigate(self) -> bool:
 68        """Returns True if source is a multi-char sequence that can form a ligature.
 69
 70        See `lib.glyphs.canLigate`.
 71
 72        Examples:
 73            KGlyph("fi").canLigate  => True
 74            KGlyph("f_i").canLigate => False
 75        """
 76        return glyphs.canLigate(self.source)
 77
 78    @cached_property
 79    def isLigature(self) -> bool:
 80        """Returns True if source represents an actual ligature glyph.
 81
 82        See `lib.glyphs.isLigature`.
 83
 84        Examples:
 85            KGlyph("fi").isLigature    => True
 86            KGlyph("f_i").isLigature   => True
 87            KGlyph("tt").isLigature    => False
 88        """
 89        return glyphs.isLigature(self.source)
 90
 91    def get(self, key: str, defaultValue=None):
 92        """
 93        Retrieve an attribute by key, emulating `dict.get()`.
 94
 95        Args:
 96            key: The attribute name to retrieve.
 97            defaultValue: Value to return if the attribute is not found.
 98
 99        Returns:
100            The attribute value or defaultValue if not found.
101        """
102        return getattr(self, key, defaultValue)
103
104    def getSupported(self, fontPath: str, warn: bool = True) -> str | None:
105        """
106        Find the supported glyph name in the given font.
107
108        Args:
109            fontPath: The font path to check for glyph support.
110
111        Returns:
112            The name of the supported glyph in the font, or None if not found.
113        """
114        glyphsInFont = glyphs.listAvailableGlyphs(fontPath)
115
116        possibleMatches = set(
117            helpers.removeNone(
118                [
119                    *helpers.coerceList(self.name),
120                    self.char,
121                    self.unicode,
122                    glyphs.toSnakeCase(self.source),
123                    glyphs.toPartBase(self.source),
124                ]
125            )
126        )
127        # ! Edge case: For certain ligatures like "t_t", some fonts may use a ".liga" suffix for the supported glyph name.
128        if self.canLigate:
129            possibleMatches.update(
130                [
131                    f"{variant}.liga"
132                    for variant in [self.name, glyphs.toSnakeCase(self.source)]
133                ]
134            )
135
136        for possibleMatch in possibleMatches:
137            if possibleMatch in glyphsInFont:
138                return possibleMatch
139
140        if warn:
141            logger.warning(
142                "{} doesn’t support glyph {}",
143                fonts.getFontName(fontPath),
144                f"{colorama.Back.LIGHTYELLOW_EX}{self.source}{colorama.Style.RESET_ALL}",
145            )
146
147    def __str__(self) -> str:
148        """Returns a human-friendly string representation of the KGlyph, including name, character, unicode, and category."""
149        output = f"{colorama.Back.BLACK}KGlyph{colorama.Style.RESET_ALL}"
150
151        if self.name:
152            # Can be multiple names
153            isMultipleNames = isinstance(self.name, list)
154            names = helpers.coerceList(self.name)
155
156            output += " Name" + ("s" if isMultipleNames else " ")
157            for ni, name in enumerate(names):
158                isNotFirst = ni != 0
159                if isNotFirst:
160                    output += "\n" + " " * 12
161                output += f" {colorama.Back.LIGHTYELLOW_EX}{name.ljust(30)}{colorama.Style.RESET_ALL}"
162
163        if self.char:
164            output += f" Char {colorama.Back.BLUE}{self.char}{colorama.Style.RESET_ALL}"
165            # Justify text
166            charAllotedSpaces = 6
167            if self.name == "zerowidthspace":
168                charAllotedSpaces += 1
169            output += " " * (charAllotedSpaces - len(self.char))
170
171        unicodeToPrint = self.unicode or ""
172        output += (
173            f" Unicode {colorama.Back.CYAN}{unicodeToPrint}{colorama.Style.RESET_ALL}"
174        )
175        output += " " * (9 - len(unicodeToPrint))
176
177        output += f" Category {colorama.Back.LIGHTRED_EX}{self.category}{colorama.Style.RESET_ALL}"
178
179        return output
class KGlyph:
 11class KGlyph:
 12    def __init__(self, source: str) -> None:
 13        """Initialize a KGlyph instance."""
 14        self.source: str = source
 15        """The glyph source identifier. Can be character, name, or unicode."""
 16
 17    @cached_property
 18    def name(self) -> str:
 19        """
 20        Returns:
 21            The glyph name, see `lib.glyphs.toName`.
 22
 23        Example:
 24            ccaron
 25        """
 26        return glyphs.toName(self.source)
 27
 28    @cached_property
 29    def char(self) -> str:
 30        """
 31        Returns:
 32            The corresponding character, see `lib.glyphs.toChar`.
 33
 34        Example:
 35            č
 36        """
 37        return glyphs.toChar(self.source)
 38
 39    @cached_property
 40    def charBase(self) -> str:
 41        """
 42        Returns:
 43            The base character of the glyph, see `lib.glyphs.toCharBase`.
 44
 45        Example:
 46            Á.ss01 => Á
 47            S.ss01.cv01 => S
 48        """
 49        return glyphs.toCharBase(self.source)
 50
 51    @cached_property
 52    def unicode(self) -> str:
 53        """
 54        Returns:
 55            The corresponding unicode string, see `lib.glyphs.toUni`.
 56
 57        Example:
 58            uni010D
 59        """
 60        return glyphs.toUni(self.source, "full")
 61
 62    @cached_property
 63    def category(self) -> str:
 64        """Returns the glyph category, see `lib.glyphs.getCategory`."""
 65        return glyphs.getCategory(self.source)
 66
 67    @cached_property
 68    def canLigate(self) -> bool:
 69        """Returns True if source is a multi-char sequence that can form a ligature.
 70
 71        See `lib.glyphs.canLigate`.
 72
 73        Examples:
 74            KGlyph("fi").canLigate  => True
 75            KGlyph("f_i").canLigate => False
 76        """
 77        return glyphs.canLigate(self.source)
 78
 79    @cached_property
 80    def isLigature(self) -> bool:
 81        """Returns True if source represents an actual ligature glyph.
 82
 83        See `lib.glyphs.isLigature`.
 84
 85        Examples:
 86            KGlyph("fi").isLigature    => True
 87            KGlyph("f_i").isLigature   => True
 88            KGlyph("tt").isLigature    => False
 89        """
 90        return glyphs.isLigature(self.source)
 91
 92    def get(self, key: str, defaultValue=None):
 93        """
 94        Retrieve an attribute by key, emulating `dict.get()`.
 95
 96        Args:
 97            key: The attribute name to retrieve.
 98            defaultValue: Value to return if the attribute is not found.
 99
100        Returns:
101            The attribute value or defaultValue if not found.
102        """
103        return getattr(self, key, defaultValue)
104
105    def getSupported(self, fontPath: str, warn: bool = True) -> str | None:
106        """
107        Find the supported glyph name in the given font.
108
109        Args:
110            fontPath: The font path to check for glyph support.
111
112        Returns:
113            The name of the supported glyph in the font, or None if not found.
114        """
115        glyphsInFont = glyphs.listAvailableGlyphs(fontPath)
116
117        possibleMatches = set(
118            helpers.removeNone(
119                [
120                    *helpers.coerceList(self.name),
121                    self.char,
122                    self.unicode,
123                    glyphs.toSnakeCase(self.source),
124                    glyphs.toPartBase(self.source),
125                ]
126            )
127        )
128        # ! Edge case: For certain ligatures like "t_t", some fonts may use a ".liga" suffix for the supported glyph name.
129        if self.canLigate:
130            possibleMatches.update(
131                [
132                    f"{variant}.liga"
133                    for variant in [self.name, glyphs.toSnakeCase(self.source)]
134                ]
135            )
136
137        for possibleMatch in possibleMatches:
138            if possibleMatch in glyphsInFont:
139                return possibleMatch
140
141        if warn:
142            logger.warning(
143                "{} doesn’t support glyph {}",
144                fonts.getFontName(fontPath),
145                f"{colorama.Back.LIGHTYELLOW_EX}{self.source}{colorama.Style.RESET_ALL}",
146            )
147
148    def __str__(self) -> str:
149        """Returns a human-friendly string representation of the KGlyph, including name, character, unicode, and category."""
150        output = f"{colorama.Back.BLACK}KGlyph{colorama.Style.RESET_ALL}"
151
152        if self.name:
153            # Can be multiple names
154            isMultipleNames = isinstance(self.name, list)
155            names = helpers.coerceList(self.name)
156
157            output += " Name" + ("s" if isMultipleNames else " ")
158            for ni, name in enumerate(names):
159                isNotFirst = ni != 0
160                if isNotFirst:
161                    output += "\n" + " " * 12
162                output += f" {colorama.Back.LIGHTYELLOW_EX}{name.ljust(30)}{colorama.Style.RESET_ALL}"
163
164        if self.char:
165            output += f" Char {colorama.Back.BLUE}{self.char}{colorama.Style.RESET_ALL}"
166            # Justify text
167            charAllotedSpaces = 6
168            if self.name == "zerowidthspace":
169                charAllotedSpaces += 1
170            output += " " * (charAllotedSpaces - len(self.char))
171
172        unicodeToPrint = self.unicode or ""
173        output += (
174            f" Unicode {colorama.Back.CYAN}{unicodeToPrint}{colorama.Style.RESET_ALL}"
175        )
176        output += " " * (9 - len(unicodeToPrint))
177
178        output += f" Category {colorama.Back.LIGHTRED_EX}{self.category}{colorama.Style.RESET_ALL}"
179
180        return output
KGlyph(source: str)
12    def __init__(self, source: str) -> None:
13        """Initialize a KGlyph instance."""
14        self.source: str = source
15        """The glyph source identifier. Can be character, name, or unicode."""

Initialize a KGlyph instance.

source: str

The glyph source identifier. Can be character, name, or unicode.

name: str
17    @cached_property
18    def name(self) -> str:
19        """
20        Returns:
21            The glyph name, see `lib.glyphs.toName`.
22
23        Example:
24            ccaron
25        """
26        return glyphs.toName(self.source)
Returns:

The glyph name, see lib.glyphs.toName.

Example:

ccaron

char: str
28    @cached_property
29    def char(self) -> str:
30        """
31        Returns:
32            The corresponding character, see `lib.glyphs.toChar`.
33
34        Example:
35            č
36        """
37        return glyphs.toChar(self.source)
Returns:

The corresponding character, see lib.glyphs.toChar.

Example:

č

charBase: str
39    @cached_property
40    def charBase(self) -> str:
41        """
42        Returns:
43            The base character of the glyph, see `lib.glyphs.toCharBase`.
44
45        Example:
46            Á.ss01 => Á
47            S.ss01.cv01 => S
48        """
49        return glyphs.toCharBase(self.source)
Returns:

The base character of the glyph, see lib.glyphs.toCharBase.

Example:

Á.ss01 => Á S.ss01.cv01 => S

unicode: str
51    @cached_property
52    def unicode(self) -> str:
53        """
54        Returns:
55            The corresponding unicode string, see `lib.glyphs.toUni`.
56
57        Example:
58            uni010D
59        """
60        return glyphs.toUni(self.source, "full")
Returns:

The corresponding unicode string, see lib.glyphs.toUni.

Example:

uni010D

category: str
62    @cached_property
63    def category(self) -> str:
64        """Returns the glyph category, see `lib.glyphs.getCategory`."""
65        return glyphs.getCategory(self.source)

Returns the glyph category, see lib.glyphs.getCategory.

canLigate: bool
67    @cached_property
68    def canLigate(self) -> bool:
69        """Returns True if source is a multi-char sequence that can form a ligature.
70
71        See `lib.glyphs.canLigate`.
72
73        Examples:
74            KGlyph("fi").canLigate  => True
75            KGlyph("f_i").canLigate => False
76        """
77        return glyphs.canLigate(self.source)

Returns True if source is a multi-char sequence that can form a ligature.

See lib.glyphs.canLigate.

Examples:

KGlyph("fi").canLigate => True KGlyph("f_i").canLigate => False

isLigature: bool
79    @cached_property
80    def isLigature(self) -> bool:
81        """Returns True if source represents an actual ligature glyph.
82
83        See `lib.glyphs.isLigature`.
84
85        Examples:
86            KGlyph("fi").isLigature    => True
87            KGlyph("f_i").isLigature   => True
88            KGlyph("tt").isLigature    => False
89        """
90        return glyphs.isLigature(self.source)

Returns True if source represents an actual ligature glyph.

See lib.glyphs.isLigature.

Examples:

KGlyph("fi").isLigature => True KGlyph("f_i").isLigature => True KGlyph("tt").isLigature => False

def get(self, key: str, defaultValue=None):
 92    def get(self, key: str, defaultValue=None):
 93        """
 94        Retrieve an attribute by key, emulating `dict.get()`.
 95
 96        Args:
 97            key: The attribute name to retrieve.
 98            defaultValue: Value to return if the attribute is not found.
 99
100        Returns:
101            The attribute value or defaultValue if not found.
102        """
103        return getattr(self, key, defaultValue)

Retrieve an attribute by key, emulating dict.get().

Arguments:
  • key: The attribute name to retrieve.
  • defaultValue: Value to return if the attribute is not found.
Returns:

The attribute value or defaultValue if not found.

def getSupported(self, fontPath: str, warn: bool = True) -> str | None:
105    def getSupported(self, fontPath: str, warn: bool = True) -> str | None:
106        """
107        Find the supported glyph name in the given font.
108
109        Args:
110            fontPath: The font path to check for glyph support.
111
112        Returns:
113            The name of the supported glyph in the font, or None if not found.
114        """
115        glyphsInFont = glyphs.listAvailableGlyphs(fontPath)
116
117        possibleMatches = set(
118            helpers.removeNone(
119                [
120                    *helpers.coerceList(self.name),
121                    self.char,
122                    self.unicode,
123                    glyphs.toSnakeCase(self.source),
124                    glyphs.toPartBase(self.source),
125                ]
126            )
127        )
128        # ! Edge case: For certain ligatures like "t_t", some fonts may use a ".liga" suffix for the supported glyph name.
129        if self.canLigate:
130            possibleMatches.update(
131                [
132                    f"{variant}.liga"
133                    for variant in [self.name, glyphs.toSnakeCase(self.source)]
134                ]
135            )
136
137        for possibleMatch in possibleMatches:
138            if possibleMatch in glyphsInFont:
139                return possibleMatch
140
141        if warn:
142            logger.warning(
143                "{} doesn’t support glyph {}",
144                fonts.getFontName(fontPath),
145                f"{colorama.Back.LIGHTYELLOW_EX}{self.source}{colorama.Style.RESET_ALL}",
146            )

Find the supported glyph name in the given font.

Arguments:
  • fontPath: The font path to check for glyph support.
Returns:

The name of the supported glyph in the font, or None if not found.