MOSS2010βのメディアライブラリで MP4 を Silverlight で再生する方法

MOSS2010のベータ版が公開されていたので早速試してみた。
今回から(?)ドキュメントコレクションの一つとしてメディアライブラリが作成可能で、縮小表示ビューでは対応したファイルは Silverlight で直接ブラウザ上から再生できるようになっている。現在 MOSS として対応しているファイルは wmv, wma, mp3 のみのようでそれ以外は再生ではなくダウンロードする処理になってしまった。Silverlight はMP4の再生も対応しているはずなので何とかならないかなーと探っていたところひとまずの解決策を見つけた。
下記ファイルの59行目に任意の拡張子を付け加えればとりあえずはブラウザ上の Silverlight で再生できる。アイコンの表示とかはおかしいままだけどコレで MP4 はブラウザ上で再生可能に。

C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions
  \14\TEMPLATE\LAYOUTS\1041\CmsSiteManager.js

L59 : var g_silverlightExtensions=new Array("wmv","wma","mp3");
  →  var g_silverlightExtensions=new Array("wmv","wma","mp3","mp4");

RTMする際には対応しておいてほしいところだ。

synergy-plus における106日本語KBでのALT+半角/全角(修正)

前回のを使っていたところ、ALT+半角/全角による IME の ON/OFF が効かなくなっていたので修正。

-	/* 0x019 */ { kKeyHanjaKanzi },	// VK_KANJI
+	/* 0x019 */ { kKeyZenkaku },	// VK_KANJI

英語キーボードだと問題でちゃうかなぁ…。その場合は古いバージョンを。
一応ビルドし直したものも。

synergy-plus r236 mod rev パッチ&バイナリ

synergy-plus の日本語対応+クリップボード共有拡張+αのパッチとバイナリ

下記の続きというか、まとめ。

r236 からの変更点をまとめたパッチと x86/x64 のバイナリについて、下記に公開しておく。236からの主な変更点は下記の通り。

  • 無変換、変換、半角/全角、ひらがな・カタカナキーに対応
  • エクスプローラのファイルについてクリップボードで共有できるように対応(Windowsのみ)
  • ログウィンドウでの日本語文字化けの修正
  • ALT をつかんだままになる問題の修正
  • 英語キーボードで Alt+~ で漢字変換の ON/OFF が出来ないのを修正(未確認)

synergy-plus r236 mod パッチ&バイナリ

とりあえずテストは WinXP SP3 間でのみ。Win7 環境の準備はもう少し時間がかかりそう。

追記

ちょっと修正をしたのでこっちも載せておく。詳細はこっちの日記を。

synergy-plus r236 mod rev パッチ&バイナリ

synergy-plus へファイルのクリップボード共有機能実装

前回の続きで、synergy-plus にタイトルの通りファイルに対するクリップボード有機能を実装する。実現方法はファイルのデータ本体をやりとりするという方法もあるが、そうすると巨大なファイルの際に問題が発生するのが目に見えているので、Windows間限定になってしまうが、Windowsの管理共有機能を利用する。

CF_HDROP

エクスプローラからファイルがクリップボードにコピーされるとフォーマットIDとしてはCF_HDROPになり、ファイルの取得方法はファイルのDrag&Drop時と同じようにDragQueryFile関数で取得できる。また、Win2000以降はファイル名にUnicode(UTF16)が使われるのでこれらを考慮して実装する。

変更箇所

元々がかなり整理して作られていたので変更しなければならない箇所はすぐにわかる。クリップボードに送られるフォーマットごとにWindows環境用は、CMSWindowsClipboardXXXConverterという名前がついている。そのため、今回はCMSWindowsClipboardFilePathConverter.cpp/hを作成する。また、作成したConverterを利用するためにいくつかの変更を行った。作り方は他のConverterのソースが参考になる。

IClipboard.h

