Skip to content

Commit

Permalink
kerning 自动测量算法
Browse files Browse the repository at this point in the history
  • Loading branch information
TakWolf committed Jan 11, 2025
1 parent 91c9f7d commit beb279f
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 1 deletion.
17 changes: 16 additions & 1 deletion tools/services/font_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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 = {}

Expand Down Expand Up @@ -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]

Expand Down Expand Up @@ -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:
Expand Down
126 changes: 126 additions & 0 deletions tools/services/kerning_service.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit beb279f

Please sign in to comment.