r""" Result of `ship`\ping a box: lists of positioned glyphs and rectangles. This class is not exposed to end users, but converted to a `VectorParse` or a `RasterParse` by `.MathTextParser.parse`.
| 117 | |
| 118 | |
| 119 | class Output: |
| 120 | r""" |
| 121 | Result of `ship`\ping a box: lists of positioned glyphs and rectangles. |
| 122 | |
| 123 | This class is not exposed to end users, but converted to a `VectorParse` or |
| 124 | a `RasterParse` by `.MathTextParser.parse`. |
| 125 | """ |
| 126 | |
| 127 | def __init__(self, box: Box): |
| 128 | self.box = box |
| 129 | self.glyphs: list[tuple[float, float, FontInfo]] = [] # (ox, oy, info) |
| 130 | self.rects: list[tuple[float, float, float, float]] = [] # (x, y, w, h) |
| 131 | |
| 132 | def to_vector(self) -> VectorParse: |
| 133 | w, h, d = map( |
| 134 | np.ceil, [self.box.width, self.box.height, self.box.depth]) |
| 135 | gs = [(info.font, info.fontsize, info.num, info.glyph_index, |
| 136 | ox, h - oy + info.offset) |
| 137 | for ox, oy, info in self.glyphs] |
| 138 | rs = [(bx, h - (by + bh), bw, bh) |
| 139 | for bx, by, bw, bh in self.rects] |
| 140 | # Output.rects has downwards ys, VectorParse.rects has upwards ys. |
| 141 | return VectorParse(w, h + d, d, gs, rs) |
| 142 | |
| 143 | def to_raster(self, *, antialiased: bool) -> RasterParse: |
| 144 | # Metrics y's and mathtext y's are oriented in opposite directions, |
| 145 | # hence the switch between ymin and ymax. |
| 146 | xmin = min([*[ox + info.metrics.xmin for ox, oy, info in self.glyphs], |
| 147 | *[x for x, y, w, h in self.rects], 0]) - 1 |
| 148 | ymin = min([*[oy - info.metrics.ymax for ox, oy, info in self.glyphs], |
| 149 | *[y for x, y, w, h in self.rects], 0]) - 1 |
| 150 | xmax = max([*[ox + info.metrics.xmax for ox, oy, info in self.glyphs], |
| 151 | *[x + w for x, y, w, h in self.rects], 0]) + 1 |
| 152 | ymax = max([*[oy - info.metrics.ymin for ox, oy, info in self.glyphs], |
| 153 | *[y + h for x, y, w, h in self.rects], 0]) + 1 |
| 154 | w = xmax - xmin |
| 155 | h = ymax - ymin - self.box.depth |
| 156 | d = ymax - ymin - self.box.height |
| 157 | image = np.zeros((math.ceil(h + max(d, 0)), math.ceil(w)), np.uint8) |
| 158 | |
| 159 | # Ideally, we could just use self.glyphs and self.rects here, shifting |
| 160 | # their coordinates by (-xmin, -ymin), but this yields slightly |
| 161 | # different results due to floating point slop; shipping twice is the |
| 162 | # old approach and keeps baseline images backcompat. |
| 163 | shifted = ship(self.box, (-xmin, -ymin)) |
| 164 | |
| 165 | for ox, oy, info in shifted.glyphs: |
| 166 | info.font.draw_glyph_to_bitmap( |
| 167 | image, int(ox), int(oy - info.metrics.iceberg), info.glyph, |
| 168 | antialiased=antialiased) |
| 169 | for x, y, bw, bh in shifted.rects: |
| 170 | height = max(int(bh) - 1, 0) |
| 171 | if height == 0: |
| 172 | center = y + bh / 2 |
| 173 | y = int(center - (height + 1) / 2) |
| 174 | else: |
| 175 | y = int(y) |
| 176 | x1 = math.floor(x) |
no outgoing calls
no test coverage detected
searching dependent graphs…