OTP interne

Introduction

Un OTP est ce qui permet à Oméga de convertir un flux de caractères en un autre. Par exemple, pour passer d'un codage à un autre (du codage d'entrée vers le codage utilisé par Oméga (UCS2), ou du codage utilisé par Oméga vers celui de la fonte).

On peut écrire les OTP dans un langage de programmation quelconque (on parle alors d'OTP externe : on dispose de toute la puissance du langage de programmation choisi, mais ce n'est pas très portable) ou dans un format qui sera directement reconnu par Oméga (un OTP interne).

Un OTP est un fichier texte, humainement compréhensible (en fait, ce n'est pas l'OTP qu'Oméga va lire, mais l'OCP, sa forme compilée, qui contient exactement la même chose et qui est créé à l'aide de la commande otp2ocp).

Ses premières lignes sont souvent

input: 1;
output: 2;

ce qui signifie que le flux d'entrée contient des caractères codés sur 1 octet et le flux de sortie des caractères codés sur deux octets.

Vient ensuite la ligne

expressions:

puis une liste d'expressions qui indiquent comment les caractères doivent être convertis. Par exemple, si on veut taper du grec sous la forme « <'ubris », on aura des choses du genre

`<'`''`u'   =>    #(@"1F55)  ;
`b'         =>    #(@"03B2)  ;
`i'         =>    #(@"03B9)  ;
`r'         =>    #(@"03C1)  ;
`s'         =>    #(@"03C3)  ;

Romaji

Nous allons écrire un OTP qui permet d'obtenir du japonais (hiragana, c'est l'une des écritures phonétiques du japonais) en tapant uniquement la transcription phonétique des caractères. Par exemple, en tapant « kon'nichiha » on obtiendra « こんにちは ».

Le fichier romkana.cnv (un morceau de xjdic, de J. Breen) contient déjà la liste des syllabes et de leur transcription, sous la forme suivante.

#   This is the Romaji to Kana conversion file for JDIC and XJDIC
#   Any line beginning with a # is a comment.
#   Each data line must contain a kana/romaji pair, separated by one or
#   more spaces, the rest of the line is a comment.
#   Each entry can have at most 6 ascii or 3 kana characters.
#   Order doesn't matter.
#
#   As distributed, the file should handle most of the common Hepburn and
#   Kunrei moras. Note that some mappings are commented out. These can
#   modified according to user taste.
#   Feel free to add to this file, but don't delete the "*" or "q" lines
#   
#   The original version of this file was obtained from a couple of MOKE's
#   .hlp files. Thanks Mark.
#
$HIRAGANA TABLE   (don't delete this line)
ー -
・ .
ぁ xa
あ a
ぃ xi
い i
ぅ xu
う u
ぇ xe
え e
...
ゎ xwa
わ wa
ゐ wi
ゑ we
を wo
ん n'
ん q  note that q = n is necessary
$KATAKANA  (DON'T delete this line!)
ー -
・ .
ァ xa
ア a
...
ン q note that q = n is necessary
ヴ vu
ヴァ va
ヴィ vi
ヴェ ve
ヴォ vo
ヵ xka
ヶ xke

Nous allons donc le lire et le transformer de la manière suivante.

#! perl -w
  
use Unicode::String qw(utf8);
  
use strict;
use constant TRUE => (0==0);
use constant FALSE => (0==1);
$|++;
  
open(ROMKANA, '<', "romkana.utf8")
  || die "Cannot open romkana.utf8 for reading: $!";
  
print "input: 1;
output: 2;
  
expressions:
";
  
my $wanted = FALSE;
while(<ROMKANA>){
  s/\#.*//;
  $wanted = FALSE if m/^\$/;
  # Change the following line if you want katakana
  $wanted = TRUE if m/HIRAGANA/;
  next if m/^\$/;
  next unless $wanted;
  next unless m/^([^\s]+)\s+([^\s]+)/;
  my($kana, $romaji) = (utf8($1), $2);
  for(my $i=0; $i<length($romaji); $i++){
    print "`". substr($romaji, $i, 1) ."'";
  }
  print " => ";
  foreach my $num ($kana->unpack){
    printf "\#(\@\"%0.2x) ", $num;
  }
  print "; \% $romaji -> $kana\n";
}
close(ROMKANA);

Le fichier ainsi créé ressemble à

input: 1;
output: 2;
  
expressions:
`-' => #(@"30fc) ; % - -> ー
`.' => #(@"30fb) ; % . -> ・
`x'`a' => #(@"3041) ; % xa -> ぁ
`a' => #(@"3042) ; % a -> あ
`x'`i' => #(@"3043) ; % xi -> ぃ
`i' => #(@"3044) ; % i -> い
`x'`u' => #(@"3045) ; % xu -> ぅ
`u' => #(@"3046) ; % u -> う
...
`x'`w'`a' => #(@"308e) ; % xwa -> ゎ
`w'`a' => #(@"308f) ; % wa -> わ
`w'`i' => #(@"3090) ; % wi -> ゐ
`w'`e' => #(@"3091) ; % we -> ゑ
`w'`o' => #(@"3092) ; % wo -> を
`n'`'' => #(@"3093) ; % n' -> ん
`q' => #(@"3093) ; % q -> ん

On peut maintenant créer l'OTP, le compiler

perl make_otp_hiragana.pl > romaji_to_hiragana.otp
otp2ocp romaji_to_hiragana

et l'essayer

\documentclass[a4paper,12pt]{article}
\usepackage[english]{babel}
\usepackage{omega}
    
%% La fonte
\usepackage[T1]{fontenc}
\DeclareFontFamily{T1}{cyber}{}
\DeclareFontShape{T1}{cyber}{m}{n}{<-> ombitstreamcyberbitroman}{}
\def\cyber{\fontfamily{cyber}\selectfont}
  
%% L'OTP
\ocp\JpHiraganaUni=romaji_to_hiragana
\ocplist\HiraganaOCP=
   \addbeforeocplist 1 \JpHiraganaUni
\nullocplist
\newenvironment{japanese}{\pushocplist\HiraganaOCP\cyber}{}
  
\begin{document}
\pagestyle{empty}
\begin{japanese}
  kon'nichiwa
\end{japanese}
\end{document}

*

Classes de caractères

Voici un exemple plus compliqué. On sépare les caractères en trois classes : ceux avant lesquels on n'a pas le droit d'aller à la ligne

ぁぃぅぇぉゎゃゅょっァィゥェォヮャュョヵヶー。、」』》)

ceux après lesquels on n'a pas le droit d'aller à la ligne

「『《(

et les autres. On demande à l'OTP d'insérer une macro \CJKunbreakablekern là où il est interdit de couper et une macro \CJKbreakablekern là où c'est autorisé. Voici l'OTP. On définit les classes de caractères exactement comme des expressions régulières. La flèche <= permet de remettre un (ou plusieurs) caractères dans le flux d'entrée.

input: 2;
output: 2;
  
aliases:
  
FORBIDDEN_AFTER = (@"300C | @"300E | @"300A);
FORBIDDEN_BEFORE = (@"3041 | @"3043 | @"3045 | @"3047 | 
                    @"3049 | @"308E | @"3083 | @"3085 | 
                    @"3087 | @"3063 | @"30A1 | @"30A3 | 
                    @"30A5 | @"30A7 | @"30A9 | @"30EE | 
                    @"30E3 | @"30E5 | @"30E7 | @"30F5 | 
                    @"30F6 | @"3002 | @"3001 | @"300D | 
                    @"300F | @"300B);
ANY = .;
OTHER = ^( {FORBIDDEN_AFTER} | {FORBIDDEN_BEFORE} );
  
HIRAGANA = (@"3041-@"3096);
KATAKANA = (@"30A1-@"30FA);
  
expressions:
  
{ANY}{FORBIDDEN_BEFORE} => #(\1) "\CJKunbreakablekern "
                        <= #(\2);
  
{FORBIDDEN_AFTER}{ANY} => #(\1) "\CJKunbreakablekern "
                       <= #(\2);
  
{OTHER}{OTHER} => #(\1) "\CJKbreakablekern "
               <= #(\2);

Comme il est très pénible de taper des choses du genre @"30F5, on a écrit un programme qui écrit l'OTP.

#! perl -w
use strict;
use Unicode::String qw(utf8);
  
print 'input: 2;
output: 2;
  
aliases:
  
';
  
sub get_codes {
  my $string = shift;
  $string = utf8($string);
  return map { $_ = sprintf('@"%0.4X', $_) } ($string->unpack);
}
  
print 'FORBIDDEN_AFTER = ('.
      join(' | ', get_codes("「『《")) . ");\n";
  
print 'FORBIDDEN_BEFORE = ('.
      join(' | ', get_codes("ぁぃぅぇぉゎゃゅょっァィゥェォヮャュョヵヶー。、」』》")).");\n";
print "ANY = .;\n";
print "OTHER = ^( {FORBIDDEN_AFTER} | {FORBIDDEN_BEFORE} );\n";
  
print '
HIRAGANA = (@"3041-@"3096);
KATAKANA = (@"30A1-@"30FA);
';
  
print '
expressions:
  
{ANY}{FORBIDDEN_BEFORE} => #(\1) "\CJKunbreakablekern 1 "
                        <= #(\2);
  
{FORBIDDEN_AFTER}{ANY} => #(\1) "\CJKunbreakablekern 2 "
                       <= #(\2);
  
{OTHER}{OTHER} => #(\1) "\CJKbreakablekern 3 "
               <= #(\2);
';

À des fins de débuggage, on peut utiliser un OTP (externe) qui affiche sur la sortie d'erreur tout ce qu'on lui donne. (Attention, toutefois : si jamais l'un des OTP ajoute des macros dans le flux de texte, ces macros seront étendues avant de passer à l'OTP suivant. Dans un premier temps, j'avais défini les macros \CJKunbreakablekern et \CJKbreakablekern comme ne faisant rien, et je ne comprenais pas pourquoi rien n'apparaissait...)

#!/usr/bin/perl -w
use strict;
$|++;
print STDERR "OTP DEBUG START\n";
while(<>){
  print STDERR "OTP DEBUG: $_";
  print $_;
}
print STDERR "OTP DEBUG END\n";

Voici un exemple d'utilisation.

\documentclass[a4paper,12pt]{article}
\usepackage[english]{babel}
\usepackage{omega}
    
%% La fonte
\usepackage[T1]{fontenc}
\DeclareFontFamily{T1}{cyber}{}
\DeclareFontShape{T1}{cyber}{m}{n}{<-> ombitstreamcyberbitroman}{}
\def\cyber{\fontfamily{cyber}\selectfont}
  
%% Les OTPs
\ocp\OCPutf=inutf8
\ocp\OCPjphyph=japanese_hyphenation
\externalocp\OCPdebug=debug.pl {}
\ocplist\JapaneseOCP=
  \addbeforeocplist 1 \OCPutf
  \addbeforeocplist 1 \OCPjphyph
  \addbeforeocplist 1 \OCPdebug
\nullocplist
\newenvironment{japanese}{\pushocplist\JapaneseOCP\cyber}{}
  
% For debugging purposes
\def\CJKbreakablekern{ br }
\def\CJKunbreakablekern{ unbr }
% The real macros
\def\CJKbreakablekern{%
  \nobreak
  \hskip 0sp plus 2sp minus 2sp
  \nobreak
}
\def\CJKunbreakablekern{\hskip 0sp plus 2pt minus 2sp}

\begin{document}
\pagestyle{empty}
\begin{japanese}
  これはサンプル文章です。
  あー、「やっぱり」。
\end{japanese}
\end{document}

États

En fait, on peut carrément utiliser le langage des OTP pour écrire des automates finis. À titre d'exemple, nous allons écrire un OTP qui permet de mélanger du texte occidental (utilisant la fonte standard d'Oméga) à du texte oriental (utilisant la fonte cyberbit, et dans lequel on oublie les espaces).

Voici l'OTP.

input: 2;
output: 2;
  
states: E;
  
aliases:
  
WESTERN = (@"0000-@"2E7F);
EASTERN = (@"2E80-@"FFFF);
  
expressions:
  
<E>` ' => ;
<E>{WESTERN} => "\western " #(\1)
                <pop:>;