synergyは複数のOS間で利用できるように作られているので、クリップボードのフォーマットを独自に定義してあり、IClipboard.hにある。ここに、ファイルのパスを表すkFilePathを追加し、これを用いて機能を実現することに。もし、ファイルパスではなくファイルのデータ丸ごとやりとりする場合等はここに別の値を追加してやればできるだろう。どうやらUTF8で文字のやりとりが多いようなのでそれに従ってファイルパスもUTF16ではなくUTF8に変換して送ることにした。

diff --git a/lib/synergy/IClipboard.h b/lib/synergy/IClipboard.h
--- a/lib/synergy/IClipboard.h
+++ b/lib/synergy/IClipboard.h
@@ -56,6 +56,7 @@
 		kText,			//!< Text format, UTF-8, newline is LF
 		kBitmap,		//!< Bitmap format, BMP 24/32bpp, BI_RGB
 		kHTML,			//!< HTML format, HTML fragment, UTF-8, newline is LF
+		kFilePath,		//!< File Path format, UTF-8, separetor is \t
 		kNumFormats		//!< The number of clipboard formats
 	};
CMSWindowsClipborad.cpp

MSWindowsとファイル名にあるものはすべてWindows用の実装になっている。ここのコンストラクタで各種コンバータが登録されているので作成したCMSWindowsClipboardFilePathConverterを登録するように追加する。

diff --git a/lib/platform/CMSWindowsClipboard.cpp b/lib/platform/CMSWindowsClipboard.cpp
--- a/lib/platform/CMSWindowsClipboard.cpp
+++ b/lib/platform/CMSWindowsClipboard.cpp
@@ -17,6 +17,7 @@
 #include "CMSWindowsClipboardUTF16Converter.h"
 #include "CMSWindowsClipboardBitmapConverter.h"
 #include "CMSWindowsClipboardHTMLConverter.h"
+#include "CMSWindowsClipboardFilePathConverter.h"
 #include "CLog.h"
 #include "CArchMiscWindows.h"
 
@@ -39,6 +40,7 @@
 	}
 	m_converters.push_back(new CMSWindowsClipboardBitmapConverter);
 	m_converters.push_back(new CMSWindowsClipboardHTMLConverter);
+	m_converters.push_back(new CMSWindowsClipboardFilePathConverter);
 }
 
 CMSWindowsClipboard::~CMSWindowsClipboard()
CMSWindowsClipboardFilePathConverter.cpp

クリップボードにコピーされたファイルパスは管理共有を使ったUNCパスに変換(複数アイテムの場合はタブ(\t)を区切り文字)し、UTF8にした後で相手先に送信する。受信側は逆順に展開し、クリップボードにデータとしてセットするように実装している。

#include "CMSWindowsClipboardFilePathConverter.h"
#include "CLog.h"
#include "shellapi.h"
#include "shlobj.h"
#include "CUnicode.h"

#define	WCToCString(x)	(CString((const char*)(x), wcslen(x)*sizeof(wchar_t)))

//
// CMSWindowsClipboardFilePathConverter
//

CMSWindowsClipboardFilePathConverter::CMSWindowsClipboardFilePathConverter()
{
	OSVERSIONINFO os_info;
	os_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	::GetVersionEx(&os_info);
	if ( os_info.dwPlatformId == VER_PLATFORM_WIN32_NT )
	{	//	Widechar
		useWideChar = TRUE;
		
		//	ARCH->getHostName()
		DWORD size = sizeof(computerNameWC);
		if ( ::GetComputerNameW(computerNameWC, &size) == FALSE )
		{	//	Failed
			computerNameWC[0] = 0;
		}
	}
	else
	{	//	Multibyte
		useWideChar = FALSE;
		DWORD size = sizeof(computerNameMB);
		if ( ::GetComputerNameA(computerNameMB, &size) == FALSE )
		{	//	Failed
			computerNameMB[0] = 0;
		}
	}
	separator = CUnicode::textToUTF8(CString("\t"));
}

CMSWindowsClipboardFilePathConverter::~CMSWindowsClipboardFilePathConverter()
{

}

IClipboard::EFormat
CMSWindowsClipboardFilePathConverter::getFormat() const
{
	return IClipboard::kFilePath;
}

