mirror of
https://github.com/Suwayomi/Tachidesk.git
synced 2025-12-10 06:42:07 +01:00
Implement Bitmap.copy, text layouting (#1455)
* Bitmap: Use provided config * Bitmap: implement copy * Bitmap: Simplify getPixels This also fixes a bug where the returned data may not be in the correct format Android getPixels(): > The returned colors are non-premultiplied ARGB values in the sRGB color space. BufferedImage getRGB(): > Returns an array of integer pixels in the default RGB color model (TYPE_INT_ARGB) and default sRGB color space * Stub TextPaint and Paint * Paint: Implement some required functions * Stub StaticLayout and Layout * Implement some Paint support * Draw Bounds * WebP write support * First text rendering * Paint: Fix text size, font metrics * Paint: Fix not copying new properties Fixes font size in draw * Canvas: Stroke add cap/join for better aliasing Otherwise we get bad artifacts on sharp corners Based on https://stackoverflow.com/a/35222059/ * Remove logs * Canvas: Implement other drawText methods * Bitmap: support erase * Layout: Fix text direction Should be LTR, otherwise 0 is read, which is automatically interpreted as RTL without explicit check * Bitmap: scale to destination rectangle * Canvas: drawBitmap with just x/y * Bitmap: Convert image on JPEG export to RGB JPEG does not support alpha, so will throw "bogus color space" * Switch to newer fork
This commit is contained in:
@@ -47,4 +47,5 @@ dependencies {
|
||||
|
||||
// OpenJDK lacks native JPEG encoder and native WEBP decoder
|
||||
implementation(libs.bundles.twelvemonkeys)
|
||||
implementation(libs.sejda.webp)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package android.annotation;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
/**
|
||||
* <p>Denotes that the annotated element represents a packed color
|
||||
* long. If applied to a long array, every element in the array
|
||||
* represents a color long. For more information on how colors
|
||||
* are packed in a long, please refer to the documentation of
|
||||
* the {@link android.graphics.Color} class.</p>
|
||||
*
|
||||
* <p>Example:</p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* public void setFillColor(@ColorLong long color);
|
||||
* }</pre>
|
||||
*
|
||||
* @see android.graphics.Color
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Retention(SOURCE)
|
||||
@Target({PARAMETER,METHOD,LOCAL_VARIABLE,FIELD})
|
||||
public @interface ColorLong {
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package android.annotation;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
/**
|
||||
* <p>Denotes that the annotated element represents a half-precision floating point
|
||||
* value. Such values are stored in short data types and can be manipulated with
|
||||
* the {@link android.util.Half} class. If applied to an array of short, every
|
||||
* element in the array represents a half-precision float.</p>
|
||||
*
|
||||
* <p>Example:</p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* public abstract void setPosition(@HalfFloat short x, @HalfFloat short y, @HalfFloat short z);
|
||||
* }</pre>
|
||||
*
|
||||
* @see android.util.Half
|
||||
* @see android.util.Half#toHalf(float)
|
||||
* @see android.util.Half#toFloat(short)
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Retention(SOURCE)
|
||||
@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
|
||||
public @interface HalfFloat {
|
||||
}
|
||||
@@ -2,17 +2,17 @@ package android.graphics;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Iterator;
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.Raster;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Iterator;
|
||||
|
||||
|
||||
public final class Bitmap {
|
||||
private final int width;
|
||||
@@ -75,6 +75,19 @@ public final class Bitmap {
|
||||
}
|
||||
}
|
||||
|
||||
private static int configToBufferedImageType(Config config) {
|
||||
switch (config) {
|
||||
case ALPHA_8:
|
||||
return BufferedImage.TYPE_BYTE_GRAY;
|
||||
case RGB_565:
|
||||
return BufferedImage.TYPE_USHORT_565_RGB;
|
||||
case ARGB_8888:
|
||||
return BufferedImage.TYPE_INT_ARGB;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Bitmap.Config(" + config + ") not supported");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common code for checking that x and y are >= 0
|
||||
*
|
||||
@@ -106,7 +119,7 @@ public final class Bitmap {
|
||||
}
|
||||
|
||||
public static Bitmap createBitmap(int width, int height, Config config) {
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
BufferedImage image = new BufferedImage(width, height, configToBufferedImageType(config));
|
||||
return new Bitmap(image);
|
||||
}
|
||||
|
||||
@@ -144,8 +157,10 @@ public final class Bitmap {
|
||||
formatString = "png";
|
||||
} else if (format == Bitmap.CompressFormat.JPEG) {
|
||||
formatString = "jpg";
|
||||
} else if (format == Bitmap.CompressFormat.WEBP || format == Bitmap.CompressFormat.WEBP_LOSSY) {
|
||||
formatString = "webp";
|
||||
} else {
|
||||
throw new IllegalArgumentException("unsupported compression format!");
|
||||
throw new IllegalArgumentException("unsupported compression format! " + format);
|
||||
}
|
||||
|
||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatString);
|
||||
@@ -162,14 +177,19 @@ public final class Bitmap {
|
||||
}
|
||||
writer.setOutput(ios);
|
||||
|
||||
BufferedImage img = image;
|
||||
|
||||
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||
if ("jpg".equals(formatString)) {
|
||||
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||
param.setCompressionQuality(qualityFloat);
|
||||
|
||||
img = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
|
||||
img.getGraphics().drawImage(image, 0, 0, null);
|
||||
}
|
||||
|
||||
try {
|
||||
writer.write(null, new IIOImage(image, null, null), param);
|
||||
writer.write(null, new IIOImage(img, null, null), param);
|
||||
ios.close();
|
||||
writer.dispose();
|
||||
} catch (IOException ex) {
|
||||
@@ -179,6 +199,12 @@ public final class Bitmap {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Bitmap copy(Config config, boolean isMutable) {
|
||||
Bitmap ret = createBitmap(width, height, config);
|
||||
ret.image.getGraphics().drawImage(image, 0, 0, null);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared code to check for illegal arguments passed to getPixels()
|
||||
* or setPixels()
|
||||
@@ -224,12 +250,18 @@ public final class Bitmap {
|
||||
int x, int y, int width, int height) {
|
||||
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
|
||||
|
||||
Raster raster = image.getData();
|
||||
int[] rasterPixels = raster.getPixels(x, y, width, height, (int[]) null);
|
||||
image.getRGB(x, y, width, height, pixels, offset, stride);
|
||||
}
|
||||
|
||||
for (int ht = 0; ht < height; ht++) {
|
||||
int rowOffset = offset + stride * ht;
|
||||
System.arraycopy(rasterPixels, ht * width, pixels, rowOffset, width);
|
||||
}
|
||||
public void eraseColor(int c) {
|
||||
java.awt.Color color = Color.valueOf(c).toJavaColor();
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
graphics.setColor(color);
|
||||
graphics.fillRect(0, 0, width, height);
|
||||
graphics.dispose();
|
||||
}
|
||||
|
||||
public void recycle() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,184 @@
|
||||
package android.graphics;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Shape;
|
||||
import java.awt.font.GlyphVector;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
public final class Canvas {
|
||||
private BufferedImage canvasImage;
|
||||
private Graphics2D canvas;
|
||||
private List<AffineTransform> transformStack = new ArrayList<AffineTransform>();
|
||||
|
||||
private static final String TAG = "Canvas";
|
||||
|
||||
public Canvas(Bitmap bitmap) {
|
||||
canvasImage = bitmap.getImage();
|
||||
canvas = canvasImage.createGraphics();
|
||||
}
|
||||
|
||||
public void drawBitmap(Bitmap sourceBitmap, Rect src, Rect dst, Paint paint) {
|
||||
public void drawBitmap(Bitmap sourceBitmap, Rect src, Rect dst, Paint paint) {
|
||||
BufferedImage sourceImage = sourceBitmap.getImage();
|
||||
BufferedImage sourceImageCropped = sourceImage.getSubimage(src.left, src.top, src.getWidth(), src.getHeight());
|
||||
canvas.drawImage(sourceImageCropped, null, dst.left, dst.top);
|
||||
canvas.drawImage(sourceImageCropped, dst.left, dst.top, dst.getWidth(), dst.getHeight(), null);
|
||||
}
|
||||
|
||||
public void drawBitmap(Bitmap sourceBitmap, float left, float top, Paint paint) {
|
||||
BufferedImage sourceImage = sourceBitmap.getImage();
|
||||
canvas.drawImage(sourceImage, null, (int) left, (int) top);
|
||||
}
|
||||
|
||||
public void drawText(@NonNull char[] text, int index, int count, float x, float y,
|
||||
@NonNull Paint paint) {
|
||||
drawText(new String(text, index, count), x, y, paint);
|
||||
}
|
||||
|
||||
public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
|
||||
applyPaint(paint);
|
||||
GlyphVector glyphVector = paint.getFont().createGlyphVector(canvas.getFontRenderContext(), text);
|
||||
Shape textShape = glyphVector.getOutline();
|
||||
switch (paint.getStyle()) {
|
||||
case Paint.Style.FILL:
|
||||
canvas.drawString(text, x, y);
|
||||
break;
|
||||
case Paint.Style.STROKE:
|
||||
save();
|
||||
translate(x, y);
|
||||
canvas.draw(textShape);
|
||||
restore();
|
||||
break;
|
||||
case Paint.Style.FILL_AND_STROKE:
|
||||
save();
|
||||
translate(x, y);
|
||||
canvas.draw(textShape);
|
||||
canvas.fill(textShape);
|
||||
restore();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void drawText(@NonNull String text, int start, int end, float x, float y,
|
||||
@NonNull Paint paint) {
|
||||
drawText(text.substring(start, end), x, y, paint);
|
||||
}
|
||||
|
||||
public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
|
||||
@NonNull Paint paint) {
|
||||
String str = text.subSequence(start, end).toString();
|
||||
drawText(str, x, y, paint);
|
||||
}
|
||||
|
||||
public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
|
||||
@NonNull Paint paint) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public void drawPath(@NonNull Path path, @NonNull Paint paint) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public void translate(float dx, float dy) {
|
||||
if (dx == 0.0f && dy == 0.0f) return;
|
||||
// TODO: check this, should translations stack?
|
||||
canvas.translate(dx, dy);
|
||||
}
|
||||
|
||||
public void scale(float sx, float sy) {
|
||||
if (sx == 1.0f && sy == 1.0f) return;
|
||||
canvas.scale(sx, sy);
|
||||
}
|
||||
|
||||
public final void scale(float sx, float sy, float px, float py) {
|
||||
if (sx == 1.0f && sy == 1.0f) return;
|
||||
translate(px, py);
|
||||
scale(sx, sy);
|
||||
translate(-px, -py);
|
||||
}
|
||||
|
||||
public void rotate(float degrees) {
|
||||
if (degrees == 0.0f) return;
|
||||
canvas.rotate(degrees);
|
||||
}
|
||||
|
||||
public final void rotate(float degrees, float px, float py) {
|
||||
if (degrees == 0.0f) return;
|
||||
canvas.rotate(degrees, px, py);
|
||||
}
|
||||
|
||||
public int getSaveCount() {
|
||||
return transformStack.size();
|
||||
}
|
||||
|
||||
public int save() {
|
||||
transformStack.add(canvas.getTransform());
|
||||
return getSaveCount();
|
||||
}
|
||||
|
||||
public void restoreToCount(int saveCount) {
|
||||
if (saveCount < 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"Underflow in restoreToCount - more restores than saves");
|
||||
}
|
||||
if (saveCount > getSaveCount()) {
|
||||
throw new IllegalArgumentException("Overflow in restoreToCount");
|
||||
|
||||
}
|
||||
AffineTransform ts = transformStack.get(saveCount - 1);
|
||||
canvas.setTransform(ts);
|
||||
while (transformStack.size() >= saveCount) {
|
||||
transformStack.remove(transformStack.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void restore() {
|
||||
restoreToCount(getSaveCount());
|
||||
}
|
||||
|
||||
public boolean getClipBounds(@NonNull Rect bounds) {
|
||||
Rectangle r = canvas.getClipBounds();
|
||||
if (r == null) {
|
||||
bounds.left = 0;
|
||||
bounds.top = 0;
|
||||
bounds.right = canvasImage.getWidth();
|
||||
bounds.bottom = canvasImage.getHeight();
|
||||
return true;
|
||||
}
|
||||
bounds.left = r.x;
|
||||
bounds.top = r.y;
|
||||
bounds.right = r.x + r.width;
|
||||
bounds.bottom = r.y + r.height;
|
||||
return r.width != 0 && r.height != 0;
|
||||
}
|
||||
|
||||
private void applyPaint(Paint paint) {
|
||||
canvas.setFont(paint.getFont());
|
||||
java.awt.Color color = Color.valueOf(paint.getColorLong()).toJavaColor();
|
||||
canvas.setColor(color);
|
||||
canvas.setStroke(new BasicStroke(paint.getStrokeWidth(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||
if (paint.isAntiAlias()) {
|
||||
canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
} else {
|
||||
canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
|
||||
}
|
||||
if (paint.isDither()) {
|
||||
canvas.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
|
||||
} else {
|
||||
canvas.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||
}
|
||||
// TODO: use more from paint?
|
||||
}
|
||||
}
|
||||
|
||||
578
AndroidCompat/src/main/java/android/graphics/Color.java
Normal file
578
AndroidCompat/src/main/java/android/graphics/Color.java
Normal file
@@ -0,0 +1,578 @@
|
||||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.graphics;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.annotation.ColorLong;
|
||||
import android.annotation.HalfFloat;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.Size;
|
||||
import android.util.Half;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
public class Color {
|
||||
@ColorInt public static final int BLACK = 0xFF000000;
|
||||
@ColorInt public static final int DKGRAY = 0xFF444444;
|
||||
@ColorInt public static final int GRAY = 0xFF888888;
|
||||
@ColorInt public static final int LTGRAY = 0xFFCCCCCC;
|
||||
@ColorInt public static final int WHITE = 0xFFFFFFFF;
|
||||
@ColorInt public static final int RED = 0xFFFF0000;
|
||||
@ColorInt public static final int GREEN = 0xFF00FF00;
|
||||
@ColorInt public static final int BLUE = 0xFF0000FF;
|
||||
@ColorInt public static final int YELLOW = 0xFFFFFF00;
|
||||
@ColorInt public static final int CYAN = 0xFF00FFFF;
|
||||
@ColorInt public static final int MAGENTA = 0xFFFF00FF;
|
||||
@ColorInt public static final int TRANSPARENT = 0;
|
||||
|
||||
@NonNull
|
||||
@Size(min = 4, max = 5)
|
||||
private final float[] mComponents;
|
||||
|
||||
@NonNull
|
||||
private final ColorSpace mColorSpace;
|
||||
|
||||
public Color() {
|
||||
// This constructor is required for compatibility with previous APIs
|
||||
mComponents = new float[] { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
|
||||
}
|
||||
|
||||
private Color(float r, float g, float b, float a) {
|
||||
this(r, g, b, a, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||
}
|
||||
|
||||
private Color(float r, float g, float b, float a, @NonNull ColorSpace colorSpace) {
|
||||
mComponents = new float[] { r, g, b, a };
|
||||
mColorSpace = colorSpace;
|
||||
}
|
||||
|
||||
private Color(@Size(min = 4, max = 5) float[] components, @NonNull ColorSpace colorSpace) {
|
||||
mComponents = components;
|
||||
mColorSpace = colorSpace;
|
||||
}
|
||||
|
||||
public java.awt.Color toJavaColor() {
|
||||
return new java.awt.Color(red(), green(), blue(), alpha());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ColorSpace getColorSpace() {
|
||||
return mColorSpace;
|
||||
}
|
||||
|
||||
public ColorSpace.Model getModel() {
|
||||
return mColorSpace.getModel();
|
||||
}
|
||||
|
||||
public boolean isWideGamut() {
|
||||
return getColorSpace().isWideGamut();
|
||||
}
|
||||
|
||||
public boolean isSrgb() {
|
||||
return getColorSpace().isSrgb();
|
||||
}
|
||||
|
||||
@IntRange(from = 4, to = 5)
|
||||
public int getComponentCount() {
|
||||
return mColorSpace.getComponentCount() + 1;
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public long pack() {
|
||||
return pack(mComponents[0], mComponents[1], mComponents[2], mComponents[3], mColorSpace);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Color convert(@NonNull ColorSpace colorSpace) {
|
||||
ColorSpace.Connector connector = ColorSpace.connect(mColorSpace, colorSpace);
|
||||
float[] color = new float[] {
|
||||
mComponents[0], mComponents[1], mComponents[2], mComponents[3]
|
||||
};
|
||||
connector.transform(color);
|
||||
return new Color(color, colorSpace);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public int toArgb() {
|
||||
if (mColorSpace.isSrgb()) {
|
||||
return ((int) (mComponents[3] * 255.0f + 0.5f) << 24) |
|
||||
((int) (mComponents[0] * 255.0f + 0.5f) << 16) |
|
||||
((int) (mComponents[1] * 255.0f + 0.5f) << 8) |
|
||||
(int) (mComponents[2] * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
float[] color = new float[] {
|
||||
mComponents[0], mComponents[1], mComponents[2], mComponents[3]
|
||||
};
|
||||
// The transformation saturates the output
|
||||
ColorSpace.connect(mColorSpace).transform(color);
|
||||
|
||||
return ((int) (color[3] * 255.0f + 0.5f) << 24) |
|
||||
((int) (color[0] * 255.0f + 0.5f) << 16) |
|
||||
((int) (color[1] * 255.0f + 0.5f) << 8) |
|
||||
(int) (color[2] * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
public float red() {
|
||||
return mComponents[0];
|
||||
}
|
||||
|
||||
public float green() {
|
||||
return mComponents[1];
|
||||
}
|
||||
|
||||
public float blue() {
|
||||
return mComponents[2];
|
||||
}
|
||||
|
||||
public float alpha() {
|
||||
return mComponents[mComponents.length - 1];
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Size(min = 4, max = 5)
|
||||
public float[] getComponents() {
|
||||
return Arrays.copyOf(mComponents, mComponents.length);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Size(min = 4)
|
||||
public float[] getComponents(@Nullable @Size(min = 4) float[] components) {
|
||||
if (components == null) {
|
||||
return Arrays.copyOf(mComponents, mComponents.length);
|
||||
}
|
||||
|
||||
if (components.length < mComponents.length) {
|
||||
throw new IllegalArgumentException("The specified array's length must be at "
|
||||
+ "least " + mComponents.length);
|
||||
}
|
||||
|
||||
System.arraycopy(mComponents, 0, components, 0, mComponents.length);
|
||||
return components;
|
||||
}
|
||||
|
||||
public float getComponent(@IntRange(from = 0, to = 4) int component) {
|
||||
return mComponents[component];
|
||||
}
|
||||
|
||||
public float luminance() {
|
||||
if (mColorSpace.getModel() != ColorSpace.Model.RGB) {
|
||||
throw new IllegalArgumentException("The specified color must be encoded in an RGB " +
|
||||
"color space. The supplied color space is " + mColorSpace.getModel());
|
||||
}
|
||||
|
||||
DoubleUnaryOperator eotf = ((ColorSpace.Rgb) mColorSpace).getEotf();
|
||||
double r = eotf.applyAsDouble(mComponents[0]);
|
||||
double g = eotf.applyAsDouble(mComponents[1]);
|
||||
double b = eotf.applyAsDouble(mComponents[2]);
|
||||
|
||||
return saturate((float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Color color = (Color) o;
|
||||
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (!Arrays.equals(mComponents, color.mComponents)) return false;
|
||||
return mColorSpace.equals(color.mColorSpace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Arrays.hashCode(mComponents);
|
||||
result = 31 * result + mColorSpace.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String toString() {
|
||||
StringBuilder b = new StringBuilder("Color(");
|
||||
for (float c : mComponents) {
|
||||
b.append(c).append(", ");
|
||||
}
|
||||
b.append(mColorSpace.getName());
|
||||
b.append(')');
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ColorSpace colorSpace(@ColorLong long color) {
|
||||
return ColorSpace.get((int) (color & 0x3fL));
|
||||
}
|
||||
|
||||
public static float red(@ColorLong long color) {
|
||||
if ((color & 0x3fL) == 0L) return ((color >> 48) & 0xff) / 255.0f;
|
||||
return Half.toFloat((short) ((color >> 48) & 0xffff));
|
||||
}
|
||||
|
||||
public static float green(@ColorLong long color) {
|
||||
if ((color & 0x3fL) == 0L) return ((color >> 40) & 0xff) / 255.0f;
|
||||
return Half.toFloat((short) ((color >> 32) & 0xffff));
|
||||
}
|
||||
|
||||
public static float blue(@ColorLong long color) {
|
||||
if ((color & 0x3fL) == 0L) return ((color >> 32) & 0xff) / 255.0f;
|
||||
return Half.toFloat((short) ((color >> 16) & 0xffff));
|
||||
}
|
||||
|
||||
public static float alpha(@ColorLong long color) {
|
||||
if ((color & 0x3fL) == 0L) return ((color >> 56) & 0xff) / 255.0f;
|
||||
return ((color >> 6) & 0x3ff) / 1023.0f;
|
||||
}
|
||||
|
||||
public static boolean isSrgb(@ColorLong long color) {
|
||||
return colorSpace(color).isSrgb();
|
||||
}
|
||||
|
||||
public static boolean isWideGamut(@ColorLong long color) {
|
||||
return colorSpace(color).isWideGamut();
|
||||
}
|
||||
|
||||
public static boolean isInColorSpace(@ColorLong long color, @NonNull ColorSpace colorSpace) {
|
||||
return (int) (color & 0x3fL) == colorSpace.getId();
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int toArgb(@ColorLong long color) {
|
||||
if ((color & 0x3fL) == 0L) return (int) (color >> 32);
|
||||
|
||||
float r = red(color);
|
||||
float g = green(color);
|
||||
float b = blue(color);
|
||||
float a = alpha(color);
|
||||
|
||||
// The transformation saturates the output
|
||||
float[] c = ColorSpace.connect(colorSpace(color)).transform(r, g, b);
|
||||
|
||||
return ((int) (a * 255.0f + 0.5f) << 24) |
|
||||
((int) (c[0] * 255.0f + 0.5f) << 16) |
|
||||
((int) (c[1] * 255.0f + 0.5f) << 8) |
|
||||
(int) (c[2] * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(@ColorInt int color) {
|
||||
float r = ((color >> 16) & 0xff) / 255.0f;
|
||||
float g = ((color >> 8) & 0xff) / 255.0f;
|
||||
float b = ((color ) & 0xff) / 255.0f;
|
||||
float a = ((color >> 24) & 0xff) / 255.0f;
|
||||
return new Color(r, g, b, a, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(@ColorLong long color) {
|
||||
return new Color(red(color), green(color), blue(color), alpha(color), colorSpace(color));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(float r, float g, float b) {
|
||||
return new Color(r, g, b, 1.0f);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(float r, float g, float b, float a) {
|
||||
return new Color(saturate(r), saturate(g), saturate(b), saturate(a));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(float r, float g, float b, float a, @NonNull ColorSpace colorSpace) {
|
||||
if (colorSpace.getComponentCount() > 3) {
|
||||
throw new IllegalArgumentException("The specified color space must use a color model " +
|
||||
"with at most 3 color components");
|
||||
}
|
||||
return new Color(r, g, b, a, colorSpace);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(@NonNull @Size(min = 4, max = 5) float[] components,
|
||||
@NonNull ColorSpace colorSpace) {
|
||||
if (components.length < colorSpace.getComponentCount() + 1) {
|
||||
throw new IllegalArgumentException("Received a component array of length " +
|
||||
components.length + " but the color model requires " +
|
||||
(colorSpace.getComponentCount() + 1) + " (including alpha)");
|
||||
}
|
||||
return new Color(Arrays.copyOf(components, colorSpace.getComponentCount() + 1), colorSpace);
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long pack(@ColorInt int color) {
|
||||
return (color & 0xffffffffL) << 32;
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long pack(float red, float green, float blue) {
|
||||
return pack(red, green, blue, 1.0f, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long pack(float red, float green, float blue, float alpha) {
|
||||
return pack(red, green, blue, alpha, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long pack(float red, float green, float blue, float alpha,
|
||||
@NonNull ColorSpace colorSpace) {
|
||||
if (colorSpace.isSrgb()) {
|
||||
int argb =
|
||||
((int) (alpha * 255.0f + 0.5f) << 24) |
|
||||
((int) (red * 255.0f + 0.5f) << 16) |
|
||||
((int) (green * 255.0f + 0.5f) << 8) |
|
||||
(int) (blue * 255.0f + 0.5f);
|
||||
return (argb & 0xffffffffL) << 32;
|
||||
}
|
||||
|
||||
int id = colorSpace.getId();
|
||||
if (id == ColorSpace.MIN_ID) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unknown color space, please use a color space returned by ColorSpace.get()");
|
||||
}
|
||||
if (colorSpace.getComponentCount() > 3) {
|
||||
throw new IllegalArgumentException(
|
||||
"The color space must use a color model with at most 3 components");
|
||||
}
|
||||
|
||||
@HalfFloat short r = Half.toHalf(red);
|
||||
@HalfFloat short g = Half.toHalf(green);
|
||||
@HalfFloat short b = Half.toHalf(blue);
|
||||
|
||||
int a = (int) (Math.max(0.0f, Math.min(alpha, 1.0f)) * 1023.0f + 0.5f);
|
||||
|
||||
// Suppress sign extension
|
||||
return (r & 0xffffL) << 48 |
|
||||
(g & 0xffffL) << 32 |
|
||||
(b & 0xffffL) << 16 |
|
||||
(a & 0x3ffL ) << 6 |
|
||||
id & 0x3fL;
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long convert(@ColorInt int color, @NonNull ColorSpace colorSpace) {
|
||||
float r = ((color >> 16) & 0xff) / 255.0f;
|
||||
float g = ((color >> 8) & 0xff) / 255.0f;
|
||||
float b = ((color ) & 0xff) / 255.0f;
|
||||
float a = ((color >> 24) & 0xff) / 255.0f;
|
||||
ColorSpace source = ColorSpace.get(ColorSpace.Named.SRGB);
|
||||
return convert(r, g, b, a, source, colorSpace);
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long convert(@ColorLong long color, @NonNull ColorSpace colorSpace) {
|
||||
float r = red(color);
|
||||
float g = green(color);
|
||||
float b = blue(color);
|
||||
float a = alpha(color);
|
||||
ColorSpace source = colorSpace(color);
|
||||
return convert(r, g, b, a, source, colorSpace);
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long convert(float r, float g, float b, float a,
|
||||
@NonNull ColorSpace source, @NonNull ColorSpace destination) {
|
||||
float[] c = ColorSpace.connect(source, destination).transform(r, g, b);
|
||||
return pack(c[0], c[1], c[2], a, destination);
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long convert(@ColorLong long color, @NonNull ColorSpace.Connector connector) {
|
||||
float r = red(color);
|
||||
float g = green(color);
|
||||
float b = blue(color);
|
||||
float a = alpha(color);
|
||||
return convert(r, g, b, a, connector);
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long convert(float r, float g, float b, float a,
|
||||
@NonNull ColorSpace.Connector connector) {
|
||||
float[] c = connector.transform(r, g, b);
|
||||
return pack(c[0], c[1], c[2], a, connector.getDestination());
|
||||
}
|
||||
|
||||
public static float luminance(@ColorLong long color) {
|
||||
ColorSpace colorSpace = colorSpace(color);
|
||||
if (colorSpace.getModel() != ColorSpace.Model.RGB) {
|
||||
throw new IllegalArgumentException("The specified color must be encoded in an RGB " +
|
||||
"color space. The supplied color space is " + colorSpace.getModel());
|
||||
}
|
||||
|
||||
DoubleUnaryOperator eotf = ((ColorSpace.Rgb) colorSpace).getEotf();
|
||||
double r = eotf.applyAsDouble(red(color));
|
||||
double g = eotf.applyAsDouble(green(color));
|
||||
double b = eotf.applyAsDouble(blue(color));
|
||||
|
||||
return saturate((float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b)));
|
||||
}
|
||||
|
||||
private static float saturate(float v) {
|
||||
return v <= 0.0f ? 0.0f : (v >= 1.0f ? 1.0f : v);
|
||||
}
|
||||
|
||||
@IntRange(from = 0, to = 255)
|
||||
public static int alpha(int color) {
|
||||
return color >>> 24;
|
||||
}
|
||||
|
||||
@IntRange(from = 0, to = 255)
|
||||
public static int red(int color) {
|
||||
return (color >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
@IntRange(from = 0, to = 255)
|
||||
public static int green(int color) {
|
||||
return (color >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
@IntRange(from = 0, to = 255)
|
||||
public static int blue(int color) {
|
||||
return color & 0xFF;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int rgb(
|
||||
@IntRange(from = 0, to = 255) int red,
|
||||
@IntRange(from = 0, to = 255) int green,
|
||||
@IntRange(from = 0, to = 255) int blue) {
|
||||
return 0xff000000 | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int rgb(float red, float green, float blue) {
|
||||
return 0xff000000 |
|
||||
((int) (red * 255.0f + 0.5f) << 16) |
|
||||
((int) (green * 255.0f + 0.5f) << 8) |
|
||||
(int) (blue * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int argb(
|
||||
@IntRange(from = 0, to = 255) int alpha,
|
||||
@IntRange(from = 0, to = 255) int red,
|
||||
@IntRange(from = 0, to = 255) int green,
|
||||
@IntRange(from = 0, to = 255) int blue) {
|
||||
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int argb(float alpha, float red, float green, float blue) {
|
||||
return ((int) (alpha * 255.0f + 0.5f) << 24) |
|
||||
((int) (red * 255.0f + 0.5f) << 16) |
|
||||
((int) (green * 255.0f + 0.5f) << 8) |
|
||||
(int) (blue * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
public static float luminance(@ColorInt int color) {
|
||||
ColorSpace.Rgb cs = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
|
||||
DoubleUnaryOperator eotf = cs.getEotf();
|
||||
|
||||
double r = eotf.applyAsDouble(red(color) / 255.0);
|
||||
double g = eotf.applyAsDouble(green(color) / 255.0);
|
||||
double b = eotf.applyAsDouble(blue(color) / 255.0);
|
||||
|
||||
return (float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b));
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int parseColor(@Size(min=1) String colorString) {
|
||||
if (colorString.charAt(0) == '#') {
|
||||
// Use a long to avoid rollovers on #ffXXXXXX
|
||||
long color = Long.parseLong(colorString.substring(1), 16);
|
||||
if (colorString.length() == 7) {
|
||||
// Set the alpha value
|
||||
color |= 0x00000000ff000000;
|
||||
} else if (colorString.length() != 9) {
|
||||
throw new IllegalArgumentException("Unknown color");
|
||||
}
|
||||
return (int)color;
|
||||
} else {
|
||||
Integer color = sColorNameMap.get(colorString.toLowerCase(Locale.ROOT));
|
||||
if (color != null) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown color");
|
||||
}
|
||||
|
||||
public static void RGBToHSV(
|
||||
@IntRange(from = 0, to = 255) int red,
|
||||
@IntRange(from = 0, to = 255) int green,
|
||||
@IntRange(from = 0, to = 255) int blue, @Size(3) float hsv[]) {
|
||||
if (hsv.length < 3) {
|
||||
throw new RuntimeException("3 components required for hsv");
|
||||
}
|
||||
nativeRGBToHSV(red, green, blue, hsv);
|
||||
}
|
||||
|
||||
public static void colorToHSV(@ColorInt int color, @Size(3) float hsv[]) {
|
||||
RGBToHSV((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF, hsv);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int HSVToColor(@Size(3) float hsv[]) {
|
||||
return HSVToColor(0xFF, hsv);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int HSVToColor(@IntRange(from = 0, to = 255) int alpha, @Size(3) float hsv[]) {
|
||||
if (hsv.length < 3) {
|
||||
throw new RuntimeException("3 components required for hsv");
|
||||
}
|
||||
return nativeHSVToColor(alpha, hsv);
|
||||
}
|
||||
|
||||
private static native void nativeRGBToHSV(int red, int greed, int blue, float hsv[]);
|
||||
private static native int nativeHSVToColor(int alpha, float hsv[]);
|
||||
|
||||
private static final HashMap<String, Integer> sColorNameMap;
|
||||
static {
|
||||
sColorNameMap = new HashMap<>();
|
||||
sColorNameMap.put("black", BLACK);
|
||||
sColorNameMap.put("darkgray", DKGRAY);
|
||||
sColorNameMap.put("gray", GRAY);
|
||||
sColorNameMap.put("lightgray", LTGRAY);
|
||||
sColorNameMap.put("white", WHITE);
|
||||
sColorNameMap.put("red", RED);
|
||||
sColorNameMap.put("green", GREEN);
|
||||
sColorNameMap.put("blue", BLUE);
|
||||
sColorNameMap.put("yellow", YELLOW);
|
||||
sColorNameMap.put("cyan", CYAN);
|
||||
sColorNameMap.put("magenta", MAGENTA);
|
||||
sColorNameMap.put("aqua", 0xFF00FFFF);
|
||||
sColorNameMap.put("fuchsia", 0xFFFF00FF);
|
||||
sColorNameMap.put("darkgrey", DKGRAY);
|
||||
sColorNameMap.put("grey", GRAY);
|
||||
sColorNameMap.put("lightgrey", LTGRAY);
|
||||
sColorNameMap.put("lime", 0xFF00FF00);
|
||||
sColorNameMap.put("maroon", 0xFF800000);
|
||||
sColorNameMap.put("navy", 0xFF000080);
|
||||
sColorNameMap.put("olive", 0xFF808000);
|
||||
sColorNameMap.put("purple", 0xFF800080);
|
||||
sColorNameMap.put("silver", 0xFFC0C0C0);
|
||||
sColorNameMap.put("teal", 0xFF008080);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
1847
AndroidCompat/src/main/java/android/graphics/ColorSpace.java
Normal file
1847
AndroidCompat/src/main/java/android/graphics/ColorSpace.java
Normal file
File diff suppressed because it is too large
Load Diff
1549
AndroidCompat/src/main/java/android/graphics/Paint.java
Normal file
1549
AndroidCompat/src/main/java/android/graphics/Paint.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
package android.graphics;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import android.os.Parcel;
|
||||
|
||||
public final class Rect {
|
||||
int left;
|
||||
int top;
|
||||
int right;
|
||||
int bottom;
|
||||
public int left;
|
||||
public int top;
|
||||
public int right;
|
||||
public int bottom;
|
||||
|
||||
private static final class UnflattenHelper {
|
||||
private static final Pattern FLATTENED_PATTERN = Pattern.compile(
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.graphics;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public class TemporaryBuffer {
|
||||
public static char[] obtain(int len) {
|
||||
char[] buf;
|
||||
|
||||
synchronized (TemporaryBuffer.class) {
|
||||
buf = sTemp;
|
||||
sTemp = null;
|
||||
}
|
||||
|
||||
if (buf == null || buf.length < len) {
|
||||
buf = ArrayUtils.newUnpaddedCharArray(len);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
public static void recycle(char[] temp) {
|
||||
if (temp.length > 1000) return;
|
||||
|
||||
synchronized (TemporaryBuffer.class) {
|
||||
sTemp = temp;
|
||||
}
|
||||
}
|
||||
|
||||
private static char[] sTemp = null;
|
||||
}
|
||||
|
||||
1905
AndroidCompat/src/main/java/android/text/Layout.java
Normal file
1905
AndroidCompat/src/main/java/android/text/Layout.java
Normal file
File diff suppressed because it is too large
Load Diff
631
AndroidCompat/src/main/java/android/text/StaticLayout.java
Normal file
631
AndroidCompat/src/main/java/android/text/StaticLayout.java
Normal file
@@ -0,0 +1,631 @@
|
||||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.text;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.font.LineBreakMeasurer;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.awt.font.TextLayout;
|
||||
import java.text.AttributedString;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.GrowingArrayUtils;
|
||||
|
||||
|
||||
public class StaticLayout extends Layout {
|
||||
/*
|
||||
* The break iteration is done in native code. The protocol for using the native code is as
|
||||
* follows.
|
||||
*
|
||||
* First, call nInit to setup native line breaker object. Then, for each paragraph, do the
|
||||
* following:
|
||||
*
|
||||
* - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
|
||||
* native.
|
||||
* - Run LineBreaker.computeLineBreaks() to obtain line breaks for the paragraph.
|
||||
*
|
||||
* After all paragraphs, call finish() to release expensive buffers.
|
||||
*/
|
||||
|
||||
static final String TAG = "StaticLayout";
|
||||
|
||||
public final static class Builder {
|
||||
private Builder() {}
|
||||
|
||||
@NonNull
|
||||
public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start,
|
||||
@IntRange(from = 0) int end, @NonNull TextPaint paint,
|
||||
@IntRange(from = 0) int width) {
|
||||
Builder b = new Builder();
|
||||
|
||||
// set default initial values
|
||||
b.mText = source;
|
||||
b.mStart = start;
|
||||
b.mEnd = end;
|
||||
b.mPaint = paint;
|
||||
b.mWidth = width;
|
||||
b.mAlignment = Alignment.ALIGN_NORMAL;
|
||||
b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
|
||||
b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
|
||||
b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
|
||||
b.mIncludePad = true;
|
||||
b.mFallbackLineSpacing = false;
|
||||
b.mEllipsizedWidth = width;
|
||||
b.mEllipsize = null;
|
||||
b.mMaxLines = Integer.MAX_VALUE;
|
||||
b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
|
||||
b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
|
||||
b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
|
||||
b.mMinimumFontMetrics = null;
|
||||
return b;
|
||||
}
|
||||
|
||||
// release any expensive state
|
||||
/* package */ void finish() {
|
||||
mText = null;
|
||||
mPaint = null;
|
||||
mLeftIndents = null;
|
||||
mRightIndents = null;
|
||||
mMinimumFontMetrics = null;
|
||||
}
|
||||
|
||||
public Builder setText(CharSequence source) {
|
||||
return setText(source, 0, source.length());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setText(@NonNull CharSequence source, int start, int end) {
|
||||
mText = source;
|
||||
mStart = start;
|
||||
mEnd = end;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setPaint(@NonNull TextPaint paint) {
|
||||
mPaint = paint;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setWidth(@IntRange(from = 0) int width) {
|
||||
mWidth = width;
|
||||
if (mEllipsize == null) {
|
||||
mEllipsizedWidth = width;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setAlignment(@NonNull Alignment alignment) {
|
||||
mAlignment = alignment;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
|
||||
mTextDir = textDir;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setLineSpacing(float spacingAdd, float spacingMult) {
|
||||
mSpacingAdd = spacingAdd;
|
||||
mSpacingMult = spacingMult;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setIncludePad(boolean includePad) {
|
||||
mIncludePad = includePad;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
|
||||
mFallbackLineSpacing = useLineSpacingFromFallbacks;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
|
||||
mEllipsizedWidth = ellipsizedWidth;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
|
||||
mEllipsize = ellipsize;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
|
||||
mMaxLines = maxLines;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
|
||||
mBreakStrategy = breakStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
|
||||
mHyphenationFrequency = hyphenationFrequency;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
|
||||
mLeftIndents = leftIndents;
|
||||
mRightIndents = rightIndents;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setJustificationMode(@JustificationMode int justificationMode) {
|
||||
mJustificationMode = justificationMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
/* package */ Builder setAddLastLineLineSpacing(boolean value) {
|
||||
mAddLastLineLineSpacing = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressLint("MissingGetterMatchingBuilder") // The base class `Layout` has a getter.
|
||||
@NonNull
|
||||
public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
|
||||
mUseBoundsForWidth = useBoundsForWidth;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
// The corresponding getter is getShiftDrawingOffsetForStartOverhang()
|
||||
@SuppressLint("MissingGetterMatchingBuilder")
|
||||
public Builder setShiftDrawingOffsetForStartOverhang(
|
||||
boolean shiftDrawingOffsetForStartOverhang) {
|
||||
mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCalculateBounds(boolean value) {
|
||||
mCalculateBounds = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
|
||||
mMinimumFontMetrics = minimumFontMetrics;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public StaticLayout build() {
|
||||
StaticLayout result = new StaticLayout(this, mIncludePad, mEllipsize != null
|
||||
? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
|
||||
return result;
|
||||
}
|
||||
|
||||
private CharSequence mText;
|
||||
private int mStart;
|
||||
private int mEnd;
|
||||
private TextPaint mPaint;
|
||||
private int mWidth;
|
||||
private Alignment mAlignment;
|
||||
private TextDirectionHeuristic mTextDir;
|
||||
private float mSpacingMult;
|
||||
private float mSpacingAdd;
|
||||
private boolean mIncludePad;
|
||||
private boolean mFallbackLineSpacing;
|
||||
private int mEllipsizedWidth;
|
||||
private TextUtils.TruncateAt mEllipsize;
|
||||
private int mMaxLines;
|
||||
private int mBreakStrategy;
|
||||
private int mHyphenationFrequency;
|
||||
@Nullable private int[] mLeftIndents;
|
||||
@Nullable private int[] mRightIndents;
|
||||
private int mJustificationMode;
|
||||
private boolean mAddLastLineLineSpacing;
|
||||
private boolean mUseBoundsForWidth;
|
||||
private boolean mShiftDrawingOffsetForStartOverhang;
|
||||
private boolean mCalculateBounds;
|
||||
@Nullable private Paint.FontMetrics mMinimumFontMetrics;
|
||||
|
||||
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
|
||||
}
|
||||
|
||||
private StaticLayout() {
|
||||
super(
|
||||
null, // text
|
||||
null, // paint
|
||||
0, // width
|
||||
null, // alignment
|
||||
null, // textDir
|
||||
1, // spacing multiplier
|
||||
0, // spacing amount
|
||||
false, // include font padding
|
||||
false, // fallback line spacing
|
||||
0, // ellipsized width
|
||||
null, // ellipsize
|
||||
1, // maxLines
|
||||
BREAK_STRATEGY_SIMPLE,
|
||||
HYPHENATION_FREQUENCY_NONE,
|
||||
null, // leftIndents
|
||||
null, // rightIndents
|
||||
JUSTIFICATION_MODE_NONE,
|
||||
false, // useBoundsForWidth
|
||||
false, // shiftDrawingOffsetForStartOverhang
|
||||
null // minimumFontMetrics
|
||||
);
|
||||
|
||||
mColumns = COLUMNS_ELLIPSIZE;
|
||||
mLineDirections = new Directions[2];
|
||||
mLines = new int[2 * mColumns];
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public StaticLayout(CharSequence source, TextPaint paint,
|
||||
int width,
|
||||
Alignment align, float spacingmult, float spacingadd,
|
||||
boolean includepad) {
|
||||
this(source, 0, source.length(), paint, width, align,
|
||||
spacingmult, spacingadd, includepad);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public StaticLayout(CharSequence source, int bufstart, int bufend,
|
||||
TextPaint paint, int outerwidth,
|
||||
Alignment align,
|
||||
float spacingmult, float spacingadd,
|
||||
boolean includepad) {
|
||||
this(source, bufstart, bufend, paint, outerwidth, align,
|
||||
spacingmult, spacingadd, includepad, null, 0);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public StaticLayout(CharSequence source, int bufstart, int bufend,
|
||||
TextPaint paint, int outerwidth,
|
||||
Alignment align,
|
||||
float spacingmult, float spacingadd,
|
||||
boolean includepad,
|
||||
TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
|
||||
this(source, bufstart, bufend, paint, outerwidth, align,
|
||||
TextDirectionHeuristics.FIRSTSTRONG_LTR,
|
||||
spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public StaticLayout(CharSequence source, int bufstart, int bufend,
|
||||
TextPaint paint, int outerwidth,
|
||||
Alignment align, TextDirectionHeuristic textDir,
|
||||
float spacingmult, float spacingadd,
|
||||
boolean includepad,
|
||||
TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
|
||||
this(Builder.obtain(source, bufstart, bufend, paint, outerwidth)
|
||||
.setAlignment(align)
|
||||
.setTextDirection(textDir)
|
||||
.setLineSpacing(spacingadd, spacingmult)
|
||||
.setIncludePad(includepad)
|
||||
.setEllipsize(ellipsize)
|
||||
.setEllipsizedWidth(ellipsizedWidth)
|
||||
.setMaxLines(maxLines), includepad,
|
||||
ellipsize != null ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
|
||||
}
|
||||
|
||||
private StaticLayout(Builder b, boolean trackPadding, int columnSize) {
|
||||
super((b.mEllipsize == null) ? b.mText : (b.mText instanceof Spanned)
|
||||
? new SpannedEllipsizer(b.mText) : new Ellipsizer(b.mText),
|
||||
b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd,
|
||||
b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize,
|
||||
b.mMaxLines, b.mBreakStrategy, b.mHyphenationFrequency, b.mLeftIndents,
|
||||
b.mRightIndents, b.mJustificationMode, b.mUseBoundsForWidth,
|
||||
b.mShiftDrawingOffsetForStartOverhang, b.mMinimumFontMetrics);
|
||||
|
||||
mColumns = columnSize;
|
||||
if (b.mEllipsize != null) {
|
||||
Ellipsizer e = (Ellipsizer) getText();
|
||||
|
||||
e.mLayout = this;
|
||||
e.mWidth = b.mEllipsizedWidth;
|
||||
e.mMethod = b.mEllipsize;
|
||||
throw new UnsupportedOperationException("Ellipsis not supported");
|
||||
}
|
||||
|
||||
mLineDirections = new Directions[2];
|
||||
mLines = new int[2 * mColumns];
|
||||
mMaximumVisibleLineCount = b.mMaxLines;
|
||||
|
||||
mLeftIndents = b.mLeftIndents;
|
||||
mRightIndents = b.mRightIndents;
|
||||
|
||||
String str = b.mText.subSequence(b.mStart, b.mEnd).toString();
|
||||
AttributedString text = new AttributedString(str);
|
||||
text.addAttribute(TextAttribute.FONT, getPaint().getFont());
|
||||
FontRenderContext frc = new FontRenderContext(getPaint().getFont().getTransform(), RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
|
||||
LineBreakMeasurer measurer = new LineBreakMeasurer(text.getIterator(), frc);
|
||||
// TODO: directions
|
||||
|
||||
float y = 0;
|
||||
while (measurer.getPosition() < str.length()) {
|
||||
int off = mLineCount * mColumns;
|
||||
int want = off + mColumns + TOP;
|
||||
if (want >= mLines.length) {
|
||||
final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
|
||||
System.arraycopy(mLines, 0, grow, 0, mLines.length);
|
||||
mLines = grow;
|
||||
}
|
||||
|
||||
int pos = measurer.getPosition();
|
||||
TextLayout l = measurer.nextLayout(getWidth());
|
||||
mLines[off + START] = pos;
|
||||
mLines[off + TOP] = (int) y;
|
||||
mLines[off + DESCENT] = (int) (l.getDescent() + l.getLeading());
|
||||
mLines[off + EXTRA] = (int) l.getLeading();
|
||||
mLines[off + DIR] |= Layout.DIR_LEFT_TO_RIGHT << DIR_SHIFT;
|
||||
|
||||
y += l.getAscent();
|
||||
y += l.getDescent() + l.getLeading();
|
||||
|
||||
mLines[off + mColumns + START] = measurer.getPosition();
|
||||
mLines[off + mColumns + TOP] = (int) y;
|
||||
|
||||
mLineCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Override the base class so we can directly access our members,
|
||||
// rather than relying on member functions.
|
||||
// The logic mirrors that of Layout.getLineForVertical
|
||||
// FIXME: It may be faster to do a linear search for layouts without many lines.
|
||||
@Override
|
||||
public int getLineForVertical(int vertical) {
|
||||
int high = mLineCount;
|
||||
int low = -1;
|
||||
int guess;
|
||||
int[] lines = mLines;
|
||||
while (high - low > 1) {
|
||||
guess = (high + low) >> 1;
|
||||
if (lines[mColumns * guess + TOP] > vertical){
|
||||
high = guess;
|
||||
} else {
|
||||
low = guess;
|
||||
}
|
||||
}
|
||||
if (low < 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return low;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineCount() {
|
||||
return mLineCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineTop(int line) {
|
||||
return mLines[mColumns * line + TOP];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineExtra(int line) {
|
||||
return mLines[mColumns * line + EXTRA];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineDescent(int line) {
|
||||
return mLines[mColumns * line + DESCENT];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineStart(int line) {
|
||||
return mLines[mColumns * line + START] & START_MASK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getParagraphDirection(int line) {
|
||||
return mLines[mColumns * line + DIR] >> DIR_SHIFT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getLineContainsTab(int line) {
|
||||
return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Directions getLineDirections(int line) {
|
||||
if (line > getLineCount()) {
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
return new Directions(null);
|
||||
// return mLineDirections[line];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTopPadding() {
|
||||
return mTopPadding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBottomPadding() {
|
||||
return mBottomPadding;
|
||||
}
|
||||
|
||||
// To store into single int field, pack the pair of start and end hyphen edit.
|
||||
static int packHyphenEdit(
|
||||
@Paint.StartHyphenEdit int start, @Paint.EndHyphenEdit int end) {
|
||||
return start << START_HYPHEN_BITS_SHIFT | end;
|
||||
}
|
||||
|
||||
static int unpackStartHyphenEdit(int packedHyphenEdit) {
|
||||
return (packedHyphenEdit & START_HYPHEN_MASK) >> START_HYPHEN_BITS_SHIFT;
|
||||
}
|
||||
|
||||
static int unpackEndHyphenEdit(int packedHyphenEdit) {
|
||||
return packedHyphenEdit & END_HYPHEN_MASK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Paint.StartHyphenEdit int getStartHyphenEdit(int lineNumber) {
|
||||
return unpackStartHyphenEdit(mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Paint.EndHyphenEdit int getEndHyphenEdit(int lineNumber) {
|
||||
return unpackEndHyphenEdit(mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndentAdjust(int line, Alignment align) {
|
||||
if (align == Alignment.ALIGN_LEFT) {
|
||||
if (mLeftIndents == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
|
||||
}
|
||||
} else if (align == Alignment.ALIGN_RIGHT) {
|
||||
if (mRightIndents == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
|
||||
}
|
||||
} else if (align == Alignment.ALIGN_CENTER) {
|
||||
int left = 0;
|
||||
if (mLeftIndents != null) {
|
||||
left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
|
||||
}
|
||||
int right = 0;
|
||||
if (mRightIndents != null) {
|
||||
right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
|
||||
}
|
||||
return (left - right) >> 1;
|
||||
} else {
|
||||
throw new AssertionError("unhandled alignment " + align);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEllipsisCount(int line) {
|
||||
if (mColumns < COLUMNS_ELLIPSIZE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return mLines[mColumns * line + ELLIPSIS_COUNT];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEllipsisStart(int line) {
|
||||
if (mColumns < COLUMNS_ELLIPSIZE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return mLines[mColumns * line + ELLIPSIS_START];
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public RectF computeDrawingBoundingBox() {
|
||||
// Cache the drawing bounds result because it does not change after created.
|
||||
if (mDrawingBounds == null) {
|
||||
mDrawingBounds = super.computeDrawingBoundingBox();
|
||||
}
|
||||
return mDrawingBounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight(boolean cap) {
|
||||
if (cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight == -1
|
||||
&& Log.isLoggable(TAG, Log.WARN)) {
|
||||
Log.w(TAG, "maxLineHeight should not be -1. "
|
||||
+ " maxLines:" + mMaximumVisibleLineCount
|
||||
+ " lineCount:" + mLineCount);
|
||||
}
|
||||
|
||||
return cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight != -1
|
||||
? mMaxLineHeight : super.getHeight();
|
||||
}
|
||||
|
||||
private int mLineCount;
|
||||
private int mTopPadding, mBottomPadding;
|
||||
private int mColumns;
|
||||
private RectF mDrawingBounds = null; // lazy calculation.
|
||||
|
||||
private boolean mEllipsized;
|
||||
|
||||
private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
|
||||
|
||||
private static final int COLUMNS_NORMAL = 5;
|
||||
private static final int COLUMNS_ELLIPSIZE = 7;
|
||||
private static final int START = 0;
|
||||
private static final int DIR = START;
|
||||
private static final int TAB = START;
|
||||
private static final int TOP = 1;
|
||||
private static final int DESCENT = 2;
|
||||
private static final int EXTRA = 3;
|
||||
private static final int HYPHEN = 4;
|
||||
private static final int ELLIPSIS_START = 5;
|
||||
private static final int ELLIPSIS_COUNT = 6;
|
||||
|
||||
private int[] mLines;
|
||||
private Directions[] mLineDirections;
|
||||
private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
|
||||
|
||||
private static final int START_MASK = 0x1FFFFFFF;
|
||||
private static final int DIR_SHIFT = 30;
|
||||
private static final int TAB_MASK = 0x20000000;
|
||||
private static final int HYPHEN_MASK = 0xFF;
|
||||
private static final int START_HYPHEN_BITS_SHIFT = 3;
|
||||
private static final int START_HYPHEN_MASK = 0x18; // 0b11000
|
||||
private static final int END_HYPHEN_MASK = 0x7; // 0b00111
|
||||
|
||||
private static final float TAB_INCREMENT = 20; // same as Layout, but that's private
|
||||
|
||||
private static final char CHAR_NEW_LINE = '\n';
|
||||
|
||||
private static final double EXTRA_ROUNDING = 0.5;
|
||||
|
||||
private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
|
||||
|
||||
// Unused, here because of gray list private API accesses.
|
||||
/*package*/ static class LineBreaks {
|
||||
private static final int INITIAL_SIZE = 16;
|
||||
public int[] breaks = new int[INITIAL_SIZE];
|
||||
public float[] widths = new float[INITIAL_SIZE];
|
||||
public float[] ascents = new float[INITIAL_SIZE];
|
||||
public float[] descents = new float[INITIAL_SIZE];
|
||||
public int[] flags = new int[INITIAL_SIZE]; // hasTab
|
||||
// breaks, widths, and flags should all have the same length
|
||||
}
|
||||
|
||||
@Nullable private int[] mLeftIndents;
|
||||
@Nullable private int[] mRightIndents;
|
||||
}
|
||||
|
||||
178
AndroidCompat/src/main/java/android/text/TextLine.java
Normal file
178
AndroidCompat/src/main/java/android/text/TextLine.java
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.text;
|
||||
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint.FontMetricsInt;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.text.Layout.Directions;
|
||||
import android.text.Layout.TabStops;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.font.FontRenderContext;
|
||||
|
||||
|
||||
public class TextLine {
|
||||
private TextPaint mPaint;
|
||||
private CharSequence mText;
|
||||
private int mStart;
|
||||
private int mLen;
|
||||
private int mDir;
|
||||
private Directions mDirections;
|
||||
private boolean mHasTabs;
|
||||
private TabStops mTabs;
|
||||
private char[] mChars;
|
||||
private boolean mCharsValid;
|
||||
private Spanned mSpanned;
|
||||
private PrecomputedText mComputed;
|
||||
private RectF mTmpRectForMeasure;
|
||||
private RectF mTmpRectForPaintAPI;
|
||||
private Rect mTmpRectForPrecompute;
|
||||
|
||||
|
||||
public static final class LineInfo {
|
||||
private int mClusterCount;
|
||||
|
||||
public int getClusterCount() {
|
||||
return mClusterCount;
|
||||
}
|
||||
|
||||
public void setClusterCount(int clusterCount) {
|
||||
mClusterCount = clusterCount;
|
||||
}
|
||||
};
|
||||
|
||||
public float getAddedWordSpacingInPx() {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public float getAddedLetterSpacingInPx() {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public boolean isJustifying() {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
/** Not allowed to access. If it's for memory leak workaround, it was already fixed M. */
|
||||
private static final TextLine[] sCached = new TextLine[3];
|
||||
|
||||
public static TextLine obtain() {
|
||||
TextLine tl;
|
||||
synchronized (sCached) {
|
||||
for (int i = sCached.length; --i >= 0;) {
|
||||
if (sCached[i] != null) {
|
||||
tl = sCached[i];
|
||||
sCached[i] = null;
|
||||
return tl;
|
||||
}
|
||||
}
|
||||
}
|
||||
tl = new TextLine();
|
||||
return tl;
|
||||
}
|
||||
|
||||
public static TextLine recycle(TextLine tl) {
|
||||
synchronized(sCached) {
|
||||
for (int i = 0; i < sCached.length; ++i) {
|
||||
if (sCached[i] == null) {
|
||||
sCached[i] = tl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
|
||||
Directions directions, boolean hasTabs, TabStops tabStops,
|
||||
int ellipsisStart, int ellipsisEnd, boolean useFallbackLineSpacing) {
|
||||
mPaint = paint;
|
||||
mText = text;
|
||||
mStart = start;
|
||||
mLen = limit - start;
|
||||
mDir = dir;
|
||||
mDirections = directions;
|
||||
if (mDirections == null) {
|
||||
throw new IllegalArgumentException("Directions cannot be null");
|
||||
}
|
||||
mHasTabs = hasTabs;
|
||||
mSpanned = null;
|
||||
|
||||
if (text instanceof Spanned) {
|
||||
mSpanned = (Spanned) text;
|
||||
}
|
||||
|
||||
mComputed = null;
|
||||
if (text instanceof PrecomputedText) {
|
||||
// Here, no need to check line break strategy or hyphenation frequency since there is no
|
||||
// line break concept here.
|
||||
mComputed = (PrecomputedText) text;
|
||||
if (!mComputed.getParams().getTextPaint().equalsForTextMeasurement(paint)) {
|
||||
mComputed = null;
|
||||
}
|
||||
}
|
||||
|
||||
mTabs = tabStops;
|
||||
}
|
||||
|
||||
public void justify(@Layout.JustificationMode int justificationMode, float justifyWidth) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public static int calculateRunFlag(int bidiRunIndex, int bidiRunCount, int lineDirection) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public static int resolveRunFlagForSubSequence(int runFlag, boolean isRtlRun, int runStart,
|
||||
int runEnd, int spanStart, int spanEnd) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public float metrics(FontMetricsInt fmi, @Nullable RectF drawBounds, boolean returnDrawWidth,
|
||||
@Nullable LineInfo lineInfo) {
|
||||
FontRenderContext frc = new FontRenderContext(null, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
|
||||
return (float) mPaint.getFont().getStringBounds(mText.toString(), mStart, mStart + mLen, frc).getWidth();
|
||||
}
|
||||
|
||||
public float measure(@IntRange(from = 0) int offset, boolean trailing,
|
||||
@NonNull FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable LineInfo lineInfo) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public void measureAllBounds(@NonNull float[] bounds, @Nullable float[] advances) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public float[] measureAllOffsets(boolean[] trailing, FontMetricsInt fmi) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
// Note: keep this in sync with Minikin LineBreaker::isLineEndSpace()
|
||||
public static boolean isLineEndSpace(char ch) {
|
||||
return ch == ' ' || ch == '\t' || ch == 0x1680
|
||||
|| (0x2000 <= ch && ch <= 0x200A && ch != 0x2007)
|
||||
|| ch == 0x205F || ch == 0x3000;
|
||||
}
|
||||
|
||||
void draw(Canvas c, float x, int top, int y, int bottom) {
|
||||
c.drawText(mText, mStart, mStart + mLen, x, y, mPaint);
|
||||
}
|
||||
}
|
||||
74
AndroidCompat/src/main/java/android/text/TextPaint.java
Normal file
74
AndroidCompat/src/main/java/android/text/TextPaint.java
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.text;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.graphics.Paint;
|
||||
|
||||
public class TextPaint extends Paint {
|
||||
|
||||
// Special value 0 means no background paint
|
||||
@ColorInt
|
||||
public int bgColor;
|
||||
public int baselineShift;
|
||||
@ColorInt
|
||||
public int linkColor;
|
||||
public int[] drawableState;
|
||||
public float density = 1.0f;
|
||||
@ColorInt
|
||||
public int underlineColor = 0;
|
||||
|
||||
public float underlineThickness;
|
||||
|
||||
public TextPaint() {
|
||||
super();
|
||||
}
|
||||
|
||||
public TextPaint(int flags) {
|
||||
super(flags);
|
||||
}
|
||||
|
||||
public TextPaint(Paint p) {
|
||||
super(p);
|
||||
}
|
||||
|
||||
public void set(TextPaint tp) {
|
||||
super.set(tp);
|
||||
|
||||
bgColor = tp.bgColor;
|
||||
baselineShift = tp.baselineShift;
|
||||
linkColor = tp.linkColor;
|
||||
drawableState = tp.drawableState;
|
||||
density = tp.density;
|
||||
underlineColor = tp.underlineColor;
|
||||
underlineThickness = tp.underlineThickness;
|
||||
}
|
||||
|
||||
public void setUnderlineText(int color, float thickness) {
|
||||
underlineColor = color;
|
||||
underlineThickness = thickness;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUnderlineThickness() {
|
||||
if (underlineColor != 0) { // Return custom thickness only if underline color is set.
|
||||
return underlineThickness;
|
||||
} else {
|
||||
return super.getUnderlineThickness();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.internal.util;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.util.ArraySet;
|
||||
import libcore.util.EmptyArray;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
import libcore.util.EmptyArray;
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* ArrayUtils contains some methods that you can call to find out
|
||||
* the most efficient increments by which to grow arrays.
|
||||
@@ -50,6 +50,10 @@ public class ArrayUtils {
|
||||
public static Object[] newUnpaddedObjectArray(int minLen) {
|
||||
return new Object[minLen];
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T[] newUnpaddedArray(Class<T> clazz, int minLen) {
|
||||
return (T[])Array.newInstance(clazz, minLen);
|
||||
}
|
||||
/**
|
||||
* Checks if the beginnings of two byte arrays are equal.
|
||||
*
|
||||
@@ -468,4 +472,4 @@ public class ArrayUtils {
|
||||
}
|
||||
return size - leftIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.internal.util;
|
||||
|
||||
public final class GrowingArrayUtils {
|
||||
|
||||
public static <T> T[] append(T[] array, int currentSize, T element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 > array.length) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] newArray = ArrayUtils.newUnpaddedArray(
|
||||
(Class<T>) array.getClass().getComponentType(), growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||
array = newArray;
|
||||
}
|
||||
array[currentSize] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
public static int[] append(int[] array, int currentSize, int element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 > array.length) {
|
||||
int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||
array = newArray;
|
||||
}
|
||||
array[currentSize] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
public static long[] append(long[] array, int currentSize, long element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 > array.length) {
|
||||
long[] newArray = ArrayUtils.newUnpaddedLongArray(growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||
array = newArray;
|
||||
}
|
||||
array[currentSize] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
public static boolean[] append(boolean[] array, int currentSize, boolean element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 > array.length) {
|
||||
boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||
array = newArray;
|
||||
}
|
||||
array[currentSize] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
public static float[] append(float[] array, int currentSize, float element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 > array.length) {
|
||||
float[] newArray = ArrayUtils.newUnpaddedFloatArray(growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||
array = newArray;
|
||||
}
|
||||
array[currentSize] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 <= array.length) {
|
||||
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||
array[index] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] newArray = ArrayUtils.newUnpaddedArray((Class<T>)array.getClass().getComponentType(),
|
||||
growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, index);
|
||||
newArray[index] = element;
|
||||
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||
return newArray;
|
||||
}
|
||||
|
||||
public static int[] insert(int[] array, int currentSize, int index, int element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 <= array.length) {
|
||||
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||
array[index] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, index);
|
||||
newArray[index] = element;
|
||||
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||
return newArray;
|
||||
}
|
||||
|
||||
public static long[] insert(long[] array, int currentSize, int index, long element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 <= array.length) {
|
||||
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||
array[index] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
long[] newArray = ArrayUtils.newUnpaddedLongArray(growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, index);
|
||||
newArray[index] = element;
|
||||
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||
return newArray;
|
||||
}
|
||||
|
||||
public static boolean[] insert(boolean[] array, int currentSize, int index, boolean element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 <= array.length) {
|
||||
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||
array[index] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, index);
|
||||
newArray[index] = element;
|
||||
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||
return newArray;
|
||||
}
|
||||
|
||||
public static int growSize(int currentSize) {
|
||||
return currentSize <= 4 ? 8 : currentSize * 2;
|
||||
}
|
||||
|
||||
// Uninstantiable
|
||||
private GrowingArrayUtils() {}
|
||||
}
|
||||
Reference in New Issue
Block a user