InDesign SDK Plugin ExtendScriptのルビの機能を拡張する

ExtendScriptのルビの機能を拡張するPluginは、これで良いのかな・・・？

~~~C++:ExtendRubyAttributeScriptElement.h
/** 利用する事で起こる如何なる不具合にも責任を持てません。
	2022/09/02 更新
*/
#ifndef __ExtendRubyAttributeScriptElement_h__
#define __ExtendRubyAttributeScriptElement_h__

// Interface includes:
#include "IScript.h"
#include "IScriptRequestData.h"
#include "ITextModel.h"
#include "ITextAttrBoolean.h"
#include "ITextAttrUtils.h"
#include "ITextScriptUtils.h"
#include "IRubyStrand.h" // IRubyAttrStrand

// General includes:
// #include "CAlert.h"
#include "CreateObject.h" // CreateObject2
#include "CmdUtils.h"
#include "ScriptData.h"
#include "TextRange.h"
#include "Utils.h"

/** スクリプトエレメントを追加する為の手順（メモ）

	０、１、sdk/devtools/dolly/のdollyツールのFeaturesタブのGenerate Script Supportをチェックしてプロジェクトを作成（JAVAが必要）
		
		２、sdk/build/win/prj/のSDKSamples.slnをVisual Studio Commmunityを開く
		
		３、ソリューションに先ほど作成したプロジェクトを（既存のプロジェクトから）追加する。
		
		４、プロジェクトを開く

		５、このファイルを○○○ID.hのあるフォルダーに保存。（○○○はshort name）

		６、このファイルをプロジェクトに追加

	１、Resource Files/○○○.frの変更

		１、#include "TextScriptID.h" // kCharacterObjectScriptElement
			を追加

		２、VersionedScriptElementInfoを変更、もしくは追加

			例
			resource VersionedScriptElementInfo(1)
			{
				// Contexts
				{
					kFiredrakeScriptVersion, kCoreScriptManagerBoss, kInDesignAllLanguagesFS, k_Wild,
					kFiredrakeScriptVersion, kCoreScriptManagerBoss, kInCopyAllLanguagesFS, k_Wild,
				}
	
				// Elements
				{
					// プロパティを指定します。
					//
					// GetRubyRunResult
					Property
					{
						k○○○PropertyScriptElementGetRubyRunResult, // スクリプトエレメントID ○○○ID.h（HeaderFilesにあり）で設定
						p_GetRubyRunResult, // ScriptID ○○○ScriptingDefs.h（外部依存関係にあり）で設定
						"get ruby run result", // ExtendScriptでの名前 「kohakuNekotarou」としたいなら「kohaku nekotarou」と入力
						"IRubyAttrStrand::GetRubyRunの結果", // description
						BoolType, // プロパティのデータ型
						{} // alternate types
						kNoAttributeClass, // attribute class ID
					}

					// RubyParentText
					Property
					{
						k○○○PropertyScriptElementRubyParentText, // スクリプトエレメントID ○○○ID.h（HeaderFilesにあり）で設定
						p_RubyParentText, // ScriptID ○○○ScriptingDefs.h（外部依存関係にあり）で設定
						"ruby parent text", // ExtendScriptでの名前 「kohakuNekotarou」としたいなら「kohaku nekotarou」と入力
						"ルビの親テキスト", // description
						ObjectType( kTextObjectScriptElement ), // プロパティのデータ型
						{} // alternate types
						kNoAttributeClass, // attribute class ID
					}

					// このプラグインのメソッドとプロパティをスクリプトに接続します。
					Provider
					{
						k○○○ScriptProviderBoss,	// provider boss ID
						{
							// 追加される側のオブジェクト
							Object{ kCharacterObjectScriptElement },

							// 追加するプロパティ スクリプトエレメントIDで指定
							// 
							// kReadOnly
							// 読み込み専用
							Property{ k○○○PropertyScriptElementGetRubyRunResult, kReadOnly },
							Property{ k○○○PropertyScriptElementRubyParentText, kReadOnly },
						}
					}
				}
			};

			resource VersionedScriptElementInfo(2) // 数値を重複させない事
			{
				// Contexts
				{
					kFiredrakeScriptVersion, kCoreScriptManagerBoss, kInDesignAllLanguagesFS, k_Wild,
					kFiredrakeScriptVersion, kCoreScriptManagerBoss, kInCopyAllLanguagesFS, k_Wild,
				}

				// Elements
				{
					// ClearRubyAttribute
					Method
					{
						k○○○MethodScriptElemenClearRubyAttribute, // スクリプトエレメントID ○○○ID.h（HeaderFilesにあり）で設定
						e_ClearRubyAttribute, // スクリプトID ○○○ScriptingDefs.h（外部依存関係にあり）で設定
						"clear ruby attribute", // ExtendScriptでの名前 「kohakuNekotarou」としたいなら「kohaku nekotarou」と入力
						"ルビ属性を消す", // description
						VoidType, // 戻り値
						{} // 引数無し
					}

					// このプラグインのメソッドとプロパティをスクリプトに接続します。
					Provider
					{
						k○○○ScriptProviderBoss,	// provider boss ID
						{
							// 追加される側のオブジェクト
							Object{ kTextObjectScriptElement },

							// 追加するメソッド スクリプトエレメントIDで指定
							Method{ k○○○MethodScriptElemenClearRubyAttribute },
						}
					}
				}
			};

	２、Heade Files/○○○ID.hのScript Element IDsを変更、もしくは追加
		例
		//Script Element IDs
		DECLARE_PMID(kScriptInfoIDSpace, k○○○PropertyScriptElementGetRubyRunResult, k○○○Prefix + 1)
		DECLARE_PMID(kScriptInfoIDSpace, k○○○PropertyScriptElementRubyParentText, k○○○Prefix + 2)
		DECLARE_PMID(kScriptInfoIDSpace, k○○○MethodScriptElemenClearRubyAttribute, k○○○Prefix + 3)

	３、外部依存関係/○○○ScriptingDefs.hのScript Element IDを変更、もしくは追加（ScriptingDefs.hの値と重複しない様に）

		例
		// Method IDs
		enum ○○○ScriptEvents
		{
			// ,を忘れない事
			e_ClearRubyAttribute = 'eCra',
		};

		// Property IDs
		enum ○○○ScriptProperties
		{
			// ,を忘れない事
			p_GetRubyRunResult = 'pGrr',
			p_RubyParentText = 'pRpt',
		};

	４、Source Files/○○○ScriptProvider.cppの変更
	
		１、#include "ExtendRubyAttributeScriptElement.h"
			を追加

		２、HandleMethodのcase部分を変更、もしくは追加

			例
			case e_ClearRubyAttribute:
				status = ExtendRubyAttributeScriptElement::ClearRubyAttribute(parent);
				break;

		３、AccessPropertyのcase部分を変更、もしくは追加
	
			例
			case p_GetRubyRunResult:
				status = ExtendRubyAttributeScriptElement::GetRubyRunResult(propID, data, parent);
				break;
			case p_RubyParentText:
				status = ExtendRubyAttributeScriptElement::RubyParentText(propID, data, parent);
				break;

	５、Releaseビルド

	６、ExtendScriptで使う

		例
		// ストーリーに含まれるルビの親文字を表示していく
		for(var i = 0; i < app.activeDocument.stories.firstItem().characters.count(); i++){
			
			// ルビがオンになっているかどうか（ルビが表示されていないのにrubyFlagがtrueになる場合でも間違いなく判定します）
			if(app.activeDocument.stories.firstItem().characters.item(i).getRubyRunResult){

				// ルビの親文字のテキストオブジェクトを取得して内容を表示 
				alert(app.activeDocument.stories.firstItem().characters.item(i).rubyParentText.contents);

				// ルビの親文字長さだけ先に進める（i++される分、-する）
				i += app.activeDocument.stories.firstItem().characters.item(i).rubyParentText.characters.count() - 1;
			}
		}

		例
		// ルビ属性をクリアする
		app.activeDocument.stories.firstItem().texts.firstItem().clearRubyAttribute();

*/
class ExtendRubyAttributeScriptElement
{
public:
	/** IRubyAttrStrand::GetRubyRunの結果
		@param scriptID_Property 処理するプロパティのIDを識別します。
		@param iScriptRequestData スクリプト要求との間でデータをやり取りするために使用されます。。
		@param iScript_Parent スクリプトアーキテクチャのオブジェクトとして表示し、すべてのスクリプトクライアントで使用できるようにしたい上司に追加されます。
	*/
	 static ErrorCode GetRubyRunResult(ScriptID scriptID_Property, IScriptRequestData* iScriptRequestData, IScript* iScript_Parent);

