| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -22,6 +22,12 @@ | |||
| 22 | 22 | from fontTools.ttLib import TTFont | |
| 23 | 23 | ||
| 24 | 24 | ||
| 25 | + _FONT_MAX_GLYPH = { | ||
| 26 | + 3: 256, | ||
| 27 | + 42: 65536, | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + | ||
| 25 | 31 | @functools.lru_cache(50) | |
| 26 | 32 | def _cached_get_afm_from_fname(fname): | |
| 27 | 33 | with open(fname, "rb") as fh: | |
@@ -103,6 +109,57 @@ def font_as_file(font): | |||
| 103 | 109 | return fh | |
| 104 | 110 | ||
| 105 | 111 | ||
| 112 | + class GlyphMap: | ||
| 113 | + """ | ||
| 114 | + A two-way glyph mapping. | ||
| 115 | + | ||
| 116 | + The forward glyph map is from (character string, glyph index)-pairs to | ||
| 117 | + (subset index, subset character code)-pairs. | ||
| 118 | + | ||
| 119 | + The inverse glyph map is from to (subset index, subset character code)-pairs to | ||
| 120 | + (character string, glyph index)-pairs. | ||
| 121 | + """ | ||
| 122 | + | ||
| 123 | + def __init__(self) -> None: | ||
| 124 | + self._forward: dict[tuple[CharacterCodeType, GlyphIndexType], | ||
| 125 | + tuple[int, CharacterCodeType]] = {} | ||
| 126 | + self._inverse: dict[tuple[int, CharacterCodeType], | ||
| 127 | + tuple[CharacterCodeType, GlyphIndexType]] = {} | ||
| 128 | + | ||
| 129 | + def get(self, charcodes: str, | ||
| 130 | + glyph_index: GlyphIndexType) -> tuple[int, CharacterCodeType] | None: | ||
| 131 | + """ | ||
| 132 | + Get the forward mapping from a (character string, glyph index)-pair. | ||
| 133 | + | ||
| 134 | + This may return *None* if the pair is not currently mapped. | ||
| 135 | + """ | ||
| 136 | + return self._forward.get((charcodes, glyph_index)) | ||
| 137 | + | ||
| 138 | + def iget(self, subset: int, | ||
| 139 | + subset_charcode: CharacterCodeType) -> tuple[str, GlyphIndexType]: | ||
| 140 | + """Get the inverse mapping from a (subset, subset charcode)-pair.""" | ||
| 141 | + return self._inverse[(subset, subset_charcode)] | ||
| 142 | + | ||
| 143 | + def add(self, charcode: str, glyph_index: GlyphIndexType, subset: int, | ||
| 144 | + subset_charcode: CharacterCodeType) -> None: | ||
| 145 | + """ | ||
| 146 | + Add a mapping to this instance. | ||
| 147 | + | ||
| 148 | + Parameters | ||
| 149 | + ---------- | ||
| 150 | + charcode : CharacterCodeType | ||
| 151 | + The character code to record. | ||
| 152 | + glyph : GlyphIndexType | ||
| 153 | + The corresponding glyph index to record. | ||
| 154 | + subset : int | ||
| 155 | + The subset in which the subset character code resides. | ||
| 156 | + subset_charcode : CharacterCodeType | ||
| 157 | + The subset character code within the above subset. | ||
| 158 | + """ | ||
| 159 | + self._forward[(charcode, glyph_index)] = (subset, subset_charcode) | ||
| 160 | + self._inverse[(subset, subset_charcode)] = (charcode, glyph_index) | ||
| 161 | + | ||
| 162 | + | ||
| 106 | 163 | class CharacterTracker: | |
| 107 | 164 | """ | |
| 108 | 165 | Helper for font subsetting by the PDF and PS backends. | |
@@ -114,16 +171,20 @@ class CharacterTracker: | |||
| 114 | 171 | ---------- | |
| 115 | 172 | subset_size : int | |
| 116 | 173 | The size at which characters are grouped into subsets. | |
| 117 | - used : dict[tuple[str, int], dict[CharacterCodeType, GlyphIndexType]] | ||
| 174 | + used : dict | ||
| 118 | 175 | A dictionary of font files to character maps. | |
| 119 | 176 | ||
| 120 | - The key is a font filename and subset within that font. | ||
| 177 | + The key is a font filename. | ||
| 121 | 178 | ||
| 122 | - The value is a dictionary mapping a character code to a glyph index. Note this | ||
| 123 | - mapping is the inverse of FreeType, which maps glyph indices to character codes. | ||
| 179 | + The value is a list of dictionaries, each mapping at most *subset_size* | ||
| 180 | + character codes to glyph indices. Note this mapping is the inverse of FreeType, | ||
| 181 | + which maps glyph indices to character codes. | ||
| 124 | 182 | ||
| 125 | 183 | If *subset_size* is not set, then there will only be one subset per font | |
| 126 | 184 | filename. | |
| 185 | + glyph_maps : dict | ||
| 186 | + A dictionary of font files to glyph maps. You probably will want to use the | ||
| 187 | + `.subset_to_unicode` method instead of this attribute. | ||
| 127 | 188 | """ | |
| 128 | 189 | ||
| 129 | 190 | def __init__(self, subset_size: int = 0): | |
@@ -134,7 +195,8 @@ def __init__(self, subset_size: int = 0): | |||
| 134 | 195 | The maximum size that is supported for an embedded font. If provided, then | |
| 135 | 196 | characters will be grouped into these sized subsets. | |
| 136 | 197 | """ | |
| 137 | - self.used: dict[tuple[str, int], dict[CharacterCodeType, GlyphIndexType]] = {} | ||
| 198 | + self.used: dict[str, list[dict[CharacterCodeType, GlyphIndexType]]] = {} | ||
| 199 | + self.glyph_maps: dict[str, GlyphMap] = {} | ||
| 138 | 200 | self.subset_size = subset_size | |
| 139 | 201 | ||
| 140 | 202 | def track(self, font: FT2Font, s: str) -> list[tuple[int, CharacterCodeType]]: | |
@@ -157,33 +219,24 @@ def track(self, font: FT2Font, s: str) -> list[tuple[int, CharacterCodeType]]: | |||
| 157 | 219 | whole). If *subset_size* is not specified, then the subset will always be 0 | |
| 158 | 220 | and the character codes will be returned from the string unchanged. | |
| 159 | 221 | """ | |
| 160 | - font_glyphs = [] | ||
| 161 | - char_to_font = font._get_fontmap(s) | ||
| 162 | - for _c, _f in char_to_font.items(): | ||
| 163 | - charcode = ord(_c) | ||
| 164 | - glyph_index = _f.get_char_index(charcode) | ||
| 165 | - if self.subset_size != 0: | ||
| 166 | - subset = charcode // self.subset_size | ||
| 167 | - subset_charcode = charcode % self.subset_size | ||
| 168 | - else: | ||
| 169 | - subset = 0 | ||
| 170 | - subset_charcode = charcode | ||
| 171 | - self.used.setdefault((_f.fname, subset), {})[subset_charcode] = glyph_index | ||
| 172 | - font_glyphs.append((subset, subset_charcode)) | ||
| 173 | - return font_glyphs | ||
| 174 | - | ||
| 175 | - def track_glyph( | ||
| 176 | - self, font: FT2Font, charcode: CharacterCodeType, | ||
| 177 | - glyph: GlyphIndexType) -> tuple[int, CharacterCodeType]: | ||
| 222 | + return [ | ||
| 223 | + self.track_glyph(f, ord(c), f.get_char_index(ord(c))) | ||
| 224 | + for c, f in font._get_fontmap(s).items() | ||
| 225 | + ] | ||
| 226 | + | ||
| 227 | + def track_glyph(self, font: FT2Font, chars: str | CharacterCodeType, | ||
| 228 | + glyph: GlyphIndexType) -> tuple[int, CharacterCodeType]: | ||
| 178 | 229 | """ | |
| 179 | 230 | Record character code *charcode* at glyph index *glyph* as using font *font*. | |
| 180 | 231 | ||
| 181 | 232 | Parameters | |
| 182 | 233 | ---------- | |
| 183 | 234 | font : FT2Font | |
| 184 | 235 | A font that is being used for the provided string. | |
| 185 | - charcode : CharacterCodeType | ||
| 186 | - The character code to record. | ||
| 236 | + chars : str or CharacterCodeType | ||
| 237 | + The character(s) to record. This may be a single character code, or multiple | ||
| 238 | + characters in a string, if the glyph maps to several characters. It will be | ||
| 239 | + normalized to a string internally. | ||
| 187 | 240 | glyph : GlyphIndexType | |
| 188 | 241 | The corresponding glyph index to record. | |
| 189 | 242 | ||
@@ -196,33 +249,64 @@ def track_glyph( | |||
| 196 | 249 | The character code within the above subset. If *subset_size* was not | |
| 197 | 250 | specified on this instance, then this is just *charcode* unmodified. | |
| 198 | 251 | """ | |
| 199 | - if self.subset_size != 0: | ||
| 200 | - subset = charcode // self.subset_size | ||
| 201 | - subset_charcode = charcode % self.subset_size | ||
| 252 | + if isinstance(chars, str): | ||
| 253 | + charcode = ord(chars[0]) | ||
| 254 | + else: | ||
| 255 | + charcode = chars | ||
| 256 | + chars = chr(chars) | ||
| 257 | + | ||
| 258 | + glyph_map = self.glyph_maps.setdefault(font.fname, GlyphMap()) | ||
| 259 | + if result := glyph_map.get(chars, glyph): | ||
| 260 | + return result | ||
| 261 | + | ||
| 262 | + subset_maps = self.used.setdefault(font.fname, [{}]) | ||
| 263 | + use_next_charmap = ( | ||
| 264 | + # Multi-character glyphs always go in the non-0 subset. | ||
| 265 | + len(chars) > 1 or | ||
| 266 | + # Default to preserving the character code as it was. | ||
| 267 | + self.subset_size != 0 | ||
| 268 | + and ( | ||
| 269 | + # But start filling a new subset if outside the first block; this | ||
| 270 | + # preserves ASCII (for Type 3) or the Basic Multilingual Plane (for | ||
| 271 | + # Type 42). | ||
| 272 | + charcode >= self.subset_size | ||
| 273 | + # Or, use a new subset if the character code is already mapped for the | ||
| 274 | + # first block. This means it's using an alternate glyph. | ||
| 275 | + or charcode in subset_maps[0] | ||
| 276 | + ) | ||
| 277 | + ) | ||
| 278 | + if use_next_charmap: | ||
| 279 | + if len(subset_maps) == 1 or len(subset_maps[-1]) == self.subset_size: | ||
| 280 | + subset_maps.append({}) | ||
| 281 | + subset = len(subset_maps) - 1 | ||
| 282 | + subset_charcode = len(subset_maps[-1]) | ||
| 202 | 283 | else: | |
| 203 | 284 | subset = 0 | |
| 204 | 285 | subset_charcode = charcode | |
| 205 | - self.used.setdefault((font.fname, subset), {})[subset_charcode] = glyph | ||
| 286 | + subset_maps[subset][subset_charcode] = glyph | ||
| 287 | + glyph_map.add(chars, glyph, subset, subset_charcode) | ||
| 206 | 288 | return (subset, subset_charcode) | |
| 207 | 289 | ||
| 208 | - def subset_to_unicode(self, index: int, | ||
| 209 | - charcode: CharacterCodeType) -> CharacterCodeType: | ||
| 290 | + def subset_to_unicode(self, fontname: str, subset: int, | ||
| 291 | + subset_charcode: CharacterCodeType) -> str: | ||
| 210 | 292 | """ | |
| 211 | 293 | Map a subset index and character code to a Unicode character code. | |
| 212 | 294 | ||
| 213 | 295 | Parameters | |
| 214 | 296 | ---------- | |
| 215 | - index : int | ||
| 297 | + fontname : str | ||
| 298 | + The name of the font, from the *used* dictionary key. | ||
| 299 | + subset : int | ||
| 216 | 300 | The subset index within a font. | |
| 217 | - charcode : CharacterCodeType | ||
| 301 | + subset_charcode : CharacterCodeType | ||
| 218 | 302 | The character code within a subset to map back. | |
| 219 | 303 | ||
| 220 | 304 | Returns | |
| 221 | 305 | ------- | |
| 222 | - CharacterCodeType | ||
| 223 | - The Unicode character code corresponding to the subsetted one. | ||
| 306 | + str | ||
| 307 | + The Unicode character(s) corresponding to the subsetted character code. | ||
| 224 | 308 | """ | |
| 225 | - return index * self.subset_size + charcode | ||
| 309 | + return self.glyph_maps[fontname].iget(subset, subset_charcode)[0] | ||
| 226 | 310 | ||
| 227 | 311 | ||
| 228 | 312 | class RendererPDFPSBase(RendererBase): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -368,12 +368,6 @@ def pdfRepr(obj): | |||
| 368 | 368 | "objects") | |
| 369 | 369 | ||
| 370 | 370 | ||
| 371 | - _FONT_MAX_GLYPH = { | ||
| 372 | - 3: 256, | ||
| 373 | - 42: 65536, | ||
| 374 | - } | ||
| 375 | - | ||
| 376 | - | ||
| 377 | 371 | class Reference: | |
| 378 | 372 | """ | |
| 379 | 373 | PDF reference object. | |
@@ -691,7 +685,7 @@ def __init__(self, filename, metadata=None): | |||
| 691 | 685 | self._fontNames = {} # maps filenames to internal font names | |
| 692 | 686 | self._dviFontInfo = {} # maps pdf names to dvifonts | |
| 693 | 687 | self._character_tracker = _backend_pdf_ps.CharacterTracker( | |
| 694 | - _FONT_MAX_GLYPH.get(mpl.rcParams['pdf.fonttype'], 0)) | ||
| 688 | + _backend_pdf_ps._FONT_MAX_GLYPH.get(mpl.rcParams['ps.fonttype'], 0)) | ||
| 695 | 689 | ||
| 696 | 690 | self.alphaStates = {} # maps alpha values to graphics state objects | |
| 697 | 691 | self._alpha_state_seq = (Name(f'A{i}') for i in itertools.count(1)) | |
@@ -948,9 +942,8 @@ def writeFonts(self): | |||
| 948 | 942 | else: | |
| 949 | 943 | # a normal TrueType font | |
| 950 | 944 | _log.debug('Writing TrueType font.') | |
| 951 | - charmap = self._character_tracker.used.get((filename, subset)) | ||
| 952 | - if charmap: | ||
| 953 | - fonts[Fx] = self.embedTTF(filename, subset, charmap) | ||
| 945 | + charmap = self._character_tracker.used[filename][subset] | ||
| 946 | + fonts[Fx] = self.embedTTF(filename, subset, charmap) | ||
| 954 | 947 | self.writeObject(self.fontObject, fonts) | |
| 955 | 948 | ||
| 956 | 949 | def _write_afm_font(self, filename): | |
@@ -992,8 +985,12 @@ def _embedTeXFont(self, dvifont): | |||
| 992 | 985 | ||
| 993 | 986 | # Reduce the font to only the glyphs used in the document, get the encoding | |
| 994 | 987 | # for that subset, and compute various properties based on the encoding. | |
| 995 | - charmap = self._character_tracker.used[(dvifont.fname, 0)] | ||
| 996 | - chars = frozenset(charmap.keys()) | ||
| 988 | + charmap = self._character_tracker.used[dvifont.fname][0] | ||
| 989 | + chars = { | ||
| 990 | + # DVI type 1 fonts always map single glyph to single character. | ||
| 991 | + ord(self._character_tracker.subset_to_unicode(dvifont.fname, 0, ccode)) | ||
| 992 | + for ccode in charmap | ||
| 993 | + } | ||
| 997 | 994 | t1font = t1font.subset(chars, self._get_subset_prefix(charmap.values())) | |
| 998 | 995 | fontdict['BaseFont'] = Name(t1font.prop['FontName']) | |
| 999 | 996 | # createType1Descriptor writes the font data as a side effect | |
@@ -1144,14 +1141,16 @@ def generate_unicode_cmap(subset_index, charmap): | |||
| 1144 | 1141 | unicode_groups[-1][1] = ccode | |
| 1145 | 1142 | last_ccode = ccode | |
| 1146 | 1143 | ||
| 1144 | + def _to_unicode(ccode): | ||
| 1145 | + chars = self._character_tracker.subset_to_unicode( | ||
| 1146 | + filename, subset_index, ccode) | ||
| 1147 | + hexstr = chars.encode('utf-16be').hex() | ||
| 1148 | + return f'<{hexstr}>' | ||
| 1149 | + | ||
| 1147 | 1150 | width = 2 if fonttype == 3 else 4 | |
| 1148 | 1151 | unicode_bfrange = [] | |
| 1149 | 1152 | for start, end in unicode_groups: | |
| 1150 | - real_start = self._character_tracker.subset_to_unicode(subset_index, | ||
| 1151 | - start) | ||
| 1152 | - real_end = self._character_tracker.subset_to_unicode(subset_index, end) | ||
| 1153 | - real_values = ' '.join('<%s>' % chr(x).encode('utf-16be').hex() | ||
| 1154 | - for x in range(real_start, real_end+1)) | ||
| 1153 | + real_values = ' '.join(_to_unicode(x) for x in range(start, end+1)) | ||
| 1155 | 1154 | unicode_bfrange.append( | |
| 1156 | 1155 | f'<{start:0{width}x}> <{end:0{width}x}> [{real_values}]') | |
| 1157 | 1156 | unicode_cmap = (self._identityToUnicodeCMap % | |
@@ -2325,7 +2324,7 @@ def output_singlebyte_chunk(kerns_or_chars): | |||
| 2325 | 2324 | for item in _text_helpers.layout(s, font, kern_mode=Kerning.UNFITTED, | |
| 2326 | 2325 | language=language): | |
| 2327 | 2326 | subset, charcode = self.file._character_tracker.track_glyph( | |
| 2328 | - item.ft_object, ord(item.char), item.glyph_index) | ||
| 2327 | + item.ft_object, item.char, item.glyph_index) | ||
| 2329 | 2328 | if (item.ft_object, subset) != prev_font: | |
| 2330 | 2329 | if singlebyte_chunk: | |
| 2331 | 2330 | output_singlebyte_chunk(singlebyte_chunk) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments