Beautiful (Anti-alias) Chinese Character Display - java

(source: google.com)
Recently, I realize the Chinese Character displayed are rather ugly in my application.
I thought I should make them to "anti-alias". But, how can I do that in Java?
FYI, I didn't explicitly choose the font I want to use in my GUI application. I solely let the system decide their own during startup. I however, do explicitly set the locale, before show up the GUI.
Locale.setDefault(locale);
The system will always choose
javax.swing.plaf.FontUIResource[family=Tahoma,name=Tahoma,style=plain,size=11]
no matter I am in English or Chinese locale.

Anti-aliasing considered harmful: http://www.joelonsoftware.com/articles/fog0000000041.html
The point is, that beauty of characters is not necessarily the user interface goal. It is not everything. What you should look for, is readability of text. When your Chinese characters look not smooth, it may be exactly what helps human eye's control loop to know that it is in focus and stop blaming the eye muscules for blurriness. Really, don't fall in this pitfal.

Here's a method to read a truetype font from the classpath and register it with the graphics environment:
private static Font readFont(String name) {
InputStream in = Fonts.class.getResourceAsStream(name + ".ttf");
if (in == null) {
throw new IllegalArgumentException(name);
}
try {
Font retval = Font.createFont(Font.TRUETYPE_FONT, in);
GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(retval);
return retval;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
You can then use this font object to derive characters of different sizes, or you could try applying this font using Swing CSS. In this case, the value you would put in the "font-family" attribute is the value returned by Font.getName().
For example:
static {
Font font = readFont("VeraMono");
if (font != null) {
font = font.deriveFont(14f);
} else {
throw new IllegalStateException();
}
MONOSPACED_TEXT_FONT = font;
MONOSPACED_TEXT_FONT_STYLE = "font-family: " + font.getName() + "; font-size: 14pt; font-weight: normal;";
}

Related

Fonts slightly wider in OpenJDK vs OracleJDK

I'm noticing differences in font spacing using OpenJDK compared to OracleJDK. I've narrowed this down to the fonts. They are rendered by OpenJDK ever so slightly wider... Careful visual inspection of the screenshot above shows the character widths are identical, the only difference is the spacing. I also confirmed this with programmatic check of font metrics for all characters A-Za-z0-9.
e.g. the String "Dialog - plain" at 12pt is
125px wide in OpenJDK - my build of 8u131-b11
125px wide in OpenJDK - stock RPM from redhat disk - 1.8u45-b13
120px wide in OracleJDK - 8u131-b11 release from Oracle website
I've searched extensively for information on this and found various options including -Dawt.useSystemAAFontSettings, -Dswing.useSystemFontSettings, -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel from Java_Runtime_Environment_fonts. I've tried altering all of these but the results remain the same.
Further investigation has found sun.font.FontScaler, this uses different underlying fontscaler. This appears partially configurable in sun.font.FontUtilities which checks the system property for -Dsun.java2d.font.scaler=t2k, however setting this makes no difference.
My question: can FreetypeFontScaler be configured to behave in a similar or closer way to T2KFontScaler?
if (FontUtilities.isOpenJDK) {
scalerClass = Class.forName("sun.font.FreetypeFontScaler");
} else {
scalerClass = Class.forName("sun.font.T2KFontScaler");
}
This is the test program I've been using
public class FontTester {
public static void main(String[] args) throws Exception {
System.out.println(String.format("java.home=%s", System.getProperty("java.home")));
String family = Font.DIALOG;
int style = Font.PLAIN;
describeFont(new Font(family, style, 12));
JFrame frame = new JFrame();
frame.setSize(800, 600);
frame.add(new DemoPanel());
frame.setVisible(true);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
private static class DemoPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
String family = Font.DIALOG;
int style = Font.PLAIN;
Font font = new Font(family, style, 20);
g.setFont(font);
String str = family + " - " + name(font) + " ";
Rectangle2D bounds = g.getFontMetrics().getStringBounds(str, g);
str += String.format("%f x %f", bounds.getWidth(), bounds.getHeight());
g.drawString(str, 10, 50);
}
private String name(Font font) {
List<String> attrs = new ArrayList<>();
if (font.isBold()) {
attrs.add("bold");
}
if (font.isItalic()) {
attrs.add("italic");
}
if (font.isPlain()) {
attrs.add("plain");
}
return String.join(",", attrs);
}
}
private static void describeFont(Font font) throws Exception {
Method method = Font.class.getDeclaredMethod("getFont2D");
method.setAccessible(true);
Font2D font2d = (Font2D) method.invoke(font);
System.out.print(String.format("%s: ", font));
describeFont2D(font2d);
}
private static void describeFont2D(Font2D font) {
if (font instanceof CompositeFont) {
CompositeFont cf = (CompositeFont) font;
for (int i = 0; i < cf.getNumSlots(); i++) {
PhysicalFont pf = cf.getSlotFont(i);
describeFont2D(pf);
break;
}
} else {
System.out.print(String.format("-> %s \n", font));
}
}
}
Yet more investigation has traced this though to sun.font.FontStrike.getGlyphMetrics(int) returning different results. For a glyph-id 39 ("D") the advance X value is returned as 14.0px using Oracle JDK (via T2KFontScaler) but 15.0px using OpenJDK (via FreetypeFontScaler)
To figure which is "correct" I used the fontbox Java parser to extract the advance X value for Glyph-ID 39 from the HMTX table within the TTF file LiberationSans-Regular.ttf. The value is 1479 font design units - which maps to 14.44px at 20pt font size.
There is no difference for the next character "i" - glyph-id 76. This is 4.44 according to fontbox, and returned as 4 by both T2KScaler and FreetypeScaler
I've further narrowed this down to the rounding / calculation used when standard "non-fractional" metrics are used. If fractional metrics are enabled then both Oracle and Open JDKs behave identically, although the widths are still slighly smaller than the Oracle non-fractional case.
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
Update - Jan 2020
* In Java 11 there is new option which may be related - see https://bugs.openjdk.java.net/browse/JDK-8217731
FREETYPE_PROPERTIES=truetype:interpreter-version=35
Further investigation has found sun.font.FontScaler, this uses different underlying fontscaler. This appears partially configurable in sun.font.FontUtilities which checks the system property for -Dsun.java2d.font.scaler=t2k, however setting this makes no difference.
You're correct in that the underlying font scaler is different between Oracle and OpenJDK - unfortunately however, this is hard coded and not configurable.
The relevant code is in FontScaler:97:
if (FontUtilities.isOpenJDK) {
scalerClass = Class.forName("sun.font.FreetypeFontScaler");
} else {
scalerClass = Class.forName("sun.font.T2KFontScaler");
}
And the isOpenJDK flag? It's set by FontUtilities:125:
File lucidaFile = new File(jreFontDirName + File.separator + LUCIDA_FILE_NAME);
isOpenJDK = !lucidaFile.exists();
And that constant:
static final String LUCIDA_FILE_NAME = "LucidaSansRegular.ttf";
Unless I've missed something in those source files, there's no other configuration flag or any other condition that will change that scaler class being used.
So the only vaguely sensible way of doing this (I'm excluding horrible classloader / reflection hacks here) is to add that Lucida file in place. Unfortunately though in most scenarios I can think of (assuming you're not distributing the JRE along with the package) this isn't really going to be a viable solution either.

Change font of custome key (label) of android custom keyboard

I am having a problem with changing the font style (non English - Unicode) of the custom keys of android custom keyboard.
I have followed something similar to the answer of this link
and it seems working fine for single character buttons. It will change the font of entire app to the new font including single character keys of the keyboard.
If I wanna change the size of the key text I can use below two entries
android:keyTextSize="25sp" // for single character keys
android:labelTextSize="20sp" // for multiple character keys
But unfortunately method in above link only works for single character keys only. Is there any way to set the font of multiple character keys.
See below image for example:
First button has some default system font while second and third buttons have the correct font.
EDIT:
After reading Bhavita Lalwani answer it got me thinking.
if (label != null) {
// For characters, use large font. For labels like "Done", use small font.
if (label.length() > 1 && key.codes.length < 2) {
paint.setTextSize(mLabelTextSize);
paint.setTypeface(Typeface.DEFAULT_BOLD);
} else {
paint.setTextSize(mKeyTextSize);
paint.setTypeface(Typeface.DEFAULT);
}
}
here it says
if (label.length() > 1 && key.codes.length < 2)
so it uses BOLD fonts for multiple characters only if they have single code.
eg. I think Android Engs thinking about these things. ???
Keyboard.KEYCODE_DONE
Keyboard.KEYCODE_DELETE
So the ugly workaround will be to add multiple codes and only use first code when needed.
<Key android:codes="5001,1" android:keyLabel="AB" android:keyWidth="12%p" />
Now every key with multiple codes also user DEFAULT typeface.
This works for now, (Until I find a proper solution :))
I was having a similar issue in creating Hindi custom keyboard.(Non English-Unicode)
So let's find your culprit why this variation happens.
KeyboardView.java in Android Source code
lines 701-709
if (label != null) {
// For characters, use large font. For labels like "Done", use small font.
if (label.length() > 1 && key.codes.length < 2) {
paint.setTextSize(mLabelTextSize);
paint.setTypeface(Typeface.DEFAULT_BOLD);
} else {
paint.setTextSize(mKeyTextSize);
paint.setTypeface(Typeface.DEFAULT);
}
}
This means it makes multi character labels to Bold and different size.
And single char labels stay as it is.
Solution
Create a CustomKeyboardView class which extends this KeyboardView class
public class CustomKeyboardView extends KeyboardView {
public CustomKeyboardView(Context context, AttributeSet attrs) {
super(context, attrs);
}
Now in this CustomKeyboardView class, override onDraw method. This method would be called when drawing the keyboard and keys on the canvas
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint mpaint = new Paint();
mpaint.setTypeface(Typeface.DEFAULT_BOLD); //to make all Bold. Choose Default to make all normal font
mpaint.setTextSize(24); // in px
List<Key> keys = getKeyboard().getKeys();
for (Keyboard.Key key : keys) {
if (key.label != null) {
String keyLabel = key.label.toString();
canvas.drawText(keyLabel, key.x + key.width, key.y + key.height, mpaint);
} else if (key.icon != null) {
key.icon.setBounds(key.x, key.y, key.x + key.width, key.y + key.height);
key.icon.draw(canvas);
}
}
}
You can use this cheatcode to use sp for setTextSize method
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="custom_text_size">25sp</dimen>
</resources>
and
mpaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.custom_text_size));
Finally just as we used to create the Keyboard,
KeyboardView kv = (CustomKeyboardView) getLayoutInflater().inflate(R.layout.mainkeyboard, null); //mainkeyboard
Keyboard keyboard = new Keyboard(this, R.xml.hindi); //Your Keyboard Layout
kv.setKeyboard(keyboard); //Set the keyboard
And you are good to go.
Hope it helps :D

