[PYTHON] Use BMFont as the font for pyglet

Introduction

I was able to incorporate BMFont into the font of pyglet, so I will publish it. Written in Python 3.5 It is unconfirmed whether it works with 2 systems.

What is BMFont?

Texture font creation software. http://www.angelcode.com/products/bmfont/ Some other software can export in the same format, so it's a relatively major software.

Source

The license should be the same as pyglet.

bmfont.py


from xml.etree import ElementTree
import weakref

import pyglet
from pyglet import gl
from pyglet.font import base


class DummyGlyphRenderer(base.GlyphRenderer):
    def __init__(self, font):
        super(DummyGlyphRenderer, self).__init__(font)
        self.font = font
    
    def render(self, text):
        glyph = self.font.create_dummy_glyph()
        glyph.set_bearings(0, 0, 0)
        return glyph


class BMFont(base.Font):
    glyph_renderer_class = DummyGlyphRenderer
    
    def __init__(self, file_name):
        super(BMFont, self).__init__()
        self._setup(file_name)
        
    def _setup(self, file_name):
        with pyglet.resource.file(file_name, mode="rb") as f:
            data = f.read()
        str_data = data.decode("utf-8", "ignore")
        root = ElementTree.fromstring(str_data)
        
        common = root.find("common")
        lineheight = int(common.attrib["lineHeight"])
        baseline = int(common.attrib["base"])
        scaleH = int(common.attrib["scaleH"])
        pages = int(common.attrib["pages"])
        self.ascent = baseline
        self.descent = -(lineheight - baseline)
        self.textures.extend([None] * pages)
        
        pages = root.find("pages")
        for page in pages:
            id_ = int(page.attrib["id"])
            file = page.attrib["file"]
            texture = pyglet.resource.texture(file)
            image_data = texture.get_image_data()
            texture = self.texture_class.create_for_size(gl.GL_TEXTURE_2D,
                texture.width, texture.height, gl.GL_RGBA, gl.GL_NEAREST,
                gl.GL_NEAREST)
            texture.blit_into(image_data, 0, 0, 0)
            self.textures[id_] = texture
        
        chars = root.find("chars")
        for char in chars:
            key = chr(int(char.attrib["id"]))
            x = int(char.attrib["x"])
            y = int(char.attrib["y"])
            width = int(char.attrib["width"])
            height = int(char.attrib["height"])
            xoffset = int(char.attrib["xoffset"])
            yoffset = int(char.attrib["yoffset"])
            xadvance = int(char.attrib["xadvance"])
            page = int(char.attrib["page"])
            
            texture = self.textures[page]
            y = scaleH - height - y
            glyph = texture.get_region(x, y, width, height)
            
            bottom = baseline - yoffset - height
            glyph.set_bearings(-bottom, xoffset, xadvance)
            
            self.glyphs[key] = glyph
    
    def create_dummy_glyph(self):
        return self.textures[0].get_region(0, 0, 0, 0)
        

def load(name, size=None, bold=False, italic=False, dpi=None, file_name=None):
    assert name
    assert file_name
    
    if size is None:
        size = 12

    if dpi is None:
        dpi = 96
    
    shared_object_space = gl.current_context.object_space
    if not hasattr(shared_object_space, 'pyglet_font_font_cache'):
        shared_object_space.pyglet_font_font_cache = \
            weakref.WeakValueDictionary()
        shared_object_space.pyglet_font_font_hold = []
    font_cache = shared_object_space.pyglet_font_font_cache
    font_hold = shared_object_space.pyglet_font_font_hold
    
    descriptor = (name, size, bold, italic, dpi)
    if descriptor in font_cache:
        return font_cache[descriptor]
    
    font = BMFont(file_name)
    
    font.name = name
    font.size = size
    font.bold = bold
    font.italic = italic
    font.dpi = dpi
    
    font_cache[descriptor] = font
    
    del font_hold[3:]
    font_hold.insert(0, font)
    
    return font

How to use

Save the above code as bmfont.py in a suitable location. Prepare a font created with BMFont and Set the font name to be registered and the .fnt file.

python


import bmfont
font = bmfont.load("myfont", file_name="font.fnt")

After that, specify the normally registered font name and use it. Label or Document.

python


label = pyglet.text.Label("Hello World!", "myfont", x=10, y=10)

document = pyglet.text.decode_text("Hello world!")
document.set_style(0, len(document.text), {
    "font_name": "myfont",
    "color": (255, 255, 255, 255),
})
layout = pyglet.text.layout.TextLayout(document, 200, 200)

Various precautions

Compare with pyglet font

I can use the ttf font in pyglet, but it looks a little blurry, so I think this is better if you want to display it clearly. 2016-09-27_14h52_55.png However, it is troublesome to recreate the texture font every time you add characters.

at the end

It was easier to incorporate than I expected, so I thought it was as if I was expecting to use BMFont.

The font used was borrowed from the following site. http://itouhiro.hatenablog.com/entry/20130602/font

Recommended Posts

Use BMFont as the font for pyglet
Use Flask as the next step for SimpleHTTPServer
How to use MkDocs for the first time
Use the e-paper module as a to-do list
Use get as much as possible for dictionary-type references
Use the company name recognition dictionary "JCLdic" for MeCab
[python] How to use the library Matplotlib for drawing graphs
Tips for Python beginners to use the Scikit-image example for themselves