エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

Ruby/RSVG と Cairo を使って SVG から PDF を生成しためも

前回C++ で書いたのですが、調べたら Ruby でも同じように書くことができることがわかりました。

今回は libRSVG を Ruby から利用する Ruby/RSVG (rsvg2 gem) を利用。

require 'rsvg2'

source = <<~EOS
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<svg
   xmlns='http://www.w3.org/2000/svg'
   version='1.1'
   width='210mm'
   height='297mm'
>
  <rect x='100' y='100' width='100' height='100' fill='red' />
  <text
    font-family='sans-serif'
    font-size='18'
    x='200'
    y='200'>エンジニアのソフトウェア的愛情</text>
  <text
    font-size='12'
    x='200'
    y='220'>または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか</text>
</svg>
EOS

svg = RSVG::Handle.new_from_data(source)

dim = svg.dimensions

puts "document size = #{dim.width}x#{dim.height}"

surface = Cairo::PDFSurface.new('sample.pdf', dim.width, dim.height)

cairo = Cairo::Context.new(surface)

cairo.render_rsvg_handle(svg)


結果は前回と同じです。

Cairo::PDFSurface.new の第一引数に文字列を渡すと出力先のファイルのファイル名と解釈します。第一引数に write メソッドを持つオブジェクトを渡すと、そのメソッドを使って結果を書き出します。

詳しくはこちら: http://cairo.rubyforge.org/doc/ja/cairo-pdf-surface.html



SVG をベタに書くのがになったので、 Haml で描き直してみました。
ついでに Cairo::PDFSurface.new の第一引数に Fileインスタンスwrite メソッドを持つオブジェクト)を渡しています。
HamlSVG を書く話は 以前の記事 なんかもご参照ください。

require 'haml'
require 'rsvg2'

source = Haml::Engine.new(<<EOS).to_html
!!! XML
%svg{xmlns: 'http://www.w3.org/2000/svg', version: '1.1', width: '210mm', height: '297mm'}
  %rect{x: 100, y: 100, width: 100, height: 100, fill: 'red'}
  %text{font: {family: 'sans-serif', size: 18}, x: 200, y: 200} エンジニアのソフトウェア的愛情
  %text{font: {size: 12}, x: 200, y: 220} または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか
EOS

svg = RSVG::Handle.new_from_data(source)

dim = svg.dimensions

puts "document size = #{dim.width}x#{dim.height}"

surface = Cairo::PDFSurface.new(File.new('sample.pdf', 'w'), dim.width, dim.height)

cairo = Cairo::Context.new(surface)

cairo.render_rsvg_handle(svg)

いつか読むはずっと読まない:バチガルピ短篇

イーガンをまとめて読んでいた時期があったせいか、第一篇を読んでてイーガンを読んでいるような錯覚をしてしまいました。設定が。
作風が似ているかどうか判断できるほど、わたしの SF 脳は肥えていない。

第六ポンプ (ハヤカワ文庫SF)

第六ポンプ (ハヤカワ文庫SF)

libRSVG と Cairo を使って SVG から PDF を生成しためも

それぞれのサイト。


今回参照したドキュメント。

#if 0
# このファイルを Ruby で実行すると、このファイルを C++ のソースとしてコンパイルします。
# $ ruby minimum-svg2pdf.cpp
system('g++ --std=c++11 `pkg-config --cflags --libs cairo librsvg-2.0` -o minimum-svg2pdf minimum-svg2pdf.cpp')
__END__
#endif

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>

#include <librsvg/rsvg.h>

#include <cairo/cairo.h>
#include <cairo/cairo-pdf.h>

static const std::string source =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"
"<svg"
"   xmlns=\"http://www.w3.org/2000/svg\""
"   version=\"1.1\""
"   width=\"210mm\""
"   height=\"297mm\""
">"
"  <rect x=\"100\" y=\"100\" width=\"100\" height=\"100\" fill=\"red\" />"
"  <text"
"    font-family=\"sans-serif\""
"    font-size=\"18\""
"    x=\"200\""
"    y=\"200\">エンジニアのソフトウェア的愛情</text>"
"  <text"
"    font-size=\"12\""
"    x=\"200\""
"    y=\"220\">または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか</text>"
"</svg>";

int main(int argc, char* argv[])
{
    GError* error = 0; 
    RsvgHandle* svg = rsvg_handle_new_from_data(reinterpret_cast<const guint8*>(source.c_str()), source.length() , &error);

    if(error != 0)
    {
        std::cout << error->message << std::endl;
        return 0;
    }

    if(svg == 0)
    {
        std::cout << "no handle" << std::endl;
        return 0;
    }

    RsvgDimensionData dim;
    rsvg_handle_get_dimensions(svg, &dim);

    std::cout << "document size = " << dim.width << "x" << dim.height << std::endl;

    cairo_surface_t* surface = cairo_pdf_surface_create("sample.pdf", dim.width, dim.height);
    cairo_t* cairo = cairo_create(surface);
    cairo_set_source_rgb(cairo, 1.0, 1.0, 1.0);
    rsvg_handle_render_cairo(svg, cairo);
    cairo_surface_flush(surface);

    cairo_destroy(cairo);
    cairo_surface_destroy(surface);

    rsvg_handle_close(svg, &error);

    if(error != 0)
    {
        std::cout << error->message << std::endl;
    }

    g_object_unref(svg);

    return 0;
}


