繁体   English   中英

如何确定 Unicode 字符的宽度

[英]How can I determine the width of a Unicode character

我和一个朋友正在用java编写我们自己的控制台,但是由于无法准确确定unicode字符的宽度,我们无法正确调整行。 这导致了不仅unicode的行,而且后面的行都被移动的问题。

有没有办法确定 unicode 的宽度?

可以在下面找到问题的屏幕截图。

这就是它的外观: https ://abload.de/img/richtigslkmg.jpeg

这是终端中的一个示例: https ://abload.de/img/terminal7dj5o.jpeg

这是 PowerShell 中的一个示例: https ://abload.de/img/powershelln7je0.jpeg

这是 Visual Studio 代码中的一个示例: https ://abload.de/img/visualstudiocode4xkuo.jpeg

这是 Putty 中的一个示例: https ://abload.de/img/putty0ujsk.png

编辑:

很抱歉这个问题不清楚。

它与显示宽度有关,在示例中,我尝试确定显示长度以使每行具有相同的长度。 函数real_length用于计算/确定并返回显示宽度。

这里的示例代码:

public static void main(String[] args) {
    String[] tests = {
        "Peter",
        "SHGAMI",
        "Marcel №1",
        "💏",
        "👨‍❤️‍👨",
        "👩‍❤️‍💋‍👩",
        "👨‍👩‍👦"
    };
    for(String test : tests) test(test);
}

public static void test(String text) {
    int max = 20;
    for(int i = 0; i < max;i++) System.out.print("#");
    System.out.println();
    System.out.print(text);
    int length = real_length(text);
    for(int i = 0; i < max - length;i++) System.out.print("#");
    System.out.println();
}

public static int real_length(String text) {
    return text.length();
}

tl;博士

使用代码点而不是char 避免调用String#length

input 
+ 
"#".repeat( targetLength - input.codePoints().toArray().length ) 

细节

您的问题忽略了显示任何代码。 所以我只能猜测你在做什么以及可能是什么问题。

避免char

我猜您的目标是根据需要附加一定数量的NUMBER SIGN字符以制作固定长度的文本行。

我猜问题是您使用的是旧版char类型或其包装类Character 自 Java 2 以来, char类型已基本被破坏。作为 16 位值, char在物理上无法表示大多数字符。

使用代码点编号

相反,在处理单个字符时使用代码点整数。 代码点是永久分配给Unicode 中定义的超过 140,000 个字符中的每一个的数字。

Java 5+ 中的各种类中添加了各种与代码点相关的方法: StringStringBuilderCharacter等。

在这里,我们使用String#codePoints来获取代码点的IntStream ,源代码中的每个字符对应一个元素。 我们使用StringBuilder#appendCodePoint来收集最终结果字符串的代码点。

final int targetLength = 10;
final int fillerCodePoint = "#".codePointAt( 0 ); // Annoying zero-based index counting.
String input = "😷🤠🤡";

int[] codePoints = input.codePoints().toArray();
StringBuilder stringBuilder = new StringBuilder();
for ( int index = 0 ; index < targetLength ; index++ )
{
    if ( index < codePoints.length )
    {
        stringBuilder.appendCodePoint( codePoints[ index ] );
    } else
    {
        stringBuilder.appendCodePoint( fillerCodePoint );
    }
}

或者,使用三元运算符缩短for循环。

for ( int index = 0 ; index < targetLength ; index++ )
{
    int codePoint = ( index < codePoints.length ) ? codePoints[ index ] : fillerCodePoint;
    stringBuilder.appendCodePoint( codePoint );
}

报告结果。

System.out.println( Arrays.toString( codePoints ) );
String output = stringBuilder.toString();
System.out.println( "output = " + output );

[128567、129312、129313]

输出=😷🤠🤡#######


可能有一种聪明的方法可以用流和 lambda 更简短地编写代码,但我现在想不出一个。

而且,可以巧妙地使用 Java 11+ 中的String#repeat方法。

String output = input + "#".repeat( targetLength - input.codePoints().toArray().length ) ;

不幸的是,对于您看似简单的问题,没有简单的解决方案,原因如下:

  • 根据所使用的字体,在控制台上呈现的字符的宽度可能(并且可能会)有所不同。 因此代码需要确定或假设目标字体以计算宽度。

  • System.out只是一个不知道也不关心字体和字符宽度的PrintStream ,因此任何解决方案都必须独立于它。

  • 即使您可以确定控制台上使用的字体,并且您有办法确定您尝试以该特定字体呈现的每个字符的宽度,这对您有什么帮助? 知道宽度的变化可能会让您巧妙地调整正在渲染的线条以使它们对齐,但这很可能是不切实际的。

  • 一个潜在的解决方案是保留您的代码,并在println()正在写入的控制台上使用等宽字体,但这种方法仍然存在一些主要问题。 首先,您需要确定一种等宽字体,但也将支持您要渲染的所有字符。 包含表情符号时,这可能会出现问题。 其次,即使您识别出这种字体,您也可能会发现该字体的所有字形都不是等宽的! 这样的字体将确保(比如说)小写i和大写W具有相同的宽度,但你也不能对表情符号做出这样的假设,你甚至不能假设“等宽”表情符号都会有一样的非标宽度! 第三,您标识的字体(如果存在的话)必须在您的目标环境(您的 PowerShell、您朋友的 PuTTY shell 等)中可用。 这不是一个主要障碍,但它是另一件需要担心的事情。

  • 您可能会发现渲染的文本因操作系统而异。 您的输出在 Linux 终端窗口中可能看起来对齐,但使用相同字体的相同输出在 PowerShell 窗口中可能未对齐。

