|
25 | 25 | _Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
|
26 | 26 | GraphicsContextBase, RendererBase)
|
27 | 27 | from matplotlib.cbook import is_writable_file_like, file_requires_unicode
|
28 |
| -from matplotlib.font_manager import is_opentype_cff_font, get_font |
29 |
| -from matplotlib.ft2font import LOAD_NO_HINTING |
| 28 | +from matplotlib.font_manager import get_font |
| 29 | +from matplotlib.ft2font import LOAD_NO_HINTING, LOAD_NO_SCALE |
30 | 30 | from matplotlib._ttconv import convert_ttf_to_ps
|
31 | 31 | from matplotlib.mathtext import MathTextParser
|
32 | 32 | from matplotlib._mathtext_data import uni2type1
|
@@ -134,6 +134,86 @@ def _move_path_to_path_or_stream(src, dst):
|
134 | 134 | shutil.move(src, dst, copy_function=shutil.copyfile)
|
135 | 135 |
|
136 | 136 |
|
| 137 | +def _font_to_ps_type3(font_path, glyph_ids): |
| 138 | + """ |
| 139 | + Subset *glyph_ids* from the font at *font_path* into a Type 3 font. |
| 140 | +
|
| 141 | + Parameters |
| 142 | + ---------- |
| 143 | + font_path : path-like |
| 144 | + Path to the font to be subsetted. |
| 145 | + glyph_ids : list of int |
| 146 | + The glyph indices to include in the subsetted font. |
| 147 | +
|
| 148 | + Returns |
| 149 | + ------- |
| 150 | + str |
| 151 | + The string representation of a Type 3 font, which can be included |
| 152 | + verbatim into a PostScript file. |
| 153 | + """ |
| 154 | + font = get_font(font_path, hinting_factor=1) |
| 155 | + |
| 156 | + preamble = """\ |
| 157 | +%!PS-Adobe-3.0 Resource-Font |
| 158 | +%%Creator: Converted from TrueType to Type 3 by Matplotlib. |
| 159 | +10 dict begin |
| 160 | +/FontName /{font_name} def |
| 161 | +/PaintType 0 def |
| 162 | +/FontMatrix [{inv_units_per_em} 0 0 {inv_units_per_em} 0 0] def |
| 163 | +/FontBBox [{bbox}] def |
| 164 | +/FontType 3 def |
| 165 | +/Encoding [{encoding}] def |
| 166 | +/CharStrings {num_glyphs} dict dup begin |
| 167 | +/.notdef 0 def |
| 168 | +""".format(font_name=font.postscript_name, |
| 169 | + inv_units_per_em=1 / font.units_per_EM, |
| 170 | + bbox=" ".join(map(str, font.bbox)), |
| 171 | + encoding=" ".join("/{}".format(font.get_glyph_name(glyph_id)) |
| 172 | + for glyph_id in glyph_ids), |
| 173 | + num_glyphs=len(glyph_ids) + 1) |
| 174 | + postamble = """ |
| 175 | +end readonly def |
| 176 | +
|
| 177 | +/BuildGlyph { |
| 178 | + exch begin |
| 179 | + CharStrings exch |
| 180 | + 2 copy known not {pop /.notdef} if |
| 181 | + true 3 1 roll get exec |
| 182 | + end |
| 183 | +} d |
| 184 | +
|
| 185 | +/BuildChar { |
| 186 | + 1 index /Encoding get exch get |
| 187 | + 1 index /BuildGlyph get exec |
| 188 | +} d |
| 189 | +
|
| 190 | +FontName currentdict end definefont pop |
| 191 | +""" |
| 192 | + |
| 193 | + entries = [] |
| 194 | + for glyph_id in glyph_ids: |
| 195 | + g = font.load_glyph(glyph_id, LOAD_NO_SCALE) |
| 196 | + v, c = font.get_path() |
| 197 | + entries.append( |
| 198 | + "/%(name)s{%(bbox)s sc\n" % { |
| 199 | + "name": font.get_glyph_name(glyph_id), |
| 200 | + "bbox": " ".join(map(str, [g.horiAdvance, 0, *g.bbox])), |
| 201 | + } |
| 202 | + + _path.convert_to_string( |
| 203 | + # Convert back to TrueType's internal units (1/64's). |
| 204 | + # (Other dimensions are already in these units.) |
| 205 | + Path(v * 64, c), None, None, False, None, 0, |
| 206 | + # No code for quad Beziers triggers auto-conversion to cubics. |
| 207 | + # Drop intermediate closepolys (relying on the outline |
| 208 | + # decomposer always explicitly moving to the closing point |
| 209 | + # first). |
| 210 | + [b"m", b"l", b"", b"c", b""], True).decode("ascii") |
| 211 | + + "ce} d" |
| 212 | + ) |
| 213 | + |
| 214 | + return preamble + "\n".join(entries) + postamble |
| 215 | + |
| 216 | + |
137 | 217 | class RendererPS(_backend_pdf_ps.RendererPDFPSBase):
|
138 | 218 | """
|
139 | 219 | The renderer handles all the drawing primitives using a graphics
|
@@ -922,22 +1002,18 @@ def print_figure_impl(fh):
|
922 | 1002 | # Can't use more than 255 chars from a single Type 3 font.
|
923 | 1003 | if len(glyph_ids) > 255:
|
924 | 1004 | fonttype = 42
|
925 |
| - # The ttf to ps (subsetting) support doesn't work for |
926 |
| - # OpenType fonts that are Postscript inside (like the STIX |
927 |
| - # fonts). This will simply turn that off to avoid errors. |
928 |
| - if is_opentype_cff_font(font_path): |
929 |
| - raise RuntimeError( |
930 |
| - "OpenType CFF fonts can not be saved using " |
931 |
| - "the internal Postscript backend at this " |
932 |
| - "time; consider using the Cairo backend") |
933 | 1005 | fh.flush()
|
934 |
| - try: |
935 |
| - convert_ttf_to_ps(os.fsencode(font_path), |
936 |
| - fh, fonttype, glyph_ids) |
937 |
| - except RuntimeError: |
938 |
| - _log.warning("The PostScript backend does not " |
939 |
| - "currently support the selected font.") |
940 |
| - raise |
| 1006 | + if fonttype == 3: |
| 1007 | + fh.write(_font_to_ps_type3(font_path, glyph_ids)) |
| 1008 | + else: |
| 1009 | + try: |
| 1010 | + convert_ttf_to_ps(os.fsencode(font_path), |
| 1011 | + fh, fonttype, glyph_ids) |
| 1012 | + except RuntimeError: |
| 1013 | + _log.warning( |
| 1014 | + "The PostScript backend does not currently " |
| 1015 | + "support the selected font.") |
| 1016 | + raise |
941 | 1017 | print("end", file=fh)
|
942 | 1018 | print("%%EndProlog", file=fh)
|
943 | 1019 |
|
@@ -1312,30 +1388,36 @@ def pstoeps(tmpfile, bbox=None, rotated=False):
|
1312 | 1388 | # The usage comments use the notation of the operator summary
|
1313 | 1389 | # in the PostScript Language reference manual.
|
1314 | 1390 | psDefs = [
|
| 1391 | + # name proc *d* - |
| 1392 | + "/d { bind def } bind def", |
1315 | 1393 | # x y *m* -
|
1316 |
| - "/m { moveto } bind def", |
| 1394 | + "/m { moveto } d", |
1317 | 1395 | # x y *l* -
|
1318 |
| - "/l { lineto } bind def", |
| 1396 | + "/l { lineto } d", |
1319 | 1397 | # x y *r* -
|
1320 |
| - "/r { rlineto } bind def", |
| 1398 | + "/r { rlineto } d", |
1321 | 1399 | # x1 y1 x2 y2 x y *c* -
|
1322 |
| - "/c { curveto } bind def", |
1323 |
| - # *closepath* - |
1324 |
| - "/cl { closepath } bind def", |
| 1400 | + "/c { curveto } d", |
| 1401 | + # *cl* - |
| 1402 | + "/cl { closepath } d", |
| 1403 | + # *ce* - |
| 1404 | + "/ce { closepath eofill } d", |
1325 | 1405 | # w h x y *box* -
|
1326 | 1406 | """/box {
|
1327 | 1407 | m
|
1328 | 1408 | 1 index 0 r
|
1329 | 1409 | 0 exch r
|
1330 | 1410 | neg 0 r
|
1331 | 1411 | cl
|
1332 |
| - } bind def""", |
| 1412 | + } d""", |
1333 | 1413 | # w h x y *clipbox* -
|
1334 | 1414 | """/clipbox {
|
1335 | 1415 | box
|
1336 | 1416 | clip
|
1337 | 1417 | newpath
|
1338 |
| - } bind def""", |
| 1418 | + } d""", |
| 1419 | + # wx wy llx lly urx ury *setcachedevice* - |
| 1420 | + "/sc { setcachedevice } d", |
1339 | 1421 | ]
|
1340 | 1422 |
|
1341 | 1423 |
|
|
0 commit comments