blob: 26d91e3f810dfc456cb50002265d6573a7937828 [file] [log] [blame]
Yu-Ping Wud71b4452020-06-16 11:00:26 +08001#!/usr/bin/env python
Hung-Te Lin707e2ef2013-08-06 10:20:04 +08002# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +08005"""Script to generate bitmaps for firmware screens."""
Hung-Te Lin707e2ef2013-08-06 10:20:04 +08006
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +08007import argparse
8from collections import defaultdict, namedtuple
9import copy
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080010import glob
Jes Klinke1687a992020-06-16 13:47:17 -070011import json
Hung-Te Lin04addcc2015-03-23 18:43:30 +080012import multiprocessing
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080013import os
14import re
Jes Klinke1687a992020-06-16 13:47:17 -070015import shutil
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080016import signal
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080017import subprocess
18import sys
Jes Klinke1687a992020-06-16 13:47:17 -070019import tempfile
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080020from xml.etree import ElementTree
Hung-Te Lin04addcc2015-03-23 18:43:30 +080021
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080022import yaml
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080023from PIL import Image
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080024
25SCRIPT_BASE = os.path.dirname(os.path.abspath(__file__))
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080026
27STRINGS_GRD_FILE = 'firmware_strings.grd'
28STRINGS_JSON_FILE_TMPL = '{}.json'
29FORMAT_FILE = 'format.yaml'
30BOARDS_CONFIG_FILE = 'boards.yaml'
31
32TXT_TO_PNG_SVG = os.path.join(SCRIPT_BASE, 'text_to_png_svg')
33STRINGS_DIR = os.path.join(SCRIPT_BASE, 'strings')
34LOCALE_DIR = os.path.join(STRINGS_DIR, 'locale')
35OUTPUT_DIR = os.getenv('OUTPUT', os.path.join(SCRIPT_BASE, 'build'))
36STAGE_DIR = os.path.join(OUTPUT_DIR, '.stage')
37STAGE_LOCALE_DIR = os.path.join(STAGE_DIR, 'locale')
38STAGE_FONT_DIR = os.path.join(STAGE_DIR, 'font')
39
40ONE_LINE_DIR = 'one_line'
41SVG_FILES = '*.svg'
42PNG_FILES = '*.png'
43
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +080044DIAGNOSTIC_UI = os.getenv('DIAGNOSTIC_UI') == '1'
45
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080046# String format YAML key names.
Yu-Ping Wu10cf2892020-08-10 17:20:11 +080047DEFAULT_NAME = '_DEFAULT_'
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080048KEY_LOCALES = 'locales'
Yu-Ping Wu338f0832020-10-23 16:14:40 +080049KEY_GENERIC_FILES = 'generic_files'
50KEY_LOCALIZED_FILES = 'localized_files'
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080051KEY_FONTS = 'fonts'
52KEY_STYLES = 'styles'
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +080053KEY_BGCOLOR = 'bgcolor'
54KEY_FGCOLOR = 'fgcolor'
55KEY_HEIGHT = 'height'
Matt Delco4c5580d2019-03-07 14:00:28 -080056DIAGNOSTIC_FILES = 'diagnostic_files'
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080057
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080058# Board config YAML key names.
59SCREEN_KEY = 'screen'
60PANEL_KEY = 'panel'
61SDCARD_KEY = 'sdcard'
62BAD_USB3_KEY = 'bad_usb3'
63LOCALES_KEY = 'locales'
64RTL_KEY = 'rtl'
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080065TEXT_COLORS_KEY = 'text_colors'
66RW_OVERRIDE_KEY = 'rw_override'
67
68BMP_HEADER_OFFSET_NUM_LINES = 6
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080069
Jes Klinke1687a992020-06-16 13:47:17 -070070# Regular expressions used to eliminate spurious spaces and newlines in
71# translation strings.
72NEWLINE_PATTERN = re.compile(r'([^\n])\n([^\n])')
73NEWLINE_REPLACEMENT = r'\1 \2'
74CRLF_PATTERN = re.compile(r'\r\n')
75MULTIBLANK_PATTERN = re.compile(r' *')
76
Yu-Ping Wu11027f02020-10-14 17:35:42 +080077GLYPH_FONT = 'Noto Sans Mono'
78
Yu-Ping Wuabb9afb2020-10-27 17:15:22 +080079LocaleInfo = namedtuple('LocaleInfo', ['code', 'rtl'])
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080080
Yu-Ping Wu6b282c52020-03-19 12:54:15 +080081
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080082class DataError(Exception):
83 pass
84
85
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080086class BuildImageError(Exception):
87 """The exception class for all errors generated during build image process."""
88
89
Hsuan Ting Chenf2917582020-10-21 13:50:08 +080090# Some of the numbers below are from depthcharge:
91# - 1000: UI_SCALE
92# - 50: UI_MARGIN_H
93# - 228: UI_REC_QR_SIZE
94# - 24: UI_REC_QR_MARGIN_H
95# - 24: UI_DESC_TEXT_HEIGHT
96# - 128: UI_FOOTER_HEIGHT
97# - 20: UI_FOOTER_COL1_MARGIN_RIGHT
98# - 282: width of rec_url.bmp
99# - 40: UI_FOOTER_COL2_MARGIN_RIGHT
100# - 40: UI_FOOTER_COL3_MARGIN_LEFT
101# - 20: UI_FOOTER_TEXT_HEIGHT
102
103DEFAULT_MAX_WIDTH = 1000 - 50 * 2
104
105MAX_WIDTH_LOOKUPS = [
106 (re.compile(r'rec_phone_step2_desc'), DEFAULT_MAX_WIDTH - 228 - 24 * 2, 24),
107 (re.compile(r'navigate\w*'), DEFAULT_MAX_WIDTH - 128 - 20 - 282 - 40 - 40,
108 20),
109 (re.compile(r'\w+_desc\w*'), DEFAULT_MAX_WIDTH, 24),
110]
111
112
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800113def get_config_with_defaults(configs, key):
114 """Gets config of `key` from `configs`.
115
116 If `key` is not present in `configs`, the default config will be returned.
117 Similarly, if some config values are missing for `key`, the default ones will
118 be used.
119 """
120 config = configs[DEFAULT_NAME].copy()
121 config.update(configs.get(key, {}))
122 return config
123
124
Yu-Ping Wu11027f02020-10-14 17:35:42 +0800125def convert_text_to_png(locale, input_file, font, margin, output_dir,
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800126 bgcolor='#000000', fgcolor='#ffffff', **options):
Yu-Ping Wu11027f02020-10-14 17:35:42 +0800127 """Converts text files into PNG image files.
128
129 Args:
130 locale: Locale (language) to select implicit rendering options. None for
131 locale-independent strings.
132 input_file: Path of input text file.
133 font: Font spec.
134 margin: CSS-style margin.
135 output_dir: Directory to generate image files.
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800136 bgcolor: Background color (#rrggbb).
137 fgcolor: Foreground color (#rrggbb).
Yu-Ping Wu338f0832020-10-23 16:14:40 +0800138 **options: Other options to be added.
Yu-Ping Wu11027f02020-10-14 17:35:42 +0800139 """
140 name, _ = os.path.splitext(os.path.basename(input_file))
141 command = [TXT_TO_PNG_SVG, '--outdir=%s' % output_dir]
142 if locale:
143 command.append('--lan=%s' % locale)
144 if font:
145 command.append("--font='%s'" % font)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800146 font_size = os.getenv('FONT_SIZE')
Yu-Ping Wu11027f02020-10-14 17:35:42 +0800147 if font_size:
148 command.append('--point=%r' % font_size)
149 if margin:
150 command.append('--margin="%s"' % margin)
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800151 command.append('--bgcolor="%s"' % bgcolor)
152 command.append('--color="%s"' % fgcolor)
153
Yu-Ping Wu11027f02020-10-14 17:35:42 +0800154 # TODO(b/159399377): Set different widths for titles and descriptions.
Hsuan Ting Chenf2917582020-10-21 13:50:08 +0800155 # Currently only wrap lines for descriptions and navigation instructions in
156 # the footer.
157 # Without the --width option set, the minimum height of the output SVG
158 # image is roughly 22px (for locale 'en'). With --width=WIDTH passed to
159 # pango-view, the width of the output seems to always be (WIDTH * 4 / 3),
160 # regardless of the font being used. Therefore, set the max_width in
161 # points as follows to prevent drawing from exceeding canvas boundary in
162 # depthcharge runtime.
163
164 for pattern, max_width, text_height in MAX_WIDTH_LOOKUPS:
165 if pattern.fullmatch(name):
166 max_width_pt = int(22 * max_width / text_height / (4 / 3))
167 command.append('--width=%d' % max_width_pt)
168 break
169
Yu-Ping Wu338f0832020-10-23 16:14:40 +0800170 for k, v in options.items():
171 command.append('--%s="%s"' % (k, v))
Yu-Ping Wu11027f02020-10-14 17:35:42 +0800172 command.append(input_file)
173
174 return subprocess.call(' '.join(command), shell=True,
175 stdout=subprocess.PIPE) == 0
176
177
178def convert_glyphs():
179 """Converts glyphs of ascii characters."""
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800180 os.makedirs(STAGE_FONT_DIR, exist_ok=True)
Yu-Ping Wu11027f02020-10-14 17:35:42 +0800181 # Remove the extra whitespace at the top/bottom within the glyphs
182 margin = '-3 0 -1 0'
Yu-Ping Wu11027f02020-10-14 17:35:42 +0800183 for c in range(ord(' '), ord('~') + 1):
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800184 txt_file = os.path.join(STAGE_FONT_DIR, f'idx{c:03d}_{c:02x}.txt')
Yu-Ping Wu11027f02020-10-14 17:35:42 +0800185 with open(txt_file, 'w', encoding='ascii') as f:
186 f.write(chr(c))
187 f.write('\n')
188 # TODO(b/163109632): Parallelize the conversion of glyphs
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800189 convert_text_to_png(None, txt_file, GLYPH_FONT, margin, STAGE_FONT_DIR)
Yu-Ping Wu11027f02020-10-14 17:35:42 +0800190
191
Yu-Ping Wuae79af62020-09-23 16:48:06 +0800192def _load_locale_json_file(locale, json_dir):
Jes Klinke1687a992020-06-16 13:47:17 -0700193 result = {}
Yu-Ping Wuae79af62020-09-23 16:48:06 +0800194 filename = os.path.join(json_dir, STRINGS_JSON_FILE_TMPL.format(locale))
Yu-Ping Wud71b4452020-06-16 11:00:26 +0800195 with open(filename, encoding='utf-8-sig') as input_file:
Jes Klinke1687a992020-06-16 13:47:17 -0700196 for tag, msgdict in json.load(input_file).items():
197 msgtext = msgdict['message']
198 msgtext = re.sub(CRLF_PATTERN, '\n', msgtext)
199 msgtext = re.sub(NEWLINE_PATTERN, NEWLINE_REPLACEMENT, msgtext)
200 msgtext = re.sub(MULTIBLANK_PATTERN, ' ', msgtext)
201 # Strip any trailing whitespace. A trailing newline appears to make
202 # Pango report a larger layout size than what's actually visible.
203 msgtext = msgtext.strip()
204 result[tag] = msgtext
205 return result
206
Yu-Ping Wuae79af62020-09-23 16:48:06 +0800207
208def parse_locale_json_file(locale, json_dir):
Yu-Ping Wu338f0832020-10-23 16:14:40 +0800209 """Parses given firmware string json file.
Mathew King89d48c62019-02-15 10:08:39 -0700210
211 Args:
Yu-Ping Wu8f633b82020-09-22 14:27:57 +0800212 locale: The name of the locale, e.g. "da" or "pt-BR".
Jes Klinke1687a992020-06-16 13:47:17 -0700213 json_dir: Directory containing json output from grit.
Mathew King89d48c62019-02-15 10:08:39 -0700214
215 Returns:
216 A dictionary for mapping of "name to content" for files to be generated.
217 """
Yu-Ping Wuae79af62020-09-23 16:48:06 +0800218 result = _load_locale_json_file(locale, json_dir)
219 original = _load_locale_json_file('en', json_dir)
220 for tag in original:
221 if tag not in result:
222 # Use original English text, in case translation is not yet available
223 print('WARNING: locale "%s", missing entry %s' % (locale, tag))
224 result[tag] = original[tag]
Mathew King89d48c62019-02-15 10:08:39 -0700225
Yu-Ping Wuae79af62020-09-23 16:48:06 +0800226 return result
Mathew King89d48c62019-02-15 10:08:39 -0700227
Yu-Ping Wuae79af62020-09-23 16:48:06 +0800228
229def parse_locale_input_files(locale, json_dir):
230 """Parses all firmware string files for the given locale.
231
232 Args:
233 locale: The name of the locale, e.g. "da" or "pt-BR".
234 json_dir: Directory containing json output from grit.
235
236 Returns:
237 A dictionary for mapping of "name to content" for files to be generated.
238 """
239 result = parse_locale_json_file(locale, json_dir)
240
241 # Walk locale directory to add pre-generated texts such as language names.
Yu-Ping Wu8f633b82020-09-22 14:27:57 +0800242 for input_file in glob.glob(os.path.join(LOCALE_DIR, locale, "*.txt")):
Mathew King89d48c62019-02-15 10:08:39 -0700243 name, _ = os.path.splitext(os.path.basename(input_file))
Yu-Ping Wud71b4452020-06-16 11:00:26 +0800244 with open(input_file, 'r', encoding='utf-8-sig') as f:
Mathew King89d48c62019-02-15 10:08:39 -0700245 result[name] = f.read().strip()
Shelley Chen2f616ac2017-05-22 13:19:40 -0700246
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800247 return result
248
249
Yu-Ping Wuae79af62020-09-23 16:48:06 +0800250def build_text_files(inputs, files, output_dir):
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800251 """Builds text files from given input data.
252
253 Args:
254 inputs: Dictionary of contents for given file name.
Yu-Ping Wu338f0832020-10-23 16:14:40 +0800255 files: List of files.
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800256 output_dir: Directory to generate text files.
257 """
Yu-Ping Wu338f0832020-10-23 16:14:40 +0800258 for name in files:
259 file_name = os.path.join(output_dir, name + '.txt')
260 with open(file_name, 'w', encoding='utf-8-sig') as f:
261 f.write(inputs[name] + '\n')
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800262
263
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800264def convert_localized_strings(formats):
265 """Converts localized strings."""
Yu-Ping Wu7f6639a2020-09-28 15:31:35 +0800266 # Make a copy of formats to avoid modifying it
267 formats = copy.deepcopy(formats)
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800268
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800269 env_locales = os.getenv('LOCALES')
270 if env_locales:
271 locales = env_locales.split()
272 else:
273 locales = formats[KEY_LOCALES]
Yu-Ping Wu7f6639a2020-09-28 15:31:35 +0800274
Yu-Ping Wu338f0832020-10-23 16:14:40 +0800275 files = formats[KEY_LOCALIZED_FILES]
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800276 if DIAGNOSTIC_UI:
Yu-Ping Wu338f0832020-10-23 16:14:40 +0800277 files.update(formats[DIAGNOSTIC_FILES])
278
Yu-Ping Wu7f6639a2020-09-28 15:31:35 +0800279 styles = formats[KEY_STYLES]
280 fonts = formats[KEY_FONTS]
Yu-Ping Wu338f0832020-10-23 16:14:40 +0800281 default_font = fonts[DEFAULT_NAME]
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800282
Yu-Ping Wu51940352020-09-17 08:48:55 +0800283 # Sources are one .grd file with identifiers chosen by engineers and
284 # corresponding English texts, as well as a set of .xlt files (one for each
285 # language other than US english) with a mapping from hash to translation.
286 # Because the keys in the xlt files are a hash of the English source text,
287 # rather than our identifiers, such as "btn_cancel", we use the "grit"
288 # command line tool to process the .grd and .xlt files, producing a set of
289 # .json files mapping our identifier to the translated string, one for every
290 # language including US English.
Jes Klinke1687a992020-06-16 13:47:17 -0700291
Yu-Ping Wu51940352020-09-17 08:48:55 +0800292 # Create a temporary directory to place the translation output from grit in.
293 json_dir = tempfile.mkdtemp()
Yu-Ping Wu7f6639a2020-09-28 15:31:35 +0800294
Yu-Ping Wu51940352020-09-17 08:48:55 +0800295 # This invokes the grit build command to generate JSON files from the XTB
296 # files containing translations. The results are placed in `json_dir` as
297 # specified in firmware_strings.grd, i.e. one JSON file per locale.
298 subprocess.check_call([
299 'grit',
Yu-Ping Wu8f633b82020-09-22 14:27:57 +0800300 '-i', os.path.join(LOCALE_DIR, STRINGS_GRD_FILE),
Yu-Ping Wu51940352020-09-17 08:48:55 +0800301 'build',
302 '-o', os.path.join(json_dir)
303 ])
Jes Klinke1687a992020-06-16 13:47:17 -0700304
Yu-Ping Wuc90a22f2020-04-24 11:17:15 +0800305 # Ignore SIGINT in child processes
306 sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
Hung-Te Lin04addcc2015-03-23 18:43:30 +0800307 pool = multiprocessing.Pool(multiprocessing.cpu_count())
Yu-Ping Wuc90a22f2020-04-24 11:17:15 +0800308 signal.signal(signal.SIGINT, sigint_handler)
309
Hung-Te Lin04addcc2015-03-23 18:43:30 +0800310 results = []
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800311 for locale in locales:
Yu-Ping Wud71b4452020-06-16 11:00:26 +0800312 print(locale, end=' ', flush=True)
Yu-Ping Wuae79af62020-09-23 16:48:06 +0800313 inputs = parse_locale_input_files(locale, json_dir)
314 output_dir = os.path.normpath(os.path.join(STAGE_DIR, 'locale', locale))
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800315 if not os.path.exists(output_dir):
316 os.makedirs(output_dir)
Matt Delco4c5580d2019-03-07 14:00:28 -0800317
Yu-Ping Wuae79af62020-09-23 16:48:06 +0800318 build_text_files(inputs, files, output_dir)
Shelley Chen2f616ac2017-05-22 13:19:40 -0700319
Yu-Ping Wu338f0832020-10-23 16:14:40 +0800320 for name, category in files.items():
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800321 style = get_config_with_defaults(styles, category)
322 args = (
323 locale,
324 os.path.join(output_dir, '%s.txt' % name),
325 fonts.get(locale, default_font),
326 '0',
327 output_dir,
328 )
329 kwargs = {
330 'bgcolor': style[KEY_BGCOLOR],
331 'fgcolor': style[KEY_FGCOLOR],
332 }
333 results.append(pool.apply_async(convert_text_to_png, args, kwargs))
Hung-Te Lin04addcc2015-03-23 18:43:30 +0800334 pool.close()
Jes Klinke1687a992020-06-16 13:47:17 -0700335 if json_dir is not None:
336 shutil.rmtree(json_dir)
Yu-Ping Wud71b4452020-06-16 11:00:26 +0800337 print()
Yu-Ping Wuc90a22f2020-04-24 11:17:15 +0800338
339 try:
340 success = [r.get() for r in results]
341 except KeyboardInterrupt:
342 pool.terminate()
343 pool.join()
344 exit('Aborted by user')
345 else:
346 pool.join()
347 if not all(success):
348 exit('Failed to render some locales')
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800349
350
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800351def build_strings(formats):
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800352 """Builds text strings."""
Yu-Ping Wu11027f02020-10-14 17:35:42 +0800353 # Convert glyphs
354 print('Converting glyphs...')
355 convert_glyphs()
356
Yu-Ping Wu7f6639a2020-09-28 15:31:35 +0800357 # Convert generic (locale-independent) strings
Yu-Ping Wu338f0832020-10-23 16:14:40 +0800358 files = formats[KEY_GENERIC_FILES]
Yu-Ping Wu7f6639a2020-09-28 15:31:35 +0800359 styles = formats[KEY_STYLES]
360 fonts = formats[KEY_FONTS]
Yu-Ping Wu338f0832020-10-23 16:14:40 +0800361 default_font = fonts[DEFAULT_NAME]
Yu-Ping Wu7f6639a2020-09-28 15:31:35 +0800362
363 for input_file in glob.glob(os.path.join(STRINGS_DIR, '*.txt')):
364 name, _ = os.path.splitext(os.path.basename(input_file))
Yu-Ping Wu338f0832020-10-23 16:14:40 +0800365 category = files[name]
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800366 style = get_config_with_defaults(styles, category)
367 if not convert_text_to_png(None, input_file, default_font, '0', STAGE_DIR,
368 bgcolor=style[KEY_BGCOLOR],
369 fgcolor=style[KEY_FGCOLOR]):
Yu-Ping Wu7f6639a2020-09-28 15:31:35 +0800370 exit('Failed to convert text %s' % input_file)
371
372 # Convert localized strings
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800373 convert_localized_strings(formats)
374
375
376def load_boards_config(filename):
377 """Loads the configuration of all boards from `filename`.
378
379 Args:
380 filename: File name of a YAML config file.
381
382 Returns:
383 A dictionary mapping each board name to its config.
384 """
385 with open(filename, 'rb') as file:
386 raw = yaml.load(file)
387
388 configs = {}
389 default = raw[DEFAULT_NAME]
390 if not default:
391 raise BuildImageError('Default configuration is not found')
392 for boards, params in raw.items():
393 if boards == DEFAULT_NAME:
394 continue
395 config = copy.deepcopy(default)
396 if params:
397 config.update(params)
398 for board in boards.replace(',', ' ').split():
399 configs[board] = config
400
401 return configs
402
403
404class Converter(object):
405 """Converter from assets, texts, URLs, and fonts to bitmap images.
406
407 Attributes:
408 ASSET_DIR (str): Directory of image assets.
409 DEFAULT_OUTPUT_EXT (str): Default output file extension.
410 DEFAULT_REPLACE_MAP (dict): Default mapping of file replacement. For
411 {'a': 'b'}, "a.*" will be converted to "b.*".
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800412 SCALE_BASE (int): See ASSET_HEIGHTS below.
413 DEFAULT_FONT_HEIGHT (tuple): Height of the font images.
414 ASSET_HEIGHTS (dict): Height of each image asset. Key is the image name and
415 value is the height relative to the screen resolution. For example, if
416 `SCALE_BASE` is 1000, height = 500 means the image will be scaled to 50%
417 of the screen height.
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800418 ASSET_MAX_COLORS (int): Maximum colors to use for converting image assets
419 to bitmaps.
420 DEFAULT_BACKGROUND (tuple): Default background color.
421 BACKGROUND_COLORS (dict): Background color of each image. Key is the image
422 name and value is a tuple of RGB values.
423 """
424
425 ASSET_DIR = 'assets'
426 DEFAULT_OUTPUT_EXT = '.bmp'
427
428 DEFAULT_REPLACE_MAP = {
429 'rec_sel_desc1_no_sd': '',
430 'rec_sel_desc1_no_phone_no_sd': '',
431 'rec_disk_step1_desc0_no_sd': '',
432 'rec_to_dev_desc1_phyrec': '',
433 'rec_to_dev_desc1_power': '',
434 'navigate0_tablet': '',
435 'navigate1_tablet': '',
436 'nav-button_power': '',
437 'nav-button_volume_up': '',
438 'nav-button_volume_down': '',
439 'broken_desc_phyrec': '',
440 'broken_desc_detach': '',
441 }
442
443 # scales
444 SCALE_BASE = 1000 # 100.0%
445
446 # These are supposed to be kept in sync with the numbers set in depthcharge
447 # to avoid runtime scaling, which makes images blurry.
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800448 DEFAULT_ASSET_HEIGHT = 30
449 DEFAULT_FONT_HEIGHT = 20
450 ICON_HEIGHT = 45
451 STEP_ICON_HEIGHT = 28
452 BUTTON_ICON_HEIGHT = 24
453 BUTTON_ARROW_HEIGHT = 20
454 QR_FOOTER_HEIGHT = 128
455 QR_DESC_HEIGHT = 228
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800456
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800457 ASSET_HEIGHTS = {
458 'ic_globe': 20,
459 'ic_dropdown': 24,
460 'ic_info': ICON_HEIGHT,
461 'ic_error': ICON_HEIGHT,
462 'ic_dev_mode': ICON_HEIGHT,
463 'ic_restart': ICON_HEIGHT,
464 'ic_1': STEP_ICON_HEIGHT,
465 'ic_1-done': STEP_ICON_HEIGHT,
466 'ic_2': STEP_ICON_HEIGHT,
467 'ic_2-done': STEP_ICON_HEIGHT,
468 'ic_3': STEP_ICON_HEIGHT,
469 'ic_3-done': STEP_ICON_HEIGHT,
470 'ic_done': STEP_ICON_HEIGHT,
471 'ic_search': BUTTON_ICON_HEIGHT,
472 'ic_search_focus': BUTTON_ICON_HEIGHT,
473 'ic_settings': BUTTON_ICON_HEIGHT,
474 'ic_settings_focus': BUTTON_ICON_HEIGHT,
475 'ic_power': BUTTON_ICON_HEIGHT,
476 'ic_power_focus': BUTTON_ICON_HEIGHT,
477 'ic_dropleft': BUTTON_ARROW_HEIGHT,
478 'ic_dropleft_focus': BUTTON_ARROW_HEIGHT,
479 'ic_dropright': BUTTON_ARROW_HEIGHT,
480 'ic_dropright_focus': BUTTON_ARROW_HEIGHT,
481 'qr_rec': QR_FOOTER_HEIGHT,
482 'qr_rec_phone': QR_DESC_HEIGHT,
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800483 }
484
485 # background colors
486 DEFAULT_BACKGROUND = (0x20, 0x21, 0x24)
487 LANG_HEADER_BACKGROUND = (0x16, 0x17, 0x19)
488 LINK_SELECTED_BACKGROUND = (0x2a, 0x2f, 0x39)
489 ASSET_MAX_COLORS = 128
490
491 BACKGROUND_COLORS = {
492 'ic_dropdown': LANG_HEADER_BACKGROUND,
493 'ic_dropleft_focus': LINK_SELECTED_BACKGROUND,
494 'ic_dropright_focus': LINK_SELECTED_BACKGROUND,
495 'ic_globe': LANG_HEADER_BACKGROUND,
496 'ic_search_focus': LINK_SELECTED_BACKGROUND,
497 'ic_settings_focus': LINK_SELECTED_BACKGROUND,
498 'ic_power_focus': LINK_SELECTED_BACKGROUND,
499 }
500
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800501 def __init__(self, board, formats, board_config, output):
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800502 """Inits converter.
503
504 Args:
505 board: Board name.
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800506 formats: A dictionary of string formats.
507 board_config: A dictionary of board configurations.
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800508 output: Output directory.
509 """
510 self.board = board
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800511 self.formats = formats
512 self.config = board_config
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800513 self.set_dirs(output)
514 self.set_screen()
515 self.set_replace_map()
516 self.set_locales()
517 self.text_max_colors = self.config[TEXT_COLORS_KEY]
518
519 def set_dirs(self, output):
520 """Sets board output directory and stage directory.
521
522 Args:
523 output: Output directory.
524 """
525 self.output_dir = os.path.join(output, self.board)
526 self.output_ro_dir = os.path.join(self.output_dir, 'locale', 'ro')
527 self.output_rw_dir = os.path.join(self.output_dir, 'locale', 'rw')
528 self.stage_dir = os.path.join(output, '.stage')
529 self.temp_dir = os.path.join(self.stage_dir, 'tmp')
530
531 def set_screen(self):
532 """Sets screen width and height."""
533 self.screen_width, self.screen_height = self.config[SCREEN_KEY]
534
535 self.stretch = (1, 1)
536 if self.config[PANEL_KEY]:
537 # Calculate 'stretch'. It's used to shrink images horizontally so that
538 # resulting images will look proportional to the original image on the
539 # stretched display. If the display is not stretched, meaning aspect
540 # ratio is same as the screen where images were rendered (1366x766),
541 # no shrinking is performed.
542 panel_width, panel_height = self.config[PANEL_KEY]
543 self.stretch = (self.screen_width * panel_height,
544 self.screen_height * panel_width)
545
546 if self.stretch[0] > self.stretch[1]:
547 raise BuildImageError('Panel aspect ratio (%f) is smaller than screen '
548 'aspect ratio (%f). It indicates screen will be '
549 'shrunk horizontally. It is currently unsupported.'
550 % (panel_width / panel_height,
551 self.screen_width / self.screen_height))
552
553 # Set up square drawing area
554 self.canvas_px = min(self.screen_width, self.screen_height)
555
556 def set_replace_map(self):
557 """Sets a map replacing images.
558
559 For each (key, value), image 'key' will be replaced by image 'value'.
560 """
561 replace_map = self.DEFAULT_REPLACE_MAP.copy()
562
563 if os.getenv('DETACHABLE') == '1':
564 replace_map.update({
565 'nav-key_enter': 'nav-button_power',
566 'nav-key_up': 'nav-button_volume_up',
567 'nav-key_down': 'nav-button_volume_down',
568 'navigate0': 'navigate0_tablet',
569 'navigate1': 'navigate1_tablet',
570 'broken_desc': 'broken_desc_detach',
571 })
572
573 physical_presence = os.getenv('PHYSICAL_PRESENCE')
574 if physical_presence == 'recovery':
575 replace_map['rec_to_dev_desc1'] = 'rec_to_dev_desc1_phyrec'
576 replace_map['broken_desc'] = 'broken_desc_phyrec'
577 elif physical_presence == 'power':
578 replace_map['rec_to_dev_desc1'] = 'rec_to_dev_desc1_power'
579 elif physical_presence != 'keyboard':
580 raise BuildImageError('Invalid physical presence setting %s for board %s'
581 % (physical_presence, self.board))
582
583 if not self.config[SDCARD_KEY]:
584 replace_map.update({
585 'rec_sel_desc1': 'rec_sel_desc1_no_sd',
586 'rec_sel_desc1_no_phone': 'rec_sel_desc1_no_phone_no_sd',
587 'rec_disk_step1_desc0': 'rec_disk_step1_desc0_no_sd',
588 })
589
590 self.replace_map = replace_map
591
592 def set_locales(self):
593 """Sets a list of locales for which localized images are converted."""
594 # LOCALES environment variable can overwrite boards.yaml
595 env_locales = os.getenv('LOCALES')
596 rtl_locales = set(self.config[RTL_KEY])
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800597 if env_locales:
598 locales = env_locales.split()
599 else:
600 locales = self.config[LOCALES_KEY]
601 # Check rtl_locales are contained in locales.
602 unknown_rtl_locales = rtl_locales - set(locales)
603 if unknown_rtl_locales:
604 raise BuildImageError('Unknown locales %s in %s' %
605 (list(unknown_rtl_locales), RTL_KEY))
Yu-Ping Wuabb9afb2020-10-27 17:15:22 +0800606 self.locales = [LocaleInfo(code, code in rtl_locales)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800607 for code in locales]
608
609 def calculate_dimension(self, original, scale, num_lines):
610 """Calculates scaled width and height.
611
612 This imitates the function of Depthcharge with the same name.
613
614 Args:
615 original: (width, height) of the original image.
616 scale: (x, y) scale parameter relative to the canvas size using
617 SCALE_BASE as a base.
618 num_lines: multiplication factor for the y-dimension.
619
620 Returns:
621 (width, height) of the scaled image.
622 """
623 dim_width, dim_height = (0, 0)
624 scale_x, scale_y = scale
625 org_width, org_height = original
626
627 if scale_x == 0 and scale_y == 0:
628 raise BuildImageError('Invalid scale parameter: %s' % (scale))
629 if scale_x > 0:
630 dim_width = int(self.canvas_px * scale_x / self.SCALE_BASE)
631 if scale_y > 0:
632 dim_height = int(self.canvas_px * scale_y / self.SCALE_BASE) * num_lines
633 if scale_x == 0:
634 dim_width = org_width * dim_height // org_height
635 if scale_y == 0:
636 dim_height = org_height * dim_width // org_width
637
638 dim_width = int(dim_width * self.stretch[0] / self.stretch[1])
639
640 return dim_width, dim_height
641
642 def _get_svg_height(self, svg_file):
643 tree = ElementTree.parse(svg_file)
644 height = tree.getroot().attrib['height']
645 m = re.match('([0-9]+)pt', height)
646 if not m:
647 raise BuildImageError('Cannot get height from %s' % svg_file)
648 return int(m.group(1))
649
650 def get_num_lines(self, file, one_line_dir):
651 """Gets the number of lines of text in `file`."""
652 name, _ = os.path.splitext(os.path.basename(file))
653 svg_name = name + '.svg'
654 multi_line_file = os.path.join(os.path.dirname(file), svg_name)
655 one_line_file = os.path.join(one_line_dir, svg_name)
656 # The number of lines id determined by comparing the height of
657 # `multi_line_file` with `one_line_file`, where the latter is generated
658 # without the '--width' option passed to pango-view.
659 height = self._get_svg_height(multi_line_file)
660 line_height = self._get_svg_height(one_line_file)
661 return int(round(height / line_height))
662
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800663 def convert_svg_to_png(self, svg_file, png_file, height, num_lines,
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800664 background):
665 """Converts .svg file to .png file."""
666 background_hex = ''.join(format(x, '02x') for x in background)
667 # If the width/height of the SVG file is specified in points, the
668 # rsvg-convert command with default 90DPI will potentially cause the pixels
669 # at the right/bottom border of the output image to be transparent (or
670 # filled with the specified background color). This seems like an
671 # rsvg-convert issue regarding image scaling. Therefore, use 72DPI here
672 # to avoid the scaling.
673 command = ['rsvg-convert',
674 '--background-color', "'#%s'" % background_hex,
675 '--dpi-x', '72',
676 '--dpi-y', '72',
677 '-o', png_file]
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800678 if height:
679 height_px = int(self.canvas_px * height / self.SCALE_BASE) * num_lines
680 command.extend(['--height', '%d' % height_px])
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800681 command.append(svg_file)
682 subprocess.check_call(' '.join(command), shell=True)
683
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800684 def convert_to_bitmap(self, input_file, height, num_lines, background, output,
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800685 max_colors):
686 """Converts an image file `input_file` to a BMP file `output`."""
687 image = Image.open(input_file)
688
689 # Process alpha channel and transparency.
690 if image.mode == 'RGBA':
691 target = Image.new('RGB', image.size, background)
692 image.load() # required for image.split()
693 mask = image.split()[-1]
694 target.paste(image, mask=mask)
695 elif (image.mode == 'P') and ('transparency' in image.info):
696 exit('Sorry, PNG with RGBA palette is not supported.')
697 elif image.mode != 'RGB':
698 target = image.convert('RGB')
699 else:
700 target = image
701
702 # Process scaling
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800703 if height:
704 new_size = self.calculate_dimension(image.size, (0, height), num_lines)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800705 if new_size[0] == 0 or new_size[1] == 0:
706 print('Scaling', input_file)
707 print('Warning: width or height is 0 after resizing: '
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800708 'height=%d size=%s stretch=%s new_size=%s' %
709 (height, image.size, self.stretch, new_size))
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800710 return
711 target = target.resize(new_size, Image.BICUBIC)
712
713 # Export and downsample color space.
714 target.convert('P', dither=None, colors=max_colors, palette=Image.ADAPTIVE
715 ).save(output)
716
717 with open(output, 'rb+') as f:
718 f.seek(BMP_HEADER_OFFSET_NUM_LINES)
719 f.write(bytearray([num_lines]))
720
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800721 def convert(self, files, output_dir, heights, max_colors, one_line_dir=None):
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800722 """Converts file(s) to bitmap format."""
723 if not files:
724 raise BuildImageError('Unable to find file(s) to convert')
725
726 for file in files:
727 name, ext = os.path.splitext(os.path.basename(file))
728 output = os.path.join(output_dir, name + self.DEFAULT_OUTPUT_EXT)
729
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800730 if name in self.replace_map:
731 name = self.replace_map[name]
732 if not name:
733 continue
734 print('Replace: %s => %s' % (file, name))
735 file = os.path.join(os.path.dirname(file), name + ext)
736
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800737 background = self.BACKGROUND_COLORS.get(name, self.DEFAULT_BACKGROUND)
738 height = heights[name]
739
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800740 # Determine num_lines in order to scale the image
741 # TODO(b/159399377): Wrap lines for texts other than descriptions.
Hsuan Ting Chenf2917582020-10-21 13:50:08 +0800742 if one_line_dir and ('_desc' in name or name.startswith('navigate')):
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800743 num_lines = self.get_num_lines(file, one_line_dir)
744 else:
745 num_lines = 1
746
747 if ext == '.svg':
748 png_file = os.path.join(self.temp_dir, name + '.png')
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800749 self.convert_svg_to_png(file, png_file, height, num_lines, background)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800750 file = png_file
751
752 self.convert_to_bitmap(
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800753 file, height, num_lines, background, output, max_colors)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800754
755 def convert_assets(self):
756 """Converts images in assets folder."""
757 files = []
758 files.extend(glob.glob(os.path.join(self.ASSET_DIR, SVG_FILES)))
759 files.extend(glob.glob(os.path.join(self.ASSET_DIR, PNG_FILES)))
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800760 heights = defaultdict(lambda: self.DEFAULT_ASSET_HEIGHT)
761 heights.update(self.ASSET_HEIGHTS)
762 self.convert(files, self.output_dir, heights, self.ASSET_MAX_COLORS)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800763
764 def convert_generic_strings(self):
765 """Converts generic (locale-independent) strings."""
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800766 names = self.formats[KEY_GENERIC_FILES]
767 styles = self.formats[KEY_STYLES]
768 heights = {}
769 for name, category in names.items():
770 style = get_config_with_defaults(styles, category)
771 heights[name] = style[KEY_HEIGHT]
772
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800773 files = glob.glob(os.path.join(self.stage_dir, SVG_FILES))
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800774 self.convert(files, self.output_dir, heights, self.text_max_colors)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800775
776 def convert_localized_strings(self):
777 """Converts localized strings."""
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800778 names = self.formats[KEY_LOCALIZED_FILES].copy()
779 if DIAGNOSTIC_UI:
780 names.update(self.formats[DIAGNOSTIC_FILES])
781 styles = self.formats[KEY_STYLES]
782 heights = {}
783 for name, category in names.items():
784 style = get_config_with_defaults(styles, category)
785 heights[name] = style[KEY_HEIGHT]
786
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800787 # Using stderr to report progress synchronously
788 print(' processing:', end='', file=sys.stderr, flush=True)
789 for locale_info in self.locales:
790 locale = locale_info.code
791 ro_locale_dir = os.path.join(self.output_ro_dir, locale)
792 stage_locale_dir = os.path.join(STAGE_LOCALE_DIR, locale)
Yu-Ping Wuabb9afb2020-10-27 17:15:22 +0800793 print(' ' + locale, end='', file=sys.stderr, flush=True)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800794 os.makedirs(ro_locale_dir)
795 self.convert(
796 glob.glob(os.path.join(stage_locale_dir, SVG_FILES)),
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800797 ro_locale_dir, heights, self.text_max_colors,
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800798 one_line_dir=os.path.join(stage_locale_dir, ONE_LINE_DIR))
799 print(file=sys.stderr)
800
801 def move_language_images(self):
802 """Renames language bitmaps and move to self.output_dir.
803
804 The directory self.output_dir contains locale-independent images, and is
805 used for creating vbgfx.bin by archive_images.py.
806 """
807 for locale_info in self.locales:
808 locale = locale_info.code
809 ro_locale_dir = os.path.join(self.output_ro_dir, locale)
810 old_file = os.path.join(ro_locale_dir, 'language.bmp')
811 new_file = os.path.join(self.output_dir, 'language_%s.bmp' % locale)
812 if os.path.exists(new_file):
813 raise BuildImageError('File already exists: %s' % new_file)
814 shutil.move(old_file, new_file)
815
816 def convert_fonts(self):
817 """Converts font images"""
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800818 heights = defaultdict(lambda: self.DEFAULT_FONT_HEIGHT)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800819 files = glob.glob(os.path.join(STAGE_FONT_DIR, SVG_FILES))
820 font_output_dir = os.path.join(self.output_dir, 'font')
821 os.makedirs(font_output_dir)
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800822 self.convert(files, font_output_dir, heights, self.text_max_colors)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800823
824 def copy_images_to_rw(self):
825 """Copies localized images specified in boards.yaml for RW override."""
826 if not self.config[RW_OVERRIDE_KEY]:
827 print(' No localized images are specified for RW, skipping')
828 return
829
830 for locale_info in self.locales:
831 locale = locale_info.code
832 rw_locale_dir = os.path.join(self.output_ro_dir, locale)
833 ro_locale_dir = os.path.join(self.output_rw_dir, locale)
834 os.makedirs(rw_locale_dir)
835
836 for name in self.config[RW_OVERRIDE_KEY]:
837 ro_src = os.path.join(ro_locale_dir, name + self.DEFAULT_OUTPUT_EXT)
838 rw_dst = os.path.join(rw_locale_dir, name + self.DEFAULT_OUTPUT_EXT)
839 shutil.copyfile(ro_src, rw_dst)
840
841 def create_locale_list(self):
842 """Creates locale list as a CSV file.
843
844 Each line in the file is of format "code,rtl", where
845 - "code": language code of the locale
846 - "rtl": "1" for right-to-left language, "0" otherwise
847 """
848 with open(os.path.join(self.output_dir, 'locales'), 'w') as f:
849 for locale_info in self.locales:
850 f.write('{},{}\n'.format(locale_info.code,
851 int(locale_info.rtl)))
852
853 def build(self):
854 """Builds all images required by a board."""
855 # Clean up output directory
856 if os.path.exists(self.output_dir):
857 shutil.rmtree(self.output_dir)
858 os.makedirs(self.output_dir)
859
860 if not os.path.exists(self.stage_dir):
861 raise BuildImageError('Missing stage folder. Run make in strings dir.')
862
863 # Clean up temp directory
864 if os.path.exists(self.temp_dir):
865 shutil.rmtree(self.temp_dir)
866 os.makedirs(self.temp_dir)
867
868 print('Converting asset images...')
869 self.convert_assets()
870
871 print('Converting generic strings...')
872 self.convert_generic_strings()
873
874 print('Converting localized strings...')
875 self.convert_localized_strings()
876
877 print('Moving language images to locale-independent directory...')
878 self.move_language_images()
879
880 print('Creating locale list file...')
881 self.create_locale_list()
882
883 print('Converting fonts...')
884 self.convert_fonts()
885
886 print('Copying specified images to RW packing directory...')
887 self.copy_images_to_rw()
888
889
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800890def build_images(board, formats):
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800891 """Builds images for `board`."""
892 configs = load_boards_config(BOARDS_CONFIG_FILE)
893 print('Building for ' + board)
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800894 converter = Converter(board, formats, configs[board], OUTPUT_DIR)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800895 converter.build()
896
897
898def main():
899 """Builds bitmaps for firmware screens."""
900 parser = argparse.ArgumentParser()
901 parser.add_argument('board', help='Target board')
902 args = parser.parse_args()
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800903
904 with open(FORMAT_FILE, encoding='utf-8') as f:
905 formats = yaml.load(f)
906 build_strings(formats)
907 build_images(args.board, formats)
Yu-Ping Wu7f6639a2020-09-28 15:31:35 +0800908
909
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800910if __name__ == '__main__':
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800911 main()