鉴于所有这些,更好的方法可能是使用 Swing 或 JavaFX,您可以更好地控制正在呈现的输出。 即使您不熟悉这些技术,只需调整通过搜索获得的一些示例代码,也不会花费太长时间来获得工作。 即使考虑到学习曲线,它仍然比提出一个强大的解决方案来对齐写入任意控制台的任意字符所需的时间更少,因为这是一个很难解决的问题。

笔记:

听起来您正在寻找 POSIX wcwidthwcswidth函数的 Java 实现,它们实现了Unicode 技术报告 #11中定义的规则(专门关注 Unicode 代码点在呈现到固定宽度设备时的显示宽度 - 终端等)。 我所知道的唯一这样的 Java 实现是在JLine3 库中,这是为这一类引入的大量代码,但这可能是您最好的选择。

但是请注意,该代码似乎不完整。 例如,Unicode 代码点 0x26AA (⚪️) 被 JLine3 代码报告为宽度为 1,但在我测试过的每个平台上(包括此处的 StackOverflow 编辑器,它是一个固定宽度的“设备”),代码点显示在两列中。

祝你好运——这东西比看起来要复杂得多。 JVM 不幸的 UCS-2 历史(不是 Sun 的错 - 这是 Unicode 标准的错误时机)只会让事情变得更糟,正如其他人在这里所说的那样,避免像瘟疫一样的charCharacter数据类型- 它们不能正常工作您期望,并且使用这些类型的即时代码遇到包括来自 Unicode 补充平面的代码点在内的数据,几乎可以肯定其功能不正确(除非作者特别小心 - 你觉得幸运吗?😉)。

注意:这个答案与我之前的答案(我仍然支持)截然不同并且在质量上有所不同。

Java 应用程序(即不使用图形用户界面的应用程序)有一种简单的方法来获取以给定字体和给定字体大小呈现的字符串的宽度。 它需要使用一些即使在非 AWT 环境中也受支持的awt类。 这是使用问题中提供的数据的演示:

package fixedwidth;

import java.awt.Canvas;
import java.awt.Font;
import java.awt.FontMetrics;

public class FixedWidth {

    static String[] tests = {
        "Peter", "SHGAMI", "Marcel №1", "💏", "👨‍❤️‍👨", "👩‍❤️‍💋‍👩", "👨‍👩‍👦"
    };
    static Font smallFont = new Font("Monospaced", Font.PLAIN, 10);
    static Font bigFont = new Font("Monospaced", Font.BOLD, 24);

    /**
     * This code is based on an answer by SO user Lonzak. 
     * See SO Answer https://stackoverflow.com/a/18123024/2985643
     */
    public static void main(String[] args) {
        FontMetrics fm1 = new Canvas().getFontMetrics(FixedWidth.smallFont);
        FixedWidth.demo(tests, fm1);

        FontMetrics fm2 = new Canvas().getFontMetrics(FixedWidth.bigFont);
        FixedWidth.demo(tests, fm2);
    }

    static void demo(String[] tests, FontMetrics fm) {
        Font f = fm.getFont();
        System.out.println("\nFont name:" + f.getName() + ", font size:" + 
                f.getSize() + ", font style:" + f.getStyle());
        for (String test : tests) {
            int width = fm.stringWidth(test);
            System.out.println("width=" + width + ", data=" + test);
        }
    }
}

上面的代码基于用户 LonzakJava-FontMetrics without Graphics问题的这个旧答案。 这些 AWT 类允许您创建具有定义特征(即名称、大小、样式)的Font ,然后在使用该字体时使用FontMetrics实例来获取任意字符串的宽度。

这是运行上面显示的代码的输出:

Font name:Monospaced, font size:10, font style:0
width=30, data=Peter
width=60, data=SHGAMI
width=59, data=Marcel №1
width=10, data=💏
width=30, data=👨‍❤️‍👨
width=40, data=👩‍❤️‍💋‍👩
width=30, data=👨‍👩‍👦

Font name:Monospaced, font size:24, font style:1
width=70, data=Peter
width=149, data=SHGAMI
width=140, data=Marcel №1
width=25, data=💏
width=73, data=👨‍❤️‍👨
width=98, data=👩‍❤️‍💋‍👩
width=74, data=👨‍👩‍👦

笔记:

  • 第一组结果显示了使用纯 Monospaced 10 磅字体时问题中样本数据的宽度。 第二组结果显示了使用粗体等宽 24 磅字体时这些相同字符串的宽度。

  • 某些表情符号的宽度看起来不正确,但这是因为当将源代码和输出结果粘贴到 SO 中时,一些表情符号表示会发生变化,大概是因为浏览器中使用了不同的字体。 (我对源和输出都使用Monospaced 。)这是原始输出的屏幕截图,显示宽度至少看起来是合理的:

    IDE输出

  • 即使宽度是针对固定宽度字体 ( Monospaced ) 计算和渲染的,但很明显,无法从普通键盘字符的宽度预测表情符号的宽度。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM