Ubuntu Weekly Recipe

第437回 LibreOfficeはreOfficeのライブラリではありません 〜LibreOffice Onlineを支える技術

この記事を読むのに必要な時間:およそ 17 分

スクリーンショットをCalcに貼り付けるプログラム

Microsoft OfficeにはExcelというとても強力な事務書類作成ツールが存在します。グリッドに沿った文字列の配置,強力で柔軟な罫線ツール,簡単に配置可能な入力フォームといった便利な機能のおかげで,日々の業務における,事務書類(の雛形)作成の生産性を「向上」させていることでしょう。

LibreOfficeにもExcelに似たUIのツールとしてCalcが存在しますが,こちらは表計算ソフトという位置づけです。しかしながらExcelでできることはたいていCalcでもできます。Excelの主な用途に,各シートへのスクリーンショットの貼り付けが存在するということを,小鳥の噂で聞いたことがあります。そこで最後のサンプルとして,一定時間ごとにスクリーンショットを撮影し,新しいシートにそれを貼り付けるコマンドを作成してみました。

lok_screenshot.cpp

#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <fstream>
#include <mutex>
#include <condition_variable>
#include <thread>
#define LOK_USE_UNSTABLE_API
#include <LibreOfficeKit/LibreOfficeKit.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>

class LokTools {
public:
    static const std::string LO_PATH;

    LokTools(std::string path) :
        isUnoCompleted(false),
        _lo(NULL),
        _doc(NULL) {
        _lo = lok::lok_cpp_init(path.c_str());
        if (!_lo)
            throw;
    };

    ~LokTools() {
        if (_doc) delete _doc;
        if (_lo) delete _lo;
    };

    int open(std::string file) {
        _doc = _lo->documentLoad(file.c_str());
        if (!_doc) {
            std::cerr << "Error: Failed to load document: ";
            std::cerr << _lo->getError() << std::endl;
            return -1;
        }

        _doc->registerCallback(docCallback, this);
        return 0;
    };

    static void docCallback(int type, const char* payload, void* data) {
        LokTools* self = reinterpret_cast<LokTools*>(data);

        switch (type) {
        case LOK_CALLBACK_UNO_COMMAND_RESULT:
            {
                std::unique_lock<std::mutex> lock(self->unoMtx);
                self->isUnoCompleted = true;
            }
            self->unoCv.notify_one();
            break;
        default:
            ; /* do nothing */
        }
    };

    void postUnoCommand(const char *cmd, const char *args = NULL,
                        bool notify = false) {
        isUnoCompleted = false;
        _doc->postUnoCommand(cmd, args, notify);

        if (notify) {
            std::unique_lock<std::mutex> lock(unoMtx);
            unoCv.wait(lock, [this]{ return isUnoCompleted;});
        }
    };

    int takeScreenshot(char col, char row, int sheet) {
        if (!_doc) return -1;

        if (_doc->getDocumentType() != LOK_DOCTYPE_SPREADSHEET) {
            std::cerr << "Error: Is not Calc file (type = ";
            std::cerr << _doc->getDocumentType() << ")" << std::endl;
            return -1;
        }

        if (sheet >= _doc->getParts()) {
            std::string sheetName = "Sheet" + std::to_string(sheet+1);
            std::string sheetCmd = ".uno:Add";
            std::string sheetArg = "{"
                "\"Name\":{"
                    "\"type\":\"string\","
                    "\"value\":\"" + sheetName + "\""
                "}}";
            postUnoCommand(sheetCmd.c_str(), sheetArg.c_str(), true);
        }
        _doc->setPart(sheet);

        gotoCell(std::string(1, col), std::string(1, row));

        /* Take screenshot */
        std::string fileName = "/tmp/lok_tools_screen.png";
        std::string cmdName = "import -window root " + fileName;
        if (system(cmdName.c_str())) {
            std::cerr << "Error: Failed to take screenshot" << std::endl;
        }

        insertGraphic(fileName);
        gotoCell("A", "1");

        return 0;
    };

    void gotoCell(const std::string &col, const std::string &row) {
        std::string command = ".uno:GoToCell";
        std::string arguments = "{"
            "\"ToPoint\":{"
                "\"type\":\"string\","
                "\"value\":\"$" + col + "$" + row + "\""
            "}}";
        postUnoCommand(command.c_str(), arguments.c_str(), true);
    };

    void insertGraphic(const std::string &file) {
        std::string scheme = "file://";
        std::string command = ".uno:InsertGraphic";
        std::string argument = "{"
            "\"FileName\":{"
                "\"type\":\"string\","
                "\"value\":\"" + scheme + file + "\""
            "}}";
        postUnoCommand(command.c_str(), argument.c_str(), true);
    };

    int save() {
        postUnoCommand(".uno:Save", NULL, true);
        return 0;
    };

    std::mutex unoMtx;
    std::condition_variable unoCv;
    bool isUnoCompleted;

private:
    lok::Office *_lo;
    lok::Document *_doc;
};
const std::string LokTools::LO_PATH = "/usr/lib/libreoffice/program";

