Subsections

13章 H25.2.13 ファイル処理

13.1 本日の課題

  1. 「C言語によるSVG描画ファイルの作成」[13.5] を前から順に読み, C言語におけるファイル処理を理解する. 必要に応じて, ファイル処理については「ファイル処理」[13.3] SVGについては「SVGによるグラフィックス」[13.4]を参照すること.

  2. [program 1] を書けるようにすること.

  3. 課題1[13.5.2], 課題2[13.5.3], 課題3[13.5.5], 課題4[13.5.6]すべてについて, SVGファイルを作成し, Webブラウザで正しく作成できたかを確認する.
  4. 最後に, 4つの課題について出力したSVGファイル( .svg のつくファイル)と対応するC言語のソースコード( .c の付くファイル)をメールで送ること.

注意:


13.2 Windows における拡張子

Windows では初めは, ファイルの拡張子を自動的に付けたり, 省略して表示しなかったりするように設定されている. これらは, 自分で作成したプログラムの中でのファイル名とは一致しない場合もあるので, この機能はオフにしておくとよい.

Windows Vista では, 「エクスプローラ」のメニューから以下のようにして設定できる.

  1. メニューバーの「整理」$\Rightarrow$ 「フォルダの検索とオプション」により「フォルダオプション」ダイアログを開く.

  2. 「フォルダオプション」ダイアログにおいて, 「表示」タブを表示し, 「登録されている拡張子は表示しない」のチェックを外す.


13.3 ファイル処理

13.3.1 ファイル処理の概要

printf関数を用いて画面に出力するように, ファイルに出力したり, scanf関数を用いてキーボードから入力するように, ファイルに予め値や文字を保存しておき, その内容を変数に入力する方法について述べる.

ファイル処理は, 先頭にfが付いた関数を用い, 以下の手順で行う.

  1. まず, fopen関数によりファイルを開く. これによりファイルを指定するためのFILE構造体のメンバに適切な値が設定され, FILE構造体へのポインタ(ファイルポインタ)が返される.

  2. ファイルを開くことができたら, 以下のファイル操作を行う.

  3. ファイルに出力するためには, fprintf関数を用いる. また, ファイルから入力するためには, fscanf関数を用いる. fprintf 関数と fscanf 関数は, どのファイルに出力したり, どのファイルから入力するかを指定する必要があるが, それ以外は printf関数やscanf関数と同じである. ファイルの指定には, fopen関数により得られたファイルポインタを用いる.

  4. ファイルを使用しなくなった時点で, できるだけ早くfclose関数によりそのファイルを閉じる.

fprintf関数, fscanf関数, fopen関数, fclose関数は stdio.hでプロトタイプ宣言されている. また, FILE構造体も stdio.hの中で定義されている. したがって, fprintf関数やfscanf関数を用いるときは, それに先立って stdio.h ファイルを include しておく必要がある. 以降で, 各関数について説明する.

fopen関数
ファイルを開く.
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
path には, 入力または出力を行いたいファイル名を文字列として与える. mode には, 書き込みを行いたいときは"w" (write)を 読み込みを行いたいときは "r" (read)を指定する.

戻り値は, FILE構造体へのポインタである. ファイルを開くことができた場合には, FILE構造体をシステムが用意し, FILE構造体のメンバに必要な値が代入され, それへのポインタのみが返される. FILE構造体は, ファイル処理に必要な情報をメンバとして持つ.

ファイルを開くことに失敗した場合は, NULLを返す. NULLはstdio.hで定義されている定数のアドレスで, エラーが起きたときに返される値として使用される.

fclose関数
ファイルを閉じる.
#include <stdio.h>
int fclose(FILE *fp);
fpにより指定されるファイルを閉じる.

fprintf関数
ファイルに書式に従って出力する.

以下に 参考のためprintf 関数と fprintf関数のプロトタイプ宣言を比較して挙げる.

#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);

fprintf関数は, fpで指定されるファイルに, formatで指定された書式に従って, ...で与えられた任意の数の変数の値を出力する. fpとしては, fopen関数で返された FILE構造体のアドレスを用いる.

fscanf関数
ファイルから書式に従って入力する.

以下に scanf 関数と fscanf関数のプロトタイプ宣言を挙げる.

#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *fp, const char *format, ...);
fscanf関数は, fpで指定されるファイルから, formatで指定された書式に従って, ...で与えられた任意の数の変数に入力(代入)を行う.

UNIXを使用している場合は, 端末の中で man (manual) コマンドを用いることにより各関数の詳細な説明を得ることができる. 例えば, printf関数について調べるときには,

$ man printf

