프로그램상에 폰트를 포함할 때 다음과 같은 문장을 사용할 것이다.

[Embed(source='C:/WINDOWS/Fonts/ARIAL.TTF', fontName = "MyFont", mimeType = "application/x-font-truetype")]
private var font:Class;

그런데 Flash Builder 4에서 작업할 때부터 위처럼 Embed한 폰트가 적용이 되지 않았다. 사용한 True Type 폰트는 언제나 테스트할때 문제없이 동작한 것이기 때문에 왜 안되는지 의문이 들었다. Flash Builder 4의 컴파일러인 mxmlc가 버그가 있거나 다른 설정이 추가되었나 싶었지만 그렇지 않았다. 그래서 열심히 구글링해봤더니 폰트를 Embed 처리할때 embedAsCFF 속성이 추가되었다는 것을 발견하고 아래처럼 테스트 코드를 작성해봤다.

package {
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.text.TextFieldAutoSize;
	import flash.text.AntiAliasType;
	[SWF(width = "500", height = "240", backgroundColor = "#FFFFFF", framerate = "24")]
	public class FontTest extends Sprite {
		[Embed(source='C:/WINDOWS/Fonts/ARIAL.TTF', embedAsCFF="false", fontName = "MyFont", mimeType = "application/x-font-truetype")]
		private var font:Class;
		public function FontTest() {
			var format:TextFormat = new TextFormat();
			format.font = "MyFont";
			format.size = 30;
			format.color = 0x000000;
			var label:TextField = new TextField();
			label.embedFonts = true;
			label.autoSize = TextFieldAutoSize.LEFT;
			label.antiAliasType = AntiAliasType.ADVANCED;
			label.defaultTextFormat = format;
			label.text = "blog.jidolstar.com";
			addChild(label);
			label.rotation = 5;
		}
	}
}


여기서 embedAsCFF 속성은 도데체 뭔가? 이 전에 CFF(Compact Font Format)가 뭔지 알아야만 했다.

폰트형태는 여러가지 종류가 있다. OTF(Open Type Font)와 TTF(True Type Font)등이 있다. OpenType은 MS와 Adobe가 TrueType의 후속작으로 Cross Platform을 지원하도록 만들 파일 포멧이라고 한다. 이 OpenType에는 Glyph Outline형식에 대한 정의는 없고 TrueType, CFF, Adobe Type 1(PostScript)를 사용한다고 한다. 이 말은 OTF중에는 TrueType으로 글자모양을 정의한 폰트도 있고 CFF 또는 PostScript Font로 정의한 폰트도 있음을 의미한다.
 
OpenType과 TrueType에 대한 더욱 자세한 내용은 아래 링크를 참고하자.
http://en.wikipedia.org/wiki/OpenType
http://en.wikipedia.org/wiki/TrueType

CFF는 OpenType의 하나로서 다음 링크에 더 세세하게 설명하고 있다.
http://en.wikipedia.org/wiki/Compact_Font_Format#Compact_Font_Format

CFF에 대한 스펙은 다음 파일을 참고한다.


Flash Player 10과 Adobe AIR 1.5이상부터 TTF뿐 아니라 CFF도 지원하게 되었다. 이는 PDF와 직접적인 관계가 있는 것 같다.  그래서 폰트를 포함할때 TTF인지 CFF인지 명시할 필요에 의해 생긴 것이 embededAsCFF인듯하다. 만약 TTF를 사용한다면 위 예제처럼 embededAsCFF를 false로 지정해야한다.

필자도 Font Type에 대한 지식은 거의 없어서 난감했다. CFF를 포함하면 글꼴 렌더링시에 텍스트를 보다 쉽게 읽을 수 있으며 작은 크기에서 글꼴의 표시 품질이 향상된다고 Adobe ActionScript 3.0 가이드 문서에는 나와 있을뿐 정확히 TTF와 CFF에 대한 명확한 해석을 찾지는 못했다.

Adobe ActionScript 3.0 가이드 : 글꼴을 이용한 작업


갑자기 대두된 Font 포함문제 때문에 매우 당혹스러웠지만 그나마 해결방법이라도 찾아 다행이다. 이에 대해 정확히 기술된 한글문서를 아직 찾지 못했다. 더욱 다양한 글들이 많이 올라왔으면 한다.

이 실험은 Flash Builder 4 Beta 2에서 테스트 했으며 아래 링크로부터 Flash Builder 4를 다운받을 수 있다.
http://www.adoberia.co.kr/pds/down.html?src=text&kw=000026



글쓴이 : 지돌스타(http://blog.jidolstar.com/614)

폰트(Font)를 동적으로 로딩해서 시스템에 등록한다는 의미는 Embed 폰트를 적용하되 애플리케이션에 포함된 폰트가 아닌 폰트를 담은 SWF를 원하는 시점에 읽어와 애플리케이션에 사용하겠다는 의미이다.

 

OS에서 기본적으로 제공하는 폰트는 System Font라고 불리고 애플리케이션에 포함되어 컴파일된 폰트는 Embedded Font라고 불리운다. System Font는 사용하기 편하지만 사용자의 사용환경에 따라 폰트가 설치되어 있지 않으면 심하게는 글씨가 안보이는 일이 생길 수 있다. 대신 Embedded Font는 애플리케이션에 포함되기 때문에 어떤 사용자가 애플리케이션을 사용하든지 항상 폰트를 사용한 글자를 볼 수 있다. 예를 들어 한글에 관련된 폰트를 애플리케이션에 포함시켜버리면 시스템에 한글폰트가 전혀없는 아랍국가에 있는 사용자도 애플리케이션 실행시 문제없이 한글을 볼 수 있다. 그리고 Embedded Font는 Anti-aliasing, 회전등 다양한 효과를 줄 수 있는 반면 System Font는 이런 효과에 제약이 있다. Embeded Font는 애플리케이션에 포함하는 것이므로 애플리케이션 자체가 커진다. 무거워진 애플리케이션은 결국 사용자들의 시간과 인내를 한계점에 도달시키게 할지도 모른다. 그러므로 Embeded Font를 꼭 사용해야하는 경우, 직접 애플리케이션 내부에 포함하지 않고 따로 폰트만을 담은 SWF파일을 만들어 애플리케이션이 실행되는 중간에 필요할 때 폰트를 로드할 수 있도록 설계를 하게 된다. 이러한 작업은 생각보다 단순하다. 가령, Adobe Flex의 경우 CSS의 @font-face를 이용해 폰트를 등록하고 CSS를 SWF로 만들어 StyleManager를 이용해 사용할 수 있으며 ActionScript 3.0 기반에서도 Class를 만들어 폰트를 담은 SWF를 만들어 Loader로 읽어들여 해당 폰트를Font.registerFont()로 등록할 수 있다. 본인은 이미 폰트에 관련된 글을 수도 없이 적었다. 아래 링크에 있는 내용만 가지고도 폰트를 적용하는데 충분할 것이다.

 

이렇게 많이 폰트에 관련된 글을 적고도 또 언급하는 이유는 ActionScript 3.0 프로젝트에서 Font 관리자 제작 필요성을 느꼈기 때문이다. Font 클래스가 있는데 무슨 또 관리자냐고 생각할 수 있다. 하지만 Font는 이미 등록되어 있는 폰트를 관리하는 것이다.(System Font든 Embeded Font든) 본인이 하고자 하는 것은 동적으로 폰트를 로딩해서 시스템에 적용하는데 필요한 클래스를 작성하는 것이다. 즉, 폰트를 동적으로 로드하고 등록해주는 본인이 제작한 FontManager 클래스를 소개하는 것이 이 글을 쓰는 목적이다.

 

1. Flex에서 Font를 어떻게 동적으로 등록하여 사용하나?

(Flash 개발자이지만 Flex는 생소하다면 그냥 가볍게 읽자. 자세하게 볼 필요는 없다.) 결과를 먼저 이야기 하자면 flash.text.Font 클래스를 사용하도록 컴파일 된다. 아래 코드를 보자.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init()">

	<mx:Style>
		/* CSS file */
		@font-face
		{
			src:url("../assets/DinmedRegular.ttf");
			fontFamily: "DinmedRegular";
			advancedAntiAliasing: true;
		}

		@font-face
		{
			src:url("../assets/MetaPluBolRom.TTF");
			fontFamily: "MetaPluBolRom";
			advancedAntiAliasing: true;
		}

		Application
		{
			color:#ffffff;
			fontFamily : "DinmedRegular";
		}

		.myStyle
		{
			color:#ffcfff;
			fontFamily : "MetaPluBolRom";
		}
	</mx:Style>

	<mx:Label text="I'm Jidolstar"/>
	<mx:Label text="http://jidolstar.com/blog" styleName="myStyle"/>
</mx:Application>

위 코드는 어렵지 않게 해석할 수 있을 것이다. 잘 알고 있겠지만 Flex의 MXML과 CSS는 컴파일시 ActionScript 3.0으로 변환되어 진다. 위 코드에서 CSS부분만 어떻게 변경되는가 보면 다음과 같다. (참고로 컴파일 옵션으로 -keep-generated-actionscript=true 을 설정하면 아래 코드 처럼 볼 수 있겠다.)

mx_internal static var _FontTest_StylesInit_done:Boolean = false;

mx_internal function _FontTest_StylesInit():void
{
	//	only add our style defs to the StyleManager once
	if (mx_internal::_FontTest_StylesInit_done)
		return;
	else
		mx_internal::_FontTest_StylesInit_done = true;

	var style:CSSStyleDeclaration;
	var effects:Array;

	// myStyle
	style = StyleManager.getStyleDeclaration(".myStyle");
	if (!style)
	{
		style = new CSSStyleDeclaration();
		StyleManager.setStyleDeclaration(".myStyle", style, false);
	}
	if (style.factory == null)
	{
		style.factory = function():void
		{
			this.color = 0xffcfff;
			this.fontFamily = "MetaPluBolRom";
		};
	}
	// Application
	style = StyleManager.getStyleDeclaration("Application");
	if (!style)
	{
		style = new CSSStyleDeclaration();
		StyleManager.setStyleDeclaration("Application", style, false);
	}
	if (style.factory == null)
	{
		style.factory = function():void
		{
			this.color = 0xffffff;
			this.fontFamily = "DinmedRegular";
		};
	}

	StyleManager.mx_internal::initProtoChainRoots();
}

// embed carrier vars
[Embed(advancedAntiAliasing='true', mimeType='application/x-font', _pathsep='true', source='../assets/DinmedRegular.ttf', _file='D:/FontTest/src/FontTest.mxml', fontName='DinmedRegular', _line='8')]
 private var _embed__font_DinmedRegular_medium_normal_444147531:Class;

[Embed(advancedAntiAliasing='true', mimeType='application/x-font', _pathsep='true', source='../assets/MetaPluBolRom.TTF', _file='D:/FontTest/src/FontTest.mxml', fontName='MetaPluBolRom', _line='15')]
 private var _embed__font_MetaPluBolRom_medium_normal_1521687713:Class;

 

매우 신기하게 Flex 컴파일러(mxmlc)는 저렇게 바꿔준다. CSS를 적용할 때는 StyleManager 클래스를 사용하고 Font와 같은 외부 자원을 사용하는 경우에는 그냥 Embed 시켜버린다. 폰트는 @font-face가 [Embed(…)]형태로 바뀐다는 것이다. 위의 경우 애플리케이션에 CSS가 포함되는 경우이고 반대로 동적으로 CSS를 만든 경우는 어떨까? CSS를 mxmlc로 컴파일해서 사용할 수 있다는 것은 알고 있을 것이다. 다음 코드는 동적으로 CSS를 로드하는 코드이다.

 

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init()">
	<mx:Script>
		<![CDATA[
			import mx.events.StyleEvent;
			import com.starpl.net.FontEvent;
			import com.starpl.net.IFontInfo;
			import com.starpl.net.FontManager;

			private var fontInfo:IFontInfo;
			private var textField:TextField;
			private var styleEventDispatcher:IEventDispatcher;

			private function init():void
			{
				styleEventDispatcher = StyleManager.loadStyleDeclarations( "MyStyle.swf" );
				styleEventDispatcher.addEventListener( StyleEvent.COMPLETE, onComplete );
				styleEventDispatcher.addEventListener( StyleEvent.ERROR, onError );
			}

			private function onComplete( event:StyleEvent ):void
			{
			}

			private function onError( event:StyleEvent ):void
			{

			}

		]]>
	</mx:Script>
	<mx:Label text="I'm Jidolstar"/>
	<mx:Label text="http://jidolstar.com/blog" styleName="myStyle"/>
</mx:Application>

 

그럼, 위 코드에서 StyleManager.loadStyleDeclarations()에서 로드하는 MyStyle.swf는 MyStyle.css로 만들어졌다는 이야기인데 이 코드는 어떻게 변해있을까? 다음 코드는 mxmlc로 MyStyle.css를 컴파일할때 만들어지는 ActionScript 3.0 코드이다.

 

package
{
import flash.system.Security;
import flash.text.Font;
import mx.core.mx_internal;
import mx.managers.SystemManager;
import mx.modules.ModuleBase;
import mx.styles.CSSStyleDeclaration;
import mx.styles.IStyleModule;
import mx.styles.StyleManager;

[ExcludeClass]
public class MyStyle extends ModuleBase implements IStyleModule
{
    /**
     * @private
     */
    private var selectors:Array = [];
    /**
     * @private
     */
    private var overrideMap:Object = {};
    /**
     * @private
     */
    private var effectMap:Object = {};
    /**
     * @private
     */
    private var unloadGlobal:Boolean;

    /**
     * @private
     */
    private static var domainsAllowed:Boolean = allowDomains();

    /**
     * @private
     */
    private static function allowDomains():Boolean
    {
		// allowDomain not allowed in AIR
		if (Security.sandboxType != "application")
			Security.allowDomain("*");
        return true;
    }

    [Embed(advancedAntiAliasing='true', mimeType='application/x-font', _pathsep='true', source='../assets/DinmedRegular.ttf', _file='D:/FontTest/src/MyStyle.css', fontName='DinmedRegular', _line='4')]
    private static var _embed__font_DinmedRegular_medium_normal_731457564:Class;
    [Embed(advancedAntiAliasing='true', mimeType='application/x-font', _pathsep='true', source='../assets/MetaPluBolRom.TTF', _file='D:/FontTest/src/MyStyle.css', fontName='MetaPluBolRom', _line='11')]
    private static var _embed__font_MetaPluBolRom_medium_normal_1234377680:Class;

    public function MyStyle()
    {
        var style:CSSStyleDeclaration;
        var keys:Array;
        var selector:String;
        var effects:Array;
        var addedEffects:Array;

        selector = ".myStyle";

        style = StyleManager.getStyleDeclaration(selector);

        if (!style)
        {
            style = new CSSStyleDeclaration();
            StyleManager.setStyleDeclaration(selector, style, false);
            selectors.push(selector);
        }

        keys = overrideMap[selector];

        if (keys == null)
        {
            keys = [];
            overrideMap[selector] = keys;
        }

        style.mx_internal::setStyle('color', 0xffcfff);
        keys.push("color");
        style.mx_internal::setStyle('fontFamily', "MetaPluBolRom");
        keys.push("fontFamily");

        selector = "Application";

        style = StyleManager.getStyleDeclaration(selector);

        if (!style)
        {
            style = new CSSStyleDeclaration();
            StyleManager.setStyleDeclaration(selector, style, false);
            selectors.push(selector);
        }

        keys = overrideMap[selector];

        if (keys == null)
        {
            keys = [];
            overrideMap[selector] = keys;
        }

        style.mx_internal::setStyle('color', 0xffffff);
        keys.push("color");
        style.mx_internal::setStyle('fontFamily', "DinmedRegular");
        keys.push("fontFamily");

        Font.registerFont(_embed__font_DinmedRegular_medium_normal_731457564);
        Font.registerFont(_embed__font_MetaPluBolRom_medium_normal_1234377680);
    }

    public function unload():void
    {
        unloadOverrides();
        unloadStyleDeclarations();

        if (unloadGlobal)
        {
            StyleManager.mx_internal::stylesRoot = null;
            StyleManager.mx_internal::initProtoChainRoots();
        }
    }

    /**
     * @private
     */
    private function unloadOverrides():void
    {
        for (var selector:String in overrideMap)
        {
            var style:CSSStyleDeclaration = StyleManager.getStyleDeclaration(selector);

            if (style != null)
            {
                var keys:Array = overrideMap[selector];
                var numKeys:int;
                var i:uint;

                if (keys != null)
                {
                    numKeys = keys.length;

                    for (i = 0; i < numKeys; i++)
                    {
                        style.mx_internal::clearOverride(keys[i]);
                    }
                }

                keys = effectMap[selector];

                if (keys != null)
                {
                    numKeys = keys.length;
                    var index:uint;
                    var effects:Array = style.mx_internal::effects;

                    for (i = 0; i < numKeys; i++)
                    {
                        index = effects.indexOf(numKeys[i]);
                        if (index >= 0)
                        {
                            effects.splice(index, 1);
                        }
                    }
                }
            }
        }

        overrideMap = null;
        effectMap = null;
    }

    /**
     * @private
     */
    private function unloadStyleDeclarations():void
    {
        var numSelectors:int = selectors.length;

        for (var i:int = 0; i < numSelectors; i++)
        {
            var selector:String = selectors[i];
            StyleManager.clearStyleDeclaration(selector, false);
        }

        selectors = null;
    }
}

}

 

위 코드는 CSS코드가 ActionScript 3.0으로 만들어진 것을 보여준다. 재미있게도 ModuleBase 클래스를 확장했다. 또한 IStyleModule을 구현했다. StyleManager.loadStyleDeclaration()을 호출할때 ModuleManager를 이용해 이 MyStyle.swf를 로드한다는 것을 의미한다. 실제로 StyleManager를 살펴보면 ModuleManager 활용해서 CSS를 동적으로 로드한다. 또한 CSS로 SWF로 만들면 내부적으로 StyleManager를 이용해 Style을 적용하고 폰트도 Embed를 통해서 포함하고 있다. 코드를 보면 Embed된 폰트를 Font.registerFont()함수를 이용해서 등록하고 있는 것 또한 확인할 수 있다. 살펴본 결과만 보면 Flex가 ActionScript 3.0 기반이고 Flex는 개발의 효율성을 증대시키기 위한 일종의 프레임워크이다라는 것을 쉽게 이해시켜주는 부분이 아닐가 싶다.

 

 

2. FontLoader 만들기 – 잘못 제작된 클래스

 

앞서 Flex SDK를 살펴본 결과 폰트를 동적으로 로드해서 등록하는 것은 생각보다 어려울 것 같지 않다. 결국 Font.registerFont()를 이용하면 되는 것이다. 그런데 본인은 Flex SDK를 이용하지 않고 순수 ActionScript 3.0 환경에서도 Font를 동적으로 로드해서 사용할 수 있도록 하는 환경을 만들고 싶다. 이런 환경을 구축하기 위한 조건은 다음과 같다.

 

  • 조건 1 : ActionScript 3.0 환경에서도 정상적으로 동작하도록 한다.
  • 조건 2 : 이미 로드된 폰트는 다시 로드하지 않는다.

 

조건 2는 당연하다. 이미 로드되어 Font.registerFont()를 이용해 등록된 폰트를 또 로드할 필요가 없지 않는가? 이 조건에 따르도록 FontLoader 클래스를 작성해보았다.

 

package com.jidolstar.fonts
{
	import flash.display.Loader;
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.URLRequest;
	import flash.system.ApplicationDomain;
	import flash.system.LoaderContext;
	import flash.system.Security;
	import flash.system.SecurityDomain;
	import flash.text.Font;
	import flash.utils.describeType;    

	[Event(name="loaded", type="com.starpl.net.FontLoaderEvent")]
	[Event(name="error", type="com.starpl.net.FontLoaderEvent")]

	/**
	 * 폰트를 담은 SWF를 동적으로 로딩하고 로드한 SWF로 부터 Embed 폰트의 Class를 시스템에 등록하는 역할을 한다.
	 *
	 * <p>폰트를 담은 SWF를 만드는 방법</p>
	 * <ul>
	 * <li> 아래 예제처럼 fontName(JidolstarFont)은 클래스이름, AS파일 이름과 같아야한다.
	 * <pre>
	 * 		package
	 * 		{
	 * 			import flash.display.Sprite;
	 * 			public class JidolstarFont extends Sprite
	 * 			{
	 * 				[Embed(	mimeType='application/x-font',
	 * 						source='../../assets/MetaPluBolRom.TTF',
	 * 						fontName='JidolstarFont',
	 * 						unicodeRange='U+0041,U+0044,U+0059,U+0061,U+0064,U+0079,U+0030-U+0039,U+002B,U+002D'
	 * 				)]
	 * 				static public var FontClass:Class;
	 * 			}
	 * 		}
	 * </pre>
	 * <li> FontClass 이름은 바뀌면 안된다.
	 * <li> mxmlc등을 이용해 단독으로 컴파일해서 swf파일을 만든다.
	 * </ul>
	 *
	 * <p>클래스를 사용하는 방법 </p>
	 * <pre>
	 * 	var testFontName:String = "HYNamuB";
	 *	var textField:TextField = new TextField;
	 *	textField.text = "안녕하세요";
	 *	addChild( textField );
	 *
	 *	textField.x = 50;
	 *	textField.y = 50;
	 *
	 *	fontLoader = new FontLoader( testFontName+".swf", testFontName );
	 *	fontLoader.addEventListener( FontLoaderEvent.LOADED, onFontLoaded );
	 *	fontLoader.addEventListener( FontLoaderEvent.ERROR, onFontLoadError );
	 *	fontLoader.load();
	 *
 	 *	private function onFontLoaded( event:FontLoaderEvent ):void
	 *	{
	 *		//Embed 처리된 폰트 리스팅
	 *		var embededfonts:Array = Font.enumerateFonts( false );
	 *		for each( var embededfont:Font in embededfonts )
	 *		{
	 *			trace( 	"fontName="+embededfont.fontName+
	 *					",fontType="+embededfont.fontType+
	 *					",fontStyle="+embededfont.fontStyle+
	 *					",hasGlyphs = "+embededfont.hasGlyphs( "안녕하세요" ) );
	 *		}
	 *
	 *		var textFormat:TextFormat = new TextFormat(testFontName, 14)
	 *		textField.embedFonts = true;
	 *		textField.setTextFormat( textFormat );
	 *	}
	 *
	 *	private function onFontLoadError( event:FontLoaderEvent ):void
	 *	{
 	 *		trace( event.fontName, event.fontURL, event.message );
	 *	}
	 * </pre>
	 *
	 * @author Yongho Ji(jidolstar[at]gmail.com)
	 * @since 2009.03.06
	 */
	public class FontLoader extends EventDispatcher
	{
		private var loader:Loader;
		private var loaderContext:LoaderContext;

		private var _fontURL:String;
		private var _fontName:String;

		/**
		 * 생성자
		 * @param fontURL 폰트를 로드할 경로
		 * @param fontName 폰트이름
		 */
		public function FontLoader( fontURL:String, fontName:String )
		{
			super();
			this._fontURL = fontURL;
			this._fontName = fontName;
		}

		/**
		 * 폰트를 로드할 URL
		 */
		public function get fontURL():String
		{
			return _fontURL;
		}

		/**
		 * 폰트 이름
		 */
		public function get fontName():String
		{
			return _fontName;
		}

		/**
		 * 해당폰트가 등록되어 있는지 검사후에 폰트 로드 실시
		 * 등록되어 있는 경우 loaded 이벤트 송출됨.
		 * 등록되어 있지 않은 경우 로드요청.
		 */
		public function load():void
		{
			//해당 폰트가 이미 등록되어 있는지 조사
            var currentFonts:Array = Font.enumerateFonts( false );
            var hasFont:Boolean = false;
            for each ( var f:Font in currentFonts )
            {
                if( fontName == f.fontName )
                {
                    hasFont = true;
                    break;
                }
            }		

            //폰트가 등록되어 있다면 Loaded이벤트를 날려준다!
            if( hasFont )
            {
            	this.dispatchEvent(new FontLoaderEvent(FontLoaderEvent.LOADED, _fontName, _fontURL, null ));
            }
            //폰트가 등록되어  있지 않다면 폰트 로드를 시도한다.
            else
            {
				if( !loader )
				{
					loader = new Loader();
				}
				if( !loaderContext )
				{
					loaderContext = new LoaderContext();
					loaderContext.checkPolicyFile = true;
					loaderContext.applicationDomain = new ApplicationDomain( ApplicationDomain.currentDomain );
					if ( Security.sandboxType == Security.REMOTE)
					{
					    loaderContext.securityDomain = SecurityDomain.currentDomain;
					}
					else
					{
				        loaderContext.securityDomain = null;
					}
				}
				loader.contentLoaderInfo.addEventListener( Event.COMPLETE, onLoaded, false, 0, true);
            	loader.contentLoaderInfo.addEventListener( IOErrorEvent.IO_ERROR, onError, false, 0, true);
            	loader.contentLoaderInfo.addEventListener( SecurityErrorEvent.SECURITY_ERROR, onError, false, 0, true);
            	loader.load( new URLRequest( _fontURL ), loaderContext );
            }
		}

		/**
		 * 로드 완료시 해당 폰트가 있는지 확인후 등록한다.
		 */
		private function onLoaded( event:Event ):void
		{
			if( loader.contentLoaderInfo.applicationDomain.hasDefinition( _fontName ) )
			{
				//폰트 등록
				var fontClass:Class = loader.contentLoaderInfo.applicationDomain.getDefinition( _fontName ) as Class;
				Font.registerFont( fontClass.FontClass );
				this.dispatchEvent( new FontLoaderEvent( FontLoaderEvent.LOADED, _fontName, _fontURL ) );
			}
			else
			{
				//해당폰트를 찾을 수 없으므로 실패 이벤트 송출
				this.dispatchEvent( new FontLoaderEvent( FontLoaderEvent.ERROR, _fontName, _fontURL, "Font Load fail" ) );
			}
			clearLoader();
		}

		/**
		 * 폰트 로드 실패시 처리
		 */
		private function onError( event:ErrorEvent ):void
		{
			this.dispatchEvent( new FontLoaderEvent( FontLoaderEvent.ERROR, _fontName, _fontURL, event.text ) );
			clearLoader();
		}

		/**
		 * 로더 삭제
		 */
	    private function clearLoader():void
	    {
	        if (loader)
	        {
	            if (loader.contentLoaderInfo)
	            {
	                loader.contentLoaderInfo.removeEventListener( Event.COMPLETE, onLoaded, false );
	                loader.contentLoaderInfo.removeEventListener( IOErrorEvent.IO_ERROR, onError, false );
	                loader.contentLoaderInfo.removeEventListener( SecurityErrorEvent.SECURITY_ERROR, onError, false );
	            }

	            try
	            {
	            	loader.close();
	                loader.unload();
	            }
	            catch(error:Error)
	            {
	            }
	            loader = null;
	            loaderContext = null;
	        }
	    }		

	}
}

 

FontLoader 클래스를 사용한 예제는 다음과 같다.

 

package
{
	import com.jidolstar.fonts.FontLoader;
	import com.jidolstar.fonts.FontLoaderEvent;

	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.text.Font;
	import flash.text.TextField;
	import flash.text.TextFormat;

	/**
	 * FontLoader 테스터
	 * @author Yongho, Ji
	 * @since 2009.03.17
	 */
	public class FontTest extends Sprite
	{
		private var fontLoader:FontLoader;
		private var testFontName:String = "HYNamuB";

		private var textField:TextField;

		public function FontTest()
		{
			super();

			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;

			textField = new TextField;
			textField.text = "안녕하세요";
			addChild( textField );

			textField.x = 50;
			textField.y = 50;

			fontLoader = new FontLoader( testFontName+".swf", testFontName );
			fontLoader.addEventListener( FontLoaderEvent.LOADED, onFontLoaded );
			fontLoader.addEventListener( FontLoaderEvent.ERROR, onFontLoadError );
			fontLoader.load();
		}

		private function onFontLoaded( event:FontLoaderEvent ):void
		{
			//Embed 처리된 폰트 리스팅
			var embededfonts:Array = Font.enumerateFonts( false );
			for each( var embededfont:Font in embededfonts )
			{
				trace( 	"fontName="+embededfont.fontName+
						",fontType="+embededfont.fontType+
						",fontStyle="+embededfont.fontStyle+
						",hasGlyphs = "+embededfont.hasGlyphs( "안녕하세요" ) );
			}

			var textFormat:TextFormat = new TextFormat(testFontName, 14)
			textField.embedFonts = true;
			textField.setTextFormat( textFormat );
		}

		private function onFontLoadError( event:FontLoaderEvent ):void
		{
			trace( event.fontName, event.fontURL, event.message );
		}
	}
}

 

따로 분석은 하지 않겠다. 위에서 사용한 소스는 아래 링크를 통해 다운로드 받아볼 수 있다. Flex Builder 3에 import 해서 테스트 해보길 바란다.

 

위 소스는 FontLoader 클래스를 이용해 Embed폰트를 담은 SWF를 로드해 Font.registerFont()를 이용해 등록하고 나름대로 똑같은 Font는 등록이 되지 않도록 노력했다. 실제로 매우 잘 돌아가는 소스이다.
하지만 이 코드에는 맹점이 존재한다. load() 함수 호출시에 같은 폰트(같은 url)를 2번 이상 로드하는 경우에 그대로 2번 로드될 수 있다. load() 함수 앞에 이전에 등록된 폰트가 없나 검사를 하고 있지만 비동기적으로 폰트가 로드되는 것이므로 “조건 2. 이미 로드된 폰트는 다시 로드하지 않는다.”를 지키지 못한 것이다.(지킨줄 알았는데… ㅎㅎ) 폰트는 왠만하면 거의 100kb 이상이다. 한글의 경우 300kb가 되는 경우도 많다. 보통 Flex 애플리케이션도 500kb를 넘지 않는 마당에 이렇게 큰 용량의 폰트가 2번이상 로드하게 된다면… 말 안해도 알거다.
 
“그럼 개발자가 같은 폰트를 로드하지 않도록 1개만 로드하고 다음에는 로드를 시도하지 않도록 잘~ 만들면 되지 않느냐?” 라고 반문할 수 있다. 하지만 이런 문제를 알면서도 잊고 그냥 넘어가 버리는 경우가 발생할 수 있다. 또한 다음과 같은 경우도 있다. 가령, 이 FontLoader를 사용하는 위젯 SWF가 3~4개가 있다. 이들 위젯을 관리하는 애플리케이션이 각각의 위젯 SWF를 거의 동시에 로드한다. 로드된 위젯들은 FontLoader를 이용해 해당 폰트를 로드한다. 물론 FontLoader는 애플리케이션의 ApplicationDomain에 위치하고 있고 각각의 위젯은 이 ApplicationDomain내에 정의된 FontLoader를 이용하는 것이다. 이 경우 위젯들에서 사용하는 폰트가 중복되지 않으리라는 보장을 어떻게 할 것인가? 또한 위젯들에게 “내가 이 폰트 로드했으니깐 너는 로드하지마!” 라고 어떻게 전달할 것인가? 결국 잘못 만들어진 클래스 하나로 애플리케이션의 퍼포먼스는 저하될 수 밖에 없는 것이다. 이는 FontLoader 클래스를 만든 개발자 잘못인 것이다. 조건에 맞게 제대로 만들지 못했다는 것을 의미한다. 이러한 FontLoader를 쓰라고? 너무 무책임해지는 것이다.
 

3. FontManager 만들기 – 중복으로 폰트 로드하는 것을 완벽하게 방지

위에서 FontLoader의 문제점을 살펴보았다. 생각보다 간단히 해결될 것 같지는 않다. 결국 약간의 디자인(설계) 패턴을 응용해야한다. 본인은 이 문제를 해결하기 위해 Singleton 디자인 패턴Proxy 디자인 패턴을 활용하고자 한다.

 

Singleton 패턴은 주로 제한된 객체를 생성하기 위해 사용한다. 이름 자체만 보더라도 1개의 객체만을 허용하는 클래스를 만드는 것을 목적으로 한다.

 

Proxy 패턴은 말그대로 대리자 클래스를 만들어 사용하는 것을 의미한다. 기존 클래스를 그대로 사용하되 부가적인 기능을 더하는데 사용하는 설계 패턴으로 생각하면 되겠다.

 

참고로 이런 디자인 패턴은 GoF의 23가지 디자인 패턴에 포함하므로 필요할 때 학습해두면 클래스 설계시 매우 유용할 것이다. 2번의 조건(중복로드방지, AS3 프로젝트에서 실행가능)을 가능하게 하기 위해 FontManager를 만들었다. FontManager는 크게 3개 ActionScript 코드로 이뤄진다. IFontInfo.as, FontManager.as, FontEvent.as가 그것이다. 아래는 IFontInfo.as 이다. IFontInfo 인터페이스이며 사용자는 이 인터페이스를 이용해서 Font를 로드한다.

 

package com.jidolstar.fonts
{
	import flash.events.IEventDispatcher;

	/**
	 * Font 컨텐츠를 로드하는 중간 에러 발생시 송출된다.
	 * @eventType  com.jidolstar.fonts.FontEvent.ERROR
	 */
	[Event(name="error", type="com.jidolstar.fonts.FontEvent")]

	/**
	 * Font 컨텐츠를 로드하고 있는 중일때 송출
	 * @eventType  com.jidolstar.fonts.FontEvent.PROGRESS
	 */
	[Event(name="progress", type="com.jidolstar.fonts.FontEvent")]

	/**
	 * Font 컨텐츠를 로드를 완료하고 애플리케이션에 폰트 적용이 완료되었을때 송출
	 * @eventType  com.jidolstar.fonts.FontEvent.COMPLETE
	 */
	[Event(name="complete", type="com.jidolstar.fonts.FontEvent")]

	/**
	 * Font 컨텐츠가 로드 준비가 완료되었을때 송출
	 * @eventType  com.jidolstar.fonts.FontEvent.SETUP
	 */
	[Event(name="setup", type="com.jidolstar.fonts.FontEvent")]

	/**
	 * 폰트 로드를 하기 위한 인터페이스
	 */
	public interface IFontInfo extends IEventDispatcher
	{
		/**
		 * Font 컨텐츠 로드 실시하면 true
		 */
		function get start():Boolean;		

		/**
		 * 에러발생시 true
		 */
		function get error():Boolean;

		/**
		 * Font 컨텐츠 로드 완료되고 적용되었을때 true
		 */
		function get complete():Boolean;

		/**
		 * Font 컨텐츠 로드 준비가 완료되었을때 true
		 */
		function get setup():Boolean;

		/**
		 * Font 컨텐츠를 로드할 URL
		 */
		function get url():String;

		/**
		 * Font 컨텐츠 로드 시작
		 */
		function load():void;
	}
}

 

아래는 FontEvent.as 파일이다.

 

package com.jidolstar.fonts
{
	import flash.events.Event;
	import flash.events.ProgressEvent;

	/**
	 * 폰트 이벤트
	 * @author Yongho, Ji
	 * @since 2009.03.09
	 */
	public class FontEvent extends ProgressEvent
	{
		/**
		 * 로드 성공시
		 */
		public static const COMPLETE:String = "complete";
		/**
		 * 로드 설정시
		 */
		public static const SETUP:String = "setup";

		/**
		 * 로드 진행시
		 */
		public static const PROGRESS:String = "progress";

		/**
		 * 로드 실패시
		 */
		public static const ERROR:String = "error";

		/**
		 * 에러 메시지
		 */
		public var errorText:String;

		/**
		 * 폰트 정보
		 */
		public var fontInfo:IFontInfo;

		/**
		 * 폰트 로더 이벤트
		 * @param type 이벤트 Type
		 * @param bubbles
		 * @param cancelable
		 * @param bytesLoaded
		 * @param bytesTotal
		 * @param errorText
		 * @param fontInfo
		 */
		public function FontEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false,
								bytesLoaded:uint = 0, bytesTotal:uint = 0,
								errorText:String = null, fontInfo:IFontInfo = null )
		{
			super(type, bubbles, cancelable, bytesLoaded, bytesTotal)
			this.errorText = errorText;
			this.fontInfo = fontInfo;
		}

		/**
		 * 이벤트 클론
		 */
		public override function clone():Event
		{
			return new FontEvent( type, bubbles, cancelable, bytesLoaded, bytesTotal, errorText, fontInfo );
		}
	}
}

 

