I am using matplotlib's Adobe Font Metrics interface to calculate the bounding box of various strings. Calculating the bounding boxes for '12' and '°' works well, but when calculating the bounding box for '12°' the size of the box seems far too large.
I understand that the dimensions of the bounding box isn't necessarily equal to the sum of the bounding boxes of its parts due to kerning and such like, however this just seems ridiculous. (It also doesn't seem to reflect the size of such text when it is rendered).
The code:
import os.path
from matplotlib import rcParams
from matplotlib.afm import AFM
afm_fname = os.path.join(rcParams['datapath'],
'fonts', 'afm', 'ptmr8a.afm')
with open(afm_fname, 'rb') as fh:
afm = AFM(fh)
print(afm.get_str_bbox('12'))
print(afm.get_str_bbox('\u00B0'))
print(afm.get_str_bbox('12\u00B0'))
(\u00B0 is the unicode for °)
Results
(0, 0, 1000.0, 676)
(0, 390, 400.0, 676)
(0, 0, 1400.0, 1066)
The height of the third result is clearly too large. 1066 is 676 + 390, I don't know if this is a coincidence but I don't understand
Question's
Why is the bounding box so large / What is causing this behaviour?
Is there a way to 'fix' this, so that I get a more representative bbox, to what the text actually looks like?
(further info on bounding boxes is found here: https://wwwimages2.adobe.com/content/dam/acom/en/devnet/font/pdfs/5004.AFM_Spec.pdf )
EDIT:
Inspecting the AFM class in matplotlib.afm in I think I have found the offending line. Underneath # find the max y
is thismax = b + h
. I can't understand why b+h
should be used there, and not just h
????
def get_str_bbox_and_descent(self, s):
"""
Return the string bounding box
"""
if not len(s):
return 0, 0, 0, 0
totalw = 0
namelast = None
miny = 1e9
maxy = 0
left = 0
if not isinstance(s, six.text_type):
s = _to_str(s)
for c in s:
if c == '\n':
continue
name = uni2type1.get(ord(c), 'question')
try:
wx, bbox = self._metrics_by_name[name]
except KeyError:
name = 'question'
wx, bbox = self._metrics_by_name[name]
l, b, w, h = bbox
if l < left:
left = l
# find the width with kerning
try:
kp = self._kern[(namelast, name)]
except KeyError:
kp = 0
totalw += wx + kp
# find the max y
thismax = b + h
if thismax > maxy:
maxy = thismax
# find the min y
thismin = b
if thismin < miny:
miny = thismin
namelast = name
return left, miny, totalw, maxy - miny, -miny