blob: a011f10e497bc35eacd5b626b919eb8cebd50259 [file] [log] [blame]
Tianjie Xu721f6792018-10-08 17:04:54 -07001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.recovery.tools;
18
Tao Bao529bb742018-11-06 11:24:53 -080019import org.apache.commons.cli.CommandLine;
20import org.apache.commons.cli.GnuParser;
21import org.apache.commons.cli.HelpFormatter;
22import org.apache.commons.cli.OptionBuilder;
23import org.apache.commons.cli.Options;
24import org.apache.commons.cli.ParseException;
25import org.w3c.dom.Document;
26import org.w3c.dom.Node;
27import org.w3c.dom.NodeList;
28
Tianjie Xu721f6792018-10-08 17:04:54 -070029import java.awt.Color;
30import java.awt.Font;
31import java.awt.FontFormatException;
32import java.awt.FontMetrics;
33import java.awt.Graphics2D;
34import java.awt.RenderingHints;
35import java.awt.image.BufferedImage;
36import java.io.File;
37import java.io.IOException;
38import java.util.ArrayList;
Tianjie Xu22dd0192018-10-17 15:39:00 -070039import java.util.Arrays;
40import java.util.HashSet;
Tianjie Xu721f6792018-10-08 17:04:54 -070041import java.util.List;
42import java.util.Locale;
43import java.util.Map;
Tianjie Xu22dd0192018-10-17 15:39:00 -070044import java.util.Set;
Tianjie Xu721f6792018-10-08 17:04:54 -070045import java.util.StringTokenizer;
Tao Bao529bb742018-11-06 11:24:53 -080046import java.util.TreeMap;
Tianjie Xu721f6792018-10-08 17:04:54 -070047
48import javax.imageio.ImageIO;
49import javax.xml.parsers.DocumentBuilder;
50import javax.xml.parsers.DocumentBuilderFactory;
51import javax.xml.parsers.ParserConfigurationException;
52
Tianjie Xub97f7e52018-11-12 16:28:14 -080053/** Command line tool to generate the localized image for recovery mode. */
Tianjie Xu721f6792018-10-08 17:04:54 -070054public class ImageGenerator {
Tianjie Xub97f7e52018-11-12 16:28:14 -080055 // Initial height of the image to draw.
56 private static final int INITIAL_HEIGHT = 20000;
Tianjie Xu721f6792018-10-08 17:04:54 -070057
Tianjie Xub97f7e52018-11-12 16:28:14 -080058 private static final float DEFAULT_FONT_SIZE = 40;
Tianjie Xu721f6792018-10-08 17:04:54 -070059
Tianjie Xub97f7e52018-11-12 16:28:14 -080060 // This is the canvas we used to draw texts.
61 private BufferedImage mBufferedImage;
Tianjie Xu721f6792018-10-08 17:04:54 -070062
Tianjie Xub97f7e52018-11-12 16:28:14 -080063 // The width in pixels of our image. Once set, its value won't change.
64 private final int mImageWidth;
Tianjie Xu721f6792018-10-08 17:04:54 -070065
Tianjie Xub97f7e52018-11-12 16:28:14 -080066 // The current height in pixels of our image. We will adjust the value when drawing more texts.
67 private int mImageHeight;
Tianjie Xu721f6792018-10-08 17:04:54 -070068
Tianjie Xub97f7e52018-11-12 16:28:14 -080069 // The current vertical offset in pixels to draw the top edge of new text strings.
70 private int mVerticalOffset;
Tianjie Xu721f6792018-10-08 17:04:54 -070071
Tianjie Xub97f7e52018-11-12 16:28:14 -080072 // The font size to draw the texts.
73 private final float mFontSize;
Tianjie Xu721f6792018-10-08 17:04:54 -070074
Tianjie Xub97f7e52018-11-12 16:28:14 -080075 // The name description of the text to localize. It's used to find the translated strings in the
76 // resource file.
77 private final String mTextName;
Tianjie Xu721f6792018-10-08 17:04:54 -070078
Tianjie Xub97f7e52018-11-12 16:28:14 -080079 // The directory that contains all the needed font files (e.g. ttf, otf, ttc files).
80 private final String mFontDirPath;
Tianjie Xu721f6792018-10-08 17:04:54 -070081
Tianjie Xub97f7e52018-11-12 16:28:14 -080082 // An explicit map from language to the font name to use.
83 // The map is extracted from frameworks/base/data/fonts/fonts.xml.
84 // And the language-subtag-registry is found in:
85 // https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
86 private static final String DEFAULT_FONT_NAME = "Roboto-Regular";
87 private static final Map<String, String> LANGUAGE_TO_FONT_MAP =
88 new TreeMap<String, String>() {
89 {
90 put("am", "NotoSansEthiopic-Regular");
91 put("ar", "NotoNaskhArabicUI-Regular");
92 put("as", "NotoSansBengaliUI-Regular");
93 put("bn", "NotoSansBengaliUI-Regular");
94 put("fa", "NotoNaskhArabicUI-Regular");
95 put("gu", "NotoSansGujaratiUI-Regular");
96 put("hi", "NotoSansDevanagariUI-Regular");
97 put("hy", "NotoSansArmenian-Regular");
98 put("iw", "NotoSansHebrew-Regular");
99 put("ja", "NotoSansCJK-Regular");
100 put("ka", "NotoSansGeorgian-Regular");
101 put("ko", "NotoSansCJK-Regular");
102 put("km", "NotoSansKhmerUI-Regular");
103 put("kn", "NotoSansKannadaUI-Regular");
104 put("lo", "NotoSansLaoUI-Regular");
105 put("ml", "NotoSansMalayalamUI-Regular");
106 put("mr", "NotoSansDevanagariUI-Regular");
107 put("my", "NotoSansMyanmarUI-Regular");
108 put("ne", "NotoSansDevanagariUI-Regular");
109 put("or", "NotoSansOriya-Regular");
110 put("pa", "NotoSansGurmukhiUI-Regular");
111 put("si", "NotoSansSinhala-Regular");
112 put("ta", "NotoSansTamilUI-Regular");
113 put("te", "NotoSansTeluguUI-Regular");
114 put("th", "NotoSansThaiUI-Regular");
115 put("ur", "NotoNaskhArabicUI-Regular");
116 put("zh", "NotoSansCJK-Regular");
117 }
118 };
Tianjie Xu721f6792018-10-08 17:04:54 -0700119
Tianjie Xub97f7e52018-11-12 16:28:14 -0800120 // Languages that write from right to left.
121 private static final Set<String> RTL_LANGUAGE =
122 new HashSet<String>() {
123 {
124 add("ar"); // Arabic
125 add("fa"); // Persian
126 add("he"); // Hebrew
127 add("iw"); // Hebrew
128 add("ur"); // Urdu
129 }
130 };
Tianjie Xu22dd0192018-10-17 15:39:00 -0700131
Tianjie Xub97f7e52018-11-12 16:28:14 -0800132 // Languages that breaks on arbitrary characters.
133 // TODO(xunchang) switch to icu library if possible.
134 private static final Set<String> LOGOGRAM_LANGUAGE =
135 new HashSet<String>() {
136 {
137 add("ja"); // Japanese
138 add("km"); // Khmer
139 add("ko"); // Korean
140 add("lo"); // Lao
141 add("zh"); // Chinese
142 }
143 };
Tianjie Xu22dd0192018-10-17 15:39:00 -0700144
Tianjie Xub97f7e52018-11-12 16:28:14 -0800145 /** Exception to indicate the failure to find the translated text strings. */
146 public static class LocalizedStringNotFoundException extends Exception {
147 public LocalizedStringNotFoundException(String message) {
148 super(message);
149 }
150
151 public LocalizedStringNotFoundException(String message, Throwable cause) {
152 super(message, cause);
153 }
Tianjie Xu721f6792018-10-08 17:04:54 -0700154 }
155
Tianjie Xub97f7e52018-11-12 16:28:14 -0800156 /** Initailizes the fields of the image image. */
157 public ImageGenerator(int imageWidth, String textName, float fontSize, String fontDirPath) {
158 mImageWidth = imageWidth;
159 mImageHeight = INITIAL_HEIGHT;
160 mVerticalOffset = 0;
Tianjie Xu721f6792018-10-08 17:04:54 -0700161
Tianjie Xub97f7e52018-11-12 16:28:14 -0800162 // Initialize the canvas with the default height.
163 mBufferedImage = new BufferedImage(mImageWidth, mImageHeight, BufferedImage.TYPE_BYTE_GRAY);
Tianjie Xu721f6792018-10-08 17:04:54 -0700164
Tianjie Xub97f7e52018-11-12 16:28:14 -0800165 mTextName = textName;
166 mFontSize = fontSize;
167 mFontDirPath = fontDirPath;
Tianjie Xu721f6792018-10-08 17:04:54 -0700168 }
169
Tianjie Xub97f7e52018-11-12 16:28:14 -0800170 /**
171 * Finds the translated text string for the given textName by parsing the resourceFile. Example
172 * of the xml fields: <resources xmlns:android="http://schemas.android.com/apk/res/android">
173 * <string name="recovery_installing_security" msgid="9184031299717114342"> "Sicherheitsupdate
174 * wird installiert"</string> </resources>
175 *
176 * @param resourceFile the input resource file in xml format.
177 * @param textName the name description of the text.
178 * @return the string representation of the translated text.
179 */
180 private String getTextString(File resourceFile, String textName)
181 throws IOException, ParserConfigurationException, org.xml.sax.SAXException,
182 LocalizedStringNotFoundException {
183 DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance();
184 DocumentBuilder db = builder.newDocumentBuilder();
Tianjie Xu721f6792018-10-08 17:04:54 -0700185
Tianjie Xub97f7e52018-11-12 16:28:14 -0800186 Document doc = db.parse(resourceFile);
187 doc.getDocumentElement().normalize();
Tianjie Xu721f6792018-10-08 17:04:54 -0700188
Tianjie Xub97f7e52018-11-12 16:28:14 -0800189 NodeList nodeList = doc.getElementsByTagName("string");
190 for (int i = 0; i < nodeList.getLength(); i++) {
191 Node node = nodeList.item(i);
192 String name = node.getAttributes().getNamedItem("name").getNodeValue();
193 if (name.equals(textName)) {
194 return node.getTextContent();
195 }
196 }
Tianjie Xu721f6792018-10-08 17:04:54 -0700197
Tianjie Xu721f6792018-10-08 17:04:54 -0700198 throw new LocalizedStringNotFoundException(
Tianjie Xub97f7e52018-11-12 16:28:14 -0800199 textName + " not found in " + resourceFile.getName());
Tianjie Xu721f6792018-10-08 17:04:54 -0700200 }
201
Tianjie Xub97f7e52018-11-12 16:28:14 -0800202 /** Constructs the locale from the name of the resource file. */
203 private Locale getLocaleFromFilename(String filename) throws IOException {
204 // Gets the locale string by trimming the top "values-".
205 String localeString = filename.substring(7);
206 if (localeString.matches("[A-Za-z]+")) {
207 return Locale.forLanguageTag(localeString);
208 }
209 if (localeString.matches("[A-Za-z]+-r[A-Za-z]+")) {
210 // "${Language}-r${Region}". e.g. en-rGB
211 String[] tokens = localeString.split("-r");
212 return Locale.forLanguageTag(String.join("-", tokens));
213 }
214 if (localeString.startsWith("b+")) {
215 // The special case of b+sr+Latn, which has the form "b+${Language}+${ScriptName}"
216 String[] tokens = localeString.substring(2).split("\\+");
217 return Locale.forLanguageTag(String.join("-", tokens));
218 }
Tianjie Xu721f6792018-10-08 17:04:54 -0700219
Tianjie Xub97f7e52018-11-12 16:28:14 -0800220 throw new IOException("Unrecognized locale string " + localeString);
Tianjie Xu721f6792018-10-08 17:04:54 -0700221 }
222
Tianjie Xub97f7e52018-11-12 16:28:14 -0800223 /**
224 * Iterates over the xml files in the format of values-$LOCALE/strings.xml under the resource
225 * directory and collect the translated text.
226 *
227 * @param resourcePath the path to the resource directory
228 * @return a map with the locale as key, and translated text as value
229 * @throws LocalizedStringNotFoundException if we cannot find the translated text for the given
230 * locale
231 */
232 public Map<Locale, String> readLocalizedStringFromXmls(String resourcePath)
233 throws IOException, LocalizedStringNotFoundException {
234 File resourceDir = new File(resourcePath);
235 if (!resourceDir.isDirectory()) {
236 throw new LocalizedStringNotFoundException(resourcePath + " is not a directory.");
237 }
Tianjie Xu721f6792018-10-08 17:04:54 -0700238
Tianjie Xub97f7e52018-11-12 16:28:14 -0800239 Map<Locale, String> result =
240 // Overrides the string comparator so that sr is sorted behind sr-Latn. And thus
241 // recovery can find the most relevant locale when going down the list.
242 new TreeMap<>(
243 (Locale l1, Locale l2) -> {
244 if (l1.toLanguageTag().equals(l2.toLanguageTag())) {
245 return 0;
246 }
247 if (l1.getLanguage().equals(l2.toLanguageTag())) {
248 return -1;
249 }
250 if (l2.getLanguage().equals(l1.toLanguageTag())) {
251 return 1;
252 }
253 return l1.toLanguageTag().compareTo(l2.toLanguageTag());
254 });
Tianjie Xu721f6792018-10-08 17:04:54 -0700255
Tianjie Xub97f7e52018-11-12 16:28:14 -0800256 // Find all the localized resource subdirectories in the format of values-$LOCALE
257 String[] nameList =
258 resourceDir.list((File file, String name) -> name.startsWith("values-"));
259 for (String name : nameList) {
260 File textFile = new File(resourcePath, name + "/strings.xml");
261 String localizedText;
262 try {
263 localizedText = getTextString(textFile, mTextName);
264 } catch (IOException | ParserConfigurationException | org.xml.sax.SAXException e) {
265 throw new LocalizedStringNotFoundException(
266 "Failed to read the translated text for locale " + name, e);
267 }
268
269 Locale locale = getLocaleFromFilename(name);
270 // Removes the double quotation mark from the text.
271 result.put(locale, localizedText.substring(1, localizedText.length() - 1));
272 }
273
274 return result;
275 }
276
277 /**
278 * Returns a font object associated given the given locale
279 *
280 * @throws IOException if the font file fails to open
281 * @throws FontFormatException if the font file doesn't have the expected format
282 */
283 private Font loadFontsByLocale(String language) throws IOException, FontFormatException {
284 String fontName = LANGUAGE_TO_FONT_MAP.getOrDefault(language, DEFAULT_FONT_NAME);
285 String[] suffixes = {".otf", ".ttf", ".ttc"};
286 for (String suffix : suffixes) {
287 File fontFile = new File(mFontDirPath, fontName + suffix);
288 if (fontFile.isFile()) {
289 return Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(mFontSize);
290 }
291 }
292
293 throw new IOException(
294 "Can not find the font file " + fontName + " for language " + language);
295 }
296
297 /** Separates the text string by spaces and wraps it by words. */
298 private List<String> wrapTextByWords(String text, FontMetrics metrics) {
299 List<String> wrappedText = new ArrayList<>();
300 StringTokenizer st = new StringTokenizer(text, " \n");
301
302 // TODO(xunchang). We assume that all words can fit on the screen. Raise an
303 // IllegalStateException if the word is wider than the image width.
304 StringBuilder line = new StringBuilder();
305 while (st.hasMoreTokens()) {
306 String token = st.nextToken();
307 if (metrics.stringWidth(line + token + " ") > mImageWidth) {
308 wrappedText.add(line.toString());
309 line = new StringBuilder();
310 }
311 line.append(token).append(" ");
312 }
Tianjie Xu721f6792018-10-08 17:04:54 -0700313 wrappedText.add(line.toString());
Tianjie Xub97f7e52018-11-12 16:28:14 -0800314
315 return wrappedText;
Tianjie Xu721f6792018-10-08 17:04:54 -0700316 }
Tianjie Xu721f6792018-10-08 17:04:54 -0700317
Tianjie Xub97f7e52018-11-12 16:28:14 -0800318 /** One character is a word for CJK. */
319 private List<String> wrapTextByCharacters(String text, FontMetrics metrics) {
320 List<String> wrappedText = new ArrayList<>();
Tianjie Xu721f6792018-10-08 17:04:54 -0700321
Tianjie Xub97f7e52018-11-12 16:28:14 -0800322 StringBuilder line = new StringBuilder();
323 for (char token : text.toCharArray()) {
324 if (metrics.stringWidth(line + Character.toString(token)) > mImageWidth) {
325 wrappedText.add(line.toString());
326 line = new StringBuilder();
327 }
328 line.append(token);
329 }
Tianjie Xu22dd0192018-10-17 15:39:00 -0700330 wrappedText.add(line.toString());
Tianjie Xu22dd0192018-10-17 15:39:00 -0700331
Tianjie Xub97f7e52018-11-12 16:28:14 -0800332 return wrappedText;
Tianjie Xu22dd0192018-10-17 15:39:00 -0700333 }
Tianjie Xu721f6792018-10-08 17:04:54 -0700334
Tianjie Xub97f7e52018-11-12 16:28:14 -0800335 /**
336 * Wraps the text with a maximum of mImageWidth pixels per line.
337 *
338 * @param text the string representation of text to wrap
339 * @param metrics the metrics of the Font used to draw the text; it gives the width in pixels of
340 * the text given its string representation
341 * @return a list of strings with their width smaller than mImageWidth pixels
342 */
343 private List<String> wrapText(String text, FontMetrics metrics, String language) {
344 if (LOGOGRAM_LANGUAGE.contains(language)) {
345 return wrapTextByCharacters(text, metrics);
346 }
Tianjie Xu721f6792018-10-08 17:04:54 -0700347
Tianjie Xub97f7e52018-11-12 16:28:14 -0800348 return wrapTextByWords(text, metrics);
Tianjie Xu721f6792018-10-08 17:04:54 -0700349 }
Tianjie Xu22dd0192018-10-17 15:39:00 -0700350
Tianjie Xub97f7e52018-11-12 16:28:14 -0800351 /**
352 * Encodes the information of the text image for |locale|. According to minui/resources.cpp, the
353 * width, height and locale of the image is decoded as: int w = (row[1] << 8) | row[0]; int h =
354 * (row[3] << 8) | row[2]; __unused int len = row[4]; char* loc =
355 * reinterpret_cast<char*>(&row[5]);
356 */
357 private List<Integer> encodeTextInfo(int width, int height, String locale) {
358 List<Integer> info =
359 new ArrayList<>(
360 Arrays.asList(
361 width & 0xff,
362 width >> 8,
363 height & 0xff,
364 height >> 8,
365 locale.length()));
Tianjie Xu721f6792018-10-08 17:04:54 -0700366
Tianjie Xub97f7e52018-11-12 16:28:14 -0800367 byte[] localeBytes = locale.getBytes();
368 for (byte b : localeBytes) {
369 info.add((int) b);
370 }
371 info.add(0);
Tianjie Xu721f6792018-10-08 17:04:54 -0700372
Tianjie Xub97f7e52018-11-12 16:28:14 -0800373 return info;
Tianjie Xu22dd0192018-10-17 15:39:00 -0700374 }
375
Tianjie Xub97f7e52018-11-12 16:28:14 -0800376 /**
377 * Draws the text string on the canvas for given locale.
378 *
379 * @param text the string to draw on canvas
380 * @param locale the current locale tag of the string to draw
381 * @throws IOException if we cannot find the corresponding font file for the given locale.
382 * @throws FontFormatException if we failed to load the font file for the given locale.
383 */
384 private void drawText(String text, Locale locale, String languageTag, boolean centralAlignment)
385 throws IOException, FontFormatException {
386 Graphics2D graphics = mBufferedImage.createGraphics();
387 graphics.setColor(Color.WHITE);
388 graphics.setRenderingHint(
389 RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
390 graphics.setFont(loadFontsByLocale(locale.getLanguage()));
Tianjie Xu22dd0192018-10-17 15:39:00 -0700391
Tianjie Xub97f7e52018-11-12 16:28:14 -0800392 System.out.println("Encoding \"" + locale + "\" as \"" + languageTag + "\": " + text);
393
394 FontMetrics fontMetrics = graphics.getFontMetrics();
395 List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage());
396
397 // Marks the start y offset for the text image of current locale; and reserves one line to
398 // encode the image metadata.
399 int currentImageStart = mVerticalOffset;
400 mVerticalOffset += 1;
401 for (String line : wrappedText) {
402 int lineHeight = fontMetrics.getHeight();
403 // Doubles the height of the image if we are short of space.
404 if (mVerticalOffset + lineHeight >= mImageHeight) {
405 resizeHeight(mImageHeight * 2);
406 }
407
408 // Draws the text at mVerticalOffset and increments the offset with line space.
409 int baseLine = mVerticalOffset + lineHeight - fontMetrics.getDescent();
410
411 // Draws from right if it's an RTL language.
412 int x =
413 centralAlignment
414 ? (mImageWidth - fontMetrics.stringWidth(line)) / 2
415 : RTL_LANGUAGE.contains(languageTag)
416 ? mImageWidth - fontMetrics.stringWidth(line)
417 : 0;
418
419 graphics.drawString(line, x, baseLine);
420
421 mVerticalOffset += lineHeight;
422 }
423
424 // Encodes the metadata of the current localized image as pixels.
425 int currentImageHeight = mVerticalOffset - currentImageStart - 1;
426 List<Integer> info = encodeTextInfo(mImageWidth, currentImageHeight, languageTag);
427 for (int i = 0; i < info.size(); i++) {
428 int[] pixel = {info.get(i)};
429 mBufferedImage.getRaster().setPixel(i, currentImageStart, pixel);
430 }
Tianjie Xu721f6792018-10-08 17:04:54 -0700431 }
432
Tianjie Xub97f7e52018-11-12 16:28:14 -0800433 /**
434 * Redraws the image with the new height.
435 *
436 * @param height the new height of the image in pixels.
437 */
438 private void resizeHeight(int height) {
439 BufferedImage resizedImage =
440 new BufferedImage(mImageWidth, height, BufferedImage.TYPE_BYTE_GRAY);
441 Graphics2D graphic = resizedImage.createGraphics();
442 graphic.drawImage(mBufferedImage, 0, 0, null);
443 graphic.dispose();
Tianjie Xu721f6792018-10-08 17:04:54 -0700444
Tianjie Xub97f7e52018-11-12 16:28:14 -0800445 mBufferedImage = resizedImage;
446 mImageHeight = height;
Tianjie Xu721f6792018-10-08 17:04:54 -0700447 }
448
Tianjie Xub97f7e52018-11-12 16:28:14 -0800449 /**
450 * This function draws the font characters and saves the result to outputPath.
451 *
452 * @param localizedTextMap a map from locale to its translated text string
453 * @param outputPath the path to write the generated image file.
454 * @throws FontFormatException if there's a format error in one of the font file
455 * @throws IOException if we cannot find the font file for one of the locale, or we failed to
456 * write the image file.
457 */
458 public void generateImage(Map<Locale, String> localizedTextMap, String outputPath)
459 throws FontFormatException, IOException {
460 Map<String, Integer> languageCount = new TreeMap<>();
461 for (Locale locale : localizedTextMap.keySet()) {
462 String language = locale.getLanguage();
463 languageCount.put(language, languageCount.getOrDefault(language, 0) + 1);
464 }
Tianjie Xu721f6792018-10-08 17:04:54 -0700465
Tianjie Xub97f7e52018-11-12 16:28:14 -0800466 for (Locale locale : localizedTextMap.keySet()) {
467 Integer count = languageCount.get(locale.getLanguage());
468 // Recovery expects en-US instead of en_US.
469 String languageTag = locale.toLanguageTag();
470 if (count == 1) {
471 // Make the last country variant for a given language be the catch-all for that
472 // language.
473 languageTag = locale.getLanguage();
474 } else {
475 languageCount.put(locale.getLanguage(), count - 1);
476 }
Tianjie Xu721f6792018-10-08 17:04:54 -0700477
Tianjie Xub97f7e52018-11-12 16:28:14 -0800478 drawText(localizedTextMap.get(locale), locale, languageTag, false);
479 }
480
481 // TODO(xunchang) adjust the width to save some space if all texts are smaller than
482 // imageWidth.
483 resizeHeight(mVerticalOffset);
484 ImageIO.write(mBufferedImage, "png", new File(outputPath));
485 }
486
487 /** Prints the helper message. */
488 public static void printUsage(Options options) {
489 new HelpFormatter().printHelp("java -jar path_to_jar [required_options]", options);
490 }
491
492 /** Creates the command line options. */
493 public static Options createOptions() {
494 Options options = new Options();
495 options.addOption(
496 OptionBuilder.withLongOpt("image_width")
497 .withDescription("The initial width of the image in pixels.")
498 .hasArgs(1)
499 .isRequired()
500 .create());
501
502 options.addOption(
503 OptionBuilder.withLongOpt("text_name")
504 .withDescription(
505 "The description of the text string, e.g. recovery_erasing")
506 .hasArgs(1)
507 .isRequired()
508 .create());
509
510 options.addOption(
511 OptionBuilder.withLongOpt("font_dir")
512 .withDescription(
513 "The directory that contains all the support font format files, "
514 + "e.g. $OUT/system/fonts/")
515 .hasArgs(1)
516 .isRequired()
517 .create());
518
519 options.addOption(
520 OptionBuilder.withLongOpt("resource_dir")
521 .withDescription(
522 "The resource directory that contains all the translated strings in"
523 + " xml format, e.g."
524 + " bootable/recovery/tools/recovery_l10n/res/")
525 .hasArgs(1)
526 .isRequired()
527 .create());
528
529 options.addOption(
530 OptionBuilder.withLongOpt("output_file")
531 .withDescription("Path to the generated image")
532 .hasArgs(1)
533 .isRequired()
534 .create());
535
536 return options;
537 }
538
539 /** The main function parses the command line options and generates the desired text image. */
540 public static void main(String[] args)
541 throws NumberFormatException, IOException, FontFormatException,
542 LocalizedStringNotFoundException {
543 Options options = createOptions();
544 CommandLine cmd;
545 try {
546 cmd = new GnuParser().parse(options, args);
547 } catch (ParseException e) {
548 System.err.println(e.getMessage());
549 printUsage(options);
550 return;
551 }
552
553 int imageWidth = Integer.parseUnsignedInt(cmd.getOptionValue("image_width"));
554
555 ImageGenerator imageGenerator =
556 new ImageGenerator(
557 imageWidth,
558 cmd.getOptionValue("text_name"),
559 DEFAULT_FONT_SIZE,
560 cmd.getOptionValue("font_dir"));
561
562 Map<Locale, String> localizedStringMap =
563 imageGenerator.readLocalizedStringFromXmls(cmd.getOptionValue("resource_dir"));
564 imageGenerator.generateImage(localizedStringMap, cmd.getOptionValue("output_file"));
565 }
Tianjie Xu721f6792018-10-08 17:04:54 -0700566}