blob: ec4539c729b71df0cfab44d0805cd7a7dd3dd1e6 [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
Yu-Ping Wue445e042020-11-19 15:53:42 +080010import fractions
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080011import glob
Jes Klinke1687a992020-06-16 13:47:17 -070012import json
Hung-Te Lin04addcc2015-03-23 18:43:30 +080013import multiprocessing
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080014import os
15import re
Jes Klinke1687a992020-06-16 13:47:17 -070016import shutil
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080017import signal
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080018import subprocess
19import sys
Jes Klinke1687a992020-06-16 13:47:17 -070020import tempfile
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080021from xml.etree import ElementTree
Hung-Te Lin04addcc2015-03-23 18:43:30 +080022
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080023import yaml
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080024from PIL import Image
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080025
26SCRIPT_BASE = os.path.dirname(os.path.abspath(__file__))
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080027
28STRINGS_GRD_FILE = 'firmware_strings.grd'
29STRINGS_JSON_FILE_TMPL = '{}.json'
30FORMAT_FILE = 'format.yaml'
31BOARDS_CONFIG_FILE = 'boards.yaml'
32
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080033OUTPUT_DIR = os.getenv('OUTPUT', os.path.join(SCRIPT_BASE, 'build'))
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080034
35ONE_LINE_DIR = 'one_line'
36SVG_FILES = '*.svg'
37PNG_FILES = '*.png'
38
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +080039DIAGNOSTIC_UI = os.getenv('DIAGNOSTIC_UI') == '1'
40
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080041# String format YAML key names.
Yu-Ping Wu177f12c2020-11-04 15:55:37 +080042KEY_DEFAULT = '_DEFAULT_'
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080043KEY_LOCALES = 'locales'
Yu-Ping Wu338f0832020-10-23 16:14:40 +080044KEY_GENERIC_FILES = 'generic_files'
45KEY_LOCALIZED_FILES = 'localized_files'
Yu-Ping Wu177f12c2020-11-04 15:55:37 +080046KEY_DIAGNOSTIC_FILES = 'diagnostic_files'
47KEY_SPRITE_FILES = 'sprite_files'
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080048KEY_STYLES = 'styles'
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +080049KEY_BGCOLOR = 'bgcolor'
50KEY_FGCOLOR = 'fgcolor'
51KEY_HEIGHT = 'height'
Yu-Ping Wued95df32020-11-04 17:08:15 +080052KEY_MAX_WIDTH = 'max_width'
Yu-Ping Wu177f12c2020-11-04 15:55:37 +080053KEY_FONTS = 'fonts'
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080054
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080055# Board config YAML key names.
56SCREEN_KEY = 'screen'
57PANEL_KEY = 'panel'
58SDCARD_KEY = 'sdcard'
59BAD_USB3_KEY = 'bad_usb3'
Yu-Ping Wue66a7b02020-11-19 15:18:08 +080060DPI_KEY = 'dpi'
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080061LOCALES_KEY = 'locales'
62RTL_KEY = 'rtl'
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080063RW_OVERRIDE_KEY = 'rw_override'
64
65BMP_HEADER_OFFSET_NUM_LINES = 6
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080066
Jes Klinke1687a992020-06-16 13:47:17 -070067# Regular expressions used to eliminate spurious spaces and newlines in
68# translation strings.
69NEWLINE_PATTERN = re.compile(r'([^\n])\n([^\n])')
70NEWLINE_REPLACEMENT = r'\1 \2'
71CRLF_PATTERN = re.compile(r'\r\n')
72MULTIBLANK_PATTERN = re.compile(r' *')
73
Yu-Ping Wu3d07a062021-01-26 18:10:32 +080074# The base for bitmap scales, same as UI_SCALE in depthcharge. For example, if
75# `SCALE_BASE` is 1000, then height = 200 means 20% of the screen height. Also
76# see the 'styles' section in format.yaml.
77SCALE_BASE = 1000
78DEFAULT_GLYPH_HEIGHT = 20
79
Yu-Ping Wucc86d6a2020-11-27 12:48:19 +080080GLYPH_FONT = 'Cousine'
Yu-Ping Wu11027f02020-10-14 17:35:42 +080081
Yu-Ping Wuabb9afb2020-10-27 17:15:22 +080082LocaleInfo = namedtuple('LocaleInfo', ['code', 'rtl'])
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080083
Yu-Ping Wu6b282c52020-03-19 12:54:15 +080084
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080085class DataError(Exception):
86 pass
87
88
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +080089class BuildImageError(Exception):
90 """The exception class for all errors generated during build image process."""
91
92
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +080093def get_config_with_defaults(configs, key):
94 """Gets config of `key` from `configs`.
95
96 If `key` is not present in `configs`, the default config will be returned.
97 Similarly, if some config values are missing for `key`, the default ones will
98 be used.
99 """
Yu-Ping Wu177f12c2020-11-04 15:55:37 +0800100 config = configs[KEY_DEFAULT].copy()
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800101 config.update(configs.get(key, {}))
102 return config
103
104
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800105def load_boards_config(filename):
106 """Loads the configuration of all boards from `filename`.
107
108 Args:
109 filename: File name of a YAML config file.
110
111 Returns:
112 A dictionary mapping each board name to its config.
113 """
114 with open(filename, 'rb') as file:
115 raw = yaml.load(file)
116
117 configs = {}
118 default = raw[KEY_DEFAULT]
119 if not default:
120 raise BuildImageError('Default configuration is not found')
121 for boards, params in raw.items():
122 if boards == KEY_DEFAULT:
123 continue
124 config = copy.deepcopy(default)
125 if params:
126 config.update(params)
127 for board in boards.replace(',', ' ').split():
128 configs[board] = config
129
130 return configs
131
132
133def check_fonts(fonts):
134 """Check if all fonts are available."""
135 for locale, font in fonts.items():
136 if subprocess.run(['fc-list', '-q', font]).returncode != 0:
137 raise BuildImageError('Font %r not found for locale %r'
138 % (font, locale))
139
140
Yu-Ping Wu97046932021-01-25 17:38:56 +0800141def run_pango_view(input_file, output_file, locale, font, height, max_width,
142 dpi, bgcolor, fgcolor, hinting='full'):
143 """Run pango-view."""
144 command = ['pango-view', '-q']
Yu-Ping Wu11027f02020-10-14 17:35:42 +0800145 if locale:
Yu-Ping Wu97046932021-01-25 17:38:56 +0800146 command += ['--language', locale]
147
148 # Font size should be proportional to the height. Here we use 2 as the
149 # divisor so that setting dpi to 96 (pango-view's default) in boards.yaml
150 # will be roughly equivalent to setting the screen resolution to 1366x768.
151 font_size = height / 2
152 font_spec = '%s %r' % (font, font_size)
153 command += ['--font', font_spec]
154
Yu-Ping Wued95df32020-11-04 17:08:15 +0800155 if max_width:
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800156 # When converting text to PNG by pango-view, the ratio of image height to
157 # the font size is usually no more than 1.1875 (with Roboto). Therefore,
158 # set the `max_width_pt` as follows to prevent UI drawing from exceeding
159 # the canvas boundary in depthcharge runtime. The divisor 2 is the same in
160 # the calculation of `font_size` above.
161 max_width_pt = int(max_width / 2 * 1.1875)
Yu-Ping Wued95df32020-11-04 17:08:15 +0800162 command.append('--width=%d' % max_width_pt)
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800163 if dpi:
164 command.append('--dpi=%d' % dpi)
Yu-Ping Wucc86d6a2020-11-27 12:48:19 +0800165 command.append('--margin=0')
Yu-Ping Wu97046932021-01-25 17:38:56 +0800166 command += ['--background', bgcolor]
167 command += ['--foreground', fgcolor]
168 command += ['--hinting', hinting]
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800169
Yu-Ping Wu97046932021-01-25 17:38:56 +0800170 command += ['--output', output_file]
Yu-Ping Wu11027f02020-10-14 17:35:42 +0800171 command.append(input_file)
172
Yu-Ping Wu97046932021-01-25 17:38:56 +0800173 subprocess.check_call(command, stdout=subprocess.PIPE)
174
175
176def convert_text_to_image(locale, input_file, font, output_dir, height=None,
177 max_width=None, dpi=None, bgcolor='#000000',
178 fgcolor='#ffffff', use_svg=False):
179 """Converts text file `input_file` into image file(s).
180
181 Because pango-view does not support assigning output format options for
182 bitmap, we must create images in SVG/PNG format and then post-process them
183 (e.g. convert into BMP by ImageMagick).
184
185 Args:
186 locale: Locale (language) to select implicit rendering options. None for
187 locale-independent strings.
188 input_file: Path of input text file.
189 font: Font name.
190 height: Image height relative to the screen resolution.
191 max_width: Maximum image width relative to the screen resolution.
192 output_dir: Directory to generate image files.
193 bgcolor: Background color (#rrggbb).
194 fgcolor: Foreground color (#rrggbb).
195 use_svg: If set to True, generate SVG file. Otherwise, generate PNG file.
196 """
197 os.makedirs(os.path.join(output_dir, ONE_LINE_DIR), exist_ok=True)
198 name, _ = os.path.splitext(os.path.basename(input_file))
199 svg_file = os.path.join(output_dir, name + '.svg')
200 png_file = os.path.join(output_dir, name + '.png')
201 png_file_one_line = os.path.join(output_dir, ONE_LINE_DIR, name + '.png')
202
203 if use_svg:
204 run_pango_view(input_file, svg_file, locale, font, height, 0, dpi,
205 bgcolor, fgcolor, hinting='none')
206 else:
207 run_pango_view(input_file, png_file, locale, font, height, max_width, dpi,
208 bgcolor, fgcolor)
209 if locale:
210 run_pango_view(input_file, png_file_one_line, locale, font, height, 0,
211 dpi, bgcolor, fgcolor)
Yu-Ping Wu11027f02020-10-14 17:35:42 +0800212
213
Yu-Ping Wu703dcfd2021-01-08 10:52:10 +0800214def parse_locale_json_file(locale, json_dir):
215 """Parses given firmware string json file.
216
217 Args:
218 locale: The name of the locale, e.g. "da" or "pt-BR".
219 json_dir: Directory containing json output from grit.
220
221 Returns:
222 A dictionary for mapping of "name to content" for files to be generated.
223 """
Jes Klinke1687a992020-06-16 13:47:17 -0700224 result = {}
Yu-Ping Wuae79af62020-09-23 16:48:06 +0800225 filename = os.path.join(json_dir, STRINGS_JSON_FILE_TMPL.format(locale))
Yu-Ping Wud71b4452020-06-16 11:00:26 +0800226 with open(filename, encoding='utf-8-sig') as input_file:
Jes Klinke1687a992020-06-16 13:47:17 -0700227 for tag, msgdict in json.load(input_file).items():
228 msgtext = msgdict['message']
229 msgtext = re.sub(CRLF_PATTERN, '\n', msgtext)
230 msgtext = re.sub(NEWLINE_PATTERN, NEWLINE_REPLACEMENT, msgtext)
231 msgtext = re.sub(MULTIBLANK_PATTERN, ' ', msgtext)
232 # Strip any trailing whitespace. A trailing newline appears to make
233 # Pango report a larger layout size than what's actually visible.
234 msgtext = msgtext.strip()
235 result[tag] = msgtext
236 return result
237
Yu-Ping Wuae79af62020-09-23 16:48:06 +0800238
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800239class Converter(object):
240 """Converter from assets, texts, URLs, and fonts to bitmap images.
241
242 Attributes:
243 ASSET_DIR (str): Directory of image assets.
244 DEFAULT_OUTPUT_EXT (str): Default output file extension.
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800245 ASSET_MAX_COLORS (int): Maximum colors to use for converting image assets
246 to bitmaps.
247 DEFAULT_BACKGROUND (tuple): Default background color.
248 BACKGROUND_COLORS (dict): Background color of each image. Key is the image
249 name and value is a tuple of RGB values.
250 """
251
252 ASSET_DIR = 'assets'
253 DEFAULT_OUTPUT_EXT = '.bmp'
254
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800255 # background colors
256 DEFAULT_BACKGROUND = (0x20, 0x21, 0x24)
257 LANG_HEADER_BACKGROUND = (0x16, 0x17, 0x19)
258 LINK_SELECTED_BACKGROUND = (0x2a, 0x2f, 0x39)
259 ASSET_MAX_COLORS = 128
260
261 BACKGROUND_COLORS = {
262 'ic_dropdown': LANG_HEADER_BACKGROUND,
263 'ic_dropleft_focus': LINK_SELECTED_BACKGROUND,
264 'ic_dropright_focus': LINK_SELECTED_BACKGROUND,
265 'ic_globe': LANG_HEADER_BACKGROUND,
266 'ic_search_focus': LINK_SELECTED_BACKGROUND,
267 'ic_settings_focus': LINK_SELECTED_BACKGROUND,
268 'ic_power_focus': LINK_SELECTED_BACKGROUND,
269 }
270
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800271 def __init__(self, board, formats, board_config, output):
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800272 """Inits converter.
273
274 Args:
275 board: Board name.
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800276 formats: A dictionary of string formats.
277 board_config: A dictionary of board configurations.
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800278 output: Output directory.
279 """
280 self.board = board
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800281 self.formats = formats
282 self.config = board_config
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800283 self.set_dirs(output)
284 self.set_screen()
Yu-Ping Wu3d272e72021-03-01 12:01:55 +0800285 self.set_rename_map()
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800286 self.set_locales()
Yu-Ping Wu96cf0022021-01-07 15:55:49 +0800287 self.text_max_colors = self.get_text_colors(self.config[DPI_KEY])
Yu-Ping Wu354a7002021-01-07 16:07:02 +0800288 self.dpi_warning_printed = False
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800289
290 def set_dirs(self, output):
291 """Sets board output directory and stage directory.
292
293 Args:
294 output: Output directory.
295 """
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800296 self.strings_dir = os.path.join(SCRIPT_BASE, 'strings')
297 self.locale_dir = os.path.join(self.strings_dir, 'locale')
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800298 self.output_dir = os.path.join(output, self.board)
299 self.output_ro_dir = os.path.join(self.output_dir, 'locale', 'ro')
300 self.output_rw_dir = os.path.join(self.output_dir, 'locale', 'rw')
301 self.stage_dir = os.path.join(output, '.stage')
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800302 self.stage_locale_dir = os.path.join(self.stage_dir, 'locale')
303 self.stage_font_dir = os.path.join(self.stage_dir, 'font')
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800304 self.temp_dir = os.path.join(self.stage_dir, 'tmp')
305
306 def set_screen(self):
307 """Sets screen width and height."""
308 self.screen_width, self.screen_height = self.config[SCREEN_KEY]
309
Yu-Ping Wue445e042020-11-19 15:53:42 +0800310 self.panel_stretch = fractions.Fraction(1)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800311 if self.config[PANEL_KEY]:
Yu-Ping Wue445e042020-11-19 15:53:42 +0800312 # Calculate `panel_stretch`. It's used to shrink images horizontally so
313 # that the resulting images will look proportional to the original image
314 # on the stretched display. If the display is not stretched, meaning the
315 # aspect ratio is same as the screen where images were rendered, no
316 # shrinking is performed.
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800317 panel_width, panel_height = self.config[PANEL_KEY]
Yu-Ping Wue445e042020-11-19 15:53:42 +0800318 self.panel_stretch = fractions.Fraction(self.screen_width * panel_height,
319 self.screen_height * panel_width)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800320
Yu-Ping Wue445e042020-11-19 15:53:42 +0800321 if self.panel_stretch > 1:
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800322 raise BuildImageError('Panel aspect ratio (%f) is smaller than screen '
323 'aspect ratio (%f). It indicates screen will be '
324 'shrunk horizontally. It is currently unsupported.'
325 % (panel_width / panel_height,
326 self.screen_width / self.screen_height))
327
328 # Set up square drawing area
329 self.canvas_px = min(self.screen_width, self.screen_height)
330
Yu-Ping Wu3d272e72021-03-01 12:01:55 +0800331 def set_rename_map(self):
332 """Initializes a dict `self.rename_map` for image renaming.
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800333
Yu-Ping Wu3d272e72021-03-01 12:01:55 +0800334 For each items in the dict, image `key` will be renamed to `value`.
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800335 """
Yu-Ping Wu3d272e72021-03-01 12:01:55 +0800336 is_detachable = os.getenv('DETACHABLE') == '1'
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800337 physical_presence = os.getenv('PHYSICAL_PRESENCE')
Yu-Ping Wu3d272e72021-03-01 12:01:55 +0800338 rename_map = {}
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800339
Yu-Ping Wu3d272e72021-03-01 12:01:55 +0800340 # Navigation instructions
341 if is_detachable:
342 rename_map.update({
343 'nav-button_power': 'nav-key_enter',
344 'nav-button_volume_up': 'nav-key_up',
345 'nav-button_volume_down': 'nav-key_down',
346 'navigate0_tablet': 'navigate0',
347 'navigate1_tablet': 'navigate1',
348 })
349 else:
350 rename_map.update({
351 'nav-button_power': None,
352 'nav-button_volume_up': None,
353 'nav-button_volume_down': None,
354 'navigate0_tablet': None,
355 'navigate1_tablet': None,
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800356 })
357
Yu-Ping Wu3d272e72021-03-01 12:01:55 +0800358 # Physical presence confirmation
359 if physical_presence == 'recovery':
360 rename_map['rec_to_dev_desc1_phyrec'] = 'rec_to_dev_desc1'
361 rename_map['rec_to_dev_desc1_power'] = None
362 elif physical_presence == 'power':
363 rename_map['rec_to_dev_desc1_phyrec'] = None
364 rename_map['rec_to_dev_desc1_power'] = 'rec_to_dev_desc1'
365 else:
366 rename_map['rec_to_dev_desc1_phyrec'] = None
367 rename_map['rec_to_dev_desc1_power'] = None
368 if physical_presence != 'keyboard':
369 raise BuildImageError('Invalid physical presence setting %s for board '
370 '%s' % (physical_presence, self.board))
371
372 # Broken screen
373 if physical_presence == 'recovery':
374 rename_map['broken_desc_phyrec'] = 'broken_desc'
375 rename_map['broken_desc_detach'] = None
376 elif is_detachable:
377 rename_map['broken_desc_phyrec'] = None
378 rename_map['broken_desc_detach'] = 'broken_desc'
379 else:
380 rename_map['broken_desc_phyrec'] = None
381 rename_map['broken_desc_detach'] = None
382
383 # SD card
384 if not self.config[SDCARD_KEY]:
385 rename_map.update({
386 'rec_sel_desc1_no_sd': 'rec_sel_desc1',
387 'rec_sel_desc1_no_phone_no_sd': 'rec_sel_desc1_no_phone',
388 'rec_disk_step1_desc0_no_sd': 'rec_disk_step1_desc0',
389 })
390 else:
391 rename_map.update({
392 'rec_sel_desc1_no_sd': None,
393 'rec_sel_desc1_no_phone_no_sd': None,
394 'rec_disk_step1_desc0_no_sd': None,
395 })
396
397 # Check for duplicate new names
398 new_names = list(new_name for new_name in rename_map.values() if new_name)
399 if len(set(new_names)) != len(new_names):
400 raise BuildImageError('Duplicate values found in rename_map')
401
402 # Map new_name to None to skip image generation for it
403 for new_name in new_names:
404 if new_name not in rename_map:
405 rename_map[new_name] = None
406
407 # Print mapping
408 print('Rename map:')
409 for name, new_name in sorted(rename_map.items()):
410 print(' %s => %s' % (name, new_name))
411
412 self.rename_map = rename_map
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800413
414 def set_locales(self):
415 """Sets a list of locales for which localized images are converted."""
416 # LOCALES environment variable can overwrite boards.yaml
417 env_locales = os.getenv('LOCALES')
418 rtl_locales = set(self.config[RTL_KEY])
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800419 if env_locales:
420 locales = env_locales.split()
421 else:
422 locales = self.config[LOCALES_KEY]
423 # Check rtl_locales are contained in locales.
424 unknown_rtl_locales = rtl_locales - set(locales)
425 if unknown_rtl_locales:
426 raise BuildImageError('Unknown locales %s in %s' %
427 (list(unknown_rtl_locales), RTL_KEY))
Yu-Ping Wuabb9afb2020-10-27 17:15:22 +0800428 self.locales = [LocaleInfo(code, code in rtl_locales)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800429 for code in locales]
430
Yu-Ping Wu96cf0022021-01-07 15:55:49 +0800431 @classmethod
432 def get_text_colors(cls, dpi):
433 """Derive maximum text colors from `dpi`."""
434 if dpi < 64:
435 return 2
436 elif dpi < 72:
437 return 3
438 elif dpi < 80:
439 return 4
440 elif dpi < 96:
441 return 5
442 elif dpi < 112:
443 return 6
444 else:
445 return 7
446
Yu-Ping Wu08defcc2020-05-07 16:21:03 +0800447 def _to_px(self, length, num_lines=1):
448 """Converts the relative coordinate to absolute one in pixels."""
Yu-Ping Wu3d07a062021-01-26 18:10:32 +0800449 return int(self.canvas_px * length / SCALE_BASE) * num_lines
Yu-Ping Wu08defcc2020-05-07 16:21:03 +0800450
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800451 def _get_png_height(self, png_file):
452 with Image.open(png_file) as image:
453 return image.size[1]
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800454
455 def get_num_lines(self, file, one_line_dir):
456 """Gets the number of lines of text in `file`."""
457 name, _ = os.path.splitext(os.path.basename(file))
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800458 png_name = name + '.png'
459 multi_line_file = os.path.join(os.path.dirname(file), png_name)
460 one_line_file = os.path.join(one_line_dir, png_name)
461 # The number of lines is determined by comparing the height of
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800462 # `multi_line_file` with `one_line_file`, where the latter is generated
463 # without the '--width' option passed to pango-view.
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800464 height = self._get_png_height(multi_line_file)
465 line_height = self._get_png_height(one_line_file)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800466 return int(round(height / line_height))
467
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800468 def convert_svg_to_png(self, svg_file, png_file, height, num_lines,
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800469 background):
470 """Converts .svg file to .png file."""
471 background_hex = ''.join(format(x, '02x') for x in background)
472 # If the width/height of the SVG file is specified in points, the
473 # rsvg-convert command with default 90DPI will potentially cause the pixels
474 # at the right/bottom border of the output image to be transparent (or
475 # filled with the specified background color). This seems like an
476 # rsvg-convert issue regarding image scaling. Therefore, use 72DPI here
477 # to avoid the scaling.
478 command = ['rsvg-convert',
479 '--background-color', "'#%s'" % background_hex,
480 '--dpi-x', '72',
481 '--dpi-y', '72',
482 '-o', png_file]
Yu-Ping Wu08defcc2020-05-07 16:21:03 +0800483 height_px = self._to_px(height, num_lines)
Yu-Ping Wue445e042020-11-19 15:53:42 +0800484 if height_px <= 0:
485 raise BuildImageError('Height of %r <= 0 (%dpx)' %
486 (os.path.basename(svg_file), height_px))
487 command.extend(['--height', '%d' % height_px])
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800488 command.append(svg_file)
489 subprocess.check_call(' '.join(command), shell=True)
490
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800491 def convert_to_bitmap(self, input_file, height, num_lines, background, output,
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800492 max_colors):
493 """Converts an image file `input_file` to a BMP file `output`."""
494 image = Image.open(input_file)
495
496 # Process alpha channel and transparency.
497 if image.mode == 'RGBA':
498 target = Image.new('RGB', image.size, background)
499 image.load() # required for image.split()
500 mask = image.split()[-1]
501 target.paste(image, mask=mask)
502 elif (image.mode == 'P') and ('transparency' in image.info):
503 exit('Sorry, PNG with RGBA palette is not supported.')
504 elif image.mode != 'RGB':
505 target = image.convert('RGB')
506 else:
507 target = image
508
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800509 width_px, height_px = image.size
Yu-Ping Wu08defcc2020-05-07 16:21:03 +0800510 max_height_px = self._to_px(height, num_lines)
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800511 # If the image size is larger than what will be displayed at runtime,
512 # downscale it.
513 if height_px > max_height_px:
Yu-Ping Wu354a7002021-01-07 16:07:02 +0800514 if not self.dpi_warning_printed:
515 print('Reducing effective DPI to %d, limited by screen resolution' %
516 (self.config[DPI_KEY] * max_height_px // height_px))
517 self.dpi_warning_printed = True
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800518 height_px = max_height_px
519 width_px = height_px * image.size[0] // image.size[1]
520 # Stretch image horizontally for stretched display.
Yu-Ping Wue445e042020-11-19 15:53:42 +0800521 if self.panel_stretch != 1:
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800522 width_px = int(width_px * self.panel_stretch)
523 new_size = width_px, height_px
524 if new_size != image.size:
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800525 target = target.resize(new_size, Image.BICUBIC)
526
527 # Export and downsample color space.
528 target.convert('P', dither=None, colors=max_colors, palette=Image.ADAPTIVE
529 ).save(output)
530
531 with open(output, 'rb+') as f:
532 f.seek(BMP_HEADER_OFFSET_NUM_LINES)
533 f.write(bytearray([num_lines]))
534
Yu-Ping Wued95df32020-11-04 17:08:15 +0800535 def convert(self, files, output_dir, heights, max_widths, max_colors,
536 one_line_dir=None):
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800537 """Converts file(s) to bitmap format."""
538 if not files:
539 raise BuildImageError('Unable to find file(s) to convert')
540
541 for file in files:
542 name, ext = os.path.splitext(os.path.basename(file))
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800543
Yu-Ping Wu3d272e72021-03-01 12:01:55 +0800544 if name in self.rename_map:
545 new_name = self.rename_map[name]
546 if not new_name:
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800547 continue
Yu-Ping Wu3d272e72021-03-01 12:01:55 +0800548 else:
549 new_name = name
550 output = os.path.join(output_dir, new_name + self.DEFAULT_OUTPUT_EXT)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800551
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800552 background = self.BACKGROUND_COLORS.get(name, self.DEFAULT_BACKGROUND)
553 height = heights[name]
Yu-Ping Wued95df32020-11-04 17:08:15 +0800554 max_width = max_widths[name]
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800555
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800556 # Determine num_lines in order to scale the image
Yu-Ping Wued95df32020-11-04 17:08:15 +0800557 if one_line_dir and max_width:
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800558 num_lines = self.get_num_lines(file, one_line_dir)
559 else:
560 num_lines = 1
561
562 if ext == '.svg':
563 png_file = os.path.join(self.temp_dir, name + '.png')
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800564 self.convert_svg_to_png(file, png_file, height, num_lines, background)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800565 file = png_file
566
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800567 self.convert_to_bitmap(file, height, num_lines, background, output,
568 max_colors)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800569
Yu-Ping Wu177f12c2020-11-04 15:55:37 +0800570 def convert_sprite_images(self):
571 """Converts sprite images."""
572 names = self.formats[KEY_SPRITE_FILES]
573 styles = self.formats[KEY_STYLES]
574 # Check redundant images
575 for filename in glob.glob(os.path.join(self.ASSET_DIR, SVG_FILES)):
576 name, _ = os.path.splitext(os.path.basename(filename))
577 if name not in names:
578 raise BuildImageError('Sprite image %r not specified in %s' %
579 (filename, FORMAT_FILE))
580 # Convert images
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800581 files = []
Yu-Ping Wu177f12c2020-11-04 15:55:37 +0800582 heights = {}
583 for name, category in names.items():
584 style = get_config_with_defaults(styles, category)
585 files.append(os.path.join(self.ASSET_DIR, name + '.svg'))
586 heights[name] = style[KEY_HEIGHT]
Yu-Ping Wued95df32020-11-04 17:08:15 +0800587 max_widths = defaultdict(lambda: None)
588 self.convert(files, self.output_dir, heights, max_widths,
589 self.ASSET_MAX_COLORS)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800590
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800591 def build_generic_strings(self):
592 """Builds images of generic (locale-independent) strings."""
593 dpi = self.config[DPI_KEY]
594
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800595 names = self.formats[KEY_GENERIC_FILES]
596 styles = self.formats[KEY_STYLES]
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800597 fonts = self.formats[KEY_FONTS]
598 default_font = fonts[KEY_DEFAULT]
599
600 files = []
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800601 heights = {}
Yu-Ping Wued95df32020-11-04 17:08:15 +0800602 max_widths = {}
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800603 for txt_file in glob.glob(os.path.join(self.strings_dir, '*.txt')):
604 name, _ = os.path.splitext(os.path.basename(txt_file))
605 category = names[name]
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800606 style = get_config_with_defaults(styles, category)
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800607 convert_text_to_image(None, txt_file, default_font, self.stage_dir,
608 height=style[KEY_HEIGHT],
609 max_width=style[KEY_MAX_WIDTH],
610 dpi=dpi,
611 bgcolor=style[KEY_BGCOLOR],
612 fgcolor=style[KEY_FGCOLOR])
613 files.append(os.path.join(self.stage_dir, name + '.png'))
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800614 heights[name] = style[KEY_HEIGHT]
Yu-Ping Wued95df32020-11-04 17:08:15 +0800615 max_widths[name] = style[KEY_MAX_WIDTH]
Yu-Ping Wued95df32020-11-04 17:08:15 +0800616 self.convert(files, self.output_dir, heights, max_widths,
617 self.text_max_colors)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800618
Yu-Ping Wu08defcc2020-05-07 16:21:03 +0800619 def _check_text_width(self, output_dir, heights, max_widths):
620 """Check if the width of text image will exceed canvas boundary."""
621 for filename in glob.glob(os.path.join(output_dir,
622 '*' + self.DEFAULT_OUTPUT_EXT)):
623 name, _ = os.path.splitext(os.path.basename(filename))
624 max_width = max_widths[name]
625 if not max_width:
626 continue
627 max_width_px = self._to_px(max_width)
628 with open(filename, 'rb') as f:
629 f.seek(BMP_HEADER_OFFSET_NUM_LINES)
630 num_lines = f.read(1)[0]
631 height_px = self._to_px(heights[name] * num_lines)
632 with Image.open(filename) as image:
633 width_px = height_px * image.size[0] // image.size[1]
634 if width_px > max_width_px:
635 raise BuildImageError('%s: Image width %dpx greater than max width '
636 '%dpx' % (filename, width_px, max_width_px))
637
Yu-Ping Wu703dcfd2021-01-08 10:52:10 +0800638 def _copy_missing_bitmaps(self):
639 """Copy missing (not yet translated) strings from locale 'en'."""
640 en_files = glob.glob(os.path.join(self.output_ro_dir, 'en',
641 '*' + self.DEFAULT_OUTPUT_EXT))
642 for locale_info in self.locales:
643 locale = locale_info.code
644 if locale == 'en':
645 continue
646 ro_locale_dir = os.path.join(self.output_ro_dir, locale)
647 for en_file in en_files:
648 filename = os.path.basename(en_file)
649 locale_file = os.path.join(ro_locale_dir, filename)
650 if not os.path.isfile(locale_file):
651 print("WARNING: Locale '%s': copying '%s'" % (locale, filename))
652 shutil.copyfile(en_file, locale_file)
653
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800654 def generate_localized_pngs(self, names, json_dir):
655 """Generates PNG files for localized strings."""
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800656 styles = self.formats[KEY_STYLES]
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800657 fonts = self.formats[KEY_FONTS]
658 default_font = fonts[KEY_DEFAULT]
659 dpi = self.config[DPI_KEY]
660
661 # Ignore SIGINT in child processes
662 sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
663 pool = multiprocessing.Pool(multiprocessing.cpu_count())
664 signal.signal(signal.SIGINT, sigint_handler)
665
666 results = []
667 for locale_info in self.locales:
668 locale = locale_info.code
669 print(locale, end=' ', flush=True)
670 inputs = parse_locale_json_file(locale, json_dir)
671
672 # Walk locale directory to add pre-generated texts such as language names.
673 for txt_file in glob.glob(os.path.join(self.locale_dir, locale, '*.txt')):
674 name, _ = os.path.splitext(os.path.basename(txt_file))
675 with open(txt_file, 'r', encoding='utf-8-sig') as f:
676 inputs[name] = f.read().strip()
677
678 output_dir = os.path.join(self.stage_locale_dir, locale)
679 os.makedirs(output_dir, exist_ok=True)
680
681 for name, category in names.items():
682 # Ignore missing translation
683 if locale != 'en' and name not in inputs:
684 continue
685
686 # Write to text file
687 text_file = os.path.join(output_dir, name + '.txt')
688 with open(text_file, 'w', encoding='utf-8-sig') as f:
689 f.write(inputs[name] + '\n')
690
691 # Convert to PNG file
692 style = get_config_with_defaults(styles, category)
693 args = (
694 locale,
695 os.path.join(output_dir, '%s.txt' % name),
696 fonts.get(locale, default_font),
697 output_dir,
698 )
699 kwargs = {
700 'height': style[KEY_HEIGHT],
701 'max_width': style[KEY_MAX_WIDTH],
702 'dpi': dpi,
703 'bgcolor': style[KEY_BGCOLOR],
704 'fgcolor': style[KEY_FGCOLOR],
705 }
706 results.append(pool.apply_async(convert_text_to_image, args, kwargs))
707
708 print()
709 pool.close()
710
711 try:
712 for r in results:
713 r.get()
714 except KeyboardInterrupt:
715 pool.terminate()
716 pool.join()
717 exit('Aborted by user')
718 else:
719 pool.join()
720
721 def convert_localized_pngs(self, names):
722 """Converts PNGs of localized strings to BMPs."""
723 styles = self.formats[KEY_STYLES]
724 fonts = self.formats[KEY_FONTS]
725 default_font = fonts[KEY_DEFAULT]
726
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800727 heights = {}
Yu-Ping Wued95df32020-11-04 17:08:15 +0800728 max_widths = {}
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800729 for name, category in names.items():
730 style = get_config_with_defaults(styles, category)
731 heights[name] = style[KEY_HEIGHT]
Yu-Ping Wued95df32020-11-04 17:08:15 +0800732 max_widths[name] = style[KEY_MAX_WIDTH]
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800733
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800734 # Using stderr to report progress synchronously
735 print(' processing:', end='', file=sys.stderr, flush=True)
736 for locale_info in self.locales:
737 locale = locale_info.code
738 ro_locale_dir = os.path.join(self.output_ro_dir, locale)
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800739 stage_locale_dir = os.path.join(self.stage_locale_dir, locale)
Yu-Ping Wuabb9afb2020-10-27 17:15:22 +0800740 print(' ' + locale, end='', file=sys.stderr, flush=True)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800741 os.makedirs(ro_locale_dir)
742 self.convert(
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800743 glob.glob(os.path.join(stage_locale_dir, PNG_FILES)),
Yu-Ping Wued95df32020-11-04 17:08:15 +0800744 ro_locale_dir, heights, max_widths, self.text_max_colors,
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800745 one_line_dir=os.path.join(stage_locale_dir, ONE_LINE_DIR))
Yu-Ping Wu08defcc2020-05-07 16:21:03 +0800746 self._check_text_width(ro_locale_dir, heights, max_widths)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800747 print(file=sys.stderr)
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800748
749 def build_localized_strings(self):
750 """Builds images of localized strings."""
751 # Sources are one .grd file with identifiers chosen by engineers and
752 # corresponding English texts, as well as a set of .xlt files (one for each
753 # language other than US English) with a mapping from hash to translation.
754 # Because the keys in the xlt files are a hash of the English source text,
755 # rather than our identifiers, such as "btn_cancel", we use the "grit"
756 # command line tool to process the .grd and .xlt files, producing a set of
757 # .json files mapping our identifier to the translated string, one for every
758 # language including US English.
759
760 # Create a temporary directory to place the translation output from grit in.
761 json_dir = tempfile.mkdtemp()
762
763 # This invokes the grit build command to generate JSON files from the XTB
764 # files containing translations. The results are placed in `json_dir` as
765 # specified in firmware_strings.grd, i.e. one JSON file per locale.
766 subprocess.check_call([
767 'grit',
768 '-i', os.path.join(self.locale_dir, STRINGS_GRD_FILE),
769 'build',
770 '-o', os.path.join(json_dir),
771 ])
772
773 # Make a copy to avoid modifying `self.formats`
774 names = copy.deepcopy(self.formats[KEY_LOCALIZED_FILES])
775 if DIAGNOSTIC_UI:
776 names.update(self.formats[KEY_DIAGNOSTIC_FILES])
777
778 # TODO(b/163109632): Merge generate_localized_pngs() and
779 # convert_localized_pngs(), and parallelize them altogether.
780 self.generate_localized_pngs(names, json_dir)
781 shutil.rmtree(json_dir)
782
783 self.convert_localized_pngs(names)
Yu-Ping Wu703dcfd2021-01-08 10:52:10 +0800784 self._copy_missing_bitmaps()
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800785
786 def move_language_images(self):
787 """Renames language bitmaps and move to self.output_dir.
788
789 The directory self.output_dir contains locale-independent images, and is
790 used for creating vbgfx.bin by archive_images.py.
791 """
792 for locale_info in self.locales:
793 locale = locale_info.code
794 ro_locale_dir = os.path.join(self.output_ro_dir, locale)
795 old_file = os.path.join(ro_locale_dir, 'language.bmp')
796 new_file = os.path.join(self.output_dir, 'language_%s.bmp' % locale)
797 if os.path.exists(new_file):
798 raise BuildImageError('File already exists: %s' % new_file)
799 shutil.move(old_file, new_file)
800
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800801 def build_glyphs(self):
802 """Builds glyphs of ascii characters."""
803 os.makedirs(self.stage_font_dir, exist_ok=True)
804 files = []
805 # TODO(b/163109632): Parallelize the conversion of glyphs
806 for c in range(ord(' '), ord('~') + 1):
807 name = f'idx{c:03d}_{c:02x}'
808 txt_file = os.path.join(self.stage_font_dir, name + '.txt')
809 with open(txt_file, 'w', encoding='ascii') as f:
810 f.write(chr(c))
811 f.write('\n')
812 convert_text_to_image(None, txt_file, GLYPH_FONT, self.stage_font_dir,
813 height=DEFAULT_GLYPH_HEIGHT, use_svg=True)
814 files.append(os.path.join(self.stage_font_dir, name + '.svg'))
Yu-Ping Wu3d07a062021-01-26 18:10:32 +0800815 heights = defaultdict(lambda: DEFAULT_GLYPH_HEIGHT)
Yu-Ping Wued95df32020-11-04 17:08:15 +0800816 max_widths = defaultdict(lambda: None)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800817 font_output_dir = os.path.join(self.output_dir, 'font')
818 os.makedirs(font_output_dir)
Yu-Ping Wued95df32020-11-04 17:08:15 +0800819 self.convert(files, font_output_dir, heights, max_widths,
820 self.text_max_colors)
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800821
822 def copy_images_to_rw(self):
823 """Copies localized images specified in boards.yaml for RW override."""
824 if not self.config[RW_OVERRIDE_KEY]:
825 print(' No localized images are specified for RW, skipping')
826 return
827
828 for locale_info in self.locales:
829 locale = locale_info.code
830 rw_locale_dir = os.path.join(self.output_ro_dir, locale)
831 ro_locale_dir = os.path.join(self.output_rw_dir, locale)
832 os.makedirs(rw_locale_dir)
833
834 for name in self.config[RW_OVERRIDE_KEY]:
835 ro_src = os.path.join(ro_locale_dir, name + self.DEFAULT_OUTPUT_EXT)
836 rw_dst = os.path.join(rw_locale_dir, name + self.DEFAULT_OUTPUT_EXT)
837 shutil.copyfile(ro_src, rw_dst)
838
839 def create_locale_list(self):
840 """Creates locale list as a CSV file.
841
842 Each line in the file is of format "code,rtl", where
843 - "code": language code of the locale
844 - "rtl": "1" for right-to-left language, "0" otherwise
845 """
846 with open(os.path.join(self.output_dir, 'locales'), 'w') as f:
847 for locale_info in self.locales:
848 f.write('{},{}\n'.format(locale_info.code,
849 int(locale_info.rtl)))
850
851 def build(self):
852 """Builds all images required by a board."""
853 # Clean up output directory
854 if os.path.exists(self.output_dir):
855 shutil.rmtree(self.output_dir)
856 os.makedirs(self.output_dir)
857
858 if not os.path.exists(self.stage_dir):
859 raise BuildImageError('Missing stage folder. Run make in strings dir.')
860
861 # Clean up temp directory
862 if os.path.exists(self.temp_dir):
863 shutil.rmtree(self.temp_dir)
864 os.makedirs(self.temp_dir)
865
Yu-Ping Wu177f12c2020-11-04 15:55:37 +0800866 print('Converting sprite images...')
867 self.convert_sprite_images()
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800868
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800869 print('Building generic strings...')
870 self.build_generic_strings()
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800871
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800872 print('Building localized strings...')
873 self.build_localized_strings()
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800874
875 print('Moving language images to locale-independent directory...')
876 self.move_language_images()
877
878 print('Creating locale list file...')
879 self.create_locale_list()
880
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800881 print('Building glyphs...')
882 self.build_glyphs()
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800883
884 print('Copying specified images to RW packing directory...')
885 self.copy_images_to_rw()
886
887
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800888def main():
889 """Builds bitmaps for firmware screens."""
890 parser = argparse.ArgumentParser()
891 parser.add_argument('board', help='Target board')
892 args = parser.parse_args()
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800893 board = args.board
Yu-Ping Wu8c8bfc72020-10-27 16:19:34 +0800894
895 with open(FORMAT_FILE, encoding='utf-8') as f:
896 formats = yaml.load(f)
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800897 board_config = load_boards_config(BOARDS_CONFIG_FILE)[board]
898
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800899 print('Building for ' + board)
Yu-Ping Wu675e7e82021-01-29 08:32:12 +0800900 check_fonts(formats[KEY_FONTS])
901 print('Output dir: ' + OUTPUT_DIR)
Yu-Ping Wue66a7b02020-11-19 15:18:08 +0800902 converter = Converter(board, formats, board_config, OUTPUT_DIR)
903 converter.build()
Yu-Ping Wu7f6639a2020-09-28 15:31:35 +0800904
905
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800906if __name__ == '__main__':
Yu-Ping Wu6e4d3892020-10-19 14:09:37 +0800907 main()