New java font's size is always 1

I added a new font for my project in java. However upon rendering it the text size is always at 1 it appears. The text basically appears as just a few little lines. I tried this with multiple fonts and they all did it. This is my code.
public static void intializeFonts(){
try{
File font = new File("C:/The Woods/Fonts/script.ttf");
Font scriptFontU = Font.createFont(Font.TRUETYPE_FONT, font);
Font scriptFont = scriptFontU.deriveFont(20);
script = new TrueTypeFont(scriptFont, false);
} catch(Exception e){
System.out.println("Error Loading Font");
}
}
This is also what I am using to render it if this helps.
g.setFont(Fonts.script);
g.drawString("Weight: "+ItemContainer.getWeight()+"lbs", 30, 600);
Any help would be great. Thank You.
When you call scriptFontU.deriveFont(20), you are calling Font.deriveFont(int). In this function, the first argument is an integer representing a style. Instead, you want to call Font.deriveFont(float), which takes a size and leaves the style unchanged. You can do this by calling scriptFontU.deriveFont(20.0), or equivalent; or call Font.deriveFont(int, float) as scriptFontU.deriveFont(Font.PLAIN, 20.0) to make it unambiguous.

Change Swing font size from command line

I am using a Swing application which on my computer shows text with a ridiculously small font size.
Is there a way to change the font size, or maybe some kind of DPI setting, from the command line or with some kind of configuration file (for example, something like a swing.properties file)?
I don't have access to the source code.
EDIT:
Small font sizes should not be a problem any more since Java 9. Swing has started to scale its GUI components depending on the screen resolution.
There is no command line switch to change the font size for Swing. What you would have to do is to invoke the following method:
public static void adjustFontSize(int adjustment) {
UIDefaults defaults = UIManager.getDefaults();
List<Object> newDefaults = new ArrayList<Object>();
Map<Object, Font> newFonts = new HashMap<Object, Font>();
Enumeration<Object> en = defaults.keys();
while (en.hasMoreElements()) {
Object key = en.nextElement();
Object value = defaults.get(key);
if (value instanceof Font) {
Font oldFont = (Font)value;
Font newFont = newFonts.get(oldFont);
if (newFont == null) {
newFont = new Font(oldFont.getName(), oldFont.getStyle(), oldFont.getSize() + adjustment);
newFonts.put(oldFont, newFont);
}
newDefaults.add(key);
newDefaults.add(newFont);
}
}
defaults.putDefaults(newDefaults.toArray());
}
where adjustment is the number of points that should be added to each font size.
If you don't have access to the source code, you can always write your own wrapper main class where you call
UIManager.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
if (event.getPropertyName().equals("lookAndFeel")) {
adjustFontSize(5);
}
}
});
before invoking the main method of the actual application.
However, if the font size is very small, it has likely been set explicitly, so changing the defaults might not help.
I don't think there are such options since all the component Font sizes are defined in Look and Feel (L&F) default values. Some of L&Fs allow quick font changes, some of them doesn't. In most cases you should be able change the font size by changing the UI defaults:
UIManager.put ( "Button.font", new SwingLazyValue ( "javax.swing.plaf.FontUIResource", null, new Object[]{ fontName, fontStyle, fontSize } ) );
UIManager.put ( "Label.font", new SwingLazyValue ( "javax.swing.plaf.FontUIResource", null, new Object[]{ fontName, fontStyle, fontSize } ) );
UIManager.put ( "TextField.font", new SwingLazyValue ( "javax.swing.plaf.FontUIResource", null, new Object[]{ fontName, fontStyle, fontSize } ) );
e.t.c for each component.
And i am not sure if those values could be passed to application without changing its code or atleast having some font-size change support inside the application.
I have not tested it, but you might try launching the app. using Java Web Start. It allows properties like swing.useSystemFontSettings & swing.metalTheme to be specified even for sand-boxed apps. Doing either might 'override' small fonts set in the code.
See the JNLP file syntax for more details.

