blob: 380237e12f9c4ba808db5e17e2ef2d7eac762e68 [file] [log] [blame]
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +01001#!/usr/bin/env python3
2#
3# Copyright (c) 2018 Henrik Brix Andersen <henrik@brixandersen.dk>
4#
5# SPDX-License-Identifier: Apache-2.0
6
7import argparse
8import sys
9
10from PIL import ImageFont
11from PIL import Image
12from PIL import ImageDraw
13
14PRINTABLE_MIN = 32
Peter A. Bigot8b4d5292019-07-09 17:09:20 -050015PRINTABLE_MAX = 126
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +010016
17def generate_element(image, charcode):
18 """Generate CFB font element for a given character code from an image"""
19 blackwhite = image.convert("1", dither=Image.NONE)
20 pixels = blackwhite.load()
21
Peter A. Bigot9a0d9e32019-07-15 10:05:10 -050022 width, height = image.size
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +010023 if args.dump:
24 blackwhite.save("{}_{}.png".format(args.name, charcode))
25
26 if PRINTABLE_MIN <= charcode <= PRINTABLE_MAX:
27 char = " ({:c})".format(charcode)
28 else:
29 char = ""
30
31 args.output.write("""\t/* {:d}{} */\n\t{{\n""".format(charcode, char))
32
Peter A. Bigotfd14c742019-07-15 16:55:13 -050033 glyph = []
34 if args.hpack:
35 for row in range(0, height):
36 packed = []
37 for octet in range(0, int(width / 8)):
38 value = ""
39 for bit in range(0, 8):
40 col = octet * 8 + bit
41 if pixels[col, row]:
42 value = value + "0"
43 else:
44 value = value + "1"
45 packed.append(value)
46 glyph.append(packed)
47 else:
48 for col in range(0, width):
49 packed = []
50 for octet in range(0, int(height / 8)):
51 value = ""
52 for bit in range(0, 8):
53 row = octet * 8 + bit
54 if pixels[col, row]:
55 value = value + "0"
56 else:
57 value = value + "1"
58 packed.append(value)
59 glyph.append(packed)
60 for packed in glyph:
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +010061 args.output.write("\t\t")
Peter A. Bigotfd14c742019-07-15 16:55:13 -050062 bits = []
63 for value in packed:
64 bits.append(value)
65 if not args.msb_first:
66 value = value[::-1]
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +010067 args.output.write("0x{:02x},".format(int(value, 2)))
Peter A. Bigotfd14c742019-07-15 16:55:13 -050068 args.output.write(" /* {} */\n".format(''.join(bits).replace('0', ' ').replace('1', '#')))
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +010069 args.output.write("\t},\n")
70
71def extract_font_glyphs():
72 """Extract font glyphs from a TrueType/OpenType font file"""
73 font = ImageFont.truetype(args.input, args.size)
Peter A. Bigot9a0d9e32019-07-15 10:05:10 -050074
75 # Figure out the bounding box for the desired glyphs
76 fw_max = 0
77 fh_max = 0
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +010078 for i in range(args.first, args.last + 1):
Peter A. Bigot9a0d9e32019-07-15 10:05:10 -050079 fw, fh = font.getsize(chr(i))
80 if fw > fw_max:
81 fw_max = fw
82 if fh > fh_max:
83 fh_max = fh
84
Peter A. Bigotfd14c742019-07-15 16:55:13 -050085 # Round the packed length up to pack into bytes.
86 if args.hpack:
87 width = 8 * int((fw_max + 7) / 8)
88 height = fh_max + args.y_offset
89 else:
90 width = fw_max
91 height = 8 * int((fh_max + args.y_offset + 7) / 8)
Peter A. Bigot9a0d9e32019-07-15 10:05:10 -050092
93 # Diagnose inconsistencies with arguments
94 if width != args.width:
95 raise Exception('text width {} mismatch with -x {}'.format(width, args.width))
96 if height != args.height:
97 raise Exception('text height {} mismatch with -y {}'.format(height, args.height))
98
99 for i in range(args.first, args.last + 1):
100 image = Image.new('1', (width, height), 'white')
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100101 draw = ImageDraw.Draw(image)
Peter A. Bigot9a0d9e32019-07-15 10:05:10 -0500102
103 fw, fh = draw.textsize(chr(i), font=font)
104
105 xpos = 0
106 if args.center_x:
107 xpos = (width - fw) / 2 + 1
108 ypos = args.y_offset
109
110 draw.text((xpos, ypos), chr(i), font=font)
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100111 generate_element(image, i)
112
113def extract_image_glyphs():
114 """Extract font glyphs from an image file"""
115 image = Image.open(args.input)
116
117 x_offset = 0
118 for i in range(args.first, args.last + 1):
119 glyph = image.crop((x_offset, 0, x_offset + args.width, args.height))
120 generate_element(glyph, i)
121 x_offset += args.width
122
123def generate_header():
124 """Generate CFB font header file"""
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100125
Peter A. Bigotfd14c742019-07-15 16:55:13 -0500126 caps = []
127 if args.hpack:
128 caps.append('MONO_HPACKED')
129 else:
130 caps.append('MONO_VPACKED')
131 if args.msb_first:
132 caps.append('MSB_FIRST')
133 caps = ' | '.join(['CFB_FONT_' + f for f in caps])
134
Ulf Magnusson0d39a102019-09-06 11:13:19 +0200135 clean_cmd = []
Marc Herbert6f98db62019-06-11 15:50:04 -0700136 for arg in sys.argv:
137 if arg.startswith("--bindir"):
138 # Drop. Assumes --bindir= was passed with '=' sign.
139 continue
Peter A. Bigot98a8e292019-07-09 17:24:46 -0500140 if args.bindir and arg.startswith(args.bindir):
Marc Herbert6f98db62019-06-11 15:50:04 -0700141 # +1 to also strip '/' or '\' separator
142 striplen = min(len(args.bindir)+1, len(arg))
143 clean_cmd.append(arg[striplen:])
144 continue
145
Torsten Rasmussend7862cf2020-02-12 15:42:09 +0100146 if args.zephyr_base is not None:
147 clean_cmd.append(arg.replace(args.zephyr_base, '"${ZEPHYR_BASE}"'))
148 else:
149 clean_cmd.append(arg)
150
Marc Herbert6f98db62019-06-11 15:50:04 -0700151
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100152 args.output.write("""/*
153 * This file was automatically generated using the following command:
154 * {cmd}
155 *
156 */
157
Gerard Marull-Paretas79e6b0e2022-08-25 09:58:46 +0200158#include <zephyr/kernel.h>
Fabio Baltieri93f20d72022-05-25 16:35:50 +0100159#include <zephyr/display/cfb.h>
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100160
Kumar Galaa1b77fd2020-05-27 11:26:57 -0500161static const uint8_t cfb_font_{name:s}_{width:d}{height:d}[{elem:d}][{b:.0f}] = {{\n"""
Marc Herbert6f98db62019-06-11 15:50:04 -0700162 .format(cmd=" ".join(clean_cmd),
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100163 name=args.name,
164 width=args.width,
165 height=args.height,
166 elem=args.last - args.first + 1,
167 b=args.width / 8 * args.height))
168
169 if args.type == "font":
170 extract_font_glyphs()
171 elif args.type == "image":
172 extract_image_glyphs()
173 elif args.input.name.lower().endswith((".otf", ".otc", ".ttf", ".ttc")):
174 extract_font_glyphs()
175 else:
176 extract_image_glyphs()
177
178 args.output.write("""
179}};
180
181FONT_ENTRY_DEFINE({name}_{width}{height},
182 {width},
183 {height},
Peter A. Bigotfd14c742019-07-15 16:55:13 -0500184 {caps},
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100185 cfb_font_{name}_{width}{height},
186 {first},
187 {last}
188);
Peter A. Bigotf14b19b2019-07-18 06:14:51 -0500189""" .format(name=args.name, width=args.width, height=args.height,
Peter A. Bigotfd14c742019-07-15 16:55:13 -0500190 caps=caps, first=args.first, last=args.last))
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100191
192def parse_args():
193 """Parse arguments"""
194 global args
195 parser = argparse.ArgumentParser(
196 description="Character Frame Buffer (CFB) font header file generator",
197 formatter_class=argparse.RawDescriptionHelpFormatter)
198
199 parser.add_argument(
Torsten Rasmussend7862cf2020-02-12 15:42:09 +0100200 "-z", "--zephyr-base",
201 help="Zephyr base directory")
202
203 parser.add_argument(
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100204 "-d", "--dump", action="store_true",
205 help="dump generated CFB font elements as images for preview")
206
207 group = parser.add_argument_group("input arguments")
208 group.add_argument(
209 "-i", "--input", required=True, type=argparse.FileType('rb'), metavar="FILE",
210 help="TrueType/OpenType file or image input file")
211 group.add_argument(
212 "-t", "--type", default="auto", choices=["auto", "font", "image"],
213 help="Input file type (default: %(default)s)")
214
215 group = parser.add_argument_group("font arguments")
216 group.add_argument(
217 "-s", "--size", type=int, default=10, metavar="POINTS",
218 help="TrueType/OpenType font size in points (default: %(default)s)")
219
220 group = parser.add_argument_group("output arguments")
221 group.add_argument(
222 "-o", "--output", type=argparse.FileType('w'), default="-", metavar="FILE",
223 help="CFB font header file (default: stdout)")
224 group.add_argument(
Peter A. Bigot98a8e292019-07-09 17:24:46 -0500225 "--bindir", type=str,
Marc Herbert6f98db62019-06-11 15:50:04 -0700226 help="CMAKE_BINARY_DIR for pure logging purposes. No trailing slash.")
227 group.add_argument(
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100228 "-x", "--width", required=True, type=int,
229 help="width of the CFB font elements in pixels")
230 group.add_argument(
Peter A. Bigotfd14c742019-07-15 16:55:13 -0500231 "-y", "--height", required=True, type=int,
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100232 help="height of the CFB font elements in pixels")
233 group.add_argument(
234 "-n", "--name", default="custom",
235 help="name of the CFB font entry (default: %(default)s)")
236 group.add_argument(
237 "--first", type=int, default=PRINTABLE_MIN, metavar="CHARCODE",
238 help="character code mapped to the first CFB font element (default: %(default)s)")
239 group.add_argument(
240 "--last", type=int, default=PRINTABLE_MAX, metavar="CHARCODE",
241 help="character code mapped to the last CFB font element (default: %(default)s)")
Peter A. Bigot9a0d9e32019-07-15 10:05:10 -0500242 group.add_argument(
243 "--center-x", action='store_true',
244 help="center character glyphs horizontally")
245 group.add_argument(
246 "--y-offset", type=int, default=0,
247 help="vertical offset for character glyphs (default: %(default)s)")
Peter A. Bigotfd14c742019-07-15 16:55:13 -0500248 group.add_argument(
249 "--hpack", dest='hpack', default=False, action='store_true',
250 help="generate bytes encoding row data rather than column data (default: %(default)s)")
251 group.add_argument(
252 "--msb-first", action='store_true',
253 help="packed content starts at high bit of each byte (default: lsb-first)")
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100254
255 args = parser.parse_args()
256
257def main():
258 """Parse arguments and generate CFB font header file"""
259 parse_args()
260 generate_header()
261
262if __name__ == "__main__":
263 main()