かきかたえんぴつ

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

PDF に埋め込まれた画像を圧縮

Ghostscript (Windows 版)を使って、重たい PDF ファイルの画像を圧縮して、いくらかスリムにする方法。以下のコマンドを打つべし。

gswin32c.exe -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dAutoFilterColorImages=false -dColorImageFilter=/FlateEncode -sOutputFile=output.pdf -c .setpdfwrite -f input.pdf

/FlateEncode は Zip 圧縮の指定。JPEG 圧縮にする場合は /DCTEncode に変える。

gswin32c.exe -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dAutoFilterColorImages=false -dColorImageFilter=/DCTEncode -sOutputFile=output.pdf -c ".setpdfwrite << /ColorImageDict << /QFactor 0.1 >> >> setdistillerparams" -f input.pdf

/QFactor に画質を 0.0(良い)~1.0(悪い)の範囲で指定する。

ついでに作成者やタイトルなどの情報も埋め込むには、

% Document information
[/CreationDate (D:20180112)
/Creator (Handmade)
/Title (Example)
/Subject (Example of how images in a PDF file are compressed with Ghostscript)
/Keywords (PDF Ghostscript Compression)
/Author (PDF Hanako)
/DOCINFO pdfmark

こんなものをテキストエディタで作り、info.ps のような名前を付けて保存する。そんで、gswin32c のオプションに加えてやれば良い。

gswin32c.exe -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dAutoFilterColorImages=false -dColorImageFilter=/FlateEncode -sOutputFile=output.pdf -c .setpdfwrite -f info.ps input.pdf

らしい(大昔に書いたブログの掘り起こしなので確かめていない)。

Inkscape で覚えておくと便利なショートカット

  • Ctrl + C … コピー
  • Ctrl + X … 切り取り
  • Ctrl + V … 貼り付け
  • Ctrl + Alt + V … 元と同じ位置に貼付け

  • S … オブジェクトの選択

  • T … テキストを作成
  • R … 矩形
  • B … ベジェ曲線/直線
  • F2 … パスの編集

  • Ctrl + G … グループ化

  • Shift + Ctrl + G … グループ化解除

  • Shift + Ctrl + F … フィル/ストローク パネルを開く(fill)

  • Shift + Ctrl + L … レイヤ パネルを開く(layer)
  • Shift + Ctrl + M … 変形 パネルを開く(modify)
  • Shift + Ctrl + T … テキスト/フォント パネルを開く(text)

  • テキスト編集中に Alt+ Up … 選択した文字を上へずらす(上付き)

  • テキスト編集中に Alt+ Down … 選択した文字を下へずらす(下付き)
  • テキスト編集中に Ctrl + U … ユニコードで入力(ギリシャ文字や数学記号を入力するのに便利)

  • パレットを Click … フィルの色を指定

  • パレットを Shift + Click … ストロークの色を指定

以前の Inkscape で作った SVG ファイルを Inkscape 0.91 で開くと、ビットマップ画像がおかしなことになる

Inkscape 0.91 がリリースされました ヾ(:3ノシヾ)ノシ

ところが。

以前のバージョンの Inkscape で作成した SVG ファイルを Inkscape 0.91 で開き、EPS や PDF に保存すると、埋め込んであるビットマップ画像がJPEG圧縮されてしまい、しかも元の画像と縦横比を変えて貼り付けていた場合その縦横比もおかしくなるので、その原因を調べていた。

Inkscape 0.91 でビットマップ画像を埋め込んだ SVG ファイルを作ると、それらの画像に対応する image 要素に

  • style="image-rendering:optimizeSpeed"
  • preserveAspectRatio="none"

という属性がつく。

image-rendering は画像の拡大・縮小時のレンダリング方法を指定するもので、初期値は auto だが、画像を読み込むときのダイアログで auto、optimizeQuality、optimizeSpeed のなかから選択できる。optimizeQuality を指定すると最近接法でレンダリングされるので、ピクセルの境界をシャープに保ったまま拡大するときに適している。optimizeSpeed は補間がおこなわれるので、ピクセルのギザギザ感を無くしたいときに用いる。

preserveAspectRatio は画像の拡大・縮小時に縦横比を制約する設定で、説明しづらいので以下のリンクを参照のこと。

古いバージョンの Inkscape ではこれらの属性がサポートされておらず、SVG ファイルにも記録されない。そのため Inkscape 0.48 で作成した SVG ファイルを Inkscape 0.91 で開いた場合、おそらくデフォルトの値 style="image-rendering:auto" と preserveAspectRatio="xMidYMid" として扱われる。image-rendering:auto のせいで画像に補間がかかり、その効果を反映した画像ファイルを JPEG 圧縮したものが PDF や EPS で使われるようである。しかも xMidYMid のせいで縦横比がおかしなことになる。

image-rendering に関しては Inkscape 0.91 に設定項目がある(画像を右クリックしたときの Object Properties のなかに Image Rendering を指定できる)。しかし preserveAspectRatio を変更する機能はオリジナルのパッケージには無い。拡張機能が launchpad で公開されているので、それらを使えば可能のようだが、まだ試していない。

いずれにしろ手作業では面倒臭いので、古い Inkscape で作った SVG ファイルにこれらの属性を追加するための python スクリプトを書いたので載せておく。updatesvg.py のような名前で保存しておいて

> python updatesvg.py hoge.svg

のように実行すれば、hoge.update.svg というファイルが生成されるかもしれない。

import os, sys
import re
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'

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

    path = argv[1]
    base, ext = os.path.splitext(path)
    print '<-', path
    tree = parse(path)

    count = 0
    for image in tree.findall('.//{%s}image' % svg_ns):
        print image
        proc = 0
        if not 'style' in image.keys():
            image.set('style', 'image-rendering:optimizeSpeed')
            proc = 1
        if not 'preserveAspectRatio' in image.keys():
            image.set('preserveAspectRatio', 'none')
            proc = 1
        count += proc

    print count, ' images updated.'
    output_path = base + '.update.svg'
    tree.write(output_path, 'UTF-8')

main()

あんまりテストしてないので、修正がいるかも。

Markdown 関連

HTML の入力は面倒だし、マークアップされた文書自体はたいへん読みにくい。そこで、あとで HTML に変換することを想定して、プレーンテキストとしても読めるようにマークアップしようってことで生まれた記法が Markdown である(と、とりあえず理解している)。はてなブログも Markdown 記法に対応しており、この記事も Markdown 記法で書いている。

Windows 上で Markdown 文書を書いたり見たりするための道具を探したのでまとめておく。プレビュー表示のあるエディタを利用するより、自分の好きなエディタで編集しながら適当なビューアで表示するほうが良いと感じた。

xyzzy

  • 改変 markdown-mode
    メジャーモードなので拡張子に対応付けられる。以下の2行を .xyzzy または siteinit.l に加える。
(load-library "markdown-mode")
(push '("\\.md$" . markdown-mode) *auto-mode-alist*)

拡張子 *.md のファイルを開くと勝手に markdown-mode になり、色分け表示される。

参考:

xyzzy のデフォルトの文字コードUTF-8

ついでにこれも変えておいた。日本語をふくむ Markdown 文書を Shift-JIS で保存すると、Google Chrome + Markdown Preview Plus で開いたときに文字化けする。

(setq *default-fileio-encoding* *encoding-utf8n*) 

これを .xyzzy または siteinit.l に追加。

参考:

ブラウザ表示のためのアドオン

ビューアには普段使っているブラウザを利用することにした。

はてなブログの Markdown モードでスーパーpre 記法を使う

はてなブログでは Markdown モードというのがある。はてな記法の代わりに Markdown 記法を使える。はてな記法には「スーパー pre 記法」というのがあり、コードを色分け表示してくれて便利なのだが、同様のことを Markdown 記法で実現するには…

こう書く。(行頭のスペースは本来は要らない。うまくエスケープできなかったので。)

 ```postscr
 newpath 100 100 moveto 
 100 0 rlineto 0 100 rlineto -100 0 rlineto 
 closepath stroke show
 ```

こうなる。

newpath 100 100 moveto 
100 0 rlineto 0 100 rlineto -100 0 rlineto 
closepath stroke show

参考:

Aspell 0.60.6.1 を MinGW でコンパイルする

Win32 版 Aspell バイナリ が古いので、最新版のソースコードをコンパイルしてみた。

MinGW(32bit)と MSYS の最新版がインストールしてあり、それぞれパスが通っている状況で行っている。各バイナリパッケージは MinGW-builds

あたりから。


  1. GNU Aspell のウェブサイトから aspell-0.60.6.1.tar.gz をダウンロードし、適当なディレクトリに解凍する。

  2. 解凍したディレクトリでコマンドラインから

    sh configure --enable-win32-relocatable --disable-rpath --prefix="C:/Aspell" --enable-data-dir="C:/Aspell/data" --enable-dict-dir="C:/Aspell/data" 

    を実行。C:/Aspell がインストール先になる。Makefile が作られる。


  3. common の中にある file_util.cpp に

    #include "asc_ctype.hpp"

    を加える。


  4. mingw32-make を実行すると「CreateProcess(NULL, /usr/bin/mkdir -p common, ...) failed」などというエラーが表示される。Makefile を開いて /usr/bin/ と書かれている部分をすべて削除してみる。再度 mingw32-make するとコンパイルが進む。

  5. mingw32-make install を実行。

参考:

round の速度

Python で比較。10*1000*1000 回ループさせたらこのぐらい。

cast:   2.792 s
floor:  2.524 s
ceil:   2.539 s
round: 10.761 s
pass:   0.472 s

キャストや切り捨てに比べて round は 5 倍ぐらい時間がかかっている。

実行したコードは以下。

import datetime
import math

n = 10*1000*1000

dummy = 0
dt_1 = datetime.datetime.now()
for i in range(n):
    dummy = int(math.pi) 
    # int -> math.floor, math.ceil, round, pass
dt_2  = datetime.datetime.now()

dt_delta = dt_2 - dt_1
print 'cast:  %2d.%03d s' % (dt_delta.seconds, (dt_delta.microseconds / 1000))

続・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 レイヤ検索のさいに正規表現を使うように変更。