	/** ルビの親テキスト
		@param scriptID_Property 処理するプロパティのIDを識別します。
		@param iScriptRequestData スクリプト要求との間でデータをやり取りするために使用されます。。
		@param iScript_Parent スクリプトアーキテクチャのオブジェクトとして表示し、すべてのスクリプトクライアントで使用できるようにしたい上司に追加されます。
	*/
	static ErrorCode RubyParentText(ScriptID scriptID_Property, IScriptRequestData* iScriptRequestData, IScript* iScript_Parent);

	/** ルビ属性を消す
		@param scriptID_Property 処理するプロパティのIDを識別します。
		@param iScriptRequestData スクリプト要求との間でデータをやり取りするために使用されます。。
		@param iScript_Parent スクリプトアーキテクチャのオブジェクトとして表示し、すべてのスクリプトクライアントで使用できるようにしたい上司に追加されます。
	*/
	static ErrorCode ClearRubyAttribute(IScript* iScript_Parent);
};

ErrorCode ExtendRubyAttributeScriptElement::GetRubyRunResult(ScriptID scriptID_Property, IScriptRequestData* iScriptRequestData, IScript* iScript_Parent)
{
	// virtual bool16 IScriptRequestData::IsPropertyGet() const
	// この要求がプロパティを取得する場合は kTrue を返します。
	if (iScriptRequestData->IsPropertyGet())
	{
		// エラーの場合にbreakする為
		do {
			// InterfacePtr
			// インターフェイスポインターの操作を簡略化するスマートポインタークラス
			// 
			// ITextModel
			// テキストストーリーのメインインターフェイス
			//
			// Utils
			// kUtilsBoss上のインタフェース内のメソッドを呼び出すためのヘルパークラス
			// 
			// ITextScriptUtils
			// 一般的なテキストおよびテキストフレームユーティリティも含まれています。
			//
			// virtual ITextModel* QueryTextModel(IScript *script)
			// テキストスクリプトオブジェクトからITextModelを取得する 
			InterfacePtr<ITextModel> iTextModel(Utils<ITextScriptUtils>()->QueryTextModel(iScript_Parent));

			// kFailure が返された場合は、アサートを取得します。
			ASSERT(iTextModel);
			if (iTextModel == nil)
			{
				break;
			}

			// IRubyAttrStrand
			// 日本語属性のストランドインタフェース: Ruby
			//
			// virtual IPMUnknown* ITextModel::QueryStrand(ClassID clsID, PMIID faceID) const
			// 特定のクラスを持つモデルにアタッチされたストランド上のインターフェイスへのポインターを返します。
			//
			// IPMUnknown
			// すべてのインターフェイスが継承するルートクラスです。
			InterfacePtr<IRubyAttrStrand> iRubyAttrStrand((IRubyAttrStrand*)iTextModel->QueryStrand(kRubyAttrStrandBoss, IRubyAttrStrand::kDefaultIID));


			// kFailure が返された場合は、アサートを取得します。
			ASSERT(iRubyAttrStrand);
			if (iRubyAttrStrand == nil)
			{
				break;
			}

			// virtual RangeData ITextScriptUtils::GetScriptTextRange(IScript* script)
			// テキストスクリプトオブジェクトから範囲を取得する
			RangeData rangeData_ScriptObject = Utils<ITextScriptUtils>()->GetScriptTextRange(iScript_Parent);
			RangeData::Lean lean;
			TextIndex position = rangeData_ScriptObject.Start(&lean);
			TextIndex runBegin;
			int32 count = 0;

			// virtual bool16 IRubyAttrStrand::GetRubyRun(TextIndex position, int32* count, TextIndex* runBegin = nil)
			// ルビの範囲がある場合は、指定された位置に返します。
			// 
			// @position 位置 IN 目的のテキストインデックス。位置が 0 の場合<NOP
			// @count OUT が nil でない場合は、位置からストランドの端までの距離を返します。
			// @runBegin OUT が nil でない場合は、ルビの先頭のテキストインデックスを返します。
			// return @bool16 ルビがオンになっているかどうかが指定された位置にあります。
			bool16 resultRubyAttr = iRubyAttrStrand->GetRubyRun(position, &count, &runBegin);

			// ScriptData
			// スクリプトアーキテクチャでサポートされている任意のデータ型を保持できるクラスです。
			ScriptData scriptData;

			// void ScriptData::SetBoolean(bool16 boolean)
			scriptData.SetBoolean(resultRubyAttr);

			// virtual void AppendReturnData(const IScript* target, const ScriptID requestID, const ScriptData& returnValue)
			// 特定のターゲットの戻り値データの1つの項目を追加します。
			iScriptRequestData->AppendReturnData(iScript_Parent, scriptID_Property, scriptData);

			// kSuccessが返らないとスクリプトエラーが出るので注意
			return kSuccess;
		} while (false);
	}
	return kFailure;
}