UINT
CMSWindowsClipboardFilePathConverter::getWin32Format() const
{
	return CF_HDROP;
}

HANDLE
CMSWindowsClipboardFilePathConverter::fromIClipboard(const CString& data) const
{	//	data is UTF8
	std::vector<CString> fileList;
	{	//	separate data with separator('\t')
		CString s = data;
		for (int p = 0; (p = s.find(separator)) != s.npos; ) {
#ifdef _DEBUG
			LOG((CLOG_DEBUG "> %s", CUnicode::UTF8ToText(s.substr(0, p)).c_str()));
#endif
			fileList.push_back(s.substr(0, p));
			s = s.substr(p + separator.size());
		}
		if ( s.length() > 1 )
		{
#ifdef _DEBUG
			LOG((CLOG_DEBUG ">* %s", CUnicode::UTF8ToText(s).c_str()));
#endif
			fileList.push_back(s);
		}
	}

	LOG((CLOG_DEBUG "file: recv count > %d / widechar > %d", fileList.size(), useWideChar));
#ifdef _DEBUG
	LOG((CLOG_DEBUG "      %s", CUnicode::UTF8ToText(data).c_str()));
	for ( int i = 0; i < fileList.size(); i++ )
	{
		LOG((CLOG_DEBUG "> %s", CUnicode::UTF8ToText(fileList[i]).c_str()));
	}
#endif

	LPDROPFILES lpDropFile;
    HDROP   hDrop;
    UINT    bufferSize = 0;

	//	calc buffer size & convert encoding
    if ( useWideChar == TRUE )
	{	//	widechar
		for ( UINT i = 0;i < fileList.size(); i++ )
		{	//	Widechar is UTF16 in Windows
			fileList[i] = CUnicode::UTF8ToUTF16(fileList[i]);
			bufferSize += fileList[i].size() + sizeof(wchar_t);	//	length + NULL
		}
    }
    else
    {   // multibyte
        for ( UINT i = 0; i < fileList.size(); i++ )
        {
			fileList[i] = CUnicode::UTF8ToText(fileList[i]);
			bufferSize += fileList[i].size() + 1;
		}
    }

    hDrop = (HDROP)::GlobalAlloc(GHND,sizeof(DROPFILES) + bufferSize + 2);
    if (hDrop == NULL)
	{
		return NULL;
	}
    lpDropFile = (LPDROPFILES) ::GlobalLock(hDrop);
    lpDropFile->pFiles = sizeof(DROPFILES);
    lpDropFile->pt.x = 0;
    lpDropFile->pt.y = 0;
    lpDropFile->fNC = FALSE;
    lpDropFile->fWide = useWideChar;
    if ( useWideChar == TRUE )
    {	//	widechar
        wchar_t *buf;
        buf = (wchar_t*)(&lpDropFile[1]);
        for ( UINT i = 0; i < fileList.size(); i++ )
        {
			memcpy(buf, fileList[i].c_str(), fileList[i].size());
            buf += (fileList[i].size()/sizeof(wchar_t));
            *buf++ = 0;
        }
		*buf = 0;
    }
    else
    {   //	multibyte
        char *buf;
        buf = (char *)(&lpDropFile[1]);
        for ( UINT i = 0; i < fileList.size(); i++ )
        {
			memcpy(buf, fileList[i].c_str(), fileList[i].size());
            buf += fileList[i].size();
            *buf++ = 0;
        }
		*buf = 0;
    }
    ::GlobalUnlock(hDrop);
    return hDrop;
}

