Учитывая последовательность символов Unicode, как я могу получить строку пробельных символов одинаковой ширины (по крайней мере, в моноширинных шрифтах, которые отображают каждый символ с одинарной или двойной шириной символов из Базовая латиница)?
Примеры
Например, если строка `a b c' из пяти символов выглядит так:
a b c
('a', пробел, 'b', пробел, 'c'), я хотел бы получить строку, состоящую всего из пяти пробелов:
\u0020\u0020\u0020\u0020\u0020
и учитывая \u6b22\u8fce\u5149\u4e34
, который выглядит как
欢迎光临
Я хочу получить строку, содержащую четыре идеографических пробела: \u3000\u3000\u3000\u3000
.
Фон
Вот пример, когда это имеет значение: сообщения об ошибках в компиляторах для языков, поддерживающих Unicode. Предположим, что у нас есть некий гипотетический язык программирования PL (это может быть Python, Java, Scala, Ruby...), в котором есть строковые литералы и круглые скобки. Предположим, что это недопустимый фрагмент PL-кода, так как он содержит несовпадающую скобку:
"stringLiteral")
Если бы мы попытались его скомпилировать, компилятор PL мог бы выдать сообщение об ошибке следующего вида:
:1: error: ';' expected but ')' found.
"stringLiteral")
^
Обратите внимание на «фантомную строку», за которой следует '^'
в последней строке: она точно указывает на непарную закрывающую скобку.
Если я попробую то же самое с персонажами CJK, вот что я получу:
:1: error: ';' expected but ')' found.
"欢迎光临欢迎光临欢迎光临欢迎光临欢迎光临欢迎")
^
Обратите внимание, что теперь «фантомная строка» в последней строке состоит из обычных латинских пробелов, а в консоли '^'
выглядит так, как будто она находится где-то посередине строки символов CJK, а не в скобках.
Если я попробую то же самое с хорватскими персонажами:
:1: error: ';' expected but ')' found.
"DŽDždžLJLjljNJNjnj")
^
указатель '^'
также оказывается в, казалось бы, совершенно неправильной позиции, потому что эти специальные хорватские символы намного шире, чем обычные пробелы.
Все примеры дают одинаковые результаты на таких языках, как Python, Java, Scala, Ruby (просто скопируйте и вставьте " y⃝e҈s҉ ")
или "临欢迎光临欢迎")
в интерактивную оболочку и посмотрите, где заканчивается ^
).
Попытка решения
Вот наивная попытка генерировать "фантомные" строки в Scala. Существует метод Character.isIdeographic
а>. Я могу использовать его для определения метода phantom
, сопоставляя каждый идеографический символ с \u3000
, а все остальные символы с ' '
(обычный пробел).
def phantom(s: String) =
s.map(c => if (Character.isIdeographic(c)) '\u3000' else ' ')
В простых случаях работает. Например, если я определяю строку
val s = "foo欢迎光临欢迎bar光临欢baz"
а затем напечатайте строку, за которой следует вертикальная черта |
, разрыв строки, а затем phantom(s)
, за которой следует вертикальная черта |
,
println(s + "|\n" + phantom(s) + "|")
то я получаю:
foo欢迎光临欢迎bar光临欢baz|
|
и вертикальные полосы в конце струн идеально совпадают, потому что phantom(s)
теперь
\u0020\u0020\u0020\u3000\u3000\u3000\u3000\u3000\u3000\u0020\u0020\u0020\u3000\u3000\u3000\u0020\u0020\u0020
то есть:
- три обычных пробела, соответствующие «foo»
- шесть идеографических пробелов, соответствующих части "欢迎光临欢迎"
- затем снова три пробела, соответствующие «бару»
- ...
и так далее.
Однако, если я попробую то же самое с хорватскими символами, я снова получу беспорядок:
DŽDždžLJLjljNJNjnj|
|
(вертикальные полосы не совпадают).
Вопрос
Определяет ли Unicode какие-либо свойства, которые позволили бы мне генерировать надежные «фантомные» строки одинаковой ширины?