11"""
2- A python interface to Adobe Font Metrics Files.
2+ A Python interface to Adobe Font Metrics Files.
33
44Although a number of other Python implementations exist, and may be more
55complete than this, it was decided not to go with them because they were
1616>>> from pathlib import Path
1717>>> afm_path = Path(mpl.get_data_path(), 'fonts', 'afm', 'ptmr8a.afm')
1818>>>
19- >>> from matplotlib.afm import AFM
19+ >>> from matplotlib._afm import AFM
2020>>> with afm_path.open('rb') as fh:
2121... afm = AFM(fh)
22- >>> afm.string_width_height('What the heck?')
23- (6220.0, 694)
2422>>> afm.get_fontname()
2523'Times-Roman'
26- >>> afm.get_kern_dist('A', 'f')
27- 0
28- >>> afm.get_kern_dist('A', 'y')
29- -92.0
30- >>> afm.get_bbox_char('!')
31- [130, -9, 238, 676]
3224
3325As in the Adobe Font Metrics File Format Specification, all dimensions
3426are given in units of 1/1000 of the scale factor (point size) of the font
@@ -87,20 +79,23 @@ def _to_bool(s):
8779
8880def _parse_header(fh):
8981 """
90- Read the font metrics header (up to the char metrics) and returns
91- a dictionary mapping *key* to *val*. *val* will be converted to the
92- appropriate python type as necessary; e.g.:
82+ Read the font metrics header (up to the char metrics).
9383
94- * 'False'->False
95- * '0'->0
96- * '-168 -218 1000 898'-> [-168, -218, 1000, 898]
84+ Returns
85+ -------
86+ dict
87+ A dictionary mapping *key* to *val*. Dictionary keys are:
9788
98- Dictionary keys are
89+ StartFontMetrics, FontName, FullName, FamilyName, Weight, ItalicAngle,
90+ IsFixedPitch, FontBBox, UnderlinePosition, UnderlineThickness, Version,
91+ Notice, EncodingScheme, CapHeight, XHeight, Ascender, Descender,
92+ StartCharMetrics
9993
100- StartFontMetrics, FontName, FullName, FamilyName, Weight,
101- ItalicAngle, IsFixedPitch, FontBBox, UnderlinePosition,
102- UnderlineThickness, Version, Notice, EncodingScheme, CapHeight,
103- XHeight, Ascender, Descender, StartCharMetrics
94+ *val* will be converted to the appropriate Python type as necessary, e.g.,:
95+
96+ * 'False' -> False
97+ * '0' -> 0
98+ * '-168 -218 1000 898' -> [-168, -218, 1000, 898]
10499 """
105100 header_converters = {
106101 b'StartFontMetrics': _to_float,
@@ -185,11 +180,9 @@ def _parse_header(fh):
185180
186181def _parse_char_metrics(fh):
187182 """
188- Parse the given filehandle for character metrics information and return
189- the information as dicts.
183+ Parse the given filehandle for character metrics information.
190184
191- It is assumed that the file cursor is on the line behind
192- 'StartCharMetrics'.
185+ It is assumed that the file cursor is on the line behind 'StartCharMetrics'.
193186
194187 Returns
195188 -------
@@ -239,14 +232,15 @@ def _parse_char_metrics(fh):
239232
240233def _parse_kern_pairs(fh):
241234 """
242- Return a kern pairs dictionary; keys are (*char1*, *char2*) tuples and
243- values are the kern pair value. For example, a kern pairs line like
244- ``KPX A y -50``
245-
246- will be represented as::
235+ Return a kern pairs dictionary.
247236
248- d[ ('A', 'y') ] = -50
237+ Returns
238+ -------
239+ dict
240+ Keys are (*char1*, *char2*) tuples and values are the kern pair value. For
241+ example, a kern pairs line like ``KPX A y -50`` will be represented as::
249242
243+ d['A', 'y'] = -50
250244 """
251245
252246 line = next(fh)
@@ -279,8 +273,7 @@ def _parse_kern_pairs(fh):
279273
280274def _parse_composites(fh):
281275 """
282- Parse the given filehandle for composites information return them as a
283- dict.
276+ Parse the given filehandle for composites information.
284277
285278 It is assumed that the file cursor is on the line behind 'StartComposites'.
286279
@@ -363,36 +356,6 @@ def __init__(self, fh):
363356 self._metrics, self._metrics_by_name = _parse_char_metrics(fh)
364357 self._kern, self._composite = _parse_optional(fh)
365358
366- def get_bbox_char(self, c, isord=False):
367- if not isord:
368- c = ord(c)
369- return self._metrics[c].bbox
370-
371- def string_width_height(self, s):
372- """
373- Return the string width (including kerning) and string height
374- as a (*w*, *h*) tuple.
375- """
376- if not len(s):
377- return 0, 0
378- total_width = 0
379- namelast = None
380- miny = 1e9
381- maxy = 0
382- for c in s:
383- if c == '\n':
384- continue
385- wx, name, bbox = self._metrics[ord(c)]
386-
387- total_width += wx + self._kern.get((namelast, name), 0)
388- l, b, w, h = bbox
389- miny = min(miny, b)
390- maxy = max(maxy, b + h)
391-
392- namelast = name
393-
394- return total_width, maxy - miny
395-
396359 def get_str_bbox_and_descent(self, s):
397360 """Return the string bounding box and the maximal descent."""
398361 if not len(s):
@@ -423,45 +386,29 @@ def get_str_bbox_and_descent(self, s):
423386
424387 return left, miny, total_width, maxy - miny, -miny
425388
426- def get_str_bbox(self, s):
427- """Return the string bounding box."""
428- return self.get_str_bbox_and_descent(s)[:4]
429-
430- def get_name_char(self, c, isord=False):
431- """Get the name of the character, i.e., ';' is 'semicolon'."""
432- if not isord:
433- c = ord(c)
434- return self._metrics[c].name
389+ def get_glyph_name(self, glyph_ind): # For consistency with FT2Font.
390+ """Get the name of the glyph, i.e., ord(';') is 'semicolon'."""
391+ return self._metrics[glyph_ind].name
435392
436- def get_width_char (self, c, isord=False):
393+ def get_char_index (self, c): # For consistency with FT2Font.
437394 """
438- Get the width of the character from the character metric WX field.
395+ Return the glyph index corresponding to a character code point.
396+
397+ Note, for AFM fonts, we treat the glyph index the same as the codepoint.
439398 """
440- if not isord:
441- c = ord(c)
399+ return c
400+
401+ def get_width_char(self, c):
402+ """Get the width of the character code from the character metric WX field."""
442403 return self._metrics[c].width
443404
444405 def get_width_from_char_name(self, name):
445406 """Get the width of the character from a type1 character name."""
446407 return self._metrics_by_name[name].width
447408
448- def get_height_char(self, c, isord=False):
449- """Get the bounding box (ink) height of character *c* (space is 0)."""
450- if not isord:
451- c = ord(c)
452- return self._metrics[c].bbox[-1]
453-
454- def get_kern_dist(self, c1, c2):
455- """
456- Return the kerning pair distance (possibly 0) for chars *c1* and *c2*.
457- """
458- name1, name2 = self.get_name_char(c1), self.get_name_char(c2)
459- return self.get_kern_dist_from_name(name1, name2)
460-
461409 def get_kern_dist_from_name(self, name1, name2):
462410 """
463- Return the kerning pair distance (possibly 0) for chars
464- *name1* and *name2*.
411+ Return the kerning pair distance (possibly 0) for chars *name1* and *name2*.
465412 """
466413 return self._kern.get((name1, name2), 0)
467414
@@ -493,7 +440,7 @@ def get_familyname(self):
493440 return re.sub(extras, '', name)
494441
495442 @property
496- def family_name(self):
443+ def family_name(self): # For consistency with FT2Font.
497444 """The font family name, e.g., 'Times'."""
498445 return self.get_familyname()
499446
@@ -516,17 +463,3 @@ def get_xheight(self):
516463 def get_underline_thickness(self):
517464 """Return the underline thickness as float."""
518465 return self._header[b'UnderlineThickness']
519-
520- def get_horizontal_stem_width(self):
521- """
522- Return the standard horizontal stem width as float, or *None* if
523- not specified in AFM file.
524- """
525- return self._header.get(b'StdHW', None)
526-
527- def get_vertical_stem_width(self):
528- """
529- Return the standard vertical stem width as float, or *None* if
530- not specified in AFM file.
531- """
532- return self._header.get(b'StdVW', None)
0 commit comments