mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-19 14:53:56 -07:00
Rewrite font builder tool to be much simpler and to support more text effects. Change GUI to use new set of fonts. Switch font textures from TGA to PNG so they're easier for the font builder to create. Support RGBA font textures (for e.g. stroked text). Greatly improve text rendering performance by using vertex arrays. Fix rendering code leaving vertex buffers bound. Add 'clip' property to GUI text objects, to disable clipping when rendering. Delete part of unused console function registration system. This was SVN commit r7595.
179 lines
6.4 KiB
Python
179 lines
6.4 KiB
Python
import cairo
|
|
import codecs
|
|
import math
|
|
|
|
import FontLoader
|
|
import Packer
|
|
|
|
# Representation of a rendered glyph
|
|
class Glyph(object):
|
|
def __init__(self, ctx, renderstyle, char, idx):
|
|
self.renderstyle = renderstyle
|
|
self.char = char
|
|
self.idx = idx
|
|
self.glyph = (idx, 0, 0)
|
|
|
|
extents = ctx.glyph_extents([self.glyph])
|
|
|
|
self.xadvance = round(extents[4])
|
|
|
|
# Find the bounding box of strokes and/or fills:
|
|
|
|
inf = 1e300 * 1e300
|
|
bb = [inf, inf, -inf, -inf]
|
|
|
|
if "stroke" in self.renderstyle:
|
|
for (c, w) in self.renderstyle["stroke"]:
|
|
ctx.set_line_width(w)
|
|
ctx.glyph_path([self.glyph])
|
|
e = ctx.stroke_extents()
|
|
bb = (min(bb[0], e[0]), min(bb[1], e[1]), max(bb[2], e[2]), max(bb[3], e[3]))
|
|
ctx.new_path()
|
|
|
|
if "fill" in self.renderstyle:
|
|
ctx.glyph_path([self.glyph])
|
|
e = ctx.fill_extents()
|
|
bb = (min(bb[0], e[0]), min(bb[1], e[1]), max(bb[2], e[2]), max(bb[3], e[3]))
|
|
ctx.new_path()
|
|
|
|
bb = (math.floor(bb[0]), math.floor(bb[1]), math.ceil(bb[2]), math.ceil(bb[3]))
|
|
|
|
self.x0 = -bb[0]
|
|
self.y0 = -bb[1]
|
|
self.w = bb[2] - bb[0]
|
|
self.h = bb[3] - bb[1]
|
|
|
|
# Force multiple of 4, to avoid leakage across S3TC blocks
|
|
# (TODO: is this useful?)
|
|
#self.w += (4 - (self.w % 4)) % 4
|
|
#self.h += (4 - (self.h % 4)) % 4
|
|
|
|
def pack(self, packer):
|
|
self.pos = packer.Pack(self.w, self.h)
|
|
|
|
def render(self, ctx):
|
|
ctx.save()
|
|
ctx.translate(self.x0, self.y0)
|
|
ctx.translate(self.pos.x, self.pos.y)
|
|
|
|
# Render each stroke, and then each fill on top of it
|
|
|
|
if "stroke" in self.renderstyle:
|
|
for ((r, g, b, a), w) in self.renderstyle["stroke"]:
|
|
ctx.set_line_width(w)
|
|
ctx.set_source_rgba(r, g, b, a)
|
|
ctx.glyph_path([self.glyph])
|
|
ctx.stroke()
|
|
|
|
if "fill" in self.renderstyle:
|
|
for (r, g, b, a) in self.renderstyle["fill"]:
|
|
ctx.set_source_rgba(r, g, b, a)
|
|
ctx.glyph_path([self.glyph])
|
|
ctx.fill()
|
|
|
|
ctx.restore()
|
|
|
|
# Load the set of characters contained in the given text file
|
|
def load_char_list(filename):
|
|
f = codecs.open(filename, "r", "utf-8")
|
|
chars = f.read()
|
|
f.close()
|
|
return set(chars)
|
|
|
|
# Construct a Cairo context and surface for rendering text with the given parameters
|
|
def setup_context(width, height, face, size, renderstyle):
|
|
format = (cairo.FORMAT_ARGB32 if "colour" in renderstyle else cairo.FORMAT_A8)
|
|
surface = cairo.ImageSurface(format, width, height)
|
|
ctx = cairo.Context(surface)
|
|
ctx.set_font_face(face)
|
|
ctx.set_font_size(size)
|
|
ctx.set_line_join(cairo.LINE_JOIN_ROUND)
|
|
return ctx, surface
|
|
|
|
def generate_font(chars, outname, ttf, size, renderstyle):
|
|
|
|
(face, indexes) = FontLoader.create_cairo_font_face_for_file(ttf)
|
|
|
|
(ctx, _) = setup_context(1, 1, face, size, renderstyle)
|
|
|
|
(ascent, descent, linespacing, _, _) = ctx.font_extents()
|
|
|
|
# Estimate the 'average' height of text, for vertical center alignment
|
|
charheight = round(ctx.glyph_extents([(indexes("I"), 0.0, 0.0)])[3])
|
|
|
|
# Translate all the characters into glyphs
|
|
# (This is inefficient if multiple characters have the same glyph)
|
|
glyphs = []
|
|
for c in chars:
|
|
idx = indexes(c)
|
|
if ord(c) == 0xFFFD and idx == 0: # use "?" if the missing-glyph glyph is missing
|
|
idx = indexes("?")
|
|
if idx:
|
|
glyphs.append(Glyph(ctx, renderstyle, c, idx))
|
|
|
|
# Sort by decreasing height (tie-break on decreasing width)
|
|
glyphs.sort(key = lambda g: (-g.h, -g.w))
|
|
|
|
# Try various sizes to pack the glyphs into
|
|
sizes = []
|
|
for h in [32, 64, 128, 256, 512, 1024]:
|
|
for w in [32, 64, 128, 256, 512, 1024]:
|
|
sizes.append((w, h))
|
|
sizes.sort(key = lambda (w, h): (w*h, max(w, h))) # prefer smaller and squarer
|
|
|
|
for w, h in sizes:
|
|
try:
|
|
#packer = Packer.DumbRectanglePacker(w, h)
|
|
packer = Packer.CygonRectanglePacker(w, h)
|
|
for g in glyphs:
|
|
g.pack(packer)
|
|
except Packer.OutOfSpaceError:
|
|
continue
|
|
|
|
ctx, surface = setup_context(w, h, face, size, renderstyle)
|
|
for g in glyphs:
|
|
g.render(ctx)
|
|
surface.write_to_png("%s.png" % outname)
|
|
|
|
# Output the .fnt file with all the glyph positions etc
|
|
fnt = open("%s.fnt" % outname, "w")
|
|
fnt.write("101\n")
|
|
fnt.write("%d %d\n" % (w, h))
|
|
fnt.write("%s\n" % ("rgba" if "colour" in renderstyle else "a"))
|
|
fnt.write("%d\n" % len(glyphs))
|
|
fnt.write("%d\n" % linespacing)
|
|
fnt.write("%d\n" % charheight)
|
|
glyphs.sort(key = lambda g: ord(g.char))
|
|
for g in glyphs:
|
|
fnt.write("%d %d %d %d %d %d %d %d\n" % (ord(g.char), g.pos.x, h-g.pos.y, g.w, g.h, -g.x0, g.y0, g.xadvance))
|
|
fnt.close()
|
|
|
|
return
|
|
print "Failed to fit glyphs in texture"
|
|
|
|
filled = { "fill": [(1, 1, 1, 1)] }
|
|
stroked1 = { "colour": True, "stroke": [((0, 0, 0, 1), 2.0), ((0, 0, 0, 1), 2.0)], "fill": [(1, 1, 1, 1)] }
|
|
stroked2 = { "colour": True, "stroke": [((0, 0, 0, 1), 2.0)], "fill": [(1, 1, 1, 1), (1, 1, 1, 1)] }
|
|
|
|
chars = load_char_list("charset.txt")
|
|
|
|
fonts = (
|
|
("mono-10", "DejaVuSansMono.ttf", 10, filled),
|
|
("mono-stroke-10", "DejaVuSansMono.ttf", 10, stroked2),
|
|
("sans-10", "DejaVuSans.ttf", 10, filled),
|
|
("serif-9", "ConvertedPagella-Regular.ttf", 9, filled),
|
|
("serif-12", "ConvertedPagella-Regular.ttf", 12, filled),
|
|
("serif-13", "ConvertedPagella-Regular.ttf", 13, filled),
|
|
("serif-14", "ConvertedPagella-Regular.ttf", 14, filled),
|
|
("serif-16", "ConvertedPagella-Regular.ttf", 16, filled),
|
|
("serif-bold-12", "ConvertedPagella-Bold.ttf", 12, filled),
|
|
("serif-bold-13", "ConvertedPagella-Bold.ttf", 13, filled),
|
|
("serif-bold-14", "ConvertedPagella-Bold.ttf", 14, filled),
|
|
("serif-bold-16", "ConvertedPagella-Bold.ttf", 16, filled),
|
|
("serif-bold-18", "ConvertedPagella-Bold.ttf", 18, filled),
|
|
("serif-stroke-14", "ConvertedPagella-Regular.ttf", 14, stroked1),
|
|
)
|
|
|
|
for (name, fontname, size, style) in fonts:
|
|
print "%s..." % name
|
|
generate_font(chars, "../../../binaries/data/mods/public/fonts/%s" % name, "../../../binaries/data/tools/fontbuilder/fonts/%s" % fontname, size, style)
|