|
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,62 @@ 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 |
| - 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 |
144 |
| - 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 |
146 |
| - 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 |
| 141 | + xmin0 = min([*[ox + info.metrics.xmin for ox, oy, info in self.glyphs], |
| 142 | + *[x1 for x1, y1, x2, y2 in self.rects], 0]) |
| 143 | + ymin0 = 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]) |
| 146 | + xmax0 = max([*[ox + info.metrics.xmax for ox, oy, info in self.glyphs], |
| 147 | + *[x2 for x1, y1, x2, y2 in self.rects], 0]) |
| 148 | + ymax0 = max([*[oy - info.metrics.ymin for ox, oy, info in self.glyphs], |
| 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(xmin0 - 1) |
| 153 | + ymin = math.floor(ymin0 - 1) |
| 154 | + xmax = math.ceil(xmax0 + 1) |
| 155 | + ymax = math.ceil(ymax0 + 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 | + h -= 1 |
| 183 | + while d and (image[-1] == 0).all(): |
| 184 | + image = image[:-1] |
| 185 | + d -= 1 |
| 186 | + return RasterParse( |
| 187 | + xmin - xmin0, d - ymax0, xmax0 - xmin0, ymax0 - ymin0, d, image) |
175 | 188 |
|
176 | 189 |
|
177 | 190 | class FontMetrics(NamedTuple):
|
@@ -1607,7 +1620,7 @@ def ship(box: Box, xy: tuple[float, float] = (0, 0)) -> Output:
|
1607 | 1620 | cur_v = 0.
|
1608 | 1621 | cur_h = 0.
|
1609 | 1622 | off_h = ox
|
1610 |
| - off_v = oy + box.height |
| 1623 | + off_v = oy |
1611 | 1624 | output = Output(box)
|
1612 | 1625 |
|
1613 | 1626 | def clamp(value: float) -> float:
|
|
0 commit comments