""" Font-related classes & constants. Includes the definition of the character widths of all PDF standard fonts. The contents of this module are internal to fpdf2, and not part of the public API. They may change at any time without prior warning or any deprecation period, in non-backward-compatible ways. Usage documentation at: """ # pyright: reportUnknownArgumentType=false, reportAttributeAccessIssue=false, reportUnknownMemberType=false # pyright: reportUnknownVariableType=false, reportOptionalMemberAccess=false, reportCallIssue=false, reportArgumentType=false import logging import re import warnings from bisect import bisect_left from collections import defaultdict from copy import deepcopy from dataclasses import dataclass, replace from functools import cache from io import BytesIO from pathlib import Path from typing import ( TYPE_CHECKING, Any, Iterator, Optional, Protocol, Sequence, Union, ) from fontTools import ttLib from fontTools.pens.ttGlyphPen import TTGlyphPen from fontTools.varLib import instancer try: import uharfbuzz as hb # pylint: disable=no-member class HarfBuzzFont(hb.Font): # type: ignore[misc] "uharfbuzz.Font than can be deepcopied" ptem: float # cf. issue #1075, avoids: TypeError: no default __reduce__ due to non-trivial __cinit__ def __deepcopy__(self: "HarfBuzzFont", memo: dict[int, Any]) -> "HarfBuzzFont": return self except ImportError: hb = None from .deprecation import get_stack_level from .drawing_primitives import ( DeviceCMYK, DeviceGray, DeviceRGB, convert_to_device_color, ) from .enums import Align, FontDescriptorFlags, TextEmphasis from .font_type_3 import get_color_font_object from .syntax import Name, PDFObject from .util import escape_parens if TYPE_CHECKING: from .fpdf import FPDF class HBGlyphInfo(Protocol): codepoint: int cluster: int flags: Any class HBGlyphPosition(Protocol): position: tuple[int, int, int, int] x_advance: int y_advance: int x_offset: int y_offset: int class HBBuffer(Protocol): direction: str glyph_infos: list[HBGlyphInfo] glyph_positions: list[HBGlyphPosition] language: str script: str cluster_level: int # IntEnum BufferClusterLevel def add_str( self, text: str, item_offset: int = 0, item_length: int = -1 ) -> None: ... def guess_segment_properties(self) -> None: ... LOGGER = logging.getLogger(__name__) @dataclass class FontFace: """ Represent basic font styling properties. This is a subset of `fpdf.graphics_state.GraphicsStateMixin` properties. """ __slots__ = ( # RAM usage optimization "family", "emphasis", "size_pt", "color", "fill_color", ) family: Optional[str] emphasis: Optional[TextEmphasis] # None means "no override" # Whereas "" means "no emphasis" # This can be a combination: B | U size_pt: Optional[float] # Colors are single number grey scales or (red, green, blue) tuples: color: Optional[Union[DeviceGray, DeviceRGB, DeviceCMYK]] fill_color: Optional[Union[DeviceGray, DeviceRGB, DeviceCMYK]] def __init__( self, family: Optional[str] = None, emphasis: Optional[TextEmphasis | str | int] = None, size_pt: Optional[float] = None, color: Optional[ DeviceRGB | DeviceGray | DeviceCMYK | str | float | Sequence[float] ] = None, fill_color: Optional[ DeviceRGB | DeviceGray | DeviceCMYK | str | float | Sequence[float] ] = None, ): self.family = family self.emphasis = None if emphasis is None else TextEmphasis.coerce(emphasis) self.size_pt = size_pt self.color = None if color is None else convert_to_device_color(color) self.fill_color = ( None if fill_color is None else convert_to_device_color(fill_color) ) replace = replace """ Create a new FontFace instance, with new values for some attributes. Same as `dataclasses.replace()` """ @staticmethod def _override(current_value: Any, override_value: Any) -> Any: """Override the current value if an override value is provided""" return current_value if override_value is None else override_value @staticmethod def combine( default_style: Optional["FontFace"], override_style: Optional["FontFace"] ) -> Optional["FontFace"]: """ Create a combined FontFace with all the supplied features of the two styles. When both the default and override styles provide a feature, prefer the override style. Override specified FontFace style features Override this FontFace's values with the values of `other`. Values of `other` that are None in this FontFace will be kept unchanged. """ if override_style is None: return default_style if default_style is None: return override_style if not isinstance(override_style, FontFace): raise TypeError(f"Cannot combine FontFace with {type(override_style)}") if not isinstance(default_style, FontFace): raise TypeError(f"Cannot combine FontFace with {type(default_style)}") return FontFace( family=FontFace._override(default_style.family, override_style.family), emphasis=FontFace._override( default_style.emphasis, override_style.emphasis, ), size_pt=FontFace._override(default_style.size_pt, override_style.size_pt), color=FontFace._override(default_style.color, override_style.color), fill_color=FontFace._override( default_style.fill_color, override_style.fill_color ), ) class TextStyle(FontFace): """ Subclass of `FontFace` that allows to specify vertical & horizontal spacing """ def __init__( self, font_family: Optional[str] = None, # None means "no override" # Whereas "" means "no emphasis" font_style: Optional[str | TextEmphasis] = None, font_size_pt: Optional[float] = None, color: Optional[ Union[str, float, Sequence[float], DeviceGray, DeviceRGB, DeviceCMYK] ] = None, # grey scale or (red, green, blue), fill_color: Optional[ Union[float, Sequence[float], DeviceGray, DeviceRGB, DeviceCMYK] ] = None, # grey scale or (red, green, blue), underline: bool = False, t_margin: Optional[float] = None, l_margin: Optional[float | Align | str] = None, b_margin: Optional[float] = None, ) -> None: if isinstance(font_style, TextEmphasis): font_style = font_style.style super().__init__( font_family, ((font_style or "") + "U") if underline else font_style, font_size_pt, color, fill_color, ) self.t_margin = t_margin or 0 if isinstance(l_margin, (int, float)): self.l_margin: Optional[float | Align] = float(l_margin) elif l_margin: self.l_margin = Align.coerce(l_margin) else: self.l_margin = 0 self.b_margin = b_margin or 0 def __repr__(self) -> str: return ( super().__repr__()[:-1] + f", t_margin={self.t_margin}, l_margin={self.l_margin}, b_margin={self.b_margin})" ) # override parent method def replace( # type: ignore[override] self, /, font_family: Optional[str] = None, emphasis: Optional[TextEmphasis] = None, font_size_pt: Optional[float] = None, color: Optional[DeviceGray | DeviceRGB | DeviceCMYK] = None, fill_color: Optional[DeviceGray | DeviceRGB | DeviceCMYK] = None, t_margin: Optional[float] = None, l_margin: Optional[float | Align] = None, b_margin: Optional[float] = None, ) -> "TextStyle": """ Create a new TextStyle instance, with new values for some attributes. Same as `dataclasses.replace()` """ return TextStyle( font_family=font_family or self.family, font_style=self.emphasis if emphasis is None else emphasis.style, font_size_pt=font_size_pt or self.size_pt, color=color or self.color, fill_color=fill_color or self.fill_color, t_margin=self.t_margin if t_margin is None else t_margin, l_margin=self.l_margin if l_margin is None else l_margin, b_margin=self.b_margin if b_margin is None else b_margin, ) class TitleStyle(TextStyle): def __init__(self, *args: Any, **kwargs: Any) -> None: warnings.warn( ( "fpdf.TitleStyle is deprecated since 2.8.0." " It has been replaced by fpdf.TextStyle." ), DeprecationWarning, stacklevel=get_stack_level(), ) super().__init__(*args, **kwargs) __pdoc__ = {"TitleStyle": False} # Replaced by TextStyle class CoreFont: # RAM usage optimization: __slots__ = ( "i", "type", "name", "sp", "ss", "up", "ut", "cw", "fontkey", "emphasis", ) def __init__(self, i: int, fontkey: str, style: str) -> None: self.i = i self.type = "core" self.name = CORE_FONTS[fontkey] self.sp = 250 # strikethrough horizontal position self.ss = 50 # strikethrough size (height) self.up = -100 # underline horizontal position self.ut = 50 # underline height self.cw = CORE_FONTS_CHARWIDTHS[fontkey] self.fontkey = fontkey self.emphasis = TextEmphasis.coerce(style) def get_text_width( self, text: str, font_size_pt: float, _: Optional[dict[str, Any]] ) -> tuple[int, float]: return (len(text), sum(self.cw[c] for c in text) * font_size_pt * 0.001) # Disabling this check - method kept as is to have same method/signature on CoreConf and TTFFont: # pylint: disable=no-self-use def encode_text(self, text: str) -> str: return f"({escape_parens(text)}) Tj" def __repr__(self) -> str: return f"CoreFont(i={self.i}, fontkey={self.fontkey})" class TTFFont: __slots__ = ( # RAM usage optimization "i", "type", "name", "desc", "glyph_ids", "_hbfont", "sp", "ss", "up", "ut", "cw", "ttffile", "fontkey", "emphasis", "scale", "subset", "cmap", "ttfont", "missing_glyphs", "biggest_size_pt", "color_font", "unicode_range", "palette_index", "is_compressed", "is_cff", "is_cid_keyed", "is_symbol", "cff_ros", "collection_font_number", ) def __init__( self, fpdf: "FPDF", font_file_path: Path, fontkey: str, style: str, unicode_range: Optional[set[int]] = None, axes_dict: Optional[dict[str, float]] = None, palette_index: Optional[int] = None, collection_font_number: int = 0, ): self.i = len(fpdf.fonts) + 1 self.type = "TTF" self.ttffile = font_file_path self.is_compressed = str(self.ttffile).lower().endswith((".woff", ".woff2")) self._hbfont: Optional["HarfBuzzFont"] = None self.fontkey = fontkey self.biggest_size_pt: float = 0 self.collection_font_number = collection_font_number # recalcTimestamp=False means that it doesn't modify the "modified" timestamp in head table # if we leave recalcTimestamp=True the tests will break every time try: self.ttfont = ttLib.TTFont( self.ttffile, recalcTimestamp=False, fontNumber=collection_font_number, lazy=True, ) except ( ImportError, RuntimeError, ) as exc: # pragma: no cover - defensive messaging # If the user passed a WOFF2 file but brotli is not installed, fontTools # raises an ImportError/RuntimeError during parsing. Provide a clearer hint # only for that specific situation. Allow other exceptions (e.g. FileNotFoundError, # OSError, parsing errors) to propagate normally so they aren't masked here. fname_str = str(self.ttffile).lower() if fname_str.endswith(".woff2"): raise RuntimeError( "Could not open WOFF2 font. WOFF2 support requires an external Brotli " "library (install 'brotli' or 'brotlicffi'). Original error: " f"{exc!s}" ) from exc raise if axes_dict is not None: if "fvar" not in self.ttfont: raise AttributeError(f"{self.ttffile} is not a variable font") instancer.instantiateVariableFont( self.ttfont, axes_dict, inplace=True, static=True, ) if self.is_compressed: # Normalize to SFNT output for embedding and HarfBuzz. self.ttfont.flavor = None self.is_cff = "CFF " in self.ttfont or "CFF2" in self.ttfont self.is_cid_keyed = False self.is_symbol = False self.cff_ros: Optional[tuple[str, str, int]] = None if self.is_cff: try: cff_table = "CFF " if "CFF " in self.ttfont else "CFF2" top_dict = self.ttfont[cff_table].cff.topDictIndex[0] ros = getattr(top_dict, "ROS", None) self.is_cid_keyed = ros is not None if ros is not None: self.cff_ros = (str(ros[0]), str(ros[1]), int(ros[2])) except ( KeyError, AttributeError, IndexError, TypeError, ): # pragma: no cover self.is_cid_keyed = False upem: float = float(self.ttfont["head"].unitsPerEm) self.scale: float = 1000 / upem # check if the font is a TrueType and missing a .notdef glyph # if it is missing, provide a fallback glyph if ( "glyf" in self.ttfont and ".notdef" not in self.ttfont["glyf"] # pyright: ignore[reportOperatorIssue] ): LOGGER.warning( ( "TrueType Font '%s' is missing the '.notdef' glyph. " "Fallback glyph will be provided." ), self.fontkey, ) # draw a diagonal cross .notdef glyph xMin, xMax, yMin, yMax = ( self.ttfont["head"].xMin, self.ttfont["head"].xMax, self.ttfont["head"].yMin, self.ttfont["head"].yMax, ) pen = TTGlyphPen(self.ttfont["glyf"]) # pyright: ignore[reportArgumentType] pen.moveTo((xMin, yMin)) pen.lineTo((xMax, yMin)) pen.lineTo((xMax, yMax)) pen.lineTo((xMin, yMax)) pen.closePath() pen.moveTo((xMin, yMin)) pen.lineTo((xMax, yMax)) pen.closePath() pen.moveTo((xMax, yMin)) pen.lineTo((xMin, yMax)) pen.closePath() self.ttfont["glyf"][ # pyright: ignore[reportIndexIssue] ".notdef" ] = pen.glyph() self.ttfont["hmtx"][".notdef"] = ( # pyright: ignore[reportIndexIssue] xMax - xMin, yMax - yMin, ) default_width: float = round( self.scale * self.ttfont["hmtx"].metrics[".notdef"][0] ) os2_table = self.ttfont["OS/2"] post_table = self.ttfont["post"] try: cap_height = os2_table.sCapHeight except AttributeError: cap_height = self.ttfont["hhea"].ascent # entry for the PDF font descriptor specifying various characteristics of the font flags = FontDescriptorFlags.SYMBOLIC if post_table.isFixedPitch: flags |= FontDescriptorFlags.FIXED_PITCH if post_table.italicAngle != 0: flags |= FontDescriptorFlags.ITALIC if os2_table.usWeightClass >= 600: flags |= FontDescriptorFlags.FORCE_BOLD self.desc = PDFFontDescriptor( ascent=round(self.ttfont["hhea"].ascent * self.scale), descent=round(self.ttfont["hhea"].descent * self.scale), cap_height=round(cap_height * self.scale), flags=flags, font_b_box=( f"[{self.ttfont['head'].xMin * self.scale:.0f} {self.ttfont['head'].yMin * self.scale:.0f}" f" {self.ttfont['head'].xMax * self.scale:.0f} {self.ttfont['head'].yMax * self.scale:.0f}]" ), italic_angle=int(post_table.italicAngle), stem_v=round(50 + int(pow((os2_table.usWeightClass / 65), 2))), missing_width=default_width, ) # a map unicode_char -> char_width self.cw: dict[int, float] = defaultdict(lambda: default_width) # fonttools cmap = unicode char to glyph name # saving only the keys we have a tuple with # the unicode characters available on the font self.cmap: dict[int, str] = self.ttfont.getBestCmap() if not self.cmap: self.cmap = self._build_symbol_cmap() if self.cmap: self.is_symbol = True else: raise NotImplementedError( "Font not supported. No unicode cmap and no support for this non-standard charset." ) if unicode_range is not None and len(unicode_range) != 0: self.cmap = { codepoint: glyph_id for codepoint, glyph_id in self.cmap.items() # pyright: ignore[reportUnknownVariableType] if codepoint in unicode_range } # saving a list of glyph ids to char to allow # subset by unicode (regular) and by glyph # (shaped with harfbuz) self.glyph_ids = {} for char in self.cmap: # pyright: ignore[] # take glyph associated to char glyph = self.cmap[char] # take width associated to glyph w = self.ttfont["hmtx"].metrics[glyph][0] # probably this check could be deleted if w == 65535: w = 0 self.cw[char] = round(self.scale * w + 0.001) # ROUND_HALF_UP self.glyph_ids[char] = self.ttfont.getGlyphID(glyph) self.missing_glyphs: list[int] = [] self.name = re.sub("[ ()]", "", self.ttfont["name"].getBestFullName()) self.up = round(post_table.underlinePosition * self.scale) self.ut = round(post_table.underlineThickness * self.scale) self.sp = round(os2_table.yStrikeoutPosition * self.scale) self.ss = round(os2_table.yStrikeoutSize * self.scale) self.emphasis = TextEmphasis.coerce(style) self.subset = SubsetMap(self) self.palette_index = palette_index if palette_index is not None else 0 self.color_font = ( get_color_font_object(fpdf, self, self.palette_index) if fpdf.render_color_fonts else None ) # pylint: disable=no-member @property def hbfont(self) -> "HarfBuzzFont": if not self._hbfont: if self.is_compressed: # HarfBuzz cannot load compressed WOFF/WOFF2 files directly, so we # re-serialize the fontTools TTFont to a raw SFNT byte buffer. buf = BytesIO() self.ttfont.save(buf) buf.seek(0) ttfont_bytes = buf.read() # Try to create a HarfBuzz blob from bytes; if not available, write a # temporary file as a last resort. try: blob = hb.Blob.from_bytes(ttfont_bytes) face = hb.Face(blob=blob, index=self.collection_font_number) except (AttributeError, RuntimeError): import tempfile, os # pylint: disable=import-outside-toplevel tmp_name = None try: with tempfile.NamedTemporaryFile( suffix=".ttf", delete=False ) as tmp: tmp_name = tmp.name tmp.write(ttfont_bytes) tmp.flush() face = hb.Face( blob=hb.Blob.from_file_path(tmp_name), index=self.collection_font_number, ) finally: if tmp_name: try: os.unlink(tmp_name) except OSError as cleanup_error: # Log warning about failed cleanup - orphaned temp file may cause disk issues LOGGER.warning( "Failed to clean up temporary font file '%s': %s. This may leave an orphaned file on disk.", tmp_name, cleanup_error, ) self._hbfont = HarfBuzzFont(face) else: # For regular TTF/OTF fonts, load directly from file path (faster) self._hbfont = HarfBuzzFont( hb.Face( blob=hb.Blob.from_file_path(self.ttffile), index=self.collection_font_number, ) ) return self._hbfont def __repr__(self) -> str: return f"TTFFont(i={self.i}, fontkey={self.fontkey})" def __deepcopy__(self: "TTFFont", memo: dict[int, Any]) -> "TTFFont": """ The aim here is that FPDFRecorder.__init__() does NOT deepcopy all fonts attributes but instead share references to immutable objects between the original FPDF instance and the FPDFRecorder instances to avoid performances issues as spotted in issue #1444. """ copy = TTFFont.__new__(TTFFont) # Immutable attributes: copy.i = self.i copy.type = "TTF" copy.ttffile = self.ttffile copy.fontkey = self.fontkey copy.scale = self.scale copy.name = self.name copy.up = self.up copy.ut = self.ut copy.sp = self.sp copy.ss = self.ss copy.emphasis = self.emphasis copy.is_compressed = self.is_compressed copy.is_cff = self.is_cff copy.is_cid_keyed = self.is_cid_keyed copy.is_symbol = self.is_symbol copy.cff_ros = self.cff_ros copy.collection_font_number = self.collection_font_number # Attributes shared, to improve FPDFRecorder performances: copy.ttfont = self.ttfont copy.cmap = self.cmap copy.desc = self.desc # Attributes deepcopied: copy.cw = deepcopy(self.cw, memo) copy.glyph_ids = deepcopy(self.glyph_ids, memo) copy.missing_glyphs = deepcopy(self.missing_glyphs, memo) copy.subset = deepcopy(self.subset, memo) copy.biggest_size_pt = self.biggest_size_pt copy._hbfont = self._hbfont copy.color_font = self.color_font copy.palette_index = self.palette_index return copy def close(self) -> None: self.ttfont.close() self._hbfont = None def _build_symbol_cmap(self) -> dict[int, str]: """ Build a Unicode cmap from a Microsoft Non-Standard Symbol cmap. Maps bytes 0x00-0xFF to the PUA range U+F000-U+F0FF. Reference: https://learn.microsoft.com/en-us/typography/opentype/otspec160/recom """ try: cmap_table = self.ttfont["cmap"] except KeyError: # pragma: no cover - defensive return {} symbol_table = None for table in cmap_table.tables: if table.platformID == 3 and table.platEncID == 0: symbol_table = table break if not symbol_table: return {} mapping: dict[int, str] = {} for code, glyph in symbol_table.cmap.items(): if code <= 0xFF: mapping[0xF000 + code] = glyph else: mapping[code] = glyph return mapping def _map_symbol_text(self, text: str) -> str: if not self.is_symbol: return text mapped_chars = [] for ch in text: code = ord(ch) if 0xF000 <= code <= 0xF0FF: mapped_chars.append(ch) elif 0x20 <= code <= 0xFF and code != 0x7F: mapped_chars.append(chr(0xF000 + code)) else: mapped_chars.append(ch) return "".join(mapped_chars) def escape_text(self, text: str) -> str: if self.color_font: encoded = text.encode("latin-1", errors="replace") return escape_parens(encoded.decode("latin-1", errors="ignore")) return escape_parens(text.encode("utf-16-be").decode("latin-1")) def get_text_width( self, text: str, font_size_pt: float, text_shaping_params: Optional[dict[str, Any]], ) -> tuple[int, float]: if font_size_pt > self.biggest_size_pt: self.biggest_size_pt = font_size_pt mapped_text = self._map_symbol_text(text) if text_shaping_params: return self.shaped_text_width( mapped_text, font_size_pt, text_shaping_params ) return ( len(mapped_text), sum(self.cw[ord(c)] for c in mapped_text) * font_size_pt * 0.001, ) def shaped_text_width( self, text: str, font_size_pt: float, text_shaping_params: Optional[dict[str, Any]], ) -> tuple[int, float]: """ When texts are shaped, the length of a string is not always the sum of all individual character widths This method will invoke harfbuzz to perform the text shaping and return the sum of "x_advance" and "x_offset" for each glyph. This method works for "left to right" or "right to left" texts. """ _, glyph_positions = self.perform_harfbuzz_shaping( text, font_size_pt, text_shaping_params ) # If there is nothing to render (harfbuzz returns None), we return 0 text width if glyph_positions is None: return (0, 0) text_width: float = 0 for pos in glyph_positions: text_width += ( round(self.scale * pos.x_advance + 0.001) * font_size_pt * 0.001 ) return (len(glyph_positions), text_width) # Disabling this check - looks like cython confuses pylint: # pylint: disable=no-member def perform_harfbuzz_shaping( self, text: str, font_size_pt: float, text_shaping_params: Optional[dict[str, Any]], ) -> tuple[Sequence["HBGlyphInfo"], Sequence["HBGlyphPosition"]]: """ This method invokes Harfbuzz to perform text shaping of the input string """ text = self._map_symbol_text(text) self.hbfont.ptem = font_size_pt if TYPE_CHECKING: buf: HBBuffer buf = hb.Buffer() buf.cluster_level = 1 buf.add_str("".join(text)) buf.guess_segment_properties() features = ( text_shaping_params["features"] if text_shaping_params is not None else {} ) if text_shaping_params is None: text_shaping_params = {} if ( "fragment_direction" in text_shaping_params and text_shaping_params["fragment_direction"] is not None ): buf.direction = text_shaping_params["fragment_direction"].value if ( "script" in text_shaping_params and text_shaping_params["script"] is not None ): buf.script = text_shaping_params["script"] if ( "language" in text_shaping_params and text_shaping_params["language"] is not None ): buf.language = text_shaping_params["language"] hb.shape(self.hbfont, buf, features) return buf.glyph_infos, buf.glyph_positions def encode_text(self, text: str) -> str: text = self._map_symbol_text(text) txt_mapped = "" for char in text: uni = ord(char) # Instead of adding the actual character to the stream its code is # mapped to a position in the font's subset mapped_char: Optional[int] = self.subset.pick(uni) if mapped_char is not None: txt_mapped += chr(mapped_char) return f"({self.escape_text(txt_mapped)}) Tj" def shape_text( self, text: str, font_size_pt: float, text_shaping_params: Optional[dict[str, Any]], ) -> Sequence[dict[str, Any]]: """ This method will invoke harfbuzz for text shaping, include the mapping code of the glyphs on the subset and map input characters to the cluster codes """ if len(text) == 0: return [] glyph_infos, glyph_positions = self.perform_harfbuzz_shaping( text, font_size_pt, text_shaping_params ) text_info = [] # Find cluster gaps # Ex: text = "ABCD" # glyph infos has cluster: 0, 2, 3 - it means A and B are together on the first glyph # (ligature or substitution) - the glyph should have both unicodes and it should be translated # properly on the CID to GID mapping # def get_cluster_from_text_index(cluster_list: Sequence[int], index: int) -> int: pos = bisect_left(cluster_list, index) if pos == 0: return cluster_list[0] if pos == len(cluster_list) or cluster_list[pos] != index: return cluster_list[pos - 1] return cluster_list[pos] cluster_list = list(sorted(int(gi.cluster) for gi in glyph_infos)) cluster_mapping: dict[int, list[int]] = {} for i in range(len(text)): cl = get_cluster_from_text_index(cluster_list, i) if cl in cluster_mapping: cluster_mapping[cl].append(i) else: cluster_mapping[cl] = [i] for cluster_seq, gi in enumerate(glyph_infos): unicode = [] if gi.cluster in cluster_mapping: unicode = [ord(text[i]) for i in cluster_mapping[gi.cluster]] cluster_mapping.pop(gi.cluster) gname = self.ttfont.getGlyphName(gi.codepoint) gwidth = round(self.scale * self.ttfont["hmtx"].metrics[gname][0]) glyph = self.subset.get_glyph( glyph=gi.codepoint, unicode=tuple(unicode), glyph_name=gname, glyph_width=gwidth, ) if glyph is None: continue force_positioning = False if ( gwidth != glyph_positions[cluster_seq].x_advance or glyph_positions[cluster_seq].x_offset != 0 or glyph_positions[cluster_seq].y_offset != 0 or glyph_positions[cluster_seq].y_advance != 0 ): force_positioning = True text_info.append( { "mapped_char": self.subset.pick_glyph(glyph), "x_advance": glyph_positions[cluster_seq].x_advance, "y_advance": glyph_positions[cluster_seq].y_advance, "x_offset": glyph_positions[cluster_seq].x_offset, "y_offset": glyph_positions[cluster_seq].y_offset, "force_positioning": force_positioning, } ) return text_info class PDFFontDescriptor(PDFObject): def __init__( self, ascent: float, descent: float, cap_height: float, flags: FontDescriptorFlags, font_b_box: str, italic_angle: int, stem_v: float, missing_width: float, ): super().__init__() self.type = Name("FontDescriptor") self.ascent = ascent self.descent = descent self.cap_height = cap_height self.flags = flags self.font_b_box = font_b_box self.italic_angle = italic_angle self.stem_v = stem_v self.missing_width = missing_width self.font_name: Optional[str] = None @dataclass(order=True) class Glyph: """ This represents one glyph on the font Unicode is a tuple because ligatures or character substitution can map a sequence of unicode characters to a single glyph """ # RAM usage optimization: __slots__ = ("glyph_id", "unicode", "glyph_name", "glyph_width") glyph_id: int unicode: int | tuple[int, ...] glyph_name: str glyph_width: int def __hash__(self) -> int: return self.glyph_id class SubsetMap: """ Holds a mapping of used characters and their position in the font's subset Characters that must be mapped on their actual unicode must be part of the `identities` list during object instantiation. These non-negative values should only appear once in the list. `pick()` can be used to get the characters corresponding position in the subset. If it's not yet part of the object, a new position is acquired automatically. This implementation always tries to return the lowest possible representation. """ def __init__(self, font: TTFFont) -> None: super().__init__() self.font = font self._next: int = 0 # 0x00 ".notdef" and 0x20 "space" are reserved self._reserved = [0x00, 0x20] # Maps Glyph instances to character IDs (integers): self._char_id_per_glyph: dict[Optional[Glyph], int] = {} for x in self._reserved: glyph = self.get_glyph(unicode=x) if glyph: self._char_id_per_glyph[glyph] = int(x) def __repr__(self) -> str: return ( f"SubsetMap(font={self.font}, _next={self._next}," f" _reserved={self._reserved}, _char_id_per_glyph={self._char_id_per_glyph})" ) def __len__(self) -> int: return len(self._char_id_per_glyph) def items(self) -> Iterator[tuple[Optional[Glyph], int]]: for glyph, char_id in self._char_id_per_glyph.items(): yield glyph, char_id # pylint: disable=method-cache-max-size-none @cache def pick(self, unicode: int) -> Optional[int]: glyph = self.get_glyph(unicode=unicode) if glyph is None and unicode not in self.font.missing_glyphs: self.font.missing_glyphs.append(unicode) return self.pick_glyph(glyph) def pick_glyph(self, glyph: Optional[Glyph]) -> Optional[int]: char_id = self._char_id_per_glyph.get(glyph, None) if glyph is not None and char_id is None: while self._next in self._reserved: self._next += 1 if self._next > self._reserved[0]: del self._reserved[0] char_id = self._next self._char_id_per_glyph[glyph] = char_id self._next += 1 return char_id # pylint: disable=method-cache-max-size-none @cache def get_glyph( self, glyph: Optional[int] = None, unicode: Optional[int | tuple[int, ...]] = None, glyph_name: Optional[str] = None, glyph_width: Optional[int] = None, ) -> Optional[Glyph]: if ( glyph and unicode is not None and glyph_name is not None and glyph_width is not None ): return Glyph(glyph, unicode, glyph_name, glyph_width) if unicode is None or not isinstance(unicode, int): return None if self.font.is_symbol and 0x20 <= unicode <= 0xFF and unicode != 0x7F: unicode = 0xF000 + unicode glyph_id = self.font.glyph_ids.get(unicode) if isinstance(unicode, int) and glyph_id is not None: return Glyph( glyph_id, (unicode,), self.font.cmap[unicode], int(self.font.cw[unicode]), ) if unicode == 0x00: glyph_id = next(iter(self.font.cmap)) return Glyph(glyph_id, (0x00,), ".notdef", 0) if unicode == 0x20: if 0x20 in self.font.cmap: glyph_name = self.font.cmap[0x20] glyph_id = self.font.glyph_ids.get(0x20, next(iter(self.font.cmap))) else: glyph_name = next(iter(self.font.cmap.values())) glyph_id = next(iter(self.font.cmap)) return Glyph(glyph_id, (0x20,), glyph_name, int(self.font.cw[0x20])) return None def get_all_glyph_names(self) -> Sequence[str]: return [ glyph.glyph_name for glyph in self._char_id_per_glyph if glyph is not None ] # Standard fonts CORE_FONTS = { "courier": "Courier", "courierB": "Courier-Bold", "courierI": "Courier-Oblique", "courierBI": "Courier-BoldOblique", "helvetica": "Helvetica", "helveticaB": "Helvetica-Bold", "helveticaI": "Helvetica-Oblique", "helveticaBI": "Helvetica-BoldOblique", "times": "Times-Roman", "timesB": "Times-Bold", "timesI": "Times-Italic", "timesBI": "Times-BoldItalic", "symbol": "Symbol", "zapfdingbats": "ZapfDingbats", } COURIER_FONT = {chr(i): 600 for i in range(256)} CORE_FONTS_CHARWIDTHS = { "courier": COURIER_FONT, "courierB": COURIER_FONT, "courierI": COURIER_FONT, "courierBI": COURIER_FONT, } CORE_FONTS_CHARWIDTHS["helvetica"] = { "\x00": 278, "\x01": 278, "\x02": 278, "\x03": 278, "\x04": 278, "\x05": 278, "\x06": 278, "\x07": 278, "\x08": 278, "\t": 278, "\n": 278, "\x0b": 278, "\x0c": 278, "\r": 278, "\x0e": 278, "\x0f": 278, "\x10": 278, "\x11": 278, "\x12": 278, "\x13": 278, "\x14": 278, "\x15": 278, "\x16": 278, "\x17": 278, "\x18": 278, "\x19": 278, "\x1a": 278, "\x1b": 278, "\x1c": 278, "\x1d": 278, "\x1e": 278, "\x1f": 278, " ": 278, "!": 278, '"': 355, "#": 556, "$": 556, "%": 889, "&": 667, "'": 191, "(": 333, ")": 333, "*": 389, "+": 584, ",": 278, "-": 333, ".": 278, "/": 278, "0": 556, "1": 556, "2": 556, "3": 556, "4": 556, "5": 556, "6": 556, "7": 556, "8": 556, "9": 556, ":": 278, ";": 278, "<": 584, "=": 584, ">": 584, "?": 556, "@": 1015, "A": 667, "B": 667, "C": 722, "D": 722, "E": 667, "F": 611, "G": 778, "H": 722, "I": 278, "J": 500, "K": 667, "L": 556, "M": 833, "N": 722, "O": 778, "P": 667, "Q": 778, "R": 722, "S": 667, "T": 611, "U": 722, "V": 667, "W": 944, "X": 667, "Y": 667, "Z": 611, "[": 278, "\\": 278, "]": 278, "^": 469, "_": 556, "`": 333, "a": 556, "b": 556, "c": 500, "d": 556, "e": 556, "f": 278, "g": 556, "h": 556, "i": 222, "j": 222, "k": 500, "l": 222, "m": 833, "n": 556, "o": 556, "p": 556, "q": 556, "r": 333, "s": 500, "t": 278, "u": 556, "v": 500, "w": 722, "x": 500, "y": 500, "z": 500, "{": 334, "|": 260, "}": 334, "~": 584, "\x7f": 350, "\x80": 556, "\x81": 350, "\x82": 222, "\x83": 556, "\x84": 333, "\x85": 1000, "\x86": 556, "\x87": 556, "\x88": 333, "\x89": 1000, "\x8a": 667, "\x8b": 333, "\x8c": 1000, "\x8d": 350, "\x8e": 611, "\x8f": 350, "\x90": 350, "\x91": 222, "\x92": 222, "\x93": 333, "\x94": 333, "\x95": 350, "\x96": 556, "\x97": 1000, "\x98": 333, "\x99": 1000, "\x9a": 500, "\x9b": 333, "\x9c": 944, "\x9d": 350, "\x9e": 500, "\x9f": 667, "\xa0": 278, "\xa1": 333, "\xa2": 556, "\xa3": 556, "\xa4": 556, "\xa5": 556, "\xa6": 260, "\xa7": 556, "\xa8": 333, "\xa9": 737, "\xaa": 370, "\xab": 556, "\xac": 584, "\xad": 333, "\xae": 737, "\xaf": 333, "\xb0": 400, "\xb1": 584, "\xb2": 333, "\xb3": 333, "\xb4": 333, "\xb5": 556, "\xb6": 537, "\xb7": 278, "\xb8": 333, "\xb9": 333, "\xba": 365, "\xbb": 556, "\xbc": 834, "\xbd": 834, "\xbe": 834, "\xbf": 611, "\xc0": 667, "\xc1": 667, "\xc2": 667, "\xc3": 667, "\xc4": 667, "\xc5": 667, "\xc6": 1000, "\xc7": 722, "\xc8": 667, "\xc9": 667, "\xca": 667, "\xcb": 667, "\xcc": 278, "\xcd": 278, "\xce": 278, "\xcf": 278, "\xd0": 722, "\xd1": 722, "\xd2": 778, "\xd3": 778, "\xd4": 778, "\xd5": 778, "\xd6": 778, "\xd7": 584, "\xd8": 778, "\xd9": 722, "\xda": 722, "\xdb": 722, "\xdc": 722, "\xdd": 667, "\xde": 667, "\xdf": 611, "\xe0": 556, "\xe1": 556, "\xe2": 556, "\xe3": 556, "\xe4": 556, "\xe5": 556, "\xe6": 889, "\xe7": 500, "\xe8": 556, "\xe9": 556, "\xea": 556, "\xeb": 556, "\xec": 278, "\xed": 278, "\xee": 278, "\xef": 278, "\xf0": 556, "\xf1": 556, "\xf2": 556, "\xf3": 556, "\xf4": 556, "\xf5": 556, "\xf6": 556, "\xf7": 584, "\xf8": 611, "\xf9": 556, "\xfa": 556, "\xfb": 556, "\xfc": 556, "\xfd": 500, "\xfe": 556, "\xff": 500, } CORE_FONTS_CHARWIDTHS["helveticaB"] = { "\x00": 278, "\x01": 278, "\x02": 278, "\x03": 278, "\x04": 278, "\x05": 278, "\x06": 278, "\x07": 278, "\x08": 278, "\t": 278, "\n": 278, "\x0b": 278, "\x0c": 278, "\r": 278, "\x0e": 278, "\x0f": 278, "\x10": 278, "\x11": 278, "\x12": 278, "\x13": 278, "\x14": 278, "\x15": 278, "\x16": 278, "\x17": 278, "\x18": 278, "\x19": 278, "\x1a": 278, "\x1b": 278, "\x1c": 278, "\x1d": 278, "\x1e": 278, "\x1f": 278, " ": 278, "!": 333, '"': 474, "#": 556, "$": 556, "%": 889, "&": 722, "'": 238, "(": 333, ")": 333, "*": 389, "+": 584, ",": 278, "-": 333, ".": 278, "/": 278, "0": 556, "1": 556, "2": 556, "3": 556, "4": 556, "5": 556, "6": 556, "7": 556, "8": 556, "9": 556, ":": 333, ";": 333, "<": 584, "=": 584, ">": 584, "?": 611, "@": 975, "A": 722, "B": 722, "C": 722, "D": 722, "E": 667, "F": 611, "G": 778, "H": 722, "I": 278, "J": 556, "K": 722, "L": 611, "M": 833, "N": 722, "O": 778, "P": 667, "Q": 778, "R": 722, "S": 667, "T": 611, "U": 722, "V": 667, "W": 944, "X": 667, "Y": 667, "Z": 611, "[": 333, "\\": 278, "]": 333, "^": 584, "_": 556, "`": 333, "a": 556, "b": 611, "c": 556, "d": 611, "e": 556, "f": 333, "g": 611, "h": 611, "i": 278, "j": 278, "k": 556, "l": 278, "m": 889, "n": 611, "o": 611, "p": 611, "q": 611, "r": 389, "s": 556, "t": 333, "u": 611, "v": 556, "w": 778, "x": 556, "y": 556, "z": 500, "{": 389, "|": 280, "}": 389, "~": 584, "\x7f": 350, "\x80": 556, "\x81": 350, "\x82": 278, "\x83": 556, "\x84": 500, "\x85": 1000, "\x86": 556, "\x87": 556, "\x88": 333, "\x89": 1000, "\x8a": 667, "\x8b": 333, "\x8c": 1000, "\x8d": 350, "\x8e": 611, "\x8f": 350, "\x90": 350, "\x91": 278, "\x92": 278, "\x93": 500, "\x94": 500, "\x95": 350, "\x96": 556, "\x97": 1000, "\x98": 333, "\x99": 1000, "\x9a": 556, "\x9b": 333, "\x9c": 944, "\x9d": 350, "\x9e": 500, "\x9f": 667, "\xa0": 278, "\xa1": 333, "\xa2": 556, "\xa3": 556, "\xa4": 556, "\xa5": 556, "\xa6": 280, "\xa7": 556, "\xa8": 333, "\xa9": 737, "\xaa": 370, "\xab": 556, "\xac": 584, "\xad": 333, "\xae": 737, "\xaf": 333, "\xb0": 400, "\xb1": 584, "\xb2": 333, "\xb3": 333, "\xb4": 333, "\xb5": 611, "\xb6": 556, "\xb7": 278, "\xb8": 333, "\xb9": 333, "\xba": 365, "\xbb": 556, "\xbc": 834, "\xbd": 834, "\xbe": 834, "\xbf": 611, "\xc0": 722, "\xc1": 722, "\xc2": 722, "\xc3": 722, "\xc4": 722, "\xc5": 722, "\xc6": 1000, "\xc7": 722, "\xc8": 667, "\xc9": 667, "\xca": 667, "\xcb": 667, "\xcc": 278, "\xcd": 278, "\xce": 278, "\xcf": 278, "\xd0": 722, "\xd1": 722, "\xd2": 778, "\xd3": 778, "\xd4": 778, "\xd5": 778, "\xd6": 778, "\xd7": 584, "\xd8": 778, "\xd9": 722, "\xda": 722, "\xdb": 722, "\xdc": 722, "\xdd": 667, "\xde": 667, "\xdf": 611, "\xe0": 556, "\xe1": 556, "\xe2": 556, "\xe3": 556, "\xe4": 556, "\xe5": 556, "\xe6": 889, "\xe7": 556, "\xe8": 556, "\xe9": 556, "\xea": 556, "\xeb": 556, "\xec": 278, "\xed": 278, "\xee": 278, "\xef": 278, "\xf0": 611, "\xf1": 611, "\xf2": 611, "\xf3": 611, "\xf4": 611, "\xf5": 611, "\xf6": 611, "\xf7": 584, "\xf8": 611, "\xf9": 611, "\xfa": 611, "\xfb": 611, "\xfc": 611, "\xfd": 556, "\xfe": 611, "\xff": 556, } CORE_FONTS_CHARWIDTHS["helveticaBI"] = { "\x00": 278, "\x01": 278, "\x02": 278, "\x03": 278, "\x04": 278, "\x05": 278, "\x06": 278, "\x07": 278, "\x08": 278, "\t": 278, "\n": 278, "\x0b": 278, "\x0c": 278, "\r": 278, "\x0e": 278, "\x0f": 278, "\x10": 278, "\x11": 278, "\x12": 278, "\x13": 278, "\x14": 278, "\x15": 278, "\x16": 278, "\x17": 278, "\x18": 278, "\x19": 278, "\x1a": 278, "\x1b": 278, "\x1c": 278, "\x1d": 278, "\x1e": 278, "\x1f": 278, " ": 278, "!": 333, '"': 474, "#": 556, "$": 556, "%": 889, "&": 722, "'": 238, "(": 333, ")": 333, "*": 389, "+": 584, ",": 278, "-": 333, ".": 278, "/": 278, "0": 556, "1": 556, "2": 556, "3": 556, "4": 556, "5": 556, "6": 556, "7": 556, "8": 556, "9": 556, ":": 333, ";": 333, "<": 584, "=": 584, ">": 584, "?": 611, "@": 975, "A": 722, "B": 722, "C": 722, "D": 722, "E": 667, "F": 611, "G": 778, "H": 722, "I": 278, "J": 556, "K": 722, "L": 611, "M": 833, "N": 722, "O": 778, "P": 667, "Q": 778, "R": 722, "S": 667, "T": 611, "U": 722, "V": 667, "W": 944, "X": 667, "Y": 667, "Z": 611, "[": 333, "\\": 278, "]": 333, "^": 584, "_": 556, "`": 333, "a": 556, "b": 611, "c": 556, "d": 611, "e": 556, "f": 333, "g": 611, "h": 611, "i": 278, "j": 278, "k": 556, "l": 278, "m": 889, "n": 611, "o": 611, "p": 611, "q": 611, "r": 389, "s": 556, "t": 333, "u": 611, "v": 556, "w": 778, "x": 556, "y": 556, "z": 500, "{": 389, "|": 280, "}": 389, "~": 584, "\x7f": 350, "\x80": 556, "\x81": 350, "\x82": 278, "\x83": 556, "\x84": 500, "\x85": 1000, "\x86": 556, "\x87": 556, "\x88": 333, "\x89": 1000, "\x8a": 667, "\x8b": 333, "\x8c": 1000, "\x8d": 350, "\x8e": 611, "\x8f": 350, "\x90": 350, "\x91": 278, "\x92": 278, "\x93": 500, "\x94": 500, "\x95": 350, "\x96": 556, "\x97": 1000, "\x98": 333, "\x99": 1000, "\x9a": 556, "\x9b": 333, "\x9c": 944, "\x9d": 350, "\x9e": 500, "\x9f": 667, "\xa0": 278, "\xa1": 333, "\xa2": 556, "\xa3": 556, "\xa4": 556, "\xa5": 556, "\xa6": 280, "\xa7": 556, "\xa8": 333, "\xa9": 737, "\xaa": 370, "\xab": 556, "\xac": 584, "\xad": 333, "\xae": 737, "\xaf": 333, "\xb0": 400, "\xb1": 584, "\xb2": 333, "\xb3": 333, "\xb4": 333, "\xb5": 611, "\xb6": 556, "\xb7": 278, "\xb8": 333, "\xb9": 333, "\xba": 365, "\xbb": 556, "\xbc": 834, "\xbd": 834, "\xbe": 834, "\xbf": 611, "\xc0": 722, "\xc1": 722, "\xc2": 722, "\xc3": 722, "\xc4": 722, "\xc5": 722, "\xc6": 1000, "\xc7": 722, "\xc8": 667, "\xc9": 667, "\xca": 667, "\xcb": 667, "\xcc": 278, "\xcd": 278, "\xce": 278, "\xcf": 278, "\xd0": 722, "\xd1": 722, "\xd2": 778, "\xd3": 778, "\xd4": 778, "\xd5": 778, "\xd6": 778, "\xd7": 584, "\xd8": 778, "\xd9": 722, "\xda": 722, "\xdb": 722, "\xdc": 722, "\xdd": 667, "\xde": 667, "\xdf": 611, "\xe0": 556, "\xe1": 556, "\xe2": 556, "\xe3": 556, "\xe4": 556, "\xe5": 556, "\xe6": 889, "\xe7": 556, "\xe8": 556, "\xe9": 556, "\xea": 556, "\xeb": 556, "\xec": 278, "\xed": 278, "\xee": 278, "\xef": 278, "\xf0": 611, "\xf1": 611, "\xf2": 611, "\xf3": 611, "\xf4": 611, "\xf5": 611, "\xf6": 611, "\xf7": 584, "\xf8": 611, "\xf9": 611, "\xfa": 611, "\xfb": 611, "\xfc": 611, "\xfd": 556, "\xfe": 611, "\xff": 556, } CORE_FONTS_CHARWIDTHS["helveticaI"] = { "\x00": 278, "\x01": 278, "\x02": 278, "\x03": 278, "\x04": 278, "\x05": 278, "\x06": 278, "\x07": 278, "\x08": 278, "\t": 278, "\n": 278, "\x0b": 278, "\x0c": 278, "\r": 278, "\x0e": 278, "\x0f": 278, "\x10": 278, "\x11": 278, "\x12": 278, "\x13": 278, "\x14": 278, "\x15": 278, "\x16": 278, "\x17": 278, "\x18": 278, "\x19": 278, "\x1a": 278, "\x1b": 278, "\x1c": 278, "\x1d": 278, "\x1e": 278, "\x1f": 278, " ": 278, "!": 278, '"': 355, "#": 556, "$": 556, "%": 889, "&": 667, "'": 191, "(": 333, ")": 333, "*": 389, "+": 584, ",": 278, "-": 333, ".": 278, "/": 278, "0": 556, "1": 556, "2": 556, "3": 556, "4": 556, "5": 556, "6": 556, "7": 556, "8": 556, "9": 556, ":": 278, ";": 278, "<": 584, "=": 584, ">": 584, "?": 556, "@": 1015, "A": 667, "B": 667, "C": 722, "D": 722, "E": 667, "F": 611, "G": 778, "H": 722, "I": 278, "J": 500, "K": 667, "L": 556, "M": 833, "N": 722, "O": 778, "P": 667, "Q": 778, "R": 722, "S": 667, "T": 611, "U": 722, "V": 667, "W": 944, "X": 667, "Y": 667, "Z": 611, "[": 278, "\\": 278, "]": 278, "^": 469, "_": 556, "`": 333, "a": 556, "b": 556, "c": 500, "d": 556, "e": 556, "f": 278, "g": 556, "h": 556, "i": 222, "j": 222, "k": 500, "l": 222, "m": 833, "n": 556, "o": 556, "p": 556, "q": 556, "r": 333, "s": 500, "t": 278, "u": 556, "v": 500, "w": 722, "x": 500, "y": 500, "z": 500, "{": 334, "|": 260, "}": 334, "~": 584, "\x7f": 350, "\x80": 556, "\x81": 350, "\x82": 222, "\x83": 556, "\x84": 333, "\x85": 1000, "\x86": 556, "\x87": 556, "\x88": 333, "\x89": 1000, "\x8a": 667, "\x8b": 333, "\x8c": 1000, "\x8d": 350, "\x8e": 611, "\x8f": 350, "\x90": 350, "\x91": 222, "\x92": 222, "\x93": 333, "\x94": 333, "\x95": 350, "\x96": 556, "\x97": 1000, "\x98": 333, "\x99": 1000, "\x9a": 500, "\x9b": 333, "\x9c": 944, "\x9d": 350, "\x9e": 500, "\x9f": 667, "\xa0": 278, "\xa1": 333, "\xa2": 556, "\xa3": 556, "\xa4": 556, "\xa5": 556, "\xa6": 260, "\xa7": 556, "\xa8": 333, "\xa9": 737, "\xaa": 370, "\xab": 556, "\xac": 584, "\xad": 333, "\xae": 737, "\xaf": 333, "\xb0": 400, "\xb1": 584, "\xb2": 333, "\xb3": 333, "\xb4": 333, "\xb5": 556, "\xb6": 537, "\xb7": 278, "\xb8": 333, "\xb9": 333, "\xba": 365, "\xbb": 556, "\xbc": 834, "\xbd": 834, "\xbe": 834, "\xbf": 611, "\xc0": 667, "\xc1": 667, "\xc2": 667, "\xc3": 667, "\xc4": 667, "\xc5": 667, "\xc6": 1000, "\xc7": 722, "\xc8": 667, "\xc9": 667, "\xca": 667, "\xcb": 667, "\xcc": 278, "\xcd": 278, "\xce": 278, "\xcf": 278, "\xd0": 722, "\xd1": 722, "\xd2": 778, "\xd3": 778, "\xd4": 778, "\xd5": 778, "\xd6": 778, "\xd7": 584, "\xd8": 778, "\xd9": 722, "\xda": 722, "\xdb": 722, "\xdc": 722, "\xdd": 667, "\xde": 667, "\xdf": 611, "\xe0": 556, "\xe1": 556, "\xe2": 556, "\xe3": 556, "\xe4": 556, "\xe5": 556, "\xe6": 889, "\xe7": 500, "\xe8": 556, "\xe9": 556, "\xea": 556, "\xeb": 556, "\xec": 278, "\xed": 278, "\xee": 278, "\xef": 278, "\xf0": 556, "\xf1": 556, "\xf2": 556, "\xf3": 556, "\xf4": 556, "\xf5": 556, "\xf6": 556, "\xf7": 584, "\xf8": 611, "\xf9": 556, "\xfa": 556, "\xfb": 556, "\xfc": 556, "\xfd": 500, "\xfe": 556, "\xff": 500, } CORE_FONTS_CHARWIDTHS["symbol"] = { "\x00": 250, "\x01": 250, "\x02": 250, "\x03": 250, "\x04": 250, "\x05": 250, "\x06": 250, "\x07": 250, "\x08": 250, "\t": 250, "\n": 250, "\x0b": 250, "\x0c": 250, "\r": 250, "\x0e": 250, "\x0f": 250, "\x10": 250, "\x11": 250, "\x12": 250, "\x13": 250, "\x14": 250, "\x15": 250, "\x16": 250, "\x17": 250, "\x18": 250, "\x19": 250, "\x1a": 250, "\x1b": 250, "\x1c": 250, "\x1d": 250, "\x1e": 250, "\x1f": 250, " ": 250, "!": 333, '"': 713, "#": 500, "$": 549, "%": 833, "&": 778, "'": 439, "(": 333, ")": 333, "*": 500, "+": 549, ",": 250, "-": 549, ".": 250, "/": 278, "0": 500, "1": 500, "2": 500, "3": 500, "4": 500, "5": 500, "6": 500, "7": 500, "8": 500, "9": 500, ":": 278, ";": 278, "<": 549, "=": 549, ">": 549, "?": 444, "@": 549, "A": 722, "B": 667, "C": 722, "D": 612, "E": 611, "F": 763, "G": 603, "H": 722, "I": 333, "J": 631, "K": 722, "L": 686, "M": 889, "N": 722, "O": 722, "P": 768, "Q": 741, "R": 556, "S": 592, "T": 611, "U": 690, "V": 439, "W": 768, "X": 645, "Y": 795, "Z": 611, "[": 333, "\\": 863, "]": 333, "^": 658, "_": 500, "`": 500, "a": 631, "b": 549, "c": 549, "d": 494, "e": 439, "f": 521, "g": 411, "h": 603, "i": 329, "j": 603, "k": 549, "l": 549, "m": 576, "n": 521, "o": 549, "p": 549, "q": 521, "r": 549, "s": 603, "t": 439, "u": 576, "v": 713, "w": 686, "x": 493, "y": 686, "z": 494, "{": 480, "|": 200, "}": 480, "~": 549, "\x7f": 0, "\x80": 0, "\x81": 0, "\x82": 0, "\x83": 0, "\x84": 0, "\x85": 0, "\x86": 0, "\x87": 0, "\x88": 0, "\x89": 0, "\x8a": 0, "\x8b": 0, "\x8c": 0, "\x8d": 0, "\x8e": 0, "\x8f": 0, "\x90": 0, "\x91": 0, "\x92": 0, "\x93": 0, "\x94": 0, "\x95": 0, "\x96": 0, "\x97": 0, "\x98": 0, "\x99": 0, "\x9a": 0, "\x9b": 0, "\x9c": 0, "\x9d": 0, "\x9e": 0, "\x9f": 0, "\xa0": 750, "\xa1": 620, "\xa2": 247, "\xa3": 549, "\xa4": 167, "\xa5": 713, "\xa6": 500, "\xa7": 753, "\xa8": 753, "\xa9": 753, "\xaa": 753, "\xab": 1042, "\xac": 987, "\xad": 603, "\xae": 987, "\xaf": 603, "\xb0": 400, "\xb1": 549, "\xb2": 411, "\xb3": 549, "\xb4": 549, "\xb5": 713, "\xb6": 494, "\xb7": 460, "\xb8": 549, "\xb9": 549, "\xba": 549, "\xbb": 549, "\xbc": 1000, "\xbd": 603, "\xbe": 1000, "\xbf": 658, "\xc0": 823, "\xc1": 686, "\xc2": 795, "\xc3": 987, "\xc4": 768, "\xc5": 768, "\xc6": 823, "\xc7": 768, "\xc8": 768, "\xc9": 713, "\xca": 713, "\xcb": 713, "\xcc": 713, "\xcd": 713, "\xce": 713, "\xcf": 713, "\xd0": 768, "\xd1": 713, "\xd2": 790, "\xd3": 790, "\xd4": 890, "\xd5": 823, "\xd6": 549, "\xd7": 250, "\xd8": 713, "\xd9": 603, "\xda": 603, "\xdb": 1042, "\xdc": 987, "\xdd": 603, "\xde": 987, "\xdf": 603, "\xe0": 494, "\xe1": 329, "\xe2": 790, "\xe3": 790, "\xe4": 786, "\xe5": 713, "\xe6": 384, "\xe7": 384, "\xe8": 384, "\xe9": 384, "\xea": 384, "\xeb": 384, "\xec": 494, "\xed": 494, "\xee": 494, "\xef": 494, "\xf0": 0, "\xf1": 329, "\xf2": 274, "\xf3": 686, "\xf4": 686, "\xf5": 686, "\xf6": 384, "\xf7": 384, "\xf8": 384, "\xf9": 384, "\xfa": 384, "\xfb": 384, "\xfc": 494, "\xfd": 494, "\xfe": 494, "\xff": 0, } CORE_FONTS_CHARWIDTHS["times"] = { "\x00": 250, "\x01": 250, "\x02": 250, "\x03": 250, "\x04": 250, "\x05": 250, "\x06": 250, "\x07": 250, "\x08": 250, "\t": 250, "\n": 250, "\x0b": 250, "\x0c": 250, "\r": 250, "\x0e": 250, "\x0f": 250, "\x10": 250, "\x11": 250, "\x12": 250, "\x13": 250, "\x14": 250, "\x15": 250, "\x16": 250, "\x17": 250, "\x18": 250, "\x19": 250, "\x1a": 250, "\x1b": 250, "\x1c": 250, "\x1d": 250, "\x1e": 250, "\x1f": 250, " ": 250, "!": 333, '"': 408, "#": 500, "$": 500, "%": 833, "&": 778, "'": 180, "(": 333, ")": 333, "*": 500, "+": 564, ",": 250, "-": 333, ".": 250, "/": 278, "0": 500, "1": 500, "2": 500, "3": 500, "4": 500, "5": 500, "6": 500, "7": 500, "8": 500, "9": 500, ":": 278, ";": 278, "<": 564, "=": 564, ">": 564, "?": 444, "@": 921, "A": 722, "B": 667, "C": 667, "D": 722, "E": 611, "F": 556, "G": 722, "H": 722, "I": 333, "J": 389, "K": 722, "L": 611, "M": 889, "N": 722, "O": 722, "P": 556, "Q": 722, "R": 667, "S": 556, "T": 611, "U": 722, "V": 722, "W": 944, "X": 722, "Y": 722, "Z": 611, "[": 333, "\\": 278, "]": 333, "^": 469, "_": 500, "`": 333, "a": 444, "b": 500, "c": 444, "d": 500, "e": 444, "f": 333, "g": 500, "h": 500, "i": 278, "j": 278, "k": 500, "l": 278, "m": 778, "n": 500, "o": 500, "p": 500, "q": 500, "r": 333, "s": 389, "t": 278, "u": 500, "v": 500, "w": 722, "x": 500, "y": 500, "z": 444, "{": 480, "|": 200, "}": 480, "~": 541, "\x7f": 350, "\x80": 500, "\x81": 350, "\x82": 333, "\x83": 500, "\x84": 444, "\x85": 1000, "\x86": 500, "\x87": 500, "\x88": 333, "\x89": 1000, "\x8a": 556, "\x8b": 333, "\x8c": 889, "\x8d": 350, "\x8e": 611, "\x8f": 350, "\x90": 350, "\x91": 333, "\x92": 333, "\x93": 444, "\x94": 444, "\x95": 350, "\x96": 500, "\x97": 1000, "\x98": 333, "\x99": 980, "\x9a": 389, "\x9b": 333, "\x9c": 722, "\x9d": 350, "\x9e": 444, "\x9f": 722, "\xa0": 250, "\xa1": 333, "\xa2": 500, "\xa3": 500, "\xa4": 500, "\xa5": 500, "\xa6": 200, "\xa7": 500, "\xa8": 333, "\xa9": 760, "\xaa": 276, "\xab": 500, "\xac": 564, "\xad": 333, "\xae": 760, "\xaf": 333, "\xb0": 400, "\xb1": 564, "\xb2": 300, "\xb3": 300, "\xb4": 333, "\xb5": 500, "\xb6": 453, "\xb7": 250, "\xb8": 333, "\xb9": 300, "\xba": 310, "\xbb": 500, "\xbc": 750, "\xbd": 750, "\xbe": 750, "\xbf": 444, "\xc0": 722, "\xc1": 722, "\xc2": 722, "\xc3": 722, "\xc4": 722, "\xc5": 722, "\xc6": 889, "\xc7": 667, "\xc8": 611, "\xc9": 611, "\xca": 611, "\xcb": 611, "\xcc": 333, "\xcd": 333, "\xce": 333, "\xcf": 333, "\xd0": 722, "\xd1": 722, "\xd2": 722, "\xd3": 722, "\xd4": 722, "\xd5": 722, "\xd6": 722, "\xd7": 564, "\xd8": 722, "\xd9": 722, "\xda": 722, "\xdb": 722, "\xdc": 722, "\xdd": 722, "\xde": 556, "\xdf": 500, "\xe0": 444, "\xe1": 444, "\xe2": 444, "\xe3": 444, "\xe4": 444, "\xe5": 444, "\xe6": 667, "\xe7": 444, "\xe8": 444, "\xe9": 444, "\xea": 444, "\xeb": 444, "\xec": 278, "\xed": 278, "\xee": 278, "\xef": 278, "\xf0": 500, "\xf1": 500, "\xf2": 500, "\xf3": 500, "\xf4": 500, "\xf5": 500, "\xf6": 500, "\xf7": 564, "\xf8": 500, "\xf9": 500, "\xfa": 500, "\xfb": 500, "\xfc": 500, "\xfd": 500, "\xfe": 500, "\xff": 500, } CORE_FONTS_CHARWIDTHS["timesB"] = { "\x00": 250, "\x01": 250, "\x02": 250, "\x03": 250, "\x04": 250, "\x05": 250, "\x06": 250, "\x07": 250, "\x08": 250, "\t": 250, "\n": 250, "\x0b": 250, "\x0c": 250, "\r": 250, "\x0e": 250, "\x0f": 250, "\x10": 250, "\x11": 250, "\x12": 250, "\x13": 250, "\x14": 250, "\x15": 250, "\x16": 250, "\x17": 250, "\x18": 250, "\x19": 250, "\x1a": 250, "\x1b": 250, "\x1c": 250, "\x1d": 250, "\x1e": 250, "\x1f": 250, " ": 250, "!": 333, '"': 555, "#": 500, "$": 500, "%": 1000, "&": 833, "'": 278, "(": 333, ")": 333, "*": 500, "+": 570, ",": 250, "-": 333, ".": 250, "/": 278, "0": 500, "1": 500, "2": 500, "3": 500, "4": 500, "5": 500, "6": 500, "7": 500, "8": 500, "9": 500, ":": 333, ";": 333, "<": 570, "=": 570, ">": 570, "?": 500, "@": 930, "A": 722, "B": 667, "C": 722, "D": 722, "E": 667, "F": 611, "G": 778, "H": 778, "I": 389, "J": 500, "K": 778, "L": 667, "M": 944, "N": 722, "O": 778, "P": 611, "Q": 778, "R": 722, "S": 556, "T": 667, "U": 722, "V": 722, "W": 1000, "X": 722, "Y": 722, "Z": 667, "[": 333, "\\": 278, "]": 333, "^": 581, "_": 500, "`": 333, "a": 500, "b": 556, "c": 444, "d": 556, "e": 444, "f": 333, "g": 500, "h": 556, "i": 278, "j": 333, "k": 556, "l": 278, "m": 833, "n": 556, "o": 500, "p": 556, "q": 556, "r": 444, "s": 389, "t": 333, "u": 556, "v": 500, "w": 722, "x": 500, "y": 500, "z": 444, "{": 394, "|": 220, "}": 394, "~": 520, "\x7f": 350, "\x80": 500, "\x81": 350, "\x82": 333, "\x83": 500, "\x84": 500, "\x85": 1000, "\x86": 500, "\x87": 500, "\x88": 333, "\x89": 1000, "\x8a": 556, "\x8b": 333, "\x8c": 1000, "\x8d": 350, "\x8e": 667, "\x8f": 350, "\x90": 350, "\x91": 333, "\x92": 333, "\x93": 500, "\x94": 500, "\x95": 350, "\x96": 500, "\x97": 1000, "\x98": 333, "\x99": 1000, "\x9a": 389, "\x9b": 333, "\x9c": 722, "\x9d": 350, "\x9e": 444, "\x9f": 722, "\xa0": 250, "\xa1": 333, "\xa2": 500, "\xa3": 500, "\xa4": 500, "\xa5": 500, "\xa6": 220, "\xa7": 500, "\xa8": 333, "\xa9": 747, "\xaa": 300, "\xab": 500, "\xac": 570, "\xad": 333, "\xae": 747, "\xaf": 333, "\xb0": 400, "\xb1": 570, "\xb2": 300, "\xb3": 300, "\xb4": 333, "\xb5": 556, "\xb6": 540, "\xb7": 250, "\xb8": 333, "\xb9": 300, "\xba": 330, "\xbb": 500, "\xbc": 750, "\xbd": 750, "\xbe": 750, "\xbf": 500, "\xc0": 722, "\xc1": 722, "\xc2": 722, "\xc3": 722, "\xc4": 722, "\xc5": 722, "\xc6": 1000, "\xc7": 722, "\xc8": 667, "\xc9": 667, "\xca": 667, "\xcb": 667, "\xcc": 389, "\xcd": 389, "\xce": 389, "\xcf": 389, "\xd0": 722, "\xd1": 722, "\xd2": 778, "\xd3": 778, "\xd4": 778, "\xd5": 778, "\xd6": 778, "\xd7": 570, "\xd8": 778, "\xd9": 722, "\xda": 722, "\xdb": 722, "\xdc": 722, "\xdd": 722, "\xde": 611, "\xdf": 556, "\xe0": 500, "\xe1": 500, "\xe2": 500, "\xe3": 500, "\xe4": 500, "\xe5": 500, "\xe6": 722, "\xe7": 444, "\xe8": 444, "\xe9": 444, "\xea": 444, "\xeb": 444, "\xec": 278, "\xed": 278, "\xee": 278, "\xef": 278, "\xf0": 500, "\xf1": 556, "\xf2": 500, "\xf3": 500, "\xf4": 500, "\xf5": 500, "\xf6": 500, "\xf7": 570, "\xf8": 500, "\xf9": 556, "\xfa": 556, "\xfb": 556, "\xfc": 556, "\xfd": 500, "\xfe": 556, "\xff": 500, } CORE_FONTS_CHARWIDTHS["timesBI"] = { "\x00": 250, "\x01": 250, "\x02": 250, "\x03": 250, "\x04": 250, "\x05": 250, "\x06": 250, "\x07": 250, "\x08": 250, "\t": 250, "\n": 250, "\x0b": 250, "\x0c": 250, "\r": 250, "\x0e": 250, "\x0f": 250, "\x10": 250, "\x11": 250, "\x12": 250, "\x13": 250, "\x14": 250, "\x15": 250, "\x16": 250, "\x17": 250, "\x18": 250, "\x19": 250, "\x1a": 250, "\x1b": 250, "\x1c": 250, "\x1d": 250, "\x1e": 250, "\x1f": 250, " ": 250, "!": 389, '"': 555, "#": 500, "$": 500, "%": 833, "&": 778, "'": 278, "(": 333, ")": 333, "*": 500, "+": 570, ",": 250, "-": 333, ".": 250, "/": 278, "0": 500, "1": 500, "2": 500, "3": 500, "4": 500, "5": 500, "6": 500, "7": 500, "8": 500, "9": 500, ":": 333, ";": 333, "<": 570, "=": 570, ">": 570, "?": 500, "@": 832, "A": 667, "B": 667, "C": 667, "D": 722, "E": 667, "F": 667, "G": 722, "H": 778, "I": 389, "J": 500, "K": 667, "L": 611, "M": 889, "N": 722, "O": 722, "P": 611, "Q": 722, "R": 667, "S": 556, "T": 611, "U": 722, "V": 667, "W": 889, "X": 667, "Y": 611, "Z": 611, "[": 333, "\\": 278, "]": 333, "^": 570, "_": 500, "`": 333, "a": 500, "b": 500, "c": 444, "d": 500, "e": 444, "f": 333, "g": 500, "h": 556, "i": 278, "j": 278, "k": 500, "l": 278, "m": 778, "n": 556, "o": 500, "p": 500, "q": 500, "r": 389, "s": 389, "t": 278, "u": 556, "v": 444, "w": 667, "x": 500, "y": 444, "z": 389, "{": 348, "|": 220, "}": 348, "~": 570, "\x7f": 350, "\x80": 500, "\x81": 350, "\x82": 333, "\x83": 500, "\x84": 500, "\x85": 1000, "\x86": 500, "\x87": 500, "\x88": 333, "\x89": 1000, "\x8a": 556, "\x8b": 333, "\x8c": 944, "\x8d": 350, "\x8e": 611, "\x8f": 350, "\x90": 350, "\x91": 333, "\x92": 333, "\x93": 500, "\x94": 500, "\x95": 350, "\x96": 500, "\x97": 1000, "\x98": 333, "\x99": 1000, "\x9a": 389, "\x9b": 333, "\x9c": 722, "\x9d": 350, "\x9e": 389, "\x9f": 611, "\xa0": 250, "\xa1": 389, "\xa2": 500, "\xa3": 500, "\xa4": 500, "\xa5": 500, "\xa6": 220, "\xa7": 500, "\xa8": 333, "\xa9": 747, "\xaa": 266, "\xab": 500, "\xac": 606, "\xad": 333, "\xae": 747, "\xaf": 333, "\xb0": 400, "\xb1": 570, "\xb2": 300, "\xb3": 300, "\xb4": 333, "\xb5": 576, "\xb6": 500, "\xb7": 250, "\xb8": 333, "\xb9": 300, "\xba": 300, "\xbb": 500, "\xbc": 750, "\xbd": 750, "\xbe": 750, "\xbf": 500, "\xc0": 667, "\xc1": 667, "\xc2": 667, "\xc3": 667, "\xc4": 667, "\xc5": 667, "\xc6": 944, "\xc7": 667, "\xc8": 667, "\xc9": 667, "\xca": 667, "\xcb": 667, "\xcc": 389, "\xcd": 389, "\xce": 389, "\xcf": 389, "\xd0": 722, "\xd1": 722, "\xd2": 722, "\xd3": 722, "\xd4": 722, "\xd5": 722, "\xd6": 722, "\xd7": 570, "\xd8": 722, "\xd9": 722, "\xda": 722, "\xdb": 722, "\xdc": 722, "\xdd": 611, "\xde": 611, "\xdf": 500, "\xe0": 500, "\xe1": 500, "\xe2": 500, "\xe3": 500, "\xe4": 500, "\xe5": 500, "\xe6": 722, "\xe7": 444, "\xe8": 444, "\xe9": 444, "\xea": 444, "\xeb": 444, "\xec": 278, "\xed": 278, "\xee": 278, "\xef": 278, "\xf0": 500, "\xf1": 556, "\xf2": 500, "\xf3": 500, "\xf4": 500, "\xf5": 500, "\xf6": 500, "\xf7": 570, "\xf8": 500, "\xf9": 556, "\xfa": 556, "\xfb": 556, "\xfc": 556, "\xfd": 444, "\xfe": 500, "\xff": 444, } CORE_FONTS_CHARWIDTHS["timesI"] = { "\x00": 250, "\x01": 250, "\x02": 250, "\x03": 250, "\x04": 250, "\x05": 250, "\x06": 250, "\x07": 250, "\x08": 250, "\t": 250, "\n": 250, "\x0b": 250, "\x0c": 250, "\r": 250, "\x0e": 250, "\x0f": 250, "\x10": 250, "\x11": 250, "\x12": 250, "\x13": 250, "\x14": 250, "\x15": 250, "\x16": 250, "\x17": 250, "\x18": 250, "\x19": 250, "\x1a": 250, "\x1b": 250, "\x1c": 250, "\x1d": 250, "\x1e": 250, "\x1f": 250, " ": 250, "!": 333, '"': 420, "#": 500, "$": 500, "%": 833, "&": 778, "'": 214, "(": 333, ")": 333, "*": 500, "+": 675, ",": 250, "-": 333, ".": 250, "/": 278, "0": 500, "1": 500, "2": 500, "3": 500, "4": 500, "5": 500, "6": 500, "7": 500, "8": 500, "9": 500, ":": 333, ";": 333, "<": 675, "=": 675, ">": 675, "?": 500, "@": 920, "A": 611, "B": 611, "C": 667, "D": 722, "E": 611, "F": 611, "G": 722, "H": 722, "I": 333, "J": 444, "K": 667, "L": 556, "M": 833, "N": 667, "O": 722, "P": 611, "Q": 722, "R": 611, "S": 500, "T": 556, "U": 722, "V": 611, "W": 833, "X": 611, "Y": 556, "Z": 556, "[": 389, "\\": 278, "]": 389, "^": 422, "_": 500, "`": 333, "a": 500, "b": 500, "c": 444, "d": 500, "e": 444, "f": 278, "g": 500, "h": 500, "i": 278, "j": 278, "k": 444, "l": 278, "m": 722, "n": 500, "o": 500, "p": 500, "q": 500, "r": 389, "s": 389, "t": 278, "u": 500, "v": 444, "w": 667, "x": 444, "y": 444, "z": 389, "{": 400, "|": 275, "}": 400, "~": 541, "\x7f": 350, "\x80": 500, "\x81": 350, "\x82": 333, "\x83": 500, "\x84": 556, "\x85": 889, "\x86": 500, "\x87": 500, "\x88": 333, "\x89": 1000, "\x8a": 500, "\x8b": 333, "\x8c": 944, "\x8d": 350, "\x8e": 556, "\x8f": 350, "\x90": 350, "\x91": 333, "\x92": 333, "\x93": 556, "\x94": 556, "\x95": 350, "\x96": 500, "\x97": 889, "\x98": 333, "\x99": 980, "\x9a": 389, "\x9b": 333, "\x9c": 667, "\x9d": 350, "\x9e": 389, "\x9f": 556, "\xa0": 250, "\xa1": 389, "\xa2": 500, "\xa3": 500, "\xa4": 500, "\xa5": 500, "\xa6": 275, "\xa7": 500, "\xa8": 333, "\xa9": 760, "\xaa": 276, "\xab": 500, "\xac": 675, "\xad": 333, "\xae": 760, "\xaf": 333, "\xb0": 400, "\xb1": 675, "\xb2": 300, "\xb3": 300, "\xb4": 333, "\xb5": 500, "\xb6": 523, "\xb7": 250, "\xb8": 333, "\xb9": 300, "\xba": 310, "\xbb": 500, "\xbc": 750, "\xbd": 750, "\xbe": 750, "\xbf": 500, "\xc0": 611, "\xc1": 611, "\xc2": 611, "\xc3": 611, "\xc4": 611, "\xc5": 611, "\xc6": 889, "\xc7": 667, "\xc8": 611, "\xc9": 611, "\xca": 611, "\xcb": 611, "\xcc": 333, "\xcd": 333, "\xce": 333, "\xcf": 333, "\xd0": 722, "\xd1": 667, "\xd2": 722, "\xd3": 722, "\xd4": 722, "\xd5": 722, "\xd6": 722, "\xd7": 675, "\xd8": 722, "\xd9": 722, "\xda": 722, "\xdb": 722, "\xdc": 722, "\xdd": 556, "\xde": 611, "\xdf": 500, "\xe0": 500, "\xe1": 500, "\xe2": 500, "\xe3": 500, "\xe4": 500, "\xe5": 500, "\xe6": 667, "\xe7": 444, "\xe8": 444, "\xe9": 444, "\xea": 444, "\xeb": 444, "\xec": 278, "\xed": 278, "\xee": 278, "\xef": 278, "\xf0": 500, "\xf1": 500, "\xf2": 500, "\xf3": 500, "\xf4": 500, "\xf5": 500, "\xf6": 500, "\xf7": 675, "\xf8": 500, "\xf9": 500, "\xfa": 500, "\xfb": 500, "\xfc": 500, "\xfd": 444, "\xfe": 500, "\xff": 444, } CORE_FONTS_CHARWIDTHS["zapfdingbats"] = { "\x00": 0, "\x01": 0, "\x02": 0, "\x03": 0, "\x04": 0, "\x05": 0, "\x06": 0, "\x07": 0, "\x08": 0, "\t": 0, "\n": 0, "\x0b": 0, "\x0c": 0, "\r": 0, "\x0e": 0, "\x0f": 0, "\x10": 0, "\x11": 0, "\x12": 0, "\x13": 0, "\x14": 0, "\x15": 0, "\x16": 0, "\x17": 0, "\x18": 0, "\x19": 0, "\x1a": 0, "\x1b": 0, "\x1c": 0, "\x1d": 0, "\x1e": 0, "\x1f": 0, " ": 278, "!": 974, '"': 961, "#": 974, "$": 980, "%": 719, "&": 789, "'": 790, "(": 791, ")": 690, "*": 960, "+": 939, ",": 549, "-": 855, ".": 911, "/": 933, "0": 911, "1": 945, "2": 974, "3": 755, "4": 846, "5": 762, "6": 761, "7": 571, "8": 677, "9": 763, ":": 760, ";": 759, "<": 754, "=": 494, ">": 552, "?": 537, "@": 577, "A": 692, "B": 786, "C": 788, "D": 788, "E": 790, "F": 793, "G": 794, "H": 816, "I": 823, "J": 789, "K": 841, "L": 823, "M": 833, "N": 816, "O": 831, "P": 923, "Q": 744, "R": 723, "S": 749, "T": 790, "U": 792, "V": 695, "W": 776, "X": 768, "Y": 792, "Z": 759, "[": 707, "\\": 708, "]": 682, "^": 701, "_": 826, "`": 815, "a": 789, "b": 789, "c": 707, "d": 687, "e": 696, "f": 689, "g": 786, "h": 787, "i": 713, "j": 791, "k": 785, "l": 791, "m": 873, "n": 761, "o": 762, "p": 762, "q": 759, "r": 759, "s": 892, "t": 892, "u": 788, "v": 784, "w": 438, "x": 138, "y": 277, "z": 415, "{": 392, "|": 392, "}": 668, "~": 668, "\x7f": 0, "\x80": 390, "\x81": 390, "\x82": 317, "\x83": 317, "\x84": 276, "\x85": 276, "\x86": 509, "\x87": 509, "\x88": 410, "\x89": 410, "\x8a": 234, "\x8b": 234, "\x8c": 334, "\x8d": 334, "\x8e": 0, "\x8f": 0, "\x90": 0, "\x91": 0, "\x92": 0, "\x93": 0, "\x94": 0, "\x95": 0, "\x96": 0, "\x97": 0, "\x98": 0, "\x99": 0, "\x9a": 0, "\x9b": 0, "\x9c": 0, "\x9d": 0, "\x9e": 0, "\x9f": 0, "\xa0": 0, "\xa1": 732, "\xa2": 544, "\xa3": 544, "\xa4": 910, "\xa5": 667, "\xa6": 760, "\xa7": 760, "\xa8": 776, "\xa9": 595, "\xaa": 694, "\xab": 626, "\xac": 788, "\xad": 788, "\xae": 788, "\xaf": 788, "\xb0": 788, "\xb1": 788, "\xb2": 788, "\xb3": 788, "\xb4": 788, "\xb5": 788, "\xb6": 788, "\xb7": 788, "\xb8": 788, "\xb9": 788, "\xba": 788, "\xbb": 788, "\xbc": 788, "\xbd": 788, "\xbe": 788, "\xbf": 788, "\xc0": 788, "\xc1": 788, "\xc2": 788, "\xc3": 788, "\xc4": 788, "\xc5": 788, "\xc6": 788, "\xc7": 788, "\xc8": 788, "\xc9": 788, "\xca": 788, "\xcb": 788, "\xcc": 788, "\xcd": 788, "\xce": 788, "\xcf": 788, "\xd0": 788, "\xd1": 788, "\xd2": 788, "\xd3": 788, "\xd4": 894, "\xd5": 838, "\xd6": 1016, "\xd7": 458, "\xd8": 748, "\xd9": 924, "\xda": 748, "\xdb": 918, "\xdc": 927, "\xdd": 928, "\xde": 928, "\xdf": 834, "\xe0": 873, "\xe1": 828, "\xe2": 924, "\xe3": 924, "\xe4": 917, "\xe5": 930, "\xe6": 931, "\xe7": 463, "\xe8": 883, "\xe9": 836, "\xea": 836, "\xeb": 867, "\xec": 867, "\xed": 696, "\xee": 696, "\xef": 874, "\xf0": 0, "\xf1": 874, "\xf2": 760, "\xf3": 946, "\xf4": 771, "\xf5": 865, "\xf6": 771, "\xf7": 888, "\xf8": 967, "\xf9": 888, "\xfa": 831, "\xfb": 873, "\xfc": 927, "\xfd": 970, "\xfe": 918, "\xff": 0, }