Code to render character cells

This commit is contained in:
Kovid Goyal 2016-10-24 13:50:24 +05:30
parent 697db21c64
commit 809a833dc5
4 changed files with 76 additions and 14 deletions

View File

@ -992,7 +992,7 @@ class Face( object ):
FT_Done_Face( self._FT_Face )
def __repr__(self):
return 'Face({})'.format(self.family_name)
return 'Face({}, {})'.format(self.family_name, self.style_name)
def attach_file( self, filename ):
'''

View File

@ -5,7 +5,7 @@
from threading import Lock
from .config import build_ansi_color_tables, to_color
from .fonts import load_font_family
from .fonts import set_font_family
from OpenGL.arrays import ArrayDatatype
from OpenGL.GL import (
@ -51,8 +51,7 @@ class CharGrid:
self.opts = opts
self.default_bg = self.original_bg = opts.background
self.default_fg = self.original_fg = opts.foreground
self.base_font_family = load_font_family(opts.font_family)
self.font_size = int(opts.font_size * 64)
self.base_font_family = set_font_family(opts.font_family)
self.apply_clear_color()
def on_resize(self, window, w, h):

View File

@ -3,11 +3,17 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import subprocess
import unicodedata
import re
from collections import namedtuple
from functools import lru_cache
from freetype import Face
from freetype import (
Face, FT_LOAD_RENDER, FT_LOAD_TARGET_NORMAL, FT_LOAD_TARGET_LIGHT,
FT_LOAD_NO_HINTING, FT_PIXEL_MODE_GRAY
)
from .utils import get_logical_dpi
def escape_family_name(name):
@ -23,8 +29,8 @@ def get_font_information(q, bold=False, italic=False):
q += ':bold=200'
if italic:
q += ':slant=100'
raw = subprocess.check_output(['fc-match', q, '-f', '%{file}\x31%{hinting}\x31%{hintstyle}']).decode('utf-8')
parts = raw.split('\x31')
raw = subprocess.check_output(['fc-match', q, '-f', '%{file}\x1e%{hinting}\x1e%{hintstyle}']).decode('utf-8')
parts = raw.split('\x1e')
hintstyle, hinting = 1, 'True'
if len(parts) == 3:
path, hinting, hintstyle = parts
@ -48,6 +54,58 @@ def get_font_files(family):
return ans
@lru_cache()
def load_font_family(r):
return get_font_files(r)
current_font_family = current_font_family_name = None
def set_font_family(family):
global current_font_family, current_font_family_name
if current_font_family_name != family:
current_font_family = get_font_files(family)
current_font_family_name = family
return current_font_family['regular'].face
CharData = namedtuple('CharData', 'left width ascender descender')
def render_char(text, size_in_pts, bold=False, italic=False):
# TODO: Handle non-normalizable combining chars. Probably need to use
# harfbuzz for that
text = unicodedata.normalize('NFC', text)[0]
key = 'regular'
if bold:
key = 'bi' if italic else 'bold'
elif italic:
key = 'italic'
font = current_font_family.get(key) or current_font_family['regular']
dpi = get_logical_dpi()
sz = int(64 * size_in_pts)
face = font.face
face.set_char_size(width=sz, height=sz, hres=dpi[0], vres=dpi[1])
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)
bitmap = face.glyph.bitmap
if bitmap.pixel_mode != FT_PIXEL_MODE_GRAY:
raise ValueError('FreeType rendered the glyph with an unsupported pixel mode: {}'.format(bitmap.pixel_mode))
width = bitmap.width
ascender = face.glyph.bitmap_top
descender = bitmap.rows - ascender
left = face.glyph.bitmap_left
return bitmap.buffer, CharData(left, width, ascender, descender)
def test_rendering(text='M', sz=144, family='monospace'):
set_font_family(family)
buf, char_data = render_char(text, sz)
print(char_data)
from PIL import Image
img = Image.new('L', (char_data.width, char_data.ascender + char_data.descender))
img.putdata(buf)
img.show()

View File

@ -108,6 +108,14 @@ def sanitize_title(x):
return re.sub(r'\s+', ' ', re.sub(r'[\0-\x19]', '', x))
def get_logical_dpi():
if not hasattr(get_logical_dpi, 'ans'):
raw = subprocess.check_output(['xdpyinfo']).decode('utf-8')
m = re.search(r'^\s*resolution:\s*(\d+)+x(\d+)', raw, flags=re.MULTILINE)
get_logical_dpi.ans = int(m.group(1)), int(m.group(2))
return get_logical_dpi.ans
def get_dpi():
if not hasattr(get_dpi, 'ans'):
m = glfw.glfwGetPrimaryMonitor()
@ -115,8 +123,5 @@ def get_dpi():
vmode = glfw.glfwGetVideoMode(m)
dpix = vmode.width / (width / 25.4)
dpiy = vmode.height / (height / 25.4)
get_dpi.ans = {'physical': (dpix, dpiy)}
raw = subprocess.check_output(['xdpyinfo']).decode('utf-8')
m = re.search(r'^\s*resolution:\s*(\d+)+x(\d+)', raw, flags=re.MULTILINE)
get_dpi.ans['logical'] = (int(m.group(1)), int(m.group(2)))
get_dpi.ans = {'physical': (dpix, dpiy), 'logical': get_logical_dpi()}
return get_dpi.ans