<E>{EASTERN} => #(\1);
{EASTERN} => "\eastern " #(\1)
              <push: E>;
{WESTERN} => #(\1);

Voici un exemple.

\documentclass[a4paper,12pt]{article}
\usepackage[english]{babel}
\usepackage{omega}
    
%% La fonte
\usepackage[OT1]{fontenc}
\DeclareFontFamily{T1}{cyber}{}
\DeclareFontShape{T1}{cyber}{m}{n}{<-> ombitstreamcyberbitroman}{}
\def\cyber{\fontencoding{T1}\fontfamily{cyber}\selectfont}
\def\omegafont{\fontencoding{OT1}\fontfamily{omlgc}\selectfont}
  
%% Les OTPs
\ocp\OCPutf=inutf8
\ocp\OCPew=melange
\externalocp\OCPdebug=debug.pl {}
\ocplist\JapaneseOCP=
  \addbeforeocplist 10 \OCPutf
  \addbeforeocplist 20 \OCPew
  \addbeforeocplist 30 \OCPdebug
\nullocplist
  
\def\western{\omegafont }
\def\eastern{\cyber }
  
\begin{document}
\pagestyle{empty}
\raggedright
\pushocplist\JapaneseOCP
  これはサンプル文章です。Avec un peu de français au milieu (avec des
  caractères rares : �uf, ^^^^0153uf ^^^^0152uf). Du grec : η υβρισ
  もう一つ、日本語でのサンプル文章。
  Du japonais avec plein d'espaces parasites :
  も う一 つ、 日本 語で のサ ンプ ル文 章 。
  Idem avec des retours-chariots.
  もう
  一つ
  、日
  本語
  での
  サン
  プル
  文章
  。