13.3.2 fprintf関数によるファイルへの出力

  1. ★ 次のプログラムでは, まずfopen関数により "output.txt"というファイルを書き込み("w")のために開く. 以降このファイルは fpにより識別されることになる. 次に, このファイルにfprintf関数により整数 100を書き込んでいる. 次に, 同じファイルに数値 200を書き込んでいる.

    #include <stdio.h>
    
    int main(void)
    {
    	 FILE *fp;
    
    	 fp = fopen("output.txt", "w");
    	 fprintf(fp, "%d\n", 100);
    	 fprintf(fp, "%d\n", 200);
    
    	 return 0;
    }
    
    出力された結果は, output.txtファイルをメモ帳 (Windows), emacs (UNIX), gedit (UNIX)などのエディタで開いて確認できる. またコマンドラインから more (Windows, UNIX), lv (UNIX), cat (UNIX) などのコマンドで内容を画面に表示できる.
    $ more output.txt
    ...
    $ lv output.txt
    ...
    $ cat output.txt
    
    lv を終了するときは q を押す. morelvで次のページに進むときは スペースキーを押す.

  2. ★ 次に, 複数のファイルを同時に処理するために, ファイルの識別を行う例を示す. fp_aoutputA.txtに出力するためのファイルポインタ, fp_boutputB.txtに出力するためのファイルポインタである. fp_a に対して100, fp_bに対して500を出力している.
    #include <stdio.h>
    
    int main(void)
    {
    	 FILE *fp_a;
    	 FILE *fp_b;
    
    	 fp_a = fopen("outputA.txt", "w");
    	 fp_b = fopen("outputB.txt", "w");
    
    	 fprintf(fp_a, "%d\n", 100);
    	 fprintf(fp_b, "%d\n", 500);
    	 
    	 return 0;
    }
    

  3. ★ 実際には書き込み禁止などにより, ファイルを開くことができないこともある. その場合には, 書き込みの処理を行うことはできない. ここでは, ファイルを開くことができない場合にはプログラムを終了することとする. fopen関数は, ファイルを開くことに失敗した場合, NULLを返す. NULLは, stdio.hで定義されていているアドレスで, ファイルを開くことができなかったときそのアドレスが返される.
    #include <stdio.h>
    
    int main(void)
    {
    	 FILE *fp;
    
    	 fp = fopen("output.txt", "w");
    	 if(fp == NULL){
    		  printf("File can not be created.");
    	 }else{
    		  fprintf(fp, "%d\n", 100);
    	 }
    
    	 return 0;
    }
    

  4. ★ OSには, 同時に開くことができるファイルの数には制限がある場合が多い. ファイルの処理が終わったらできるだけ早くそのファイルを閉じる必要がある. ファイルを閉じるためには, fclose関数を用いる. 次の例では, outputA.datへの書き込みが終わった時点でそのファイルを閉じ, outputB.datへの書き込みが終わった時点でそのファイルを閉じている. outputB.dat を開くのも書き込みを行う直前が望ましい.
    #include <stdio.h>
    
    int main(void)
    {
    	 FILE *fp_a, *fp_b;
    
    	 fp_a = fopen("outputA.dat", "w");
    	 if(fp_a == NULL){
    		  printf("File A can not be created.");
    	 }else{
    		  fprintf(fp_a, "%d\n", 100);
    		  fprintf(fp_a, "%d\n", 200);
    		  fcolse(fp_a); // 必要がなくなったらできるだけ早くcloseする.
    	 }
    	 
    	 fp_b = fopen("outputB.dat", "w");
    	 if(fp_b == NULL){
    		  printf("File A can not be created.");
    	 }else{
    		  fprintf(fp_b, "%d\n", 500);
    		  fprintf(fp_b, "%d\n", 600);
    		  fcolse(fp_b); // 必要がなくなったらできるだけ早くcloseする.
    	 }
    
    	 return 0;
    }
    

  5. ★ C言語では, 代入式は「代入した値」を持つ. 次の例では, 代入式 fp=fopen(output.dat", w")fpの値と同じである. この代入式が, NULL と同じである場合ファイルを開くことに失敗したことになる. この表現はC言語ではよく使われるが, 読みにくいように思う.
    #include <stdio.h>
    
    int main(void)
    {
    	 FILE *fp;
    
    	 // よく使われる表現.
    	 if ( (fp = fopen("output.dat", "w")) == NULL ) {
    		  printf("File A can not be created.");
    	 } else {
    		  fprintf(fp, "%d\n", 100);
    		  fclose(fp);
    	 }
    	 
    	 return 0;
    }
    

  6. ★ ファイルを開くことができなかった場合, その時点でそのプログラムを終了することもできる. そのためには, プログラム中の任意の時点でプログラムを終了するexit関数を用いる. exit関数は, stdlib.hでプロトタイプ宣言されているので, この関数を使用する場合は, stdlib.h をインクルードする必要がある.

    [program 1]

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
    	 FILE *fp;
    
    	 // よく使われる表現.
    	 if ( (fp = fopen("output.dat", "w")) == NULL ) {
    		  printf("File A can not be created.");
    		  exit(1);
    	 }
    	 fprintf(fp, "%d\n", 100);
    	 fclose(fp);
    
    	 return 0;
    }
    

    また, exit関数の引数の値や main 関数の戻り値 (main 関数中のreturn 文に与える値)は, プログラムの戻り値としてOSに返される. この値はstdlib.h で定義されていて, プログラムが正常に終了するときEXIT_SUCCESSは返し, エラーで終了するときはEXIT_FAILUREを返すとよい.

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
    	 FILE *fp;
    
    	 fp = fopen("output.dat", "w");
    
    	 if ( fp  == NULL ) {
    		  printf("File A can not be created.");
    		  exit(EXIT_FAILURE);      // プログラム異常終了.
    	 } else {
    		  fprintf(fp, "%d\n", 100);
    		  fclose(fp);
    	 }
    	 
    	 return EXIT_SUCCESS;        // プログラム正常終了.
    }
    

  7. 先ほどの2つのファイルを開いてそれぞれに値を書き込むプログラムは, ファイルを開く際のエラーの処理を加えると次のようになる.
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
    	 FILE *fp_a, *fp_b;
    
    	 fp_a = fopen("outputA.dat", "w");
    	 if(fp == NULL){
    		  printf("File A can not be created.");
    		  exit(EXIT_FAILURE);
    	 }
    
    	 fp_b = fopen("outputB.dat", "w");
    	 if(fp_b == NULL){
    		  printf("File A can not be created.");
    		  exit(EXIT_FAILURE);
    	 }
    	 
    	 fprintf(fp_a, "%d\n", 100);
    	 fprintf(fp_b, "%d\n", 500);
    
    	 fclose(fp_a);
    	 fclose(fp_b);
    
    	 return EXIT_SUCCESS;
    }
    

  8. いくつものファイルを開くプログラムでは, ファイルを開くたびに エラー処理を記述すると長くなってしまう. そこで, ファイルを開く部分からエラー処理までを一つの関数にすると簡潔に表現できる. 次の例では, エラー処理を備えたファイルを開く関数 my_fopenを作成した.
    #include <stdio.h>
    #include <stdlib.h>
    
    FILE *my_fopen(const char *filename, const char *mode)
    {
    	 FILE *fp;
    
    	 fp = fopen(filename, mode);
    
    	 if(fp  == NULL){
    		  printf("Fiel %s can not be created.\n", filename);
    		  exit(EXIT_FAILURE);
    	 }
    	 return fp;
    }
    
    int main(void)
    {
    	 FILE *fp;
    
    	 fp = my_fopen("outputA.dat", "w");
    	 fprintf(fp, "%d\n", 100);
    	 fclose(fp);
    
    	 fp = my_fopen("outputB.dat", "w");
    	 fprintf(fp, "%d\n", 500);
    	 fclose(fp);
    	 
    	 return EXIT_SUCCESS;
    }
    

    ここで作成したmy_fopen関数により main関数の内部が簡潔に記述できていることに注意すること. また, ここに示したように, 同時にファイルを開いていないのであれば, 1つのファイルポインタで複数のファイルを操作できる.

  9. エラーが起きたときのエラーメッセージは, 予め用意されているのでそれをそのまま用いた方がよい. ファイルを開くことができなかった原因も色々あり, その原因に応じて異なるメッセージが用意されている. これは, perror関数により出力することができる. この関数は追加のメッセージを文字列として受け取り, 直前に起きたエラーに対応するメッセージを出力する. 次に, perrorを用いたエラー処理をする例を示す.
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
    	 FILE *fp;
    	 char *filename = "output.dat";
    
    	 if((fp = fopen(filename, "w"))  == NULL){
    		  perror(filename);
    		  exit(EXIT_FAILURE);
    	 }
    	 fprintf(fp, "%d\n", 100);
    	 fclose(fp);
    	 
    	 return EXIT_SUCCESS;
    }
    
    この例では, ファイルを開くことができない場合
    output.dat: No such file or directory
    
    output.dat: Permission denied
    
    のように原因に応じてメッセージが表示される.

