blob: 0bba785bfe7f9ae10cec2e86ae628e9ad9ca80ff [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):
Jonathan Ricodb52d272023-08-22 22:34:27 +020079 # returns (left, top, right, bottom) bounding box
80 size = font.getbbox(chr(i))
81
82 # calculate width + height
83 fw = size[2] - size[0] # right - left
84 fh = size[3] - size[1] # bottom - top
85
Peter A. Bigot9a0d9e32019-07-15 10:05:10 -050086 if fw > fw_max:
87 fw_max = fw
88 if fh > fh_max:
89 fh_max = fh
90
Peter A. Bigotfd14c742019-07-15 16:55:13 -050091 # Round the packed length up to pack into bytes.
92 if args.hpack:
93 width = 8 * int((fw_max + 7) / 8)
94 height = fh_max + args.y_offset
95 else:
96 width = fw_max
97 height = 8 * int((fh_max + args.y_offset + 7) / 8)
Peter A. Bigot9a0d9e32019-07-15 10:05:10 -050098
99 # Diagnose inconsistencies with arguments
100 if width != args.width:
101 raise Exception('text width {} mismatch with -x {}'.format(width, args.width))
102 if height != args.height:
103 raise Exception('text height {} mismatch with -y {}'.format(height, args.height))
104
105 for i in range(args.first, args.last + 1):
106 image = Image.new('1', (width, height), 'white')
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100107 draw = ImageDraw.Draw(image)
Peter A. Bigot9a0d9e32019-07-15 10:05:10 -0500108
Jonathan Ricodb52d272023-08-22 22:34:27 +0200109 # returns (left, top, right, bottom) bounding box
110 size = draw.textbbox((0, 0), chr(i), font=font)
111
112 # calculate width + height
113 fw = size[2] - size[0] # right - left
114 fh = size[3] - size[1] # bottom - top
Peter A. Bigot9a0d9e32019-07-15 10:05:10 -0500115
116 xpos = 0
117 if args.center_x:
118 xpos = (width - fw) / 2 + 1
119 ypos = args.y_offset
120
121 draw.text((xpos, ypos), chr(i), font=font)
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100122 generate_element(image, i)
123
124def extract_image_glyphs():
125 """Extract font glyphs from an image file"""
126 image = Image.open(args.input)
127
128 x_offset = 0
129 for i in range(args.first, args.last + 1):
130 glyph = image.crop((x_offset, 0, x_offset + args.width, args.height))
131 generate_element(glyph, i)
132 x_offset += args.width
133
134def generate_header():
135 """Generate CFB font header file"""
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100136
Peter A. Bigotfd14c742019-07-15 16:55:13 -0500137 caps = []
138 if args.hpack:
139 caps.append('MONO_HPACKED')
140 else:
141 caps.append('MONO_VPACKED')
142 if args.msb_first:
143 caps.append('MSB_FIRST')
144 caps = ' | '.join(['CFB_FONT_' + f for f in caps])
145
Ulf Magnusson0d39a102019-09-06 11:13:19 +0200146 clean_cmd = []
Marc Herbert6f98db62019-06-11 15:50:04 -0700147 for arg in sys.argv:
148 if arg.startswith("--bindir"):
149 # Drop. Assumes --bindir= was passed with '=' sign.
150 continue
Peter A. Bigot98a8e292019-07-09 17:24:46 -0500151 if args.bindir and arg.startswith(args.bindir):
Marc Herbert6f98db62019-06-11 15:50:04 -0700152 # +1 to also strip '/' or '\' separator
153 striplen = min(len(args.bindir)+1, len(arg))
154 clean_cmd.append(arg[striplen:])
155 continue
156
Torsten Rasmussend7862cf2020-02-12 15:42:09 +0100157 if args.zephyr_base is not None:
158 clean_cmd.append(arg.replace(args.zephyr_base, '"${ZEPHYR_BASE}"'))
159 else:
160 clean_cmd.append(arg)
161
Marc Herbert6f98db62019-06-11 15:50:04 -0700162
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100163 args.output.write("""/*
164 * This file was automatically generated using the following command:
165 * {cmd}
166 *
167 */
168
Gerard Marull-Paretas79e6b0e2022-08-25 09:58:46 +0200169#include <zephyr/kernel.h>
Fabio Baltieri93f20d72022-05-25 16:35:50 +0100170#include <zephyr/display/cfb.h>
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100171
Kumar Galaa1b77fd2020-05-27 11:26:57 -0500172static const uint8_t cfb_font_{name:s}_{width:d}{height:d}[{elem:d}][{b:.0f}] = {{\n"""
Marc Herbert6f98db62019-06-11 15:50:04 -0700173 .format(cmd=" ".join(clean_cmd),
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100174 name=args.name,
175 width=args.width,
176 height=args.height,
177 elem=args.last - args.first + 1,
178 b=args.width / 8 * args.height))
179
180 if args.type == "font":
181 extract_font_glyphs()
182 elif args.type == "image":
183 extract_image_glyphs()
184 elif args.input.name.lower().endswith((".otf", ".otc", ".ttf", ".ttc")):
185 extract_font_glyphs()
186 else:
187 extract_image_glyphs()
188
189 args.output.write("""
190}};
191
192FONT_ENTRY_DEFINE({name}_{width}{height},
193 {width},
194 {height},
Peter A. Bigotfd14c742019-07-15 16:55:13 -0500195 {caps},
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100196 cfb_font_{name}_{width}{height},
197 {first},
198 {last}
199);
Peter A. Bigotf14b19b2019-07-18 06:14:51 -0500200""" .format(name=args.name, width=args.width, height=args.height,
Peter A. Bigotfd14c742019-07-15 16:55:13 -0500201 caps=caps, first=args.first, last=args.last))
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100202
203def parse_args():
204 """Parse arguments"""
205 global args
206 parser = argparse.ArgumentParser(
207 description="Character Frame Buffer (CFB) font header file generator",
Jamie McCraeec704442023-01-04 16:08:36 +0000208 formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False)
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100209
210 parser.add_argument(
Torsten Rasmussend7862cf2020-02-12 15:42:09 +0100211 "-z", "--zephyr-base",
212 help="Zephyr base directory")
213
214 parser.add_argument(
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100215 "-d", "--dump", action="store_true",
216 help="dump generated CFB font elements as images for preview")
217
218 group = parser.add_argument_group("input arguments")
219 group.add_argument(
220 "-i", "--input", required=True, type=argparse.FileType('rb'), metavar="FILE",
221 help="TrueType/OpenType file or image input file")
222 group.add_argument(
223 "-t", "--type", default="auto", choices=["auto", "font", "image"],
224 help="Input file type (default: %(default)s)")
225
226 group = parser.add_argument_group("font arguments")
227 group.add_argument(
228 "-s", "--size", type=int, default=10, metavar="POINTS",
229 help="TrueType/OpenType font size in points (default: %(default)s)")
230
231 group = parser.add_argument_group("output arguments")
232 group.add_argument(
233 "-o", "--output", type=argparse.FileType('w'), default="-", metavar="FILE",
234 help="CFB font header file (default: stdout)")
235 group.add_argument(
Peter A. Bigot98a8e292019-07-09 17:24:46 -0500236 "--bindir", type=str,
Marc Herbert6f98db62019-06-11 15:50:04 -0700237 help="CMAKE_BINARY_DIR for pure logging purposes. No trailing slash.")
238 group.add_argument(
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100239 "-x", "--width", required=True, type=int,
240 help="width of the CFB font elements in pixels")
241 group.add_argument(
Peter A. Bigotfd14c742019-07-15 16:55:13 -0500242 "-y", "--height", required=True, type=int,
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100243 help="height of the CFB font elements in pixels")
244 group.add_argument(
245 "-n", "--name", default="custom",
246 help="name of the CFB font entry (default: %(default)s)")
247 group.add_argument(
248 "--first", type=int, default=PRINTABLE_MIN, metavar="CHARCODE",
249 help="character code mapped to the first CFB font element (default: %(default)s)")
250 group.add_argument(
251 "--last", type=int, default=PRINTABLE_MAX, metavar="CHARCODE",
252 help="character code mapped to the last CFB font element (default: %(default)s)")
Peter A. Bigot9a0d9e32019-07-15 10:05:10 -0500253 group.add_argument(
254 "--center-x", action='store_true',
255 help="center character glyphs horizontally")
256 group.add_argument(
257 "--y-offset", type=int, default=0,
258 help="vertical offset for character glyphs (default: %(default)s)")
Peter A. Bigotfd14c742019-07-15 16:55:13 -0500259 group.add_argument(
260 "--hpack", dest='hpack', default=False, action='store_true',
261 help="generate bytes encoding row data rather than column data (default: %(default)s)")
262 group.add_argument(
263 "--msb-first", action='store_true',
264 help="packed content starts at high bit of each byte (default: lsb-first)")
Henrik Brix Andersen9e8c9ca2018-11-08 20:47:39 +0100265
266 args = parser.parse_args()
267
268def main():
269 """Parse arguments and generate CFB font header file"""
270 parse_args()
271 generate_header()
272
273if __name__ == "__main__":
274 main()