ErrorCode ExtendRubyAttributeScriptElement::RubyParentText(ScriptID scriptID_Property, IScriptRequestData* iScriptRequestData, IScript* iScript_Parent)
{
	// virtual bool16 IScriptRequestData::IsPropertyGet() const
	// この要求がプロパティを取得する場合は kTrue を返します。
	if (iScriptRequestData->IsPropertyGet())
	{
		// エラーの場合にbreakする為
		do {
			// InterfacePtr
			// インターフェイスポインターの操作を簡略化するスマートポインタークラス
			// 
			// ITextModel
			// テキストストーリーのメインインターフェイス
			//
			// Utils
			// kUtilsBoss上のインタフェース内のメソッドを呼び出すためのヘルパークラス
			// 
			// ITextScriptUtils
			// 一般的なテキストおよびテキストフレームユーティリティも含まれています。
			//
			// virtual ITextModel* QueryTextModel(IScript *script)
			// テキストスクリプトオブジェクトからITextModelを取得する 
			InterfacePtr<ITextModel> iTextModel(Utils<ITextScriptUtils>()->QueryTextModel(iScript_Parent));

			// kFailure が返された場合は、アサートを取得します。
			ASSERT(iTextModel);
			if (iTextModel == nil)
			{
				break;
			}

			// IRubyAttrStrand
			// 日本語属性のストランドインタフェース: Ruby
			//
			// virtual IPMUnknown* ITextModel::QueryStrand(ClassID clsID, PMIID faceID) const
			// 特定のクラスを持つモデルにアタッチされたストランド上のインターフェイスへのポインターを返します。
			//
			// IPMUnknown
			// すべてのインターフェイスが継承するルートクラスです。
			InterfacePtr<IRubyAttrStrand> iRubyAttrStrand((IRubyAttrStrand*)iTextModel->QueryStrand(kRubyAttrStrandBoss, IRubyAttrStrand::kDefaultIID));

			// kFailure が返された場合は、アサートを取得します。
			ASSERT(iRubyAttrStrand);
			if (iRubyAttrStrand == nil)
			{
				break;
			}

			// virtual RangeData ITextScriptUtils::GetScriptTextRange(IScript* script)
			// テキストスクリプトオブジェクトから範囲を取得する
			RangeData rangeData_ScriptObject = Utils<ITextScriptUtils>()->GetScriptTextRange(iScript_Parent);
			RangeData::Lean lean;
			TextIndex position = rangeData_ScriptObject.Start(&lean);
			TextIndex runBegin;
			int32 count = 0;

			// virtual bool16 IRubyAttrStrand::GetRubyRun(TextIndex position, int32* count, TextIndex* runBegin = nil)
			// ルビの範囲がある場合は、指定された位置に返します。
			// 
			// @position 位置 IN 目的のテキストインデックス。位置が 0 の場合<NOP
			// @count OUT が nil でない場合は、位置からストランドの端までの距離を返します。
			// @runBegin OUT が nil でない場合は、ルビの先頭のテキストインデックスを返します。
			// return @bool16 ルビがオンになっているかどうかが指定された位置にあります。
			bool16 resultRubyAttr = iRubyAttrStrand->GetRubyRun(position, &count, &runBegin);
			if (resultRubyAttr != kFalse)
			{
				// RequestContext
				// スクリプトDOM（ドキュメントオブジェクトモデル）のコンテキスト
				//
				// virtual const EngineContext& IScriptRequestData::GetRequestContext()	const
				// この要求の要求コンテキストへのアクセス
				const RequestContext requestContext = iScriptRequestData->GetRequestContext();

				// RangeData(TextIndex start, TextIndex end, const RangeData *clip = nil)
				// コンストラクタ
				RangeData rangeData_Text(runBegin, position + count);

				// virtual IScript* QueryTextObject(const RequestContext& context, ITextModel* model, const RangeData& range, const ScriptID& type = kInvalidScriptID)
				// 新しいテキストスクリプトオブジェクトを作成する
				InterfacePtr<IScript> iScript_TextRange(Utils<ITextScriptUtils>()->QueryTextObject(requestContext, iTextModel, rangeData_Text, kInvalidScriptID));

				// ScriptData
				// スクリプトアーキテクチャでサポートされている任意のデータ型を保持できるクラスです。
				// 
				// ScriptData (IScript *object)
				// コンストラクタ
				ScriptData scriptData(iScript_TextRange);

				// virtual void AppendReturnData(const IScript* target, const ScriptID requestID, const ScriptData& returnValue)
				// 特定のターゲットの戻り値データの1つの項目を追加します。
				iScriptRequestData->AppendReturnData(iScript_Parent, scriptID_Property, scriptData);
			}

			// kSuccessが返らないとスクリプトエラーが出るので注意
			return kSuccess;
		} while (false);
	}
	return kFailure;
}

