かきかたえんぴつ

いつかどこかで何かの役にたつかもしれないメモ

続・Inkscape SVG の各レイヤを PDF にエクスポートする

プレゼンのスライド作りに Inkscape を使いたいので、前回のスクリプトを拡張した。

以下のスクリプトは、Inkscape で作成した SVG の各レイヤを各ページにもつ PDF を出力する。'background' という名前のレイヤはつねに表示される。id に 'pages' を含むテキストオブジェクトはページ指示器とみなされ、「ページ番号 / 全ページ数」で置き換えられる。

JessyInk で同じことができるのだけれど、自分でコードを書いたほうがいろいろ自由にできるので。

inkscape および ghostscript

  • svg2pdf.py
import os, sys
import re
import tempfile
from xml.etree.ElementTree import *

svg_ns      = 'http://www.w3.org/2000/svg'
inkscape_ns = 'http://www.inkscape.org/namespaces/inkscape'
sodipodi_ns = 'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'

gs_exe = 'gswin32c'
is_exe = 'inkscape'
bg_keyword = 'background'
pi_keyword = 'pages'      

argv = sys.argv
argc = len(argv)
if(argc < 2):
    print 'Usage: svg2pdf.py hoge.svg'
    quit()

path = argv[1]
base, ext = os.path.splitext(path)

tree = parse(path)
pdfs = ''
fg_list = [] # foreground layers
bg_list = [] # background layers
pi_list = [] # page-indicators

# finding page indicator
for text in tree.findall('.//{%s}text' % svg_ns):
    if text.get('id').find(pi_keyword) >= 0:
        pi_list.append(text)

# setting all foreground layers hidden and all background visible
for layer in tree.findall('.//{%s}g' % svg_ns):
    if re.search(r'^layer[0-9]+$', layer.get('id')) == None:
        continue
    
    if layer.get('{%s}label' % inkscape_ns).find(bg_keyword) >= 0:
        layer.set('style', 'display:inline')
        bg_list.append(layer)
    else:
        layer.set('style', 'display:none')
        fg_list.append(layer)

# create a temporary pdf file for each foreground layer
for i, layer in enumerate(fg_list):
    layer.set('style', 'display:inline')

    for pi in pi_list:
        pi[0].text = '%d / %d' % (i+1, len(fg_list))
    
    temp_svg = tempfile.mkstemp(suffix='.svg')
    tree.write(temp_svg[1], 'UTF-8')
    
    temp_pdf = tempfile.mkstemp(suffix='.pdf')
    os.close(temp_pdf[0])
    cmd = '%s -C -f \"%s\" -A %s' \
          % (is_exe, temp_svg[1], temp_pdf[1])
    print cmd
    os.system(cmd)
    pdfs += '%s ' % temp_pdf[1]

    os.close(temp_svg[0])
    os.remove(temp_svg[1])
    
    layer.set('style', 'display:none')

# bind all the pdf files into one
cmd = '%s -dNOPAUSE -sDEVICE=pdfwrite ' % gs_exe \
      + '-dAutoFilterColorImages=false ' \
      + '-dColorImageFilter=/FlateEncode ' \
      + '-sOUTPUTFILE=%s.pdf -dBATCH %s' \
      % (base, pdfs)
print cmd
os.system(cmd)

# delete all the temporary pdf files
for temp_pdf_path in pdfs.split():
    os.remove(temp_pdf_path)
  • 2013.02.12 レイヤ検索のさいに正規表現を使うように変更。

Inkscape SVG の各レイヤを PDF にエクスポートする

Inkscape で作った SVG の各レイヤを個別の PDF ファイルに書き出し、最後にそれらを 1 つの PDF ファイルにまとめる Python スクリプト。

Inkscape と gswin32c に PATH が通っていると仮定。Windows 用なので、他の環境で動かすには修正が要る。

import os, sys
from xml.etree.ElementTree import *

argv = sys.argv
argc = len(argv)
if(argc < 2):
    print 'Usage: svg2pdf.py hoge.svg'
    quit()

path = argv[1]
base, ext = os.path.splitext(path)

tree = parse(path)
pdflist = ""

SVG_NS = "http://www.w3.org/2000/svg"

