diff options
Diffstat (limited to 'graphics')
35 files changed, 1709 insertions, 130 deletions
diff --git a/graphics/TEST_MAPPING b/graphics/TEST_MAPPING index abeaf1996ca7..8afc30d54a53 100644 --- a/graphics/TEST_MAPPING +++ b/graphics/TEST_MAPPING @@ -7,6 +7,18 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "CtsTextTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "androidx.test.filters.LargeTest" + } + ], + "file_patterns": ["(/|^)Typeface\\.java", "(/|^)Paint\\.java"] } ] } diff --git a/graphics/java/Android.bp b/graphics/java/Android.bp index 63d1f6d6f2d6..db37a38756d2 100644 --- a/graphics/java/Android.bp +++ b/graphics/java/Android.bp @@ -7,6 +7,12 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +aconfig_declarations { + name: "framework_graphics_flags", + package: "com.android.graphics.flags", + srcs: ["android/framework_graphics.aconfig"], +} + filegroup { name: "framework-graphics-nonupdatable-sources", srcs: [ diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig new file mode 100644 index 000000000000..9a0a22a08a0c --- /dev/null +++ b/graphics/java/android/framework_graphics.aconfig @@ -0,0 +1,8 @@ +package: "com.android.graphics.flags" + +flag { + name: "exact_compute_bounds" + namespace: "core_graphics" + description: "Add a function without unused exact param for computeBounds." + bug: "304478551" +}
\ No newline at end of file diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index b9d3756ac6d2..250362b1e1e3 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -479,7 +479,8 @@ public final class Bitmap implements Parcelable { * This configuration may be useful when using opaque bitmaps * that do not require high color fidelity. * - * <p>Use this formula to pack into 16 bits:</p> + * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer, + * use this formula to pack into 16 bits:</p> * <pre class="prettyprint"> * short color = (R & 0x1f) << 11 | (G & 0x3f) << 5 | (B & 0x1f); * </pre> @@ -516,7 +517,8 @@ public final class Bitmap implements Parcelable { * This configuration is very flexible and offers the best * quality. It should be used whenever possible. * - * <p>Use this formula to pack into 32 bits:</p> + * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer, + * use this formula to pack into 32 bits:</p> * <pre class="prettyprint"> * int color = (A & 0xff) << 24 | (B & 0xff) << 16 | (G & 0xff) << 8 | (R & 0xff); * </pre> @@ -531,7 +533,8 @@ public final class Bitmap implements Parcelable { * This configuration is particularly suited for wide-gamut and * HDR content. * - * <p>Use this formula to pack into 64 bits:</p> + * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer, + * use this formula to pack into 64 bits:</p> * <pre class="prettyprint"> * long color = (A & 0xffff) << 48 | (B & 0xffff) << 32 | (G & 0xffff) << 16 | (R & 0xffff); * </pre> @@ -556,7 +559,8 @@ public final class Bitmap implements Parcelable { * blending, such that the memory cost is the same as ARGB_8888 while enabling higher color * precision. * - * <p>Use this formula to pack into 32 bits:</p> + * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer, + * use this formula to pack into 32 bits:</p> * <pre class="prettyprint"> * int color = (A & 0x3) << 30 | (B & 0x3ff) << 20 | (G & 0x3ff) << 10 | (R & 0x3ff); * </pre> @@ -782,10 +786,13 @@ public final class Bitmap implements Parcelable { @Nullable public static Bitmap wrapHardwareBuffer(@NonNull HardwareBuffer hardwareBuffer, @Nullable ColorSpace colorSpace) { - if ((hardwareBuffer.getUsage() & HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) == 0) { + final long usage = hardwareBuffer.getUsage(); + if ((usage & HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) == 0) { throw new IllegalArgumentException("usage flags must contain USAGE_GPU_SAMPLED_IMAGE."); } - int format = hardwareBuffer.getFormat(); + if ((usage & HardwareBuffer.USAGE_PROTECTED_CONTENT) != 0) { + throw new IllegalArgumentException("Bitmap is not compatible with protected buffers"); + } if (colorSpace == null) { colorSpace = ColorSpace.get(ColorSpace.Named.SRGB); } @@ -1776,7 +1783,7 @@ public final class Bitmap implements Parcelable { * If the bitmap's internal config is in one of the public formats, return * that config, otherwise return null. */ - @NonNull + @Nullable public final Config getConfig() { if (mRecycled) { Log.w(TAG, "Called getConfig() on a recycle()'d bitmap! This is undefined behavior!"); diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java index 5c065775eea2..dcfff62459ab 100644 --- a/graphics/java/android/graphics/BitmapShader.java +++ b/graphics/java/android/graphics/BitmapShader.java @@ -16,9 +16,13 @@ package android.graphics; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.graphics.hwui.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -32,6 +36,7 @@ public class BitmapShader extends Shader { * Prevent garbage collection. */ /*package*/ Bitmap mBitmap; + private Gainmap mOverrideGainmap; private int mTileX; private int mTileY; @@ -173,6 +178,24 @@ public class BitmapShader extends Shader { } /** + * Draws the BitmapShader with a copy of the given gainmap instead of the gainmap on the Bitmap + * the shader was constructed from + * + * @param overrideGainmap The gainmap to draw instead, null to use any gainmap on the Bitmap + */ + @FlaggedApi(Flags.FLAG_GAINMAP_ANIMATIONS) + public void setOverrideGainmap(@Nullable Gainmap overrideGainmap) { + if (!Flags.gainmapAnimations()) throw new IllegalStateException("API not available"); + + if (overrideGainmap == null) { + mOverrideGainmap = null; + } else { + mOverrideGainmap = new Gainmap(overrideGainmap, overrideGainmap.getGainmapContents()); + } + discardNativeInstance(); + } + + /** * Returns the current max anisotropic filtering value configured by * {@link #setFilterMode(int)}. If {@link #setFilterMode(int)} is invoked this returns zero. */ @@ -199,14 +222,9 @@ public class BitmapShader extends Shader { mIsDirectSampled = mRequestDirectSampling; mRequestDirectSampling = false; - - if (mMaxAniso > 0) { - return nativeCreateWithMaxAniso(nativeMatrix, mBitmap.getNativeInstance(), mTileX, - mTileY, mMaxAniso, mIsDirectSampled); - } else { - return nativeCreate(nativeMatrix, mBitmap.getNativeInstance(), mTileX, mTileY, - enableLinearFilter, mIsDirectSampled); - } + return nativeCreate(nativeMatrix, mBitmap.getNativeInstance(), mTileX, + mTileY, mMaxAniso, enableLinearFilter, mIsDirectSampled, + mOverrideGainmap != null ? mOverrideGainmap.mNativePtr : 0); } /** @hide */ @@ -217,9 +235,7 @@ public class BitmapShader extends Shader { } private static native long nativeCreate(long nativeMatrix, long bitmapHandle, - int shaderTileModeX, int shaderTileModeY, boolean filter, boolean isDirectSampled); - - private static native long nativeCreateWithMaxAniso(long nativeMatrix, long bitmapHandle, - int shaderTileModeX, int shaderTileModeY, int maxAniso, boolean isDirectSampled); + int shaderTileModeX, int shaderTileModeY, int maxAniso, boolean filter, + boolean isDirectSampled, long overrideGainmapHandle); } diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index e7814cbd67e7..d1aceafc4e2c 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -114,6 +114,7 @@ public class Canvas extends BaseCanvas { throw new IllegalStateException("Immutable bitmap passed to Canvas constructor"); } throwIfCannotDraw(bitmap); + bitmap.setGainmap(null); mNativeCanvasWrapper = nInitRaster(bitmap.getNativeInstance()); mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation( this, mNativeCanvasWrapper); @@ -178,7 +179,7 @@ public class Canvas extends BaseCanvas { throw new IllegalStateException(); } throwIfCannotDraw(bitmap); - + bitmap.setGainmap(null); nSetBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance()); mDensity = bitmap.mDensity; } diff --git a/graphics/java/android/graphics/ColorFilter.java b/graphics/java/android/graphics/ColorFilter.java index 8fd6f7f609c6..7050325997b6 100644 --- a/graphics/java/android/graphics/ColorFilter.java +++ b/graphics/java/android/graphics/ColorFilter.java @@ -41,21 +41,11 @@ public class ColorFilter { * Current native SkColorFilter instance. */ private long mNativeInstance; - // Runnable to do immediate destruction - private Runnable mCleaner; long createNativeInstance() { return 0; } - synchronized final void discardNativeInstance() { - if (mNativeInstance != 0) { - mCleaner.run(); - mCleaner = null; - mNativeInstance = 0; - } - } - /** @hide */ public synchronized final long getNativeInstance() { if (mNativeInstance == 0) { @@ -65,8 +55,7 @@ public class ColorFilter { // Note: we must check for null here, since it's possible for createNativeInstance() // to return nullptr if the native SkColorFilter would be a no-op at draw time. // See native implementations of subclass create methods for more info. - mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation( - this, mNativeInstance); + NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeInstance); } } return mNativeInstance; diff --git a/graphics/java/android/graphics/ColorMatrixColorFilter.java b/graphics/java/android/graphics/ColorMatrixColorFilter.java index 90ff1899f34d..bfdf3187c575 100644 --- a/graphics/java/android/graphics/ColorMatrixColorFilter.java +++ b/graphics/java/android/graphics/ColorMatrixColorFilter.java @@ -81,12 +81,12 @@ public class ColorMatrixColorFilter extends ColorFilter { */ @UnsupportedAppUsage public void setColorMatrix(@Nullable ColorMatrix matrix) { - discardNativeInstance(); if (matrix == null) { mMatrix.reset(); } else { mMatrix.set(matrix); } + nativeSetColorMatrix(getNativeInstance(), mMatrix.getArray()); } /** @@ -111,7 +111,6 @@ public class ColorMatrixColorFilter extends ColorFilter { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setColorMatrixArray(@Nullable float[] array) { // called '...Array' so that passing null isn't ambiguous - discardNativeInstance(); if (array == null) { mMatrix.reset(); } else { @@ -120,6 +119,7 @@ public class ColorMatrixColorFilter extends ColorFilter { } mMatrix.set(array); } + nativeSetColorMatrix(getNativeInstance(), mMatrix.getArray()); } @Override @@ -128,4 +128,6 @@ public class ColorMatrixColorFilter extends ColorFilter { } private static native long nativeColorMatrixFilter(float[] array); + + private static native void nativeSetColorMatrix(long colorMatrixColorFilter, float[] array); } diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 99bebb8b9812..a2319a53a659 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -199,6 +199,8 @@ public abstract class ColorSpace { private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }; private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f }; + private static final float[] DCI_P3_PRIMARIES = + { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }; private static final float[] BT2020_PRIMARIES = { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f }; /** @@ -211,6 +213,9 @@ public abstract class ColorSpace { private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS = new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4); + private static final Rgb.TransferParameters SMPTE_170M_TRANSFER_PARAMETERS = + new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45); + // HLG transfer with an SDR whitepoint of 203 nits private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS = new Rgb.TransferParameters(2.0, 2.0, 1 / 0.17883277, 0.28466892, 0.55991073, @@ -1559,10 +1564,10 @@ public abstract class ColorSpace { DataSpace.DATASPACE_SCRGB_LINEAR, Named.LINEAR_EXTENDED_SRGB.ordinal()); sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb( "Rec. ITU-R BT.709-5", - new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }, + SRGB_PRIMARIES, ILLUMINANT_D65, null, - new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), + SMPTE_170M_TRANSFER_PARAMETERS, Named.BT709.ordinal() ); sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal()); @@ -1577,7 +1582,7 @@ public abstract class ColorSpace { sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020, Named.BT2020.ordinal()); sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb( "SMPTE RP 431-2-2007 DCI (P3)", - new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, + DCI_P3_PRIMARIES, new float[] { 0.314f, 0.351f }, 2.6, 0.0f, 1.0f, @@ -1586,7 +1591,7 @@ public abstract class ColorSpace { sDataToColorSpaces.put(DataSpace.DATASPACE_DCI_P3, Named.DCI_P3.ordinal()); sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb( "Display P3", - new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, + DCI_P3_PRIMARIES, ILLUMINANT_D65, null, SRGB_TRANSFER_PARAMETERS, @@ -1598,7 +1603,7 @@ public abstract class ColorSpace { NTSC_1953_PRIMARIES, ILLUMINANT_C, null, - new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), + SMPTE_170M_TRANSFER_PARAMETERS, Named.NTSC_1953.ordinal() ); sNamedColorSpaces[Named.SMPTE_C.ordinal()] = new ColorSpace.Rgb( @@ -1606,7 +1611,7 @@ public abstract class ColorSpace { new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f }, ILLUMINANT_D65, null, - new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), + SMPTE_170M_TRANSFER_PARAMETERS, Named.SMPTE_C.ordinal() ); sNamedColorSpaces[Named.ADOBE_RGB.ordinal()] = new ColorSpace.Rgb( @@ -3057,7 +3062,7 @@ public abstract class ColorSpace { * primaries for such a ColorSpace does not make sense, so we use a special * set of primaries that are all 1s.</p> * - * @return A new non-null array of 2 floats + * @return A new non-null array of 6 floats * * @see #getPrimaries(float[]) */ diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 674246acafef..52b0b95d3e76 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -16,6 +16,10 @@ package android.graphics; +import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE; +import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL; +import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY; +import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT; import static android.text.FontConfig.NamedFamilyList; import android.annotation.NonNull; @@ -28,6 +32,7 @@ import android.os.Build; import android.os.LocaleList; import android.text.FontConfig; import android.util.ArraySet; +import android.util.Log; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; @@ -231,7 +236,9 @@ public class FontListParser { } } - return new FontConfig(families, filtered, resultNamedFamilies, lastModifiedDate, + return new FontConfig(families, filtered, resultNamedFamilies, + customization.getLocaleFamilyCustomizations(), + lastModifiedDate, configVersion); } @@ -256,6 +263,7 @@ public class FontListParser { final String lang = parser.getAttributeValue("", "lang"); final String variant = parser.getAttributeValue(null, "variant"); final String ignore = parser.getAttributeValue(null, "ignore"); + final String varFamilyTypeStr = parser.getAttributeValue(null, "varFamilyType"); final List<FontConfig.Font> fonts = new ArrayList<>(); while (keepReading(parser)) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; @@ -278,12 +286,45 @@ public class FontListParser { intVariant = FontConfig.FontFamily.VARIANT_ELEGANT; } } + int varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE; + if (varFamilyTypeStr != null) { + varFamilyType = Integer.parseInt(varFamilyTypeStr); + if (varFamilyType <= -1 || varFamilyType > 3) { + Log.e(TAG, "Error: unexpected varFamilyType value: " + varFamilyTypeStr); + varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE; + } + + // validation but don't read font content for performance reasons. + switch (varFamilyType) { + case VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY: + if (fonts.size() != 1) { + Log.e(TAG, "Error: Single font support wght axis, but two or more fonts are" + + " included in the font family."); + varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE; + } + break; + case VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL: + if (fonts.size() != 1) { + Log.e(TAG, "Error: Single font support both ital and wght axes, but two or" + + " more fonts are included in the font family."); + varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE; + } + break; + case VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT: + if (fonts.size() != 2) { + Log.e(TAG, "Error: two fonts that support wght axis, but one or three or" + + " more fonts are included in the font family."); + varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE; + } + } + } boolean skip = (ignore != null && (ignore.equals("true") || ignore.equals("1"))); if (skip || fonts.isEmpty()) { return null; } - return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant); + return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant, + varFamilyType); } private static void throwIfAttributeExists(String attrName, XmlPullParser parser) { diff --git a/graphics/java/android/graphics/ForceDarkType.java b/graphics/java/android/graphics/ForceDarkType.java new file mode 100644 index 000000000000..396b03703bb9 --- /dev/null +++ b/graphics/java/android/graphics/ForceDarkType.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 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.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * The style of force dark to use in {@link HardwareRenderer}. + * + * You must keep this in sync with the C++ enum ForceDarkType in + * frameworks/base/libs/hwui/utils/ForceDark.h + * + * @hide + */ +public class ForceDarkType { + /** + * Force dark disabled: normal, default operation. + * + * @hide + */ + public static final int NONE = 0; + + /** + * Use force dark + * @hide + */ + public static final int FORCE_DARK = 1; + + /** + * Force force-dark. {@see Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED} + * @hide */ + public static final int FORCE_INVERT_COLOR_DARK = 2; + + /** @hide */ + @IntDef({ + NONE, + FORCE_DARK, + FORCE_INVERT_COLOR_DARK, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ForceDarkTypeDef {} + +} diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java index f639521ff250..0a6fb8424094 100644 --- a/graphics/java/android/graphics/Gainmap.java +++ b/graphics/java/android/graphics/Gainmap.java @@ -16,11 +16,14 @@ package android.graphics; +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import com.android.graphics.hwui.flags.Flags; + import libcore.util.NativeAllocationRegistry; /** @@ -124,9 +127,8 @@ public final class Gainmap implements Parcelable { /** * Creates a new gainmap using the provided gainmap as the metadata source and the provided * bitmap as the replacement for the gainmapContents - * TODO: Make public, it's useful - * @hide */ + @FlaggedApi(Flags.FLAG_GAINMAP_CONSTRUCTOR_WITH_METADATA) public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) { this(gainmapContents, nCreateCopy(gainmap.mNativePtr)); } diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index 9cde1878d9d8..20e393eaee6d 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -182,7 +182,7 @@ public class HardwareRenderer { /** @hide */ protected RenderNode mRootNode; private boolean mOpaque = true; - private boolean mForceDark = false; + private int mForceDark = ForceDarkType.NONE; private @ActivityInfo.ColorMode int mColorMode = ActivityInfo.COLOR_MODE_DEFAULT; private float mDesiredSdrHdrRatio = 1f; @@ -571,10 +571,10 @@ public class HardwareRenderer { * Whether or not the force-dark feature should be used for this renderer. * @hide */ - public boolean setForceDark(boolean enable) { - if (mForceDark != enable) { - mForceDark = enable; - nSetForceDark(mNativeProxy, enable); + public boolean setForceDark(@ForceDarkType.ForceDarkTypeDef int type) { + if (mForceDark != type) { + mForceDark = type; + nSetForceDark(mNativeProxy, type); return true; } return false; @@ -1390,10 +1390,6 @@ public class HardwareRenderer { int largestWidth = activeMode.getPhysicalWidth(); int largestHeight = activeMode.getPhysicalHeight(); final OverlayProperties overlayProperties = defaultDisplay.getOverlaySupport(); - boolean supportFp16ForHdr = overlayProperties != null - ? overlayProperties.supportFp16ForHdr() : false; - boolean supportMixedColorSpaces = overlayProperties != null - ? overlayProperties.supportMixedColorSpaces() : false; for (int i = 0; i < allDisplays.length; i++) { final Display display = allDisplays[i]; @@ -1421,7 +1417,8 @@ public class HardwareRenderer { nInitDisplayInfo(largestWidth, largestHeight, defaultDisplay.getRefreshRate(), wideColorDataspace, defaultDisplay.getAppVsyncOffsetNanos(), defaultDisplay.getPresentationDeadlineNanos(), - supportFp16ForHdr, supportMixedColorSpaces); + overlayProperties.isFp16SupportedForHdr(), + overlayProperties.isMixedColorSpacesSupported()); mDisplayInitialized = true; } @@ -1600,7 +1597,7 @@ public class HardwareRenderer { private static native void nAllocateBuffers(long nativeProxy); - private static native void nSetForceDark(long nativeProxy, boolean enabled); + private static native void nSetForceDark(long nativeProxy, int type); private static native void nSetDisplayDensityDpi(int densityDpi); diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java index df91c5d492bd..0aa6f1282c1a 100644 --- a/graphics/java/android/graphics/LightingColorFilter.java +++ b/graphics/java/android/graphics/LightingColorFilter.java @@ -78,7 +78,7 @@ public class LightingColorFilter extends ColorFilter { public void setColorMultiply(@ColorInt int mul) { if (mMul != mul) { mMul = mul; - discardNativeInstance(); + native_SetLightingFilterMul(getNativeInstance(), mul); } } @@ -104,7 +104,7 @@ public class LightingColorFilter extends ColorFilter { public void setColorAdd(@ColorInt int add) { if (mAdd != add) { mAdd = add; - discardNativeInstance(); + native_SetLightingFilterAdd(getNativeInstance(), add); } } @@ -114,4 +114,8 @@ public class LightingColorFilter extends ColorFilter { } private static native long native_CreateLightingFilter(int mul, int add); + + private static native void native_SetLightingFilterAdd(long lightingFilter, int add); + + private static native void native_SetLightingFilterMul(long lightingFilter, int mul); } diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java index 66fabec91924..a4bce9eb5e88 100644 --- a/graphics/java/android/graphics/Mesh.java +++ b/graphics/java/android/graphics/Mesh.java @@ -23,6 +23,8 @@ import android.annotation.NonNull; import libcore.util.NativeAllocationRegistry; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.nio.Buffer; import java.nio.ShortBuffer; @@ -43,6 +45,7 @@ public class Mesh { * Determines how the mesh is represented and will be drawn. */ @IntDef({TRIANGLES, TRIANGLE_STRIP}) + @Retention(RetentionPolicy.SOURCE) private @interface Mode {} /** diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index d35dcab11f49..92c4de6490a3 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -16,14 +16,20 @@ package android.graphics; +import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE; + import android.annotation.ColorInt; import android.annotation.ColorLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Px; import android.annotation.Size; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.fonts.FontVariationAxis; import android.os.Build; @@ -611,6 +617,7 @@ public class Paint { mCompatScaling = mInvCompatScaling = 1; setTextLocales(LocaleList.getAdjustedDefault()); mColor = Color.pack(Color.BLACK); + resetElegantTextHeight(); } /** @@ -651,7 +658,7 @@ public class Paint { mBidiFlags = BIDI_DEFAULT_LTR; setTextLocales(LocaleList.getAdjustedDefault()); - setElegantTextHeight(false); + resetElegantTextHeight(); mFontFeatureSettings = null; mFontVariationSettings = null; @@ -1606,7 +1613,7 @@ public class Paint { /** * Returns the color of the shadow layer. * - * @return the shadow layer's color encoded as a {@link ColorLong}. + * @return the shadow layer's color encoded as a {@code ColorLong}. * @see #setShadowLayer(float,float,float,int) * @see #setShadowLayer(float,float,float,long) * @see Color @@ -1732,12 +1739,30 @@ public class Paint { /** * Get the elegant metrics flag. * + * From API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default value will be true by + * default if the app has a target SDK of API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or + * later. + * * @return true if elegant metrics are enabled for text drawing. */ public boolean isElegantTextHeight() { - return nIsElegantTextHeight(mNativePaint); + int rawValue = nGetElegantTextHeight(mNativePaint); + switch (rawValue) { + case ELEGANT_TEXT_HEIGHT_DISABLED: + return false; + case ELEGANT_TEXT_HEIGHT_ENABLED: + return true; + case ELEGANT_TEXT_HEIGHT_UNSET: + default: + return com.android.text.flags.Flags.deprecateUiFonts(); + } } + // Note: the following three values must be equal to the ones in the JNI file: Paint.cpp + private static final int ELEGANT_TEXT_HEIGHT_UNSET = -1; + private static final int ELEGANT_TEXT_HEIGHT_ENABLED = 0; + private static final int ELEGANT_TEXT_HEIGHT_DISABLED = 1; + /** * Set the paint's elegant height metrics flag. This setting selects font * variants that have not been compacted to fit Latin-based vertical @@ -1746,7 +1771,29 @@ public class Paint { * @param elegant set the paint's elegant metrics flag for drawing text. */ public void setElegantTextHeight(boolean elegant) { - nSetElegantTextHeight(mNativePaint, elegant); + nSetElegantTextHeight(mNativePaint, + elegant ? ELEGANT_TEXT_HEIGHT_ENABLED : ELEGANT_TEXT_HEIGHT_DISABLED); + } + + /** + * A change ID for deprecating UI fonts. + * + * From API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default value will be true by + * default if the app has a target SDK of API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or + * later. + * + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + public static final long DEPRECATE_UI_FONT = 279646685L; + + private void resetElegantTextHeight() { + if (CompatChanges.isChangeEnabled(DEPRECATE_UI_FONT)) { + nSetElegantTextHeight(mNativePaint, ELEGANT_TEXT_HEIGHT_UNSET); + } else { + nSetElegantTextHeight(mNativePaint, ELEGANT_TEXT_HEIGHT_DISABLED); + } } /** @@ -2110,6 +2157,31 @@ public class Paint { * The recommended additional space to add between lines of text. */ public float leading; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || !(o instanceof FontMetrics)) return false; + FontMetrics that = (FontMetrics) o; + return that.top == top && that.ascent == ascent && that.descent == descent + && that.bottom == bottom && that.leading == leading; + } + + @Override + public int hashCode() { + return Objects.hash(top, ascent, descent, bottom, leading); + } + + @Override + public String toString() { + return "FontMetrics{" + + "top=" + top + + ", ascent=" + ascent + + ", descent=" + descent + + ", bottom=" + bottom + + ", leading=" + leading + + '}'; + } } /** @@ -2125,7 +2197,7 @@ public class Paint { * @return the font's recommended interline spacing. */ public float getFontMetrics(FontMetrics metrics) { - return nGetFontMetrics(mNativePaint, metrics); + return nGetFontMetrics(mNativePaint, metrics, false); } /** @@ -2139,6 +2211,32 @@ public class Paint { } /** + * Get the font metrics used for the locale + * + * Obtain the metrics of the font that is used for the specified locale by + * {@link #setTextLocales(LocaleList)}. If multiple locales are specified, the minimum ascent + * and maximum descent will be set. + * + * This API is useful for determining the default line height of the empty text field, e.g. + * {@link android.widget.EditText}. + * + * Note, if the {@link android.graphics.Typeface} is created from the custom font files, its + * metrics are reserved, i.e. the ascent will be the custom font's ascent or smaller, the + * descent will be the custom font's descent or larger. + * + * Note, if the {@link android.graphics.Typeface} is a system fallback, e.g. + * {@link android.graphics.Typeface#SERIF}, the default font's metrics are reserved, i.e. the + * ascent will be the serif font's ascent or smaller, the descent will be the serif font's + * descent or larger. + * + * @param metrics an output parameter. All members will be set by calling this function. + */ + @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) + public void getFontMetricsForLocale(@NonNull FontMetrics metrics) { + nGetFontMetrics(mNativePaint, metrics, true); + } + + /** * Returns the font metrics value for the given text. * * If the text is rendered with multiple font files, this function returns the large ascent and @@ -2280,6 +2378,33 @@ public class Paint { */ public int leading; + /** + * Set values from {@link FontMetricsInt}. + * @param fontMetricsInt a font metrics. + */ + @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) + public void set(@NonNull FontMetricsInt fontMetricsInt) { + top = fontMetricsInt.top; + ascent = fontMetricsInt.ascent; + descent = fontMetricsInt.descent; + bottom = fontMetricsInt.bottom; + leading = fontMetricsInt.leading; + } + + /** + * Set values from {@link FontMetrics} with rounding accordingly. + * @param fontMetrics a font metrics. + */ + @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) + public void set(@NonNull FontMetrics fontMetrics) { + // See GraphicsJNI::set_metrics_int method for consistency. + top = (int) Math.floor(fontMetrics.top); + ascent = Math.round(fontMetrics.ascent); + descent = Math.round(fontMetrics.descent); + bottom = (int) Math.ceil(fontMetrics.bottom); + leading = Math.round(fontMetrics.leading); + } + @Override public String toString() { return "FontMetricsInt: top=" + top + " ascent=" + ascent + " descent=" + descent + " bottom=" + bottom + @@ -2318,7 +2443,7 @@ public class Paint { * @return the font's interline spacing. */ public int getFontMetricsInt(FontMetricsInt fmi) { - return nGetFontMetricsInt(mNativePaint, fmi); + return nGetFontMetricsInt(mNativePaint, fmi, false); } public FontMetricsInt getFontMetricsInt() { @@ -2328,6 +2453,32 @@ public class Paint { } /** + * Get the font metrics used for the locale + * + * Obtain the metrics of the font that is used for the specified locale by + * {@link #setTextLocales(LocaleList)}. If multiple locales are specified, the minimum ascent + * and maximum descent will be set. + * + * This API is useful for determining the default line height of the empty text field, e.g. + * {@link android.widget.EditText}. + * + * Note, if the {@link android.graphics.Typeface} is created from the custom font files, its + * metrics are reserved, i.e. the ascent will be the custom font's ascent or smaller, the + * descent will be the custom font's descent or larger. + * + * Note, if the {@link android.graphics.Typeface} is a system fallback, e.g. + * {@link android.graphics.Typeface#SERIF}, the default font's metrics are reserved, i.e. the + * ascent will be the serif font's ascent or smaller, the descent will be the serif font's + * descent or larger. + * + * @param metrics an output parameter. All members will be set by calling this function. + */ + @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) + public void getFontMetricsIntForLocale(@NonNull FontMetricsInt metrics) { + nGetFontMetricsInt(mNativePaint, metrics, true); + } + + /** * Return the recommend line spacing based on the current typeface and * text size. * @@ -3172,6 +3323,32 @@ public class Paint { public float getRunCharacterAdvance(@NonNull char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset, @Nullable float[] advances, int advancesIndex) { + return getRunCharacterAdvance(text, start, end, contextStart, contextEnd, isRtl, offset, + advances, advancesIndex, null); + } + + /** + * Measure the advance of each character within a run of text and also return the cursor + * position within the run. + * + * @see #getRunAdvance(char[], int, int, int, int, boolean, int) for more details. + * + * @param text the text to measure. Cannot be null. + * @param start the start index of the range to measure, inclusive + * @param end the end index of the range to measure, exclusive + * @param contextStart the start index of the shaping context, inclusive + * @param contextEnd the end index of the shaping context, exclusive + * @param isRtl whether the run is in RTL direction + * @param offset index of caret position + * @param advances the array that receives the computed character advances + * @param advancesIndex the start index from which the advances array is filled + * @param drawBounds the output parameter for the bounding box of drawing text, optional + * @return width measurement between start and offset + * @hide + */ + public float getRunCharacterAdvance(@NonNull char[] text, int start, int end, int contextStart, + int contextEnd, boolean isRtl, int offset, + @Nullable float[] advances, int advancesIndex, @Nullable RectF drawBounds) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } @@ -3201,7 +3378,7 @@ public class Paint { } return nGetRunCharacterAdvance(mNativePaint, text, start, end, contextStart, contextEnd, - isRtl, offset, advances, advancesIndex); + isRtl, offset, advances, advancesIndex, drawBounds); } /** @@ -3228,6 +3405,29 @@ public class Paint { public float getRunCharacterAdvance(@NonNull CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset, @Nullable float[] advances, int advancesIndex) { + return getRunCharacterAdvance(text, start, end, contextStart, contextEnd, isRtl, offset, + advances, advancesIndex, null); + } + + /** + * @see #getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int) + * + * @param text the text to measure. Cannot be null. + * @param start the index of the start of the range to measure + * @param end the index + 1 of the end of the range to measure + * @param contextStart the index of the start of the shaping context + * @param contextEnd the index + 1 of the end of the shaping context + * @param isRtl whether the run is in RTL direction + * @param offset index of caret position + * @param advances the array that receives the computed character advances + * @param advancesIndex the start index from which the advances array is filled + * @param drawBounds the output parameter for the bounding box of drawing text, optional + * @return width measurement between start and offset + * @hide + */ + public float getRunCharacterAdvance(@NonNull CharSequence text, int start, int end, + int contextStart, int contextEnd, boolean isRtl, int offset, + @Nullable float[] advances, int advancesIndex, @Nullable RectF drawBounds) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } @@ -3260,7 +3460,7 @@ public class Paint { TextUtils.getChars(text, contextStart, contextEnd, buf, 0); final float result = getRunCharacterAdvance(buf, start - contextStart, end - contextStart, 0, contextEnd - contextStart, isRtl, offset - contextStart, - advances, advancesIndex); + advances, advancesIndex, drawBounds); TemporaryBuffer.recycle(buf); return result; } @@ -3378,7 +3578,7 @@ public class Paint { int contextStart, int contextEnd, boolean isRtl, int offset); private static native float nGetRunCharacterAdvance(long paintPtr, char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset, float[] advances, - int advancesIndex); + int advancesIndex, RectF drawingBounds); private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance); private static native void nGetFontMetricsIntForText(long paintPtr, char[] text, @@ -3397,9 +3597,11 @@ public class Paint { @FastNative private static native void nSetFontFeatureSettings(long paintPtr, String settings); @FastNative - private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics); + private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics, + boolean useLocale); @FastNative - private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi); + private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi, + boolean useLocale); // ---------------- @CriticalNative ------------------------ @@ -3502,9 +3704,9 @@ public class Paint { @CriticalNative private static native void nSetStrikeThruText(long paintPtr, boolean strikeThruText); @CriticalNative - private static native boolean nIsElegantTextHeight(long paintPtr); + private static native int nGetElegantTextHeight(long paintPtr); @CriticalNative - private static native void nSetElegantTextHeight(long paintPtr, boolean elegant); + private static native void nSetElegantTextHeight(long paintPtr, int elegant); @CriticalNative private static native float nGetTextSize(long paintPtr); @CriticalNative diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index 81b8542c20f7..deb89faf3419 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -16,11 +16,14 @@ package android.graphics; +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import com.android.graphics.flags.Flags; + import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; @@ -293,9 +296,24 @@ public class Path { * * @param bounds Returns the computed bounds of the path's control points. * @param exact This parameter is no longer used. + * + * @deprecated use computeBounds(RectF) instead */ + @Deprecated @SuppressWarnings({"UnusedDeclaration"}) public void computeBounds(@NonNull RectF bounds, boolean exact) { + computeBounds(bounds); + } + + /** + * Compute the bounds of the control points of the path, and write the + * answer into bounds. If the path contains 0 or 1 points, the bounds is + * set to (0,0,0,0) + * + * @param bounds Returns the computed bounds of the path's control points. + */ + @FlaggedApi(Flags.FLAG_EXACT_COMPUTE_BOUNDS) + public void computeBounds(@NonNull RectF bounds) { nComputeBounds(mNativePath, bounds); } diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java index a5184f28dbc1..635e78e674be 100644 --- a/graphics/java/android/graphics/RecordingCanvas.java +++ b/graphics/java/android/graphics/RecordingCanvas.java @@ -276,9 +276,7 @@ public final class RecordingCanvas extends BaseRecordingCanvas { @CriticalNative private static native void nResetDisplayListCanvas(long canvas, long node, int width, int height); - @CriticalNative private static native int nGetMaximumTextureWidth(); - @CriticalNative private static native int nGetMaximumTextureHeight(); @CriticalNative private static native void nEnableZ(long renderer, boolean enableZ); diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index 2e91c240d71b..27325694073c 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -272,6 +272,17 @@ public final class RenderNode { void positionChanged(long frameNumber, int left, int top, int right, int bottom); /** + * Called by native by a Rendering Worker thread to update window position; includes + * the local rect that represents the clipped area of the RenderNode's bounds. + * + * @hide + */ + default void positionChanged(long frameNumber, int left, int top, int right, int bottom, + int clipLeft, int clipTop, int clipRight, int clipBottom) { + positionChanged(frameNumber, left, top, right, bottom); + } + + /** * Called by JNI * * @hide */ @@ -287,6 +298,23 @@ public final class RenderNode { } /** + * Called by JNI + * + * @hide */ + static boolean callPositionChanged2(WeakReference<PositionUpdateListener> weakListener, + long frameNumber, int left, int top, int right, int bottom, + int clipLeft, int clipTop, int clipRight, int clipBottom) { + final PositionUpdateListener listener = weakListener.get(); + if (listener != null) { + listener.positionChanged(frameNumber, left, top, right, bottom, clipLeft, + clipTop, clipRight, clipBottom); + return true; + } else { + return false; + } + } + + /** * Call to apply a stretch effect to any child SurfaceControl layers * * TODO: Fold this into positionChanged & have HWUI do the ASurfaceControl calls? @@ -371,6 +399,15 @@ public final class RenderNode { } @Override + public void positionChanged(long frameNumber, int left, int top, int right, int bottom, + int clipLeft, int clipTop, int clipRight, int clipBottom) { + for (PositionUpdateListener pul : mListeners) { + pul.positionChanged(frameNumber, left, top, right, bottom, clipLeft, clipTop, + clipRight, clipBottom); + } + } + + @Override public void positionLost(long frameNumber) { for (PositionUpdateListener pul : mListeners) { pul.positionLost(frameNumber); @@ -971,6 +1008,23 @@ public final class RenderNode { } /** + * Configure the {@link android.graphics.RenderEffect} to apply to the backdrop contents of + * this RenderNode. This will apply a visual effect to the result of the backdrop contents + * of this RenderNode before the RenderNode is drawn into the destination. For example if + * {@link RenderEffect#createBlurEffect(float, float, RenderEffect, Shader.TileMode)} + * is provided, the previous content behind this RenderNode will be blurred before the + * RenderNode is drawn in to the destination. + * @param renderEffect to be applied to the backdrop contents of this RenderNode. Passing + * null clears all previously configured RenderEffects + * @return True if the value changed, false if the new value was the same as the previous value. + * @hide + */ + public boolean setBackdropRenderEffect(@Nullable RenderEffect renderEffect) { + return nSetBackdropRenderEffect(mNativeRenderNode, + renderEffect != null ? renderEffect.getNativeInstance() : 0); + } + + /** * Returns the translucency level of this display list. * * @return A value between 0.0f and 1.0f @@ -1797,6 +1851,9 @@ public final class RenderNode { private static native boolean nSetRenderEffect(long renderNode, long renderEffect); @CriticalNative + private static native boolean nSetBackdropRenderEffect(long renderNode, long renderEffect); + + @CriticalNative private static native boolean nSetHasOverlappingRendering(long renderNode, boolean hasOverlappingRendering); diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java index 3e6457919031..78d257f86613 100644 --- a/graphics/java/android/graphics/RuntimeShader.java +++ b/graphics/java/android/graphics/RuntimeShader.java @@ -49,11 +49,11 @@ import libcore.util.NativeAllocationRegistry; * possible antialiasing logic for border pixels).</li> * <li>Logic for the {@link Shader}, {@link ColorFilter}, and {@link BlendMode} on the * {@link Paint}.</li> - * <li>Color space conversion code, as part of Android’s color management.</li> + * <li>Color space conversion code, as part of Android's color management.</li> * </ul> * * <p>A {@link RuntimeShader}, like other {@link Shader} types, effectively contributes a function - * to the GPU’s fragment shader.</p> + * to the GPU's fragment shader.</p> * * <h3>AGSL Shader Execution</h3> * <p>Just like a GLSL shader, an AGSL shader begins execution in a main function. Unlike GLSL, the @@ -78,10 +78,10 @@ import libcore.util.NativeAllocationRegistry; * {@link ColorSpace} for an AGSL shader is defined to be the color space of the destination, which * in most cases is determined by {@link Window#setColorMode(int)}.</p> * - * <p>When authoring an AGSL shader, you won’t know what the working color space is. For many + * <p>When authoring an AGSL shader, you won't know what the working color space is. For many * effects, this is fine because by default color inputs are automatically converted into the * working color space. For certain effects, it may be important to do some math in a fixed, known - * color space. A common example is lighting – to get physically accurate lighting, math should be + * color space. A common example is lighting - to get physically accurate lighting, math should be * done in a linear color space. To help with this, AGSL provides two intrinsic functions that * convert colors between the working color space and the * {@link ColorSpace.Named#LINEAR_EXTENDED_SRGB} color space: @@ -93,7 +93,7 @@ import libcore.util.NativeAllocationRegistry; * <h3>AGSL and Premultiplied Alpha</h3> * <p>When dealing with transparent colors, there are two (common) possible representations: * straight (unassociated) alpha and premultiplied (associated) alpha. In ASGL the color returned - * by the main function is expected to be premultiplied. AGSL’s use of premultiplied alpha + * by the main function is expected to be premultiplied. AGSL's use of premultiplied alpha * implies: * </p> * @@ -101,7 +101,7 @@ import libcore.util.NativeAllocationRegistry; * <li>If your AGSL shader will return transparent colors, be sure to multiply the RGB by A. The * resulting color should be [R*A, G*A, B*A, A], not [R, G, B, A].</li> * <li>For more complex shaders, you must understand which of your colors are premultiplied vs. - * straight. Many operations don’t make sense if you mix both kinds of color together.</li> + * straight. Many operations don't make sense if you mix both kinds of color together.</li> * </ul> * * <h3>Uniforms</h3> @@ -224,7 +224,7 @@ import libcore.util.NativeAllocationRegistry; * shader uniform is undefined if it is declared in the AGSL shader but not initialized.</p> * * <p>Although most {@link BitmapShader}s contain colors that should be color managed, some contain - * data that isn’t actually colors. This includes bitmaps storing normals, material properties + * data that isn't actually colors. This includes bitmaps storing normals, material properties * (e.g. roughness), heightmaps, or any other purely mathematical data that happens to be stored in * a bitmap. When using these kinds of shaders in AGSL, you probably want to initialize them with * {@link #setInputBuffer(String, BitmapShader)}. Shaders initialized this way work much like @@ -237,7 +237,7 @@ import libcore.util.NativeAllocationRegistry; * * <p>In addition, when sampling from a {@link BitmapShader} be aware that the shader does not use * normalized coordinates (like a texture in GLSL). It uses (0, 0) in the upper-left corner, and - * (width, height) in the bottom-right corner. Normally, this is exactly what you want. If you’re + * (width, height) in the bottom-right corner. Normally, this is exactly what you want. If you're * evaluating the shader with coordinates based on the ones passed to your AGSL program, the scale * is correct. However, if you want to adjust those coordinates (to do some kind of re-mapping of * the bitmap), remember that the coordinates are local to the canvas.</p> diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index ee0d64727cff..dd82fed03087 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -16,6 +16,7 @@ package android.graphics; +import android.annotation.FloatRange; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.compat.annotation.UnsupportedAppUsage; @@ -24,7 +25,10 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.Trace; import android.view.Surface; +import android.view.TextureView; +import android.view.flags.Flags; import java.lang.ref.WeakReference; @@ -44,6 +48,10 @@ import java.lang.ref.WeakReference; * frames from the image stream to be sent to the SurfaceTexture object rather than to the device's * display. * + * <p>A typical pattern is to use SurfaceTexture to render frames to a {@link TextureView}; however, + * a TextureView is not <i>required</i> for using the texture object. The texture object may be used + * as part of an OpenGL ES shader. + * * <p>When sampling from the texture one should first transform the texture coordinates using the * matrix queried via {@link #getTransformMatrix(float[])}. The transform matrix may change each * time {@link #updateTexImage} is called, so it should be re-queried each time the texture image @@ -74,6 +82,7 @@ public class SurfaceTexture { private final Looper mCreatorLooper; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private Handler mOnFrameAvailableHandler; + private Handler mOnSetFrameRateHandler; /** * These fields are used by native code, do not access or modify. @@ -95,6 +104,21 @@ public class SurfaceTexture { } /** + * Callback interface for being notified that a producer set a frame rate + * @hide + */ + public interface OnSetFrameRateListener { + /** + * Called when the producer sets a frame rate + * @hide + */ + void onSetFrameRate(SurfaceTexture surfaceTexture, + @FloatRange(from = 0.0) float frameRate, + @Surface.FrameRateCompatibility int compatibility, + @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy); + } + + /** * Exception thrown when a SurfaceTexture couldn't be created or resized. * * @deprecated No longer thrown. {@link android.view.Surface.OutOfResourcesException} @@ -219,6 +243,48 @@ public class SurfaceTexture { } } + private static class SetFrameRateArgs { + SetFrameRateArgs(@FloatRange(from = 0.0) float frameRate, + @Surface.FrameRateCompatibility int compatibility, + @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy) { + this.mFrameRate = frameRate; + this.mCompatibility = compatibility; + this.mChangeFrameRateStrategy = changeFrameRateStrategy; + } + final float mFrameRate; + final int mCompatibility; + final int mChangeFrameRateStrategy; + } + + /** + * Register a callback to be invoked when the producer sets a frame rate using + * Surface.setFrameRate. + * @hide + */ + public void setOnSetFrameRateListener(@Nullable final OnSetFrameRateListener listener, + @Nullable Handler handler) { + if (listener != null) { + Looper looper = handler != null ? handler.getLooper() : + mCreatorLooper != null ? mCreatorLooper : Looper.getMainLooper(); + mOnSetFrameRateHandler = new Handler(looper, null, true /*async*/) { + @Override + public void handleMessage(Message msg) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onSetFrameRateHandler"); + try { + SetFrameRateArgs args = (SetFrameRateArgs) msg.obj; + listener.onSetFrameRate(SurfaceTexture.this, + args.mFrameRate, args.mCompatibility, + args.mChangeFrameRateStrategy); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + } + }; + } else { + mOnSetFrameRateHandler = null; + } + } + /** * Set the default size of the image buffers. The image producer may override the buffer size, * in which case the producer-set buffer size will be used, not the default size set by this @@ -413,6 +479,35 @@ public class SurfaceTexture { } /** + * This method is invoked from native code only. + * @hide + */ + @SuppressWarnings({"UnusedDeclaration"}) + private static void postOnSetFrameRateEventFromNative(WeakReference<SurfaceTexture> weakSelf, + @FloatRange(from = 0.0) float frameRate, + @Surface.FrameRateCompatibility int compatibility, + @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "postOnSetFrameRateEventFromNative"); + try { + if (Flags.toolkitSetFrameRate()) { + SurfaceTexture st = weakSelf.get(); + if (st != null) { + Handler handler = st.mOnSetFrameRateHandler; + if (handler != null) { + Message msg = new Message(); + msg.obj = new SetFrameRateArgs(frameRate, compatibility, + changeFrameRateStrategy); + handler.sendMessage(msg); + } + } + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + + } + + /** * Returns {@code true} if the SurfaceTexture is single-buffered. * @hide */ diff --git a/graphics/java/android/graphics/TEST_MAPPING b/graphics/java/android/graphics/TEST_MAPPING new file mode 100644 index 000000000000..df912222909a --- /dev/null +++ b/graphics/java/android/graphics/TEST_MAPPING @@ -0,0 +1,21 @@ +{ + "presubmit": [ + { + "name": "CtsTextTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "androidx.test.filters.LargeTest" + } + ], + "file_patterns": [ + "Typeface\\.java", + "Paint\\.java", + "[^/]*Canvas\\.java", + "[^/]*Font[^/]*\\.java" + ] + } + ] +} diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 9fb627fcc501..4c4e8fa9c088 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -1475,7 +1475,10 @@ public class Typeface { String locale = SystemProperties.get("persist.sys.locale", "en-US"); String script = ULocale.addLikelySubtags(ULocale.forLanguageTag(locale)).getScript(); - FontConfig config = SystemFonts.getSystemPreinstalledFontConfig(); + // The feature flag cannot be referred from Zygote. Use legacy fonts.xml for preloading font + // files. + // TODO(nona): Use new XML file once the feature is fully launched. + FontConfig config = SystemFonts.getSystemPreinstalledFontConfigFromLegacyXml(); for (int i = 0; i < config.getFontFamilies().size(); ++i) { FontConfig.FontFamily family = config.getFontFamilies().get(i); if (!family.getLocaleList().isEmpty()) { diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java index 708feeb9e421..45e29a88c7db 100644 --- a/graphics/java/android/graphics/drawable/Icon.java +++ b/graphics/java/android/graphics/drawable/Icon.java @@ -24,9 +24,12 @@ import android.annotation.DrawableRes; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.IUriGrantsManager; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.ColorStateList; @@ -44,10 +47,13 @@ import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.os.Process; +import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.RequiresPermission; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; @@ -56,6 +62,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Objects; @@ -110,6 +118,7 @@ public final class Icon implements Parcelable { */ @IntDef({TYPE_BITMAP, TYPE_RESOURCE, TYPE_DATA, TYPE_URI, TYPE_ADAPTIVE_BITMAP, TYPE_URI_ADAPTIVE_BITMAP}) + @Retention(RetentionPolicy.SOURCE) public @interface IconType { } @@ -530,6 +539,46 @@ public final class Icon implements Parcelable { return loadDrawable(context); } + /** + * Load a drawable, but in the case of URI types, it will check if the passed uid has a grant + * to load the resource. The check will be performed using the permissions of the passed uid, + * and not those of the caller. + * <p> + * This should be called for {@link Icon} objects that come from a not trusted source and may + * contain a URI. + * + * After the check, if passed, {@link #loadDrawable} will be called. If failed, this will + * return {@code null}. + * + * @see #loadDrawable + * + * @hide + */ + @Nullable + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + public Drawable loadDrawableCheckingUriGrant( + Context context, + IUriGrantsManager iugm, + int callingUid, + String packageName + ) { + if (getType() == TYPE_URI || getType() == TYPE_URI_ADAPTIVE_BITMAP) { + try { + iugm.checkGrantUriPermission_ignoreNonSystem( + callingUid, + packageName, + ContentProvider.getUriWithoutUserId(getUri()), + Intent.FLAG_GRANT_READ_URI_PERMISSION, + ContentProvider.getUserIdFromUri(getUri()) + ); + } catch (SecurityException | RemoteException e) { + Log.e(TAG, "Failed to get URI permission for: " + getUri(), e); + return null; + } + } + return loadDrawable(context); + } + /** @hide */ public static final int MIN_ASHMEM_ICON_SIZE = 128 * (1 << 10); diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java index b458dd9021d0..ba5628cd2bc1 100644 --- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java +++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java @@ -22,6 +22,7 @@ import static android.text.FontConfig.NamedFamilyList; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.FontListParser; +import android.text.FontConfig; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; @@ -34,6 +35,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; /** @@ -52,14 +54,19 @@ public class FontCustomizationParser { private final List<Alias> mAdditionalAliases; + private final List<FontConfig.Customization.LocaleFallback> mLocaleFamilyCustomizations; + public Result() { mAdditionalNamedFamilies = Collections.emptyMap(); + mLocaleFamilyCustomizations = Collections.emptyList(); mAdditionalAliases = Collections.emptyList(); } public Result(Map<String, NamedFamilyList> additionalNamedFamilies, + List<FontConfig.Customization.LocaleFallback> localeFamilyCustomizations, List<Alias> additionalAliases) { mAdditionalNamedFamilies = additionalNamedFamilies; + mLocaleFamilyCustomizations = localeFamilyCustomizations; mAdditionalAliases = additionalAliases; } @@ -70,6 +77,10 @@ public class FontCustomizationParser { public List<Alias> getAdditionalAliases() { return mAdditionalAliases; } + + public List<FontConfig.Customization.LocaleFallback> getLocaleFamilyCustomizations() { + return mLocaleFamilyCustomizations; + } } /** @@ -89,7 +100,9 @@ public class FontCustomizationParser { } private static Result validateAndTransformToResult( - List<NamedFamilyList> families, List<Alias> aliases) { + List<NamedFamilyList> families, + List<FontConfig.Customization.LocaleFallback> outLocaleFamilies, + List<Alias> aliases) { HashMap<String, NamedFamilyList> namedFamily = new HashMap<>(); for (int i = 0; i < families.size(); ++i) { final NamedFamilyList family = families.get(i); @@ -105,7 +118,7 @@ public class FontCustomizationParser { + "requires fallackTarget attribute"); } } - return new Result(namedFamily, aliases); + return new Result(namedFamily, outLocaleFamilies, aliases); } private static Result readFamilies( @@ -115,12 +128,13 @@ public class FontCustomizationParser { ) throws XmlPullParserException, IOException { List<NamedFamilyList> families = new ArrayList<>(); List<Alias> aliases = new ArrayList<>(); + List<FontConfig.Customization.LocaleFallback> outLocaleFamilies = new ArrayList<>(); parser.require(XmlPullParser.START_TAG, null, "fonts-modification"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (tag.equals("family")) { - readFamily(parser, fontDir, families, updatableFontMap); + readFamily(parser, fontDir, families, outLocaleFamilies, updatableFontMap); } else if (tag.equals("family-list")) { readFamilyList(parser, fontDir, families, updatableFontMap); } else if (tag.equals("alias")) { @@ -129,13 +143,14 @@ public class FontCustomizationParser { FontListParser.skip(parser); } } - return validateAndTransformToResult(families, aliases); + return validateAndTransformToResult(families, outLocaleFamilies, aliases); } private static void readFamily( @NonNull XmlPullParser parser, @NonNull String fontDir, @NonNull List<NamedFamilyList> out, + @NonNull List<FontConfig.Customization.LocaleFallback> outCustomization, @Nullable Map<String, File> updatableFontMap) throws XmlPullParserException, IOException { final String customizationType = parser.getAttributeValue(null, "customizationType"); @@ -148,6 +163,29 @@ public class FontCustomizationParser { if (fontFamily != null) { out.add(fontFamily); } + } else if (customizationType.equals("new-locale-family")) { + final String lang = parser.getAttributeValue(null, "lang"); + final String op = parser.getAttributeValue(null, "operation"); + final int intOp; + if (op.equals("append")) { + intOp = FontConfig.Customization.LocaleFallback.OPERATION_APPEND; + } else if (op.equals("prepend")) { + intOp = FontConfig.Customization.LocaleFallback.OPERATION_PREPEND; + } else if (op.equals("replace")) { + intOp = FontConfig.Customization.LocaleFallback.OPERATION_REPLACE; + } else { + throw new IllegalArgumentException("Unknown operation=" + op); + } + + final FontConfig.FontFamily family = FontListParser.readFamily( + parser, fontDir, updatableFontMap, false); + + // For ignoring the customization, consume the new-locale-family element but don't + // register any customizations. + if (com.android.text.flags.Flags.vendorCustomLocaleFallback()) { + outCustomization.add(new FontConfig.Customization.LocaleFallback( + Locale.forLanguageTag(lang), intOp, family)); + } } else { throw new IllegalArgumentException("Unknown customizationType=" + customizationType); } diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java index bf79b1bedd8e..685fd825d43e 100644 --- a/graphics/java/android/graphics/fonts/FontFamily.java +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -16,6 +16,12 @@ package android.graphics.fonts; +import static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -29,7 +35,9 @@ import dalvik.annotation.optimization.FastNative; import libcore.util.NativeAllocationRegistry; +import java.lang.annotation.Retention; import java.util.ArrayList; +import java.util.Set; /** * A font family class can be used for creating Typeface. @@ -58,6 +66,7 @@ import java.util.ArrayList; * */ public final class FontFamily { + private static final String TAG = "FontFamily"; /** @@ -73,6 +82,7 @@ public final class FontFamily { // initial capacity. private final SparseIntArray mStyles = new SparseIntArray(4); + /** * Constructs a builder. * @@ -110,23 +120,64 @@ public final class FontFamily { } /** + * Build a variable font family that automatically adjust the `wght` and `ital` axes value + * for the requested weight/italic style values. + * + * To build a variable font family, added fonts must meet one of following conditions. + * + * If two font files are added, both font files must support `wght` axis and one font must + * support {@link FontStyle#FONT_SLANT_UPRIGHT} and another font must support + * {@link FontStyle#FONT_SLANT_ITALIC}. If the requested weight value is lower than minimum + * value of the supported `wght` axis, the minimum supported `wght` value is used. If the + * requested weight value is larger than maximum value of the supported `wght` axis, the + * maximum supported `wght` value is used. The weight values of the fonts are ignored. + * + * If one font file is added, that font must support the `wght` axis. If that font support + * `ital` axis, that `ital` value is set to 1 when the italic style is requested. If that + * font doesn't support `ital` axis, synthetic italic may be used. If the requested + * weight value is lower than minimum value of the supported `wght` axis, the minimum + * supported `wght` value is used. If the requested weight value is larger than maximum + * value of the supported `wght`axis, the maximum supported `wght` value is used. The weight + * value of the font is ignored. + * + * If none of the above conditions are met, this function return {@code null}. + * + * @return A variable font family. null if a variable font cannot be built from the given + * fonts. + */ + @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) + public @Nullable FontFamily buildVariableFamily() { + int variableFamilyType = analyzeAndResolveVariableType(mFonts); + if (variableFamilyType == VARIABLE_FONT_FAMILY_TYPE_UNKNOWN) { + return null; + } + return build("", FontConfig.FontFamily.VARIANT_DEFAULT, + true /* isCustomFallback */, + false /* isDefaultFallback */, + variableFamilyType); + } + + /** * Build the font family * @return a font family */ public @NonNull FontFamily build() { - return build("", FontConfig.FontFamily.VARIANT_DEFAULT, true /* isCustomFallback */, - false /* isDefaultFallback */); + return build("", FontConfig.FontFamily.VARIANT_DEFAULT, + true /* isCustomFallback */, + false /* isDefaultFallback */, + VARIABLE_FONT_FAMILY_TYPE_NONE); } /** @hide */ public @NonNull FontFamily build(@NonNull String langTags, int variant, - boolean isCustomFallback, boolean isDefaultFallback) { + boolean isCustomFallback, boolean isDefaultFallback, int variableFamilyType) { + final long builderPtr = nInitBuilder(); for (int i = 0; i < mFonts.size(); ++i) { nAddFont(builderPtr, mFonts.get(i).getNativePtr()); } final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback, - isDefaultFallback); + isDefaultFallback, variableFamilyType); final FontFamily family = new FontFamily(ptr); sFamilyRegistory.registerNativeAllocation(family, ptr); return family; @@ -136,11 +187,123 @@ public final class FontFamily { return font.getStyle().getWeight() | (font.getStyle().getSlant() << 16); } + /** + * A special variable font family type that indicates `analyzeAndResolveVariableType` could + * not be identified the variable font family type. + * + * @see #buildVariableFamily() + * @hide + */ + public static final int VARIABLE_FONT_FAMILY_TYPE_UNKNOWN = -1; + + /** + * A variable font family type that indicates no variable font family can be used. + * + * The font family is used as bundle of static fonts. + * @see #buildVariableFamily() + * @hide + */ + public static final int VARIABLE_FONT_FAMILY_TYPE_NONE = 0; + /** + * A variable font family type that indicates single font file can be used for multiple + * weight. For the italic style, fake italic may be applied. + * + * @see #buildVariableFamily() + * @hide + */ + public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY = 1; + /** + * A variable font family type that indicates single font file can be used for multiple + * weight and italic. + * + * @see #buildVariableFamily() + * @hide + */ + public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL = 2; + /** + * A variable font family type that indicates two font files are included in the family: + * one can be used for upright with various weights, the other one can be used for italic + * with various weights. + * + * @see #buildVariableFamily() + * @hide + */ + public static final int VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT = 3; + + /** @hide */ + @Retention(SOURCE) + @IntDef(prefix = { "VARIABLE_FONT_FAMILY_TYPE_" }, value = { + VARIABLE_FONT_FAMILY_TYPE_UNKNOWN, + VARIABLE_FONT_FAMILY_TYPE_NONE, + VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY, + VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL, + VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT + }) + public @interface VariableFontFamilyType {} + + /** + * The registered italic axis used for adjusting requested style. + * https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_ital + */ + private static final int TAG_ital = 0x6974616C; // i(0x69), t(0x74), a(0x61), l(0x6c) + + /** + * The registered weight axis used for adjusting requested style. + * https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_wght + */ + private static final int TAG_wght = 0x77676874; // w(0x77), g(0x67), h(0x68), t(0x74) + + /** @hide */ + public static @VariableFontFamilyType int analyzeAndResolveVariableType( + ArrayList<Font> fonts) { + if (fonts.size() > 2) { + return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN; + } + + if (fonts.size() == 1) { + Font font = fonts.get(0); + Set<Integer> supportedAxes = + FontFileUtil.getSupportedAxes(font.getBuffer(), font.getTtcIndex()); + if (supportedAxes.contains(TAG_wght)) { + if (supportedAxes.contains(TAG_ital)) { + return VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL; + } else { + return VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY; + } + } else { + return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN; + } + } else { + for (int i = 0; i < fonts.size(); ++i) { + Font font = fonts.get(i); + Set<Integer> supportedAxes = + FontFileUtil.getSupportedAxes(font.getBuffer(), font.getTtcIndex()); + if (!supportedAxes.contains(TAG_wght)) { + return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN; + } + } + boolean italic1 = fonts.get(0).getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC; + boolean italic2 = fonts.get(1).getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC; + + if (italic1 == italic2) { + return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN; + } else { + if (italic1) { + // Swap fonts to make the first font upright, second font italic. + Font firstFont = fonts.get(0); + fonts.set(0, fonts.get(1)); + fonts.set(1, firstFont); + } + return VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT; + } + } + } + private static native long nInitBuilder(); @CriticalNative private static native void nAddFont(long builderPtr, long fontPtr); private static native long nBuild(long builderPtr, String langTags, int variant, - boolean isCustomFallback, boolean isDefaultFallback); + boolean isCustomFallback, boolean isDefaultFallback, int variableFamilyType); @CriticalNative private static native long nGetReleaseNativeFamily(); } diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java index 917eef2ffede..ff38282255f2 100644 --- a/graphics/java/android/graphics/fonts/FontFileUtil.java +++ b/graphics/java/android/graphics/fonts/FontFileUtil.java @@ -19,11 +19,14 @@ package android.graphics.fonts; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.util.ArraySet; import dalvik.annotation.optimization.FastNative; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Collections; +import java.util.Set; /** * Provides a utility for font file operations. @@ -62,6 +65,7 @@ public class FontFileUtil { private static final int SFNT_VERSION_OTTO = 0x4F54544F; private static final int TTC_TAG = 0x74746366; private static final int OS2_TABLE_TAG = 0x4F532F32; + private static final int FVAR_TABLE_TAG = 0x66766172; private static final int ANALYZE_ERROR = 0xFFFFFFFF; @@ -200,6 +204,73 @@ public class FontFileUtil { } } + private static int getUInt16(ByteBuffer buffer, int offset) { + return ((int) buffer.getShort(offset)) & 0xFFFF; + } + + /** + * Returns supported axes of font + * + * @param buffer A buffer of the entire font file. + * @param index A font index in case of font collection. Must be 0 otherwise. + * @return set of supported axes tag. Returns empty set on error. + */ + public static Set<Integer> getSupportedAxes(@NonNull ByteBuffer buffer, int index) { + ByteOrder originalOrder = buffer.order(); + buffer.order(ByteOrder.BIG_ENDIAN); + try { + int fontFileOffset = 0; + int magicNumber = buffer.getInt(0); + if (magicNumber == TTC_TAG) { + // TTC file. + if (index >= buffer.getInt(8 /* offset to number of fonts in TTC */)) { + return Collections.EMPTY_SET; + } + fontFileOffset = buffer.getInt( + 12 /* offset to array of offsets of font files */ + 4 * index); + } + int sfntVersion = buffer.getInt(fontFileOffset); + + if (sfntVersion != SFNT_VERSION_1 && sfntVersion != SFNT_VERSION_OTTO) { + return Collections.EMPTY_SET; + } + + int numTables = buffer.getShort(fontFileOffset + 4 /* offset to number of tables */); + int fvarTableOffset = -1; + for (int i = 0; i < numTables; ++i) { + int tableOffset = fontFileOffset + 12 /* size of offset table */ + + i * 16 /* size of table record */; + if (buffer.getInt(tableOffset) == FVAR_TABLE_TAG) { + fvarTableOffset = buffer.getInt(tableOffset + 8 /* offset to the table */); + break; + } + } + + if (fvarTableOffset == -1) { + // Couldn't find OS/2 table. use regular style + return Collections.EMPTY_SET; + } + + if (buffer.getShort(fvarTableOffset) != 1 + || buffer.getShort(fvarTableOffset + 2) != 0) { + return Collections.EMPTY_SET; + } + + int axesArrayOffset = getUInt16(buffer, fvarTableOffset + 4); + int axisCount = getUInt16(buffer, fvarTableOffset + 8); + int axisSize = getUInt16(buffer, fvarTableOffset + 10); + + ArraySet<Integer> axes = new ArraySet<>(); + for (int i = 0; i < axisCount; ++i) { + axes.add(buffer.getInt(fvarTableOffset + axesArrayOffset + axisSize * i)); + } + + return axes; + } finally { + buffer.order(originalOrder); + } + } + @FastNative private static native long nGetFontRevision(@NonNull ByteBuffer buffer, @IntRange(from = 0) int index); diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index acc4da6e1527..3ef714ed8bdf 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -16,10 +16,15 @@ package android.graphics.fonts; +import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_APPEND; +import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_PREPEND; +import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_REPLACE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.FontListParser; import android.graphics.Typeface; +import android.os.LocaleList; import android.text.FontConfig; import android.util.ArrayMap; import android.util.Log; @@ -38,6 +43,7 @@ import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -48,6 +54,8 @@ public final class SystemFonts { private static final String TAG = "SystemFonts"; private static final String FONTS_XML = "/system/etc/font_fallback.xml"; + private static final String LEGACY_FONTS_XML = "/system/etc/fonts.xml"; + /** @hide */ public static final String SYSTEM_FONT_DIR = "/system/fonts/"; private static final String OEM_XML = "/product/etc/fonts_customization.xml"; @@ -117,9 +125,9 @@ public final class SystemFonts { } } - final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily( - defaultFonts, languageTags, variant, false, cache); + defaultFonts, languageTags, variant, xmlFamily.getVariableFontFamilyType(), false, + cache); // Insert family into fallback map. for (int i = 0; i < fallbackMap.size(); i++) { final String name = fallbackMap.keyAt(i); @@ -136,8 +144,8 @@ public final class SystemFonts { familyListSet.familyList.add(defaultFamily); } } else { - final FontFamily family = createFontFamily(fallback, languageTags, variant, false, - cache); + final FontFamily family = createFontFamily(fallback, languageTags, variant, + xmlFamily.getVariableFontFamilyType(), false, cache); if (family != null) { familyListSet.familyList.add(family); } else if (defaultFamily != null) { @@ -153,6 +161,7 @@ public final class SystemFonts { @NonNull List<FontConfig.Font> fonts, @NonNull String languageTags, @FontConfig.FontFamily.Variant int variant, + int varFamilyType, boolean isDefaultFallback, @NonNull Map<String, ByteBuffer> cache) { if (fonts.size() == 0) { @@ -194,7 +203,7 @@ public final class SystemFonts { } } return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */, - isDefaultFallback); + isDefaultFallback, varFamilyType); } private static void appendNamedFamilyList(@NonNull FontConfig.NamedFamilyList namedFamilyList, @@ -208,6 +217,7 @@ public final class SystemFonts { final FontFamily family = createFontFamily( xmlFamily.getFontList(), xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(), + xmlFamily.getVariableFontFamilyType(), true, // named family is always default bufferCache); if (family == null) { @@ -220,15 +230,23 @@ public final class SystemFonts { } /** - * Get the updated FontConfig for testing purposes. + * Get the updated FontConfig. + * + * @param updatableFontMap a font mapping of updated font files. * @hide */ - public static @NonNull FontConfig getSystemFontConfigForTesting( - @NonNull String fontsXmlPath, + public static @NonNull FontConfig getSystemFontConfig( @Nullable Map<String, File> updatableFontMap, long lastModifiedDate, - int configVersion) { - return getSystemFontConfigInternal(fontsXmlPath, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, + int configVersion + ) { + final String fontsXml; + if (com.android.text.flags.Flags.newFontsFallbackXml()) { + fontsXml = FONTS_XML; + } else { + fontsXml = LEGACY_FONTS_XML; + } + return getSystemFontConfigInternal(fontsXml, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, updatableFontMap, lastModifiedDate, configVersion); } @@ -238,12 +256,13 @@ public final class SystemFonts { * @param updatableFontMap a font mapping of updated font files. * @hide */ - public static @NonNull FontConfig getSystemFontConfig( + public static @NonNull FontConfig getSystemFontConfigForTesting( + @NonNull String fontsXml, @Nullable Map<String, File> updatableFontMap, long lastModifiedDate, int configVersion ) { - return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, + return getSystemFontConfigInternal(fontsXml, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, updatableFontMap, lastModifiedDate, configVersion); } @@ -252,10 +271,24 @@ public final class SystemFonts { * @hide */ public static @NonNull FontConfig getSystemPreinstalledFontConfig() { - return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null, + final String fontsXml; + if (com.android.text.flags.Flags.newFontsFallbackXml()) { + fontsXml = FONTS_XML; + } else { + fontsXml = LEGACY_FONTS_XML; + } + return getSystemFontConfigInternal(fontsXml, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null, 0, 0); } + /** + * @hide + */ + public static @NonNull FontConfig getSystemPreinstalledFontConfigFromLegacyXml() { + return getSystemFontConfigInternal(LEGACY_FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, + null, 0, 0); + } + /* package */ static @NonNull FontConfig getSystemFontConfigInternal( @NonNull String fontsXml, @NonNull String systemFontDir, @@ -266,16 +299,17 @@ public final class SystemFonts { int configVersion ) { try { + Log.i(TAG, "Loading font config from " + fontsXml); return FontListParser.parse(fontsXml, systemFontDir, oemXml, productFontDir, updatableFontMap, lastModifiedDate, configVersion); } catch (IOException e) { Log.e(TAG, "Failed to open/read system font configurations.", e); return new FontConfig(Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), 0, 0); + Collections.emptyList(), Collections.emptyList(), 0, 0); } catch (XmlPullParserException e) { Log.e(TAG, "Failed to parse the system font configuration.", e); return new FontConfig(Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), 0, 0); + Collections.emptyList(), Collections.emptyList(), 0, 0); } } @@ -299,6 +333,8 @@ public final class SystemFonts { ArrayMap<String, ByteBuffer> outBufferCache) { final ArrayMap<String, NativeFamilyListSet> fallbackListMap = new ArrayMap<>(); + final List<FontConfig.Customization.LocaleFallback> localeFallbacks = + fontConfig.getLocaleFallbackCustomizations(); final List<FontConfig.NamedFamilyList> namedFamilies = fontConfig.getNamedFamilyLists(); for (int i = 0; i < namedFamilies.size(); ++i) { @@ -307,10 +343,54 @@ public final class SystemFonts { } // Then, add fallback fonts to the fallback map. + final List<FontConfig.Customization.LocaleFallback> customizations = new ArrayList<>(); final List<FontConfig.FontFamily> xmlFamilies = fontConfig.getFontFamilies(); + final SparseIntArray seenCustomization = new SparseIntArray(); for (int i = 0; i < xmlFamilies.size(); i++) { final FontConfig.FontFamily xmlFamily = xmlFamilies.get(i); - pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache); + + customizations.clear(); + for (int j = 0; j < localeFallbacks.size(); ++j) { + if (seenCustomization.get(j, -1) != -1) { + continue; // The customization is already applied. + } + FontConfig.Customization.LocaleFallback localeFallback = localeFallbacks.get(j); + if (scriptMatch(xmlFamily.getLocaleList(), localeFallback.getScript())) { + customizations.add(localeFallback); + seenCustomization.put(j, 1); + } + } + + if (customizations.isEmpty()) { + pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache); + } else { + for (int j = 0; j < customizations.size(); ++j) { + FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j); + if (localeFallback.getOperation() == OPERATION_PREPEND) { + pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap, + outBufferCache); + } + } + boolean isReplaced = false; + for (int j = 0; j < customizations.size(); ++j) { + FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j); + if (localeFallback.getOperation() == OPERATION_REPLACE) { + pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap, + outBufferCache); + isReplaced = true; + } + } + if (!isReplaced) { // If nothing is replaced, push the original one. + pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache); + } + for (int j = 0; j < customizations.size(); ++j) { + FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j); + if (localeFallback.getOperation() == OPERATION_APPEND) { + pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap, + outBufferCache); + } + } + } } // Build the font map and fallback map. @@ -336,4 +416,42 @@ public final class SystemFonts { Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result); return result; } + + private static boolean scriptMatch(LocaleList localeList, String targetScript) { + if (localeList == null || localeList.isEmpty()) { + return false; + } + for (int i = 0; i < localeList.size(); ++i) { + Locale locale = localeList.get(i); + if (locale == null) { + continue; + } + String baseScript = FontConfig.resolveScript(locale); + if (baseScript.equals(targetScript)) { + return true; + } + + // Subtag match + if (targetScript.equals("Bopo") && baseScript.equals("Hanb")) { + // Hanb is Han with Bopomofo. + return true; + } else if (targetScript.equals("Hani")) { + if (baseScript.equals("Hanb") || baseScript.equals("Hans") + || baseScript.equals("Hant") || baseScript.equals("Kore") + || baseScript.equals("Jpan")) { + // Han id suppoted by Taiwanese, Traditional Chinese, Simplified Chinese, Korean + // and Japanese. + return true; + } + } else if (targetScript.equals("Hira") || targetScript.equals("Hrkt") + || targetScript.equals("Kana")) { + if (baseScript.equals("Jpan") || baseScript.equals("Hrkt")) { + // Hiragana, Hiragana-Katakana, Katakana is supported by Japanese and + // Hiragana-Katakana script. + return true; + } + } + } + return false; + } } diff --git a/graphics/java/android/graphics/fonts/TEST_MAPPING b/graphics/java/android/graphics/fonts/TEST_MAPPING new file mode 100644 index 000000000000..99cbfe720c05 --- /dev/null +++ b/graphics/java/android/graphics/fonts/TEST_MAPPING @@ -0,0 +1,15 @@ +{ + "presubmit": [ + { + "name": "CtsTextTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "androidx.test.filters.LargeTest" + } + ] + } + ] +} diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index 0c493f5a1fdc..c5e451a5ec3a 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -16,8 +16,20 @@ package android.graphics.text; +import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN; +import static com.android.text.flags.Flags.FLAG_WORD_STYLE_AUTO; + +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; +import android.os.Build; +import android.os.LocaleList; +import android.os.Parcel; +import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -30,7 +42,100 @@ import java.util.Objects; * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external"> * line-break property</a> for more information. */ -public final class LineBreakConfig { +public final class LineBreakConfig implements Parcelable { + + /** + * A feature ID for automatic line break word style. + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + public static final long WORD_STYLE_AUTO = 280005585L; + + /** + * No hyphenation preference is specified. + * + * <p> + * This is a special value of hyphenation preference indicating no hyphenation preference is + * specified. When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} + * with {@link Builder#merge(LineBreakConfig)} function, the hyphenation preference of + * overridden config will be kept if the hyphenation preference of overriding config is + * {@link #HYPHENATION_UNSPECIFIED}. + * + * <p> + * <pre> + * val override = LineBreakConfig.Builder() + * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) + * .build(); // UNSPECIFIED if no setHyphenation is called. + * val config = LineBreakConfig.Builder() + * .setHyphenation(LineBreakConfig.HYPHENATION_DISABLED) + * .merge(override) + * .build() + * // Here, config has HYPHENATION_DISABLED for line break config and + * // LINE_BREAK_WORD_STYLE_PHRASE for line break word style. + * </pre> + * + * <p> + * This value is resolved to {@link #HYPHENATION_ENABLED} if this value is used for text + * layout/rendering. + */ + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + public static final int HYPHENATION_UNSPECIFIED = -1; + + /** + * The hyphenation is disabled. + */ + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + public static final int HYPHENATION_DISABLED = 0; + + /** + * The hyphenation is enabled. + * + * Note: Even if the hyphenation is enabled with a line break strategy + * {@link LineBreaker#BREAK_STRATEGY_SIMPLE}, the hyphenation will not be performed unless a + * single word cannot meet width constraints. + */ + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + public static final int HYPHENATION_ENABLED = 1; + + /** @hide */ + @IntDef(prefix = { "HYPHENATION_" }, value = { + HYPHENATION_UNSPECIFIED, HYPHENATION_ENABLED, HYPHENATION_DISABLED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Hyphenation {} + + /** + * No line break style is specified. + * + * <p> + * This is a special value of line break style indicating no style value is specified. + * When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} with + * {@link Builder#merge(LineBreakConfig)} function, the line break style of overridden config + * will be kept if the line break style of overriding config is + * {@link #LINE_BREAK_STYLE_UNSPECIFIED}. + * + * <pre> + * val override = LineBreakConfig.Builder() + * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) + * .build(); // UNSPECIFIED if no setLineBreakStyle is called. + * val config = LineBreakConfig.Builder() + * .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT) + * .merge(override) + * .build() + * // Here, config has LINE_BREAK_STYLE_STRICT for line break config and + * // LINE_BREAK_WORD_STYLE_PHRASE for line break word style. + * </pre> + * + * <p> + * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if the target SDK version is API + * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or before and this value is used for text + * layout/rendering. This value is resolved to {@link #LINE_BREAK_STYLE_AUTO} if the target SDK + * version is API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or after and this value is + * used for text layout/rendering. + */ + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1; /** * No line-break rules are used for line breaking. @@ -53,15 +158,79 @@ public final class LineBreakConfig { */ public static final int LINE_BREAK_STYLE_STRICT = 3; + /** + * The line break style that used for preventing automatic line breaking. + * + * This is useful when you want to preserve some words in the same line by using + * {@link android.text.style.LineBreakConfigSpan} or + * {@link android.text.style.LineBreakConfigSpan#createNoBreakSpan()} as a shorthand. + * Note that even if this style is specified, the grapheme based line break is still performed + * for preventing clipping text. + * + * @see android.text.style.LineBreakConfigSpan + * @see android.text.style.LineBreakConfigSpan#createNoBreakSpan() + */ + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + public static final int LINE_BREAK_STYLE_NO_BREAK = 4; + + /** + * A special value for the line breaking style option. + * + * <p> + * The auto option for the line break style set the line break style based on the locale of the + * text rendering context. You can specify the context locale by + * {@link android.widget.TextView#setTextLocales(LocaleList)} or + * {@link android.graphics.Paint#setTextLocales(LocaleList)}. + * + * <p> + * In the API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, auto option does followings: + * - If at least one locale in the locale list contains Japanese script, this option is + * equivalent to {@link #LINE_BREAK_STYLE_STRICT}. + * - Otherwise, this option is equivalent to {@link #LINE_BREAK_STYLE_NONE}. + */ + @FlaggedApi(FLAG_WORD_STYLE_AUTO) + public static final int LINE_BREAK_STYLE_AUTO = 5; + /** @hide */ @IntDef(prefix = { "LINE_BREAK_STYLE_" }, value = { LINE_BREAK_STYLE_NONE, LINE_BREAK_STYLE_LOOSE, LINE_BREAK_STYLE_NORMAL, - LINE_BREAK_STYLE_STRICT + LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED, LINE_BREAK_STYLE_NO_BREAK, + LINE_BREAK_STYLE_AUTO }) @Retention(RetentionPolicy.SOURCE) public @interface LineBreakStyle {} /** + * No line break word style is specified. + * + * This is a special value of line break word style indicating no style value is specified. + * When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} with + * {@link Builder#merge(LineBreakConfig)} function, the line break word style of overridden + * config will be kept if the line break word style of overriding config is + * {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}. + * + * <pre> + * val override = LineBreakConfig.Builder() + * .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT) + * .build(); // UNSPECIFIED if no setLineBreakWordStyle is called. + * val config = LineBreakConfig.Builder() + * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) + * .merge(override) + * .build() + * // Here, config has LINE_BREAK_STYLE_STRICT for line break config and + * // LINE_BREAK_WORD_STYLE_PHRASE for line break word style. + * </pre> + * + * This value is resolved to {@link #LINE_BREAK_WORD_STYLE_NONE} if the target SDK version is + * API {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or before and this value is used for text + * layout/rendering. This value is resolved to {@link #LINE_BREAK_WORD_STYLE_AUTO} if the target + * SDK version is API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or after and this value is + * used for text layout/rendering. + */ + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1; + + /** * No line-break word style is used for line breaking. */ public static final int LINE_BREAK_WORD_STYLE_NONE = 0; @@ -76,9 +245,29 @@ public final class LineBreakConfig { */ public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; + /** + * A special value for the line breaking word style option. + * + * <p> + * The auto option for the line break word style does some heuristics based on locales and line + * count. + * + * <p> + * In the API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, auto option does followings: + * - If at least one locale in the locale list contains Korean script, this option is equivalent + * to {@link #LINE_BREAK_WORD_STYLE_PHRASE}. + * - If not, then if at least one locale in the locale list contains Japanese script, this + * option is equivalent to {@link #LINE_BREAK_WORD_STYLE_PHRASE} if the result of its line + * count is less than 5 lines. + * - Otherwise, this option is equivalent to {@link #LINE_BREAK_WORD_STYLE_NONE}. + */ + @FlaggedApi(FLAG_WORD_STYLE_AUTO) + public static final int LINE_BREAK_WORD_STYLE_AUTO = 2; + /** @hide */ @IntDef(prefix = { "LINE_BREAK_WORD_STYLE_" }, value = { - LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE + LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE, LINE_BREAK_WORD_STYLE_UNSPECIFIED, + LINE_BREAK_WORD_STYLE_AUTO }) @Retention(RetentionPolicy.SOURCE) public @interface LineBreakWordStyle {} @@ -88,21 +277,95 @@ public final class LineBreakConfig { */ public static final class Builder { // The line break style for the LineBreakConfig. - private @LineBreakStyle int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE; + private @LineBreakStyle int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_UNSPECIFIED; // The line break word style for the LineBreakConfig. private @LineBreakWordStyle int mLineBreakWordStyle = - LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; + LineBreakConfig.LINE_BREAK_WORD_STYLE_UNSPECIFIED; + + private @Hyphenation int mHyphenation = LineBreakConfig.HYPHENATION_UNSPECIFIED; /** * Builder constructor. */ public Builder() { + reset(null); + } + + /** + * Merges line break config with other config + * + * Update the internal configurations with passed {@code config}. If the config values of + * passed {@code config} are unspecified, the original config values are kept. For example, + * the following code passes {@code config} that has {@link #LINE_BREAK_STYLE_UNSPECIFIED}. + * This code generates {@link LineBreakConfig} that has line break config + * {@link #LINE_BREAK_STYLE_STRICT}. + * + * <pre> + * val override = LineBreakConfig.Builder() + * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) + * .build(); // UNSPECIFIED if no setLineBreakStyle is called. + * val config = LineBreakConfig.Builder() + * .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT) + * .merge(override) + * .build() + * // Here, config has LINE_BREAK_STYLE_STRICT of line break config and + * // LINE_BREAK_WORD_STYLE_PHRASE of line break word style. + * </pre> + * + * @see #LINE_BREAK_STYLE_UNSPECIFIED + * @see #LINE_BREAK_WORD_STYLE_UNSPECIFIED + * + * @param config an override line break config + * @return This {@code Builder}. + */ + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + public @NonNull Builder merge(@NonNull LineBreakConfig config) { + if (config.mLineBreakStyle != LINE_BREAK_STYLE_UNSPECIFIED) { + mLineBreakStyle = config.mLineBreakStyle; + } + if (config.mLineBreakWordStyle != LINE_BREAK_WORD_STYLE_UNSPECIFIED) { + mLineBreakWordStyle = config.mLineBreakWordStyle; + } + if (config.mHyphenation != HYPHENATION_UNSPECIFIED) { + mHyphenation = config.mHyphenation; + } + return this; + } + + /** + * Resets this builder to the given config state. + * + * @param config a config value used for resetting. {@code null} is allowed. If {@code null} + * is passed, all configs are reset to unspecified. + * @return This {@code Builder}. + * @hide + */ + public @NonNull Builder reset(@Nullable LineBreakConfig config) { + if (config == null) { + mLineBreakStyle = LINE_BREAK_STYLE_UNSPECIFIED; + mLineBreakWordStyle = LINE_BREAK_WORD_STYLE_UNSPECIFIED; + mHyphenation = HYPHENATION_UNSPECIFIED; + } else { + mLineBreakStyle = config.mLineBreakStyle; + mLineBreakWordStyle = config.mLineBreakWordStyle; + mHyphenation = config.mHyphenation; + } + return this; } /** * Sets the line-break style. * + * Note: different from {@link #merge(LineBreakConfig)} if this function is called with + * {@link #LINE_BREAK_STYLE_UNSPECIFIED}, the line break style is reset to + * {@link #LINE_BREAK_STYLE_UNSPECIFIED}. + * + * @see <a href="https://unicode.org/reports/tr35/#UnicodeLineBreakStyleIdentifier"> + * Unicode Line Break Style Identifier</a> + * @see <a href="https://drafts.csswg.org/css-text/#line-break-property"> + * CSS Line Break Property</a> + * * @param lineBreakStyle The new line-break style. * @return This {@code Builder}. */ @@ -114,6 +377,15 @@ public final class LineBreakConfig { /** * Sets the line-break word style. * + * Note: different from {@link #merge(LineBreakConfig)} method, if this function is called + * with {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}, the line break style is reset to + * {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}. + * + * @see <a href="https://unicode.org/reports/tr35/#UnicodeLineBreakWordIdentifier"> + * Unicode Line Break Word Identifier</a> + * @see <a href="https://drafts.csswg.org/css-text/#word-break-property"> + * CSS Word Break Property</a> + * * @param lineBreakWordStyle The new line-break word style. * @return This {@code Builder}. */ @@ -123,12 +395,36 @@ public final class LineBreakConfig { } /** + * Sets the hyphenation preference + * + * Note: Even if the {@link LineBreakConfig#HYPHENATION_ENABLED} is specified, the + * hyphenation will not be performed if the {@link android.widget.TextView} or underlying + * {@link android.text.StaticLayout}, {@link LineBreaker} are configured with + * {@link LineBreaker#HYPHENATION_FREQUENCY_NONE}. + * + * Note: Even if the hyphenation is enabled with a line break strategy + * {@link LineBreaker#BREAK_STRATEGY_SIMPLE}, the hyphenation will not be performed unless a + * single word cannot meet width constraints. + * + * @param hyphenation The hyphenation preference. + * @return This {@code Builder}. + */ + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + public @NonNull Builder setHyphenation(@Hyphenation int hyphenation) { + mHyphenation = hyphenation; + return this; + } + + /** * Builds a {@link LineBreakConfig} instance. * + * This method can be called multiple times for generating multiple {@link LineBreakConfig} + * instances. + * * @return The {@code LineBreakConfig} instance. */ public @NonNull LineBreakConfig build() { - return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle); + return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mHyphenation); } } @@ -156,17 +452,21 @@ public final class LineBreakConfig { private final @LineBreakStyle int mLineBreakStyle; private final @LineBreakWordStyle int mLineBreakWordStyle; + private final @Hyphenation int mHyphenation; /** * Constructor with line-break parameters. * * <p>Use {@link LineBreakConfig.Builder} to create the * {@code LineBreakConfig} instance. + * @hide */ - private LineBreakConfig(@LineBreakStyle int lineBreakStyle, - @LineBreakWordStyle int lineBreakWordStyle) { + public LineBreakConfig(@LineBreakStyle int lineBreakStyle, + @LineBreakWordStyle int lineBreakWordStyle, + @Hyphenation int hyphenation) { mLineBreakStyle = lineBreakStyle; mLineBreakWordStyle = lineBreakWordStyle; + mHyphenation = hyphenation; } /** @@ -179,6 +479,24 @@ public final class LineBreakConfig { } /** + * Gets the resolved line break style. + * + * This method never returns {@link #LINE_BREAK_STYLE_UNSPECIFIED}. + * + * @return The line break style. + * @hide + */ + public static @LineBreakStyle int getResolvedLineBreakStyle(@Nullable LineBreakConfig config) { + final int defaultStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO) + ? LINE_BREAK_STYLE_AUTO : LINE_BREAK_STYLE_NONE; + if (config == null) { + return defaultStyle; + } + return config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED + ? defaultStyle : config.mLineBreakStyle; + } + + /** * Gets the current line-break word style. * * @return The line-break word style to be used for text wrapping. @@ -187,6 +505,86 @@ public final class LineBreakConfig { return mLineBreakWordStyle; } + /** + * Gets the resolved line break style. + * + * This method never returns {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}. + * + * @return The line break word style. + * @hide + */ + public static @LineBreakWordStyle int getResolvedLineBreakWordStyle( + @Nullable LineBreakConfig config) { + final int defaultWordStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO) + ? LINE_BREAK_WORD_STYLE_AUTO : LINE_BREAK_WORD_STYLE_NONE; + if (config == null) { + return defaultWordStyle; + } + return config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED + ? defaultWordStyle : config.mLineBreakWordStyle; + } + + /** + * Returns a hyphenation preference. + * + * @return A hyphenation preference. + */ + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + public @Hyphenation int getHyphenation() { + return mHyphenation; + } + + /** + * Returns a hyphenation preference. + * + * This method never returns {@link #HYPHENATION_UNSPECIFIED}. + * + * @return A hyphenation preference. + * @hide + */ + public static @Hyphenation int getResolvedHyphenation( + @Nullable LineBreakConfig config) { + if (config == null) { + return HYPHENATION_ENABLED; + } + return config.mHyphenation == HYPHENATION_UNSPECIFIED + ? HYPHENATION_ENABLED : config.mHyphenation; + } + + + /** + * Generates a new {@link LineBreakConfig} instance merged with given {@code config}. + * + * If values of passing {@code config} are unspecified, the original values are kept. For + * example, the following code shows how line break config is merged. + * + * <pre> + * val override = LineBreakConfig.Builder() + * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) + * .build(); // UNSPECIFIED if no setLineBreakStyle is called. + * val config = LineBreakConfig.Builder() + * .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT) + * .build(); + * + * val newConfig = config.merge(override) + * // newConfig has LINE_BREAK_STYLE_STRICT of line break style and + * LINE_BREAK_WORD_STYLE_PHRASE of line break word style. + * </pre> + * + * @param config an overriding config. + * @return newly created instance that is current style merged with passed config. + */ + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + public @NonNull LineBreakConfig merge(@NonNull LineBreakConfig config) { + return new LineBreakConfig( + config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED + ? mLineBreakStyle : config.mLineBreakStyle, + config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED + ? mLineBreakWordStyle : config.mLineBreakWordStyle, + config.mHyphenation == HYPHENATION_UNSPECIFIED + ? mHyphenation : config.mHyphenation); + } + @Override public boolean equals(Object o) { if (o == null) return false; @@ -194,11 +592,52 @@ public final class LineBreakConfig { if (!(o instanceof LineBreakConfig)) return false; LineBreakConfig that = (LineBreakConfig) o; return (mLineBreakStyle == that.mLineBreakStyle) - && (mLineBreakWordStyle == that.mLineBreakWordStyle); + && (mLineBreakWordStyle == that.mLineBreakWordStyle) + && (mHyphenation == that.mHyphenation); } @Override public int hashCode() { - return Objects.hash(mLineBreakStyle, mLineBreakWordStyle); + return Objects.hash(mLineBreakStyle, mLineBreakWordStyle, mHyphenation); + } + + @Override + public String toString() { + return "LineBreakConfig{" + + "mLineBreakStyle=" + mLineBreakStyle + + ", mLineBreakWordStyle=" + mLineBreakWordStyle + + ", mHyphenation= " + mHyphenation + + '}'; + } + + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + @Override + public int describeContents() { + return 0; } + + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mLineBreakStyle); + dest.writeInt(mLineBreakWordStyle); + dest.writeInt(mHyphenation); + } + + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + public static final @NonNull Creator<LineBreakConfig> CREATOR = new Creator<>() { + + @Override + public LineBreakConfig createFromParcel(Parcel source) { + final int lineBreakStyle = source.readInt(); + final int lineBreakWordStyle = source.readInt(); + final int hyphenation = source.readInt(); + return new LineBreakConfig(lineBreakStyle, lineBreakWordStyle, hyphenation); + } + + @Override + public LineBreakConfig[] newArray(int size) { + return new LineBreakConfig[size]; + } + }; } diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java index babcfc3815f4..0e3fb163ef75 100644 --- a/graphics/java/android/graphics/text/LineBreaker.java +++ b/graphics/java/android/graphics/text/LineBreaker.java @@ -16,12 +16,16 @@ package android.graphics.text; +import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH; + +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Px; +import android.text.Layout; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; @@ -182,6 +186,7 @@ public class LineBreaker { private @HyphenationFrequency int mHyphenationFrequency = HYPHENATION_FREQUENCY_NONE; private @JustificationMode int mJustificationMode = JUSTIFICATION_MODE_NONE; private @Nullable int[] mIndents = null; + private boolean mUseBoundsForWidth = false; /** * Set break strategy. @@ -231,13 +236,35 @@ public class LineBreaker { } /** + * Set true for using width of bounding box as a source of automatic line breaking. + * + * If this value is false, the automatic line breaking uses total amount of advances as text + * widths. By setting true, it uses joined all glyph bound's width as a width of the text. + * + * If the font has glyphs that have negative bearing X or its xMax is greater than advance, + * the glyph clipping can happen because the drawing area may be bigger. By setting this to + * true, the line breaker will break line based on bounding box, so clipping can be + * prevented. + * + * @param useBoundsForWidth True for using bounding box, false for advances. + * @return this builder instance + * @see Layout#getUseBoundsForWidth() + * @see android.text.StaticLayout.Builder#setUseBoundsForWidth(boolean) + */ + @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) + public @NonNull Builder setUseBoundsForWidth(boolean useBoundsForWidth) { + mUseBoundsForWidth = useBoundsForWidth; + return this; + } + + /** * Build a new LineBreaker with given parameters. * * You can reuse the Builder instance even after calling this method. */ public @NonNull LineBreaker build() { return new LineBreaker(mBreakStrategy, mHyphenationFrequency, mJustificationMode, - mIndents); + mIndents, mUseBoundsForWidth); } } @@ -456,9 +483,9 @@ public class LineBreaker { */ private LineBreaker(@BreakStrategy int breakStrategy, @HyphenationFrequency int hyphenationFrequency, @JustificationMode int justify, - @Nullable int[] indents) { + @Nullable int[] indents, boolean useBoundsForWidth) { mNativePtr = nInit(breakStrategy, hyphenationFrequency, - justify == JUSTIFICATION_MODE_INTER_WORD, indents); + justify == JUSTIFICATION_MODE_INTER_WORD, indents, useBoundsForWidth); sRegistry.registerNativeAllocation(this, mNativePtr); } @@ -493,7 +520,7 @@ public class LineBreaker { @FastNative private static native long nInit(@BreakStrategy int breakStrategy, @HyphenationFrequency int hyphenationFrequency, boolean isJustified, - @Nullable int[] indents); + @Nullable int[] indents, boolean useBoundsForWidth); @CriticalNative private static native long nGetReleaseFunc(); diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index 3f7f0880d160..2d33e8d24ece 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -60,17 +60,19 @@ public class MeasuredText { private final long mNativePtr; private final boolean mComputeHyphenation; private final boolean mComputeLayout; + private final boolean mComputeBounds; @NonNull private final char[] mChars; private final int mTop; private final int mBottom; // Use builder instead. private MeasuredText(long ptr, @NonNull char[] chars, boolean computeHyphenation, - boolean computeLayout, int top, int bottom) { + boolean computeLayout, boolean computeBounds, int top, int bottom) { mNativePtr = ptr; mChars = chars; mComputeHyphenation = computeHyphenation; mComputeLayout = computeLayout; + mComputeBounds = computeBounds; mTop = top; mBottom = bottom; } @@ -217,6 +219,7 @@ public class MeasuredText { private final @NonNull char[] mText; private boolean mComputeHyphenation = false; private boolean mComputeLayout = true; + private boolean mComputeBounds = true; private boolean mFastHyphenation = false; private int mCurrentOffset = 0; private @Nullable MeasuredText mHintMt = null; @@ -296,11 +299,11 @@ public class MeasuredText { Preconditions.checkArgument(length > 0, "length can not be negative"); final int end = mCurrentOffset + length; Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length"); - int lbStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakStyle() : - LineBreakConfig.LINE_BREAK_STYLE_NONE; - int lbWordStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakWordStyle() : - LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; - nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle, + int lbStyle = LineBreakConfig.getResolvedLineBreakStyle(lineBreakConfig); + int lbWordStyle = LineBreakConfig.getResolvedLineBreakWordStyle(lineBreakConfig); + boolean hyphenation = LineBreakConfig.getResolvedHyphenation(lineBreakConfig) + == LineBreakConfig.HYPHENATION_ENABLED; + nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle, hyphenation, mCurrentOffset, end, isRtl); mCurrentOffset = end; @@ -436,6 +439,20 @@ public class MeasuredText { } /** + * Hidden API that tells native to calculate bounding box as well. + * Different from {@link #setComputeLayout(boolean)}, the result bounding box is not stored + * into MeasuredText instance. Just warm up the global word cache entry. + * + * @hide + * @param computeBounds + * @return + */ + public @NonNull Builder setComputeBounds(boolean computeBounds) { + mComputeBounds = computeBounds; + return this; + } + + /** * Creates a MeasuredText. * * Once you called build() method, you can't reuse the Builder class again. @@ -455,9 +472,9 @@ public class MeasuredText { try { long hintPtr = (mHintMt == null) ? 0 : mHintMt.getNativePtr(); long ptr = nBuildMeasuredText(mNativePtr, hintPtr, mText, mComputeHyphenation, - mComputeLayout, mFastHyphenation); + mComputeLayout, mComputeBounds, mFastHyphenation); final MeasuredText res = new MeasuredText(ptr, mText, mComputeHyphenation, - mComputeLayout, mTop, mBottom); + mComputeLayout, mComputeBounds, mTop, mBottom); sRegistry.registerNativeAllocation(res, ptr); return res; } finally { @@ -495,6 +512,7 @@ public class MeasuredText { /* Non Zero */ long paintPtr, int lineBreakStyle, int lineBreakWordStyle, + boolean hyphenation, @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl); @@ -519,6 +537,7 @@ public class MeasuredText { @NonNull char[] text, boolean computeHyphenation, boolean computeLayout, + boolean computeBounds, boolean fastHyphenationMode); private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java index 8d20e9cee7d7..7932e3334063 100644 --- a/graphics/java/android/graphics/text/PositionedGlyphs.java +++ b/graphics/java/android/graphics/text/PositionedGlyphs.java @@ -16,6 +16,9 @@ package android.graphics.text; +import static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML; + +import android.annotation.FlaggedApi; import android.annotation.IntRange; import android.annotation.NonNull; import android.graphics.Paint; @@ -165,6 +168,73 @@ public final class PositionedGlyphs { } /** + * Returns true if the fake bold option used for drawing, otherwise false. + * + * @param index the glyph index + * @return true if the fake bold option is on, otherwise off. + */ + @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) + public boolean getFakeBold(@IntRange(from = 0) int index) { + Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); + return nGetFakeBold(mLayoutPtr, index); + } + + /** + * Returns true if the fake italic option used for drawing, otherwise false. + * + * @param index the glyph index + * @return true if the fake italic option is on, otherwise off. + */ + @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) + public boolean getFakeItalic(@IntRange(from = 0) int index) { + Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); + return nGetFakeItalic(mLayoutPtr, index); + } + + /** + * A special value returned by {@link #getWeightOverride(int)} and + * {@link #getItalicOverride(int)} that indicates no font variation setting is overridden. + */ + @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) + public static final float NO_OVERRIDE = Float.MIN_VALUE; + + /** + * Returns overridden weight value if the font is variable font and `wght` value is overridden + * for drawing. Otherwise returns {@link #NO_OVERRIDE}. + * + * @param index the glyph index + * @return overridden weight value or {@link #NO_OVERRIDE}. + */ + @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) + public float getWeightOverride(@IntRange(from = 0) int index) { + Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); + float value = nGetWeightOverride(mLayoutPtr, index); + if (value == -1) { + return NO_OVERRIDE; + } else { + return value; + } + } + + /** + * Returns overridden italic value if the font is variable font and `ital` value is overridden + * for drawing. Otherwise returns {@link #NO_OVERRIDE}. + * + * @param index the glyph index + * @return overridden weight value or {@link #NO_OVERRIDE}. + */ + @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) + public float getItalicOverride(@IntRange(from = 0) int index) { + Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); + float value = nGetItalicOverride(mLayoutPtr, index); + if (value == -1) { + return NO_OVERRIDE; + } else { + return value; + } + } + + /** * Create single style layout from native result. * * @hide @@ -210,6 +280,14 @@ public final class PositionedGlyphs { private static native long nGetFont(long minikinLayout, int i); @CriticalNative private static native long nReleaseFunc(); + @CriticalNative + private static native boolean nGetFakeBold(long minikinLayout, int i); + @CriticalNative + private static native boolean nGetFakeItalic(long minikinLayout, int i); + @CriticalNative + private static native float nGetWeightOverride(long minikinLayout, int i); + @CriticalNative + private static native float nGetItalicOverride(long minikinLayout, int i); @Override public boolean equals(Object o) { diff --git a/graphics/java/android/graphics/text/TEST_MAPPING b/graphics/java/android/graphics/text/TEST_MAPPING new file mode 100644 index 000000000000..99cbfe720c05 --- /dev/null +++ b/graphics/java/android/graphics/text/TEST_MAPPING @@ -0,0 +1,15 @@ +{ + "presubmit": [ + { + "name": "CtsTextTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "androidx.test.filters.LargeTest" + } + ] + } + ] +} diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java index 0e198d5c56ec..e6de5978ceb0 100644 --- a/graphics/java/android/view/PixelCopy.java +++ b/graphics/java/android/view/PixelCopy.java @@ -322,7 +322,7 @@ public final class PixelCopy { } /** - * Returns the {@link CopyResultStatus} of the copy request. + * Returns the status of the copy request. */ public @CopyResultStatus int getStatus() { return mStatus; |