마지막으로 FontManager.as이다

 

package com.jidolstar.fonts
{
	/**
	 * Font를 동적으로 로드해서 적용하기 위한 매니저이다.
	 * Font 컨텐츠는 SWF로 존재하며 이 SWF을 로드하기 위한 URL이 있다면 사용자는 FontManager.getInstance().getFontInfo( url )로
	 * IFontInfo 인스턴스를 얻을 수 있으며 폰트 로드/적용 되는 상황을 FontEvent로 받을 수 있다.
	 *
	 * @author Yongho, Ji
	 * @since 2009.03.09
	 */
	public class FontManager
	{
		private static var _singleton:FontManager;

		private var fontInfoList:Object = {};

		/**
		 * 싱글턴을 위한 생성자
		 */
		public function FontManager( singletonForce:SingletonForce )
		{
		}

		/**
		 * 싱글턴 인스턴스 생성
		 */
		public static function getInstance():FontManager
		{
			if( !_singleton )
			{
				_singleton = new FontManager( new SingletonForce );
			}
			return _singleton;
		}

		/**
		 * URL에 대한 Font 정보를 얻오온다.
		 */
		public function getFontInfo( url:String ):IFontInfo
		{
			var info:FontInfo = fontInfoList[url] as FontInfo;
			if( !info )
			{
				info = new FontInfo( url );
				fontInfoList[url] = info;
			}
			return new FontInfoProxy( info );
		}
	}
}