void usage(const char *name)
{
    std::cerr << "Usage: " << name;
    std::cerr << " [-p path-of-libreoffice] [-c column] [-r row]";
    std::cerr << " [-s sheets] [-i interval] CalcFile" << std::endl;
}

int main(int argc, char **argv)
{
    int opt;
    std::string lo_path = LokTools::LO_PATH;
    char column = 'A';
    char row = '1';
    int sheets = 1;
    int interval = 5;
    while ((opt = getopt(argc, argv, "p:c:r:s:i:")) != -1) {
        switch (opt) {
        case 'p':
            lo_path = std::string(optarg);
            break;
        case 'c':
            if (isalpha(*optarg))
                column = toupper(*optarg);
            break;
        case 'r':
            if (isdigit(*optarg))
                row = *optarg;
            break;
        case 's':
            sheets = atoi(optarg);
            break;
        case 'i':
            interval = atoi(optarg);
            break;
        default:
            usage(argv[0]);
            exit(EXIT_FAILURE);
        }
    }

    if (argc - optind < 1) {
        usage(argv[0]);
        exit(EXIT_FAILURE);
    }
    const char *calc_file = argv[optind++];


    try {
        LokTools lok(lo_path);

        if (lok.open(calc_file)) {
            std::cerr << "Error: Failed to open" << std::endl;
            exit(EXIT_FAILURE);
        }

        for (int i = 0; i < sheets; ++i) {
            std::cout << "Take screenshot for Sheet" << i+1 << std::endl;

            if (lok.takeScreenshot(column, row, i)) {
                std::cerr << "Error: Failed to take screenshot" << std::endl;
                exit(EXIT_FAILURE);
            }

            std::this_thread::sleep_for(std::chrono::seconds(interval));
        }

        if (lok.save()) {
            std::cerr << "Error: Failed to save document" << std::endl;
            exit(EXIT_FAILURE);
        }

    } catch (const std::exception & e) {
        std::cerr << "Error: " << e.what() << std::endl;
        exit(EXIT_FAILURE);
    }

    return 0;
}

ビルド方法は次のとおりです。

$ g++ lok_screenshot.cpp -Wall -Werror -std=c++11 -ldl -o lok_screenshot
$ ./lok_screenshot -c F -r 5 -s 10 -i 5 sample.ods

-c-rはスクリーンショットを貼り付ける列と行の指定です。-sは撮影するスクリーンショットの枚数で,-iは撮影間隔です。上記コマンドだと「5秒ごとにスクリーンショットを撮影し,F5セルに貼り付けることを10回繰り返す」ということになります。

Impressの時にも説明したように,シートの数はgetParts()で取得できます。シートを増やしたい場合は.uno:Addコマンドを使用します。

スクリーンショットの撮影はImageMagickのimportコマンドを使うことにしました。デスクトップ版のUbuntuなら最初から入っているはずです。importコマンドのオプションを変更すれば,特定のウィンドウや特定の領域のみ作成することも可能です。

画像ファイルをセルに貼り付けるコマンドは.uno:InsertGraphicです。今回はファイル名以外の引数は設定していませんが,フィルタをかけることなども可能なようです。paste()でもできるとは思いますが,ファイルを開く部分もあわせてLibreOfficeに任せられるので,こちらの方が簡単です。貼り付けたあとに-iオプションで指定した分,スリープします。

作業の割には,一つ前のプログラムに比べて特に複雑なことはしていません。たとえばImpressのスライドを出力した時のように,PNG++と組み合わせれば,画像を埋め込む代わりに,各ピクセルをセルにして「表」にしてしまうことも可能です。むしろそのほうが,すべてのピクセルの統計情報を「計算」できるので,より表計算らしい使い方と言えるでしょう。なに,そんなことをする意味は,あとから考えればいいのです。

LibreOfficeの可能性は無限大

このようにLibreOfficeには外部からの操作ができるような仕組みが備わっています。LibreOfficeKitでなくても,python3-unoパッケージをインストールすればPythonからUNO API経由でもっと簡単に操作できるでしょう。日々同じようなフォーマットのオフィスドキュメントの入力に疲れている方は,LibreOfficeを使って「Software Defined Office Document」な世界に足を踏み入れてはいかがでしょうか。

ちなみにUbuntu Phone向けアプリであるubuntu-doc-viewerは,プラグインとしてLibreOfficeファイルの閲覧にも対応しています。これはLibreOfficeKitを用いてタイル画像に描画しつつ,Qt5/QML用のLibreOffice描画プラグインを作ってその画像データを画面に表示させているというものです。

また最近のUbuntu Phoneはフル機能のLibreOfficeを起動することも可能です。スマートフォンぐらいの画面サイズだと厳しいですが,SlimPort/HDMI経由やMiracast経由での外部ディスプレイ出力にも対応しているので,速度的な点に目を瞑ればPCがなくてもLibreOfficeファイルを直接編集できます。

著者プロフィール

柴田充也(しばたみつや)

Ubuntu Japanese Team Member株式会社 創夢所属。数年前にLaunchpad上でStellariumの翻訳をしたことがきっかけで,Ubuntuの翻訳にも関わるようになりました。