Skip to content

Commit

Permalink
Add Glyph.set_bitmap() method
Browse files Browse the repository at this point in the history
  • Loading branch information
mikee47 committed Dec 23, 2024
1 parent 51db454 commit 22ed9be
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 130 deletions.
146 changes: 50 additions & 96 deletions Tools/rc/rclib/font.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import sys
import struct
from .base import Resource, findFile, StructSize, fstrSize
from PIL import Image

class FontStyle(enum.Enum):
"""Style is a set of these values, using strings here but bitfields in library"""
Expand All @@ -50,116 +51,69 @@ def evaluate(value: list[str]):


class Glyph(Resource):
class Alpha(enum.IntEnum):
L1 = 0
L8 = 1
L2 = 2
L4 = 3

def __init__(self, typeface):
def __init__(self, typeface: Typeface):
super().__init__()
self.typeface = typeface
self.typeface: Typeface = typeface
self.codePoint = None
self.bitmap = None
self.bitmap: bytearray = None
self.width = None
self.height = None
self.xOffset = None
self.yOffset = None
self.xAdvance = None
self.alpha = Glyph.Alpha.L1

def packBits(self, rows, width):
""" Convert bitmap to internal (GFX) format
Source bitmap source data is array.array('I') of row bitmap data,
with last bit in position 0.
We identify defined area, exclude surround empty region, then pack bits
and update glyph details.
self.alpha = 1

def set_bitmap(self, img: Image):
""" Convert bitmap to internal (GFX) format and set width, height.
We remove space round the glyph, update glyph offsets and pack the resulting bits.
"""
height = len(rows)

# Identify leading/trailing blank rows and columns
leadingRows = trailingRows = 0
leadingCols = trailingCols = width
started = False
for row in rows:
if row == 0:
if started:
trailingRows += 1
else:
leadingRows += 1
else:
started = True
trailingRows = 0
# Examine columns
n = 0
mask = 1 << (width - 1)
for i in range(width):
if row & mask != 0:
break
n += 1
mask >>= 1
leadingCols = min(leadingCols, n)
mask = 0x01
n = 0
for i in range(width):
if row & mask != 0:
break
n += 1
mask <<= 1
trailingCols = min(trailingCols, n)

# print("leadingRows %u, trailingRows %u, height %u" % (leadingRows, trailingRows, height))

if leadingRows == height:
self.width = self.height = self.xOffset = self.yOffset = 0
self.bitmap = bytearray(0)
w, h = img.width, img.height
self.width = w
self.height = h
stride = w # All modes 1 byte per pixel
self.alpha = 1 if img.mode == '1' else self.typeface.font.alpha
pixels_per_byte = 8 // self.alpha
dstsize = (w * h + pixels_per_byte - 1) // pixels_per_byte

# Pack source bits so resulting data is as compact as possible
imgdata = img.tobytes('raw', ('L'))

# print(f'Bitmap {w} x {h}, bpp {bpp}, src {len(imgdata)} bytes, dst {dstsize}')

if self.alpha == 8:
self.bitmap = imgdata
return

# Existing glyph attributes should only be adjusted
if self.width is None:
self.width = width
self.width -= leadingCols + trailingCols
if self.height is None:
self.height = height
self.height -= leadingRows + trailingRows
if self.xOffset is None:
self.xOffset = 0
self.xOffset += leadingCols
if self.yOffset is None:
self.yOffset = -height
self.yOffset += leadingRows

# print("glyph %d x %d, leadingRows %u, trailingRows %u, leadingCols %u, trailingCols %u"
# % (self.width, self.height, leadingRows, trailingRows, leadingCols, trailingCols))

n = self.height
destBytes = (self.width * self.height + 7) // 8
self.bitmap = bytearray(destBytes)
off = 0
dstmask = 0x80
for i in range(n):
row = rows[leadingRows + i]
srcmask = 1 << (width - leadingCols - 1)
for j in range(self.width):
if row & srcmask != 0:
self.bitmap[off] |= dstmask
srcmask >>= 1
dstmask >>= 1
if dstmask == 0:
dstmask = 0x80
off += 1
# print("packBits %u; width %u, height %u, leading %u, trailing %u" % (len(src), width, height, leading, trailing))
dstbuf = bytearray(dstsize)
srcoff = 0
dstoff = 0
dstbits = 0
dstbitlen = 0
mask = 1 << 8
for bit in imgdata:
mask >>= 1
if bit:
dstbits |= mask
dstbitlen += 1
if dstbitlen == 8:
dstbuf[dstoff] = dstbits
dstoff += 1
dstbits = 0
dstbitlen = 0
mask = 1 << 8
if dstbitlen:
dstbuf[dstoff] = dstbits
self.bitmap = dstbuf


class Typeface(Resource):
def __init__(self, font, style):
def __init__(self, font: Font, style):
super().__init__()
self.font = font
self.font: Font = font
self.style = style
self.bitmap = None
self.yAdvance = None
self.descent = None
self.glyphs = []
self.glyphs: list[Glyph] = []
self.headerSize = 0

def serialize(self, bmOffset, res_offset, ptr64: bool):
Expand Down Expand Up @@ -232,7 +186,7 @@ def writeGlyphRecords(self, out):
c = ''
elif c == '\\':
c = "'\\'"
out.write("\t{ 0x%04x, %3u, %3u, %3d, %3d, %3u, %u }, // #0x%04x %s \n" %
out.write("\t{ 0x%04x, %3u, %3u, %3d, %3d, %3u, GlyphResource::L%u }, // #0x%04x %s \n" %
(bmOffset, g.width, g.height, g.xOffset, g.yOffset, g.xAdvance, g.alpha, g.codePoint, c))
bmOffset += len(g.bitmap)
self.headerSize += StructSize.GlyphResource
Expand Down Expand Up @@ -300,7 +254,7 @@ def __init__(self):
self.yAdvance = 0
self.descent = 0
self.headerSize = 0
self.alpha: Glyph.Alpha = None
self.alpha = 1

def serialize(self, bmOffset, res_offset, ptr64: bool):
resdata = b''
Expand Down Expand Up @@ -402,7 +356,7 @@ def val(s):
font.name = name
font.pointSize = item.get('size')
mono = item.get('mono', False)
font.alpha = item.get('alpha', Glyph.Alpha.L1 if mono else Glyph.Alpha.L8)
font.alpha = item.get('alpha', 1 if mono else 8)
font.codePoints = codePoints

def add(name, style):
Expand Down
22 changes: 4 additions & 18 deletions Tools/rc/rclib/freetype.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ def print_bitmap_diff(bitmap: freetype.Bitmap):
def parse_typeface(typeface: Typeface):
face = freetype.Face(typeface.source)

# printDetails(face)

if typeface.font.pointSize is None:
face.select_size(0)
else:
Expand All @@ -72,32 +70,20 @@ def parse_typeface(typeface: Typeface):
if index == 0:
continue # No glyph for this codepoint
flags = freetype.FT_LOAD_RENDER
if not typeface.font.alpha:
if typeface.font.alpha == 1:
flags |= freetype.FT_LOAD_MONOCHROME | freetype.FT_LOAD_TARGET_MONO
face.load_glyph(index, flags)
bitmap = face.glyph.bitmap
w, h = bitmap.width, bitmap.rows
g = Glyph(typeface)
g.codePoint = c
g.width = w
g.height = h
g.xAdvance = pointsToPixels(face.glyph.advance.x)
g.xOffset = face.glyph.bitmap_left
g.yOffset = 1 - face.glyph.bitmap_top

if w + h == 0:
g.bitmap = bytearray(0)
else:
if bitmap.pixel_mode == freetype.FT_PIXEL_MODE_MONO:
img = Image.frombuffer('1', (w, h), bytearray(bitmap.buffer), 'raw', ('1', bitmap.pitch))
img = img.convert('L')
imgdata = img.tobytes()
g.alpha = Glyph.Alpha.L8
g.bitmap = imgdata
else:
g.alpha = Glyph.Alpha.L8
g.bitmap = bytearray(bitmap.buffer)

mode = '1' if bitmap.pixel_mode == freetype.FT_PIXEL_MODE_MONO else 'L'
img = Image.frombuffer(mode, (w, h), bytearray(bitmap.buffer), 'raw', (mode, bitmap.pitch, 1))
g.set_bitmap(img)
typeface.glyphs.append(g)


Expand Down
8 changes: 2 additions & 6 deletions Tools/rc/rclib/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,11 @@ def parse_typeface(typeface):
if codePoint in typeface.font.codePoints:
g = Glyph(typeface)
g.codePoint = codePoint
img = Image.frombuffer('1', (width, height), bitmap[offset : offset+bmp_bytes], 'raw', ('1', stride))
img = img.convert('L')
g.width = width
g.height = height
g.xOffset = 0
g.yOffset = -height
g.alpha = Glyph.Alpha.L8
g.bitmap = img.tobytes()
g.xAdvance = 1 + width
img = Image.frombuffer('1', (width, height), bitmap[offset : offset+bmp_bytes], 'raw', ('1', stride))
g.set_bitmap(img)
typeface.glyphs.append(g)
offset += bmp_bytes

Expand Down
14 changes: 5 additions & 9 deletions Tools/rc/rclib/pfi.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def parse_typeface(typeface):
pbmFilename = os.path.splitext(typeface.source)[0] + '.pbm'
pbm = Image.open(pbmFilename)
pbm = ImageOps.invert(pbm)
print(f'{os.path.basename(pbmFilename)}: {pbm.size}, mode {pbm.mode}')
# print(f'{os.path.basename(pbmFilename)}: {pbm.size}, mode {pbm.mode}')

for line in pfi[3:]:
line = line.split(' ')
Expand All @@ -69,19 +69,15 @@ def parse_typeface(typeface):
else:
w = width
g.xAdvance = 1 + w
g.xOffset = 0
g.yOffset = -height

if line and line[0] != '':
xo, yo = int(line[0]), int(line[1])
img = pbm.crop((xo, yo, xo+w, yo+height))
img = img.convert('L')
g.bitmap = img.tobytes()
else:
g.bitmap = bytearray(w*height)
g.width = w
g.height = height
g.xOffset = 0
g.yOffset = -height
g.alpha = Glyph.Alpha.L8
img = Image.new('1', (w, height))
g.set_bitmap(img)
typeface.glyphs.append(g)

def sortkey(g):
Expand Down
2 changes: 1 addition & 1 deletion Tools/rc/rclib/vlw.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def parse_typeface(typeface):
descent = max(descent, g.height - topExtent)
ascent = max(ascent, topExtent)
g.yOffset = -topExtent
g.alpha = Glyph.Alpha.L8
g.alpha = 8
g.bitmap = data[bmOffset:bmOffset+bmSize]
typeface.glyphs.append(g)
offset += GLYPH_HEADER_SIZE
Expand Down

0 comments on commit 22ed9be

Please sign in to comment.