CString
CMSWindowsClipboardFilePathConverter::toIClipboard(HANDLE data) const
{
	CString fileList;
	UINT fileCount = 0;

	//	FileCount
	if ( useWideChar == TRUE )
		fileCount = ::DragQueryFileW((HDROP)data, (UINT)-1, NULL, 0);
	else
		fileCount = ::DragQueryFileA((HDROP)data, (UINT)-1, NULL, 0);

	LOG((CLOG_DEBUG "file: send count > %d / widechar > %d", fileCount, useWideChar));

	if ( fileCount == 0 )
	{	//	nothing
		return CString();
	}

	//	Get file path from clipboard
	if ( useWideChar == TRUE )
	{
		wchar_t szPath[MAX_PATH];
		for ( UINT i = 0; i < fileCount; i++ )
		{
			::DragQueryFileW((HDROP)data, i, szPath, sizeof(szPath));
			CString cnvPath = convertPath(szPath);
			fileList.append(cnvPath);
			fileList.append(separator);	//	separate with '\t'
			LOG((CLOG_DEBUG "      %s", CUnicode::UTF8ToText(cnvPath).c_str()));
		}
	}
	else
	{
		char szPath[MAX_PATH];
		for ( UINT i = 0; i < fileCount; i++ )
		{
			::DragQueryFileA((HDROP)data, i, szPath, sizeof(szPath));
			CString cnvPath = convertPath(szPath);
			fileList.append(cnvPath);
			fileList.append(separator);	//	separate with '\t'
			LOG((CLOG_DEBUG "      %s", szPath));
			LOG((CLOG_DEBUG "      -> %s", cnvPath.c_str()));
		}
	}

	return fileList;
}

// Convert to UNC path using administrative shared folder
CString
CMSWindowsClipboardFilePathConverter::convertPath(char* szPath) const
{
	CString path = CString(szPath);
	CString unc;
	if ( path.substr(0, 2) == "\\\\" )
	{	//	Remote Path
		unc = CString(path);
	}
	else
	{	//	Local Path
		unc = CString("\\\\");
		unc.append(computerNameMB);
		unc.append("\\");
		unc.append(path.replace(1, 1, "$"));	//	Convert 'C:\...' to 'C$\...'
	}
	return CUnicode::textToUTF8(unc);
}
CString
CMSWindowsClipboardFilePathConverter::convertPath(wchar_t* szPath) const
{
	size_t lenPath = wcslen(szPath);

	if ( lenPath < 3 )
		return CString();

	CString unc;
	if ( szPath[0] == L'\\' && szPath[1] == L'\\' )
	{	//	Remote Path
		unc = WCToCString(szPath);
	}
	else
	{	//	Local Path
		unc = WCToCString(L"\\\\");
		unc.append(WCToCString((wchar_t*)computerNameWC));
		unc.append(WCToCString(L"\\"));
		char* drive = new char[sizeof(wchar_t)];
		memcpy(drive, szPath, sizeof(wchar_t));
		unc.append(drive, 1 * sizeof(wchar_t));	//	Convert 'C:\...' to 'C$\...'
		delete drive;
		unc.append(WCToCString(L"$"));
		unc.append(WCToCString(szPath+2));
	}
	return CUnicode::UTF16ToUTF8(unc);	//	Widechar is UTF16 in Windows
}
CMSWindowsClipboardFilePathConverter.h
#ifndef CMSWindowsClipboardFilePathConverter_H
#define CMSWindowsClipboardFilePathConverter_H

#include "CMSWindowsClipboard.h"

//! Convert to/from some text encoding
class CMSWindowsClipboardFilePathConverter :
				public IMSWindowsClipboardConverter {
private:
	CString	separator;
	BOOL	useWideChar;
	char	computerNameMB[MAX_COMPUTERNAME_LENGTH+1];
	wchar_t	computerNameWC[MAX_COMPUTERNAME_LENGTH+1];
	CString	convertPath(char* szPath) const;
	CString	convertPath(wchar_t* szPath) const;
public:
	CMSWindowsClipboardFilePathConverter();
	virtual ~CMSWindowsClipboardFilePathConverter();

	// IMSWindowsClipboardConverter overrides
	virtual IClipboard::EFormat
						getFormat() const;
	virtual UINT		getWin32Format() const;
	virtual HANDLE		fromIClipboard(const CString&) const;
	virtual CString		toIClipboard(HANDLE) const;
};

#endif

完成

