blob: 6467bc5b290a36b36c32b350c5688dd2d8195e23 [file] [log] [blame]
Hung-Te Lin707e2ef2013-08-06 10:20:04 +08001#!/usr/bin/env python
2# 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.
5"""Build localized text resources by extracting firmware localization strings
6 and convert into TXT and PNG files into stage folder.
7
8Usage:
9 ./build.py <locale-list>
10"""
11
12# TODO(hungte) Read, write and handle UTF8 BOM.
13
Yu-Ping Wu6b282c52020-03-19 12:54:15 +080014import enum
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080015import glob
Hung-Te Lin04addcc2015-03-23 18:43:30 +080016import multiprocessing
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080017import os
18import re
19import subprocess
20import sys
Hung-Te Lin04addcc2015-03-23 18:43:30 +080021
Hung-Te Lindf738512018-09-14 08:39:27 +080022from PIL import Image
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080023import yaml
24
25SCRIPT_BASE = os.path.dirname(os.path.abspath(__file__))
26KEY_LOCALES = 'locales'
27KEY_INPUTS = 'inputs'
28KEY_FILES = 'files'
29KEY_FONTS = 'fonts'
30KEY_STYLES = 'styles'
Yu-Ping Wu2f061872019-12-23 17:18:29 +080031LEGACY_MENU_INPUTS = 'legacy_menu_inputs'
32LEGACY_MENU_FILES = 'legacy_menu_files'
33LEGACY_CLAMSHELL_FILES = 'legacy_clamshell_files'
Mathew King89d48c62019-02-15 10:08:39 -070034VENDOR_INPUTS = 'vendor_inputs'
35VENDOR_FILES = 'vendor_files'
Matt Delco4c5580d2019-03-07 14:00:28 -080036DIAGNOSTIC_FILES = 'diagnostic_files'
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080037
Yu-Ping Wu6b282c52020-03-19 12:54:15 +080038STRINGS_FILE = 'strings.txt'
Yu-Ping Wu2f061872019-12-23 17:18:29 +080039LEGACY_STRINGS_FILE = 'legacy_strings.txt'
40LEGACY_MENU_STRINGS_FILE = 'legacy_menu_strings.txt'
Mathew King89d48c62019-02-15 10:08:39 -070041VENDOR_STRINGS_FILE = 'vendor_strings.txt'
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080042FORMAT_FILE = 'format.yaml'
Yu-Ping Wu6b282c52020-03-19 12:54:15 +080043LEGACY_FORMAT_FILE = 'legacy_format.yaml'
Mathew King89d48c62019-02-15 10:08:39 -070044VENDOR_FORMAT_FILE = 'vendor_format.yaml'
Hung-Te Line2d00042014-02-14 19:41:10 +080045TXT_TO_PNG_SVG = os.path.join(SCRIPT_BASE, '..', 'text_to_png_svg')
Hung-Te Lin6e381992015-03-18 20:09:30 +080046OUTPUT_DIR = os.path.join(os.getenv('OUTPUT', os.path.join(SCRIPT_BASE, '..',
47 '..', 'build')),
48 '.stage', 'locale')
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080049
Mathew King89d48c62019-02-15 10:08:39 -070050VENDOR_STRINGS_DIR = os.getenv("VENDOR_STRINGS_DIR")
51VENDOR_STRINGS = VENDOR_STRINGS_DIR != None
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080052
Yu-Ping Wu6b282c52020-03-19 12:54:15 +080053class UIType(enum.Enum):
54 MENU = 1
55 LEGACY_MENU = 2
56 LEGACY_CLAMSHELL = 3
57
58UI = (UIType.MENU if os.getenv("MENU_UI") == "1" else
59 UIType.LEGACY_MENU if os.getenv("LEGACY_MENU_UI") == "1" else
60 UIType.LEGACY_CLAMSHELL)
61
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080062class DataError(Exception):
63 pass
64
65
66def GetImageWidth(filename):
67 """Returns the width of given image file."""
68 return Image.open(filename).size[0]
69
Mathew King89d48c62019-02-15 10:08:39 -070070def ParseLocaleInputFile(locale_dir, strings_file, input_format):
71 """Parses firmware string file in given locale directory for BuildTextFiles
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080072
73 Args:
Mathew King89d48c62019-02-15 10:08:39 -070074 locale: The locale folder with firmware string files.
75 strings_file: The name of the string txt file
76 input_format: Format description for each line in strings_file.
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080077
78 Returns:
79 A dictionary for mapping of "name to content" for files to be generated.
80 """
Mathew King89d48c62019-02-15 10:08:39 -070081 input_file = os.path.join(locale_dir, strings_file)
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080082 with open(input_file, 'r') as f:
83 input_data = f.readlines()
84 if len(input_data) != len(input_format):
Mathew King89d48c62019-02-15 10:08:39 -070085 raise DataError('Input file <%s> for locale <%s> '
86 'does not match input format.' %
87 (strings_file, locale_dir))
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080088 input_data = [s.strip() for s in input_data]
Mathew King89d48c62019-02-15 10:08:39 -070089 return dict(zip(input_format, input_data))
Hung-Te Lin707e2ef2013-08-06 10:20:04 +080090
Mathew King89d48c62019-02-15 10:08:39 -070091def ParseLocaleInputFiles(locale_dir, input_format,
Yu-Ping Wu2f061872019-12-23 17:18:29 +080092 legacy_menu_format, vendor_format):
Mathew King89d48c62019-02-15 10:08:39 -070093 """Parses all firmware string files in given locale directory for
94 BuildTextFiles
95
96 Args:
97 locale: The locale folder with firmware string files.
Yu-Ping Wu2f061872019-12-23 17:18:29 +080098 input_format: Format description for each line in LEGACY_STRINGS_FILE.
99 legacy_menu_format: Format description for each line in
100 LEGACY_MENU_STRINGS_FILE.
Mathew King89d48c62019-02-15 10:08:39 -0700101 vendor_format: Format description for each line in VENDOR_STRINGS_FILE.
102
103 Returns:
104 A dictionary for mapping of "name to content" for files to be generated.
105 """
106 result = dict()
107 result.update(ParseLocaleInputFile(locale_dir,
Yu-Ping Wu6b282c52020-03-19 12:54:15 +0800108 STRINGS_FILE if UI == UIType.MENU
109 else LEGACY_STRINGS_FILE,
Mathew King89d48c62019-02-15 10:08:39 -0700110 input_format))
Yu-Ping Wu6b282c52020-03-19 12:54:15 +0800111 # Now parse legacy menu strings
112 if UI == UIType.LEGACY_MENU:
Yu-Ping Wu2f061872019-12-23 17:18:29 +0800113 print " (legacy_menu_ui enabled)"
Mathew King89d48c62019-02-15 10:08:39 -0700114 result.update(ParseLocaleInputFile(locale_dir,
Yu-Ping Wu2f061872019-12-23 17:18:29 +0800115 LEGACY_MENU_STRINGS_FILE,
116 legacy_menu_format))
Mathew King89d48c62019-02-15 10:08:39 -0700117
118 # Parse vendor files if enabled
119 if VENDOR_STRINGS:
120 print " (vendor specific strings)"
121 result.update(
122 ParseLocaleInputFile(os.path.join(VENDOR_STRINGS_DIR, locale_dir),
123 VENDOR_STRINGS_FILE,
124 vendor_format))
125
126 # Walk locale directory to add pre-generated items.
127 for input_file in glob.glob(os.path.join(locale_dir, "*.txt")):
Yu-Ping Wu2f061872019-12-23 17:18:29 +0800128 if (os.path.basename(input_file) == LEGACY_STRINGS_FILE or
129 os.path.basename(input_file) == LEGACY_MENU_STRINGS_FILE or
Mathew King89d48c62019-02-15 10:08:39 -0700130 os.path.basename(input_file) == VENDOR_STRINGS_FILE):
131 continue
132 name, _ = os.path.splitext(os.path.basename(input_file))
133 with open(input_file, "r") as f:
134 result[name] = f.read().strip()
Shelley Chen2f616ac2017-05-22 13:19:40 -0700135
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800136 return result
137
138
139def CreateFile(file_name, contents, output_dir):
140 """Creates a text file in output directory by given contents.
141
142 Args:
143 file_name: Output file name without extension.
144 contents: A list of strings for file content.
145 output_dir: The directory to store output file.
146 """
147 output_name = os.path.join(output_dir, file_name + '.txt')
148 with open(output_name, 'w') as f:
149 f.write('\n'.join(contents) + '\n')
150
151
152def ModifyContent(input_data, command):
153 """Modifies some input content with given Regex commands.
154
155 Args:
156 input_data: Input string to be modified.
157 command: Regex commands to execute.
158
159 Returns:
160 Processed output string.
161 """
162 if not command.startswith('s/'):
163 raise DataError('Unknown command: %s' % command)
164 _, pattern, repl, _ = command.split('/')
165 return re.sub(pattern, repl, input_data)
166
167
168def BuildTextFiles(inputs, files, output_dir):
169 """Builds text files from given input data.
170
171 Args:
172 inputs: Dictionary of contents for given file name.
173 files: List of file records: [name, content].
174 output_dir: Directory to generate text files.
175 """
176 for file_name, file_content in files.iteritems():
177 if file_content is None:
178 CreateFile(file_name, [inputs[file_name]], output_dir)
179 else:
180 contents = []
181 for data in file_content:
182 if '@' in data:
183 name, _, command = data.partition('@')
184 contents.append(ModifyContent(inputs[name], command))
185 else:
186 contents.append('' if data == '' else inputs[data])
187 CreateFile(file_name, contents, output_dir)
188
189
Daisuke Nojiri9ae0ed82016-08-30 15:43:00 -0700190def ConvertPngFile(locale, file_name, styles, fonts, output_dir):
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800191 """Converts text files into PNG image files.
192
193 Args:
194 locale: Locale (language) to select implicit rendering options.
Hung-Te Lin04addcc2015-03-23 18:43:30 +0800195 file_name: String of input file name to generate.
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800196 styles: Dictionary to get associated per-file style options.
197 fonts: Dictionary to get associated per-file font options.
198 output_dir: Directory to generate image files.
199 """
Hung-Te Lin04addcc2015-03-23 18:43:30 +0800200 input_file = os.path.join(output_dir, file_name + '.txt')
201 command = [TXT_TO_PNG_SVG, "--lan=%s" % locale, "--outdir=%s" % output_dir]
202 if file_name in styles:
203 command.append(styles[file_name])
204 if locale in fonts:
205 command.append("--font='%s'" % fonts[locale])
206 font_size = os.getenv("FONTSIZE")
207 if font_size is not None:
208 command.append('--point=%r' % font_size)
209 command.append(input_file)
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800210
Hung-Te Lin04addcc2015-03-23 18:43:30 +0800211 if subprocess.call(' '.join(command), shell=True,
212 stdout=subprocess.PIPE) != 0:
213 return False
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800214
Hung-Te Lin04addcc2015-03-23 18:43:30 +0800215 # Check output file size
216 output_file = os.path.join(output_dir, file_name + '.png')
Hung-Te Lin04addcc2015-03-23 18:43:30 +0800217
218 return True
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800219
220def main(argv):
Yu-Ping Wu6b282c52020-03-19 12:54:15 +0800221 with open(FORMAT_FILE if UI == UIType.MENU else LEGACY_FORMAT_FILE) as f:
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800222 formats = yaml.load(f)
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800223
Mathew King89d48c62019-02-15 10:08:39 -0700224 if VENDOR_STRINGS:
225 with open(os.path.join(VENDOR_STRINGS_DIR, VENDOR_FORMAT_FILE)) as f:
226 formats.update(yaml.load(f))
227
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800228 # Decide locales to build.
229 if len(argv) > 0:
230 locales = argv
231 else:
232 locales = os.getenv('LOCALES', '').split()
233 if not locales:
234 locales = formats[KEY_LOCALES]
235
Hung-Te Lin04addcc2015-03-23 18:43:30 +0800236 pool = multiprocessing.Pool(multiprocessing.cpu_count())
237 results = []
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800238 for locale in locales:
239 print locale,
Mathew King89d48c62019-02-15 10:08:39 -0700240 inputs = ParseLocaleInputFiles(locale, formats[KEY_INPUTS],
Yu-Ping Wu6b282c52020-03-19 12:54:15 +0800241 formats.get(LEGACY_MENU_INPUTS),
Mathew King89d48c62019-02-15 10:08:39 -0700242 formats[VENDOR_INPUTS] if VENDOR_STRINGS
243 else None)
Vadim Bendeburyc2dfa1d2014-02-12 10:32:27 -0800244 output_dir = os.path.normpath(os.path.join(OUTPUT_DIR, locale))
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800245 if not os.path.exists(output_dir):
246 os.makedirs(output_dir)
Julius Wernera77900c2018-02-01 17:39:07 -0800247 files = formats[KEY_FILES]
248 styles = formats[KEY_STYLES]
Yu-Ping Wu6b282c52020-03-19 12:54:15 +0800249 if UI == UIType.LEGACY_MENU:
Yu-Ping Wu2f061872019-12-23 17:18:29 +0800250 files.update(formats[LEGACY_MENU_FILES])
Yu-Ping Wu6b282c52020-03-19 12:54:15 +0800251 elif UI == UIType.LEGACY_CLAMSHELL:
Yu-Ping Wu2f061872019-12-23 17:18:29 +0800252 files.update(formats[LEGACY_CLAMSHELL_FILES])
Matt Delco4c5580d2019-03-07 14:00:28 -0800253
254 # Now parse strings for optional features
255 if os.getenv("DIAGNOSTIC_UI") == "1":
256 files.update(formats[DIAGNOSTIC_FILES])
257
Mathew King89d48c62019-02-15 10:08:39 -0700258 if VENDOR_STRINGS:
259 files.update(formats[VENDOR_FILES])
Shelley Chen2f616ac2017-05-22 13:19:40 -0700260 BuildTextFiles(inputs, files, output_dir)
261
Hung-Te Lin04addcc2015-03-23 18:43:30 +0800262 results += [pool.apply_async(ConvertPngFile,
Daisuke Nojiri9ae0ed82016-08-30 15:43:00 -0700263 (locale, file_name,
Shelley Chen2f616ac2017-05-22 13:19:40 -0700264 styles, formats[KEY_FONTS],
Hung-Te Lin04addcc2015-03-23 18:43:30 +0800265 output_dir))
266 for file_name in formats[KEY_FILES]]
267 print ""
268 pool.close()
269 pool.join()
270 if not all((r.get() for r in results)):
271 exit("Failed to render some locales.")
Hung-Te Lin707e2ef2013-08-06 10:20:04 +0800272
273
274if __name__ == '__main__':
275 main(sys.argv[1:])