OpenJDK字体渲染修正

时间:2023-02-05 22:36:49
现在Java的字体渲染比以前进步多了,但最近装了个Monaco,却发现无论如何不能在idea中很好地显示,在eclipse里很正常,于是切换到eclipse,没用几下就放弃了,eclipse的Scala插件实在太不成熟了,简单的命名重构居然把我的代码改的面目全非,通不过编译了,相比之下idea的Scala插件虽然也有一些问题,但基本还是能用的。google看能不能在idea里正常显示monaco, 于是找到这里,2010年的问题,看来这个问题已经存在很久了。

看了回复,大致是两个解决办法:
  1. 修改字体文件
  2. 修改jdk
先试简单的办法,用fontforge把字体的hints和Instructions去掉,一看,果然显示正常了,但一编辑,问题就来了,光标位置字符会出现重叠,根本不能用,看来不是这么简单可以解决的,于是试plan B,参考 openJDK fontfix修改代码。

和Linux一样,openJDK也是用freeType2来渲染字体,但相比较,openJDK存在以下几点不同:
  • Linux下的fontconfig可以对单个字体配置不同的渲染参数,但openJDK只能全局设置,就是awt.useSystemAAFontSettings这个参数了,这造成了某些字体不能很好地显示,这可以说是最大的局限
  • 不能使用autohint
  • hintslight也不能用
  • lcdfilter没有开启
要解决这些问题,一种方法是在awt.useSystemAAFontSettings参数之外再为JDK增加字体配置信息,这就比较复杂了,改动太大;还是一种方法是直接读取系统的配置信息,这个简单一些,本文就采用这个方案,并且把系统限定为Linux,字体配置采用fontconfig,所以不能在windows下编译。

openJDK的字体渲染是在jdk/src/share/native/sun/font/freetypeScaler.c这个文件中完成的,只需要修改一个文件就行了。

第一步首先要读取Linux fontconfig的字体配置信息,增加下面两个函数:
C代码   OpenJDK字体渲染修正
  1. static FcPattern* matchedPattern(const FcChar8* family, double ptSize) {  
  2.     FcPattern* fcPattern = 0;  
  3.     fcPattern = FcPatternCreate();  
  4.     FcValue fcValue;  
  5.     fcValue.type = FcTypeString;  
  6.     fcValue.u.s = family;  
  7.     FcPatternAdd(fcPattern, FC_FAMILY, fcValue, FcTrue);  
  8.     FcPatternAddBool(fcPattern, FC_SCALABLE, FcTrue);  
  9.     FcPatternAddDouble(fcPattern, FC_SIZE, ptSize);  
  10.     FcConfigSubstitute(0, fcPattern, FcMatchPattern);  
  11.     FcDefaultSubstitute(fcPattern);  
  12.     FcResult res;  
  13.     FcPattern *pattern = 0;  
  14.     pattern = FcFontMatch(0, fcPattern, &res);  
  15.     FcPatternDestroy(fcPattern);  
  16.     return pattern;  
  17. }  
  18.   
  19. typedef struct {  
  20.     FT_Int32 loadFlags;  
  21.     FT_Render_Mode renderMode;  
  22.     FT_LcdFilter lcdFilter;  
  23. } RenderProperty;  
  24.   
  25. static void readFontconfig(FTScalerInfo* scalerInfo, FTScalerContext* context, RenderProperty* rp) {  
  26.   
  27.    FcPattern *pattern = matchedPattern((const FcChar8 *)scalerInfo->face->family_name, context->ptsz);  
  28.   
  29.    FT_Int32 load_flags = FT_LOAD_DEFAULT;  
  30.    FT_Render_Mode render_mode = FT_RENDER_MODE_NORMAL;  
  31.    FT_LcdFilter lcd_filter = FT_LCD_FILTER_NONE;  
  32.   
  33.    if (TEXT_AA_OFF == context->aaType) {  
  34.       load_flags = FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO;  
  35.       render_mode = FT_RENDER_MODE_MONO;  
  36.    } else {  
  37.       FcBool hinting = FcTrue;  
  38.       FcPatternGetBool (pattern, FC_HINTING, 0, &hinting);  
  39.   
  40.       FcBool autohint = FcFalse;  
  41.       FcPatternGetBool(pattern, FC_AUTOHINT, 0, &autohint);  
  42.   
  43.       int hint_style = FC_HINT_FULL;  
  44.       FcPatternGetInteger(pattern, FC_HINT_STYLE, 0, &hint_style);  
  45.   
  46.       if (!hinting || hint_style == FC_HINT_NONE) {  
  47.          load_flags |= FT_LOAD_NO_HINTING;  
  48.          if (autohint) load_flags |= FT_LOAD_FORCE_AUTOHINT;  
  49.       } else if (FC_HINT_NONE < hint_style && hint_style < FC_HINT_FULL) {  
  50.          load_flags |= FT_LOAD_TARGET_LIGHT;  
  51.          render_mode = FT_RENDER_MODE_LIGHT;  
  52.       } else  
  53.          load_flags |= FT_LOAD_TARGET_NORMAL;  
  54.         
  55.       switch (context->aaType) {    
  56.       case TEXT_AA_LCD_HRGB:    
  57.       case TEXT_AA_LCD_HBGR:    
  58.          load_flags |= FT_LOAD_TARGET_LCD;    
  59.          render_mode = FT_RENDER_MODE_LCD;    
  60.          break;    
  61.       case TEXT_AA_LCD_VRGB:    
  62.       case TEXT_AA_LCD_VBGR:    
  63.          load_flags |= FT_LOAD_TARGET_LCD_V;    
  64.          render_mode = FT_RENDER_MODE_LCD_V;    
  65.       }    
  66.    }  
  67.   
  68.    if (FT_RENDER_MODE_LCD_V == render_mode || FT_RENDER_MODE_LCD == render_mode) {  
  69.       int lcdfilter = FC_LCD_NONE;  
  70.       FcPatternGetInteger(pattern, FC_LCD_FILTER, 0, &lcdfilter);  
  71.       switch (lcdfilter) {  
  72.       case FC_LCD_NONE:  
  73.          lcd_filter = FT_LCD_FILTER_NONE;  
  74.          break;  
  75.       case FC_LCD_LIGHT:  
  76.          lcd_filter = FT_LCD_FILTER_LIGHT;  
  77.          break;  
  78.       case FC_LCD_LEGACY:  
  79.          lcd_filter = FT_LCD_FILTER_LEGACY;  
  80.          break;  
  81.       default:  
  82.          lcd_filter = FT_LCD_FILTER_DEFAULT;  
  83.       }  
  84.    }     
  85.   
  86.    FcPatternDestroy(pattern);  
  87.   
  88.    rp->loadFlags = load_flags;  
  89.    rp->renderMode = render_mode;  
  90.    rp->lcdFilter = lcd_filter;  
  91. }  