\end{document}

A FAIRE : dans cet exemple, le caractère \oe disparait
(complètement).

Exercice

Mélanger les deux OTP précédents.

Exercice

En grec ancien (du moins quand on apprend cette langue en France), le sigma a deux formes, selon qu'il est à la fin d'un mot (U+03C2) ou pas (U+03C3), et le bêta a deux formes, selon qu'il est au début d'un mot (U+03B2) ou pas (U+03D0). Écrire un OTP qui permette de taper sans faire attention à ces détails (on peut utiliser des états : début, intérieur ou fin d'un mot, mais on n'y est pas obligé).

A FAIRE

Lors de l'écriture de l'automate fini, on a parfois besoin de repérer la fin du flux (l'exemple est tiré de uni2cuni.otp).

(@"002B|@"002D|@"002E){ARABIC_NUMBER} end:
        => "{\textdir TLT{}" #(\1) #(\2) "}"
        ;

A FAIRE : je ne comprends pas la syntaxe suivante.

<MEDIAL>{QUADRIFORM}{ACCENT}
      => #(\1 + @"DC00) #(\2 + @"DA00) @"0620
      ;

A FAIRE : Il y a aussi des tableaux.

Vincent Zoonekynd
<zoonek@math.jussieu.fr>
latest modification on mar mai 14 13:03:01 CEST 2002