コンパイルと実行*1

$ ruby minimum-svg2pdf.cpp
$ ./minimum-svg2pdf 
document size = 744x1052


こんなイメージの PDF ファイルが作成されます。

いつか読むはずっと読まない:今ここにある世界のSF

フィクションであり、フィクションでない。


遠隔機動歩兵 -ティン・メン- (ハヤカワ文庫SF)

遠隔機動歩兵 -ティン・メン- (ハヤカワ文庫SF)

*1:C/C++ のソースに Rubyスクリプトを埋め込んで自分に自分をコンパイルさせるやり方は横へなの鍋谷さんから教わりました。

SVG から PDF

PDF を生成する prawn と、PrawnSVG を解釈する prawn-svg を使って、haml で書いた SVG を PDF にした時のメモ。

prawn-svg の利用例として書かれているコードhaml で書き直したものです。

require 'prawn-svg'
require 'haml'

Prawn::Document.generate("test.pdf") do
  svg Haml::Engine.new(<<~EOS).to_html
    %svg
      %rect{width: 100, height: 100, fill: 'red'}
  EOS
end

QRコードを生成したときのめも

QRencode で QR コードを生成し、Magick++(ImageMagickC++ API)で画像ファイルに保存してみた、ときのめも。

この二つのライブラリは、Mac の場合、 Homebrew でインストールできます。


ソースコード

#include <iostream>
#include <sstream>
#include <algorithm>
#include <Magick++.h>
#include <qrencode.h>

int main(int argc, char* argv[])
{
    QRcode* qr = QRcode_encodeString(argv[1], 0, QR_ECLEVEL_H, QR_MODE_8, 1);
    int width = qr->width;

    std::ostringstream pbm_blob_sink;

    pbm_blob_sink << "P1\n" << width << " " << width << "\n";

    std::for_each(qr->data, qr->data + (width * width), [&](unsigned char data) { pbm_blob_sink << (data & 1) << " "; });

    std::string pbm_blob = pbm_blob_sink.str();
    Magick::Image image(Magick::Blob(pbm_blob.c_str(), pbm_blob.size()));
    image.write(argv[2]);

    return 0;
}


qrencode-sample.cpp という名前でファイルに保存してビルド。

$ g++ --std=c++11 `Magick++-config --cppflags --cxxflags --ldflags --libs` -lqrencode -o qrencode-sample qrencode-sample.cpp


このブログのタイトルを QR コードに変換してみます。

$ ./qrencode-sample "エンジニアのソフトウェア的愛情 または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか http://d.hatena.ne.jp/E_Mattsan/" blog.png


結果。QR コードの 1 ドットを PNG の 1 ピクセルで表現しているので(それを拡大表示しているので)見た目がよくないですが、手持ちの iPhone の app で読み取ることができました。


エンコーディングが Shift-JIS の場合は、QR_MODE_KANJI を指定する必要があるようです。UTF-8 の場合、QR_MODE_8 で大丈夫でした。

かっこで括られたトークンを取り出す

これ とか これ の入力になっている

(A)(BC)(DEF)

のような文字列から括弧の中にある文字列を取り出したい、というのが動機。

Ruby

