在Java2D中访问字体字距调整信息

时间:2022-03-13 22:57:52

Little background..

I'm in the process of making an OpenGL game using Java and LWJGL. I've written a TextRenderer-class that renders text using cached pages of glyphs. The glyphs itself are rendered in Java2D to BufferedImages and packed into texture pages along with the glyph measurements. TextRenderer draws the characters as textured quads, using the cached information.

我正在使用Java和LWJGL制作OpenGL游戏。我编写了一个TextRenderer类,它使用缓存的字形页面呈现文本。字形本身在Java2D中呈现为BufferedImages,并与字形测量一起打包到纹理页面中。 TextRenderer使用缓存的信息将字符绘制为纹理四边形。

All this works well, except for one thing: missing kerning. Granted, it's not necessary to have as the text looks fine as it is, but it would improve the quality if I had access to the font kerning information.

所有这一切都运作良好,除了一件事:缺少字距。当然,没有必要让文本看起来很好,但如果我可以访问字体字距信息,它会提高质量。

And the question is..

Is it possible to obtain the kerning information using plain Java, in a way that would be portable across Windows, Linux and MacOS X? Back when I wrote the TextRenderer I briefly looked around but could not find such a way..

是否可以使用普通Java获取字距调整信息,以便可以跨Windows,Linux和MacOS X移植?回到我写TextRenderer时,我简单地环顾四周但却找不到这样的方式..

One possible solution

If there is no way of doing this in pure Java, I was thinking of writing a separate tool using Freetype. As listed in their features page:

如果在纯Java中没有办法做到这一点,我正在考虑使用Freetype编写一个单独的工具。如其功能页面中所列:

FreeType 2 provides information that is often not available from other similar font engines, like kerning distances, glyph names, vertical metrics, etc.

FreeType 2提供了其他类似字体引擎通常无法提供的信息,如字距调整,字形名称,垂直度量等。

The tool would store the kerning pairs for common characters into a file that my text renderer would load in and make use of. So this is probably what I will do if you guys don't come up with a better alternative. :)

该工具会将常用字符的字距调整对存储到我的文本渲染器将加载并使用的文件中。所以如果你们没有提出更好的选择,这可能就是我要做的。 :)

3 个解决方案

#1


The only libraries I know of that read the kerning info "somwhat" correctly are iText and FOP from Apache.

我所知道的唯一能够正确读取字距信息“somwhat”的库是来自Apache的iText和FOP。

http://www.1t3xt.info/api/com/lowagie/text/pdf/BaseFont.html http://svn.apache.org/viewvc/xmlgraphics/fop/tags/fop-0_95/src/java/org/apache/fop/fonts/ (a link to the svn as there seems to be no online api)

http://www.1t3xt.info/api/com/lowagie/text/pdf/BaseFont.html http://svn.apache.org/viewvc/xmlgraphics/fop/tags/fop-0_95/src/java/org / apache / fop / fonts /(链接到svn,似乎没有在线api)

#2


Starting with Java SE 6, Java can provide kerning information when the font provides it. It is off by default and can be turned on like this:

从Java SE 6开始,Java可以在字体提供时提供字距调整信息。它默认是关闭的,可以像这样打开:

Map<TextAttribute, Object> textAttributes = new HashMap<TextAttribute, Object>();  

textAttributes.put(TextAttribute.FAMILY, "Arial");  
textAttributes.put(TextAttribute.SIZE, 25f);  
textAttributes.put(TextAttribute.KERNING, TextAttribute.KERNING_ON);  

Font font = Font.getFont(textAttributes);  

This forum thread contains a more detailed discussion on the topic:

此论坛主题包含有关该主题的更详细讨论:

http://forums.sun.com/thread.jspa?threadID=5359127

#3


on my way in finding the kerning information and providing it offline within my javaScript, i read this question here, too and since there was no answer, i searched further on and at the end i got this:

在我查找字距调整信息并在我的javaScript中提供离线的途中,我也在这里阅读了这个问题,因为没有答案,我进一步搜索,最后得到了这个:

Two javascript objects which can be indexed by the unicode of the glyph:

两个javascript对象,可以通过字形的unicode索引:

