Source code for glyph_motion.lib.font_alphabet_stroke_builder

#!/usr/bin/env python
"""
K.NAKADA, kengo.nakada@gmail.com

アルファベット文字列から筆順付き stroke データを生成する。

このモジュールは、通常フォントのアウトラインを使わない。
最初からロボットでなぞるための single-line stroke を生成する。

既存のアウトラインフォント処理とは別系統の入力経路である。
既存の outline -> filled -> centerline -> motion の処理はそのまま残す。

このモジュールが出力する FontStrokeGlyph は、筆順を持つ。
FontStrokeGlyph の各 FontStrokePath は、一回ペンを下ろして書く単位である。

stroke 内部は、粗い制御点をそのまま直線で結ぶだけではなく、
stroke_division に従って細かい点列へ分割する。

これにより、FontMotionBuilder は変更せずに、
より細かいロボット移動命令を生成できる。
"""

from typing import Dict, List, Tuple

from glyph_motion.lib.font_geometry import FontPoint
from glyph_motion.lib.font_geometry import FontStrokeGlyph
from glyph_motion.lib.font_geometry import FontStrokePath


RawStroke = List[Tuple[float, float]]
RawGlyph = List[RawStroke]


[docs] class FontAlphabetStrokeBuilder: """アルファベット文字列から筆順付き stroke データを作るクラスである。""" def __init__( self, height: float, spacing: float, stroke_division: int = 20, ) -> None: """文字高さ、文字間隔、stroke内分割数を設定する。""" if height <= 0.0: raise ValueError("height は正の値である必要がある。") if spacing < 0.0: raise ValueError("spacing は0以上である必要がある。") if stroke_division < 1: raise ValueError("stroke_division は1以上である必要がある。") self._height = height self._spacing = spacing self._stroke_division = stroke_division self._width = height * 0.7 self._glyphs = self._build_glyph_table()
[docs] def build_text( self, text: str, ) -> FontStrokeGlyph: """文字列を筆順付き stroke データへ変換する。""" stroke_paths: List[FontStrokePath] = [] x_offset = 0.0 for character in text: upper_character = character.upper() if upper_character == " ": x_offset = x_offset + self._width + self._spacing continue if upper_character not in self._glyphs: raise ValueError("未定義の文字である: {}".format(character)) raw_glyph = self._glyphs[upper_character] for raw_stroke in raw_glyph: normalized_points = self._subdivide_raw_stroke( raw_stroke=raw_stroke, ) points: List[FontPoint] = [] for raw_point in normalized_points: points.append( FontPoint( x=x_offset + raw_point[0] * self._width, y=raw_point[1] * self._height, ) ) stroke_paths.append( FontStrokePath( points=points, ) ) x_offset = x_offset + self._width + self._spacing return FontStrokeGlyph( text=text, paths=stroke_paths, )
[docs] def _subdivide_raw_stroke( self, raw_stroke: RawStroke, ) -> RawStroke: """stroke 内の各線分を細かい点列へ分割する。""" if len(raw_stroke) < 2: return raw_stroke points: RawStroke = [ raw_stroke[0], ] for point_index in range(1, len(raw_stroke)): start_point = raw_stroke[point_index - 1] end_point = raw_stroke[point_index] segment_points = self._subdivide_segment( start_point=start_point, end_point=end_point, ) for segment_point in segment_points: points.append(segment_point) return points
[docs] def _subdivide_segment( self, start_point: Tuple[float, float], end_point: Tuple[float, float], ) -> RawStroke: """2点間の線分を stroke_division 個の小区間へ分割する。 返す点列には始点を含めない。 終点は含める。 """ points: RawStroke = [] start_x = start_point[0] start_y = start_point[1] end_x = end_point[0] end_y = end_point[1] for division_index in range(1, self._stroke_division + 1): ratio = float(division_index) / float(self._stroke_division) x = start_x + (end_x - start_x) * ratio y = start_y + (end_y - start_y) * ratio points.append( ( x, y, ) ) return points
[docs] def _build_glyph_table(self) -> Dict[str, RawGlyph]: """内蔵 stroke 定義を返す。 座標は 0.0 から 1.0 の正規化座標で定義する。 path の順序が筆順である。 ここで定義する点は制御点に近い粗い点である。 実際に返す FontStrokeGlyph では、stroke_division に従って 各線分を細かい点列へ分割する。 """ glyphs: Dict[str, RawGlyph] = {} glyphs["A"] = [ [(0.0, 0.0), (0.5, 1.0)], [(0.5, 1.0), (1.0, 0.0)], [(0.25, 0.45), (0.75, 0.45)], ] glyphs["B"] = [ [(0.0, 0.0), (0.0, 1.0)], [(0.0, 1.0), (0.65, 0.85), (0.65, 0.55), (0.0, 0.5)], [(0.0, 0.5), (0.75, 0.4), (0.75, 0.1), (0.0, 0.0)], ] glyphs["C"] = [ [(0.8, 0.9), (0.2, 0.9), (0.0, 0.5), (0.2, 0.1), (0.8, 0.1)], ] glyphs["I"] = [ [(0.2, 1.0), (0.8, 1.0)], [(0.5, 1.0), (0.5, 0.0)], [(0.2, 0.0), (0.8, 0.0)], ] glyphs["L"] = [ [(0.0, 1.0), (0.0, 0.0), (0.8, 0.0)], ] glyphs["O"] = [ [ (0.5, 1.0), (0.1, 0.8), (0.1, 0.2), (0.5, 0.0), (0.9, 0.2), (0.9, 0.8), (0.5, 1.0), ], ] glyphs["T"] = [ [(0.0, 1.0), (1.0, 1.0)], [(0.5, 1.0), (0.5, 0.0)], ] glyphs["X"] = [ [(0.0, 1.0), (1.0, 0.0)], [(1.0, 1.0), (0.0, 0.0)], ] glyphs["Y"] = [ [(0.0, 1.0), (0.5, 0.55), (1.0, 1.0)], [(0.5, 0.55), (0.5, 0.0)], ] glyphs["Z"] = [ [(0.0, 1.0), (1.0, 1.0), (0.0, 0.0), (1.0, 0.0)], ] return glyphs
[docs] def main() -> None: """アルファベット stroke 生成を単体確認する。""" from font_viewer import FontViewer builder = FontAlphabetStrokeBuilder( height=40.0, spacing=5.0, stroke_division=20, ) stroke_glyph = builder.build_text( text="A", ) print(stroke_glyph) viewer = FontViewer() viewer.show_stroke( stroke_glyph=stroke_glyph, )
if __name__ == "__main__": main()