import com.jidolstar.fonts.FontEvent;
import com.jidolstar.fonts.IFontInfo;

import flash.display.DisplayObject;
import flash.display.Loader;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.net.URLRequest;
import flash.system.ApplicationDomain;
import flash.system.LoaderContext;
import flash.system.Security;
import flash.system.SecurityDomain;
import flash.utils.getQualifiedClassName;

/**
 * @private
 * FontManager 싱글톤 패턴을 위한 클래스
 */
class SingletonForce {}

/**
 * @private
 * 폰트를 담은 컨텐트를 로드하고 관리하는 클래스
 */
class FontInfo extends EventDispatcher
{
	private var _url:String;

	private var loader:Loader;
	private var _error:Boolean = false;
	private var _start:Boolean = false;
	private var _complete:Boolean = false;
	private var _setup:Boolean = false;
	private var _bytesTotal:int = 0;
	private var reference:DisplayObject;
	private var applicationDomain:ApplicationDomain;

	public function FontInfo( url:String )
	{
		super();
		_url = url;
	}

	public function get start():Boolean
	{
		return _start;
	}

    public function get error():Boolean
    {
        return _error;
    }

    public function get complete():Boolean
    {
    	return _complete;
    }	

    public function get setup():Boolean
    {
    	return _setup;
    }	    