13.3.3 fscanf関数によるファイルからの読み込み

fscanf関数を用いて, ファイルに書き込んである内容を読み込んでみる.

次の例は, output.datファイルから整数をxに読み込むプログラムである.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	 FILE *fp;
	 int x;

	 fp = fopen("output.dat", "r");
	 if(fp  == NULL){
		  perror("output.dat");
		  exit(EXIT_FAILURE);
	 }
	 fscanf(fp, "%d", &x);
	 fclose(fp);

	 printf("x =%d\n", x);
	 return EXIT_SUCCESS;
}
この例では, 読み込みに用いる変数の型(ここではint)およびfscanfの書式(ここでは%d)が, ファイルに書き込んである値の型と一致している必要がある. このプログラムを正しく動作させるためには, 予め別のプログラムで output.txt というファイルを作成し, 1行目に整数を書き込んでおく必要がある. もしくは, emacs, メモ帳などエディタで作成しておいてもよい.

実際にファイルから入力を行う場合には, fscanf関数が読み込みに失敗した場合のエラー処理が必要となる. この処理は煩雑であるので, fgets関数を用いた別法を用いた方がいい.

最後に, output.datファイルに整数を書き込み, 同じプログラム中でそのファイルを開いて値を読み込む例を示す.

#include <stdio.h>
#include <stdlib.h>

FILE *my_fopen(const char *filename, const char *mode)
{
	 FILE *fp;

	 fp = fopen(filename, mode);

	 if(fp  == NULL){
		  perror(filename);
		  exit(EXIT_FAILURE);
	 }
	 return fp;
}

int main(void)
{
	 FILE *fp_w, *fp_r;
	 int x;

	 fp_w = my_fopen("output.dat", "w");
	 fprintf(fp_w, "%d\n", 100);
	 fclose(fp_w);

	 fp_r = my_fopen("output.dat", "r");
	 fscanf(fp_r, "%d", &x);
	 fclose(fp_r);

	 printf("x = %d\n", x);
	 return EXIT_SUCCESS;
}
ここで作成したmy_fopen関数は, ファイルの読み込みにも書き込みにも対応でき, main関数の内部が簡潔に記述できている.


13.4 SVGによるグラフィックス

13.4.1 SVGとは

SVGは Webなどで用いることができる Scalable Vector Grapohicsである.

http://www.hcn.zaq.ne.jp/___/REC-SVG11-20030114/index.html

例えば直線の場合, 始点と終点を指定するだけの ベクトル型の画像であるのでどんなに拡大してもギザギザにならない. これに対し, jpeg, bmp, tiff などのラスタ型の画像は点の集まりとして 画像が表現されており, 拡大するとギザギザになってしまう.

