понедельник, 30 мая 2011 г.

CERN ROOT и надписи на русском языке

Как это ни странно, но проблема поддержки кириллицы в CERN ROOT действительно существует. Я не буду утверждать, что такая проблема присутствует в версии ROOT для Windows, но в Linux она актуальна. Однако, если учесть как эта поддержка реализована в нативной графической подсистеме ROOT, а также то, насколько глючно реализован Qt бекэнд, который поддерживает более широкий набор шрифтов, чем нативный вариант, такая проблема должна существовать и в Windows.

В качестве примера возьмем простой скрипт, с помощью которого можно создать следующее изображение:


Будем считать, что нашей целью является создание изображения в формате eps или pdf с целью дальнейшей вставки его в статью для публикации. Сам скрипт выглядит следующим образом:

void  createHisto( void )
{
    TCanvas *  c1 = new TCanvas();
    TH1 *      histo = new TH1F( "h1", "DCS vs. Energy", 10, 400., 600. );
    Double_t   content[] = { 0.0, 0.115, 0.145, 0.158, 0.170, 0.182, 0.180,
                             0.172, 0.154, 0.140, 0.132, 0.0 };
    Double_t   errors[] = { 0.0, 0.0015, 0.0012, 0.0008, 0.0016, 0.0023, 0.0018,
                            0.0013, 0.0014, 0.0006, 0.0002, 0.0 };
    histo->SetContent( content );
    histo->SetError( errors );
    histo->GetXaxis()->SetTitle( "E_{cm}, MeV" );
    histo->GetYaxis()->SetTitle( "d#sigma/d#Omega, mb/sr" );
    histo->SetStats( kFALSE );          // do not draw stats box
    histo->Draw();
    c1->SaveAs( "c1.eps" );
}

void histoTest( void )
{
    gROOT->SetStyle( "Plain" );
    gStyle->SetTitleBorderSize( 0 );    // do not draw frames around title
    gStyle->SetTitleOffset( 1.2, "xy" );// do not let axes' titles overlap ticks
    createHisto();
    if ( gROOT->IsBatch() )
        exit( 0 );
}
Обратите внимание, что в подписях к осям гистограммы используется вариант Latex для ROOT, стиль gStyle установлен в Plain, а рамка вокруг титула удалена. Отсутствие рамки вокруг титула упрощает решение задачи в первом и третьем подходах (см. ниже).

Для сохранения картинки наверху в файле c1.eps нужно создать скрипт histoTest.C с указанным содержимым и запустить root в пакетном режиме:
root -b histoTest.C
Пусть нашей задачей будет замена титула изображения на ДСР в зависимости от энергии и меток mb/sr, MeV и cm на мб/ср, МэВ и цм соответственно.