    public function get url():String
    {
        return _url;
    }

    public function get size():int
    {
    	return _bytesTotal;
    }	

	public function load():void
	{
		if( _start )
			return;

		_start = true; 

		var r:URLRequest = new URLRequest( _url );
		var c:LoaderContext = new LoaderContext;
		c.applicationDomain = new ApplicationDomain( ApplicationDomain.currentDomain );
		if( Security.sandboxType == Security.REMOTE )
		{
			c.securityDomain = SecurityDomain.currentDomain;
		}

		loader = new Loader;
        loader.contentLoaderInfo.addEventListener( Event.INIT, initHandler);
        loader.contentLoaderInfo.addEventListener( Event.COMPLETE, completeHandler);
        loader.contentLoaderInfo.addEventListener( ProgressEvent.PROGRESS, progressHandler);
        loader.contentLoaderInfo.addEventListener( IOErrorEvent.IO_ERROR, errorHandler);
        loader.contentLoaderInfo.addEventListener( SecurityErrorEvent.SECURITY_ERROR, errorHandler);

        loader.load(r, c);
	}

    /**
     *  @private
     */
    private function clearLoader():void
    {
        if (loader)
        {
            if (loader.contentLoaderInfo)
            {
                loader.contentLoaderInfo.removeEventListener(
                    Event.INIT, initHandler);
                loader.contentLoaderInfo.removeEventListener(
                    Event.COMPLETE, completeHandler);
                loader.contentLoaderInfo.removeEventListener(
                    ProgressEvent.PROGRESS, progressHandler);
                loader.contentLoaderInfo.removeEventListener(
                    IOErrorEvent.IO_ERROR, errorHandler);
                loader.contentLoaderInfo.removeEventListener(
                    SecurityErrorEvent.SECURITY_ERROR, errorHandler);
            }

            if (_complete)
            {
                try
                {
                    loader.close();
                }
                catch(error:Error)
                {
                }
            }

            try
            {
                loader.unload();
            }
            catch(error:Error)
            {
            }

            loader = null;
        }
    }	