SVG ファイルは Firefox などのWebブラウザなどで画像としてで表示することができる. また, Inkscape (Linux, Windows) や Illustrator (Windows) などで編集できる.

SVGファイルは, 例えば下のような内容を持っている.

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<svg xmlns='http://www.w3.org/2000/svg'>
    <line x1='100' y1='500' x2='500' y2='100' stroke='blue' stroke-width='5'/>
</svg>


13.4.2 SVGファイルを作って表示してみる.

  1. 上のファイルを emacs (UNIX), gedit (UNIX), サクラエディタ (UNIX), メモ帳 (UNIX)などのエディタに写し, aaa.svg のように 拡張子として.svgがつくファイル名で保存してしてみよう.

  2. SVGファイルは FirefoxなどのWebブラウザで表示できる.
  3. 再度, エディタなどでファイルを変更し保存した後は, ブラウザの再読み込みの機能で新しい内容を表示し直すことができる.

  4. SVGファイルは Inkscape や Illustrator で開いて編集できる. Linux 上で Inkscape を用いて編集する場合には, 端末から $ Inkscape aaa.svg のようにして起動する. もしくは, $ Inkscape のようにファイル名を与えずに起動したのちに, ファイルを読み込むことも可能である. この場合には, デスクトップのメニューから起動することもできる.

13.4.3 SVGファイルの構造

先に挙げたSVGファイルをもう一度載せる.
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<svg xmlns='http://www.w3.org/2000/svg'>
    <line x1='100' y1='500' x2='500' y2='100' stroke='blue' stroke-width='5' />
</svg>
このSVGファイルの構造は次のようになっている.

<line ... />の中の'で囲まれた部分のパラメータ (100, 500, blueなど) を変えて色々な直線を描画してみよう.

<svg></svg>で囲まれた部分には, 複数の描画要素を記述できる. 3本の直線を描く場合には次のようになる.

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<svg xmlns='http://www.w3.org/2000/svg'>
    <line x1='100' y1='500' x2='500' y2='100' stroke='blue' stroke-width='5' />
    <line x1='100' y1='600' x2='500' y2='300' stroke='cyan' stroke-width='10' />
    <line x1='100' y1='700' x2='500' y2='500' stroke='red' stroke-width='20' />
</svg>

13.4.4 SVGの基本図形

以下に代表的な基本図形を挙げる.

直線
<line x1='数' y1='数' x2='数' y2='数' stroke='色' stroke-width='数' />
<circle cx='数' cy='数' r='数' stroke='色' stroke-width='数' fill='色' />
折れ線
<polyline points='点の組' stroke='色' stroke-width='数' fill='色' />
四角形
<rect width='数' height='数' x='数' y='数' stroke='色' stroke-width='数' fill='色' />
テキスト
<text x='数' y='数' fill='数'>.....</text>

上で挙げた基本図形を描画する例を以下に示す.

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<svg xmlns='http://www.w3.org/2000/svg'>
  <rect width='750' height='450' x='50' y='150' stroke='black' stroke-width='1' fill='azure' />
  <line x1='100' y1='550' x2='700' y2='350' stroke='blue' stroke-width='5' />
  <polyline points='100 550, 250 370, 450 520, 700 200' stroke='red' stroke-width='3' fill='none' />
  <circle cx='100' cy='550' r='10' fill='yellow' stroke='black' stroke-width='1' />
  <text x='100' y='200' font-size='30'>ABC</text>
</svg>


13.5 C言語によるSVG描画ファイルの作成

以下の内容を持つdrawing.svgというファイル名のファイルを作成してみよう.
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<svg xmlns='http://www.w3.org/2000/svg'>
    <line x1='100' y1='500' x2='500' y2='100' stroke='blue' stroke-width='5' />
</svg>

13.5.1 printf関数により画面に出力

まず練習として, printf関数を用いて画面に出力してみる.
#include <stdio.h>

int main(void)
{
	 printf("<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n");
	 printf("<svg xmlns='http://www.w3.org/2000/svg'>\n");
	 printf("<line x1='100' y1='500' x2='500' y2='100' stroke='blue' stroke-width='5' />\n");
	 printf("</svg>\n");

	 return 0;
}

13.5.2 fprintf関数によりファイルに出力

次にfprintf関数を用いてファイルに出力する. このプログラムを実行するとdrawing.svgというファイルに上に示した内容が出力される.

[[課題1]]FirefoxなどのWebブラウザによりこのファイルを画像として表示し, 正しく出力されたか確認しよう. 詳細は「SVGファイルを作って表示してみる.」[13.4.2]を参照のこと. 作成したSVGファイルと対応するC言語のソースコードをメールで送ること.

#include <stdio.h>

int main(void)
{
	 FILE *fp;                       // (*1)

	 fp = fopen("drawing.svg", "w"); // (*2)
	 if(fp==NULL){                   // (*3)
		  printf("drawing.svg: File can not be created.");
	 }else{
		  fprintf(fp, "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n"); // (*4)
		  fprintf(fp, "<svg xmlns='http://www.w3.org/2000/svg'>\n");
		  fprintf(fp, "    <line x1='100' y1='500' x2='500' y2='100' stroke='blue' stroke-width='5' />");
		  fprintf(fp, "</svg>");

		  fclose(fp);                // (*5)
	 }
	 return 0;
}

13.5.3 fprintf関数により変数の値をファイルに出力

