|
9 | 9 | import enum
|
10 | 10 | import functools
|
11 | 11 | import logging
|
| 12 | +import math |
12 | 13 | import os
|
13 | 14 | import re
|
14 | 15 | import types
|
@@ -127,50 +128,59 @@ def __init__(self, box: Box):
|
127 | 128 | def to_vector(self) -> VectorParse:
|
128 | 129 | w, h, d = map(
|
129 | 130 | np.ceil, [self.box.width, self.box.height, self.box.depth])
|
130 |
| - gs = [(info.font, info.fontsize, info.num, ox, h - oy + info.offset) |
| 131 | + gs = [(info.font, info.fontsize, info.num, ox, -oy + info.offset) |
131 | 132 | for ox, oy, info in self.glyphs]
|
132 |
| - rs = [(x1, h - y2, x2 - x1, y2 - y1) |
| 133 | + rs = [(x1, -y2, x2 - x1, y2 - y1) |
133 | 134 | for x1, y1, x2, y2 in self.rects]
|
134 | 135 | return VectorParse(w, h + d, d, gs, rs)
|
135 | 136 |
|
136 | 137 | def to_raster(self, *, antialiased: bool) -> RasterParse:
|
137 | 138 | # Metrics y's and mathtext y's are oriented in opposite directions,
|
138 | 139 | # hence the switch between ymin and ymax.
|
139 | 140 | xmin = min([*[ox + info.metrics.xmin for ox, oy, info in self.glyphs],
|
140 |
| - *[x1 for x1, y1, x2, y2 in self.rects], 0]) - 1 |
141 |
| - ymin = min([*[oy - info.metrics.ymax for ox, oy, info in self.glyphs], |
142 |
| - *[y1 for x1, y1, x2, y2 in self.rects], 0]) - 1 |
| 141 | + *[x1 for x1, y1, x2, y2 in self.rects], 0]) |
| 142 | + ymin = min([*[oy - max(info.metrics.ymax, info.metrics.iceberg) |
| 143 | + for ox, oy, info in self.glyphs], |
| 144 | + *[y1 for x1, y1, x2, y2 in self.rects], 0]) |
143 | 145 | xmax = max([*[ox + info.metrics.xmax for ox, oy, info in self.glyphs],
|
144 |
| - *[x2 for x1, y1, x2, y2 in self.rects], 0]) + 1 |
| 146 | + *[x2 for x1, y1, x2, y2 in self.rects], 0]) |
145 | 147 | ymax = max([*[oy - info.metrics.ymin for ox, oy, info in self.glyphs],
|
146 |
| - *[y2 for x1, y1, x2, y2 in self.rects], 0]) + 1 |
| 148 | + *[y2 for x1, y1, x2, y2 in self.rects], 0]) |
| 149 | + # Rasterizing can leak into the neighboring pixel, hence the +/-1; it |
| 150 | + # will be cropped at the end. |
| 151 | + xmin = math.floor(xmin - 1) |
| 152 | + ymin = math.floor(ymin - 1) |
| 153 | + xmax = math.ceil(xmax + 1) |
| 154 | + ymax = math.ceil(ymax + 1) |
147 | 155 | w = xmax - xmin
|
148 |
| - h = ymax - ymin - self.box.depth |
149 |
| - d = ymax - ymin - self.box.height |
150 |
| - image = FT2Image(int(np.ceil(w)), int(np.ceil(h + max(d, 0)))) |
| 156 | + h = max(-ymin, 0) |
| 157 | + d = max(ymax, 0) |
| 158 | + image = FT2Image(w, h + d) |
151 | 159 |
|
152 |
| - # Ideally, we could just use self.glyphs and self.rects here, shifting |
153 |
| - # their coordinates by (-xmin, -ymin), but this yields slightly |
154 |
| - # different results due to floating point slop; shipping twice is the |
155 |
| - # old approach and keeps baseline images backcompat. |
156 |
| - shifted = ship(self.box, (-xmin, -ymin)) |
157 |
| - |
158 |
| - for ox, oy, info in shifted.glyphs: |
| 160 | + for ox, oy, info in self.glyphs: |
| 161 | + ox -= xmin |
| 162 | + oy -= (-h + info.offset) |
159 | 163 | info.font.draw_glyph_to_bitmap(
|
160 |
| - image, |
161 |
| - int(ox), |
162 |
| - int(oy - np.ceil(info.metrics.iceberg)), |
163 |
| - info.glyph, |
164 |
| - antialiased=antialiased) |
165 |
| - for x1, y1, x2, y2 in shifted.rects: |
| 164 | + image, ox, oy, info.glyph, antialiased=antialiased) |
| 165 | + for x1, y1, x2, y2 in self.rects: |
| 166 | + x1 -= xmin |
| 167 | + x2 -= xmin |
| 168 | + y1 -= -h |
| 169 | + y2 -= -h |
166 | 170 | height = max(int(y2 - y1) - 1, 0)
|
167 | 171 | if height == 0:
|
168 | 172 | center = (y2 + y1) / 2
|
169 | 173 | y = int(center - (height + 1) / 2)
|
170 | 174 | else:
|
171 | 175 | y = int(y1)
|
172 |
| - image.draw_rect_filled(int(x1), y, int(np.ceil(x2)), y + height) |
173 |
| - return RasterParse(0, 0, w, h + d, d, image) |
| 176 | + image.draw_rect_filled(int(x1), y, math.ceil(x2), y + height) |
| 177 | + |
| 178 | + image = np.asarray(image) |
| 179 | + while h and (image[0] == 0).all(): |
| 180 | + image = image[1:] |
| 181 | + while d and (image[-1] == 0).all(): |
| 182 | + image = image[:-1] |
| 183 | + return RasterParse(xmin, 0, w, h + d, d, image) |
174 | 184 |
|
175 | 185 |
|
176 | 186 | class FontMetrics(NamedTuple):
|
@@ -1602,7 +1612,7 @@ def ship(box: Box, xy: tuple[float, float] = (0, 0)) -> Output:
|
1602 | 1612 | cur_v = 0.
|
1603 | 1613 | cur_h = 0.
|
1604 | 1614 | off_h = ox
|
1605 |
| - off_v = oy + box.height |
| 1615 | + off_v = oy |
1606 | 1616 | output = Output(box)
|
1607 | 1617 |
|
1608 | 1618 | def clamp(value: float) -> float:
|
|
0 commit comments