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