fprintf 関数は printf 関数と同じように, 書式に従って変数の値を出力する. 変数の値を出力する場合には, 書式として与える文字列のうち%で指定される位置に, 引数として与えられた変数の値を出力する. 変数の型によって異なる書式を指定する必要があるので注意すること.

書式の復習:

[[課題2]]以下の変数 x1, y1, x2, y2, stroke, stroke_widthの値を変更することで異なる直線が描かれることを確認しましょう. また, fopenの第1引数として与えている "drawing.svg" の部分を, "drawing2.svg" のように変更すると出力する先のファイルを変更できることを確認しましょう. 作成したSVGファイルと対応するC言語のソースコードをメールで送ること.

#include <stdio.h>

int main(void)
{
	 FILE *fp;
	 double x1=100.0, y1=500.0, x2=500.0, y2=100.0, stroke_width=5.0;
	 char *stroke  = "blue";
	 
	 fp = fopen("drawing.svg", "w");
	 if(fp==NULL){
		  printf("drawing.svg: File can not be created.");
	 }else{
		  fprintf(fp, "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n");
		  fprintf(fp, "<svg xmlns='http://www.w3.org/2000/svg'>\n");
		  fprintf(fp, "<line x1='%g' y1='%g' x2='%g' y2='%g' stroke='%s' stroke-width='%g'/>\n",
				  x1, y1, x2, y2, stroke, stroke_width);
		  fprintf(fp, "</svg>\n");

		  fclose(fp);
	 }
	 return 0;
}

13.5.4 ファイル操作を関数化する

ファイル処理は, 常に fpがNULLでない場合のブロックが中心となる. ファイルを開くことができなかった場合, プログラムを終了することにすると, ファイル処理が複雑になっても簡潔に表現できる. プログラムを任意の行で終了するためには exit関数を用いる. この関数は, stdlib.h でプロトタイプ宣言されている.
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	 FILE *fp;
	 double x1=100.0, y1=500.0, x2=500.0, y2=100.0, stroke_width=5.0;
	 char *stroke  = "blue";
	 
	 // --- ファイルを開く. -----------------------------------
	 fp = fopen("drawing.svg", "w");
	 if(fp==NULL){
		  printf("drawing.svg: File can not be created.");
		  exit(1);
	 }
	 // --- ファイル処理 --------------------------------------
	 fprintf(fp, "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n");
	 fprintf(fp, "<svg xmlns='http://www.w3.org/2000/svg'>\n");
	 fprintf(fp, "<line x1='%g' y1='%g' x2='%g' y2='%g' stroke='%s' stroke-width='%g' />\n",
			 x1, y1, x2, y2, stroke, stroke_width);
	 fprintf(fp, "</svg>\n");
	 // --- ファイルを閉じる. --------------------------------
	 fclose(fp);
	 // --- 終了. --------------------------------------------
	 return 0;
}

さらに, SVG処理の観点からは, 下に示すように 「描画開始」から「SVG処理終了」の間は何を描画するかで変わるが, その前の「SVG処理開始」から「描画開始」までの準備と, 「SVG処理終了」から「終了」までの後始末はいつも同じである.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	 FILE *fp;
	 double x1=100.0, y1=500.0, x2=500.0, y2=100.0, stroke_width=5.0;
	 char *stroke  = "blue";
	 
	 // --- SVG処理開始 ---------------------------------------
	 fp = fopen("drawing.svg", "w");
	 if(fp==NULL){
		  printf("drawing.svg: File can not be created.");
		  exit(1);
	 }
	 fprintf(fp, "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n");
	 fprintf(fp, "<svg xmlns='http://www.w3.org/2000/svg'>\n");
	 // --- 描画開始 ------------------------------------------
	 fprintf(fp, "<line x1='%g' y1='%g' x2='%g' y2='%g' stroke='%s' stroke-width='%g' />\n",
			 x1, y1, x2, y2, stroke, stroke_width);
	 // --- SVG処理終了 ---------------------------------------
	 fprintf(fp, "</svg>\n");
	 fclose(fp);
	 // --- 終了. --------------------------------------------
	 return 0;
}

そこで, 準備の部分をsvg_open関数にし, 後始末の部分をsvg_close関数にする. 今後は, 描画の部分のみを目的に応じて変更すればよいことになる. main関数の中のファイルポインタは, 他の関数の中で遮蔽されて使われているファイルポインタと区別するため, svgという名前に変更した.

#include <stdio.h>
#include <stdlib.h>

FILE *svg_open(const char *filename)
{
	 FILE *fp;
	 fp = fopen(filename, "w");
	 if(fp==NULL){
		  printf("drawing.svg: File can not be created.");
		  exit(1);
	 }
	 fprintf(fp, "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n");
	 fprintf(fp, "<svg xmlns='http://www.w3.org/2000/svg'>\n");

	 return fp;
}

void svg_close(FILE *fp)
{
	 fprintf(fp, "</svg>\n");
	 fclose(fp);
}

int main(void)
{
	 FILE *svg;
	 double x1=100.0, y1=500.0, x2=500.0, y2=100.0, stroke_width=5.0;
	 char *stroke  = "blue";
	 
	 // --- SVG処理開始 ---------------------------------------
	 svg = svg_open("drawing.svg");
	 // --- 描画開始 ------------------------------------------
	 fprintf(svg, "<line x1='%g' y1='%g' x2='%g' y2='%g' stroke='%s' stroke-width='%g' />\n",
			 x1, y1, x2, y2, stroke, stroke_width);
	 // --- SVG処理終了 ---------------------------------------
	 svg_close(svg);
	 // --- 終了. --------------------------------------------
	 return 0;
}

