LaTeXのlistingsパッケージ用Arduinoスタイル

LaTeXのlistings向けに,Arduino言語用のスタイルの設定を作ってみました。
Arduino IDEでの見た目と同じような感じでシンタックスハイライトを行います。

ライセンスはMIT licenseでお願いします。
TeX Live 2016 で動作を確認しました。

ダウンロード: listings-arduino.sty 1.8.5a

サンプル:

上記サンプルのファイル一式(TeX Live 2016で動作確認。Ubuntuではtexlive-fonts-extraも入れて):
listings-arduino-sample

背景:
うちの研究室の卒業論文はソースコードも掲載してもらうことにしています。
Arduinoを使う研究も多いのでそのソースコードも良く載せるのですが,その際listingsではlanguage=Cかlanguage=C++を使っていました。しかしどうせならArduino IDEと同じ見た目の方がカッコ良いので,作ってみることにしました。

どのトークンをシンタックスハイライトするかですが,Arduino IDEのソースコードには,シンタックスハイライトのためのルールファイルのようなものが含まれているようです。
例えば,build/shared/lib/keywords.txt にTSV形式で以下のように書かれています。

# LITERAL1 specifies constants
HIGH	LITERAL1	Constants	RESERVED_WORD_2
LOW	LITERAL1	Constants	RESERVED_WORD_2
INPUT	LITERAL1	Constants	RESERVED_WORD_2
INPUT_PULLUP	LITERAL1	Constants	RESERVED_WORD_2
OUTPUT	LITERAL1	Constants	RESERVED_WORD_2
…
delay	KEYWORD2	Delay
delayMicroseconds	KEYWORD2	DelayMicroseconds
digitalWrite	KEYWORD2	DigitalWrite
digitalRead	KEYWORD2	DigitalRead

これを見ると第1コラムが終端記号で,第2コラムがその種類のようです。
これらを機械的に抽出して色付けをしました。

ちなみに keywords.txt というファイルは複数個存在します。
Arduino-1.8.5 時点では以下の個数あるようです。

hardware/arduino/avr/libraries/EEPROM/keywords.txt
hardware/arduino/avr/libraries/SoftwareSerial/keywords.txt
hardware/arduino/avr/libraries/SPI/keywords.txt
hardware/arduino/avr/libraries/Wire/keywords.txt
hardware/arduino/avr/libraries/HID/keywords.txt
build/shared/lib/keywords.txt
libraries/Stepper/keywords.txt
libraries/TFT/src/utility/keywords.txt
libraries/TFT/keywords.txt
libraries/Ethernet/keywords.txt
libraries/GSM/keywords.txt
libraries/WiFi/keywords.txt

これらから想像付くように,ライブラリそれぞれに keywords.txt は存在し,予約語だけではなくライブラリのクラス名にももれなく色を付ける方針のようなので,Arduino IDEがバージョンアップして標準ライブラリが増えるたびにこのlistings-arduino.styも
同期させる必要があります。
将来的にそのような気力が続くかどうかはともかく,混乱しないようにこのスタイルファイルのバージョンもそれが依拠するArduino IDEのバージョンと同じにしておきます。

さらに,Arduino IDEはユーザが後から入れたライブラリにも色をつけてくれます。
これはライブラリ配布者が keywords.txt を同梱すればそれを見に行くというしかけのようです。
例えば私の環境だと,

C:/Users/ユーザ名/AppData/Local/Arduino15/packages
C:/Users/ユーザ名/Documents/Arduino/libraries

にもライブラリがインストールされていますので,Arduino IDEではここの keywords.txt も考慮されて色が付きます。

というわけで,どこにArduino IDEとライブラリとがインストールされているかを指定すれば,そこから keywords.txt を探し出してそこからトークンを拾って listings-arduino.sty を生成する,というスクリプトが以下です(Ruby)。

#!/usr/bin/ruby
# coding: utf-8
# generate listings-arduino.sty from Arduino IDE