for layer in tree.findall('.//{%s}g' % SVG_NS):
    lid = layer.get("id")

    if lid.find("layer") < 0:
        continue
    
    cmd = "inkscape -C -f \"%s\" -A %s_%s.pdf -j -i \"%s\"" \
          % (path, base, lid, lid)
    print cmd
    os.system(cmd)
    pdflist += " %s_%s.pdf" % (base, lid)

cmd = "gswin32c -dNOPAUSE -sDEVICE=pdfwrite " \
      + "-dAutoFilterColorImages=false " \
      + "-dColorImageFilter=/FlateEncode " \
      + "-sOUTPUTFILE=%s.pdf -dBATCH %s" \
      % (base, pdflist)
print cmd
os.system(cmd)

個別に書き出した PDF は放ったらかし。非表示のレイヤは真っ白なページとして出力される。

  • 2013.02.06 追記

レイヤでないオブジェクトを PDF に吐き出すことがあるので、

    if lid.find("layer") < 0:
        continue

の2行を加えた。

Windows で Unicode 文字をキーボードから入力する

Unicode 文字を入力する機能を用意しているプログラムもあるが(Microsoft Word や Inkscape など)、そのような機能が無い場合、Windows の機能が使える。

レジストリをいじる。regedit で HKEY_CURRENT_USER\Control Panel\Input Method に EnableHexNumpad という REG_SZ(文字列値)の項目をつくり、値を 1 に設定する。Windows にログインし直してレジストリの変更を反映させる。

設定後、Unicode 文字を入力したいときには以下のようにする。

  1. Alt キーを押しながら
  2. テンキーの「+」を押し
  3. Unicode を 16 進数で入力し
  4. Alt キーを離す。

参考:

Cairo で PDF に埋め込んだビットマップがボケる

Cairo 1.10 以降でビットマップ画像を埋め込んだ PDF を作ると、その画像にデフォルトで /Interpolate true フラグが設定される。画像の解像度が低いとピクセルの境目がボケているのが見えてしまう。これを回避するには、CAIRO_FILTER_NEAREST フィルタを設定する。たとえば、

cairo_surface_t* cs = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
// ...
cr = cairo_create(cs);
// ...
cairo_set_source_surface(cr, img, 0, 0);
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
cairo_paint(cr);

Inkscape は PDF 出力に Cairo を使っているので、最近の Inkscape が吐いた PDF でも /Interpolate true が設定されてしまう。どうにかならんかなー。

Ghostscript を使って PDF のページサイズを変更する

たとえば in.pdf のページサイズを強制的に A4 にして out.pdf に保存するには、以下のようにする。

gswin32c -q -dNOPAUSE -dBATCH -o out.pdf -sDEVICE=pdfwrite -sPAPERSIZE=a4 -dFIXEDMEDIA -c "<</PageOffset [10 20]>> setpagedevice" -f in.pdf

[10 20] のところで x 方向、y 方向のオフセットを指定する(単位はポイント、負の値も可)。Unix だと gswin32c のかわりに gs か?

ユーザー定義のサイズを指定したいときは、

-sPAPERSIZE=a4

のかわりに

-dDEVICEWIDTHPOINTS=595 -dDEVICEHEIGHTPOINTS=842

のようにする。単位はポイント。

連番でない PNG 画像をとにかく動画に変換

指定されたディレクトリの PNG 画像を連番ファイル名にリネームしたあげく、ffmpeg を使って MP4 動画に変換してしまう Python コード。encode.py とでも名前を付けて

python encode hoge foo

のように使えば、hoge ディレクトリの PNG 画像から foo.mp4 ができる。

連番の桁数や ffmpeg のパラメータは適当に変更すべし。

import glob
import os
import subprocess
import sys

wd  = '.'
out = 'out'

argc = len(sys.argv)
if(argc > 1):
    wd = sys.argv[1]
if(argc > 2):
    out = sys.argv[2]

files = glob.glob(wd + '/*.png')
i = 0
for f in files:
    print f
    os.rename(f, wd + '/%03d.png' % i)
    i += 1

cmd = 'ffmpeg -r 10 -i ' \
      + wd + '/%03d.png -r 10 -vcodec libx264 ' \
      + out + '.mp4'
print cmd
subprocess.call(cmd, shell=True)