さらに, 基本図形の描画も関数化しておくと, 中心となるmain関数の中では毎回煩雑な記述をしなくて済むようになる. 次の例では, <line ... /> を出力する部分を line_draw 関数にした.

#include <stdio.h>
#include <stdlib.h>

FILE *svg_open(const char *filename)
{
	 FILE *fp;
	 fp = fopen(filename, "w");
	 if(fp==NULL){
		  printf("drawing.svg: File can not be created.");
		  exit(1);
	 }
	 fprintf(fp, "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n");
	 fprintf(fp, "<svg xmlns='http://www.w3.org/2000/svg'>\n");

	 return fp;
}

void svg_close(FILE *fp)
{
	 fprintf(fp, "</svg>\n");
	 fclose(fp);
}

void line_draw(FILE *fp, double x1, double y1, double x2, double y2,
			   const char *stroke, double stroke_width)
{
	 fprintf(fp, "<line x1='%g' y1='%g' x2='%g' y2='%g' stroke='%s' stroke-width='%g' />\n",
			 x1, y1, x2, y2, stroke, stroke_width);
}

// 異なる描画を行うためには, ここから下の部分を書き換えるだけでよい.
int main(void)
{
	 FILE *svg;
	 double x1=100.0, y1=500.0, x2=500.0, y2=100.0, stroke_width=5.0;
	 char *stroke  = "blue";
	 
	 svg = svg_open("drawing.svg");
	 line_draw(svg, x1, y1, x2, y2, stroke, stroke_width);
	 svg_close(svg);
	 return 0;
}

13.5.5 関数に渡す引数を構造体にまとめる

<line ... />のようなSVG の図形には, x1, y1, x2, y2 のような必須の属性と, stroke, stroke-width のように与えなくてもよい属性がある. また, 与えなくてもよい属性はここに紹介した以外にも多くある. これらの与えなくてよい属性も全て関数の引数として渡すのは煩雑である. 逆に, 一部の属性だけを渡すことにした場合, 別の属性を渡そうとすると, 関数を書き換える必要があり, さらにその関数を呼び出している部分を全て書き直すことになる. そこで, 与えなくてもよい属性は構造体のメンバとし, そのような属性をまとめた構造体1つを関数に渡すことにする.

ここでは stroke と stroke_width をメンバに持つ構造体 ATTRIBUTE を定義する.

#include <stdio.h>
#include <stdlib.h>

typedef struct{
	 char *stroke;
	 double stroke_width;
}  ATTRIBUTE;

FILE *svg_open(const char *filename)
{
	 FILE *fp;
	 fp = fopen(filename, "w");
	 if(fp==NULL){
		  printf("drawing.svg: File can not be created.\n");
		  exit(1);
	 }
	 fprintf(fp, "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n");
	 fprintf(fp, "<svg xmlns='http://www.w3.org/2000/svg'>\n");

	 return fp;
}

void svg_close(FILE *fp)
{
	 fprintf(fp, "</svg>\n");
	 fclose(fp);
}

void line_draw(FILE *fp, double x1, double y1, double x2, double y2,
			   const ATTRIBUTE *attrib)
{
	 fprintf(fp, "<line x1='%g' y1='%g' x2='%g' y2='%g' stroke='%s' stroke-width='%g'/>\n",
			 x1, y1, x2, y2, attrib->stroke, attrib->stroke_width);
}

// 異なる描画を行うためには, ここから下の部分を書き換えるだけでよい.
int main(void)
{
	 FILE *svg;
	 ATTRIBUTE attrib = {"blue", 5.0};
	 
	 svg = svg_open("drawing.svg");
	 line_draw(svg, 100.0, 500.0, 500.0, 100.0, &attrib);
	 svg_close(svg);
	 return 0;
}

[[課題3]] 以下のように直線 <line .../> を自由に描画できるようになった. main関数の中を書き換えて, 例とは異なる直線を描いてみましょう. 出力したSVGファイルと対応するC言語のソースコードをメールで送ること.

#include <stdio.h>
#include <stdlib.h>

typedef struct{
	 char *stroke;
	 double stroke_width;
}  ATTRIBUTE;

FILE *svg_open(const char *filename)
{
	 FILE *fp;
	 fp = fopen(filename, "w");
	 if(fp==NULL){
		  printf("drawing.svg: File can not be created.");
		  exit(1);
	 }
	 fprintf(fp, "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n");
	 fprintf(fp, "<svg xmlns='http://www.w3.org/2000/svg'>\n");

	 return fp;
}

void svg_close(FILE *fp)
{
	 fprintf(fp, "</svg>\n");
	 fclose(fp);
}

void line_draw(FILE *fp, double x1, double y1, double x2, double y2, const ATTRIBUTE *attrib)
{
	 fprintf(fp, "<line x1='%g' y1='%g' x2='%g' y2='%g' stroke='%s' stroke-width='%g' />\n",
			 x1, y1, x2, y2, attrib->stroke, attrib->stroke_width);
}

// 異なる描画を行うためには, ここから下の部分を書き換えるだけでよい.
int main(void)
{
	 FILE *svg;
	 ATTRIBUTE normal = {"blue", 2.0}, alert = {"red", 5.0};
	 
	 svg = svg_open("drawing.svg");
	 line_draw(svg, 100.0, 500.0, 500.0, 100.0, &normal);
	 line_draw(svg, 100.0, 600.0, 500.0, 200.0, &normal);
	 line_draw(svg, 100.0, 100.0, 500.0, 500.0, &alert);
	 svg_close(svg);
	 return 0;
}