Can Java render translucent text using sub-pixel AA?

I have found that while rendering opaque text in Java (latest version 6u23) uses sub-pixel AA just fine, rendering translucent text does not.
Sub-pixel AA:
The same text for which only the color has been changed from 0xFFFFFFFF to 0xBFFFFFFF:
As you can see, the translucent text is clearly standard AA and rather than a clean translucent rendering, it has that awful '90s "spidery" appearance.
Is this due to a technical limitation for sub-pixel AA in general, or a bug in Java, or just because Java doesn't even try for translucent text, or have I missed something?
Graphics Initialization
dbGraphics=(Graphics2D)dbImage.getGraphics();
if(dctRoot.properties.getBoolean("Antialias",true)) {
try {
Map hnts=(Map)(dctRoot.awtComponent.getToolkit().getDesktopProperty("awt.font.desktophints"));
// SET AA ON OVERALL (NOTE: GENERAL AA MUST BE OFF FOR SUBPIXEL AA TO BE HONORED - TEXT WIDGETS MUST DO THIS THEMSELVES)
dbGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
if(hnts!=null) {
// SET FONT RENDERING HINTS FROM DESKTOP
dbGraphics.addRenderingHints(hnts);
}
else {
try {
// SET TEXT AA TO FONT-SPECIFIED GASP AA (JAVA 6+)
dbGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.class.getField("VALUE_TEXT_ANTIALIAS_GASP").get(null));
}
catch(Throwable thr3) {
// SET TEXT AA TO DEFAULT
dbGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
}
}
catch(Throwable thr) {
dctRoot.log.println("Antialiasing not supported on this JVM ("+thr+").");
dctRoot.setProperty("Antialias","False"); // turn off AA for subsequent painting
}
}
else {
try {
dbGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
dbGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
}
catch(Throwable thr) {;} // ignore exception
}
Text Rendering
Object oaa=disableGeneralAA(gc);
...
gc.drawString(tl,xx,(ty+(xa*met.getHeight())));
restoreGeneralAA(gc,oaa);
...
static private volatile boolean hasRenderingHints=true;
// *****************************************************************************
// STATIC INIT & MAIN
// *****************************************************************************
// *****************************************************************************
// STATIC METHODS
// *****************************************************************************
/**
* Disable the general anti-aliasing rendering hint, returning whether the old value was RenderingHints.VALUE_ANTIALIAS_ON.
* <p>
* This method is needed for text rendering due to a bug in AWT; as of Java 6_20 when general AA is on text is not rendered using subpixel
* AA, so general AA has to be turned off before rendering text and turned back on when done. This method abstracts that work and deals
* with the possibility that the JVM does not support rendering hints, such as is the case with JME JVMs.
*/
static public Object disableGeneralAA(Graphics2D gc) {
Object old=null;
if(hasRenderingHints) {
try {
old=gc.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
}
catch(NoClassDefFoundError thr) { hasRenderingHints=false; }
catch(NoSuchFieldError thr) { hasRenderingHints=false; }
catch(NoSuchMethodError thr) { hasRenderingHints=false; }
}
return old;
}
/**
* Disable the general anti-aliasing rendering hint, returning whether the old value was RenderingHints.VALUE_ANTIALIAS_ON.
* <p>
* This method is needed for text rendering due to a bug in AWT; as of Java 6_20 when general AA is on text is not rendered using subpixel
* AA, so general AA has to be turned off before rendering text and turned back on when done. This method abstracts that work and deals
* with the possibility that the JVM does not support rendering hints, such as is the case with JME JVMs.
*/
static public void restoreGeneralAA(Graphics2D gc, Object val) {
Object old=null;
if(hasRenderingHints && val!=null) {
try { gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING,val); }
catch(NoClassDefFoundError thr) { hasRenderingHints=false; }
catch(NoSuchFieldError thr) { hasRenderingHints=false; }
catch(NoSuchMethodError thr) { hasRenderingHints=false; }
}
}
It seems rendering text to a transparant background is not (fully) supported, as can be seen from these bugreports:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6728834
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6749060
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6749069
I think its because your using GASP which takes the points from the font style. have you tried using VALUE_TEXT_ANTIALIAS_DEFAULT and VALUE_ALPHA_INTERPOLATION_DEFAULT? it's worth a shot.
http://download.oracle.com/javase/6/docs/api/java/awt/RenderingHints.html
What Java version are you using? You don´t say. But apparently this has been fixed as of either Java 6 update 12 (J6u12) or JDK7 b43
See here:
http://bugs.sun.com/view_bug.do?bug_id=6749060
If your test again with Java = or higher than J6u12 and still see the bug, there´s an RFE where you can comment on at Sun´s bug database.
The way to get things fixed in the Java platform, is to either:
Vote on the bug report at Sun´s BugParade, to increase its priority, and wait until the Sun/Oracle programmers get to it
or
Now that Java is open source, fix it yourself. (join the mailing list at ho.io/jkp5 :-)
The Bugparade report you want to vote or comment in is here (in case you test with j6u12 an it´s still present) is
url: ho.io/jkp2
If you want to implement a known work-around so the text looks nice even in older JREs a work-around is provided here
url: ho.io/jkpy
"Looks like the solution that i'm using to emulate the translucency by mixing the required foreground color with the background color of the component is still the way to go to make sure that the native rasterizer is used."
Good luck!

Categories

Resources