C#でlibHaruを使ってPDFを出力してみよう。その3「C#からlibHaruの呼び出し編」
さて、前回に続きC#でPDF作成できる手順を記載したいと思います。
libHaruのビルドが完了している前提になります。
ビルド手順は、前回の記事を参照してください。
stone-book.hatenablog.com
今回は、C#のプロジェクトで前回ビルドしたlibHaruのライブラリ、
libhpdf.dllを使って、libHaruのdemoに同梱されているPDF出力を目標にします。
検証に使ったソースコードは下記に公開しています。
GitHub - stone-book/libHaruApp
大まか流れは下記になります。
- プロジェクトの作成
- プロジェクトにhpdf.csとlibhpdf.dllの追加
- デモソースの修正と実行
プロジェクトの作成
Visual Studioを起動し、とりあえず.netコンソールアプリケーションを作成します。
ここで、ライブラリのビルドがx86である場合は、プロジェクトの設定を変更してください。
プロジェクトにhpdf.csとlibhpdf.dllの追加
既存の項目を追加で、libHaruフォルダにある、次のファイルをプロジェクトに追加します。
libharu\if\c#\hpdf.cs
同時にプロジェクトのカレントに「libhpdf.dll」を追加します。作成される実行ファイルと同じディレクトリに必要なので「出力ディレクトリにコピーする」としておくと便利です。
デモソースの修正と実行
次にデモソースを実行します。demoに入っていた、TextDemoをインポートし、適当にTextDemoのMain関数を呼び出すように変更。
このままでも動作するはずですが、
Console.WriteLine("libhpdf-" + HPdfDoc.HPdfGetVersion());
これと
pdf.SetCompressionMode(HPdfDoc.HPDF_COMP_ALL);
この箇所でなぜかエラーが出ました。
内容見る限り、コメントアウトしても影響なさそうと思い、コメントアウトします。若干気持ち悪いですが・・
PDFが出力されました。
念のため、ImageDemoも同様の修正を加えて実行してみたところ動作しました。
簡単な帳票くらいなら対応できそうです。
追記(2017/9/12)
HPdfDoc.HPdfGetVersion()については、hpdf.csを修正することで対応できるようです。
Critical error detected c0000374 bei Aufruf native code von managed dll
githubにあげているソースにも反映しました。
C#でlibHaruを使ってPDFを出力してみよう。その2「libHaruビルド編」
さて、前回に続きC#でPDF作成できる手順を記載したいと思います。
libpngとzlibのビルドが完了している前提になります。
これらのビルド手順は、前回の記事を参照してください。
stone-book.hatenablog.com
大まかな流れは下記になります。
フォルダ構成
公式サイトからlibHaruのソースをダウンロードしてきます。
libHaru
ダウンロード後、適当にリネームして、libpngやzlibと同ディレクトリに格納します。
(コンパイルの際、相対パスで参照するためです。)
libpngとzlibをコピー
前回ビルドした「libpng16.lib」と「zlib.lib」をRelease LibraryからlibHaruのフォルダにコピーします。
これで、必要なファイルがそろいました。
各種ファイルの変更
スタートメニューからVisual Studioの中にある「VS2017用 x84 Native Toolコマンドプロンプト」を使ってビルドします。
このまま、ビルドしてみるといろいろ怒られました。一つ一つつぶしていきます。
VS2017を用いる場合は、下記のスクリプトを利用するため、環境に合わせて変更します。
libharu\script\Makefile.msvc_dll
まず、PREFIXのパスの部分をlibpngがzlibのバージョンに合わせて修正します。
(コマンドプロンプトのカレントを基準とした相対パスに変更。)
!IFNDEF PNG_PREFIX
PNG_PREFIX = ../lpng1632
!ENDIF!IFNDEF ZLIB_PREFIX
ZLIB_PREFIX = ../zlib-1.2.8
併せて、CFLAGSの箇所も修正します。libpngとzlibのincludeフォルダに指定のヘッダファイルはないので、次に変更。
CFLAGS=/MD -nologo -O2 -Iinclude -Iwin32\include -I"$(PNG_PREFIX)" -I"$(ZLIB_PREFIX)" -DHPDF_DLL_MAKE
また、LDFLAGSの箇所で、libpng13.libとなっているので、libpng16.libに変更
LDFLAGS= /LIBPATH:$(PNG_PREFIX)\lib /LIBPATH:$(ZLIB_PREFIX)\lib /LIBPATH:win32\msvc libpng16.lib zlib.lib
この状態でビルドしてみます。
しかし、HPDF_3DAnnot_Set3DViewの参照エラーが出てしまいました。
中国のサイトで、同様に対処例?が記載されていたので参考に・・
libharu\win32\msvc\libhpdf.def
にある EXPORTS から次を削除します。
HPDF_3DAnnot_Set3DView
これで設定が完了。
VS2017用 x84 Native Toolコマンドプロンプトでコンパイル
今回はx84ですが、libpng、zlibもwin32ではなくx64でビルドしている場合は、
VS2017用 x64 Native Toolを選択すれば良いと思います。
コマンドプロンプトにてlibHaruフォルダまで移動します。
(私の環境の場合、cd E:\build\libharu ですね。)
libHaruフォルダで次のコマンドを入力します。
nmake -f script\Makefile.msvc_dll
なんやかんや警告は出ますが、とりあえず完了します。
libhpdf.dllがlibHaruフォルダに出力されます。
私の環境では、731KB程度でした。
参考
基本的な手順は、githubに記載されています。
Installation · libharu/libharu Wiki · GitHub
ビルドに関して参考にした記事
PDFライブラリのlibHaru 2.3.0 RC2をビルドしてみる - terurouメモ
PDFライブラリ「libHaru」のDLLをビルドする : 日曜ゲームクリエータの日記
HPDF_3DAnnot_Set3DViewエラーを参考
libharu(1):windows下编译方法 - oldmtn的专栏 - CSDN博客
C#でlibHaruを使ってPDFを出力してみよう。その1「libpngとzlibビルド編」
C#でPDFを出力したいとき、いろいろなライブラリの使用を検討されると思いますが・・
ライセンス的に難しいと思うことが多いとお嘆きの方に、libHaruを検討してみてはどうでしょうか。
libHaruは、もともとIPAの未踏ソフトウェア創造事業で採択されたもののようで、
商用利用も可能です。
https://www.ipa.go.jp/files/000006420.pdf
これをC#で活用するには、いくつかの手順をこなさないといけないので、
試行錯誤したときのメモを兼ねて記載します。
大きな流れとしては、以下になります。
- libpngとzlibのビルド
- libHaruのビルド
- C#プロジェクトの作成とインタフェースの追加
Visual Studio 2017 Communityを使用しています。
libpngとzlibのダウンロード
まず、公式サイトからlibpngの最新版をダウンロード
libpng Home Page
zipをダウンロードします。記載時点では、1.6.32でした。以降、これに準じて記載します。
つぎに、libpngのビルドに必要なzlibをダウンロード
以下のファイルに必要なzlibのバージョンが記載されています。
lpng1632\projects\vstudio\zlib.props
記載時点では、1.2.8でした。
zlibのリポジトリから対応するバージョンをダウンロード
Index of /fossils
それぞれダウンロード、解凍後は、それぞれを同じフォルダに格納します。私は、E:\buildというフォルダに格納しました。
libpngのビルド
以下のファイルをダブルクリックしてVisual Studioを起動します。
lpng1632\projects\vstudio\vstudio.sln
初回起動時には、このように聞かれる場合があります。「OK」をクリックします。
次に、ビルド構成「Release Library」に選択します。
このまま、ビルドすると、警告が生じされ、コンパイルが止まってしまうため、
警告が出てもコンパイルを続行させるため、プロジェクトの設定を次のように変更します。「警告をエラーとして扱う」を「いいえ /WX」にします。
これは、pngXXXのプロジェクトに対して、それぞれ設定します。
設定が完了すれば、ビルドを実行します。
ビルドが無事に完了すれば、Release Libraryのフォルダにファイルが出力されます。
これで、libpngとzlibビルドが完了です。
Visual Studio 2017 インストールエラーの対応
Visual Studio 2017 が公開されましたね!
Community版をダウンロードしてきました!
インストールしようと思ったら・・
インストール ファイルをダウンロードできません。インターネット接続を確認してやり直してください。
と表示されました。
インターネットにはつながってるのに・・。
原因調査
原因を調べようにもよくわからなかったのですが・・・
C:\Users\{ユーザ名}\AppData\Local\Temp
にVisual Studioのインストーラーが展開されて、ログも出力されるようです。
私の環境では、
dd_bootstrapper_20170824222521.log
というログファイルができておりました。
また、失敗時には
VSFaultInfo
というフォルダに日付ごとのエラーログが出力されておりました。
中をみると
インストールファイルの整合性を確認できません。
証明書を確認できませんでした。
とか
アクセス許可で禁じられている方法でソケットにアクセスしようとしました。
など記載されておりました。
検索したところそれっぽい情報を見つけた。
c# - Visual Studio 2017 fails to install offline with "Unable to download installation files" - Stack Overflow
https://developercommunity.visualstudio.com/content/problem/24328/visual-studio-installer-failed-to-download.html
対応
- vs_community__XXXX.exeインストーラを右クリックしてプロパティを表示
- デジタル証明を表示
- デジタル証明を選択して詳細を表示
- 「証明書の表示」をクリック
- 証明書のインストール
確かに証明書が必要とはあるけど、こんなんでいいのかなぁ。
オフライン環境での Visual Studio のインストールに関する特別な考慮事項 | Microsoft Docs
なぜか動いた!
参考になりましたら。
D3.jsでオーディオビジュアライザーみたいなものを作成
今回は、オーディオビジュアライザー(Audio Visualizer)みたいなものを
作ってみようと思います。
D3.js は、4.0 です。
オーディオビジュアライザー
値は乱数ですが、音楽の周波数とかと組み合わせれば、
それらしく表示できるかもしれません。
See the Pen D3_AudioVisualizer by book_stone (@book_stone) on CodePen.
究極を言うと10×10のマスを並べて色を塗っているだけです。
とはいえ、妙に悩んだところもありました。
ソースコード
主だった個所をメモしたいと思います。
// 初期データ var data = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // 10分割スケール var dataScale = d3.scaleLinear() .domain([0,100]) .rangeRound([0,10]); // 色の取得に使う var colorScale = d3.scaleLinear() .domain([0,10]) .range([1, 0.3]);
初期データとスケール設定です。
今回は、data配列に入るデータが、0~100までの値と想定し
それをマス目に対応するよう10分割スケールとしてます。
rangeRound を使うと丸めてくれて整数になるから便利です。
カラースケールは、d3.interpolateWarm(t) を使用するためのもの。
t に 0~1 の値を入れることで色々なカラーが取得できます。
今回は、緑から赤紫の範囲を使用するので、1 ~ 0.3 までにしています。
d3-scale/README.md at master · d3/d3-scale · GitHub
// 升目のもととなる配列を作成 var cells = new Array(); for(var i = 0; i < cols; i++){ for(var j = 0; j < rows; j++){ cells.push({ "i": i, "j":j }); } } // 升目オブジェクト var rects = svg.selectAll(".rects").data(cells).enter() .append("rect") .attr("x", function(d){ return (width/cols) * d.i; } ) .attr("y", function(d){ return (height/rows) * (rows - d.j); } ) .attr("width", (width / cols) - 4) .attr("height", (height /rows) - 3) //.attr("stroke-width",1) .attr("rx",2) .attr("ry",2) .attr("stroke","black");
cells という変数にマス目の位置 (i,j) を持たせて作成しています。
これは、のちに enter() で繰り返し rect オブジェクトを作成したいので
1次元配列にしています。もっといい方法があるかもしれない。
rect オブジェクトの作成は、 位置 (i,j) から x座標とy座標を計算しています。
widthとheightはSVGの幅と高さとマス目分だけ割って、1辺を求めています。
サイズから -4 とか - 3は、少し隙間を出したほうがおしゃれかと思い調整した値です。
attr("rx" 2) と attr("ry",2) は rect を角丸とするため。
// 1秒ごとに更新を呼び出し d3.interval(update, 150); // 再描画がするメソッド function update(){ // 乱数取得 var rand = d3.randomUniform(100); // 合計10個の乱数を発生 for( var data=[], i=10; i--; ) { data[ data.length ] = Math.floor( rand() ) ; } rects.attr("fill", function(d){ if(dataScale(data[d.i]) >= d.j){ //return "cyan"; return d3.interpolateWarm(colorScale(d.j)); } else{ return "gray" }; }); }
乱数を取得して、配列に放り込んで、その値によって
rect の色を塗るといったもの。比較的シンプルなつくりではないかと思います。
感想
色々検索してみると、最大のところでいったんバーが浮いたりするなど
こったアニメーションがあるものもありました。
すこし、カクカクしている感もあるので、まだ改良の余地もありそうです。
また、背景や少しマス目の幅や数を変えるだけでも印象が変わるのも面白いです。
ひとまずでした。
D3.jsでスピードメーターみたいなものを作成
今回は、スピードメーターみたいなものを作ってみようと思います。
D3.js は、4.0 です。
作成するスピードメーター
値は乱数ですが、CPUやメモリの使用量などの表示に組み合わせたら
それっぽくなるかもしれません。
See the Pen D3_SpeedMeter by book_stone (@book_stone) on CodePen.
ソースコード
主だった個所をメモしたいと思います。
// メーターの範囲を設定 var arcScale = d3.scaleLinear() .domain([0 , 100]) .range([startAngleRate*90, endAngleRate*90]); // ここは度
メータのスケール設定です。
折れ線グラフの場合は、データの値をWidth や Heightのサイズにあうように
スケールの設定をしていましたが、今回はデータの値の大小によってメーターの針の角度が変わるため
[0, 100] ⇒ [円弧の開始角度, 円弧の終了角度] になるように設定しています。
var arc = d3.arc() .innerRadius(55) .outerRadius(60) .startAngle(startAngleRate * p/2) // ここはラジアン .endAngle(endAngleRate * p/2 )
円弧を作成しています。
innerRadius と outerRadius を設定することで円弧の幅や半径が調整できます。
startAngle と endAngle は、円弧の開始と終わりですがラジアンになるみたいです。
var svgDefs = svg.append('defs'); var mainGradient = svgDefs.append('linearGradient') .attr('id', 'mainGradient'); // グラデーションの設定(3色) mainGradient.append('stop') .attr('class', 'stop-left') // green yellow .attr('offset', '0'); mainGradient.append('stop') .attr('class', 'stop-middle') // orange .attr('offset', '0.6'); mainGradient.append('stop') .attr('class', 'stop-right') // red .attr('offset', '1');
円弧のグラデーションの設定です。色の設定はCSSのクラスでしています。
offset で、グラデーションの間隔が調整できます。
上の例だと、オレンジは真ん中より少し赤よりからスタートしています。
// 目盛の追加 // 0 を100個の5間隔で配列を作成 var ticks = d3.range(0, 105, 5); // グループ要素を目盛数分追加する var ticks_g = svg.selectAll(".tick").data(ticks).enter() .append("g") .attr("class","tick") .attr("transform",function(d,i){ return "translate(" + width/2 + "," + height/2 + ")"; });
円弧の下に目盛を作成するためにまず目盛の数値を作成ています。
d3.range(0, 105, 5) は、[ 0, 5, 10, .... , 90, 95, 100] の配列です。
// 目盛りの線 var ticksLine = d3.line() .x(0) .y(function(d) { return d; }); // 目盛りの追加 ticks_g.append("path") .attr("d", function(d){ if(d % 20 == 0 ){ return ticksLine([ -50 , -45]); } // 主目盛 else{ return ticksLine([-50 , -48]); }} // 補助目盛 ) .attr("stroke", "gray") .attr("stroke-width",2) .attr("stroke-linecap","round") .attr("transform",function(d,i){ return "rotate(" + arcScale(d) + ")"; });
目盛の配置をしています。
上で設定したグループ要素の持つデータ[ 0, 5, 10, .... , 90, 95, 100]が割り当てられます。
20 で割り切れる場合だけ、少し大きい目盛にしてます。
rotate で線を回転させることによって円弧に沿った目盛を表現しています。
// ラベル 位置は角度から(x,y)を求めて、貼り付け ticks_g.append("text") .attr("x", function(d){ return 32 * Math.sin(arcScale(d) * (Math.PI / 180)); }) .attr("y" , function(d){ return -32 * Math.cos(arcScale(d) * (Math.PI / 180)) + 2; }) .attr("text-anchor", "middle") .attr("fill", "black") .attr("font-size", "14px") .attr("font-family", "sans-serif") .text(function(d){ if(d % 20 == 0 ){ return d; }});
ラベルを配置をしています。
こちらも 20 で割り切れる場合だけ、文字を出力しています。
rotateで回転させてもよかったのですが、文字が回転すると見にくいため、
x, y の座標は、回転させるべく角度から三角関数で求めています。
度からラジアンに戻すため Math.PI / 180 をかけています。
(y座標の +2 は位置の調整のため追加しました。)
D3.jsでリソースモニターみたいなグラフの作成(2) version 4.0
前回の記事では D3.js 3.0 と 4.0 の違いを簡単に記載しました。
今回は、タイマーやグリッドを使ってみようと思います。このまま 4.0 で進めます。
作成するグラフ
WindowsのタスクマネージャーにあるCPU使用率を表すグラフを参考にしてみました。
雰囲気が出ていると思うのですが、いかがでしょう。
See the Pen D3_ Resource_Monitor by book_stone (@book_stone) on CodePen.
ソースコード
主だった個所をメモしたいと思います。
// 0 を100個の配列を作成 var array = d3.range(100).map(function(d) { return 0; });
d3.rangeは、0 から 指定した数までの配列を作成する関数です。
d3.range(10)で、[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]となりますが、
この場合、mapで、値ごとに 0 を返却しているので [ 0, 0, ... , 0, 0] 0が100この配列になります。
// X軸 var tick = d3.range(0 , 100, 10); var xAxis = d3.axisBottom(xScale) .tickSizeInner(-height) .tickSizeOuter(-height) .tickFormat("") .tickValues(tick);
基本的にはスケールを作成して d3.axisBottom(xScale) だけでデフォルト値で動きます。
d3.range(0 , 100, 10) は、0 から 100 まで 10 step という意味で、
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90] を 表します。
tickSizeInner は、グリッドラインの長さです。内側に伸ばすためマイナスになります。
tickSizeOuter は、外側のラインです。これで囲んでいる線を表現します。
tickFormat は、グラフのラベル(数字など)のフォーマットです。今回はなし。
tickValues は、グリッド幅です。range で作った配列を入れています。
// 面の部分 var area = d3.area() .x(function(d,i) { return xScale(i); }) .y0(height) .y1(function(d,i) { return yScale(d); });
グラフの薄青い面部分です。
4.0 では、d3.svg.area から d3.area と名称が短くなっています。
area は、データごとに (x0, y0) と (x1, y1) の2点の座標を作成しますが
area.x(value) で x0, x1 同時に設定します。*1
なので、以下と同値(のはず。)
var area = d3.area() .x0(function(d,i) { return xScale(i); }) .y0(height) .x1(function(d,i) { return xScale(i); }) .y1(function(d,i) { return yScale(d); });
当然、データが1個だけだと面にはらない*2ので、データの個数は2個以上で面が表現できます。
詳しくはこちら
GitHub - d3/d3-shape: Graphical primitives for visualization, such as lines and areas.
// カウント var i = 10 // D3のTimer関数(setIntervalとより良いことがある?) d3.interval(pushData, 1000 );
これは setInterval と同じような使い方をしています。
次のpushData()を 1000 ms で読んでいます。
カウントも次の呼び出されている関数内で使用しています。
// データ追加し、再描画がするメソッド function pushData(){ // 先頭を削除 array.shift(); // 正規分布の乱数発生 var rand = Math.pow(d3.randomNormal(0, 0.3)(), 2); // 配列に追加 array.push(rand); // 再描画 paths.attr("d", line); areas.attr("d", area); // グリッドもずらしていく // 呼ばれるごとに 開始するtickの値を 9 はじまり、 8 はじまりと下げている tick = d3.range((i--)%10, 100, 10); xAxis.tickValues(tick); axis.call(xAxis); // グリッド線のリセット if(i<=0) i=10; }
1000 ms で呼ばれている関数です。
疑似データとして D3 正規分布(ガウス分布)*3から取得しています。
d3.randomNormal(0, 0.3)() 平均 0 標準偏差 0.3 の正規分布から取得しています。
二乗しているのはマイナスをなくすためだけです。
tick = d3.range((i--)%10, 100, 10)
これで、i が 9 に下がると [9, 19, 29, 39, 49, 59, 69, 79, 89, 99] の配列を作成し、
それを新たなX軸のグリッドに入れなおして、動きを表現しています。
感想
とりあえず、動いたといった感じでしょうか。
X軸のグリッドの更新の仕方に不安がありますが。。
ひとまずでした。