ErrorCode ExtendRubyAttributeScriptElement::ClearRubyAttribute(IScript* iScript_Parent)
{
	// エラーの場合にbreakする為
	do {
		// InterfacePtr
		// インターフェイスポインターの操作を簡略化するスマートポインタークラス
		// 
		// ITextModel
		// テキストストーリーのメインインターフェイス
		//
		// Utils
		// kUtilsBoss上のインタフェース内のメソッドを呼び出すためのヘルパークラス
		// 
		// ITextScriptUtils
		// 一般的なテキストおよびテキストフレームユーティリティも含まれています。
		//
		// virtual ITextModel* QueryTextModel(IScript *script)
		// テキストスクリプトオブジェクトからITextModelを取得する 
		InterfacePtr<ITextModel> iTextModel(Utils<ITextScriptUtils>()->QueryTextModel(iScript_Parent));

		// kFailure が返された場合は、アサートを取得します。
		ASSERT(iTextModel);
		if (iTextModel == nil)
		{
			break;
		}

		// virtual RangeData ITextScriptUtils::GetScriptTextRange(IScript* script)
		// テキストスクリプトオブジェクトから範囲を取得する
		RangeData rangeData_ScriptObject = Utils<ITextScriptUtils>()->GetScriptTextRange(iScript_Parent);

		// 選択したテキスト範囲からルビ属性をオフにする
		// sdksamplesのSnippetRunnerのSnpPerformTextAttrRuby.cppのSnpPerformTextAttrRuby::RemoveRubyを参考 
		{
			// ITextAttrBoolean
			// ブール・テキスト属性
			//
			// template <class FACE> inline FACE* CreateObject2(ClassID clsID)
			// CreateObject(ClassID、PMIID)のように動作しますが、FACEがkDefaultIIDを定義し、正しいFACEポインタが返された場合は、インターフェイスIDを省略できます。
			//
			// PUBLIC_DECL IPMUnknown* CreateObject(ClassID clsID, PMIID iid = IID_IUNKNOWN, const IObjectModel*om = GetObjectModelInstance());
			// 指定されたクラスの新しい非永続オブジェクトを作成し、ボスの要求されたインターフェイスを返します。
			InterfacePtr<ITextAttrBoolean> iTextAttrBoolean(::CreateObject2<ITextAttrBoolean>(kTARubyAttrBoss));
			if (iTextAttrBoolean == nil)
			{
				break;
			}

			// void ITextAttrBoolean::Set(ValueType flag)
			// ブール値を渡されたパラメータに設定します。
			iTextAttrBoolean->Set(kFalse);

			// ICommand
			// 要求をオブジェクトとしてカプセル化し、異なる要求、キューまたはログ要求を持つクライアントをパラメータ化し、元に戻す操作をサポートできるようにする
			//
			// ITextAttrUtils
			// テキスト属性を取得および設定するためのユーティリティ。
			//
			// virtual ICommand* ITextAttrUtils::BuildApplyTextAttrCmd(ITextModel* model, const RangeData& range, const IPMUnknown* attr, const ClassID& which)
			// テキスト属性の適用コマンド設定を作成して返し、指定されたテキスト属性をストーリー内の範囲に適用します。
			InterfacePtr<ICommand> iCommand_ApplyTextAttrCmd(Utils<ITextAttrUtils>()->BuildApplyTextAttrCmd(iTextModel, rangeData_ScriptObject, iTextAttrBoolean, kCharAttrStrandBoss));
			if (iCommand_ApplyTextAttrCmd == nil)
			{
				break;
			}

			// CmdUtils
			// コマンドを作成および処理するためのユーティリティー
			// 
			// static ErrorCode CmdUtils::ProcessCommand(ICommand* cmd)
			// cmd実行のデフォルトの方法である通常のcmd処理を使用してコマンドを処理します。
			CmdUtils::ProcessCommand(iCommand_ApplyTextAttrCmd);
		}

		// 指定したテキスト属性をクリアする
		{
			InterfacePtr<ICommand> iCommand_clearTextAttrCmd(nil);

			// sdksamplesのSnippetRunnerのSnpPerformTextAttrRuby.cppのSnpPerformTextAttrRuby::ApplyRubyのClassIDを参考
			ClassID classID[] = {
				kTARubyAttrBoss,
				kTARubyStringBoss,
				kTAMojiRubyBoss,
				kTARubyAlignmentBoss,
				kTARubyPositionBoss,
				kTARubyXOffsetBoss,
				kTARubyYOffsetBoss,
				kTARubyFontUIDBoss,
				kTARubyFontStyleBoss,
				kTARubyRelativeSizeBoss,

				kTARubyPointSizeBoss,
				kTARubyXScaleBoss,
				kTARubyYScaleBoss,
				kTARubyOTProBoss,
				kTARubyOverhangFlagBoss,
				kTARubyOverhangBoss,
				kTARubyAdjustParentBoss,
				kTARubyAutoScalingBoss,
				kTARubyAutoScaleMinBoss,
				kTARubyEdgeSpaceBoss,

				kTARubyColorBoss,
				kTARubyTintBoss,
				kTARubyOverprintBoss,
				kTARubyStrokeColorBoss,
				kTARubyStrokeTintBoss,
				kTARubyStrokeOverprintBoss,
				kTARubyOutlineBoss,
				kTARubyAutoTCYNumDigitsBoss,
				kTARubyAutoTCYIncludeRomanBoss,
				kTARubyAutoTCYAutoScaleBoss,
			};

			for (ClassID textAttributeRubyBossClassID : classID)
			{
				// virtual ICommand* ITextAttrUtils::BuildClearTextAttrCmd(ITextModel* mod, const RangeData& range, const ClassID& attrClass, const ClassID& which)
				// ストーリーの指定された範囲にわたって指定されたテキスト属性をクリアするためのクリアテキスト属性コマンド設定を作成して返します。
				iCommand_clearTextAttrCmd.reset(Utils<ITextAttrUtils>()->BuildClearTextAttrCmd(iTextModel, rangeData_ScriptObject, textAttributeRubyBossClassID, kCharAttrStrandBoss));

				CmdUtils::ProcessCommand(iCommand_clearTextAttrCmd);
			}
		}

		// kSuccessが返らないとスクリプトエラーが出るので注意
		return kSuccess;
	} while (false);

	return kFailure;
}

#endif // __ExtendRubyAttributeScriptElement_h__
~~~