    /**
     *  @private
     */
    public function initHandler(event:Event):void
    {
		reference = loader.content;

		if( !reference )
		{
            var fontEvent:FontEvent = new FontEvent(
                FontEvent.ERROR, event.bubbles, event.cancelable);
            fontEvent.bytesLoaded = 0;
            fontEvent.bytesTotal = 0;
            fontEvent.errorText = "SWF is not a loadable Font";
            dispatchEvent(fontEvent);
            return;
		}

		applicationDomain = loader.contentLoaderInfo.applicationDomain;

        _setup = true;

        dispatchEvent(new FontEvent(FontEvent.SETUP));
    }

    /**
     *  @private
     */
    public function progressHandler(event:ProgressEvent):void
    {
        var fontEvent:FontEvent = new FontEvent(
            FontEvent.PROGRESS, event.bubbles, event.cancelable);
        fontEvent.bytesLoaded = event.bytesLoaded;
        fontEvent.bytesTotal = event.bytesTotal;
        dispatchEvent(fontEvent);
    }

    /**
     *  @private
     */
    public function completeHandler(event:Event):void
    {
        _complete = true;
        var fontEvent:FontEvent;

		try
		{
	        var className:String = getQualifiedClassName( reference );
	        var mainClass:Class = applicationDomain.getDefinition( className ) as Class;
	        new mainClass();
	        fontEvent = new FontEvent( FontEvent.COMPLETE, event.bubbles, event.cancelable);
	        fontEvent.bytesLoaded = loader.contentLoaderInfo.bytesLoaded;
	        fontEvent.bytesTotal = loader.contentLoaderInfo.bytesTotal;
	        dispatchEvent(fontEvent);
	 	}
	 	catch( e:Error )
	 	{
	        fontEvent = new FontEvent( FontEvent.ERROR, event.bubbles, event.cancelable);
	        fontEvent.errorText = e.message;
	        dispatchEvent(fontEvent);
	 	}

        clearLoader();
    }

