Only set face size once. Also calculate cell width more precisely
This commit is contained in:
@@ -6,6 +6,7 @@ import subprocess
|
|||||||
import unicodedata
|
import unicodedata
|
||||||
import re
|
import re
|
||||||
import ctypes
|
import ctypes
|
||||||
|
import math
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
@@ -73,7 +74,34 @@ def get_font_files(family):
|
|||||||
|
|
||||||
current_font_family = current_font_family_name = cff_size = cell_width = cell_height = baseline = None
|
current_font_family = current_font_family_name = cff_size = cell_width = cell_height = baseline = None
|
||||||
CharTexture = underline_position = underline_thickness = glyph_cache = None
|
CharTexture = underline_position = underline_thickness = glyph_cache = None
|
||||||
fallback_font_face_cache = {}
|
alt_face_cache = {}
|
||||||
|
|
||||||
|
|
||||||
|
def load_char(font, face, text):
|
||||||
|
flags = FT_LOAD_RENDER
|
||||||
|
if font.hinting:
|
||||||
|
if font.hintstyle >= 3:
|
||||||
|
flags |= FT_LOAD_TARGET_NORMAL
|
||||||
|
elif 0 < font.hintstyle < 3:
|
||||||
|
flags |= FT_LOAD_TARGET_LIGHT
|
||||||
|
else:
|
||||||
|
flags |= FT_LOAD_NO_HINTING
|
||||||
|
face.load_char(text, flags)
|
||||||
|
|
||||||
|
|
||||||
|
def calc_cell_width(font, face):
|
||||||
|
ans = 0
|
||||||
|
for i in range(32, 128):
|
||||||
|
ch = chr(i)
|
||||||
|
load_char(font, face, ch)
|
||||||
|
m = face.glyph.metrics
|
||||||
|
ans = max(ans, int(math.ceil(m.horiAdvance / 64)))
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=2**10)
|
||||||
|
def font_for_char(char, bold=False, italic=False):
|
||||||
|
return find_font_for_character(current_font_family_name, char, bold, italic)
|
||||||
|
|
||||||
|
|
||||||
def set_font_family(family, size_in_pts):
|
def set_font_family(family, size_in_pts):
|
||||||
@@ -81,36 +109,29 @@ def set_font_family(family, size_in_pts):
|
|||||||
global underline_position, underline_thickness, glyph_cache
|
global underline_position, underline_thickness, glyph_cache
|
||||||
if current_font_family_name != family or cff_size != size_in_pts:
|
if current_font_family_name != family or cff_size != size_in_pts:
|
||||||
find_font_for_character.cache_clear()
|
find_font_for_character.cache_clear()
|
||||||
fallback_font_face_cache.clear()
|
|
||||||
current_font_family = get_font_files(family)
|
current_font_family = get_font_files(family)
|
||||||
current_font_family_name = family
|
current_font_family_name = family
|
||||||
cff_size = size_in_pts
|
|
||||||
dpi = get_logical_dpi()
|
dpi = get_logical_dpi()
|
||||||
|
glyph_cache = GlyphWidthCache()
|
||||||
|
cff_size = int(64 * size_in_pts)
|
||||||
|
cff_size = {'width': cff_size, 'height': cff_size, 'hres': dpi[0], 'vres': dpi[1]}
|
||||||
|
for fobj in current_font_family.values():
|
||||||
|
fobj.face.set_char_size(**cff_size)
|
||||||
face = current_font_family['regular'].face
|
face = current_font_family['regular'].face
|
||||||
cell_width = font_units_to_pixels(face.max_advance_width, face.units_per_EM, size_in_pts, dpi[0]) + 2
|
cell_width = calc_cell_width(current_font_family['regular'], face)
|
||||||
cell_height = font_units_to_pixels(face.height, face.units_per_EM, size_in_pts, dpi[1])
|
cell_height = font_units_to_pixels(face.height, face.units_per_EM, size_in_pts, dpi[1])
|
||||||
# Ensure dimensions are even
|
|
||||||
cell_width += cell_width % 2
|
|
||||||
cell_height += cell_height % 2
|
|
||||||
baseline = font_units_to_pixels(face.ascender, face.units_per_EM, size_in_pts, dpi[1])
|
baseline = font_units_to_pixels(face.ascender, face.units_per_EM, size_in_pts, dpi[1])
|
||||||
underline_position = baseline - font_units_to_pixels(face.underline_position, face.units_per_EM, size_in_pts, dpi[1])
|
underline_position = baseline - font_units_to_pixels(face.underline_position, face.units_per_EM, size_in_pts, dpi[1])
|
||||||
underline_thickness = font_units_to_pixels(face.underline_thickness, face.units_per_EM, size_in_pts, dpi[1])
|
underline_thickness = font_units_to_pixels(face.underline_thickness, face.units_per_EM, size_in_pts, dpi[1])
|
||||||
CharTexture = ctypes.c_ubyte * (cell_width * cell_height)
|
CharTexture = ctypes.c_ubyte * (cell_width * cell_height)
|
||||||
glyph_cache = GlyphWidthCache()
|
|
||||||
set_current_font_metrics(glyph_cache.width)
|
set_current_font_metrics(glyph_cache.width)
|
||||||
|
font_for_char.cache_clear()
|
||||||
|
alt_face_cache.clear()
|
||||||
return cell_width, cell_height
|
return cell_width, cell_height
|
||||||
|
|
||||||
CharBitmap = namedtuple('CharBitmap', 'data bearingX bearingY advance rows columns')
|
CharBitmap = namedtuple('CharBitmap', 'data bearingX bearingY advance rows columns')
|
||||||
|
|
||||||
|
|
||||||
def face_for_char(char, bold=False, italic=False):
|
|
||||||
ans = find_font_for_character(current_font_family_name, char, bold, italic)
|
|
||||||
face = fallback_font_face_cache.get(ans.face)
|
|
||||||
if face is None:
|
|
||||||
face = fallback_font_face_cache[ans.face] = Face(ans.face)
|
|
||||||
return face
|
|
||||||
|
|
||||||
|
|
||||||
def font_units_to_pixels(x, units_per_em, size_in_pts, dpi):
|
def font_units_to_pixels(x, units_per_em, size_in_pts, dpi):
|
||||||
return int(x * ((size_in_pts * dpi) / (72 * units_per_em)))
|
return int(x * ((size_in_pts * dpi) / (72 * units_per_em)))
|
||||||
|
|
||||||
@@ -118,13 +139,10 @@ def font_units_to_pixels(x, units_per_em, size_in_pts, dpi):
|
|||||||
freetype_lock = Lock()
|
freetype_lock = Lock()
|
||||||
|
|
||||||
|
|
||||||
def render_char(text, bold=False, italic=False, size_in_pts=None):
|
def render_char(text, bold=False, italic=False):
|
||||||
# TODO: Handle non-normalizable combining chars. Probably need to use
|
# TODO: Handle non-normalizable combining chars. Probably need to use
|
||||||
# harfbuzz for that
|
# harfbuzz for that
|
||||||
size_in_pts = size_in_pts or cff_size
|
|
||||||
text = unicodedata.normalize('NFC', text)[0]
|
text = unicodedata.normalize('NFC', text)[0]
|
||||||
dpi = get_logical_dpi()
|
|
||||||
sz = int(64 * size_in_pts)
|
|
||||||
key = 'regular'
|
key = 'regular'
|
||||||
if bold:
|
if bold:
|
||||||
key = 'bi' if italic else 'bold'
|
key = 'bi' if italic else 'bold'
|
||||||
@@ -134,17 +152,12 @@ def render_char(text, bold=False, italic=False, size_in_pts=None):
|
|||||||
font = current_font_family.get(key) or current_font_family['regular']
|
font = current_font_family.get(key) or current_font_family['regular']
|
||||||
face = font.face
|
face = font.face
|
||||||
if not face.get_char_index(ord(text[0])):
|
if not face.get_char_index(ord(text[0])):
|
||||||
face = face_for_char(text[0], bold, italic)
|
font = font_for_char(text[0], bold, italic)
|
||||||
face.set_char_size(width=sz, height=sz, hres=dpi[0], vres=dpi[1])
|
face = alt_face_cache.get(font)
|
||||||
flags = FT_LOAD_RENDER
|
if face is None:
|
||||||
if font.hinting:
|
face = alt_face_cache[font] = Face(font.face)
|
||||||
if font.hintstyle >= 3:
|
face.set_char_size(**cff_size)
|
||||||
flags |= FT_LOAD_TARGET_NORMAL
|
load_char(font, face, text)
|
||||||
elif 0 < font.hintstyle < 3:
|
|
||||||
flags |= FT_LOAD_TARGET_LIGHT
|
|
||||||
else:
|
|
||||||
flags |= FT_LOAD_NO_HINTING
|
|
||||||
face.load_char(text, flags)
|
|
||||||
bitmap = face.glyph.bitmap
|
bitmap = face.glyph.bitmap
|
||||||
if bitmap.pixel_mode != FT_PIXEL_MODE_GRAY:
|
if bitmap.pixel_mode != FT_PIXEL_MODE_GRAY:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -201,8 +214,8 @@ def split_char_bitmap(bitmap_char):
|
|||||||
return first, second
|
return first, second
|
||||||
|
|
||||||
|
|
||||||
def render_cell(text, bold=False, italic=False, size_in_pts=None):
|
def render_cell(text, bold=False, italic=False):
|
||||||
bitmap_char = render_char(text, bold, italic, size_in_pts)
|
bitmap_char = render_char(text, bold, italic)
|
||||||
second = None
|
second = None
|
||||||
if is_wide_char(bitmap_char):
|
if is_wide_char(bitmap_char):
|
||||||
bitmap_char, second = split_char_bitmap(bitmap_char)
|
bitmap_char, second = split_char_bitmap(bitmap_char)
|
||||||
|
|||||||
Reference in New Issue
Block a user