Paint: Support Typeface (#1459)

* Paint: Support Typeface

* Paint: Undo textSize transform
This commit is contained in:
Constantin Piber
2025-06-22 16:38:09 +02:00
committed by GitHub
parent 1cdef5e0ee
commit 972137c035
2 changed files with 341 additions and 8 deletions

View File

@@ -49,7 +49,6 @@ public class Paint {
private MaskFilter mMaskFilter;
private PathEffect mPathEffect;
private Shader mShader;
private Typeface mTypeface;
private Xfermode mXfermode;
private boolean mHasCompatScaling;
@@ -264,7 +263,6 @@ public class Paint {
mMaskFilter = null;
mPathEffect = null;
mShader = null;
mTypeface = null;
mXfermode = null;
mHasCompatScaling = false;
@@ -299,7 +297,6 @@ public class Paint {
mMaskFilter = paint.mMaskFilter;
mPathEffect = paint.mPathEffect;
mShader = paint.mShader;
mTypeface = paint.mTypeface;
mXfermode = paint.mXfermode;
mHasCompatScaling = paint.mHasCompatScaling;
@@ -371,6 +368,12 @@ public class Paint {
fontAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
}
Map<TextAttribute, Object> atts = (Map<TextAttribute, Object>) mFont.getAttributes();
Object weight = atts.getOrDefault(TextAttribute.WEIGHT, null);
if (weight instanceof Float) {
fontAttributes.put(TextAttribute.WEIGHT, weight);
}
mFont = mFont.deriveFont(fontAttributes);
}
@@ -602,11 +605,16 @@ public class Paint {
}
public Typeface getTypeface() {
return mTypeface;
return new Typeface(mFont);
}
public Typeface setTypeface(Typeface typeface) {
mTypeface = typeface;
Map<TextAttribute, Object> fontAttributes = new HashMap<TextAttribute, Object>();
fontAttributes.put(TextAttribute.WEIGHT, typeface.getJavaWeight());
mFont = typeface.getFont()
.deriveFont(mFont.getStyle(), mFont.getSize())
.deriveFont(fontAttributes);
setFlags(mFlags);
return typeface;
}
@@ -694,9 +702,7 @@ public class Paint {
}
public void setTextSize(float textSize) {
// convert px to pt using default DPI of 96
float fontSize = 72.0f * textSize / 96.0f;
mFont = mFont.deriveFont(fontSize);
mFont = mFont.deriveFont(textSize);
}
public float getTextScaleX() {

View File

@@ -0,0 +1,327 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.graphics;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.res.AssetManager;
import android.util.Log;
import com.android.internal.util.Preconditions;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.font.TextAttribute;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import android.annotation.NonNull;
public class Typeface {
private static String TAG = "Typeface";
/** @hide */
public static final boolean ENABLE_LAZY_TYPEFACE_INITIALIZATION = true;
/** The default NORMAL typeface object */
public static final Typeface DEFAULT;
public static final Typeface DEFAULT_BOLD;
/** The NORMAL style of the default sans serif typeface. */
public static final Typeface SANS_SERIF;
/** The NORMAL style of the default serif typeface. */
public static final Typeface SERIF;
/** The NORMAL style of the default monospace typeface. */
public static final Typeface MONOSPACE;
public @interface Style {}
// Style
public static final int NORMAL = 0;
public static final int BOLD = 1;
public static final int ITALIC = 2;
public static final int BOLD_ITALIC = 3;
/** @hide */ public static final int STYLE_MASK = 0x03;
public static final String DEFAULT_FAMILY = "sans-serif";
private final Font mFont;
/** Returns the typeface's weight value */
public int getWeight() {
Map<TextAttribute, Object> atts = (Map<TextAttribute, Object>) mFont.getAttributes();
Object weight = atts.getOrDefault(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
if (weight instanceof Float) {
float w = ((Float) weight).floatValue();
// undo the transformation
return (int) ((w - TextAttribute.WEIGHT_REGULAR) / (TextAttribute.WEIGHT_BOLD - TextAttribute.WEIGHT_REGULAR) * (Builder.BOLD_WEIGHT - Builder.NORMAL_WEIGHT) + Builder.NORMAL_WEIGHT);
}
return Builder.NORMAL_WEIGHT;
}
public float getJavaWeight() {
Map<TextAttribute, Object> atts = (Map<TextAttribute, Object>) mFont.getAttributes();
Object weight = atts.getOrDefault(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
if (weight instanceof Float) {
return ((Float) weight).floatValue();
}
return TextAttribute.WEIGHT_REGULAR;
}
/** Returns the typeface's intrinsic style attributes */
public @Style int getStyle() {
if (isBold() && isItalic()) return BOLD_ITALIC;
if (isBold()) return BOLD;
if (isItalic()) return ITALIC;
return NORMAL;
}
/** Returns true if getStyle() has the BOLD bit set. */
public final boolean isBold() {
return mFont.isBold();
}
/** Returns true if getStyle() has the ITALIC bit set. */
public final boolean isItalic() {
return mFont.isItalic();
}
public final @Nullable String getSystemFontFamilyName() {
return mFont.getFamily();
}
public static Typeface findFromCache(AssetManager mgr, String path) {
throw new RuntimeException("Stub!");
}
public static final class Builder {
/** @hide */
public static final int NORMAL_WEIGHT = 400;
/** @hide */
public static final int BOLD_WEIGHT = 700;
private final String mPath;
private Font mFont;
private int mStyle;
private Map<TextAttribute, Object> mAttributes;
private String mFallbackFamilyName;
public Builder(@NonNull File path) {
mFont = loadFont(path);
Log.v(TAG, "Font loaded from " + path.toURI());
mPath = null;
mStyle = 0;
mAttributes = new HashMap<TextAttribute, Object>();
}
public Builder(@NonNull FileDescriptor fd) {
throw new RuntimeException("Stub!");
}
public Builder(@NonNull String path) {
mFont = loadFont(new File(path));
mPath = path;
mStyle = 0;
mAttributes = new HashMap<TextAttribute, Object>();
}
public Builder(@NonNull AssetManager assetManager, @NonNull String path) {
throw new RuntimeException("Stub!");
}
public Builder(@NonNull AssetManager assetManager, @NonNull String path, boolean isAsset,
int cookie) {
throw new RuntimeException("Stub!");
}
public Builder setWeight(int weight) {
// java font weight does not follow typical weight distribution
// In Java, regular weight is at 1.0 and bold at 2.0, compared to 400 and 700 in TTF
// Typical range is 0 to 1000
float jWeight = (weight - NORMAL_WEIGHT) / (BOLD_WEIGHT - NORMAL_WEIGHT) * (TextAttribute.WEIGHT_BOLD - TextAttribute.WEIGHT_REGULAR) + TextAttribute.WEIGHT_REGULAR;
mAttributes.put(TextAttribute.WEIGHT, jWeight);
return this;
}
public Builder setItalic(boolean italic) {
if (italic) {
mStyle |= Font.ITALIC;
} else {
mStyle &= ~Font.ITALIC;
}
return this;
}
public Builder setTtcIndex(int ttcIndex) {
throw new RuntimeException("Stub!");
}
public Builder setFontVariationSettings(@Nullable String variationSettings) {
throw new RuntimeException("Stub!");
}
public Builder setFallback(@Nullable String familyName) {
mFallbackFamilyName = familyName;
return this;
}
private Typeface resolveFallbackTypeface() {
if (mFallbackFamilyName == null) {
return null;
}
return new Typeface(new Font(mFallbackFamilyName, mStyle, 12).deriveFont(mAttributes));
}
public Typeface build() {
if (mFont == null) return resolveFallbackTypeface();
return new Typeface(mFont.deriveFont(mStyle).deriveFont(mAttributes));
}
private Font loadFont(File fontFile) {
try {
return Font.createFont(Font.TRUETYPE_FONT, fontFile);
} catch (FontFormatException ex) {
Log.v(TAG, "Failed to create font as TTF", ex);
} catch (IOException ex) {
Log.v(TAG, "Failed to create font as TTF", ex);
throw new RuntimeException(ex);
}
try {
return Font.createFont(Font.TYPE1_FONT, fontFile);
} catch (FontFormatException ex) {
Log.v(TAG, "Failed to create font as T1", ex);
} catch (IOException ex) {
Log.v(TAG, "Failed to create font as T1", ex);
throw new RuntimeException(ex);
}
return null;
}
}
public static Typeface create(String familyName, @Style int style) {
return create(getSystemDefaultTypeface(familyName), style);
}
public static Typeface create(Typeface family, @Style int style) {
if ((style & ~STYLE_MASK) != 0) {
style = NORMAL;
}
if (family == null) {
family = getSystemDefaultTypeface(DEFAULT_FAMILY);
}
throw new RuntimeException("Stub!");
}
public static @NonNull Typeface create(@Nullable Typeface family,
int weight, boolean italic) {
Preconditions.checkArgumentInRange(weight, 0, 1000, "weight");
if (family == null) {
family = getSystemDefaultTypeface(DEFAULT_FAMILY);
}
return createWeightStyle(family, weight, italic);
}
private static @NonNull Typeface createWeightStyle(@NonNull Typeface base,
int weight, boolean italic) {
throw new RuntimeException("Stub!");
}
public static Typeface defaultFromStyle(@Style int style) {
throw new RuntimeException("Stub!");
}
public static Typeface createFromAsset(AssetManager mgr, String path) {
Preconditions.checkNotNull(path); // for backward compatibility
Preconditions.checkNotNull(mgr);
Typeface typeface = new Builder(mgr, path).build();
if (typeface != null) return typeface;
// check if the file exists, and throw an exception for backward compatibility
try (InputStream inputStream = mgr.open(path)) {
} catch (IOException e) {
throw new RuntimeException("Font asset not found " + path);
}
return Typeface.DEFAULT;
}
public Typeface(Font fnt) {
mFont = fnt;
}
public static Typeface createFromFile(@Nullable File file) {
// For the compatibility reasons, leaving possible NPE here.
// See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
Typeface typeface = new Builder(file).build();
if (typeface != null) return typeface;
// check if the file exists, and throw an exception for backward compatibility
if (!file.exists()) {
throw new RuntimeException("Font asset not found " + file.getAbsolutePath());
}
return Typeface.DEFAULT;
}
public static Typeface createFromFile(@Nullable String path) {
Preconditions.checkNotNull(path); // for backward compatibility
return createFromFile(new File(path));
}
private static Typeface getSystemDefaultTypeface(@NonNull String familyName) {
return new Typeface(new Font(familyName, 0, 12));
}
public Font getFont() {
return mFont;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Typeface typeface = (Typeface) o;
return typeface.mFont.equals(mFont);
}
@Override
public int hashCode() {
return mFont.hashCode();
}
static {
DEFAULT = new Typeface(new Font(null, 0, 12));
DEFAULT_BOLD = new Typeface(new Font(null, Font.BOLD, 12));
SANS_SERIF = new Typeface(new Font(Font.SANS_SERIF, 0, 12));
SERIF = new Typeface(new Font(Font.SERIF, 0, 12));
MONOSPACE = new Typeface(new Font(Font.MONOSPACED, 0, 12));
}
}