0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

MFCからSVG変換Advent Calendar 2022

Day 2

SVG 画像の ViewPort 設定と MFC でのファイル出力

Last updated at Posted at 2022-12-01

ViewPort 設定

ViewPort は SVG 画像の描画範囲を指定する属性値となります。
ルートになる SVG タグに記載します。

SvgImage.h
class SvgImage
{
protected:
	CString m_strFilePath;		// ファイル名
	CArray<mdSvgTag*, mdSvgTag*> m_arrayTag;	// タグ
	mdRect m_rectAll;	// viewport
public:
	SvgImage();
    ~SvgImage();
+   CreateSvgImage(CRect rectAll);
+   SvgImage::CreateSvgRoot(double dWidth, double dHeight);
}
SvgImage.cpp
void SvgImage::CreateSvgImage(CRect rectAll)
{
	m_rectAll = rectAll; // 座標変換のために保持
	CreateSvgRoot(rectAll.Width() + 1, rectAll.Height()+ 1);
}

void SvgImage::CreateSvgRoot(double dWidth, double dHeight)
{
	SvgTag* svgRoot = new SvgTag();
    svgRoot->m_strName = L"svg"; // SVGタグ
	svgRoot->m_nDepth = 0; // ルートになるタグなので階層は "0"
	svgRoot->m_bOne = FALSE; // 全体を囲むタグのため FALSE

	CString csStr;
	csStr.Format(_T("%lf"), dWidth);
	svgRoot->m_arrAttr.Add(L"width");
	svgRoot->m_arrAttrValue.Add(csStr);

	csStr.Format(_T("%lf"), dHeight);
	svgRoot->m_arrAttr.Add(L"height");
	svgRoot->m_arrAttrValue.Add(csStr);

	svgRoot->m_arrAttr.Add(L"version");
	svgRoot->m_arrAttrValue.Add(L"1.1");

	svgRoot->m_arrAttr.Add(L"xmlns");
	svgRoot->m_arrAttrValue.Add(L"http://www.w3.org/2000/svg");

	svgRoot->m_arrAttr.Add(L"xmlns:xlink");
	svgRoot->m_arrAttrValue.Add(L"http://www.w3.org/1999/xlink");

	m_arrayTag.Add(svgRoot);
}

CreateSvgRoot 関数に分ける必要はありませんが
機能単位でタグを追加する形にしたいため分けています。
必要ない場合は CreateSvgImage にまとめてください。

ルートとなる svg タグを準備します。

	SvgTag* svgRoot = new SvgTag();
    svgRoot->m_strName = L"svg"; // SVGタグ
	svgRoot->m_nDepth = 0; // ルートになるタグなので階層は "0"

CRectをそのまま保持するのは座標変換に使用するためです。

	m_rectAll = rectAll; // 座標変換のために保持

m_bOne フラグはファイル保存の際に閉じるタグを作成するか、
タグの宣言と共にタグを閉じるかの判定を行うために使用します。

	svgRoot->m_bOne = FALSE; // 全体を囲むタグのため FALSE

名前空間は属性値に当たりますので SvgTag クラスに
属性値として追加してあります。
属性値の数は属性名と同じ個数作られますので、
CArray クラスにそれぞれ追加します。

	CString csStr;
	csStr.Format(_T("%lf"), dWidth);
	svgRoot->m_arrAttr.Add(L"width");
	svgRoot->m_arrAttrValue.Add(csStr);

	csStr.Format(_T("%lf"), dHeight);
	svgRoot->m_arrAttr.Add(L"height");
	svgRoot->m_arrAttrValue.Add(csStr);

	svgRoot->m_arrAttr.Add(L"version");
	svgRoot->m_arrAttrValue.Add(L"1.1");

	svgRoot->m_arrAttr.Add(L"xmlns");
	svgRoot->m_arrAttrValue.Add(L"http://www.w3.org/2000/svg");

	svgRoot->m_arrAttr.Add(L"xmlns:xlink");
	svgRoot->m_arrAttrValue.Add(L"http://www.w3.org/1999/xlink");

本体にタグを追加して完了です。

	m_arrayTag.Add(svgRoot);

ファイルへの書き出し

MFCのテキストファイル出力で行います。

ヘッダは今後長くなりそうなので追加の部分のみ