    /**
     *  @private
     */
    public function errorHandler(event:ErrorEvent):void
    {
        _error = true;
        var fontEvent:FontEvent = new FontEvent(
            FontEvent.ERROR, event.bubbles, event.cancelable);
        fontEvent.bytesLoaded = 0;
        fontEvent.bytesTotal = 0;
        fontEvent.errorText = event.text;
        dispatchEvent(fontEvent);
    }
}

/**
 * @private
 * FontInfo 클래스를 한번 wraping하고 IFontInfo를 구현한 클래스이다.
 *
 *
 * 사용자는 FontManager.getFontInfo( url ) 을 이용해 이 클래스의 인스턴스를 얻게된다.
 * 한개의 url에 대해 여러개의 FontInfoProxy 인스턴스가 생성될 수 있다.
 * 하지만 같은 url을 참조한 FontInfoProxy 인스턴스에 대해서는 단 한개의 FontInfo 인스턴스만 참조하도록 한다.
 * 즉, URL에 대해서는 한개의 FontInfo가 된다.
 *
 *
 * 이런 Proxy를 만든 이유는 폰트를 담은 컨텐츠를 url을 비교하여 중복 로드하는 것을 방지하기 위함이다.
 * 사용자는  FontInfoProxy 존재를 알 필요없이 IFontInfo로 접근 접근만을 허용한다.
 * 따로 Unload는 구현하지 않는다. 왜냐하면 Font가 등록되면 삭제되지는 않기 때문이다.
 *
 */
class FontInfoProxy extends EventDispatcher implements IFontInfo
{
	private var info:FontInfo;

	public function FontInfoProxy( info:FontInfo )
	{
		this.info = info;	

		info.addEventListener( FontEvent.SETUP, fontEventHandler, false, 0, true );
		info.addEventListener( FontEvent.PROGRESS, fontEventHandler, false, 0, true );
		info.addEventListener( FontEvent.COMPLETE, fontEventHandler, false, 0, true );
		info.addEventListener( FontEvent.ERROR, fontEventHandler, false, 0, true );
	}

    /**
     *  @private
     */
    public function get error():Boolean
    {
        return info.error;
    }

    /**
     * @private
     */
    public function get start():Boolean
    {
		return info.start;
    }    

    /**
     *  @private
     */
    public function get complete():Boolean
    {
        return info.complete;
    }

    /**
     *  @private
     */
    public function get setup():Boolean
    {
        return info.setup;
    }       

    /**
     *  @private
     */
    public function get url():String
    {
        return info.url;
    }     

