From beb279f9f04b88598d8a1870995713c837e0a8ea Mon Sep 17 00:00:00 2001 From: TakWolf Date: Sun, 12 Jan 2025 02:49:39 +0800 Subject: [PATCH] =?UTF-8?q?kerning=20=E8=87=AA=E5=8A=A8=E6=B5=8B=E9=87=8F?= =?UTF-8?q?=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/services/font_service.py | 17 +++- tools/services/kerning_service.py | 126 ++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 tools/services/kerning_service.py diff --git a/tools/services/font_service.py b/tools/services/font_service.py index 8229d9f69..e9999ec54 100644 --- a/tools/services/font_service.py +++ b/tools/services/font_service.py @@ -13,6 +13,7 @@ from tools import configs from tools.configs import path_define, FontSize, WidthMode, LanguageFlavor, FontFormat from tools.configs.font import FontConfig +from tools.services import kerning_service class DesignContext: @@ -30,28 +31,33 @@ def load(font_config: FontConfig, mappings: list[dict[int, SourceFlavorGroup]]) glyph_files[width_mode] = dict(contexts['common']) glyph_files[width_mode].update(contexts[width_mode]) - return DesignContext(font_config, glyph_files) + return DesignContext(font_config, contexts, glyph_files) font_config: FontConfig + _contexts: dict[str, dict[int, GlyphFlavorGroup]] _glyph_files: dict[WidthMode, dict[int, GlyphFlavorGroup]] _alphabet_cache: dict[str, set[str]] _character_mapping_cache: dict[str, dict[int, str]] _glyph_sequence_cache: dict[str, list[GlyphFile]] _glyph_pool_cache: dict[str, dict[Path, Glyph]] + _proportional_kerning_pairs_cache: dict[tuple[str, str], int] | None _builder_cache: dict[str, FontBuilder] _collection_builder_cache: dict[str, FontCollectionBuilder] def __init__( self, font_config: FontConfig, + contexts: dict[str, dict[int, GlyphFlavorGroup]], glyph_files: dict[WidthMode, dict[int, GlyphFlavorGroup]], ): self.font_config = font_config + self._contexts = contexts self._glyph_files = glyph_files self._alphabet_cache = {} self._character_mapping_cache = {} self._glyph_sequence_cache = {} self._glyph_pool_cache = {} + self._proportional_kerning_pairs_cache = None self._builder_cache = {} self._collection_builder_cache = {} @@ -93,6 +99,11 @@ def _get_glyph_pool(self, width_mode: WidthMode) -> dict[Path, Glyph]: self._glyph_pool_cache[width_mode] = glyph_pool return glyph_pool + def _get_proportional_kerning_pairs(self) -> dict[tuple[str, str], int]: + if self._proportional_kerning_pairs_cache is None: + self._proportional_kerning_pairs_cache = kerning_service.calculate_kerning_pairs(self._contexts['proportional']) + return self._proportional_kerning_pairs_cache + def _create_builder(self, width_mode: WidthMode, language_flavor: LanguageFlavor, is_collection: bool) -> FontBuilder: layout_param = self.font_config.layout_params[width_mode] @@ -146,6 +157,10 @@ def _create_builder(self, width_mode: WidthMode, language_flavor: LanguageFlavor glyph_pool[glyph_file.file_path] = glyph builder.glyphs.append(glyph) + if width_mode == 'proportional': + kerning_pairs = self._get_proportional_kerning_pairs() + builder.kerning_pairs.update(kerning_pairs) + return builder def _get_builder(self, width_mode: WidthMode, language_flavor: LanguageFlavor) -> FontBuilder: diff --git a/tools/services/kerning_service.py b/tools/services/kerning_service.py new file mode 100644 index 000000000..fe846a420 --- /dev/null +++ b/tools/services/kerning_service.py @@ -0,0 +1,126 @@ +from pixel_font_knife.glyph_file_util import GlyphFlavorGroup + +_common_marks = {ord(c) for c in '"\',.'} + + +def _get_latin_alphabet() -> set[int]: + alphabet = set(_common_marks) + + # 0000-007F Basic Latin + for code_point in range(ord('A'), ord('Z') + 1): + alphabet.add(code_point) + for code_point in range(ord('a'), ord('z') + 1): + alphabet.add(code_point) + + # 0080-00FF Latin-1 Supplement + for code_point in range(0x00C0, 0x00D6 + 1): + alphabet.add(code_point) + for code_point in range(0x00D8, 0x00F6 + 1): + alphabet.add(code_point) + for code_point in range(0x00F8, 0x00FF + 1): + alphabet.add(code_point) + + # 0100-017F Latin Extended-A + for code_point in range(0x0100, 0x017F + 1): + alphabet.add(code_point) + + # 0180-024F Latin Extended-B + for code_point in range(0x0180, 0x024F + 1): + if code_point not in (0x01C0, 0x01C1, 0x01C2, 0x01C3): + alphabet.add(code_point) + + # 2C60-2C7F Latin Extended-C + for code_point in range(0x2C60, 0x2C7F + 1): + if code_point not in (0x2C7C, 0x2C7D): + alphabet.add(code_point) + + # A720-A7FF Latin Extended-D + for code_point in range(0xA722, 0xA76F + 1): + alphabet.add(code_point) + for code_point in range(0xA771, 0xA787 + 1): + alphabet.add(code_point) + for code_point in range(0xA78B, 0xA78E + 1): + alphabet.add(code_point) + for code_point in range(0xA790, 0xA7FF + 1): + if chr(code_point).isprintable(): + alphabet.add(code_point) + + # AB30-AB6F Latin Extended-E + for code_point in range(0xAB30, 0xAB5A + 1): + alphabet.add(code_point) + for code_point in range(0xAB60, 0xAB68 + 1): + alphabet.add(code_point) + + # 1E00-1EFF Latin Extended Additional + for code_point in range(0x1E00, 0x1EFF + 1): + alphabet.add(code_point) + + return alphabet + + +def _get_greek_and_coptic_alphabet() -> set[int]: + alphabet = set(_common_marks) + + # 0370-03FF Greek and Coptic + for code_point in range(0x0370, 0x03FF + 1): + if chr(code_point).isprintable() and code_point not in (0x0374, 0x0375, 0x037A, 0x037E, 0x0384, 0x0385, 0x0387): + alphabet.add(code_point) + + return alphabet + + +def _get_cyrillic_alphabet() -> set[int]: + alphabet = set(_common_marks) + + # 0400-04FF Cyrillic + for code_point in range(0x0400, 0x0481 + 1): + alphabet.add(code_point) + for code_point in range(0x048A, 0x04FF + 1): + alphabet.add(code_point) + + # 0500-052F Cyrillic Supplement + for code_point in range(0x0500, 0x052F + 1): + alphabet.add(code_point) + + return alphabet + + +_alphabets = [ + _get_latin_alphabet(), + _get_greek_and_coptic_alphabet(), + _get_cyrillic_alphabet(), +] + + +def calculate_kerning_pairs(context: dict[int, GlyphFlavorGroup]) -> dict[tuple[str, str], int]: + kerning_pairs = {} + for alphabet in _alphabets: + for left_code_point in alphabet: + if left_code_point not in context: + continue + left_glyph_file = context[left_code_point][None] + + if all(mask_row[-1] == 0 for mask_row in left_glyph_file.mask): + continue + mask = left_glyph_file.mask + + for right_code_point in alphabet: + if right_code_point not in context: + continue + right_glyph_file = context[right_code_point][None] + + bitmap = right_glyph_file.bitmap + if right_glyph_file.code_point in _common_marks: + padding = bitmap.calculate_left_padding() + bitmap = bitmap.resize(left=-padding, right=padding) + + offset = 0 + while True: + if mask != mask.plus(bitmap, x=left_glyph_file.width + offset - 1): + break + offset -= 1 + if offset <= -(right_glyph_file.width - 1): + break + if offset < 0: + kerning_pairs[(left_glyph_file.glyph_name, right_glyph_file.glyph_name)] = offset + return kerning_pairs