需要说明的是,虽然是想全部基于fontconfig的配置信息来渲染字体,但是java的AA设置不仅可以通过awt.useSystemAAFontSettings参数来设置,还可以在java程序内指定,java渲染字体的时候也会根据AA来做一些不同的处理,所以,不能简单地忽略AA设置,而全部采用fontconfig的配置,否则就可能导致显示不正常。最终的结果是fontconfig和awt.useSystemAAFontSettings合在一起来配置openJDK的字体。

awt.useSystemAAFontSettings有4个值:on,off,lcd,lcdv,就是要不要antialias,要的话用哪种antialias算法,它覆盖了fontconfig中的antialias,rgba这两个配置项和hintstyle的部分配置参数,所以读取fontconfig配置信息时,会忽略antialias和rgba这两个配置项,hintstyle的值也是优先采用awt.useSystemAAFontSettings的值,没有覆盖到的才会用fontconfig hintstyle中配置的值(hintslight)。

实际地渲染是在Java_sun_font_FreetypeFontScaler_getGlyphImageNative这个函数中完成的,修改如下:
C代码   OpenJDK字体渲染修正
  1.     //先在函数开始的地方加上这一段,创建一个RenderProperty结构体,用来保存读取的字体配置信息  
  2.     RenderProperty* rp = NULL;  
  3.     rp = (RenderProperty*) calloc(1, sizeof(RenderProperty));  
  4.     if (NULL == rp) {  
  5.         return ptr_to_jlong(getNullGlyphImage());  
  6.     }  
  7.   
  8. /**这段要注释掉 
  9.     if (context->aaType == TEXT_AA_OFF) { 
  10.         target = FT_LOAD_TARGET_MONO; 
  11.     } else if (context->aaType == TEXT_AA_ON) { 
  12.         target = FT_LOAD_TARGET_NORMAL; 
  13.     } else if (context->aaType == TEXT_AA_LCD_HRGB || 
  14.                context->aaType == TEXT_AA_LCD_HBGR) { 
  15.         target = FT_LOAD_TARGET_LCD; 
  16.     } else { 
  17.         target = FT_LOAD_TARGET_LCD_V; 
  18.     }     
  19.     renderFlags |= target; 
  20. */      
  21.   
  22.     //读取配置,设置lcdfilter  
  23.     readFontconfig(scalerInfo, context, rp);  
  24.     renderFlags |= rp->loadFlags;      
  25.     FT_Library_SetLcdFilter(scalerInfo->library, rp->lcdFilter);  
  26.   
  27.     glyph_index = FT_Get_Char_Index(scalerInfo->face, glyphCode);  
  28.   
  29.     error = FT_Load_Glyph(scalerInfo->face, glyphCode, renderFlags);  
  30.   
  31.     ......  
  32.   
  33.     if (ftglyph->format == FT_GLYPH_FORMAT_OUTLINE) {  
  34.         FT_Render_Glyph(ftglyph, rp->renderMode);//renderMode改成读取过来的  
  35.     }  
  36.     free(rp);  

因为使用了fontconfig的函数,别忘了在文件头上加上#include <fontconfig/fontconfig.h>,同时Make文件也得改一下,具体参考openJDK fontfix。好了,重新build openJDK吧,如何build参考openJDK fontfix的build脚本或 这里, build完再运行idea,看看字体是不是跟eclipse里一样了,这下不用羡慕eclipse漂亮的字体了吧。

最后再次说明一下,请注意freetypeScaler.c是放在share/native下的,也就是要求是跨平台的,但fontconfig.h是特定于linux的,所以这样改是破坏了跨平台性的,我只是改来自己用用。如果有需要,可以下载附件里patch,应用然后自己编译一下。

OpenJDK字体渲染修正