	/**
	 * Font Load 실시
	 * 이미 로드가 진행진행중이거나 완료한 상태라면 따로 load를 실시하지 않도록 한다.
	 * 즉, 실제 로드는 한번만 실시한다.
	 */
	public function load():void
	{
		if( info.error )
		{
			dispatchEvent( new FontEvent( FontEvent.ERROR ) );
		}
		else if( info.start )
		{
			if( info.setup )
			{
				dispatchEvent( new FontEvent( FontEvent.SETUP ) );
				if( info.complete )
				{
					var fontEvent:FontEvent;
					fontEvent = new FontEvent( FontEvent.PROGRESS );
					fontEvent.bytesTotal = info.size;
					fontEvent.bytesLoaded = info.size;
					dispatchEvent( fontEvent );

					fontEvent = new FontEvent( FontEvent.COMPLETE );
					fontEvent.bytesTotal = info.size;
					fontEvent.bytesLoaded = info.size;
					dispatchEvent( fontEvent );
				}
			}
		}
		else
		{
			info.load();
		}
	}

    /**
     *  @private
     */
    private function fontEventHandler(event:FontEvent):void
    {
        dispatchEvent(event);
    }
}

 

조금만 정성을 들인다면 어렵지 않게 해석할 수 있다. 앞서 설명했듯이 Proxy 패턴Singleton 패턴을 섞어서 제작했다. Flex SDK의 ModuleManager에서 Factory 부분이 빠진 구조로 볼 수 있다. 이렇게 만든 클래스 구조는 "조건 2. 이미 로드된 폰트는 다시 로드하지 않는다" 를 만족한다. 사용법은 매우 단순하다. FontManager.getInstance().getFontInfo( url ) 을 사용해서 IFontInfo 형태의 객체를 받아 로드 상태를 알기 위해 FontEvent에 정의된 이벤트 청취자를 등록하고 마지막으로 IFontInfo의 load()함수를 호출하면 해당 폰트가 로딩되고 애플리케이션에 적용된다. 폰트는 아래와 같은 방법으로 ActionScript 3.0으로 만든다.

 

package
{
	import flash.display.Sprite;
	import flash.text.Font;

	public class MyFont extends Sprite
	{
		[Embed(	mimeType='application/x-font', source='../assets/DinmedRegular.ttf', fontName='DinmedRegular', embedAsCFF=false)]
		static public var FontClass1:Class; 

		[Embed(	mimeType='application/x-font', source='../assets/MetaPluBolRom.TTF', fontName='MetaPluBolRom', embedAsCFF=false)]
		static public var FontClass2:Class;

		public function MyFont()
		{
			super();
			Font.registerFont( FontClass1 );
			Font.registerFont( FontClass2 );
		}
	}
}

 

폰트를 Embed하고 flash.text.Font의 registerFont() 함수를 이용해 Embed된 폰트를 등록해준다. 폰트는 위처럼 2개든 여러개든 상관없다. Embed한 폰트수만큼 등록하면 되겠다. 사용하기 위해 이 클래스를 mxmlc로 컴파일한다. 그런 다음 아래 예제처럼 FontManager를 이용해 로드해서 폰트를 애플리케이션에 적용할 수 있다.

 

package {
	import com.jidolstar.fonts.FontEvent;
	import com.jidolstar.fonts.FontManager;
	import com.jidolstar.fonts.IFontInfo;

	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.text.TextField;
	import flash.text.TextFormat;

	public class FontManagerTest extends Sprite
	{
		private var fontInfo1:IFontInfo;
		private var fontInfo2:IFontInfo;
		private var textField1:TextField;
		private var textField2:TextField;		

		public function FontManagerTest()
		{
			super();
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;

			fontInfo1 = FontManager.getInstance().getFontInfo( "MyFont.swf" );
			fontInfo1.addEventListener( FontEvent.COMPLETE, onComplete );
			fontInfo1.addEventListener( FontEvent.ERROR, onError );
			fontInfo1.load();

			//fontInfo1과 같은 경로의 SWF를 로드한다.
			//fontInfo1.load()를 한번 시도했기 때문에 fontInfo2.load()에서는 실제로드하지 않는다.
			//다만, 사용자는 이벤트를 통해 로드 되는 것처럼 사용하는 것 뿐이다. 즉, 인터페이스는 계속 유지시켜줌으로써 사용자의 오용을 방지시킨다.
			fontInfo2 = FontManager.getInstance().getFontInfo( "MyFont.swf" );
			fontInfo2.addEventListener( FontEvent.COMPLETE, onComplete );
			fontInfo2.addEventListener( FontEvent.ERROR, onError );
			fontInfo2.load();	

			textField1 = new TextField;
			textField1.embedFonts = true;
			textField1.defaultTextFormat = new TextFormat( "DinmedRegular" );
			textField1.text = "I'm jidolstar";
			textField1.x = 10;
			textField1.y = 10;
			textField1.rotation = 10;
			addChild( textField1 );	

			textField2 = new TextField;
			textField2.embedFonts = true;
			textField2.defaultTextFormat = new TextFormat( "MetaPluBolRom" );
			textField2.text = "http://jidolstar.com";
			textField2.x = 10;
			textField2.y = 30;
			textField2.rotation = 10;
			addChild( textField2 );
		}

		private function onComplete( event:FontEvent ):void
		{
			trace( (event.target as IFontInfo).url );
		}

		private function onError( event:FontEvent ):void
		{

		}
	}
}

실행결과 

 

 

이것으로 폰트를 동적으로 로딩하여 ActionScript 3.0 프로젝트에도 쉽게 적용할 수 있게 되었다. Flex에도 적용할 수 있다. 물론 CSS형태의 편의성은 제공하지 못하지만 중복으로 로드하지 않는 장점이 있기 때문에 사용할 가치는 있다.  

 

소스코드는 아래 링크에서 다운로드 받는다. Flex Builder 3 이상에서 Import 해서 테스트 해보면 되겠다.

 

 

정리하며...

ActionScript 3.0 기반 애플리케이션에 동적로딩을 통해 폰트를 시스템에 적용시켜주는 FontManager를 제작해보았다. 하나의 URL에 따라 중복으로 로딩되지 않도록 하고 Flex가 아닌 ActionScript 3.0 프로젝트 진행에 도움이 되도록 했다. Flex의 좋은 프레임워크를 놔두고 이런 클래스를 만드는 이유는 Flex는 대형 프로젝트의 애플리케이션에 적합하기 때문이다. 제작할 애플리케이션이 간단하고 용량이 중요하다면 Flex 사용을 하지 않는 편이 좋을 수 있다. 배보다 배꼽이 더 큰 애플리케이션을 만들지 말자는 것이다. 그래서 손수 FontManager와 같은 클래스를 제작할 필요가 느꼈다. 앞으로 이러한 문제로 FontManager 말고도 다양한 클래스를 제작할 생각이다. 이러다가 프레임워크 만드는거 아닌가? ^^;

참고 : Font Embed시 Flash player 10, AIR 1.5 이상부터 embedAsCFF 속성을 사용해야합니다. 다음 글을 참고하세요. 2009.11.16
http://blog.jidolstar.com/614

 

 

+ Recent posts