Как узнать предпочтительную ширину отображения (в столбцах) символов Юникода? - PullRequest
15 голосов
/ 03 сентября 2010

В различных кодировках Unicode, например UTF-16le или UTF-8 , символ может занимать 2 или 3 байта. Многие приложения Unicode не заботятся о ширине отображения символов Unicode, так как они все латинские буквы. Например, в текстовом столбце 80 , который должен содержать 40 китайских символов или 80 латинских букв в одной строке, но большинство приложений (например, Eclipse, Notepad ++, и все известные текстовые редакторы, осмелюсь, если есть какое-то хорошее исключение), просто посчитайте каждый китайский символ как 1 ширину как латинскую букву. Это, безусловно, делает формат результата некрасивым и не выровненным.

Например, ширина табуляции, равная 8, приведет к следующему ужасному результату (считайте весь Юникод как 1 ширину экрана):

apple   10
banana  7
苹果      6
猕猴桃     31
pear    16

Однако ожидаемый формат (считать каждый китайский символ как 2 ширины):

apple   10
banana  7
苹果    6
猕猴桃  31
pear    16

Неправильный расчет ширины отображения символов делает эти редакторы совершенно бесполезными при выравнивании табуляции, переносе строк и переформатировании абзацев.

Хотя ширина символа может варьироваться в зависимости от шрифта, но во всех случаях терминального шрифта фиксированного размера китайский символ всегда имеет двойную ширину. То есть, несмотря на шрифт, каждый китайский символ предпочтительно отображать с шириной 2.

Одним из решений является то, что я могу получить правильную ширину, преобразовав кодировку в GB2312 , в кодировке GB2312 каждый китайский символ занимает 2 байта. однако некоторые символы Unicode не существуют в кодировке GB2312 (или GBK кодировка). И вообще, не очень хорошая идея вычислять ширину экрана из закодированного размера в байтах.

Просто вычислять все символы в Юникоде в диапазоне (\u0080 .. \uFFFF) как 2 ширины также неверно, потому что в диапазоне также много символов шириной в 1 ширину.

Также сложно вычислить ширину отображения арабских и корейских букв, поскольку они строят слово / символ по произвольному количеству кодовых точек Unicode.

Таким образом, ширина отображения кодовой точки Unicode может быть не целым числом, я считаю, что это нормально, на практике они могут быть основаны на целых числах, по крайней мере лучше, чем ничего.

Итак, есть ли какой-либо атрибут, связанный с предпочтительной шириной отображения символа в стандарте Unicode? Или какая-нибудь функция библиотеки Java для вычисления ширины экрана?

Ответы [ 5 ]

20 голосов
/ 05 февраля 2012

Звучит так, будто вы ищете что-то вроде wcwidth и wcswidth, определенное в стандарте IEEE 1003.1-2001, но удаленное из ISO C:

Функция wcwidth() определяет количество позиций столбцов, необходимых для широкого символа wc .Функция wcwidth() должна либо возвращать 0 (если wc является нулевым кодом широких символов), либо возвращать количество позиций столбца, которые должны быть заняты кодом широких символов wc или верните -1 (если wc не соответствует печатному широкосимвольному коду).

Маркус Кун написал версию с открытым исходным кодом, wcwidth.c , на основе Unicode 5.0.Он включает описание проблемы и признание отсутствия стандартов в этой области:

В устройствах вывода фиксированной ширины все латинские символы занимают одну позицию "ячейки" равной ширины,тогда как идеографические символы CJK занимают две такие ячейки.Взаимодействие между приложениями терминальной линии и символьными терминалами (в стиле телетайпа), использующими кодировку UTF-8, требует согласования того, какой символ должен перемещать курсор на сколько позиций в ячейке.В настоящее время не существует установленных формальных стандартов, по которым символ Unicode должен занимать сколько позиций ячейки на символьных терминалах.Эти процедуры являются первой попыткой определения такого поведения на основе простых правил, применяемых к данным, предоставляемым Консорциумом Unicode.[...]