13.5.6 SVGの他の基本図形も関数により描画

他の基本図形も関数により描画できるようにした. このために, ATTRIBUTE構造体に fill と font_size をメンバとして加えた. このような変更を行っても先に作成した line_draw 関数に変更を加える必要がないことに注意すること. ただし, mainの中で行っている構造体の初期化の部分は変更する必要がある.

(C99 では, ATTRIBUTE attrib = .stroke="blue", .stroke_width=5のようにメンバ名を指定して初期化ができるので, 構造体のメンバが増えたり, メンバの並び順が変わっても, このような変更も生じない.)

以下では, <rect ... />, <line ... />, <polyline ... />, <circle ... />, <text>...</text>を 関数を用いて描画できるようになった.

[[課題4]] main 関数を変更し下に載せる例とは異なる図形を描画しましょう. 出力したSVGファイルと対応するC言語のソースコードをメールで送ること.

#include <stdio.h>
#include <stdlib.h>

typedef struct{
	 char *stroke;
	 double stroke_width;
	 char *fill;
	 double font_size;
}  ATTRIBUTE;

FILE *svg_open(const char *filename)
{
	 FILE *fp;
	 fp = fopen(filename, "w");
	 if(fp==NULL){
		  printf("drawing.svg: File can not be created.");
		  exit(1);
	 }
	 fprintf(fp, "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n");
	 fprintf(fp, "<svg xmlns='http://www.w3.org/2000/svg'>\n");

	 return fp;
}

void svg_close(FILE *fp)
{
	 fprintf(fp, "</svg>\n");
	 fclose(fp);
}

void line_draw(FILE *fp, double x1, double y1, double x2, double y2, const ATTRIBUTE *attrib)
{
	 fprintf(fp, "<line x1='%g' y1='%g' x2='%g' y2='%g' stroke='%s' stroke-width='%g'/>\n",
			 x1, y1, x2, y2, attrib->stroke, attrib->stroke_width);
}

void rect_draw(FILE *fp, double width, double height, double x, double y, const ATTRIBUTE *attrib)
{
	 fprintf(fp, "<rect width='%g' height='%g' x='%g' y='%g' stroke='%s' stroke-width='%g' fill='%s' />\n",
			 width, height, x, y, attrib->stroke, attrib->stroke_width, attrib->fill);
}

void polyline_draw(FILE *fp, const double *x, const double *y, size_t n, const ATTRIBUTE *attrib)
{
	 size_t i;

	 fprintf(fp, "<polyline points='");
	 for(i=0; i<n-1; i++)
		  fprintf(fp, "%g %g, ", x[i], y[i]);
	 fprintf(fp, "%g %g'", x[n-1], y[n-1]);
	 fprintf(fp, " stroke='%s' stroke-width='%g' fill='%s' />\n",
			 attrib->stroke, attrib->stroke_width, attrib->fill);
}

void circle_draw(FILE *fp, double cx, double cy, double r, const ATTRIBUTE *attrib)
{
	 fprintf(fp, "<circle cx='%g' cy='%g' r='%g' stroke='%s' stroke-width='%g' fill='%s' />\n",
			 cx, cy, r, attrib->stroke, attrib->stroke_width, attrib->fill);
}
	 
void text_draw(FILE *fp, double x, double y, const char *text, const ATTRIBUTE *attrib)
{
	 fprintf(fp, "<text x='%g' y='%g' font-size='%g'>%s</text>\n",
		  x, y, attrib->font_size, text);
}

// 異なる描画を行うためには, ここから下の部分を書き換えるだけでよい.
int main(void)
{
	 FILE *svg;
	 ATTRIBUTE rect_attrib     = {"black", 1.0, "azure",  0.0};
	 ATTRIBUTE line_attrib     = {"blue",  5.0, "none",   0.0};
	 ATTRIBUTE polyline_attrib = {"red",   3.0, "none",   0.0};
	 ATTRIBUTE circle_attrib   = {"black", 1.0, "yellow", 0.0};
	 ATTRIBUTE text_attrib     = {"black", 0.0, "green", 30.0};
	 double x[4] = {100.0, 250.0, 450.0, 700.0};
	 double y[4] = {550.0, 370.0, 520.0, 200.0};
	 
	 svg = svg_open("drawing.svg");
	 rect_draw(svg, 750.0, 450.0, 50.0, 150.0, &rect_attrib);
	 line_draw(svg, 100, 550, 700, 350, &line_attrib);
	 polyline_draw(svg, x, y, 4, &polyline_attrib);
	 circle_draw(svg, 100, 550, 10, &circle_attrib);
	 text_draw(svg, 100, 200, "ABC", &text_attrib);
	 svg_close(svg);
	 return 0;
}

描画に必要な事柄をまとめておく.

ATTRIBUTE
基本図形の属性を表す構造体
typedef struct{
	 char *stroke;        // 線の色
	 double stroke_width; // 線の太さ
	 char *fill;          // 内部を塗りつぶす色
	 double font_size;    // 文字の大きさ
}  ATTRIBUTE;

FILE *svg_open(const char *filename);
出力するSVGファイルを開く.

void svg_close(FILE *fp);
出力するSVGファイルを閉じる.

void line_draw(FILE *fp, double x1, double y1, double x2, double y2, const ATTRIBUTE *attrib);
直線を描く.