SvgImage.h
class SvgImage
{
public:
+   void OutputSVGImageFile(LPCTSTR strPath);
}
SvgImage.cpp
void SvgImage::OutputSVGImageFile(LPCTSTR strPath)
{
	FILE* pFile = NULL;
	errno_t err = _tfopen_s(&pFile, strPath, L"w, ccs=UTF-8");	// UTF-8にて出力する
	if (err == 0)
	{
		CStdioFile fileSVG(pFile);
		CString str = L"";
    	CArray<CString, CString> stackTag; // 親のタグ名をスタックに入れてクローズに使う
		int nCurrentDepth = 0; // タグの階層
		int nTagSize = (int)m_arrayTag.GetSize(); // タグの全サイズ
		for (int i = 0; i < nTagSize; i++)
		{
			mdSvgTag *svgTag = m_arrayTag.GetAt(i);
			str += L"<" + svgTag->m_strName;
			for (int j = 0; j < svgTag->m_arrAttr.GetSize(); j++)
			{
				str += L" " + svgTag->m_arrAttr.GetAt(j) + L"=\"" + svgTag->m_arrAttrValue.GetAt(j) + L"\"";
			}
			str += svgTag->m_bOne ? L"/>\r" : L">\r";
			if (svgTag->m_strValue.GetLength() > 0)
			{
				str += svgTag->m_strValue;
			}
			if (svgTag->m_bOne == FALSE)
			{
				stackTag.Add(svgTag->m_strName);
				nCurrentDepth++;
			}
			if (nTagSize > (i + 1))
			{
				while (m_arrayTag.GetAt(i + 1)->m_nDepth < nCurrentDepth)
				{
					str += L"</" + stackTag.GetAt(stackTag.GetSize() - 1) + L">\r";
					stackTag.RemoveAt(stackTag.GetSize() - 1);
					nCurrentDepth--;
				}
			}
			else
			{
				int nSize = (int)stackTag.GetSize();
				while (nSize > 0)
				{
					str += L"</" + stackTag.GetAt(stackTag.GetSize() - 1) + L">\r";
					stackTag.RemoveAt(stackTag.GetSize() - 1);
					nCurrentDepth--;
					nSize = (int)stackTag.GetSize();
				}
			}
		}
		fileSVG.WriteString(str);
		fileSVG.Flush();
		fileSVG.Close();
	}
}

ファイルのオープンクローズは説明を省略させていただきます。

ツリー構造にせずにリスト構造を使っているためコードがややこしくなっています。
SVG画像はタグが親子構造になっており、親は子を包み込むかたちでタグのオープンとクローズが配置されます。
そのため、親となるタグ名はスタックしておき、子を全て出力した後にクローズしてスタックから削除します。

    	CArray<CString, CString> stackTag; // 親のタグ名をスタックに入れてクローズに使う
		int nCurrentDepth = 0; // タグの階層

タグの数だけ書き出しを繰り返します。
str に出力する文字列を追加していく形です。

タグ名を最初に書き込み、タグに含まれる属性値を全て書き出します。

単独の(値がない)タグは宣言と共にクローズする必要があるため判定した上でクローズします。

値がある場合は値を書き込みます。
クローズは子タグを書き込んだ後に行う必要があるため、親タグ名としてスタックしておきます。

nCurrentDepth の値は現在参照しているタグの階層になります。

			mdSvgTag *svgTag = m_arrayTag.GetAt(i);
			str += L"<" + svgTag->m_strName;
			for (int j = 0; j < svgTag->m_arrAttr.GetSize(); j++)
			{
				str += L" " + svgTag->m_arrAttr.GetAt(j) + L"=\"" + svgTag->m_arrAttrValue.GetAt(j) + L"\"";
			}
			str += svgTag->m_bOne ? L"/>\r" : L">\r";
			if (svgTag->m_strValue.GetLength() > 0)
			{
				str += svgTag->m_strValue;
			}
			if (svgTag->m_bOne == FALSE)
			{
				stackTag.Add(svgTag->m_strName);
				nCurrentDepth++;
			}

スタックしておいたタグのクローズ処理を行います。
現在参照しているのが最終のタグでなければ次のタグの階層と nCurrentDepth に記録してある階層を比較して次のタグの階層と nCurrentDepth が同じ値になるまでタグのクローズを行います。

現在参照しているのが最終のタグの場合スタックに残っているタグを全てクローズします。

			if (nTagSize > (i + 1))
			{
				while (m_arrayTag.GetAt(i + 1)->m_nDepth < nCurrentDepth)
				{
					str += L"</" + stackTag.GetAt(stackTag.GetSize() - 1) + L">\r";
					stackTag.RemoveAt(stackTag.GetSize() - 1);
					nCurrentDepth--;
				}
			}
			else
			{
				int nSize = (int)stackTag.GetSize();
				while (nSize > 0)
				{
					str += L"</" + stackTag.GetAt(stackTag.GetSize() - 1) + L">\r";
					stackTag.RemoveAt(stackTag.GetSize() - 1);
					nCurrentDepth--;
					nSize = (int)stackTag.GetSize();
				}
			}

この書き込み方法ですべてのタグの書き出しが完了する作りになっています。

  • タグのオープン
  • タグ内の属性と属性値
  • タグのクローズ

これらの整合性が取れるように各々のタグのデータを用意します。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?