GLYPHS = {};
KERNS  = {};

And they are set like this:

它们设置如下:

// GLYPHS[ unicode ] = [ "name", width ];
// KERNS [ unicode ] = { "nextGlyphName"        : horizontalAdjustment };
//                   = { GLYPHS[ unicode ][ 0 ] : horizontalAdjustment };

So if i have my "Text" string, i go through it character by character and use his unicode to access the glyphs name and width like this:

所以,如果我有我的“文本”字符串,我逐字逐句地使用它并使用他的unicode访问字形名称和宽度,如下所示:

glUnicode = "Text".charCodeAt( strIndex );            // "T" == 84
glName    = GLYPHS[ glUnicode ][ 0 ];
glWidth   = GLYPHS[ glUnicode ][ 1 ];

To access the kerning value, we have to look at the next characters unicode value which is this:

要访问字距调整值,我们必须查看下一个字符unicode值,即:

nextGlyphUnicode = "Text".charCodeAt( strIndex + 1 ); // "e" == 101

And if the following object exists, then this statement will give you the kerning width (you have to check for existence first like this:

如果存在以下对象,则此语句将为您提供字距调整宽度(您必须首先检查存在,如下所示:

if ( !( KERNS[ glUnicode ] == undefined ) ) {
  if ( !( KERNS[ glUnicode ][ GLYPHS[ nextGlyphUnicode ][ 0 ] ] == undefined ) ) {
  ...

):

kernWidth = KERNS[ glUnicode ][ GLYPHS[ nextGlyphUnicode ][ 0 ] ];

In this example, kernWidth for the "e" following the "T" would be

在这个例子中,“T”之后的“e”的kernWidth将是

kernWidth == -143

I think, this is what you were looking for, right? All information accessible via the unicode value of the character and the unicode value of the following character. Very simple and very nice.

我想,这就是你要找的,对吧?所有信息都可通过字符的unicode值和后续字符的unicode值访问。非常简单,非常好。

So i created a file for each font and the first page looks like this:

所以我为每种字体创建了一个文件,第一页看起来像这样:

// Family Name
// 'Times New Roman'
// EM size
// '2048'
// is_quadratic
// '1'
//
// GLYPHS[ unicode ] = [ "name", width ];
// KERNS [ unicode ] = { "nextGlyphName"        : horizontalAdjustment };
//                   = { GLYPHS[ unicode ][ 0 ] : horizontalAdjustment };
GLYPHS = {};
KERNS  = {};

GLYPHS[    32 ] = [ "space",  512 ];
KERNS [    32 ] = {
  "Upsilondieresis" :   -76,
  "Upsilon"         :   -76,
  "Tau"             :   -37,
  "Lambda"          :  -113,
  "Delta"           :  -113,
  "Alpha"           :  -113,
  "Alphatonos"      :  -113,
  "Y"               :   -76,
  "W"               :   -37,
  "V"               :   -37,
  "T"               :   -37,
  "A"               :  -113
};
GLYPHS[    33 ] = [ "exclam",  682 ];
GLYPHS[    34 ] = [ "quotedbl",  836 ];
GLYPHS[    35 ] = [ "numbersign", 1024 ];
GLYPHS[    36 ] = [ "dollar", 1024 ];
GLYPHS[    37 ] = [ "percent", 1706 ];
GLYPHS[    38 ] = [ "ampersand", 1593 ];
GLYPHS[    39 ] = [ "quotesingle",  369 ];
GLYPHS[    40 ] = [ "parenleft",  682 ];
GLYPHS[    41 ] = [ "parenright",  682 ];
GLYPHS[    42 ] = [ "asterisk", 1024 ];
GLYPHS[    43 ] = [ "plus", 1155 ];
GLYPHS[    44 ] = [ "comma",  512 ];
GLYPHS[    45 ] = [ "hyphen",  682 ];
GLYPHS[    46 ] = [ "period",  512 ];
GLYPHS[    47 ] = [ "slash",  569 ];
GLYPHS[    48 ] = [ "zero", 1024 ];
GLYPHS[    49 ] = [ "one", 1024 ];
KERNS [    49 ] = {
  "one"             :   -76
};
GLYPHS[    50 ] = [ "two", 1024 ];
GLYPHS[    51 ] = [ "three", 1024 ];
GLYPHS[    52 ] = [ "four", 1024 ];
GLYPHS[    53 ] = [ "five", 1024 ];
GLYPHS[    54 ] = [ "six", 1024 ];
GLYPHS[    55 ] = [ "seven", 1024 ];
GLYPHS[    56 ] = [ "eight", 1024 ];
GLYPHS[    57 ] = [ "nine", 1024 ];
GLYPHS[    58 ] = [ "colon",  569 ];
GLYPHS[    59 ] = [ "semicolon",  569 ];
GLYPHS[    60 ] = [ "less", 1155 ];
GLYPHS[    61 ] = [ "equal", 1155 ];
GLYPHS[    62 ] = [ "greater", 1155 ];
GLYPHS[    63 ] = [ "question",  909 ];
GLYPHS[    64 ] = [ "at", 1886 ];
GLYPHS[    65 ] = [ "A", 1479 ];
KERNS [    65 ] = {
  "quoteright"      :  -227,
  "y"               :  -188,
  "w"               :  -188,
  "v"               :  -152,
  "Y"               :  -188,
  "W"               :  -164,
  "V"               :  -264,
  "T"               :  -227,
  "space"           :  -113
};
GLYPHS[    66 ] = [ "B", 1366 ];
GLYPHS[    67 ] = [ "C", 1366 ];

You can copy the contents of each file you need into your sourcecode or read it in at runtime to have the objects availlable.

您可以将所需的每个文件的内容复制到源代码中,或者在运行时将其读入以使对象可用。

And the files can be created with the following script that runs fine within the fontforge's "embedded" python 2.7 interpreter. This script is designed for a windows machine, so you have to adapt your paths at first!

并且可以使用以下脚本创建文件,该脚本在fontforge的“嵌入式”python 2.7解释器中运行良好。此脚本专为Windows机器设计,因此您必须首先调整路径!

#
# run these two commands in the fontforge "embedded" python interpreter (ffpython.exe)
# >>> script = open( "Scripts\\Kernings.py", "r" )
# >>> exec script

import fontforge

fontFilenames = [
  "arial.ttf",
  "arialbd.ttf",
  "ariali.ttf",
  "arialbi.ttf",
  "ARIALN.TTF",
  "ARIALNB.TTF",
  "ARIALNI.TTF",
  "ARIALNBI.TTF",
  "calibri.ttf",
  "calibrib.ttf",
  "calibrii.ttf",
  "calibriz.ttf",
  "cambria.ttc",
  "cambriab.ttf",
  "cambriai.ttf",
  "cambriaz.ttf",
  "times.ttf",
  "timesbd.ttf",
  "timesi.ttf",
  "timesbi.ttf",
  "verdana.ttf",
  "verdanab.ttf",
  "verdanai.ttf",
  "verdanaz.ttf"
  ]

for actFontFile in fontFilenames :
  print( "c:\\windows\\fonts\\" + actFontFile )
  out  = open( "Scripts\\Kern_" + actFontFile[ : len( actFontFile ) - 4 ] + "_json.txt", "w" )
  font = fontforge.open( "c:\\windows\\fonts\\" + actFontFile )
  out.write(
      "// Family Name\n// '"  + font.familyname          + "'\n"
    + "// EM size\n// '"      + str( font.em )           + "'\n"
    + "// is_quadratic\n// '" + str( font.is_quadratic ) + "'\n"
    + "//\n"
    + '// GLYPHS[ unicode ] = [ "name", width ];\n'
    + '// KERNS [ unicode ] = { "nextGlyphName"        : horizontalAdjustment };\n'
    + "//                   = { GLYPHS[ unicode ][ 0 ] : horizontalAdjustment };\n"
    + "GLYPHS = {};\n"
    + "KERNS  = {};\n\n"
    )
  glyphIdIterator = font.__iter__()
  for glyphName in glyphIdIterator :
    if font[ glyphName ].unicode >=0 :
      kerningStrings = []
      outstring = ( "GLYPHS[ "
        + str( font[ glyphName ].unicode ).rjust( 5 ) + " ] = [ \""
        + glyphName + "\"," 
        + str( font[ glyphName ].width ).rjust( 5 ) + " ];\n"
        )
      subs = font[ glyphName ].getPosSub("*")
      if len( subs ):
        for sub in subs:
          if len( sub ):
            for subsub in sub:
              if str( subsub ).lower().find( "'kern'" ) >=0:
                kerningStrings.append(
                  ("  \"" + str( sub[ 2 ] ) + "\"").ljust( 20 )
                + ":"     + str( sub[ 5 ] ).rjust( 6 )
                )
                break
      krnStrLen = len( kerningStrings )
      if ( krnStrLen ) :
        outstring = outstring + ( "KERNS [ "
          + str( font[ glyphName ].unicode ).rjust( 5 ) + " ] = {" )
        for kerningString in kerningStrings :
          outstring = outstring + "\n" + kerningString + ","
        outstring = outstring.rstrip( "," )
        outstring = outstring + "\n};\n"
      out.write( outstring )
  out.close()
  font.close()

I hope, this can help. Thank you very much for your attention,

我希望,这可以提供帮助。非常感谢您的关注,

Richard

#1


The only libraries I know of that read the kerning info "somwhat" correctly are iText and FOP from Apache.

我所知道的唯一能够正确读取字距信息“somwhat”的库是来自Apache的iText和FOP。

http://www.1t3xt.info/api/com/lowagie/text/pdf/BaseFont.html http://svn.apache.org/viewvc/xmlgraphics/fop/tags/fop-0_95/src/java/org/apache/fop/fonts/ (a link to the svn as there seems to be no online api)

http://www.1t3xt.info/api/com/lowagie/text/pdf/BaseFont.html http://svn.apache.org/viewvc/xmlgraphics/fop/tags/fop-0_95/src/java/org / apache / fop / fonts /(链接到svn,似乎没有在线api)

#2


Starting with Java SE 6, Java can provide kerning information when the font provides it. It is off by default and can be turned on like this:

从Java SE 6开始,Java可以在字体提供时提供字距调整信息。它默认是关闭的,可以像这样打开:

Map<TextAttribute, Object> textAttributes = new HashMap<TextAttribute, Object>();  

textAttributes.put(TextAttribute.FAMILY, "Arial");  
textAttributes.put(TextAttribute.SIZE, 25f);  
textAttributes.put(TextAttribute.KERNING, TextAttribute.KERNING_ON);  

Font font = Font.getFont(textAttributes);  

This forum thread contains a more detailed discussion on the topic:

此论坛主题包含有关该主题的更详细讨论:

http://forums.sun.com/thread.jspa?threadID=5359127

#3


on my way in finding the kerning information and providing it offline within my javaScript, i read this question here, too and since there was no answer, i searched further on and at the end i got this:

在我查找字距调整信息并在我的javaScript中提供离线的途中,我也在这里阅读了这个问题,因为没有答案,我进一步搜索,最后得到了这个:

Two javascript objects which can be indexed by the unicode of the glyph:

两个javascript对象,可以通过字形的unicode索引:

GLYPHS = {};
KERNS  = {};

And they are set like this:

它们设置如下:

// GLYPHS[ unicode ] = [ "name", width ];
// KERNS [ unicode ] = { "nextGlyphName"        : horizontalAdjustment };
//                   = { GLYPHS[ unicode ][ 0 ] : horizontalAdjustment };

So if i have my "Text" string, i go through it character by character and use his unicode to access the glyphs name and width like this:

所以,如果我有我的“文本”字符串,我逐字逐句地使用它并使用他的unicode访问字形名称和宽度,如下所示:

glUnicode = "Text".charCodeAt( strIndex );            // "T" == 84
glName    = GLYPHS[ glUnicode ][ 0 ];
glWidth   = GLYPHS[ glUnicode ][ 1 ];

To access the kerning value, we have to look at the next characters unicode value which is this:

要访问字距调整值,我们必须查看下一个字符unicode值,即:

nextGlyphUnicode = "Text".charCodeAt( strIndex + 1 ); // "e" == 101

And if the following object exists, then this statement will give you the kerning width (you have to check for existence first like this:

如果存在以下对象,则此语句将为您提供字距调整宽度(您必须首先检查存在,如下所示:

if ( !( KERNS[ glUnicode ] == undefined ) ) {
  if ( !( KERNS[ glUnicode ][ GLYPHS[ nextGlyphUnicode ][ 0 ] ] == undefined ) ) {
  ...

):

kernWidth = KERNS[ glUnicode ][ GLYPHS[ nextGlyphUnicode ][ 0 ] ];

In this example, kernWidth for the "e" following the "T" would be

在这个例子中,“T”之后的“e”的kernWidth将是

kernWidth == -143

I think, this is what you were looking for, right? All information accessible via the unicode value of the character and the unicode value of the following character. Very simple and very nice.

我想,这就是你要找的,对吧?所有信息都可通过字符的unicode值和后续字符的unicode值访问。非常简单,非常好。

So i created a file for each font and the first page looks like this:

所以我为每种字体创建了一个文件,第一页看起来像这样:

// Family Name
// 'Times New Roman'
// EM size
// '2048'
// is_quadratic
// '1'
//
// GLYPHS[ unicode ] = [ "name", width ];
// KERNS [ unicode ] = { "nextGlyphName"        : horizontalAdjustment };
//                   = { GLYPHS[ unicode ][ 0 ] : horizontalAdjustment };
GLYPHS = {};
KERNS  = {};

GLYPHS[    32 ] = [ "space",  512 ];
KERNS [    32 ] = {
  "Upsilondieresis" :   -76,
  "Upsilon"         :   -76,
  "Tau"             :   -37,
  "Lambda"          :  -113,
  "Delta"           :  -113,
  "Alpha"           :  -113,
  "Alphatonos"      :  -113,
  "Y"               :   -76,
  "W"               :   -37,
  "V"               :   -37,
  "T"               :   -37,
  "A"               :  -113
};
GLYPHS[    33 ] = [ "exclam",  682 ];
GLYPHS[    34 ] = [ "quotedbl",  836 ];
GLYPHS[    35 ] = [ "numbersign", 1024 ];
GLYPHS[    36 ] = [ "dollar", 1024 ];
GLYPHS[    37 ] = [ "percent", 1706 ];
GLYPHS[    38 ] = [ "ampersand", 1593 ];
GLYPHS[    39 ] = [ "quotesingle",  369 ];
GLYPHS[    40 ] = [ "parenleft",  682 ];
GLYPHS[    41 ] = [ "parenright",  682 ];
GLYPHS[    42 ] = [ "asterisk", 1024 ];
GLYPHS[    43 ] = [ "plus", 1155 ];
GLYPHS[    44 ] = [ "comma",  512 ];
GLYPHS[    45 ] = [ "hyphen",  682 ];
GLYPHS[    46 ] = [ "period",  512 ];
GLYPHS[    47 ] = [ "slash",  569 ];
GLYPHS[    48 ] = [ "zero", 1024 ];
GLYPHS[    49 ] = [ "one", 1024 ];
KERNS [    49 ] = {
  "one"             :   -76
};
GLYPHS[    50 ] = [ "two", 1024 ];
GLYPHS[    51 ] = [ "three", 1024 ];
GLYPHS[    52 ] = [ "four", 1024 ];
GLYPHS[    53 ] = [ "five", 1024 ];
GLYPHS[    54 ] = [ "six", 1024 ];
GLYPHS[    55 ] = [ "seven", 1024 ];
GLYPHS[    56 ] = [ "eight", 1024 ];
GLYPHS[    57 ] = [ "nine", 1024 ];
GLYPHS[    58 ] = [ "colon",  569 ];
GLYPHS[    59 ] = [ "semicolon",  569 ];
GLYPHS[    60 ] = [ "less", 1155 ];
GLYPHS[    61 ] = [ "equal", 1155 ];
GLYPHS[    62 ] = [ "greater", 1155 ];
GLYPHS[    63 ] = [ "question",  909 ];
GLYPHS[    64 ] = [ "at", 1886 ];
GLYPHS[    65 ] = [ "A", 1479 ];
KERNS [    65 ] = {
  "quoteright"      :  -227,
  "y"               :  -188,
  "w"               :  -188,
  "v"               :  -152,
  "Y"               :  -188,
  "W"               :  -164,
  "V"               :  -264,
  "T"               :  -227,
  "space"           :  -113
};
GLYPHS[    66 ] = [ "B", 1366 ];
GLYPHS[    67 ] = [ "C", 1366 ];

You can copy the contents of each file you need into your sourcecode or read it in at runtime to have the objects availlable.

您可以将所需的每个文件的内容复制到源代码中,或者在运行时将其读入以使对象可用。

And the files can be created with the following script that runs fine within the fontforge's "embedded" python 2.7 interpreter. This script is designed for a windows machine, so you have to adapt your paths at first!

并且可以使用以下脚本创建文件,该脚本在fontforge的“嵌入式”python 2.7解释器中运行良好。此脚本专为Windows机器设计,因此您必须首先调整路径!

#
# run these two commands in the fontforge "embedded" python interpreter (ffpython.exe)
# >>> script = open( "Scripts\\Kernings.py", "r" )
# >>> exec script

import fontforge

fontFilenames = [
  "arial.ttf",
  "arialbd.ttf",
  "ariali.ttf",
  "arialbi.ttf",
  "ARIALN.TTF",
  "ARIALNB.TTF",
  "ARIALNI.TTF",
  "ARIALNBI.TTF",
  "calibri.ttf",
  "calibrib.ttf",
  "calibrii.ttf",
  "calibriz.ttf",
  "cambria.ttc",
  "cambriab.ttf",
  "cambriai.ttf",
  "cambriaz.ttf",
  "times.ttf",
  "timesbd.ttf",
  "timesi.ttf",
  "timesbi.ttf",
  "verdana.ttf",
  "verdanab.ttf",
  "verdanai.ttf",
  "verdanaz.ttf"
  ]

for actFontFile in fontFilenames :
  print( "c:\\windows\\fonts\\" + actFontFile )
  out  = open( "Scripts\\Kern_" + actFontFile[ : len( actFontFile ) - 4 ] + "_json.txt", "w" )
  font = fontforge.open( "c:\\windows\\fonts\\" + actFontFile )
  out.write(
      "// Family Name\n// '"  + font.familyname          + "'\n"
    + "// EM size\n// '"      + str( font.em )           + "'\n"
    + "// is_quadratic\n// '" + str( font.is_quadratic ) + "'\n"
    + "//\n"
    + '// GLYPHS[ unicode ] = [ "name", width ];\n'
    + '// KERNS [ unicode ] = { "nextGlyphName"        : horizontalAdjustment };\n'
    + "//                   = { GLYPHS[ unicode ][ 0 ] : horizontalAdjustment };\n"
    + "GLYPHS = {};\n"
    + "KERNS  = {};\n\n"
    )
  glyphIdIterator = font.__iter__()
  for glyphName in glyphIdIterator :
    if font[ glyphName ].unicode >=0 :
      kerningStrings = []
      outstring = ( "GLYPHS[ "
        + str( font[ glyphName ].unicode ).rjust( 5 ) + " ] = [ \""
        + glyphName + "\"," 
        + str( font[ glyphName ].width ).rjust( 5 ) + " ];\n"
        )
      subs = font[ glyphName ].getPosSub("*")
      if len( subs ):
        for sub in subs:
          if len( sub ):
            for subsub in sub:
              if str( subsub ).lower().find( "'kern'" ) >=0:
                kerningStrings.append(
                  ("  \"" + str( sub[ 2 ] ) + "\"").ljust( 20 )
                + ":"     + str( sub[ 5 ] ).rjust( 6 )
                )
                break
      krnStrLen = len( kerningStrings )
      if ( krnStrLen ) :
        outstring = outstring + ( "KERNS [ "
          + str( font[ glyphName ].unicode ).rjust( 5 ) + " ] = {" )
        for kerningString in kerningStrings :
          outstring = outstring + "\n" + kerningString + ","
        outstring = outstring.rstrip( "," )
        outstring = outstring + "\n};\n"
      out.write( outstring )
  out.close()
  font.close()

I hope, this can help. Thank you very much for your attention,

我希望,这可以提供帮助。非常感谢您的关注,

Richard