void rect_draw(FILE *fp, double width, double height, double x, double y, const ATTRIBUTE *attrib);
四角形を描く.

void polyline_draw(FILE *fp, const double *x, const double *y, size_t n, const ATTRIBUTE *attrib);
折れ線を描く.

void circle_draw(FILE *fp, double cx, double cy, double r, const ATTRIBUTE *attrib);
円を描く.

void text_draw(FILE *fp, double x, double y, const char *text, const ATTRIBUTE *attrib);
テキスト(文字列)を描く.


13.5.7 ヘッダファイルにする

毎回共通の部分を記述するのは面倒であるし, その後誤りを修正したり, 機能を拡張したりする場合に, 全てのファイルを変更する必要が生じる.

この様な場合に, 描画を行う関数などのどのプログラムでも共通の部分と, 各プログラムに固有な部分を別けると効率がよい. 本来は別のソースファイルとして分割しそれぞれをコンパイルした後結合するという手順を踏む.

ここでは, 簡便のため共通部分をヘッダファイルにしてみる. そのために, 以下のように固有部分と共通部分に分割する.

固有な部分は次のように簡潔に記述できる. 新しいプログラムを作成するときは, このファイルのみを書き直せばよい.

#include <stdio.h>
#include "svg0.h"

int main(void)
{
	 FILE *svg;
	 ATTRIBUTE rect_attrib     = {"black", 1.0, "azure",  0.0};
	 ATTRIBUTE line_attrib     = {"blue",  5.0, "none",   0.0};
	 ATTRIBUTE polyline_attrib = {"red",   3.0, "none",   0.0};
	 ATTRIBUTE circle_attrib   = {"black", 1.0, "yellow", 0.0};
	 ATTRIBUTE text_attrib     = {"black", 0.0, "green", 30.0};
	 double x[4] = {100.0, 250.0, 450.0, 700.0};
	 double y[4] = {550.0, 370.0, 520.0, 200.0};
	 
	 svg = svg_open("drawing.svg");
	 rect_draw(svg, 750.0, 450.0, 50.0, 150.0, &rect_attrib);
	 line_draw(svg, 100, 550, 700, 350, &line_attrib);
	 polyline_draw(svg, x, y, 4, &polyline_attrib);
	 circle_draw(svg, 100, 550, 10, &circle_attrib);
	 text_draw(svg, 100, 200, "ABC", &text_attrib);
	 svg_close(svg);
	 return 0;
}

共通部分は svg0.h というファイル名で, main関数があるファイルと同じフォルダ/ディレクトリに保存しておく. これを #include 命令によりファイルの内容をその行に展開する. この場合は, "svg0.h" のように 二重引用符"でファイル名を囲み, 標準で用意されているヘッダーファイルとは異なることを明示する.

svg0.hの内容は以下のようになる. 今後はこのファイルは変更しないで, 複数のプログラムで共通に使用することができる.

#include <stdio.h>
#include <stdlib.h>

typedef struct{
	 char *stroke;
	 double stroke_width;
	 char *fill;
	 double font_size;
}  ATTRIBUTE;

FILE *svg_open(const char *filename)
{
	 FILE *fp;
	 fp = fopen(filename, "w");
	 if(fp==NULL){
		  printf("drawing.svg: File can not be created.");
		  exit(1);
	 }
	 fprintf(fp, "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n");
	 fprintf(fp, "<svg xmlns='http://www.w3.org/2000/svg'>\n");

	 return fp;
}

void svg_close(FILE *fp)
{
	 fprintf(fp, "</svg>\n");
	 fclose(fp);
}

void line_draw(FILE *fp, double x1, double y1, double x2, double y2, const ATTRIBUTE *attrib)
{
	 fprintf(fp, "<line x1='%g' y1='%g' x2='%g' y2='%g' stroke='%s' stroke-width='%g'/>\n",
			 x1, y1, x2, y2, attrib->stroke, attrib->stroke_width);
}

void rect_draw(FILE *fp, double width, double height, double x, double y, const ATTRIBUTE *attrib)
{
	 fprintf(fp, "<rect width='%g' height='%g' x='%g' y='%g' stroke='%s' stroke-width='%g' fill='%s' />\n",
			 width, height, x, y, attrib->stroke, attrib->stroke_width, attrib->fill);
}

void polyline_draw(FILE *fp, const double *x, const double *y, size_t n, const ATTRIBUTE *attrib)
{
	 size_t i;

	 fprintf(fp, "<polyline points='");
	 for(i=0; i<n-1; i++)
		  fprintf(fp, "%g %g, ", x[i], y[i]);
	 fprintf(fp, "%g %g'", x[n-1], y[n-1]);
	 fprintf(fp, " stroke='%s' stroke-width='%g' fill='%s' />\n",
			 attrib->stroke, attrib->stroke_width, attrib->fill);
}

void circle_draw(FILE *fp, double cx, double cy, double r, const ATTRIBUTE *attrib)
{
	 fprintf(fp, "<circle cx='%g' cy='%g' r='%g' stroke='%s' stroke-width='%g' fill='%s' />\n",
			 cx, cy, r, attrib->stroke, attrib->stroke_width, attrib->fill);
}
	 
void text_draw(FILE *fp, double x, double y, const char *text, const ATTRIBUTE *attrib)
{
	 fprintf(fp, "<text x='%g' y='%g' font-size='%g'>%s</text>\n",
		  x, y, attrib->font_size, text);
}

(c)1999-2013 Tetsuya Makimura