123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- #!/usr/bin/env python3
- #
- # Copyright (c) 2018 Henrik Brix Andersen <henrik@brixandersen.dk>
- #
- # SPDX-License-Identifier: Apache-2.0
- import argparse
- import sys
- from PIL import ImageFont
- from PIL import Image
- from PIL import ImageDraw
- PRINTABLE_MIN = 32
- PRINTABLE_MAX = 126
- def generate_element(image, charcode):
- """Generate CFB font element for a given character code from an image"""
- blackwhite = image.convert("1", dither=Image.NONE)
- pixels = blackwhite.load()
- width, height = image.size
- if args.dump:
- blackwhite.save("{}_{}.png".format(args.name, charcode))
- if PRINTABLE_MIN <= charcode <= PRINTABLE_MAX:
- char = " ({:c})".format(charcode)
- else:
- char = ""
- args.output.write("""\t/* {:d}{} */\n\t{{\n""".format(charcode, char))
- glyph = []
- if args.hpack:
- for row in range(0, height):
- packed = []
- for octet in range(0, int(width / 8)):
- value = ""
- for bit in range(0, 8):
- col = octet * 8 + bit
- if pixels[col, row]:
- value = value + "0"
- else:
- value = value + "1"
- packed.append(value)
- glyph.append(packed)
- else:
- for col in range(0, width):
- packed = []
- for octet in range(0, int(height / 8)):
- value = ""
- for bit in range(0, 8):
- row = octet * 8 + bit
- if pixels[col, row]:
- value = value + "0"
- else:
- value = value + "1"
- packed.append(value)
- glyph.append(packed)
- for packed in glyph:
- args.output.write("\t\t")
- bits = []
- for value in packed:
- bits.append(value)
- if not args.msb_first:
- value = value[::-1]
- args.output.write("0x{:02x},".format(int(value, 2)))
- args.output.write(" /* {} */\n".format(''.join(bits).replace('0', ' ').replace('1', '#')))
- args.output.write("\t},\n")
- def extract_font_glyphs():
- """Extract font glyphs from a TrueType/OpenType font file"""
- font = ImageFont.truetype(args.input, args.size)
- # Figure out the bounding box for the desired glyphs
- fw_max = 0
- fh_max = 0
- for i in range(args.first, args.last + 1):
- fw, fh = font.getsize(chr(i))
- if fw > fw_max:
- fw_max = fw
- if fh > fh_max:
- fh_max = fh
- # Round the packed length up to pack into bytes.
- if args.hpack:
- width = 8 * int((fw_max + 7) / 8)
- height = fh_max + args.y_offset
- else:
- width = fw_max
- height = 8 * int((fh_max + args.y_offset + 7) / 8)
- # Diagnose inconsistencies with arguments
- if width != args.width:
- raise Exception('text width {} mismatch with -x {}'.format(width, args.width))
- if height != args.height:
- raise Exception('text height {} mismatch with -y {}'.format(height, args.height))
- for i in range(args.first, args.last + 1):
- image = Image.new('1', (width, height), 'white')
- draw = ImageDraw.Draw(image)
- fw, fh = draw.textsize(chr(i), font=font)
- xpos = 0
- if args.center_x:
- xpos = (width - fw) / 2 + 1
- ypos = args.y_offset
- draw.text((xpos, ypos), chr(i), font=font)
- generate_element(image, i)
- def extract_image_glyphs():
- """Extract font glyphs from an image file"""
- image = Image.open(args.input)
- x_offset = 0
- for i in range(args.first, args.last + 1):
- glyph = image.crop((x_offset, 0, x_offset + args.width, args.height))
- generate_element(glyph, i)
- x_offset += args.width
- def generate_header():
- """Generate CFB font header file"""
- caps = []
- if args.hpack:
- caps.append('MONO_HPACKED')
- else:
- caps.append('MONO_VPACKED')
- if args.msb_first:
- caps.append('MSB_FIRST')
- caps = ' | '.join(['CFB_FONT_' + f for f in caps])
- clean_cmd = []
- for arg in sys.argv:
- if arg.startswith("--bindir"):
- # Drop. Assumes --bindir= was passed with '=' sign.
- continue
- if args.bindir and arg.startswith(args.bindir):
- # +1 to also strip '/' or '\' separator
- striplen = min(len(args.bindir)+1, len(arg))
- clean_cmd.append(arg[striplen:])
- continue
- if args.zephyr_base is not None:
- clean_cmd.append(arg.replace(args.zephyr_base, '"${ZEPHYR_BASE}"'))
- else:
- clean_cmd.append(arg)
- args.output.write("""/*
- * This file was automatically generated using the following command:
- * {cmd}
- *
- */
- #include <zephyr.h>
- #include <display/cfb.h>
- static const uint8_t cfb_font_{name:s}_{width:d}{height:d}[{elem:d}][{b:.0f}] = {{\n"""
- .format(cmd=" ".join(clean_cmd),
- name=args.name,
- width=args.width,
- height=args.height,
- elem=args.last - args.first + 1,
- b=args.width / 8 * args.height))
- if args.type == "font":
- extract_font_glyphs()
- elif args.type == "image":
- extract_image_glyphs()
- elif args.input.name.lower().endswith((".otf", ".otc", ".ttf", ".ttc")):
- extract_font_glyphs()
- else:
- extract_image_glyphs()
- args.output.write("""
- }};
- FONT_ENTRY_DEFINE({name}_{width}{height},
- {width},
- {height},
- {caps},
- cfb_font_{name}_{width}{height},
- {first},
- {last}
- );
- """ .format(name=args.name, width=args.width, height=args.height,
- caps=caps, first=args.first, last=args.last))
- def parse_args():
- """Parse arguments"""
- global args
- parser = argparse.ArgumentParser(
- description="Character Frame Buffer (CFB) font header file generator",
- formatter_class=argparse.RawDescriptionHelpFormatter)
- parser.add_argument(
- "-z", "--zephyr-base",
- help="Zephyr base directory")
- parser.add_argument(
- "-d", "--dump", action="store_true",
- help="dump generated CFB font elements as images for preview")
- group = parser.add_argument_group("input arguments")
- group.add_argument(
- "-i", "--input", required=True, type=argparse.FileType('rb'), metavar="FILE",
- help="TrueType/OpenType file or image input file")
- group.add_argument(
- "-t", "--type", default="auto", choices=["auto", "font", "image"],
- help="Input file type (default: %(default)s)")
- group = parser.add_argument_group("font arguments")
- group.add_argument(
- "-s", "--size", type=int, default=10, metavar="POINTS",
- help="TrueType/OpenType font size in points (default: %(default)s)")
- group = parser.add_argument_group("output arguments")
- group.add_argument(
- "-o", "--output", type=argparse.FileType('w'), default="-", metavar="FILE",
- help="CFB font header file (default: stdout)")
- group.add_argument(
- "--bindir", type=str,
- help="CMAKE_BINARY_DIR for pure logging purposes. No trailing slash.")
- group.add_argument(
- "-x", "--width", required=True, type=int,
- help="width of the CFB font elements in pixels")
- group.add_argument(
- "-y", "--height", required=True, type=int,
- help="height of the CFB font elements in pixels")
- group.add_argument(
- "-n", "--name", default="custom",
- help="name of the CFB font entry (default: %(default)s)")
- group.add_argument(
- "--first", type=int, default=PRINTABLE_MIN, metavar="CHARCODE",
- help="character code mapped to the first CFB font element (default: %(default)s)")
- group.add_argument(
- "--last", type=int, default=PRINTABLE_MAX, metavar="CHARCODE",
- help="character code mapped to the last CFB font element (default: %(default)s)")
- group.add_argument(
- "--center-x", action='store_true',
- help="center character glyphs horizontally")
- group.add_argument(
- "--y-offset", type=int, default=0,
- help="vertical offset for character glyphs (default: %(default)s)")
- group.add_argument(
- "--hpack", dest='hpack', default=False, action='store_true',
- help="generate bytes encoding row data rather than column data (default: %(default)s)")
- group.add_argument(
- "--msb-first", action='store_true',
- help="packed content starts at high bit of each byte (default: lsb-first)")
- args = parser.parse_args()
- def main():
- """Parse arguments and generate CFB font header file"""
- parse_args()
- generate_header()
- if __name__ == "__main__":
- main()
|