#! /usr/local/bin/env python3
#! encode : -*- utf-8 -*-
import defcon
import ufo2ft
import os
from math import *
import time
from scipy import interpolate
import numpy as np
class myFont:
def __init__(self,
familyName:str=f"myFont_{time.time()}",
unitsPerEm:int=1000,
descender:int=-120,
ascender:int=880,
xHeight=500,
capHeight=800,
directory:str=None):
self.familyName = familyName
self.unitsPerEm = unitsPerEm
self.descender = descender
self.ascender = ascender
self.xHeight = xHeight
self.capHeight = capHeight
self.font = defcon.Font()
self.font.info.familyName = self.familyName
self.font.info.unitsPerEm = self.unitsPerEm
self.font.info.descender = self.descender
self.font.info.ascender = self.ascender
self.font.info.xHeight = self.xHeight
self.font.info.capHeight = self.capHeight
self.directory = directory
if(directory != None):
self.chdir(self.directory)
self.chars = dict()
self.GOLD = 1.618033988749895 # 黄金比
self.PALT = 2.414213562373095 # 白銀比
self.PI = 3.141592653589793 # 円周率
## ディレクトリの変更
def chdir(self, directory):
os.chdir(directory)
## フォントの保存
def save(self, familyName:str=None):
otf = ufo2ft.compileOTF(self.font)
file = f"{self.familyName if familyName == None else familyName}.otf"
otf.save(file)
print(f"{file} に保存")
## 文字を作成する
def makeChar(self, char:str, width:float=1., caption:str=None):
make_char = self.Char(char, width, caption, self.font)
return make_char
## 作成したフォントをHTMLで確認
def makeHtml(self, familyName:str=None, fontSize:int=4):
familyName = self.familyName if familyName == None else familyName
with open(f"{familyName}.html", "w", encoding="utf8") as f:
f.write("""\
<style>
@font-face {
font-family: "%s";
src: url("%s.otf");
}
p {
font-family: "%s";
font-size: %svw;
}
</style>
<body>
<p style="color:#000">
abcdefghijklmnopqrstuvwxyz<br>
<br>
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br>
<br>
12345678901234567890<br>
<br>
あいうえお かきくけこ さしすせそ たちつてと なにぬねの はひふへほ まみむめも やゆよ らりるれろ わをん<br>
<br>
アイウエオ カキクケコ サシスセソ タチツテト ナニヌネノ ハヒフヘホ マミムメモ ヤユヨ ラリルレロ ワヲン<br>
</p>
</body>
"""%(familyName, familyName, familyName, fontSize))
print(f"{familyName}.html を作成")
## 作成する文字のクラス
class Char:
def __init__(self, char:str, width:float, caption:str, myfont):
self.setting = [f"hoge = myFont.makeChar(char=\"{char}\", width={width}, caption={caption})"]
self.glyph = myfont.newGlyph(char)
self.glyph.unicode = ord(char)
if(width == 1) and (caption != "up") and (caption != "big"):
if(caption == "down") or (caption == "small") or (char in list("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-*/!?\"#$%&"()=ˆ˜¥|[]{}@:;`_,.<>")):
width = 0.6
self.glyph.width = int(myfont.info.unitsPerEm * width)
self.flush(is_repr=False)
## ポリゴンの頂点の設定
def addPoint(self, x:int, y:int, is_repr:bool=True):
if(is_repr):
self.setting.append(f"hoge.addPoint(x={x}, y={y})")
self.contour.appendPoint(defcon.Point((x, y), "line"))
## 点で囲まれたポリゴンの作成
def polygon(self, coords_x, coords_y, is_repr:bool=True):
if(is_repr):
self.setting.append(f"hoge.polygon(coords_x={coords_x}, coords_y={coords_y})")
for i in range(len(coords_x)):
self.addPoint(coords_x[i], coords_y[i], is_repr=False)
self.push(isFlush=True, is_repr=False)
## 長方形の作成
def rectangle(self, origin_x:int, origin_y:int, x:int, y:int, deg:float=0., is_repr:bool=True):
if(is_repr):
self.setting.append(f"hoge.rectangle(origin_x={origin_x}, origin_y={origin_y}, x={x}, y={y}, deg={deg})")
coords = [[0, 0],
[x, 0],
[x, y],
[0, y]]
for i in range(4):
coord_x, coord_y = self.rotate(coords[i][0], coords[i][1], deg)
self.addPoint(origin_x+coord_x, origin_y+coord_y, is_repr=False)
self.push(isFlush=True, is_repr=False)
## 太字(平行四辺形)の作成
def bold(self, coord_a=[0, 0], coord_b=[0, 0], size:int=100, deg:float=0., padding:str="center", is_repr:bool=True):
if(is_repr):
self.setting.append(f"hoge.bold(coord_a={coord_a}, coord_b={coord_b}, size={size}, deg={deg}, padding=\"{padding}\")")
if(padding == "left"):
pad = [size, 0]
elif(padding == "right"):
pad = [0, size]
else:
pad = [size/2, size/2]
coords = [[coord_a[0]-pad[0], coord_a[1]],
[coord_a[0]+pad[1], coord_a[1]],
[coord_b[0]+pad[1], coord_b[1]],
[coord_b[0]-pad[0], coord_b[1]]]
for i in range(4):
coord_x, coord_y = self.rotate(coords[i][0], coords[i][1], deg)
self.addPoint(coord_x, coord_y, is_repr=False)
self.push(isFlush=True, is_repr=False)
## 楕円の作成(正円も含む)
def ellipce(self, origin_x:int, origin_y:int, rad:int, square:int=30, pow_x:float=1., pow_y:float=1., deg_range=[0., 360.], deg:float=0., is_repr:bool=True):
if(is_repr):
self.setting.append(f"hoge.ellipce(origin_x={origin_x}, origin_y={origin_y}, rad={rad}, square={square}, pow_x={pow_x}, pow_y={pow_y}, deg_range={deg_range}, deg={deg})")
deg_diff = (deg_range[1] - deg_range[0]) / square
self.addPoint(origin_x, origin_y, is_repr=False)
for i in range(square+1):
deg_now = deg_range[0] + deg_diff*i
coord_x = rad*cos(radians(deg_now)) * pow_x
coord_y = rad*sin(radians(deg_now)) * pow_y
coord_x, coord_y = self.rotate(coord_x, coord_y, -deg)
self.addPoint(origin_x+coord_x, origin_y+coord_y, is_repr=False)
self.addPoint(origin_x, origin_y, is_repr=False)
self.push(isFlush=True, is_repr=False)
## 複数の座標を通る直線の作成
def line(self, coords_x, coords_y, size:int=100, circle:bool=True, padding="center", is_repr:bool=True):
if(is_repr):
self.setting.append(f"hoge.line(coords_x={coords_x}, coords_y={coords_y}, size={size}, circle={circle}, padding=\"{padding}\")")
pad = size/2
if(padding == "left"):
for i in range(len(coords_x)):
coords_x[i] -= pad
coords_y[i] -= pad
elif(padding == "right"):
for i in range(len(coords_x)):
coords_x[i] += pad
coords_y[i] += pad
for i in range(1, len(coords_x)):
self.addPoint(coords_x[i-1]-pad, coords_y[i-1]+pad, is_repr=False)
self.addPoint(coords_x[i]-pad, coords_y[i]+pad, is_repr=False)
self.addPoint(coords_x[i]-pad, coords_y[i]-pad, is_repr=False)
self.addPoint(coords_x[i-1]-pad, coords_y[i-1]-pad, is_repr=False)
self.push(isFlush=True, is_repr=False)
self.addPoint(coords_x[i-1]-pad, coords_y[i-1]+pad, is_repr=False)
self.addPoint(coords_x[i]-pad, coords_y[i]+pad, is_repr=False)
self.addPoint(coords_x[i]+pad, coords_y[i]+pad, is_repr=False)
self.addPoint(coords_x[i-1]+pad, coords_y[i-1]+pad, is_repr=False)
self.push(isFlush=True, is_repr=False)
self.addPoint(coords_x[i-1]+pad, coords_y[i-1]+pad, is_repr=False)
self.addPoint(coords_x[i]+pad, coords_y[i]+pad, is_repr=False)
self.addPoint(coords_x[i]+pad, coords_y[i]-pad, is_repr=False)
self.addPoint(coords_x[i-1]+pad, coords_y[i-1]-pad, is_repr=False)
self.push(isFlush=True, is_repr=False)
self.addPoint(coords_x[i-1]-pad, coords_y[i-1]-pad, is_repr=False)
self.addPoint(coords_x[i]-pad, coords_y[i]-pad, is_repr=False)
self.addPoint(coords_x[i]+pad, coords_y[i]-pad, is_repr=False)
self.addPoint(coords_x[i-1]+pad, coords_y[i-1]-pad, is_repr=False)
self.push(isFlush=True, is_repr=False)
if(circle):
for i in range(len(coords_x)):
origin_pad = pad - size/2
self.ellipce(coords_x[i]+origin_pad, coords_y[i]+origin_pad, (size/2)*(2**(1/2)), is_repr=False)
## 0〜3次のスプライン補間による太線の作成
def spline(self, coords_x, coords_y, size:int=100, dim:int=None, square:int=30, fudeji:bool=False, circle:bool=True, padding="center", is_repr:bool=True):
if(is_repr):
self.setting.append(f"hoge.spline(coords_x={coords_x}, coords_y={coords_y}, size={size}, dim={dim}, square={square}, fudeji={fudeji}, circle={circle}, padding=\"{padding}\")")
if(dim == None):
dim = len(coords_x)-1
if(dim > 3):
dim = 3
elif(dim < 0):
dim = 0
if(dim >= len(coords_x)):
dim = len(coords_x)-1
pad = size/2
kind = {0:"zero", 1:"slinear", 2:"quadratic", 3:"cubic"}[dim]
func = interpolate.interp1d(coords_x, coords_y, kind=kind)
xs = np.linspace(coords_x[0], coords_x[len(coords_x)-1], square)
ys = [func(x) for x in xs]
if(fudeji):
dist = []
for i in range(1, square):
dist.append(abs(ys[i-1] - ys[i]))
dist_diff = max(dist) - min(dist)
old_y = ys[0]
for i in range(square):
y_diff = abs(old_y - ys[i]) / dist_diff
old_y = ys[i]
pad_x = pad * y_diff
pad_y = pad * y_diff
if(padding == "left"):
pad_buf = -(pad_x+pad_y)/2
elif(padding == "right"):
pad_buf = (pad_x+pad_y)/2
else:
pad_buf = 0
if(circle):
self.ellipce(xs[i]+pad_buf, ys[i]+pad_buf, (pad_x+pad_y)/2, is_repr=False)
else:
self.rectangle(xs[i]-pad_x+pad_buf, ys[i]-pad_y+pad_buf, x=pad_x, y=pad_y, is_repr=False)
else:
for i in range(square):
pad_x = pad
pad_y = pad
if(circle):
self.ellipce(xs[i], ys[i], (pad_x+pad_y)/2, is_repr=False)
else:
self.rectangle(xs[i]-pad_x, ys[i]-pad_y, x=pad_x, y=pad_y, is_repr=False)
## スプライン補間関数の筆字機能を呼び出す
def fudeji(self, coords_x, coords_y, size:int=100, dim:int=None, square:int=30, padding="center", is_repr:bool=True):
if(is_repr):
self.setting.append(f"hoge.fudeji(coords_x={coords_x}, coords_y={coords_y}, size={size}, dim={dim}, square={square}, padding=\"{padding}\")")
self.spline(coords_x, coords_y, size=size, dim=dim, square=square, fudeji=True, circle=True, padding=padding, is_repr=False)
## 座標軸の回転
def rotate(self, x:int, y:int, deg:float):
return cos(radians(-deg))*x-sin(radians(-deg))*y, sin(radians(-deg))*x+cos(radians(-deg))*y
## 保存した座標の初期化
def flush(self, is_repr:bool=True):
self.contour = defcon.Contour()
if(is_repr):
self.setting.append(f"hoge.flush()")
## 座標をフォントへ出力
def push(self, isFlush:bool=True, is_repr:bool=True):
self.glyph.appendContour(self.contour)
if(isFlush):
self.flush(is_repr=is_repr)
if(is_repr):
self.setting.append(f"hoge.push(isFlush=False)")
## 現在の状態を文字列として出力
def __repr__(self):
return "\n".join(self.setting)