Наивная подстановка русских надписей в скрипт histoTest.C приведет к выводу в изображение c1.eps "кракозябр". Что же не так с ROOT? Нативный графический бэкенд ROOT не поддерживает кириллицу. Точка. Для решения проблемы ниже представлены три подхода, которые приводятся в порядке улучшения качества решения и полноты, хотя ни один из них не является идеальным. Идеи второго и третьего подходов взяты из обсуждений здесь и здесь.
  1. Наиболее простое решение - сохранить исходное изображение в векторном формате svg и отредактировать его в каком-нибудь векторном редакторе, например в inkscape. Минусы здесь очевидны. Редактировать придется вручную. Кроме изменения текста придется изменять обрамляющие его элементы, например компоненты рамки вокруг титула (которую мы предусмотрительно убрали в исходном скрипте).
  2. Использование Qt бэкенда (Qt layer) . Это был бы идеальный вариант, если бы не невообразимая глючность данного бэкенда. Из основных глюков стоит упомянуть: полупрозрачный или полностью прозрачный фон и шрифт абсолютно всех меню, невозможность использовать диалог сохранения файлов (!), невозможность сохранить валидное изображение в форматах eps и pdf (!). В данном подходе пришлось сохранять изображение как Save -> c1.png. Для использования Qt бэкенда нужно записать в файл ~/.rootrc следующие строки:
    Gui.Backend:                $(ROOTGUI)
    Gui.Factory:                $(ROOTGUI)
    Gui.DefaultFont:            -*-helvetica-medium-r-*-*-12-*-*-*-*-*-iso8859-5
    Gui.MenuFont:               -*-helvetica-medium-r-*-*-12-*-*-*-*-*-iso8859-5
    Gui.MenuHiFont:             -*-helvetica-bold-r-*-*-12-*-*-*-*-*-iso8859-5
    Gui.DocFixedFont:           -*-courier-medium-r-*-*-12-*-*-*-*-*-iso8859-5
    Gui.DocPropFont:            -*-helvetica-medium-r-*-*-12-*-*-*-*-*-iso8859-5
    Gui.IconFont:               -*-helvetica-medium-r-*-*-10-*-*-*-*-*-iso8859-5
    Gui.StatusFont:             -*-helvetica-medium-r-*-*-10-*-*-*-*-*-iso8859-5
    
    Использование переменной среды ROOTGUI позволяет сохранить нативный бэкенд по умолчанию (однако придется добавить строку
    export ROOTGUI=native
    
    в ~/.bash_profile). Для того, чтобы запустить root с Qt бэкендом нужно ввести
    ROOTGUI=qt root histoTest_qt.C
    
    Скрипт histoTest_qt.C выглядит так:
    void  createHisto( void )
    {
        //TCanvas *  c1 = new TCanvas();
        TH1 *      histo = new TH1F( "h1", "´ÁÀ Ò ×ÐÒØáØÜÞáâØ Þâ íÝÕàÓØØ",
                                     10, 400., 600. );
        Double_t   content[] = { 0.0, 0.115, 0.145, 0.158, 0.170, 0.182, 0.180,
                                 0.172, 0.154, 0.140, 0.132, 0.0 };
        Double_t   errors[] = { 0.0, 0.0015, 0.0012, 0.0008, 0.0016, 0.0023, 0.0018,
                                0.0013, 0.0014, 0.0006, 0.0002, 0.0 };
        histo->SetContent( content );
        histo->SetError( errors );
        histo->GetXaxis()->SetTitle( "E_{æÜ}, ¼í²" );
        histo->GetYaxis()->SetTitle( "d#sigma/d#Omega, ÜÑ/áà" );
        histo->SetStats( kFALSE );          // do not draw stats box
        histo->Draw();
        //c1->SaveAs( "c1_qt.eps" );
    }
    
    void histoTest_qt( void )
    {
        gROOT->SetStyle( "Plain" );
        gStyle->SetTitleBorderSize( 0 );    // do not draw frames around title
        gStyle->SetTitleOffset( 1.1, "xy" );// do not let axes' titles overlap ticks
        createHisto();
        if ( gROOT->IsBatch() )
            exit( 0 );
    }
    
    Заметили "кракозябры" внутри скрипта? Это еще одно неудобство. Скрипт выведен в общепринятой на сегодня кодировке UTF-8, а был записан в кодировке ISO-8859-5. В vim сохранение файла в данной кодировке обеспечивается командой
    :set fenc=iso-8859-5
    
    Однако при повторной загрузке этого файла в vim появятся всё те же "кракозябры" и новые русские записи не удастся добавить без полного переписывания русских символов заново. Возможно существуют редакторы, в которых проблема перевода в разные кодировки решается проще.

    Еще один интересный момент. Создание канваса c1 в данном варианте закомментировано. Если оставить его незакомментированным, то диапазон оси ординат изменится, и на месте гистограммы окажется чистое белое поле (!). Это еще раз подтверждает тот факт, что с текущей реализацией Qt бэкенда нужно быть очень осторожным.

    Изображение, полученное с помощью Qt бэкенда, выглядит так:


    Заметьте также, что Qt бэкенд применил преобразования локали: вместо точек в вещественных числах на координатных осях стоят запятые. 
  3. Третий подход основан на Latex и двух латеховских модулях - standalone и psfrag. Оба модуля можно найти на ctan.org. В пакет texlive для Fedora 14 они не входят, однако существует отдельный репозиторий texlive, в котором эти модули присутствуют. Информация об этом репозитории здесь. Преимущество данного подхода в том, что он полностью автоматизирован. Скрипт histoTest_latex.C выглядит так:
    void  createHisto( void )
    {
        TCanvas *  c1 = new TCanvas();
        TH1 *      histo = new TH1F( "h1", "CT", 10, 400., 600. );
        Double_t   content[] = { 0.0, 0.115, 0.145, 0.158, 0.170, 0.182, 0.180,
                                 0.172, 0.154, 0.140, 0.132, 0.0 };
        Double_t   errors[] = { 0.0, 0.0015, 0.0012, 0.0008, 0.0016, 0.0023, 0.0018,
                                0.0013, 0.0014, 0.0006, 0.0002, 0.0 };
        histo->SetContent( content );
        histo->SetError( errors );
        histo->GetXaxis()->SetTitle( "CX" );
        histo->GetYaxis()->SetTitle( "CY" );
        histo->SetStats( kFALSE );          // do not draw stats box
        histo->Draw();
        c1->SaveAs( "c1_latex.eps" );
    }
    
    void histoTest_latex( void )
    {
        gROOT->SetStyle( "Plain" );
        gStyle->SetTitleBorderSize( 0 );    // do not draw frames around title
        gStyle->SetTitleOffset( 1.2, "xy" );// do not let axes' titles overlap ticks
        createHisto();
        if ( gROOT->IsBatch() )
            exit( 0 );
    }
    
    Главным отличием от исходного скрипта является то, что оригинальные титул и подписи к осям заменены метками CT, CX и CY, которые будут обрабатываться в теховском скрипте с помощью psfrag. Идея psfrag весьма проста - заменить метки в исходном ps или eps файле указанными значениями. Отмечу сразу, что метки для подстановки следует выбирать короткие, иначе psfrag может сгенерировать ломаный файл dvi. Скрипт c1_latex.tex выглядит так:
    \documentclass{standalone}
    
    \usepackage[T2A]{fontenc}
    \usepackage[utf8x]{inputenc}
    
    \usepackage[dvips]{graphicx,color}
    \usepackage{psfrag}
    
    \begin{document}
    \psfrag{CT}[lt][lt][1.8]{\bf ДСР в зависимости от энергии}
    \psfrag{CX}[rt][rt][1.4]{\bf $\mathbf{E_{\mbox{цм}}}$, МэВ}
    \psfrag{CY}[rt][rt][1.4]{\bf $\mathbf{d\sigma/d\Omega}$, мб/ср}
    \includegraphics{c1_latex.eps}
    \end{document}
    
    О параметрах psfrag можно прочитать в документации к данному пакету; кратко, первый параметр в фигурных скобках является заменяемой меткой, последний параметр в фигурных скобках - подстановкой, первые два параметра в квадратных скобках после метки - позиционными якорями, третий параметр в квадратных скобках - коэффициентом масштабирования шрифта. Пакет standalone позволяет сохранить исходные размеры изображения.

    Русификация надписей может быть произведена с помощью отдельного шелл-скрипта mkrus:
    #!/bin/sh
    # source root macro
    root -b histoTest_latex.C
    # run psfrag
    latex c1_latex
    # make pdf
    dvips c1_latex
    ps2pdf c1_latex.ps
    # delete temporary files
    rm c1_latex.eps c1_latex.ps c1_latex.dvi c1_latex.aux c1_latex.log
    # make eps
    pdftops -eps c1_latex.pdf
    
    Этот скрипт создает два изображения: c1_latex.eps и c1_latex.pdf, которые выглядят так:


    В данном изображении используется шрифт Serif - просто в моем дистрибутиве texlive не нашлось русского Sans Serif шрифта c кодировкой T2A (которая используется в c1_latex.tex). Если необходим шрифт Sans Serif, нужно установить его из внешних источников.

Комментариев нет:

Отправить комментарий