def tokenize(s, left, right)
  s.scan(/(?<=#{Regexp.escape(left)})[^#{Regexp.escape(right)}]+(?=#{Regexp.escape(right)})/)
end


pry で試す。

pry(main)> tokenize('(A)(BC)(DEF)', '(', ')')
=> ["A", "BC", "DEF"]

Haskell

tokenize [] _ _ = []
tokenize (s:ss) left right | s == left =
  t:(tokenize r left right)
  where
    (t, (_:r)) = span (\c -> c /= right) ss


ghci で試す。

*Main> tokenize "(A)(BC)(DEF)" '(' ')'
["A","BC","DEF"]

Prolog

tokenize([], _, _, []).
tokenize(Input, Left, Right, [T|TS]) :-
  append([Left|T], [Right|Rest], Input),
  tokenize(Rest, Left, Right, TS),
  !.


gprolog で試す。

| ?- tokenize("(A)(BC)(DEF)", 0'(, 0'), TS).

TS = [[65],[66,67],[68,69,70]]

C++

#include <string>
#include <iterator>
#include <algorithm>

template<typename iterator_t, typename container_t>
std::string::const_iterator tokenize(iterator_t first, iterator_t last, char left, char right, container_t& container)
{
    auto sink = std::back_inserter(container);
    iterator_t i = first;
    while(i != last)
    {
        if(*i != left) { break; }

        ++i;

        iterator_t j = std::find_if(i, last, [&right](char c) { return c == right; });

        if(j == last) { break; }

        *sink = std::string(i, j);

        i = ++j;
    }
    return i;
}
#include <vector>
#include <iostream>
#include <iterator>

int main(int, char* [])
{
    std::string s = "(A)(BC)(DEF)";
    std::vector<std::string> ss;

    tokenize(s.begin(), s.end(), '(', ')', ss);

    std::copy(ss.begin(), ss.end(), std::ostream_iterator<std::string>(std::cout, "\n"));

    return 0;
}
$ g++ --std=c++11 -o sample_c++ sample_c++.cpp
$ ./sample_c++ 
A
BC
DEF


Boost Sprit でパーサを書いたら仰々しくなったので、何か間違えている気がしてならない。

#include <string>
#include <iterator>
#include <boost/spirit/include/qi.hpp>

template<typename contailer_t>
struct Storer
{
    mutable std::back_insert_iterator<contailer_t> i;

    Storer(contailer_t& container) : i(back_inserter(container)) {}

    template<typename attribute_t, typename context_t>
    void operator () (attribute_t& attribute, context_t) const
    {
        *i = std::string(attribute.begin(), attribute.end());
    }
};

template<typename contailer_t>
Storer<contailer_t> store(contailer_t& container)
{
    return Storer<contailer_t>(container);
}

template<typename contailer_t>
void tokenize(std::string s, contailer_t& container, char left, char right)
{
    using boost::spirit::qi::char_;
    using boost::spirit::qi::phrase_parse;

    phrase_parse(
        s.begin(),
        s.end(),
        *(char_(left) >> (*~char_(right))[store(container)] >> char_(right)),
        boost::spirit::ascii::space
    );
}

diff を清書する、加えて gem にする

気がつけば。diff を実装してから7年も経っていました。

案の定、考え方がかなり頭から抜けて行ってしまっているのですが、当時 diff について詳しい記事を書かれていた方がいらっしゃって、加えてこの記事にリンクを貼ってくださっていたおかげでそれを辿って当該記事にたどり着くことができて、どうにか再実装にこぎつけることができました。感謝です。

C++ で再実装

少々思うところがあって。C++の実装を書き直してみました。

職業プログラマに転向してからは主に Ruby を使っていて、C++ の方はすっかり進歩が止まってしまっていたものと思っていたのですが、それでも7年前のコードを見ると粗が目に付きます。


利用例。

#include <iostream>
#include <vector>
#include <string>

#include "myers.h"

int main(int, char* [])
{
    using namespace emattsan::myers;

    std::string fooBarBaz = "fooBarBaz";
    std::string FooBazBar = "FooBazBarfoz";

    std::vector<EditType> ses = diff(fooBarBaz, FooBazBar);

    std::string::const_iterator i1 = fooBarBaz.begin();
    std::string::const_iterator i2 = FooBazBar.begin();
    std::for_each(ses.begin(), ses.end(), [&](EditType edit_type) {
        switch(edit_type)
        {
        case DELETE:
            std::cout << "- " << *i1 << std::endl;
            ++i1;
            break;
        case ADD:
            std::cout << "+ " << *i2 << std::endl;
            ++i2;
            break;
        default:
            std::cout << "  " << *i1 << std::endl;
            ++i1;
            ++i2;
            break;
        }
    });

    return 0;
}


ビルド & 実行。

$ g++ --std=c++11 -o sample sample.cpp 
$ ./sample
- f
+ F
  o
  o
  B
  a
- r
+ z
  B
  a
+ r
+ f
+ o
  z

Ruby で再実装

gem 作りの練習をしているので勢いで Ruby で実装してみた。


利用例。

Gemfile 。

gem 'myers_diff', github: 'mattsan/ruby_myers_diff'


サンプル。

require 'myers_diff'

FooBarBaz = "fooBarBaz";
FooBazBar = "FooBazBarfoz";

ses = MyersDiff.diff(FooBarBaz, FooBazBar)

i1 = FooBarBaz.chars.each
i2 = FooBazBar.chars.each

ses.each do |edit_type|
  case edit_type
  when :DELETE
    puts "- #{i1.next}"
  when :ADD
    puts "+ #{i2.next}"
  else
    puts "  #{i1.next}"
    i2.next
  end
end


インストール。

$ bundle install --path vendor/bundle


実行。

$ bundle exec ruby sample.rb 
- f
+ F
  o
  o
  B
  a
- r
+ z
  B
  a
+ r
+ f
+ o
  z