blob: d1bf2a6be44393df2a4297418aebfdb62170a207 [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package org.mozc.android.inputmethod.japanese.view;
import org.mozc.android.inputmethod.japanese.MozcLog;
import org.mozc.android.inputmethod.japanese.MozcUtil;
import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Join;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Picture;
import android.graphics.RadialGradient;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PictureDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
/**
* Factory to create Drawables from raw resources which is in mozc original format.
*
* Implementation note: We decided to use vector-rendering to support various devices
* which are different display resolutions.
* For that purpose, we needed to have some vector format. {@code PictureDrawable}'s
* serialization/deserialization seemed what we needed, but it turned out that its binary format
* seems not compatible among various devices, unfortunately.
* So, we decided to use our original format, and this class parses it.
* Also, for performance purpose, this class caches the parsed drawable.
*
*/
class MozcDrawableFactory {
private static class MozcStyle {
Paint paint = new Paint();
int dominantBaseline = COMMAND_PICTURE_PAINT_DOMINANTE_BASELINE_AUTO;
}
/** Locale field for {@link Paint#setTextLocale(Locale)}. */
private static final Optional<Locale> TEXT_LOCALE = (Build.VERSION.SDK_INT >= 17)
? Optional.of(Locale.JAPAN) : Optional.<Locale>absent();
private static final int DRAWABLE_PICTURE = 1;
private static final int DRAWABLE_STATE_LIST = 2;
private static final int COMMAND_PICTURE_EOP = 0;
private static final int COMMAND_PICTURE_DRAW_PATH = 1;
private static final int COMMAND_PICTURE_DRAW_POLYLINE = 2;
private static final int COMMAND_PICTURE_DRAW_POLYGON = 3;
private static final int COMMAND_PICTURE_DRAW_LINE = 4;
private static final int COMMAND_PICTURE_DRAW_RECT = 5;
private static final int COMMAND_PICTURE_DRAW_CIRCLE = 6;
private static final int COMMAND_PICTURE_DRAW_ELLIPSE = 7;
private static final int COMMAND_PICTURE_DRAW_GROUP_START = 8;
private static final int COMMAND_PICTURE_DRAW_GROUP_END = 9;
private static final int COMMAND_PICTURE_DRAW_TEXT = 10;
private static final int COMMAND_PICTURE_PATH_EOP = 0;
private static final int COMMAND_PICTURE_PATH_MOVE = 1;
private static final int COMMAND_PICTURE_PATH_LINE = 2;
private static final int COMMAND_PICTURE_PATH_HORIZONTAL_LINE = 3;
private static final int COMMAND_PICTURE_PATH_VERTICAL_LINE = 4;
private static final int COMMAND_PICTURE_PATH_CURVE = 5;
private static final int COMMAND_PICTURE_PATH_CONTINUED_CURVE = 6;
private static final int COMMAND_PICTURE_PATH_CLOSE = 7;
private static final int COMMAND_PICTURE_PAINT_EOP = 0;
private static final int COMMAND_PICTURE_PAINT_STYLE = 1;
private static final int COMMAND_PICTURE_PAINT_COLOR = 2;
private static final int COMMAND_PICTURE_PAINT_SHADOW = 3;
private static final int COMMAND_PICTURE_PAINT_STROKE_WIDTH = 4;
private static final int COMMAND_PICTURE_PAINT_STROKE_CAP = 5;
private static final int COMMAND_PICTURE_PAINT_STROKE_JOIN = 6;
private static final int COMMAND_PICTURE_PAINT_SHADER = 7;
private static final int COMMAND_PICTURE_PAINT_FONT_SIZE = 8;
private static final int COMMAND_PICTURE_PAINT_TEXT_ANCHOR = 9;
private static final int COMMAND_PICTURE_PAINT_DOMINANT_BASELINE = 10;
private static final int COMMAND_PICTURE_PAINT_FONT_WEIGHT = 11;
private static final int COMMAND_PICTURE_PAINT_TEXT_ANCHOR_START = 0;
private static final int COMMAND_PICTURE_PAINT_TEXT_ANCHOR_MIDDLE = 1;
private static final int COMMAND_PICTURE_PAINT_TEXT_ANCHOR_END = 2;
private static final int COMMAND_PICTURE_PAINT_DOMINANTE_BASELINE_AUTO = 0;
@SuppressWarnings("unused")
private static final int COMMAND_PICTURE_PAINT_DOMINANTE_BASELINE_CENTRAL = 1;
@SuppressWarnings("unused")
private static final int COMMAND_PICTURE_PAINT_FONT_WEIGHT_NORMAL = 0;
private static final int COMMAND_PICTURE_PAINT_FONT_WEIGHT_BOLD = 1;
private static final int COMMAND_PICTURE_SHADER_LINEAR_GRADIENT = 1;
private static final int COMMAND_PICTURE_SHADER_RADIAL_GRADIENT = 2;
private static final int[] EMPTY_STATE_LIST = {};
private static final String FONT_PATH = "subset_font.otf";
private final Resources resources;
private final WeakDrawableCache cacheMap = new WeakDrawableCache();
private final Skin skin;
private static volatile Optional<Typeface> typeface = Optional.absent();
MozcDrawableFactory(Resources resources, Skin skin) {
this.resources = Preconditions.checkNotNull(resources);
this.skin = Preconditions.checkNotNull(skin);
ensureTypeface(resources.getAssets());
}
Optional<Drawable> getDrawable(int resourceId) {
if (!resources.getResourceTypeName(resourceId).equalsIgnoreCase("raw")) {
// For non-"raw" resources, just delegate loading to Resources.
return Optional.fromNullable(resources.getDrawable(resourceId));
}
Integer key = Integer.valueOf(resourceId);
Optional<Drawable> drawable = cacheMap.get(key);
if (!drawable.isPresent()) {
InputStream stream = resources.openRawResource(resourceId);
try {
boolean success = false;
try {
drawable = createDrawable(new DataInputStream(stream), skin);
success = true;
} finally {
MozcUtil.close(stream, !success);
}
} catch (IOException e) {
MozcLog.e("Failed to parse file", e);
}
if (drawable.isPresent()) {
cacheMap.put(key, drawable.get());
}
}
return drawable;
}
private static Optional<Drawable> createDrawable(DataInputStream stream, Skin skin)
throws IOException {
Preconditions.checkNotNull(stream);
byte tag = stream.readByte();
switch (tag) {
case DRAWABLE_PICTURE:
return Optional.<Drawable>of(createPictureDrawable(stream, skin));
case DRAWABLE_STATE_LIST:
return Optional.<Drawable>of(createStateListDrawable(stream, skin));
default:
MozcLog.e("Unknown tag: " + tag);
}
return Optional.absent();
}
// Note, PictureDrawable may cause runtime slowness.
// Instead, we can prepare pre-rendered bit-map drawable, by modifying the interface to take
// the drawable, which should be faster theoretically.
private static PictureDrawable createPictureDrawable(DataInputStream stream, Skin skin)
throws IOException {
Preconditions.checkNotNull(stream);
Preconditions.checkNotNull(skin);
// The first eight bytes are width and height (four bytes for each).
int width = stream.readUnsignedShort();
int height = stream.readUnsignedShort();
Picture picture = new Picture();
Canvas canvas = picture.beginRecording(width, height);
MozcStyle style = new MozcStyle();
resetStyle(style);
LOOP: while (true) {
byte command = stream.readByte();
switch (command) {
case COMMAND_PICTURE_EOP:
// The end of picture.
break LOOP;
case COMMAND_PICTURE_DRAW_PATH: {
Path path = createPath(stream);
int size = stream.readByte();
if (size == 0) {
resetStyle(style);
canvas.drawPath(path, style.paint);
} else {
for (int i = 0; i < size; ++i) {
resetStyle(style);
parseStyle(stream, skin, style);
canvas.drawPath(path, style.paint);
}
}
break;
}
case COMMAND_PICTURE_DRAW_POLYLINE: {
int length = stream.readUnsignedByte();
if (length < 2 || length % 2 != 0) {
throw new IllegalArgumentException();
}
float[] points = new float[length];
for (int i = 0; i < length; ++i) {
points[i] = readCompressedFloat(stream);
}
int size = stream.readByte();
if (size == 0) {
resetStyle(style);
for (int i = 0; i < length - 2; i += 2) {
canvas.drawLine(points[i], points[i + 1], points[i + 2], points[i + 3], style.paint);
}
} else {
for (int i = 0; i < size; ++i) {
resetStyle(style);
parseStyle(stream, skin, style);
for (int j = 0; j < length - 2; j += 2) {
canvas.drawLine(points[j], points[j + 1], points[j + 2], points[j + 3],
style.paint);
}
}
}
break;
}
case COMMAND_PICTURE_DRAW_POLYGON: {
int length = stream.readUnsignedByte();
if (length < 2 || length % 2 != 0) {
throw new IllegalArgumentException();
}
Path path = new Path();
{
float x = readCompressedFloat(stream);
float y = readCompressedFloat(stream);
path.moveTo(x, y);
}
for (int i = 2; i < length; i += 2) {
float x = readCompressedFloat(stream);
float y = readCompressedFloat(stream);
path.lineTo(x, y);
}
path.close();
int size = stream.readUnsignedByte();
if (size == 0) {
resetStyle(style);
canvas.drawPath(path, style.paint);
} else {
for (int i = 0; i < size; ++i) {
resetStyle(style);
parseStyle(stream, skin, style);
canvas.drawPath(path, style.paint);
}
}
break;
}
case COMMAND_PICTURE_DRAW_LINE: {
float x1 = readCompressedFloat(stream);
float y1 = readCompressedFloat(stream);
float x2 = readCompressedFloat(stream);
float y2 = readCompressedFloat(stream);
int size = stream.readUnsignedByte();
if (size == 0) {
resetStyle(style);
canvas.drawLine(x1, y1, x2, y2, style.paint);
} else {
for (int i = 0; i < size; ++i) {
resetStyle(style);
parseStyle(stream, skin, style);
canvas.drawLine(x1, y1, x2, y2, style.paint);
}
}
break;
}
case COMMAND_PICTURE_DRAW_RECT: {
float x = readCompressedFloat(stream);
float y = readCompressedFloat(stream);
float w = readCompressedFloat(stream);
float h = readCompressedFloat(stream);
int size = stream.readUnsignedByte();
if (size == 0) {
resetStyle(style);
canvas.drawRect(x, y, x + w, y + h, style.paint);
} else {
for (int i = 0; i < size; ++i) {
resetStyle(style);
parseStyle(stream, skin, style);
canvas.drawRect(x, y, x + w, y + h, style.paint);
}
}
break;
}
case COMMAND_PICTURE_DRAW_CIRCLE: {
float cx = readCompressedFloat(stream);
float cy = readCompressedFloat(stream);
float r = readCompressedFloat(stream);
RectF bound = new RectF(cx - r, cy - r, cx + r, cy + r);
int size = stream.readUnsignedByte();
if (size == 0) {
resetStyle(style);
canvas.drawOval(bound, style.paint);
} else {
for (int i = 0; i < size; ++i) {
resetStyle(style);
parseStyle(stream, skin, style);
canvas.drawOval(bound, style.paint);
}
}
break;
}
case COMMAND_PICTURE_DRAW_ELLIPSE: {
float cx = readCompressedFloat(stream);
float cy = readCompressedFloat(stream);
float rx = readCompressedFloat(stream);
float ry = readCompressedFloat(stream);
RectF bound = new RectF(cx - rx, cy - ry, cx + rx, cy + ry);
int size = stream.readUnsignedByte();
if (size == 0) {
resetStyle(style);
canvas.drawOval(bound, style.paint);
} else {
for (int i = 0; i < size; ++i) {
resetStyle(style);
parseStyle(stream, skin, style);
canvas.drawOval(bound, style.paint);
}
}
break;
}
case COMMAND_PICTURE_DRAW_GROUP_START: {
float m11 = readCompressedFloat(stream);
float m21 = readCompressedFloat(stream);
float m31 = readCompressedFloat(stream);
float m12 = readCompressedFloat(stream);
float m22 = readCompressedFloat(stream);
float m32 = readCompressedFloat(stream);
float m13 = readCompressedFloat(stream);
float m23 = readCompressedFloat(stream);
float m33 = readCompressedFloat(stream);
Matrix matrix = new Matrix();
matrix.setValues(new float[] {m11, m12, m13, m21, m22, m23, m31, m32, m33});
canvas.save();
canvas.concat(matrix);
break;
}
case COMMAND_PICTURE_DRAW_GROUP_END:
canvas.restore();
break;
case COMMAND_PICTURE_DRAW_TEXT: {
float x = readCompressedFloat(stream);
float y = readCompressedFloat(stream);
short stringSize = stream.readShort();
byte[] stringBuffer = new byte[stringSize];
stream.read(stringBuffer);
String string = new String(stringBuffer, Charsets.UTF_8);
int size = stream.readByte();
if (size == 0) {
resetStyle(style);
canvas.drawText(string, x, y, style.paint);
} else {
for (int i = 0; i < size; ++i) {
resetStyle(style);
parseStyle(stream, skin, style);
float drawY = style.dominantBaseline == COMMAND_PICTURE_PAINT_DOMINANTE_BASELINE_AUTO
? y
: y - (style.paint.ascent() + style.paint.descent()) / 2;
canvas.drawText(string, x, drawY, style.paint);
}
}
break;
}
default:
MozcLog.e("unknown command " + command);
}
}
picture.endRecording();
return new MozcPictureDrawable(picture);
}
private void ensureTypeface(AssetManager assetManager) {
if (!typeface.isPresent()) {
synchronized (typeface) {
if (!typeface.isPresent()) {
try {
typeface = Optional.of(Typeface.createFromAsset(assetManager, FONT_PATH));
} catch (RuntimeException e) {
// Typeface cannot be made. Use default typeface as fallback.
MozcLog.e(FONT_PATH + " is not accessible. Use system font.");
typeface = Optional.of(Typeface.DEFAULT);
}
}
}
}
}
private static void resetStyle(MozcStyle style) {
style.paint.reset();
style.paint.setAntiAlias(true);
style.paint.setTypeface(typeface.get());
if (TEXT_LOCALE.isPresent()) {
style.paint.setTextLocale(TEXT_LOCALE.get());
}
style.dominantBaseline = COMMAND_PICTURE_PAINT_DOMINANTE_BASELINE_AUTO;
}
private static void parseStyle(DataInputStream stream, Skin skin, MozcStyle style)
throws IOException {
Paint paint = style.paint;
while (true) {
int tag = stream.readByte() & 0xFF;
if (tag >= 128) {
// This is a bit tricky format, but the highest 1-bit means that the style should be
// based on skin configuration. Delegate the paint to the skin.
skin.apply(paint, tag & 0x7F);
continue;
}
switch (tag) {
case COMMAND_PICTURE_PAINT_EOP:
return;
case COMMAND_PICTURE_PAINT_STYLE: {
paint.setStyle(Style.values()[stream.readUnsignedByte()]);
break;
}
case COMMAND_PICTURE_PAINT_COLOR: {
paint.setColor(stream.readInt());
break;
}
case COMMAND_PICTURE_PAINT_SHADOW: {
float r = readCompressedFloat(stream);
float x = readCompressedFloat(stream);
float y = readCompressedFloat(stream);
int color = stream.readInt();
paint.setShadowLayer(r, x, y, color);
break;
}
case COMMAND_PICTURE_PAINT_STROKE_WIDTH: {
paint.setStrokeWidth(readCompressedFloat(stream));
break;
}
case COMMAND_PICTURE_PAINT_STROKE_CAP: {
paint.setStrokeCap(Cap.values()[stream.readUnsignedByte()]);
break;
}
case COMMAND_PICTURE_PAINT_STROKE_JOIN: {
paint.setStrokeJoin(Join.values()[stream.readUnsignedByte()]);
break;
}
case COMMAND_PICTURE_PAINT_SHADER: {
paint.setShader(createShader(stream).orNull());
break;
}
case COMMAND_PICTURE_PAINT_FONT_SIZE: {
paint.setTextSize(readCompressedFloat(stream));
break;
}
case COMMAND_PICTURE_PAINT_TEXT_ANCHOR: {
byte value = stream.readByte();
switch (value) {
case COMMAND_PICTURE_PAINT_TEXT_ANCHOR_START:
paint.setTextAlign(Align.LEFT);
break;
case COMMAND_PICTURE_PAINT_TEXT_ANCHOR_MIDDLE:
paint.setTextAlign(Align.CENTER);
break;
case COMMAND_PICTURE_PAINT_TEXT_ANCHOR_END:
paint.setTextAlign(Align.RIGHT);
break;
default:
MozcLog.e("Unknown text-anchor : " + value, new Exception());
}
break;
}
case COMMAND_PICTURE_PAINT_DOMINANT_BASELINE: {
style.dominantBaseline = stream.readByte();
break;
}
case COMMAND_PICTURE_PAINT_FONT_WEIGHT: {
style.paint.setFakeBoldText(stream.readByte() == COMMAND_PICTURE_PAINT_FONT_WEIGHT_BOLD);
break;
}
default:
MozcLog.e("Unknown paint tag: " + tag, new Exception());
}
}
}
private static Optional<Shader> createShader(DataInputStream stream) throws IOException {
int tag = stream.readByte();
switch (tag) {
case COMMAND_PICTURE_SHADER_LINEAR_GRADIENT: {
float x1 = readCompressedFloat(stream);
float y1 = readCompressedFloat(stream);
float x2 = readCompressedFloat(stream);
float y2 = readCompressedFloat(stream);
int length = stream.readUnsignedByte();
int[] colors = new int[length];
float[] points = new float[length];
for (int i = 0; i < length; ++i) {
colors[i] = stream.readInt();
}
for (int i = 0; i < length; ++i) {
points[i] = readCompressedFloat(stream);
}
return Optional.<Shader>of(
new LinearGradient(x1, y1, x2, y2, colors, points, TileMode.CLAMP));
}
case COMMAND_PICTURE_SHADER_RADIAL_GRADIENT: {
float x = readCompressedFloat(stream);
float y = readCompressedFloat(stream);
float r = readCompressedFloat(stream);
Matrix matrix = null;
if (stream.readByte() != 0) {
float m11 = readCompressedFloat(stream);
float m21 = readCompressedFloat(stream);
@SuppressWarnings("unused")
float m12 = readCompressedFloat(stream);
float m22 = readCompressedFloat(stream);
float m13 = readCompressedFloat(stream);
float m23 = readCompressedFloat(stream);
matrix = new Matrix();
matrix.setValues(new float[] {m11, m21, 0f, m21, m22, 0f, m13, m23, 1f});
}
int length = stream.readByte();
int[] colors = new int[length];
float[] points = new float[length];
for (int i = 0; i < length; ++i) {
colors[i] = stream.readInt();
}
for (int i = 0; i < length; ++i) {
points[i] = readCompressedFloat(stream);
}
RadialGradient gradient = new RadialGradient(x, y, r, colors, points, TileMode.CLAMP);
if (matrix != null) {
gradient.setLocalMatrix(matrix);
}
return Optional.<Shader>of(gradient);
}
default:
MozcLog.e("Unknown shader type: " + tag);
}
return Optional.absent();
}
private static Path createPath(DataInputStream stream) throws IOException {
float startX = 0;
float startY = 0;
float prevX = 0;
float prevY = 0;
float prevControlX = 0;
float prevControlY = 0;
boolean hasPrevControl = false;
Path path = new Path();
while (true) {
byte command = stream.readByte();
switch (command) {
case COMMAND_PICTURE_PATH_EOP:
return path;
case COMMAND_PICTURE_PATH_MOVE: {
float x = readCompressedFloat(stream);
float y = readCompressedFloat(stream);
path.moveTo(x, y);
startX = x;
startY = y;
prevX = x;
prevY = y;
hasPrevControl = false;
break;
}
case COMMAND_PICTURE_PATH_LINE: {
float x = readCompressedFloat(stream);
float y = readCompressedFloat(stream);
path.lineTo(x, y);
prevX = x;
prevY = y;
hasPrevControl = false;
break;
}
case COMMAND_PICTURE_PATH_HORIZONTAL_LINE: {
float x = readCompressedFloat(stream);
path.lineTo(x, prevY);
prevX = x;
hasPrevControl = false;
break;
}
case COMMAND_PICTURE_PATH_VERTICAL_LINE: {
float y = readCompressedFloat(stream);
path.lineTo(prevX, y);
prevY = y;
hasPrevControl = false;
break;
}
case COMMAND_PICTURE_PATH_CURVE: {
float x1 = readCompressedFloat(stream);
float y1 = readCompressedFloat(stream);
float x2 = readCompressedFloat(stream);
float y2 = readCompressedFloat(stream);
float x = readCompressedFloat(stream);
float y = readCompressedFloat(stream);
path.cubicTo(x1, y1, x2, y2, x, y);
prevX = x;
prevY = y;
prevControlX = x2;
prevControlY = y2;
hasPrevControl = true;
break;
}
case COMMAND_PICTURE_PATH_CONTINUED_CURVE: {
float x2 = readCompressedFloat(stream);
float y2 = readCompressedFloat(stream);
float x = readCompressedFloat(stream);
float y = readCompressedFloat(stream);
float x1, y1;
if (hasPrevControl) {
x1 = 2 * prevX - prevControlX;
y1 = 2 * prevY - prevControlY;
} else {
x1 = prevX;
y1 = prevY;
}
path.cubicTo(x1, y1, x2, y2, x, y);
prevX = x;
prevY = y;
prevControlX = x2;
prevControlY = y2;
hasPrevControl = true;
break;
}
case COMMAND_PICTURE_PATH_CLOSE: {
path.close();
path.moveTo(startX, startY);
prevX = startX;
prevY = startY;
hasPrevControl = false;
break;
}
default:
MozcLog.e("Unknown command: " + command);
}
}
}
private static StateListDrawable createStateListDrawable(
DataInputStream stream, Skin skin) throws IOException {
int length = stream.readUnsignedByte();
StateListDrawable result = new StateListDrawable();
for (int i = 0; i < length; ++i) {
int[] stateList = createStateList(stream);
Optional<Drawable> drawable = createDrawable(stream, skin);
result.addState(stateList, drawable.orNull());
}
return result;
}
private static int[] createStateList(DataInputStream stream) throws IOException {
int length = stream.readUnsignedByte();
if (length == 0) {
return EMPTY_STATE_LIST;
}
int[] result = new int[length];
for (int i = 0; i < length; ++i) {
result[i] = stream.readInt();
}
return result;
}
private static float readCompressedFloat(DataInputStream stream) throws IOException {
// Note: the precision of float is a bit too-much for the mozc purpose.
// According to the precision in svg, 24-bits fixed-point value should be fine.
// We can reduce the package size of the picture data to about 3/4 by the hack.
// TODO(hidehiko): Implement 24-bits fixed-point value.
return stream.readFloat();
}
}