8000 Make Text._get_layout simpler to follow. by anntzer · Pull Request #12951 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Make Text._get_layout simpler to follow. #12951

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

Merged
merged 1 commit into from
Jan 13, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 55 additions & 57 deletions lib/matplotlib/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,86 +276,87 @@ def _get_layout(self, renderer):
if key in self._cached:
return self._cached[key]

horizLayout = []

thisx, thisy = 0.0, 0.0
xmin, ymin = 0.0, 0.0
width, height = 0.0, 0.0
lines = self.get_text().split('\n')
lines = self.get_text().split("\n") # Ensures lines is not empty.

whs = np.zeros((len(lines), 2))
horizLayout = np.zeros((len(lines), 4))
ws = []
hs = []
xs = []
ys = []

# Full vertical extent of font, including ascenders and descenders:
tmp, lp_h, lp_bl = renderer.get_text_width_height_descent('lp',
self._fontproperties,
ismath=False)
offsety = (lp_h - lp_bl) * self._linespacing
_, lp_h, lp_d = renderer.get_text_width_height_descent(
"lp", self._fontproperties, ismath=False)
min_dy = (lp_h - lp_d) * self._linespacing

baseline = 0
for i, line in enumerate(lines):
clean_line, ismath = self.is_math_text(line, self.get_usetex())
if clean_line:
w, h, d = renderer.get_text_width_height_descent(clean_line,
self._fontproperties,
ismath=ismath)
w, h, d = renderer.get_text_width_height_descent(
clean_line, self._fontproperties, ismath=ismath)
else:
w, h, d = 0, 0, 0
w = h = d = 0

# For multiline text, increase the line spacing when the
# text net-height(excluding baseline) is larger than that
# of a "l" (e.g., use of superscripts), which seems
# what TeX does.
# For multiline text, increase the line spacing when the text
# net-height (excluding baseline) is larger than that of a "l"
# (e.g., use of superscripts), which seems what TeX does.
h = max(h, lp_h)
d = max(d, lp_bl)
d = max(d, lp_d)

whs[i] = w, h
ws.append(w)
hs.append(h)

# Metrics of the last line that are needed later:
baseline = (h - d) - thisy

if i == 0:
Copy link
Member
@timhoffm timhoffm Dec 9, 2018

Choose a reason for hiding this comment

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

Suggested change
if i == 0:
if thisy == 0: # first line

gets rid of the need for enumerate().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not immediately obvious that thisy cannot be zero for the second line too even if the first line is empty (it's true, but only thanks to the h = max(h, lp_h)/d = max(d, lp_d) lines) so I think leaving it as it is is clearer.

Copy link
Member

Choose a reason for hiding this comment

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

Just by common sense, the vertical position thisy must increase also for empty lines. So it should only be zero before handling the first line.

But ok, if you want to leave it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think common sense applies to this function (or to a lot of matplotlib, sadly). (e.g. if you look at the way center_baseline is defined a bit below, it looks weirdly different between anchor and non-anchor rotations...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Funnily that got fixed by #13029 :)

# position at baseline
thisy = -(h - d)
else:
# put baseline a good distance from bottom of previous line
thisy -= max(offsety, (h - d) * self._linespacing)
horizLayout[i] = thisx, thisy, w, h
thisy -= max(min_dy, (h - d) * self._linespacing)

xs.append(thisx) # == 0.
Copy link
Member

Choose a reason for hiding this comment

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

Alterantively

xs = [0.] * len(ys)

outside of the loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the symmetry of handling between xs and ys is nicer.

Copy link
Member

Choose a reason for hiding this comment

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

IMO xs = [0.] * len(ys) is much clearer about xs than secretly constructing a list of zeros in the loop. The loop should only need to bother about the quantities that actually depend on the lines. But not going to argue about this.

ys.append(thisy)

thisy -= d
width = max(width, w)
descent = d

# Metrics of the last line that are needed later:
descent = d

# Bounding box definition:
width = max(ws)
xmin = 0
xmax = width
ymax = 0
# ymin is baseline of previous line minus the descent of this line
ymin = horizLayout[-1][1] - descent
ymin = ys[-1] - descent # baseline of last line minus its descent
height = ymax - ymin
xmax = xmin + width

# get the rotation matrix
M = Affine2D().rotate_deg(self.get_rotation())

offsetLayout = np.zeros((len(lines), 2))
offsetLayout[:] = horizLayout[:, 0:2]
# now offset the individual text lines within the box
if len(lines) > 1: # do the multiline aligment
malign = self._get_multialignment()
if malign == 'center':
offsetLayout[:, 0] += width / 2.0 - horizLayout[:, 2] / 2.0
elif malign == 'right':
offsetLayout[:, 0] += width - horizLayout[:, 2]
malign = self._get_multialignment()
if malign == 'left':
offset_layout = [(x, y) for x, y in zip(xs, ys)]
elif malign == 'center':
offset_layout = [(x + width / 2 - w / 2, y)
for x, y, w in zip(xs, ys, ws)]
elif malign == 'right':
offset_layout = [(x + width - w, y)
for x, y, w in zip(xs, ys, ws)]

# the corners of the unrotated bounding box
cornersHoriz = np.array(
[(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)], float)
corners_horiz = np.array(
[(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)])

# now rotate the bbox
cornersRotated = M.transform(cornersHoriz)

txs = cornersRotated[:, 0]
tys = cornersRotated[:, 1]

corners_rotated = M.transform(corners_horiz)
# compute the bounds of the rotated box
xmin, xmax = txs.min(), txs.max()
ymin, ymax = tys.min(), tys.max()
xmin = corners_rotated[:, 0].min()
xmax = corners_rotated[:, 0].max()
ymin = corners_rotated[:, 1].min()
ymax = corners_rotated[:, 1].max()
width = xmax - xmin
height = ymax - ymin

Expand All @@ -369,25 +370,25 @@ def _get_layout(self, renderer):
# compute the text location in display coords and the offsets
# necessary to align the bbox with that location
if halign == 'center':
offsetx = (xmin + width / 2.0)
offsetx = (xmin + xmax) / 2
elif halign == 'right':
offsetx = (xmin + width)
offsetx = xmax
else:
offsetx = xmin

if valign == 'center':
offsety = (ymin + height / 2.0)
offsety = (ymin + ymax) / 2
elif valign == 'top':
offsety = (ymin + height)
offsety = ymax
elif valign == 'baseline':
offsety = ymin + descent
elif valign == 'center_baseline':
offsety = ymin + height - baseline / 2.0
else:
offsety = ymin
else:
xmin1, ymin1 = cornersHoriz[0]
xmax1, ymax1 = cornersHoriz[2]
xmin1, ymin1 = corners_horiz[0]
xmax1, ymax1 = corners_horiz[2]

if halign == 'center':
offsetx = (xmin1 + xmax1) / 2.0
Expand Down Expand Up @@ -415,12 +416,9 @@ def _get_layout(self, renderer):
bbox = Bbox.from_bounds(xmin, ymin, width, height)

# now rotate the positions around the first x,y position
xys = M.transform(offsetLayout)
xys -= (offsetx, offsety)

xs, ys = xys[:, 0], xys[:, 1]
xys = M.transform(offset_layout) - (offsetx, offsety)

ret = bbox, list(zip(lines, whs, xs, ys)), descent
ret = bbox, list(zip(lines, zip(ws, hs), *xys.T)), descent
self._cached[key] = ret
return ret

Expand Down
0