class ArduinoIDEParser
  def initialize(*ref_dirs)
    # enum of "keywords.txt"s.
    # Note: These depend on installed libraries.
    @keywords_txts = []
    ref_dirs.each do |dir|
      @keywords_txts += Dir.glob(File.join(dir, "**/keywords.txt")).to_a
    end

    # table of the tokens(LITERAL1, KEYWORD1, ...)
    @tokens = Hash.new{|h, k| h[k] = []}
    @keywords_txts.each do |f|
      File.readlines(f).map(&:chomp).
        select{|l| l[0] != "#" && !l.strip.empty? && l.match(/^[a-zA-Z_]/)}.
        map{|l| l.split(/\s+/)}.each do |l|
        @tokens[l[1]] << l[0] unless @tokens[l[1]].include?(l[0])
      end
    end

    @ref_dirs = ref_dirs
  end

  def header
    header_str = <<~'EOS'
      % Arduino language style for listings package
      __GENERATED_FROM__
      % usage: \lstinputlisting[language=Arduino]{somesketch.ino}
      %
      __KEYWORDS_TXTS__
      \usepackage[dvipdfmx]{color}

      \definecolor{lst-arduino-sngq}{rgb}{0.000, 0.592, 0.612}
      \definecolor{lst-arduino-dblq}{rgb}{0.000, 0.361, 0.373}
      \definecolor{lst-arduino-literal1}{rgb}{0.000, 0.592, 0.612} % BLUE #00979C
      \definecolor{lst-arduino-keyword1}{rgb}{0.827, 0.329, 0.000} % ORANGE #D35400
      \definecolor{lst-arduino-keyword2}{rgb}{0.827, 0.329, 0.000} % ORANGE #D35400
      \definecolor{lst-arduino-keyword3}{rgb}{0.447, 0.557, 0.000} % GREEN #728E00
      \definecolor{lst-arduino-directive}{rgb}{0.369, 0.427, 0.012}
      \definecolor{lst-arduino-commentc}{rgb}{0.584, 0.647, 0.651}
      \definecolor{lst-arduino-commentcpp}{rgb}{0.263, 0.310, 0.329}
      \definecolor{lst-arduino-identifier}{rgb}{0.000, 0.000, 0.000}

      \lstdefinelanguage{Arduino}
      {
        showspaces=false,
        showstringspaces=false,
        showtabs=false,
        morestring=[s][\color{lst-arduino-sngq}]{'}{'},
        morestring=[s][\color{lst-arduino-dblq}]{"}{"},
        keywordstyle=[1]{\color{lst-arduino-literal1}},% LITERAL1(BLUE)
        keywordstyle=[2]{\color{lst-arduino-keyword1}\bfseries},% KEYWORD1(ORANGE)
        keywordstyle=[3]{\color{lst-arduino-keyword2}},% KEYWORD2(ORANGE)
        keywordstyle=[4]{\color{lst-arduino-keyword3}},% KEYWORD3(GREEN)
        directivestyle={\color{lst-arduino-directive}},
        morecomment=[s][\color{lst-arduino-commentc}]{/*}{*/},
        morecomment=[l][\color{lst-arduino-commentcpp}]{//},
        identifierstyle={\color{lst-arduino-identifier}},
    EOS
    header_str.
      sub(/__GENERATED_FROM__/, @ref_dirs.map{|x| "% Generated from: #{x}\n"}.join).
      sub(/__KEYWORDS_TXTS__/,  @keywords_txts.map{|x| "% #{x}\n"}.join)
  end

  def footer
    <<~'EOS'
      moredelim=*[directive]\#,
      moredirectives={
        define,elif,else,endif,error,if,ifdef,ifndef,line,%
        include,pragma,undef,warning
      },
      sensitive=true
    }
    EOS
  end

  def middle
    tex_lines = []
    %w(LITERAL1 KEYWORD1 KEYWORD2 KEYWORD3).each.with_index(1) do |k, i|
      tex_lines << "morekeywords=[#{i}]{"
      tex_lines << "  %%% #{k}"
      ary = @tokens[k].each_slice(5).to_a
      ary.take(ary.size-1).map{|a| "  " + a.join(",") + ","}.each do |l|
        tex_lines << l
      end
      tex_lines << "  " + ary.last.join(",")
      tex_lines << "},"
    end
    tex_lines.map{|s| "  #{s}\n"}.join
  end

  def to_listings
    header + middle + footer
  end
end

print ArduinoIDEParser.new("C:/Program Files (x86)/Arduino",
                           "C:/Users/foo/AppData/Local/Arduino15/packages",
                           "C:/Users/foo/Documents/Arduino/libraries").to_listings

Raspberry Pi をデジタルサイネージにする(動画流しっぱなしプレイヤにする)

坂本研Webの大学院講義「フィジカルコンピューティング特論」作品展示でも書いたのですが,この展示では1つの紹介動画をループ再生で流しっぱにしています。

要するにデジタルサイネージみたいなものですが,一度設定したらあとは管理フリーにしたかった(つまり放置したい)ので,以下のような条件でやりたいと思いました。

  • 朝自動的に起動し,夜自動的に終了する。
  • 自動的に起動したときに,動画を自動的に再生し始めたい。

これを何で実現するか考えたとき,

  • ノートPCやNUC: 定時の電源ON/OFFの設定が,少し調べた感じBIOSの対応を調べなければならず,面倒そう。かといって,外付けタイマーでON/OFFすると今度は「OSが正常にシャットダウンされませんでした」とか出そう。
  • デジ像などのメディアプレイヤ: 意外と高い。応用が効かない。レジューム再生はできるけど,それをループ再生にはたぶんできない。
  • デジタルフォトフレーム: 意外と高い。応用がかなり効かない。動画のフォーマットも限られている。

などと考えていったら,Raspberry Pi で実現するのが良いんじゃないかと思いました。表示はごく普通のPC用液晶ディスプレイを使う。ただしごく普通の安いのはHDMIがついていないこともあるので,HDMI-DVI変換をする。電源ON/OFFは外付けの家電用タイマで行う。

というわけで,主なものとして以下の機器を用意しました。

その他細かいものとしては以下を使いました。

RasPi2 には 2015-05-05-raspbian-wheezy をインストールし,raspi-config の Enable Boot to Desktop/Scratch では graphical desktop を選択します。

動画は今回は 960×540 (16:9), mp4(AVC/H.264), 29.97fps, 3Mbps で作りました。動画のアスペクト比やコーデックなどにあまり制限されないのも RasPi でやるメリットだと思います。この動画を RasPi2 の適当なディレクトリに置きます。

raspbian には動画プレイヤ omxplayer が始めから入っており,これでフルスクリーン再生ができます。
これを起動時に動作させるようこの辺などを参考に /etc/xdg/lxsession/LXDE-pi/autostart に以下を追加します。

@omxplayer --refresh --loop /path/to/video.mp4

リブートしたらだいたい期待通りの動きになりました。

これでうまくいくかと思ったのですが,テストのためしばらく放置してみると再生がフリーズしていることがあります(OS自体は動いています)。また,動画の最後の2~3秒が無視されて最初に飛んでしまいます。

そこで,OMXPlayer公式から最新ビルドomxplayer_0.3.6~git20150710~4d8ffd1_armhf.debを取ってきて以下のようにインストールします。

# wget http://omxplayer.sconde.net/builds/omxplayer_0.3.6~git20150710~4d8ffd1_armhf.deb
# dpkg -i omxplayer_0.3.6~git20150710~4d8ffd1_armhf.deb

なお,OMXPlayer公式にある以下の手順は必要ありませんでした(2015-05-05-raspbian-wheezyには既にこれらのパッケージの最新版が入っていました)。

# apt-get install libpcre3 fonts-freefont-ttf
# apt-get install fbset


これで完璧に動くようになりました。

あとはデジタルプログラムタイマで,RasPi と液晶ディスプレイの両方を定時ON/OFFするように設定しました。

Windows 7のボタン風LaTeXマクロ

LaTeXの文中でWindows7のボタンっぽいものを表示するマクロを作ってみました。

button.sty

例えば,

\documentclass{jreport}
\usepackage{button}
\begin{document}
こまめに\button{保存}ボタンを押しましょう。
\end{document}

とすると,以下のようになります。

button-sty-sample

また,

\button[150]{保存}

のようにすると,通常サイズの150%の幅のボタンになります。

続きを読む

RubyでPDFのページ数を数える

Rubyスクリプトから,既存のPDFファイルの総ページ数をカウントしたいことがたまにあります。

方法はいくつか考えられます(多少強引なのも含めて)。

PDF::Readerを使う方法

たぶん一番スマート。

#!/usr/bin/env ruby
require 'pdf/reader'

fname = "test.pdf"
p PDF::Reader.new(fname).page_count

PDF::Readerはgemでインストールできます。

$ gem install pdf-reader

続きを読む

白紙のPDF

需要があるかは謎ですが,白紙のPDFを置いてみます。改造や再配布はご自由にどうぞ。

これらが必要になる場面としては例えば,「幾人かの原稿をまとめて一つのPDFにしたいのだけど,各人の1ページめは必ず奇数ページにこないといけない」ような状況を想定しています。花子さんの原稿が3枚で次に太郎さんの原稿を置く場合,間にこの白紙PDFを挟めば良いかと。pdftk でやるならこうですね。

pdftk hanako.pdf blanksheet-a4-portrait.pdf tarou.pdf cat output all.pdf

Acrobatを持っている場合は新規空白ページを作成する方法(Acrobat X)でも可能なようですが,スクリプトでだーっとやりたい場面が多いので。

なお,これらのPDFは,Ruby と libHaru を用いて以下のようなスクリプトで生成しました。

#!/usr/bin/env ruby
require "hpdf"

sizes = %w(A4 A3 A5 B4 B5 LETTER)
directions = %w(PORTRAIT LANDSCAPE)

directions.each do |direction|
  sizes.each do |size|
    pdf = HPDFDoc.new
    page = pdf.add_page
    page.set_size(eval("HPDFDoc::HPDF_PAGE_SIZE_#{size}"),
                  eval("HPDFDoc::HPDF_PAGE_#{direction}"))
    pdf.save_to_file("blanksheet-#{size.downcase}-#{direction.downcase}.pdf")
  end
end

(2017-05-15 追記)
上記スクリプトは libHaru を用いていましたが,libHaru を Ruby から使う場合は hpdf.so が Ruby のバージョンに依存してしまうのがちょっとつらいところです。

Prawn は Pure Ruby なのが良いですね。というわけで最近PDF生成関係のスクリプトを Prawn で書き直したりしています。A4ポートレートの白紙PDFをPrawnで生成するとしたらこんな感じでしょうか。

#!/usr/bin/env ruby
require "prawn"

Prawn::Document.new(:page_size   => 'A4',
                    :page_layout => :portrait).render_file("hakushi-a4.pdf")