Merge changes I7b7cee68,I07d22c0d
am: fb8a636a2d
Change-Id: I859acad650b34e6038034ecd8dd42d899eb40281
This commit is contained in:
commit
5e789287d4
1 changed files with 176 additions and 28 deletions
|
@ -32,9 +32,11 @@ import java.awt.FontFormatException;
|
|||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.AttributedString;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
@ -44,6 +46,8 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
|
@ -57,6 +61,8 @@ public class ImageGenerator {
|
|||
|
||||
private static final float DEFAULT_FONT_SIZE = 40;
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(ImageGenerator.class.getName());
|
||||
|
||||
// This is the canvas we used to draw texts.
|
||||
private BufferedImage mBufferedImage;
|
||||
|
||||
|
@ -83,6 +89,20 @@ public class ImageGenerator {
|
|||
// Align the text in the center of the image.
|
||||
private final boolean mCenterAlignment;
|
||||
|
||||
// Some localized font cannot draw the word "Android" and some PUNCTUATIONS; we need to fall
|
||||
// back to use our default latin font instead.
|
||||
private static final char[] PUNCTUATIONS = {',', ';', '.', '!' };
|
||||
|
||||
private static final String ANDROID_STRING = "Android";
|
||||
|
||||
// The width of the word "Android" when drawing with the default font.
|
||||
private int mAndroidStringWidth;
|
||||
|
||||
// The default Font to draw latin characters. It's loaded from DEFAULT_FONT_NAME.
|
||||
private Font mDefaultFont;
|
||||
// Cache of the loaded fonts for all languages.
|
||||
private Map<String, Font> mLoadedFontMap;
|
||||
|
||||
// An explicit map from language to the font name to use.
|
||||
// The map is extracted from frameworks/base/data/fonts/fonts.xml.
|
||||
// And the language-subtag-registry is found in:
|
||||
|
@ -160,6 +180,72 @@ public class ImageGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class maintains the content of wrapped text, the attributes to draw these text, and
|
||||
* the width of each wrapped lines.
|
||||
*/
|
||||
private class WrappedTextInfo {
|
||||
/** LineInfo holds the AttributedString and width of each wrapped line. */
|
||||
private class LineInfo {
|
||||
public AttributedString mLineContent;
|
||||
public int mLineWidth;
|
||||
|
||||
LineInfo(AttributedString text, int width) {
|
||||
mLineContent = text;
|
||||
mLineWidth = width;
|
||||
}
|
||||
}
|
||||
|
||||
// Maintains the content of each line, as well as the width needed to draw these lines for
|
||||
// a given language.
|
||||
public List<LineInfo> mWrappedLines;
|
||||
|
||||
WrappedTextInfo() {
|
||||
mWrappedLines = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given text has words "Android" and some PUNCTUATIONS. If it does, and its
|
||||
* associated textFont cannot display them correctly (e.g. for persian and hebrew); sets the
|
||||
* attributes of these substrings to use our default font instead.
|
||||
*
|
||||
* @param text the input string to perform the check on
|
||||
* @param width the pre-calculated width for the given text
|
||||
* @param textFont the localized font to draw the input string
|
||||
* @param fallbackFont our default font to draw latin characters
|
||||
*/
|
||||
public void addLine(String text, int width, Font textFont, Font fallbackFont) {
|
||||
AttributedString attributedText = new AttributedString(text);
|
||||
attributedText.addAttribute(TextAttribute.FONT, textFont);
|
||||
attributedText.addAttribute(TextAttribute.SIZE, mFontSize);
|
||||
|
||||
// Skips the check if we don't specify a fallbackFont.
|
||||
if (fallbackFont != null) {
|
||||
// Adds the attribute to use default font to draw the word "Android".
|
||||
if (text.contains(ANDROID_STRING)
|
||||
&& textFont.canDisplayUpTo(ANDROID_STRING) != -1) {
|
||||
int index = text.indexOf(ANDROID_STRING);
|
||||
attributedText.addAttribute(TextAttribute.FONT, fallbackFont, index,
|
||||
index + ANDROID_STRING.length());
|
||||
}
|
||||
|
||||
// Adds the attribute to use default font to draw the PUNCTUATIONS ", . !"
|
||||
for (char punctuation : PUNCTUATIONS) {
|
||||
if (text.indexOf(punctuation) != -1 && !textFont.canDisplay(punctuation)) {
|
||||
int index = 0;
|
||||
while ((index = text.indexOf(punctuation, index)) != -1) {
|
||||
attributedText.addAttribute(TextAttribute.FONT, fallbackFont, index,
|
||||
index + 1);
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mWrappedLines.add(new LineInfo(attributedText, width));
|
||||
}
|
||||
}
|
||||
|
||||
/** Initailizes the fields of the image image. */
|
||||
public ImageGenerator(
|
||||
int initialImageWidth,
|
||||
|
@ -177,6 +263,7 @@ public class ImageGenerator {
|
|||
mTextName = textName;
|
||||
mFontSize = fontSize;
|
||||
mFontDirPath = fontDirPath;
|
||||
mLoadedFontMap = new TreeMap<>();
|
||||
|
||||
mCenterAlignment = centerAlignment;
|
||||
}
|
||||
|
@ -239,12 +326,13 @@ public class ImageGenerator {
|
|||
* directory and collect the translated text.
|
||||
*
|
||||
* @param resourcePath the path to the resource directory
|
||||
* @param localesSet a list of supported locales; resources of other locales will be omitted.
|
||||
* @return a map with the locale as key, and translated text as value
|
||||
* @throws LocalizedStringNotFoundException if we cannot find the translated text for the given
|
||||
* locale
|
||||
*/
|
||||
public Map<Locale, String> readLocalizedStringFromXmls(String resourcePath)
|
||||
throws IOException, LocalizedStringNotFoundException {
|
||||
public Map<Locale, String> readLocalizedStringFromXmls(String resourcePath,
|
||||
Set<String> localesSet) throws IOException, LocalizedStringNotFoundException {
|
||||
File resourceDir = new File(resourcePath);
|
||||
if (!resourceDir.isDirectory()) {
|
||||
throw new LocalizedStringNotFoundException(resourcePath + " is not a directory.");
|
||||
|
@ -271,6 +359,12 @@ public class ImageGenerator {
|
|||
String[] nameList =
|
||||
resourceDir.list((File file, String name) -> name.startsWith("values-"));
|
||||
for (String name : nameList) {
|
||||
String localeString = name.substring(7);
|
||||
if (localesSet != null && !localesSet.contains(localeString)) {
|
||||
LOGGER.info("Skip parsing text for locale " + localeString);
|
||||
continue;
|
||||
}
|
||||
|
||||
File textFile = new File(resourcePath, name + "/strings.xml");
|
||||
String localizedText;
|
||||
try {
|
||||
|
@ -295,12 +389,18 @@ public class ImageGenerator {
|
|||
* @throws FontFormatException if the font file doesn't have the expected format
|
||||
*/
|
||||
private Font loadFontsByLocale(String language) throws IOException, FontFormatException {
|
||||
if (mLoadedFontMap.containsKey(language)) {
|
||||
return mLoadedFontMap.get(language);
|
||||
}
|
||||
|
||||
String fontName = LANGUAGE_TO_FONT_MAP.getOrDefault(language, DEFAULT_FONT_NAME);
|
||||
String[] suffixes = {".otf", ".ttf", ".ttc"};
|
||||
for (String suffix : suffixes) {
|
||||
File fontFile = new File(mFontDirPath, fontName + suffix);
|
||||
if (fontFile.isFile()) {
|
||||
return Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(mFontSize);
|
||||
Font result = Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(mFontSize);
|
||||
mLoadedFontMap.put(language, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,39 +409,53 @@ public class ImageGenerator {
|
|||
}
|
||||
|
||||
/** Separates the text string by spaces and wraps it by words. */
|
||||
private List<String> wrapTextByWords(String text, FontMetrics metrics) {
|
||||
List<String> wrappedText = new ArrayList<>();
|
||||
private WrappedTextInfo wrapTextByWords(String text, FontMetrics metrics) {
|
||||
WrappedTextInfo info = new WrappedTextInfo();
|
||||
StringTokenizer st = new StringTokenizer(text, " \n");
|
||||
|
||||
int lineWidth = 0; // Width of the processed words of the current line.
|
||||
StringBuilder line = new StringBuilder();
|
||||
while (st.hasMoreTokens()) {
|
||||
String token = st.nextToken();
|
||||
if (metrics.stringWidth(line + token + " ") > mImageWidth) {
|
||||
wrappedText.add(line.toString());
|
||||
int tokenWidth = metrics.stringWidth(token + " ");
|
||||
// Handles the width mismatch of the word "Android" between different fonts.
|
||||
if (token.contains(ANDROID_STRING)
|
||||
&& metrics.getFont().canDisplayUpTo(ANDROID_STRING) != -1) {
|
||||
tokenWidth = tokenWidth - metrics.stringWidth(ANDROID_STRING) + mAndroidStringWidth;
|
||||
}
|
||||
|
||||
if (lineWidth + tokenWidth > mImageWidth) {
|
||||
info.addLine(line.toString(), lineWidth, metrics.getFont(), mDefaultFont);
|
||||
|
||||
line = new StringBuilder();
|
||||
lineWidth = 0;
|
||||
}
|
||||
line.append(token).append(" ");
|
||||
lineWidth += tokenWidth;
|
||||
}
|
||||
wrappedText.add(line.toString());
|
||||
|
||||
return wrappedText;
|
||||
info.addLine(line.toString(), lineWidth, metrics.getFont(), mDefaultFont);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/** One character is a word for CJK. */
|
||||
private List<String> wrapTextByCharacters(String text, FontMetrics metrics) {
|
||||
List<String> wrappedText = new ArrayList<>();
|
||||
|
||||
private WrappedTextInfo wrapTextByCharacters(String text, FontMetrics metrics) {
|
||||
WrappedTextInfo info = new WrappedTextInfo();
|
||||
// TODO (xunchang) handle the text wrapping with logogram language mixed with latin.
|
||||
StringBuilder line = new StringBuilder();
|
||||
for (char token : text.toCharArray()) {
|
||||
if (metrics.stringWidth(line + Character.toString(token)) > mImageWidth) {
|
||||
wrappedText.add(line.toString());
|
||||
info.addLine(line.toString(), metrics.stringWidth(line.toString()),
|
||||
metrics.getFont(), null);
|
||||
line = new StringBuilder();
|
||||
}
|
||||
line.append(token);
|
||||
}
|
||||
wrappedText.add(line.toString());
|
||||
info.addLine(line.toString(), metrics.stringWidth(line.toString()), metrics.getFont(),
|
||||
null);
|
||||
|
||||
return wrappedText;
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -350,9 +464,10 @@ public class ImageGenerator {
|
|||
* @param text the string representation of text to wrap
|
||||
* @param metrics the metrics of the Font used to draw the text; it gives the width in pixels of
|
||||
* the text given its string representation
|
||||
* @return a list of strings with their width smaller than mImageWidth pixels
|
||||
* @return a WrappedTextInfo class with the width of each AttributedString smaller than
|
||||
* mImageWidth pixels
|
||||
*/
|
||||
private List<String> wrapText(String text, FontMetrics metrics, String language) {
|
||||
private WrappedTextInfo wrapText(String text, FontMetrics metrics, String language) {
|
||||
if (LOGOGRAM_LANGUAGE.contains(language)) {
|
||||
return wrapTextByCharacters(text, metrics);
|
||||
}
|
||||
|
@ -401,11 +516,11 @@ public class ImageGenerator {
|
|||
throws IOException, FontFormatException {
|
||||
Graphics2D graphics = createGraphics(locale);
|
||||
FontMetrics fontMetrics = graphics.getFontMetrics();
|
||||
List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage());
|
||||
WrappedTextInfo wrappedTextInfo = wrapText(text, fontMetrics, locale.getLanguage());
|
||||
|
||||
int textWidth = 0;
|
||||
for (String line : wrappedText) {
|
||||
textWidth = Math.max(textWidth, fontMetrics.stringWidth(line));
|
||||
for (WrappedTextInfo.LineInfo lineInfo : wrappedTextInfo.mWrappedLines) {
|
||||
textWidth = Math.max(textWidth, lineInfo.mLineWidth);
|
||||
}
|
||||
|
||||
// This may happen if one single word is larger than the image width.
|
||||
|
@ -432,21 +547,23 @@ public class ImageGenerator {
|
|||
*/
|
||||
private void drawText(String text, Locale locale, String languageTag)
|
||||
throws IOException, FontFormatException {
|
||||
System.out.println("Encoding \"" + locale + "\" as \"" + languageTag + "\": " + text);
|
||||
LOGGER.info("Encoding \"" + locale + "\" as \"" + languageTag + "\": " + text);
|
||||
|
||||
Graphics2D graphics = createGraphics(locale);
|
||||
FontMetrics fontMetrics = graphics.getFontMetrics();
|
||||
List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage());
|
||||
WrappedTextInfo wrappedTextInfo = wrapText(text, fontMetrics, locale.getLanguage());
|
||||
|
||||
// Marks the start y offset for the text image of current locale; and reserves one line to
|
||||
// encode the image metadata.
|
||||
int currentImageStart = mVerticalOffset;
|
||||
mVerticalOffset += 1;
|
||||
for (String line : wrappedText) {
|
||||
for (WrappedTextInfo.LineInfo lineInfo : wrappedTextInfo.mWrappedLines) {
|
||||
int lineHeight = fontMetrics.getHeight();
|
||||
// Doubles the height of the image if we are short of space.
|
||||
if (mVerticalOffset + lineHeight >= mImageHeight) {
|
||||
resize(mImageWidth, mImageHeight * 2);
|
||||
// Recreates the graphics since it's attached to the buffered image.
|
||||
graphics = createGraphics(locale);
|
||||
}
|
||||
|
||||
// Draws the text at mVerticalOffset and increments the offset with line space.
|
||||
|
@ -455,12 +572,11 @@ public class ImageGenerator {
|
|||
// Draws from right if it's an RTL language.
|
||||
int x =
|
||||
mCenterAlignment
|
||||
? (mImageWidth - fontMetrics.stringWidth(line)) / 2
|
||||
? (mImageWidth - lineInfo.mLineWidth) / 2
|
||||
: RTL_LANGUAGE.contains(languageTag)
|
||||
? mImageWidth - fontMetrics.stringWidth(line)
|
||||
? mImageWidth - lineInfo.mLineWidth
|
||||
: 0;
|
||||
|
||||
graphics.drawString(line, x, baseLine);
|
||||
graphics.drawString(lineInfo.mLineContent.getIterator(), x, baseLine);
|
||||
|
||||
mVerticalOffset += lineHeight;
|
||||
}
|
||||
|
@ -502,6 +618,11 @@ public class ImageGenerator {
|
|||
*/
|
||||
public void generateImage(Map<Locale, String> localizedTextMap, String outputPath)
|
||||
throws FontFormatException, IOException {
|
||||
FontMetrics defaultFontMetrics =
|
||||
createGraphics(Locale.forLanguageTag("en")).getFontMetrics();
|
||||
mDefaultFont = defaultFontMetrics.getFont();
|
||||
mAndroidStringWidth = defaultFontMetrics.stringWidth(ANDROID_STRING);
|
||||
|
||||
Map<String, Integer> languageCount = new TreeMap<>();
|
||||
int textWidth = 0;
|
||||
for (Locale locale : localizedTextMap.keySet()) {
|
||||
|
@ -587,6 +708,19 @@ public class ImageGenerator {
|
|||
.hasArg(false)
|
||||
.create());
|
||||
|
||||
options.addOption(
|
||||
OptionBuilder.withLongOpt("verbose")
|
||||
.withDescription("Output the logging above info level.")
|
||||
.hasArg(false)
|
||||
.create());
|
||||
|
||||
options.addOption(
|
||||
OptionBuilder.withLongOpt("locales")
|
||||
.withDescription("A list of android locales separated by ',' e.g."
|
||||
+ " 'af,en,zh-rTW'")
|
||||
.hasArg(true)
|
||||
.create());
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
|
@ -606,6 +740,12 @@ public class ImageGenerator {
|
|||
|
||||
int imageWidth = Integer.parseUnsignedInt(cmd.getOptionValue("image_width"));
|
||||
|
||||
if (cmd.hasOption("verbose")) {
|
||||
LOGGER.setLevel(Level.INFO);
|
||||
} else {
|
||||
LOGGER.setLevel(Level.WARNING);
|
||||
}
|
||||
|
||||
ImageGenerator imageGenerator =
|
||||
new ImageGenerator(
|
||||
imageWidth,
|
||||
|
@ -614,8 +754,16 @@ public class ImageGenerator {
|
|||
cmd.getOptionValue("font_dir"),
|
||||
cmd.hasOption("center_alignment"));
|
||||
|
||||
Set<String> localesSet = null;
|
||||
if (cmd.hasOption("locales")) {
|
||||
String[] localesList = cmd.getOptionValue("locales").split(",");
|
||||
localesSet = new HashSet<>(Arrays.asList(localesList));
|
||||
// Ensures that we have the default locale, all english translations are identical.
|
||||
localesSet.add("en-rAU");
|
||||
}
|
||||
Map<Locale, String> localizedStringMap =
|
||||
imageGenerator.readLocalizedStringFromXmls(cmd.getOptionValue("resource_dir"));
|
||||
imageGenerator.readLocalizedStringFromXmls(cmd.getOptionValue("resource_dir"),
|
||||
localesSet);
|
||||
imageGenerator.generateImage(localizedStringMap, cmd.getOptionValue("output_file"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue