msspec_python3/msspec/msspecgui/scenegraph2d/font/__init__.py

164 lines
5.5 KiB
Python

# -*- coding: utf-8 -*-
"""font abstraction"""
# imports ####################################################################
from itertools import chain
from ctypes import byref, string_at
from .utils import get_font
from . import freetype2 as _ft2
_FT = _ft2.FT
_library = _ft2._library
# globals ####################################################################
def _on(tag):
return bool(tag & 1)
def _cubic(tag):
return bool(tag & 2)
# face #######################################################################
class Face(object):
def __init__(self, font_families, font_weight, font_style, px=10):
font_name, index = get_font(font_families, font_weight, font_style)
self.face = _ft2.Face()
self.pen = _ft2.Vector()
print('_library = %s' % str(_library))
print('font_name = %s' % str(font_name))
print('font_name.encode() = %s' % str(font_name.encode()))
if _FT.New_Face(_library, font_name.encode(), index, byref(self.face)) != 0:
raise ValueError("unable to create '%s' face" % font_name)
_FT.Select_Charmap(self.face, _ft2.ENCODING_UNICODE)
if px is not None:
self.set_size(px)
self._FT = _FT # keep a ref for finalizer
def __del__(self):
try:
self._FT.Done_Face(self.face)
except AttributeError:
pass
def set_size(self, px):
_FT.Set_Pixel_Sizes(self.face, 0, px)
def set_transform(self, a=1., b=0., c=0., d=1., e=0., f=0.):
matrix = _ft2.Matrix()
matrix.xx, matrix.xy = int(a * 0x10000), int(b * 0x10000)
matrix.yx, matrix.yy = int(c * 0x10000), int(d * 0x10000)
self.pen.x = int(e * 64)
self.pen.y = int(-f * 64)
_FT.Set_Transform(self.face, byref(matrix), byref(self.pen))
def _glyph(self, uc):
glyph_index = _FT.Get_Char_Index(self.face, ord(uc))
_FT.Load_Glyph(self.face, glyph_index, _ft2.LOAD_DEFAULT)
return self.face.contents.glyph.contents
def get_hkerning(self, ucl, ucr):
if ucl is None:
return 0.
left_glyph = _FT.Get_Char_Index(self.face, ord(ucl))
right_glyph = _FT.Get_Char_Index(self.face, ord(ucr))
kerning = _ft2.Vector()
_FT.Get_Kerning(self.face, left_glyph, right_glyph,
_ft2.KERNING_DEFAULT, byref(kerning))
return kerning.x / 64.
def get_bbox(self, text):
width = 0
top, bottom = 0, 0
up = None
glyph = None
for uc in text:
width += self.get_hkerning(up, uc)
up = uc
glyph = self._glyph(uc)
width += glyph.metrics.horiAdvance / 64.
top = max(top, glyph.metrics.horiBearingY / 64.)
bottom = min(bottom, (glyph.metrics.horiBearingY -
glyph.metrics.height) / 64.)
if glyph:
width += (glyph.metrics.horiBearingX + glyph.metrics.width -
glyph.metrics.horiAdvance) / 64.
return (0., -top), (width, top - bottom)
def bitmap(self, uc):
glyph = self._glyph(uc)
_FT.Render_Glyph(byref(glyph), _ft2.RENDER_MODE_NORMAL)
origin = glyph.bitmap_left, -glyph.bitmap_top
bitmap = glyph.bitmap
assert bitmap.pixel_mode == _ft2.PIXEL_MODE_GRAY, bitmap.pixel_mode
rows, columns = bitmap.rows, bitmap.pitch
size = columns, rows
offset = glyph.advance.x / 64., -glyph.advance.y / 64.
data = string_at(bitmap.buffer, rows * columns)
data = bytes(chain(*([255, 255, 255, c] for c in data)))
return origin, size, offset, data
def outline(self, uc):
glyph = self._glyph(uc)
outline = glyph.outline
data = []
b = 0
for c in range(outline.n_contours):
e = outline.contours[c]
# ensure we start with an 'on' point
for s in range(b, e + 1):
if _on(outline.tags[s]):
break
# generate path data
contour = []
command, offs = 'M', []
for i in chain(range(s, e + 1), range(b, s + 1)):
point, tag = outline.points[i], outline.tags[i]
point = (point.x / 64., -point.y / 64.)
if _on(tag): # 'on' point
contour.append(command)
if command is 'Q' and len(offs) >= 2:
(x0, y0) = offs[0]
for (x1, y1) in offs[1:]:
contour += [(x0, y0), ((x0 + x1) / 2, (y0 + y1) / 2), 'Q']
x0, y0 = x1, y1
contour.append((x0, y0))
else: # 'off' point
contour += offs
contour.append(point)
command, offs = 'L', []
else:
offs.append(point)
command = 'C' if _cubic(tag) else 'Q'
if contour:
contour.append('Z')
data += contour
b = e + 1
# bbox
bbox = _ft2.BBox()
_FT.Outline_Get_BBox(byref(outline), byref(bbox))
xmin, xmax = bbox.xMin / 64., bbox.xMax / 64.
ymin, ymax = bbox.yMin / 64., bbox.yMax / 64.
origin = xmin, -ymax
size = xmax - xmin, ymax - ymin
offset = glyph.advance.x / 64., -glyph.advance.y / 64.
return origin, size, offset, data