Он реализует следующие правила:

  • Нулевой символ (U + 0000) имеет ширину столбца 0.
  • Другие управляющие символы C0 / C1 и DEL приведут к возвращаемому значению -1.
  • Объединенные символы без пробелов и вмещающих символов (общий код категории Mn или Me в базе данных Unicode) имеют ширину столбца 0.
  • МЯГКИЙ ГИФЕН (U + 00AD) имеет ширину столбца 1.
  • Другие символы формата (общий код категории Cf в базе данных Unicode) и ZERO WIDTH SPACE (U + 200B) имеютширина столбца 0.
  • Медиальные гласные и заключительные согласные (Hang + Jamo) (U + 1160-U + 11FF) имеют ширину столбца 0.
  • Интервал между символами в Восточной Азии (W)или категория «Восточная Азия - полная ширина» (F), как определено в Техническом отчете Unicode # 11, имеет ширину столбца 2.
  • Все остальные символы (включая все печатаемые символы ISO 8859-1 и WGL4, управляющие символы Unicode,и т.д.) имеют ширину столбца 1.
4 голосов
/ 03 сентября 2010

Вы путаете кодовые точки, графемы и кодировки.

Кодирование - это то, как кодовые точки преобразуются в поток октетов для хранения, передачи или обработки. И UTF-8, и UTF-16 являются кодировками с переменной шириной, причем различным кодовым точкам требуется разное количество октетов (для UTF-8 от 1 до, IIRC, 6 и UTF-16 либо 2, либо 4).

Графемы - это «то, что мы видим как символ», это то, что отображается. Одна кодовая точка (например, LATIN LOWER CASE A) для одной графемы, но в других случаях может потребоваться несколько кодовых точек (например, LATIN LOWER CASE A, COMBINING ACUTE и COMBINING UNDERSCORE, чтобы получить строчные буквы с острым и подчеркиванием, используемые в Kwakwala ). В некоторых случаях существует более одной комбинации кодов для создания одной и той же графемы (например, LATIN LOWER CASE A WITH ACUTE и COMBINING UNDERSCORE), это «нормализация»,

т.е. длина кодирования одной графемы будет зависеть от кодировки и нормализации.

Ширина отображения графемы будет зависеть от гарнитуры, стиля и размера независимо от длины кодировки.

Для получения дополнительной информации см. Википедию по Юникод и Дом Юникода . Есть также несколько превосходных книг, возможно, наиболее заметных из них " Fonts & Encodings " Янниса Хараламбуса, О'Рейли.

3 голосов
/ 04 сентября 2010

Свойство Unicode, отражающее эту концепцию, равно East_Asian_Width .Это не очень надежно, так как визуальная ширина в контексте общего рендеринга Юникода, так как неазиатские символы, объединяющие символы и т. Д. Не будут выстраиваться в линию даже в моноширинном шрифте(Ваш пример, конечно, не выстроился в очередь для меня.)

Java не имеет встроенной возможности читать это свойство для символов (хотя расширение Android имеет).Вы можете получить его у ICU4J , если он вам действительно нужен.

2 голосов
/ 09 февраля 2012

Я считаю, что для правильного выполнения этой задачи вам необходимо учитывать тот компонент опубликованного стандарта Unicode, который известен как Приложение № 14 к стандарту Unicode, Алгоритм разрыва строки Unicode .

Если бы вы программировали на Perl, то, что вы хотели бы знать, было бы очень просто, потому что модуль Perl Unicode :: LineBreak , реализующий UAX # 14, включает в себя класс с простым методом columns, который сообщает вам правильный ответ для его строкового аргумента. Эти вещи особенно хорошо работают на азиатских языках, где больше ничего не поделаешь. Этот модуль включает более 6000 модульных тестов, активно поддерживается, и его автор сам является азиатом, поэтому для него важно, чтобы эти хитрые биты были абсолютно правильными.

Большинство внутренностей модуля - это библиотека, написанная на C. Я не смотрел, как вызвать его компонентную библиотеку C из других языков, кроме Perl, но вы можете посмотреть, возможно ли это.

1 голос
/ 25 июля 2012

Относительно «Или какой-либо функции библиотеки Java для вычисления ширины экрана?»: Если она есть, я ее никогда не находил.

Самый простой метод вычисления ширины символа / строки - написать его шрифтом Unicode GNU (http://unifoundry.com/unifont.html) и измерить ширину символа. Не чисто, но пока это работает для каждой кодировки, которую я могу придумать.

FWIW вот что я делаю:

java.awt.font.Font MONOSPACEFONT = Font.createFont(Font.TRUETYPE_FONT, 
    new File("unifont-5.1.20080907.ttf"));

java.awt.font.FontRenderContext FRC = new FontRenderContext(null, true, true);

int charWidth =  (int) (2.0*((java.awt.geom.Rectangle2D.Float) 
    MONOSPACEFONT.getStringBounds(stringToMeasure, FRC)).width);

... это должно работать практически везде, где вы развертываете JVM (она отлично работает в автономной среде).

...