Buckets:
| # | |
| # THIS IS WORK IN PROGRESS | |
| # | |
| # The Python Imaging Library | |
| # $Id$ | |
| # | |
| # portable compiled font file parser | |
| # | |
| # history: | |
| # 1997-08-19 fl created | |
| # 2003-09-13 fl fixed loading of unicode fonts | |
| # | |
| # Copyright (c) 1997-2003 by Secret Labs AB. | |
| # Copyright (c) 1997-2003 by Fredrik Lundh. | |
| # | |
| # See the README file for information on usage and redistribution. | |
| # | |
| from __future__ import annotations | |
| import io | |
| from . import FontFile, Image | |
| from ._binary import i8 | |
| from ._binary import i16be as b16 | |
| from ._binary import i16le as l16 | |
| from ._binary import i32be as b32 | |
| from ._binary import i32le as l32 | |
| TYPE_CHECKING = False | |
| if TYPE_CHECKING: | |
| from collections.abc import Callable | |
| from typing import BinaryIO | |
| # -------------------------------------------------------------------- | |
| # declarations | |
| PCF_MAGIC = 0x70636601 # "\x01fcp" | |
| PCF_PROPERTIES = 1 << 0 | |
| PCF_ACCELERATORS = 1 << 1 | |
| PCF_METRICS = 1 << 2 | |
| PCF_BITMAPS = 1 << 3 | |
| PCF_INK_METRICS = 1 << 4 | |
| PCF_BDF_ENCODINGS = 1 << 5 | |
| PCF_SWIDTHS = 1 << 6 | |
| PCF_GLYPH_NAMES = 1 << 7 | |
| PCF_BDF_ACCELERATORS = 1 << 8 | |
| BYTES_PER_ROW: list[Callable[[int], int]] = [ | |
| lambda bits: ((bits + 7) >> 3), | |
| lambda bits: ((bits + 15) >> 3) & ~1, | |
| lambda bits: ((bits + 31) >> 3) & ~3, | |
| lambda bits: ((bits + 63) >> 3) & ~7, | |
| ] | |
| def sz(s: bytes, o: int) -> bytes: | |
| return s[o : s.index(b"\0", o)] | |
| class PcfFontFile(FontFile.FontFile): | |
| """Font file plugin for the X11 PCF format.""" | |
| name = "name" | |
| def __init__(self, fp: BinaryIO, charset_encoding: str = "iso8859-1"): | |
| self.charset_encoding = charset_encoding | |
| magic = l32(fp.read(4)) | |
| if magic != PCF_MAGIC: | |
| msg = "not a PCF file" | |
| raise SyntaxError(msg) | |
| super().__init__() | |
| count = l32(fp.read(4)) | |
| self.toc = {} | |
| for i in range(count): | |
| type = l32(fp.read(4)) | |
| self.toc[type] = l32(fp.read(4)), l32(fp.read(4)), l32(fp.read(4)) | |
| self.fp = fp | |
| self.info = self._load_properties() | |
| metrics = self._load_metrics() | |
| bitmaps = self._load_bitmaps(metrics) | |
| encoding = self._load_encoding() | |
| # | |
| # create glyph structure | |
| for ch, ix in enumerate(encoding): | |
| if ix is not None: | |
| ( | |
| xsize, | |
| ysize, | |
| left, | |
| right, | |
| width, | |
| ascent, | |
| descent, | |
| attributes, | |
| ) = metrics[ix] | |
| self.glyph[ch] = ( | |
| (width, 0), | |
| (left, descent - ysize, xsize + left, descent), | |
| (0, 0, xsize, ysize), | |
| bitmaps[ix], | |
| ) | |
| def _getformat( | |
| self, tag: int | |
| ) -> tuple[BinaryIO, int, Callable[[bytes], int], Callable[[bytes], int]]: | |
| format, size, offset = self.toc[tag] | |
| fp = self.fp | |
| fp.seek(offset) | |
| format = l32(fp.read(4)) | |
| if format & 4: | |
| i16, i32 = b16, b32 | |
| else: | |
| i16, i32 = l16, l32 | |
| return fp, format, i16, i32 | |
| def _load_properties(self) -> dict[bytes, bytes | int]: | |
| # | |
| # font properties | |
| properties = {} | |
| fp, format, i16, i32 = self._getformat(PCF_PROPERTIES) | |
| nprops = i32(fp.read(4)) | |
| # read property description | |
| p = [(i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))) for _ in range(nprops)] | |
| if nprops & 3: | |
| fp.seek(4 - (nprops & 3), io.SEEK_CUR) # pad | |
| data = fp.read(i32(fp.read(4))) | |
| for k, s, v in p: | |
| property_value: bytes | int = sz(data, v) if s else v | |
| properties[sz(data, k)] = property_value | |
| return properties | |
| def _load_metrics(self) -> list[tuple[int, int, int, int, int, int, int, int]]: | |
| # | |
| # font metrics | |
| metrics: list[tuple[int, int, int, int, int, int, int, int]] = [] | |
| fp, format, i16, i32 = self._getformat(PCF_METRICS) | |
| append = metrics.append | |
| if (format & 0xFF00) == 0x100: | |
| # "compressed" metrics | |
| for i in range(i16(fp.read(2))): | |
| left = i8(fp.read(1)) - 128 | |
| right = i8(fp.read(1)) - 128 | |
| width = i8(fp.read(1)) - 128 | |
| ascent = i8(fp.read(1)) - 128 | |
| descent = i8(fp.read(1)) - 128 | |
| xsize = right - left | |
| ysize = ascent + descent | |
| append((xsize, ysize, left, right, width, ascent, descent, 0)) | |
| else: | |
| # "jumbo" metrics | |
| for i in range(i32(fp.read(4))): | |
| left = i16(fp.read(2)) | |
| right = i16(fp.read(2)) | |
| width = i16(fp.read(2)) | |
| ascent = i16(fp.read(2)) | |
| descent = i16(fp.read(2)) | |
| attributes = i16(fp.read(2)) | |
| xsize = right - left | |
| ysize = ascent + descent | |
| append((xsize, ysize, left, right, width, ascent, descent, attributes)) | |
| return metrics | |
| def _load_bitmaps( | |
| self, metrics: list[tuple[int, int, int, int, int, int, int, int]] | |
| ) -> list[Image.Image]: | |
| # | |
| # bitmap data | |
| fp, format, i16, i32 = self._getformat(PCF_BITMAPS) | |
| nbitmaps = i32(fp.read(4)) | |
| if nbitmaps != len(metrics): | |
| msg = "Wrong number of bitmaps" | |
| raise OSError(msg) | |
| offsets = [i32(fp.read(4)) for _ in range(nbitmaps)] | |
| bitmap_sizes = [i32(fp.read(4)) for _ in range(4)] | |
| # byteorder = format & 4 # non-zero => MSB | |
| bitorder = format & 8 # non-zero => MSB | |
| padindex = format & 3 | |
| bitmapsize = bitmap_sizes[padindex] | |
| offsets.append(bitmapsize) | |
| data = fp.read(bitmapsize) | |
| pad = BYTES_PER_ROW[padindex] | |
| mode = "1;R" | |
| if bitorder: | |
| mode = "1" | |
| bitmaps = [] | |
| for i in range(nbitmaps): | |
| xsize, ysize = metrics[i][:2] | |
| b, e = offsets[i : i + 2] | |
| bitmaps.append( | |
| Image.frombytes("1", (xsize, ysize), data[b:e], "raw", mode, pad(xsize)) | |
| ) | |
| return bitmaps | |
| def _load_encoding(self) -> list[int | None]: | |
| fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS) | |
| first_col, last_col = i16(fp.read(2)), i16(fp.read(2)) | |
| first_row, last_row = i16(fp.read(2)), i16(fp.read(2)) | |
| i16(fp.read(2)) # default | |
| nencoding = (last_col - first_col + 1) * (last_row - first_row + 1) | |
| # map character code to bitmap index | |
| encoding: list[int | None] = [None] * min(256, nencoding) | |
| encoding_offsets = [i16(fp.read(2)) for _ in range(nencoding)] | |
| for i in range(first_col, len(encoding)): | |
| try: | |
| encoding_offset = encoding_offsets[ | |
| ord(bytearray([i]).decode(self.charset_encoding)) | |
| ] | |
| if encoding_offset != 0xFFFF: | |
| encoding[i] = encoding_offset | |
| except UnicodeDecodeError: | |
| # character is not supported in selected encoding | |
| pass | |
| return encoding | |
Xet Storage Details
- Size:
- 7.22 kB
- Xet hash:
- 9cc516a40cc8654c2f544f711e0e3f1eb3ccfa6bf2aa1bd8ae6106b555264e77
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.