動機と環境
gnuplotでギリシャをUTF-8で利用する際に大分遠回りをしたの記録を残す。
Åなどの文字や惑星記号、日本語などを使いたいときにも参考になるだろう。
環境は、WSL2内ubuntu をwindows termnialで利用。
gnuplot のバージョンは5.2.8。ファイル編集は、主にvimでやっている。
gnuplotでUTF-8を使う方法(正当な方法)
個人サイト1や公式資料のFAQ2を参考にすれば、ギリシャ文字などを直打ちすればよいとのことである。
公式資料にも、UTF-8の環境下であれば特別なことは十中八九しなくてよい、と明記してある。
If you are working in a UTF-8 computing environment, you probably do not have to do anything special in gnuplot to use
ところが、x軸ラベルをλに仕様と次の様な記述をしても、公式のデモ3をコピーしてきても、正しくギリシャ文字は出力されなかった。況んや日本語をや。
set encoding utf8
set minussign
set xlabel "λ"
この原因はgnuplotにはなく、vimでファイルを保存する際に文字コードがうまく指定できていなかったことであった。
したがって、vimの方から
:set fenc=utf8
と保存時の文字コードを指定した後にファイルを保存すれば、正しくλが表示される。
つまり、ファイルの保存時に、UTF-8であることを明示すれば解決した。これに気づいた後は快適にUTF8でギリシャ文字などが扱えている。
gnuplotでUTF-8を使う方法(遠回り編)
さて、上述の問題に気づかず行った遠回りは、公式FAQの言うところの
Or you can use octal escape sequences to type them in byte-by-byte
を実践したことに始まる。文字コードをエスケープしてバイト毎に8進数で書けばいいのである。
16進数で書かれたutf8のコード表は割と簡単に見つかる4。8進数は見つけられない。そこで、16進数からエスケープ付き8進数への変換プログラムを作った。たかだか100行も行かぬコードで十分だ。まず、コード表4を睨んで、コードを見つける。λであれば、CEBB
が16進数でのコードである。これをお手製プログラムで変換すると\316\273
が得られる。そのため、
set encoding utf8
set minussign
set xlabel "\316\273"
と書けば良い。文字コード表を睨むのはしんどいし、スクリプトの可読性は最悪だ。
この運用をしばらく続けていたが、上述の保存時のエンコーディングの問題に気がついた次第である。
まとめ
gnuplotで非アスキー文字がうまく出力されないときは、一度保存のエンコーディングを調べてみよう。utf8にある記号などを利用する際に参考頂ければ幸いである。
変換のプログラム
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
constexpr int MAXLINE=100000;
constexpr int DIMDATA=12;
char upper(char c){
if('a' <= c && c <= 'z'){
c = c - ('a' - 'A');
}
return c;
}
int conv2digit(char hex[0]){
int res = 0 ;
switch (std::toupper(hex[0])){
case '0': res += 0*16; break;
case '1': res += 1*16; break;
case '2': res += 2*16; break;
case '3': res += 3*16; break;
case '4': res += 4*16; break;
case '5': res += 5*16; break;
case '6': res += 6*16; break;
case '7': res += 7*16; break;
case '8': res += 8*16; break;
case '9': res += 9*16; break;
case 'A': res +=10*16; break;
case 'B': res +=11*16; break;
case 'C': res +=12*16; break;
case 'D': res +=13*16; break;
case 'E': res +=14*16; break;
case 'F': res +=15*16; break;
default:
std::cerr << "something wrong with " << hex[1] << std::endl;
exit (-1);
}
switch (std::toupper(hex[1])){
case '0': res += 0*1; break;
case '1': res += 1*1; break;
case '2': res += 2*1; break;
case '3': res += 3*1; break;
case '4': res += 4*1; break;
case '5': res += 5*1; break;
case '6': res += 6*1; break;
case '7': res += 7*1; break;
case '8': res += 8*1; break;
case '9': res += 9*1; break;
case 'A': res +=10*1; break;
case 'B': res +=11*1; break;
case 'C': res +=12*1; break;
case 'D': res +=13*1; break;
case 'E': res +=14*1; break;
case 'F': res +=15*1; break;
default:
std::cerr << "something wrong with " << hex[1] << std::endl;
exit (-1);
}
return res;
}
int main(int argc, char ** argv){
if(argc != 2){
std::cerr <<" Error: useage is : this_command.exe hex" <<std::endl;
exit(1);
}
std::string hex(argv[1]);
//std::cout << hex << std::endl;
//std::stransform(hex.begin(), hex.end(), hex.begin(), [](unsigned char c){return std::toupper(c);});
for(int i=0; i< hex.size(); i+=2){
std::string substr = hex.substr(i,2);
//std::cout << substr << std::endl;
char tmp[2];
tmp[0]=hex.substr(i,1).c_str()[0];
tmp[1]=hex.substr(i+1,1).c_str()[0];
int digit = conv2digit(tmp);
std::cout <<"\\" << std::oct << digit ;
}
std::cout << std::endl;
return 0;
}