これでWindows間においてsynergy-plusを介してファイルについてクリップボード共有が行えるようになった。まだWindows7環境に移っていないが、WinXP間においては問題なく動いている。なお、管理共有経由のファイルコピーになるため、それぞれのマシンのログインユーザが相手側のAdministrator権限を持ったユーザアカウント(User/Pass)に一致している必要がある(はず…)。

バイナリとソース

synergy-plusをホストしているGoogle Codeがhgからsvnへ戻った(?)かなにかで、元となったリビジョンがわからなくなった。なので再整理後にソース一式とバイナリをアップしておこうと思う。

synergy-plus の日本語対応+α

気がついたら半年ほど放置していた。まぁ気が向いたときの投稿ということで。

前振り

  1. Windows7を機にx64環境へ移行を計画
  2. 常用していて手放せないsynergyのx64版がない
  3. synergyは開発が長いことと待っていたのでforkしたsynergy-plusがあり、こちらはx86/x64ともにある

というかんじで、synergyのソースにx64パッチを当てている方もいらしたが、せっかくなので設定の互換等もあるsynergy-plusへ移行をすることにした。

synergy-plusへの日本語KB対応パッチ等の適用

synergyでもあったように、日本語キーボードの場合にいろいろと問題があるのはplusでも同じだった。なのでsynergy向けに公開されている情報を元にplusのソースコードにパッチを当ててビルドし直し。調査や情報公開をされていた方々に感謝。

Synergy - 斜に
http://naname.jp/index.php?Synergy
1.3.2 (正確にはr897)をビルド - 忘れないようにメモ
http://d.hatena.ne.jp/desutai/20080304/1204598380
memo/Synergy - wiki@nothing
http://wiki.nothing.sh/page/memo/Synergy

ベースが同じだけあり、全く差異がなかったためすんなりパッチの適用が完了。すでに先達の方が公開されている内容と同じだが、自分用メモのためにも下記にまとめておく。また、併せてALT押しっぱなし問題など上述ページにあるパッチも当てさせていただいた。

diff --git a/lib/platform/CMSWindowsKeyState.cpp b/lib/platform/CMSWindowsKeyState.cpp
--- a/lib/platform/CMSWindowsKeyState.cpp
+++ b/lib/platform/CMSWindowsKeyState.cpp
@@ -64,7 +64,7 @@
 	/* 0x01a */ { kKeyNone },		// undefined
 	/* 0x01b */ { kKeyEscape },		// VK_ESCAPE
 	/* 0x01c */ { kKeyHenkan },		// VK_CONVERT		
-	/* 0x01d */ { kKeyNone },		// VK_NONCONVERT	
+	/* 0x01d */ { kKeyMuhenkan },	// VK_NONCONVERT	
 	/* 0x01e */ { kKeyNone },		// VK_ACCEPT		
 	/* 0x01f */ { kKeyNone },		// VK_MODECHANGE	
 	/* 0x020 */ { kKeyNone },		// VK_SPACE
