mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-03 02:44:34 -05:00
AndroidCompat: Use NotoSans as default font (#1572)
* Initial Noto fonts * Use Noto for other default fonts * Typeface: Prefer main font Eagerly switch back to main font as soon as it can display again; otherwise we might never switch back (or later than necessary); we should always prefer the main font * fix: Font metrics with fallback font on TextLine
This commit is contained in:
@@ -10,9 +10,11 @@ import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Shape;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.awt.font.GlyphVector;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.text.AttributedString;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -44,13 +46,16 @@ public final class Canvas {
|
||||
drawText(new String(text, index, count), x, y, paint);
|
||||
}
|
||||
|
||||
public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
|
||||
public void drawText(@NonNull String str, float x, float y, @NonNull Paint paint) {
|
||||
applyPaint(paint);
|
||||
GlyphVector glyphVector = paint.getFont().createGlyphVector(canvas.getFontRenderContext(), text);
|
||||
AttributedString text = paint.getTypeface().createWithFallback(str);
|
||||
canvas.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
|
||||
// TODO: fix with fallback fonts
|
||||
GlyphVector glyphVector = paint.getTypeface().getFont().createGlyphVector(canvas.getFontRenderContext(), text.getIterator());
|
||||
Shape textShape = glyphVector.getOutline();
|
||||
switch (paint.getStyle()) {
|
||||
case Paint.Style.FILL:
|
||||
canvas.drawString(text, x, y);
|
||||
canvas.drawString(text.getIterator(), x, y);
|
||||
break;
|
||||
case Paint.Style.STROKE:
|
||||
save();
|
||||
@@ -178,7 +183,7 @@ public final class Canvas {
|
||||
}
|
||||
|
||||
private void applyPaint(Paint paint) {
|
||||
canvas.setFont(paint.getFont());
|
||||
canvas.setFont(paint.getTypeface().getFont());
|
||||
java.awt.Color color = Color.valueOf(paint.getColorLong()).toJavaColor();
|
||||
canvas.setColor(color);
|
||||
canvas.setStroke(new BasicStroke(paint.getStrokeWidth(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||
|
||||
@@ -65,9 +65,9 @@ public class Paint {
|
||||
@ColorLong private long mShadowLayerColor;
|
||||
|
||||
private int mFlags;
|
||||
private Font mFont = new Font(null);
|
||||
private Style mStyle = Style.FILL;
|
||||
private float mStrokeWidth = 1.0f;
|
||||
private Typeface mTypeface = Typeface.DEFAULT;
|
||||
|
||||
private static final Object sCacheLock = new Object();
|
||||
|
||||
@@ -278,10 +278,10 @@ public class Paint {
|
||||
mShadowLayerDy = 0.0f;
|
||||
mShadowLayerColor = Color.pack(0);
|
||||
|
||||
setFlags(ANTI_ALIAS_FLAG);
|
||||
mFont = new Font(null);
|
||||
mStyle = Style.FILL;
|
||||
mStrokeWidth = 1.0f;
|
||||
mTypeface = Typeface.DEFAULT;
|
||||
setFlags(ANTI_ALIAS_FLAG);
|
||||
}
|
||||
|
||||
public void set(Paint src) {
|
||||
@@ -314,9 +314,9 @@ public class Paint {
|
||||
mShadowLayerColor = paint.mShadowLayerColor;
|
||||
|
||||
mFlags = paint.mFlags;
|
||||
mFont = paint.mFont;
|
||||
mStyle = paint.mStyle;
|
||||
mStrokeWidth = paint.mStrokeWidth;
|
||||
mTypeface = paint.mTypeface;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@@ -368,13 +368,13 @@ public class Paint {
|
||||
fontAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
|
||||
}
|
||||
|
||||
Map<TextAttribute, Object> atts = (Map<TextAttribute, Object>) mFont.getAttributes();
|
||||
Map<TextAttribute, Object> atts = mTypeface.getAttributes();
|
||||
Object weight = atts.getOrDefault(TextAttribute.WEIGHT, null);
|
||||
if (weight instanceof Float) {
|
||||
fontAttributes.put(TextAttribute.WEIGHT, weight);
|
||||
}
|
||||
|
||||
mFont = mFont.deriveFont(fontAttributes);
|
||||
mTypeface = mTypeface.deriveFont(fontAttributes);
|
||||
}
|
||||
|
||||
public int getHinting() {
|
||||
@@ -605,14 +605,14 @@ public class Paint {
|
||||
}
|
||||
|
||||
public Typeface getTypeface() {
|
||||
return new Typeface(mFont);
|
||||
return mTypeface;
|
||||
}
|
||||
|
||||
public Typeface setTypeface(Typeface typeface) {
|
||||
Map<TextAttribute, Object> fontAttributes = new HashMap<TextAttribute, Object>();
|
||||
fontAttributes.put(TextAttribute.WEIGHT, typeface.getJavaWeight());
|
||||
mFont = typeface.getFont()
|
||||
.deriveFont(mFont.getStyle(), mFont.getSize())
|
||||
mTypeface = typeface
|
||||
.deriveFont(mTypeface.getFont().getStyle(), mTypeface.getFont().getSize())
|
||||
.deriveFont(fontAttributes);
|
||||
setFlags(mFlags);
|
||||
return typeface;
|
||||
@@ -693,16 +693,12 @@ public class Paint {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return mFont;
|
||||
}
|
||||
|
||||
public float getTextSize() {
|
||||
return mFont.getSize2D();
|
||||
return mTypeface.getFont().getSize2D();
|
||||
}
|
||||
|
||||
public void setTextSize(float textSize) {
|
||||
mFont = mFont.deriveFont(textSize);
|
||||
mTypeface = mTypeface.deriveFont(textSize);
|
||||
}
|
||||
|
||||
public float getTextScaleX() {
|
||||
@@ -836,7 +832,7 @@ public class Paint {
|
||||
|
||||
public float getFontMetrics(FontMetrics metrics) {
|
||||
java.awt.Canvas c = new java.awt.Canvas();
|
||||
java.awt.FontMetrics m = c.getFontMetrics(mFont);
|
||||
java.awt.FontMetrics m = c.getFontMetrics(mTypeface.getFont());
|
||||
metrics.top = m.getMaxDescent();
|
||||
metrics.ascent = m.getAscent();
|
||||
metrics.descent = m.getDescent();
|
||||
|
||||
@@ -30,9 +30,15 @@ import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.text.AttributedString;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.Collectors;
|
||||
import android.annotation.NonNull;
|
||||
|
||||
|
||||
@@ -65,6 +71,7 @@ public class Typeface {
|
||||
public static final String DEFAULT_FAMILY = "sans-serif";
|
||||
|
||||
private final Font mFont;
|
||||
private final List<Font> mFallbackFonts;
|
||||
|
||||
/** Returns the typeface's weight value */
|
||||
public int getWeight() {
|
||||
@@ -271,6 +278,76 @@ public class Typeface {
|
||||
|
||||
public Typeface(Font fnt) {
|
||||
mFont = fnt;
|
||||
mFallbackFonts = Collections.emptyList();
|
||||
}
|
||||
|
||||
public Typeface(Font fnt, List<Font> fallbackFonts) {
|
||||
mFont = fnt;
|
||||
mFallbackFonts = fallbackFonts;
|
||||
}
|
||||
|
||||
public Map<TextAttribute, Object> getAttributes() {
|
||||
return (Map<TextAttribute, Object>) mFont.getAttributes();
|
||||
}
|
||||
|
||||
public Typeface deriveFont(Map<TextAttribute, Object> attributes) {
|
||||
Font mainFont = mFont.deriveFont(attributes);
|
||||
List<Font> fallbacks = mFallbackFonts.stream().map(font -> font.deriveFont(attributes))
|
||||
.collect(Collectors.toList());
|
||||
return new Typeface(mainFont, fallbacks);
|
||||
}
|
||||
|
||||
public Typeface deriveFont(float size) {
|
||||
Font mainFont = mFont.deriveFont(size);
|
||||
List<Font> fallbacks = mFallbackFonts.stream().map(font -> font.deriveFont(size))
|
||||
.collect(Collectors.toList());
|
||||
return new Typeface(mainFont, fallbacks);
|
||||
}
|
||||
|
||||
public Typeface deriveFont(int style, float size) {
|
||||
Font mainFont = mFont.deriveFont(style, size);
|
||||
List<Font> fallbacks = mFallbackFonts.stream().map(font -> font.deriveFont(style, size))
|
||||
.collect(Collectors.toList());
|
||||
return new Typeface(mainFont, fallbacks);
|
||||
}
|
||||
|
||||
public AttributedString createWithFallback(String text) {
|
||||
AttributedString result = new AttributedString(text);
|
||||
|
||||
int textLength = text.length();
|
||||
result.addAttribute(TextAttribute.FONT, mFont, 0, textLength);
|
||||
|
||||
int i = 0;
|
||||
while (true) {
|
||||
int until = mFont.canDisplayUpTo(result.getIterator(), i, textLength);
|
||||
if (until == -1) break;
|
||||
|
||||
boolean found = false;
|
||||
// find a fallback font from `until`
|
||||
for (int j = 0; j < mFallbackFonts.size(); ++j) {
|
||||
int fallbackUntil = until;
|
||||
for (; fallbackUntil < textLength; ++fallbackUntil) {
|
||||
if (mFont.canDisplay(text.charAt(fallbackUntil)) || !mFallbackFonts.get(j).canDisplay(text.charAt(fallbackUntil)))
|
||||
break;
|
||||
}
|
||||
if (fallbackUntil > until) {
|
||||
// use this and advance
|
||||
int end = fallbackUntil >= 0 ? fallbackUntil : textLength;
|
||||
result.addAttribute(TextAttribute.FONT, mFallbackFonts.get(j), until, end);
|
||||
Log.v(TAG, String.format("Fallback: from %d to %d using %s", until, end, mFallbackFonts.get(j).getName()));
|
||||
i = end;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) continue;
|
||||
|
||||
Log.w(TAG, String.format("No fallback font found at %d, skipping", until));
|
||||
i = until + 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Typeface createFromFile(@Nullable File file) {
|
||||
@@ -316,12 +393,30 @@ public class Typeface {
|
||||
return mFont.hashCode();
|
||||
}
|
||||
|
||||
private static Font loadFontAsset(String font) {
|
||||
try (InputStream defaultNormalStream = ClassLoader.getSystemClassLoader().getResourceAsStream("font/" + font)) {
|
||||
return Font.createFont(Font.TRUETYPE_FONT, defaultNormalStream).deriveFont(12.0f);
|
||||
} catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to load " + font, ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Typeface withFallback(Font baseFallback, String mainFont, String... fonts) {
|
||||
Font main = loadFontAsset(mainFont);
|
||||
if (main == null) main = new Font(null, 0, 12);
|
||||
List<Font> fallbacks = Stream.concat(Arrays.stream(fonts).map(Typeface::loadFontAsset).filter(f -> f != null), Stream.of(baseFallback))
|
||||
.collect(Collectors.toList());
|
||||
Log.v(TAG, String.format("Loaded font %s with %d fallback fonts", main.getName(), fallbacks.size()));
|
||||
return new Typeface(main, fallbacks);
|
||||
}
|
||||
|
||||
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));
|
||||
DEFAULT = withFallback(new Font(null, 0, 12), "NotoSans/NotoSans-VariableFont_wdth,wght.ttf", "NotoSans/NotoSansSymbols2-Regular.ttf", "NotoSans/NotoEmoji-VariableFont_wght.ttf");
|
||||
DEFAULT_BOLD = DEFAULT.deriveFont(Font.BOLD);
|
||||
SANS_SERIF = DEFAULT;
|
||||
SERIF = withFallback(new Font(Font.SERIF, 0, 12), "NotoSans/NotoSerif-VariableFont_wdth,wght.ttf", "NotoSans/NotoSansSymbols2-Regular.ttf", "NotoSans/NotoEmoji-VariableFont_wght.ttf");
|
||||
MONOSPACE = withFallback(new Font(Font.MONOSPACED, 0, 12), "NotoSans/NotoSansMono-VariableFont_wdth,wght.ttf", "NotoSans/NotoSansSymbols2-Regular.ttf", "NotoSans/NotoEmoji-VariableFont_wght.ttf");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -366,9 +366,8 @@ public class StaticLayout extends Layout {
|
||||
mRightIndents = b.mRightIndents;
|
||||
|
||||
String str = b.mText.subSequence(b.mStart, b.mEnd).toString();
|
||||
AttributedString text = new AttributedString(str);
|
||||
text.addAttribute(TextAttribute.FONT, getPaint().getFont());
|
||||
FontRenderContext frc = new FontRenderContext(getPaint().getFont().getTransform(), RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
|
||||
AttributedString text = getPaint().getTypeface().createWithFallback(str);
|
||||
FontRenderContext frc = new FontRenderContext(null, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
|
||||
LineBreakMeasurer measurer = new LineBreakMeasurer(text.getIterator(), frc);
|
||||
// TODO: directions
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ import android.text.Layout.Directions;
|
||||
import android.text.Layout.TabStops;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.TextMeasurer;
|
||||
import java.text.AttributedString;
|
||||
|
||||
|
||||
public class TextLine {
|
||||
@@ -149,7 +151,9 @@ public class TextLine {
|
||||
public float metrics(FontMetricsInt fmi, @Nullable RectF drawBounds, boolean returnDrawWidth,
|
||||
@Nullable LineInfo lineInfo) {
|
||||
FontRenderContext frc = new FontRenderContext(null, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
|
||||
return (float) mPaint.getFont().getStringBounds(mText.toString(), mStart, mStart + mLen, frc).getWidth();
|
||||
AttributedString text = mPaint.getTypeface().createWithFallback(mText.toString());
|
||||
TextMeasurer tm = new TextMeasurer(text.getIterator(), frc);
|
||||
return (float) tm.getLayout(mStart, mStart + mLen).getBounds().getWidth();
|
||||
}
|
||||
|
||||
public float measure(@IntRange(from = 0) int offset, boolean trailing,
|
||||
|
||||
Reference in New Issue
Block a user