@@ -1154,6 +1154,12 @@
 				else {
 					// found in table
 					switch (m_buttonToVK[i]) {
+					case VK_KANJI:
+					case VK_OEM_AUTO:
+					case VK_OEM_ENLW:
+						item.m_id       = kKeyZenkaku;
+						break;
+
 					case VK_TAB:
 						// add kKeyLeftTab, too
 						item.m_id         = kKeyLeftTab;
diff --git a/lib/synergy/KeyTypes.h b/lib/synergy/KeyTypes.h
--- a/lib/synergy/KeyTypes.h
+++ b/lib/synergy/KeyTypes.h
@@ -103,7 +103,8 @@
 static const KeyID		kKeyScrollLock	= 0xEF14;
 static const KeyID		kKeySysReq		= 0xEF15;
 static const KeyID		kKeyEscape		= 0xEF1B;
+static const KeyID		kKeyMuhenkan	= 0xEF22;	/* Cancel Conversion */
 static const KeyID		kKeyHenkan		= 0xEF23;	/* Start/Stop Conversion */
 static const KeyID		kKeyHangulKana	= 0xEF26;	/* Hangul, Kana */
 static const KeyID		kKeyHiraganaKatakana = 0xEF27;	/* Hiragana/Katakana toggle */
 static const KeyID		kKeyZenkaku		= 0xEF2A;	/* Zenkaku/Hankaku */
@@ -106,8 +107,8 @@
 static const KeyID		kKeyHenkan		= 0xEF23;	/* Start/Stop Conversion */
 static const KeyID		kKeyHangulKana	= 0xEF26;	/* Hangul, Kana */
 static const KeyID		kKeyHiraganaKatakana = 0xEF27;	/* Hiragana/Katakana toggle */
 static const KeyID		kKeyZenkaku		= 0xEF2A;	/* Zenkaku/Hankaku */
-static const KeyID		kKeyHanjaKanzi	= 0xEF2A;	/* Hanja, Kanzi */
+static const KeyID		kKeyHanjaKanzi	= 0x0060;	/* Hanja, Kanzi */
 static const KeyID		kKeyDelete		= 0xEFFF;	/* Delete, rubout */
 
 // cursor control
diff --git a/lib/platform/CSynergyHook.cpp b/lib/platform/CSynergyHook.cpp
--- a/lib/platform/CSynergyHook.cpp
+++ b/lib/platform/CSynergyHook.cpp
@@ -202,6 +202,9 @@
 bool
 doKeyboardHookHandler(WPARAM wParam, LPARAM lParam)
 {
+	if( g_mode != kHOOK_RELAY_EVENTS )
+		return false;
+
 	// check for special events indicating if we should start or stop
 	// passing events through and not report them to the server.  this
 	// is used to allow the server to synthesize events locally but
@@ -682,21 +685,22 @@
 keyboardLLHook(int code, WPARAM wParam, LPARAM lParam)
 {
 	if (code >= 0) {
-		// decode the message
-		KBDLLHOOKSTRUCT* info = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
-		WPARAM wParam = info->vkCode;
-		LPARAM lParam = 1;							// repeat code
-		lParam      |= (info->scanCode << 16);		// scan code
-		if (info->flags & LLKHF_EXTENDED) {
-			lParam  |= (1lu << 24);					// extended key
-		}
-		if (info->flags & LLKHF_ALTDOWN) {
-			lParam  |= (1lu << 29);					// context code
-		}
-		if (info->flags & LLKHF_UP) {
-			lParam  |= (1lu << 31);					// transition
-		}
-		// FIXME -- bit 30 should be set if key was already down but
-		// we don't know that info.  as a result we'll never generate
-		// key repeat events.
+		if( g_mode == kHOOK_RELAY_EVENTS ) {
+			// decode the message
+			KBDLLHOOKSTRUCT* info = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
+			WPARAM wParam = info->vkCode;
+			LPARAM lParam = 1;							// repeat code
+			lParam      |= (info->scanCode << 16);		// scan code
+			if (info->flags & LLKHF_EXTENDED) {
+				lParam  |= (1lu << 24);					// extended key
+			}
+			if (info->flags & LLKHF_ALTDOWN) {
+				lParam  |= (1lu << 29);					// context code
+			}
+			if (info->flags & LLKHF_UP) {
+				lParam  |= (1lu << 31);					// transition
+			}
+			// FIXME -- bit 30 should be set if key was already down but
+			// we don't know that info.  as a result we'll never generate
+			// key repeat events.
 
@@ -702,7 +706,8 @@
 
-		// handle the message
-		if (keyboardHookHandler(wParam, lParam)) {
-			return 1;
+			// handle the message
+			if (keyboardHookHandler(wParam, lParam)) {
+				return 1;
+			}
 		}
 	}

ログウィンドウの日本語化

現在ログウィンドウには2バイト文字が表示されると表示が崩れるので、これに対してのパッチを当てる。原因はロケールの設定がされていないためのようなので、アプリケーション起動時にシステムロケールを取得して設定するように変更。

diff --git a/cmd/synergyc/synergyc.cpp b/cmd/synergyc/synergyc.cpp
--- a/cmd/synergyc/synergyc.cpp
+++ b/cmd/synergyc/synergyc.cpp
@@ -852,6 +852,9 @@
 int WINAPI
 WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int)
 {
+	// Change locale to system locale for 'wctomb' etc...
+	setlocale(LC_CTYPE, setlocale(LC_CTYPE, ""));
+
 	try {
 		CArchMiscWindows::setIcons((HICON)LoadImage(instance,
 									MAKEINTRESOURCE(IDI_SYNERGY),
diff --git a/cmd/synergys/synergys.cpp b/cmd/synergys/synergys.cpp
--- a/cmd/synergys/synergys.cpp
+++ b/cmd/synergys/synergys.cpp
@@ -1266,6 +1266,9 @@
 int WINAPI
 WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int)
 {
+	// Change locale to system locale for 'wctomb' etc...
+	setlocale(LC_CTYPE, setlocale(LC_CTYPE, ""));
+
 	try {
 		CArchMiscWindows::setIcons((HICON)LoadImage(instance,
 									MAKEINTRESOURCE(IDI_SYNERGY),

次なる課題は

synergyクリップボードの内容についてテキストとビットマップは共有できるが、エクスプローラのファイルについては現在できない。そこで、ファイルについてもクリップボードを経由して共有できるようになると非常に便利なのでこの機能を synergy-plus へ実装したいと思う。せっかくオープンソースで公開されている訳だし、いじらにゃ損(?)ということで。

蛇足

CMAKEってのを初めて使ってみたけど便利!自分のビルド環境を使えるというのがいい。synergyの場合、無理矢理VS2008でビルドするといろいろ警告が出たりと気になっていたけど、plusでCMAKEからVS2008のソリューションを生成すればそんなことはなかった。

Word から InlineShape の画像を取り出す

Word 文書に埋め込まれた画像を取り出すにはどうしたら…といろいろ調べて回ってみると、クリップボードを経由して画像を取りだしているサンプルは見つけた。が、Word は拡縮前(劣化前)の画像データを持っている*1のでそれを何とかして取りだしてみた。
スマートなやり方ではない気がするけどポイントは下記の通り

  • Range.get_XML() から該当部分の DOCX(XML) を取得して、Base64Encode された元の画像データを取得する
  • 取得した画像データから実際に Word 上に表示されているサムネイル画像を作成する
  • Word 上の画像の単位は Point なので Pixel へ変換するため、PointsToPixels を用いて変換する

下記サンプルを実行すれば、Shape.png と Thumb.png が生成される。

object missing = Type.Missing;
 
//    取り出したい InlineShape
var shape = ActiveDocument.InlineShapes[1];
 
//    画像じゃなければ処理しない(チャートの場合などがある)
if ( shape.Type != Word.WdInlineShapeType.wdInlineShapePicture )
    return;
 
System.Drawing.Image image = null;    //    元画像
System.Drawing.Image thumb = null;    //    サムネイル
 
//    InlineShape の高さと幅を取得する
//    Width, Height はそのままだと単位が pt なので pixel へ変換する
int pixWidth = (int)shape.Application.PointsToPixels(shape.Width, ref missing);
int pixHeight = (int)shape.Application.PointsToPixels(shape.Height, ref missing);
 
//    素の画像データを取得するため DOCX に埋め込まれる XML を取得する
//    この中に Base64 エンコードされた素の画像データが入っている
XmlDocument xmlShape = new XmlDocument();
xmlShape.LoadXml(shape.Range.get_XML(false));
XmlNamespaceManager nm = new XmlNamespaceManager(xmlShape.NameTable);
nm.AddNamespace("w", "http://schemas.microsoft.com/office/word/2003/wordml");
//    画像データがあるノードを取得する
var binDataNode = xmlShape.DocumentElement.SelectSingleNode("//w:pict/w:binData", nm);
 
if ( binDataNode == null )
{    //    XML 内に画像データが見つからなかった
    //    代替として表示されているものをクリップボードを経由して画像作成
    shape.Select();
    shape.Application.Selection.Copy();
    image = Clipboard.GetImage();
}
else
{    //    素の画像データが取得できた場合
    //    メモリに展開した後に Image クラスを作成
    MemoryStream ms = new MemoryStream(System.Convert.FromBase64String(binDataNode.InnerText));
    image = System.Drawing.Image.FromStream(ms);
 
    //    クリッピングされている場合の処理
    //    PictureFormat に CropXXXX として値が入っているので取得する
    //    (Width とかと同じく pt から pixel へ変換する)
    int cropTop = (int)shape.Application.PointsToPixels(shape.PictureFormat.CropTop, ref missing);
    int cropBottom = (int)shape.Application.PointsToPixels(shape.PictureFormat.CropBottom, ref missing);
    int cropLeft = (int)shape.Application.PointsToPixels(shape.PictureFormat.CropLeft, ref missing);
    int cropRight = (int)shape.Application.PointsToPixels(shape.PictureFormat.CropRight, ref missing);
 
    //    クリッピング用 Rectangle 作成
    System.Drawing.Rectangle clippingRect = new System.Drawing.Rectangle
    {
        X = cropLeft,
        Y = cropTop,
        Width = image.Width - ( cropRight + cropLeft ),
        Height = image.Height - ( cropBottom + cropTop ),
    };
    //    サムネイル用の Bitmap を作成(Height と Width は現在表示しているサイズで)
    thumb = new System.Drawing.Bitmap(pixWidth, pixHeight, image.PixelFormat);
    //    サムネイルに描画する
    var g = System.Drawing.Graphics.FromImage(thumb);
    g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
    g.DrawImage(image,
        new System.Drawing.Rectangle(0, 0, thumb.Width, thumb.Height),
        clippingRect,
        System.Drawing.GraphicsUnit.Pixel);
}
 
image.Save("Shape.png", ImageFormat.Png);
if ( thumb != null )
{
    thumb.Save("Thumb.png", ImageFormat.Png);
}

*1:大きい画像を貼り付けると自動的に縮小されるが、画像を右クリック>サイズからリセットをすれば実寸大に表示される

Word 文書からきれいな HTML とはてな記法で出力する - 経過(1)

現在までに完成している機能

  • 見出し
  • リスト
  • 文中のスタイル変更

これからやる機能

  • Hyperlink処理
  • テーブル処理
  • 画像処理
  • 文書整形

ポイント

処理単位

Word.Document や Word.Range のプロパティにある下記を使えばそれぞれの単位で処理が行える。

プロパティ名 説明
Sections 節ごと
Paragraphs 段落(ENTER改行)ごと
Sentences 一文ごと
Words 単語ごと(日本語文書の場合はあんまり意味ない…)
Characters 一文字ごと

カウントするだけなら Count プロパティをみればいいだけなのでコードに全く意味はないけどサンプル↓

Word.Document document = Globals.ThisAddIn.Application.ActiveDocument;
int countParagraph = 0;
int countSentence = 0;
int countWord = 0;
int countCharacter = 0;
foreach ( Word.Section section in document.Sections )
{
    foreach ( Word.Paragraph paragraph in section.Range.Paragraphs )
    {
        foreach ( Word.Range sentence in paragraph.Range.Sentences )
        {
            foreach ( Word.Range word in sentence.Words )
            {
                foreach ( Word.Range charcter in word.Characters )
                {
                    countCharacter++;
                }
                countWord++;
            }
            countSentence++;
        }
        countParagraph++;
    }
    string msg = string.Format("下記セクションには段落が{0}, 文が{1}, 単語が{2}, 文字が{3} あります。\n---------------------\n{4}", 
        countParagraph, countSentence, countWord, countCharacter, section.Range.Text);
    MessageBox.Show(msg);
}

スタイルの文中変更を確認するためには Characters で一文字ずつみていかないとだめぽい…。DOCX の XML から XSLT で HTML へ変換した方がよいのだろうか…。