Article Category

분류 전체보기 (202)
사는 이야기 (15)
Programming (174)
Photo (5)
게임 (2)
프로젝트 (0)

Recent Comment

Recent Trackback

Calendar

«   2019/08   »
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

Archive

  • Total96,717
  • Today3
  • Yesterday4

HTML Transitional과 Strict 규격 비교

‘웹 표준’에 관한 흔한 오해 중의 하나는 HTML 대신에 XHTML을 사용하면 웹 표준을 더 잘 지킬 수 있다는 생각입니다. XHTML을 사용하면 문서의 ‘표현’‘구조’를 분리할 수 있고, 그래서 보다 ‘의미 있는’ 마크업이 가능하다는 주장도 있는데 이것 역시 사실이 아닙니다.

실제로 ‘의미 있는’ 마크업을 위해서는 XHTML과 HTML 중 어느 것을 사용했느냐가 아니라 어떤 문서 타입 정의(DTD)을 사용하느냐가 절대적으로 중요합니다. (X)HTML 문서는 크게 ‘Transitional(과도적인)’ 타입과 ‘Stict(엄격한)’ 타입으로 나눌 수 있는데 이 두 DTD가 어떻게 다른지 그 차이점을 간단히 알아보고, 웹 표준을 위해서 왜 엄격한 문서 타입이 권장되는지를 알아보도록 하겠습니다.

문서 타입 정의(DTD) 요약

HTML 4.01XHTML 1.0 규격은 각각 세 가지의 문서 타입 정의를 동일하게 갖고 있습니다.

DTD HTML 4.01 XHTML 1.0 비고
Strict HTML 4.01 Strict XHTML 1.0 Strict 엄격한 규격
Transitional HTML 4.01 Transitional XHTML 1.0 Transitional 과도적인 규격
Frameset HTML 4.01 Frameset XHTML 1.0 Frameset 프레임 사용 가능

XHTML 1.0은 HTML 4.01 규격을 XML에 맞춰 재해석한 규격이므로 약간의 문법 차이를 제외하면 사실상 차이가 없습니다. 따라서 앞으로는 XHTML과 HTML의 문서 타입 정의(DTD)를 따로 구분하지 않겠습니다. XHTML과 HTML의 차이점에 대해서는 앞서 포스팅한 XHTML과 HTML의 차이를 참고하시기 바랍니다.

Strict, Transitional DTD

W3C가 제안한 HTML 규격의 DTD 항목에는 다음과 같이 설명되어 있습니다.

“Strict 타입은 W3C가 스타일시트 사용을 장려하기 위해 단계적으로 사라질 ‘표현’(presentation)에 관한 태그와 속성을 배제한 문서 타입이다. 웹 문서 제작자는 가능하다면 Strict 타입을 사용해야 하지만(should), 불가피하게 표현을 담당하는 속성이 필요할 경우에는 Transitional 타입을 사용할 수도 있다(may).”

즉, Strict DTD가 W3C가 의도했던 문서 타입이고, Transitional DTD는 ‘과도적인’이라는 단어의 의미 처럼 기존에 만들어진 문서들과의 호환성을 위해서 만들어진 것임을 알 수 있습니다. 과거의 모든 문서들을 Strict DTD에 맞게 바꾸려면 엄청난 변화가 필요하므로 그 중간 단계로 Transitional DTD를 설정한 것이지요.

Frameset DTD

Frameset 타입은 Transitional DTD 기반 위에 프레임 지원을 위한 태그와 속성을 추가한 문서 타입입니다. 그러므로 문서의 구조화에 있어서는 Transitional DTD와 동일하게 취급됩니다.

Strict DTD에서 달라진 점

Strict DTD에서는 Transitional DTD에서 허용되던 많은 태그와 속성이 금지되었습니다. 또한, 일부 태그의 쓰임(Content Model)에서도 차이가 있는데 두 DTD의 이런 차이점을 간단히 정리한 문서가 있어서 일부를 소개합니다. 원문은 24 ways에 포스팅된 Roger Johansson의 Transitional vs. Strict Markup입니다.

사라진 태그(element)들
  • center
  • font
  • iframe
  • strike
  • u

글꼴 설정과 각종 요소의 배치, 인라인 프레임, 밑줄 표시를 담당하는 태그가 사라졌고, 선을 긋는다는 의미의 strike 태그는 문서에서 삭제된 부분을 나타내는 del 태그로 대체되었습니다.

사라진 속성(attribute)들
  • align : tabel 관련 태그에서만 허용됨(col, colgroup, tbody, td, tfoot, th, thead, tr).
  • language
  • background
  • bgcolor
  • border : table 태그에서만 허용됨.
  • height : img, object 태그에서만 허용됨.
  • hspace
  • name : HTML 4.01 Strict에서는 허용되지만, XHTML 1.0 Strict의 form, image 태그에는 허용되지 않음.
  • noshade
  • nowrap
  • target
  • text, link, vlink, alink
  • vspace
  • width : img, object, table, col, colgroup 태그에서만 허용됨.

보시는 바와 같이 Strict DTD에서는 HTML 문서의 표현을 담당하는 대부분의 속성이 사라지고, 대신 CSS를 사용해서 같은 효과를 얻도록 요구하고 있습니다. 즉, Strict DTD를 사용하기 위해서는 문서의 구조와 표현을 엄격하게 분리시켜야 한다는 얘기지요.

하지만 table, image, object 태그에서는 일부 표현 속성들이 남아있는데 그 이유에 대해서는 다음 글에서 하나씩 다루기로 하지요.

바뀐 컨텐츠 모델(Content Model)

컨텐츠 모델이란 문서에 포함된 모든 요소(태그)와 속성들의 종합적인 관계를 나타냅니다. 이 컨텐츠 모델에 따라서 어떤 태그가 다른 태그를 하위 요소로 포함할 수 있는지가 결정되는데 이 부분에서 Transitional과 Strict DTD에 차이가 있지요.

  • 모든 텍스트와 이미지는 body 태그 안에서 직접 사용될 수 없고, 반드시 div, p 태그와 같은 블록 레벨 요소로 감싸주어야 한다.
  • form 태그 안에서는 input 태그를 직접 사용할 수 없고, 반드시 div, p, fieldset 등의 태그로 감싸주어야 한다.
  • blockquote 태그 안의 모든 텍스트는 반드시 div, p 태그와 같은 블록 레벨 요소로 감싸주어야 한다.

Strict DTD를 사용해야 하는 이유

앞서 설명했듯이 Strict DTD는 문서의 구조와 표현을 엄격하게 분리시키기 위해 만들어졌습니다. 따라서 규격에 맞게 마크업하기만 하면 자연스럽게 그 목적을 달성할 수 있지요. 그에 반해서 Transitional DTD는 규격을 지킨다고 해서 ‘의미 있는’ 문서를 보장하지는 않습니다. 앞서 포스팅한 웹 표준에 대한 생각에서 ‘의미 있는’ 마크업의 장점을 다룬 바 있는데 그런 장점을 얻기 위해서는 Strict DTD를 사용하는 것이 최선의 방법입니다.

XHTML의 최종 권고안(규격)인 XHTML 1.1에서는 세 가지로 나뉘었던 DTD를 하나로 통합시켰는데 그 기반이 XHTML 1.0 Strict DTD라는 것을 생각해보면 앞으로의 웹 환경이 보다 구조적인 문서를 만드는 방향으로 나아가리라는 것을 짐작할 수 있습니다. 그러므로 새로 문서를 작성해야 한다면 Strict DTD를 도입하는 것이 미래의 웹 환경을 위해서도 바람직하리라 생각합니다.

마치며

Strict DTD를 사용하는 것은 그렇게 어려운 일이 아닙니다. Transitional DTD를 만족하는 문서를 작성할 수 있다면 약간의 연습만으로도 엄격한 마크업을 작성할 수 있으니까요. 이와 관련해서 몇 가지 HTML 태그의 사용법을 앞으로의 포스팅 주제로 삼을 생각입니다. 물론 자주 올리지는 않겠지만요. ^^;

Trackback 0 and Comment 0

폰트(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 클래스를 사용하도록 컴파일 된다. 아래 코드를 보자.

view source
print?
01.<?xml version="1.0" encoding="utf-8"?>
02.<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init()">
03.  
04.    <mx:Style>
05.        /* CSS file */
06.        @font-face
07.        {
08.            src:url("../assets/DinmedRegular.ttf");
09.            fontFamily: "DinmedRegular";
10.            advancedAntiAliasing: true;
11.        }
12.  
13.        @font-face
14.        {
15.            src:url("../assets/MetaPluBolRom.TTF");
16.            fontFamily: "MetaPluBolRom";
17.            advancedAntiAliasing: true;
18.        }
19.  
20.        Application
21.        {
22.            color:#ffffff;
23.            fontFamily : "DinmedRegular";
24.        }
25.  
26.        .myStyle
27.        {
28.            color:#ffcfff;
29.            fontFamily : "MetaPluBolRom";
30.        }
31.    </mx:Style>
32.  
33.    <mx:Label text="I'm Jidolstar"/>
34.    <mx:Label text="http://jidolstar.com/blog" styleName="myStyle"/>
35.</mx:Application>

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

view source
print?
01.mx_internal static var _FontTest_StylesInit_done:Boolean = false;
02.  
03.mx_internal function _FontTest_StylesInit():void
04.{
05.    //  only add our style defs to the StyleManager once
06.    if (mx_internal::_FontTest_StylesInit_done)
07.        return;
08.    else
09.        mx_internal::_FontTest_StylesInit_done = true;
10.  
11.    var style:CSSStyleDeclaration;
12.    var effects:Array;
13.  
14.    // myStyle
15.    style = StyleManager.getStyleDeclaration(".myStyle");
16.    if (!style)
17.    {
18.        style = new CSSStyleDeclaration();
19.        StyleManager.setStyleDeclaration(".myStyle", style, false);
20.    }
21.    if (style.factory == null)
22.    {
23.        style.factory = function():void
24.        {
25.            this.color = 0xffcfff;
26.            this.fontFamily = "MetaPluBolRom";
27.        };
28.    }
29.    // Application
30.    style = StyleManager.getStyleDeclaration("Application");
31.    if (!style)
32.    {
33.        style = new CSSStyleDeclaration();
34.        StyleManager.setStyleDeclaration("Application", style, false);
35.    }
36.    if (style.factory == null)
37.    {
38.        style.factory = function():void
39.        {
40.            this.color = 0xffffff;
41.            this.fontFamily = "DinmedRegular";
42.        };
43.    }
44.  
45.    StyleManager.mx_internal::initProtoChainRoots();
46.}
47.  
48.// embed carrier vars
49.[Embed(advancedAntiAliasing='true', mimeType='application/x-font', _pathsep='true', source='../assets/DinmedRegular.ttf', _file='D:/FontTest/src/FontTest.mxml', fontName='DinmedRegular', _line='8')]
50. private var _embed__font_DinmedRegular_medium_normal_444147531:Class;
51.  
52.[Embed(advancedAntiAliasing='true', mimeType='application/x-font', _pathsep='true', source='../assets/MetaPluBolRom.TTF', _file='D:/FontTest/src/FontTest.mxml', fontName='MetaPluBolRom', _line='15')]
53. private var _embed__font_MetaPluBolRom_medium_normal_1521687713:Class;

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


view source
print?
01.<?xml version="1.0" encoding="utf-8"?>
02.<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init()">
03.    <mx:Script>
04.        <![CDATA[
05.            import mx.events.StyleEvent;
06.            import com.starpl.net.FontEvent;
07.            import com.starpl.net.IFontInfo;
08.            import com.starpl.net.FontManager;
09.  
10.            private var fontInfo:IFontInfo;
11.            private var textField:TextField;
12.            private var styleEventDispatcher:IEventDispatcher;
13.  
14.            private function init():void
15.            {
16.                styleEventDispatcher = StyleManager.loadStyleDeclarations( "MyStyle.swf" );
17.                styleEventDispatcher.addEventListener( StyleEvent.COMPLETE, onComplete );
18.                styleEventDispatcher.addEventListener( StyleEvent.ERROR, onError );
19.            }
20.  
21.            private function onComplete( event:StyleEvent ):void
22.            {
23.            }
24.  
25.            private function onError( event:StyleEvent ):void
26.            {
27.  
28.            }
29.  
30.        ]]>
31.    </mx:Script>
32.    <mx:Label text="I'm Jidolstar"/>
33.    <mx:Label text="http://jidolstar.com/blog" styleName="myStyle"/>
34.</mx:Application>

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


view source
print?
001.package
002.{
003.import flash.system.Security;
004.import flash.text.Font;
005.import mx.core.mx_internal;
006.import mx.managers.SystemManager;
007.import mx.modules.ModuleBase;
008.import mx.styles.CSSStyleDeclaration;
009.import mx.styles.IStyleModule;
010.import mx.styles.StyleManager;
011.  
012.[ExcludeClass]
013.public class MyStyle extends ModuleBase implements IStyleModule
014.{
015.    /**
016.     * @private
017.     */
018.    private var selectors:Array = [];
019.    /**
020.     * @private
021.     */
022.    private var overrideMap:Object = {};
023.    /**
024.     * @private
025.     */
026.    private var effectMap:Object = {};
027.    /**
028.     * @private
029.     */
030.    private var unloadGlobal:Boolean;
031.  
032.    /**
033.     * @private
034.     */
035.    private static var domainsAllowed:Boolean = allowDomains();
036.  
037.    /**
038.     * @private
039.     */
040.    private static function allowDomains():Boolean
041.    {
042.        // allowDomain not allowed in AIR
043.        if (Security.sandboxType != "application")
044.            Security.allowDomain("*");
045.        return true;
046.    }
047.  
048.    [Embed(advancedAntiAliasing='true', mimeType='application/x-font', _pathsep='true', source='../assets/DinmedRegular.ttf', _file='D:/FontTest/src/MyStyle.css', fontName='DinmedRegular', _line='4')]
049.    private static var _embed__font_DinmedRegular_medium_normal_731457564:Class;
050.    [Embed(advancedAntiAliasing='true', mimeType='application/x-font', _pathsep='true', source='../assets/MetaPluBolRom.TTF', _file='D:/FontTest/src/MyStyle.css', fontName='MetaPluBolRom', _line='11')]
051.    private static var _embed__font_MetaPluBolRom_medium_normal_1234377680:Class;
052.  
053.    public function MyStyle()
054.    {
055.        var style:CSSStyleDeclaration;
056.        var keys:Array;
057.        var selector:String;
058.        var effects:Array;
059.        var addedEffects:Array;
060.  
061.        selector = ".myStyle";
062.  
063.        style = StyleManager.getStyleDeclaration(selector);
064.  
065.        if (!style)
066.        {
067.            style = new CSSStyleDeclaration();
068.            StyleManager.setStyleDeclaration(selector, style, false);
069.            selectors.push(selector);
070.        }
071.  
072.        keys = overrideMap[selector];
073.  
074.        if (keys == null)
075.        {
076.            keys = [];
077.            overrideMap[selector] = keys;
078.        }
079.  
080.        style.mx_internal::setStyle('color', 0xffcfff);
081.        keys.push("color");
082.        style.mx_internal::setStyle('fontFamily', "MetaPluBolRom");
083.        keys.push("fontFamily");
084.  
085.        selector = "Application";
086.  
087.        style = StyleManager.getStyleDeclaration(selector);
088.  
089.        if (!style)
090.        {
091.            style = new CSSStyleDeclaration();
092.            StyleManager.setStyleDeclaration(selector, style, false);
093.            selectors.push(selector);
094.        }
095.  
096.        keys = overrideMap[selector];
097.  
098.        if (keys == null)
099.        {
100.            keys = [];
101.            overrideMap[selector] = keys;
102.        }
103.  
104.        style.mx_internal::setStyle('color', 0xffffff);
105.        keys.push("color");
106.        style.mx_internal::setStyle('fontFamily', "DinmedRegular");
107.        keys.push("fontFamily");
108.  
109.        Font.registerFont(_embed__font_DinmedRegular_medium_normal_731457564);
110.        Font.registerFont(_embed__font_MetaPluBolRom_medium_normal_1234377680);
111.    }
112.  
113.    public function unload():void
114.    {
115.        unloadOverrides();
116.        unloadStyleDeclarations();
117.  
118.        if (unloadGlobal)
119.        {
120.            StyleManager.mx_internal::stylesRoot = null;
121.            StyleManager.mx_internal::initProtoChainRoots();
122.        }
123.    }
124.  
125.    /**
126.     * @private
127.     */
128.    private function unloadOverrides():void
129.    {
130.        for (var selector:String in overrideMap)
131.        {
132.            var style:CSSStyleDeclaration = StyleManager.getStyleDeclaration(selector);
133.  
134.            if (style != null)
135.            {
136.                var keys:Array = overrideMap[selector];
137.                var numKeys:int;
138.                var i:uint;
139.  
140.                if (keys != null)
141.                {
142.                    numKeys = keys.length;
143.  
144.                    for (i = 0; i < numKeys; i++)
145.                    {
146.                        style.mx_internal::clearOverride(keys[i]);
147.                    }
148.                }
149.  
150.                keys = effectMap[selector];
151.  
152.                if (keys != null)
153.                {
154.                    numKeys = keys.length;
155.                    var index:uint;
156.                    var effects:Array = style.mx_internal::effects;
157.  
158.                    for (i = 0; i < numKeys; i++)
159.                    {
160.                        index = effects.indexOf(numKeys[i]);
161.                        if (index >= 0)
162.                        {
163.                            effects.splice(index, 1);
164.                        }
165.                    }
166.                }
167.            }
168.        }
169.  
170.        overrideMap = null;
171.        effectMap = null;
172.    }
173.  
174.    /**
175.     * @private
176.     */
177.    private function unloadStyleDeclarations():void
178.    {
179.        var numSelectors:int = selectors.length;
180.  
181.        for (var i:int = 0; i < numSelectors; i++)
182.        {
183.            var selector:String = selectors[i];
184.            StyleManager.clearStyleDeclaration(selector, false);
185.        }
186.  
187.        selectors = null;
188.    }
189.}
190.  
191.}

위 코드는 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 클래스를 작성해보았다.


view source
print?
001.package com.jidolstar.fonts
002.{
003.    import flash.display.Loader;
004.    import flash.events.ErrorEvent;
005.    import flash.events.Event;
006.    import flash.events.EventDispatcher;
007.    import flash.events.IOErrorEvent;
008.    import flash.events.SecurityErrorEvent;
009.    import flash.net.URLRequest;
010.    import flash.system.ApplicationDomain;
011.    import flash.system.LoaderContext;
012.    import flash.system.Security;
013.    import flash.system.SecurityDomain;
014.    import flash.text.Font;
015.    import flash.utils.describeType;    
016.  
017.    [Event(name="loaded", type="com.starpl.net.FontLoaderEvent")]
018.    [Event(name="error", type="com.starpl.net.FontLoaderEvent")]
019.  
020.    /**
021.     * 폰트를 담은 SWF를 동적으로 로딩하고 로드한 SWF로 부터 Embed 폰트의 Class를 시스템에 등록하는 역할을 한다.
022.     *
023.     * <p>폰트를 담은 SWF를 만드는 방법</p>
024.     * <ul>
025.     * <li> 아래 예제처럼 fontName(JidolstarFont)은 클래스이름, AS파일 이름과 같아야한다.
026.     * <pre>
027.     *      package
028.     *      {
029.     *          import flash.display.Sprite;
030.     *          public class JidolstarFont extends Sprite
031.     *          {
032.     *              [Embed( mimeType='application/x-font',
033.     *                      source='../../assets/MetaPluBolRom.TTF',
034.     *                      fontName='JidolstarFont',
035.     *                      unicodeRange='U+0041,U+0044,U+0059,U+0061,U+0064,U+0079,U+0030-U+0039,U+002B,U+002D'
036.     *              )]
037.     *              static public var FontClass:Class;
038.     *          }
039.     *      }
040.     * </pre>
041.     * <li> FontClass 이름은 바뀌면 안된다.
042.     * <li> mxmlc등을 이용해 단독으로 컴파일해서 swf파일을 만든다.
043.     * </ul>
044.     *
045.     * <p>클래스를 사용하는 방법 </p>
046.     * <pre>
047.     *  var testFontName:String = "HYNamuB";
048.     *  var textField:TextField = new TextField;
049.     *  textField.text = "안녕하세요";
050.     *  addChild( textField );
051.     *
052.     *  textField.x = 50;
053.     *  textField.y = 50;
054.     *
055.     *  fontLoader = new FontLoader( testFontName+".swf", testFontName );
056.     *  fontLoader.addEventListener( FontLoaderEvent.LOADED, onFontLoaded );
057.     *  fontLoader.addEventListener( FontLoaderEvent.ERROR, onFontLoadError );
058.     *  fontLoader.load();
059.     *
060.     *  private function onFontLoaded( event:FontLoaderEvent ):void
061.     *  {
062.     *      //Embed 처리된 폰트 리스팅
063.     *      var embededfonts:Array = Font.enumerateFonts( false );
064.     *      for each( var embededfont:Font in embededfonts )
065.     *      {
066.     *          trace(  "fontName="+embededfont.fontName+
067.     *                  ",fontType="+embededfont.fontType+
068.     *                  ",fontStyle="+embededfont.fontStyle+
069.     *                  ",hasGlyphs = "+embededfont.hasGlyphs( "안녕하세요" ) );
070.     *      }
071.     *
072.     *      var textFormat:TextFormat = new TextFormat(testFontName, 14)
073.     *      textField.embedFonts = true;
074.     *      textField.setTextFormat( textFormat );
075.     *  }
076.     *
077.     *  private function onFontLoadError( event:FontLoaderEvent ):void
078.     *  {
079.     *      trace( event.fontName, event.fontURL, event.message );
080.     *  }
081.     * </pre>
082.     *
083.     * @author Yongho Ji(jidolstar[at]gmail.com)
084.     * @since 2009.03.06
085.     */
086.    public class FontLoader extends EventDispatcher
087.    {
088.        private var loader:Loader;
089.        private var loaderContext:LoaderContext;
090.  
091.        private var _fontURL:String;
092.        private var _fontName:String;
093.  
094.        /**
095.         * 생성자
096.         * @param fontURL 폰트를 로드할 경로
097.         * @param fontName 폰트이름
098.         */
099.        public function FontLoader( fontURL:String, fontName:String )
100.        {
101.            super();
102.            this._fontURL = fontURL;
103.            this._fontName = fontName;
104.        }
105.  
106.        /**
107.         * 폰트를 로드할 URL
108.         */
109.        public function get fontURL():String
110.        {
111.            return _fontURL;
112.        }
113.  
114.        /**
115.         * 폰트 이름
116.         */
117.        public function get fontName():String
118.        {
119.            return _fontName;
120.        }
121.  
122.        /**
123.         * 해당폰트가 등록되어 있는지 검사후에 폰트 로드 실시
124.         * 등록되어 있는 경우 loaded 이벤트 송출됨.
125.         * 등록되어 있지 않은 경우 로드요청.
126.         */
127.        public function load():void
128.        {
129.            //해당 폰트가 이미 등록되어 있는지 조사
130.            var currentFonts:Array = Font.enumerateFonts( false );
131.            var hasFont:Boolean = false;
132.            for each ( var f:Font in currentFonts )
133.            {
134.                if( fontName == f.fontName )
135.                {
136.                    hasFont = true;
137.                    break;
138.                }
139.            }       
140.  
141.            //폰트가 등록되어 있다면 Loaded이벤트를 날려준다!
142.            if( hasFont )
143.            {
144.                this.dispatchEvent(new FontLoaderEvent(FontLoaderEvent.LOADED, _fontName, _fontURL, null ));
145.            }
146.            //폰트가 등록되어  있지 않다면 폰트 로드를 시도한다.
147.            else
148.            {
149.                if( !loader )
150.                {
151.                    loader = new Loader();
152.                }
153.                if( !loaderContext )
154.                {
155.                    loaderContext = new LoaderContext();
156.                    loaderContext.checkPolicyFile = true;
157.                    loaderContext.applicationDomain = new ApplicationDomain( ApplicationDomain.currentDomain );
158.                    if ( Security.sandboxType == Security.REMOTE)
159.                    {
160.                        loaderContext.securityDomain = SecurityDomain.currentDomain;
161.                    }
162.                    else
163.                    {
164.                        loaderContext.securityDomain = null;
165.                    }
166.                }
167.                loader.contentLoaderInfo.addEventListener( Event.COMPLETE, onLoaded, false, 0, true);
168.                loader.contentLoaderInfo.addEventListener( IOErrorEvent.IO_ERROR, onError, false, 0, true);
169.                loader.contentLoaderInfo.addEventListener( SecurityErrorEvent.SECURITY_ERROR, onError, false, 0, true);
170.                loader.load( new URLRequest( _fontURL ), loaderContext );
171.            }
172.        }
173.  
174.        /**
175.         * 로드 완료시 해당 폰트가 있는지 확인후 등록한다.
176.         */
177.        private function onLoaded( event:Event ):void
178.        {
179.            if( loader.contentLoaderInfo.applicationDomain.hasDefinition( _fontName ) )
180.            {
181.                //폰트 등록
182.                var fontClass:Class = loader.contentLoaderInfo.applicationDomain.getDefinition( _fontName ) as Class;
183.                Font.registerFont( fontClass.FontClass );
184.                this.dispatchEvent( new FontLoaderEvent( FontLoaderEvent.LOADED, _fontName, _fontURL ) );
185.            }
186.            else
187.            {
188.                //해당폰트를 찾을 수 없으므로 실패 이벤트 송출
189.                this.dispatchEvent( new FontLoaderEvent( FontLoaderEvent.ERROR, _fontName, _fontURL, "Font Load fail" ) );
190.            }
191.            clearLoader();
192.        }
193.  
194.        /**
195.         * 폰트 로드 실패시 처리
196.         */
197.        private function onError( event:ErrorEvent ):void
198.        {
199.            this.dispatchEvent( new FontLoaderEvent( FontLoaderEvent.ERROR, _fontName, _fontURL, event.text ) );
200.            clearLoader();
201.        }
202.  
203.        /**
204.         * 로더 삭제
205.         */
206.        private function clearLoader():void
207.        {
208.            if (loader)
209.            {
210.                if (loader.contentLoaderInfo)
211.                {
212.                    loader.contentLoaderInfo.removeEventListener( Event.COMPLETE, onLoaded, false );
213.                    loader.contentLoaderInfo.removeEventListener( IOErrorEvent.IO_ERROR, onError, false );
214.                    loader.contentLoaderInfo.removeEventListener( SecurityErrorEvent.SECURITY_ERROR, onError, false );
215.                }
216.  
217.                try
218.                {
219.                    loader.close();
220.                    loader.unload();
221.                }
222.                catch(error:Error)
223.                {
224.                }
225.                loader = null;
226.                loaderContext = null;
227.            }
228.        }       
229.  
230.    }
231.}

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


view source
print?
01.package
02.{
03.    import com.jidolstar.fonts.FontLoader;
04.    import com.jidolstar.fonts.FontLoaderEvent;
05.  
06.    import flash.display.Sprite;
07.    import flash.display.StageAlign;
08.    import flash.display.StageScaleMode;
09.    import flash.text.Font;
10.    import flash.text.TextField;
11.    import flash.text.TextFormat;
12.  
13.    /**
14.     * FontLoader 테스터
15.     * @author Yongho, Ji
16.     * @since 2009.03.17
17.     */
18.    public class FontTest extends Sprite
19.    {
20.        private var fontLoader:FontLoader;
21.        private var testFontName:String = "HYNamuB";
22.  
23.        private var textField:TextField;
24.  
25.        public function FontTest()
26.        {
27.            super();
28.  
29.            stage.scaleMode = StageScaleMode.NO_SCALE;
30.            stage.align = StageAlign.TOP_LEFT;
31.  
32.            textField = new TextField;
33.            textField.text = "안녕하세요";
34.            addChild( textField );
35.  
36.            textField.x = 50;
37.            textField.y = 50;
38.  
39.            fontLoader = new FontLoader( testFontName+".swf", testFontName );
40.            fontLoader.addEventListener( FontLoaderEvent.LOADED, onFontLoaded );
41.            fontLoader.addEventListener( FontLoaderEvent.ERROR, onFontLoadError );
42.            fontLoader.load();
43.        }
44.  
45.        private function onFontLoaded( event:FontLoaderEvent ):void
46.        {
47.            //Embed 처리된 폰트 리스팅
48.            var embededfonts:Array = Font.enumerateFonts( false );
49.            for each( var embededfont:Font in embededfonts )
50.            {
51.                trace(  "fontName="+embededfont.fontName+
52.                        ",fontType="+embededfont.fontType+
53.                        ",fontStyle="+embededfont.fontStyle+
54.                        ",hasGlyphs = "+embededfont.hasGlyphs( "안녕하세요" ) );
55.            }
56.  
57.            var textFormat:TextFormat = new TextFormat(testFontName, 14)
58.            textField.embedFonts = true;
59.            textField.setTextFormat( textFormat );
60.        }
61.  
62.        private function onFontLoadError( event:FontLoaderEvent ):void
63.        {
64.            trace( event.fontName, event.fontURL, event.message );
65.        }
66.    }
67.}

따로 분석은 하지 않겠다. 위에서 사용한 소스는 아래 링크를 통해 다운로드 받아볼 수 있다. 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를 로드한다.


view source
print?
01.package com.jidolstar.fonts
02.{
03.    import flash.events.IEventDispatcher;
04.  
05.    /**
06.     * Font 컨텐츠를 로드하는 중간 에러 발생시 송출된다.
07.     * @eventType  com.jidolstar.fonts.FontEvent.ERROR
08.     */
09.    [Event(name="error", type="com.jidolstar.fonts.FontEvent")]
10.  
11.    /**
12.     * Font 컨텐츠를 로드하고 있는 중일때 송출
13.     * @eventType  com.jidolstar.fonts.FontEvent.PROGRESS
14.     */
15.    [Event(name="progress", type="com.jidolstar.fonts.FontEvent")]
16.  
17.    /**
18.     * Font 컨텐츠를 로드를 완료하고 애플리케이션에 폰트 적용이 완료되었을때 송출
19.     * @eventType  com.jidolstar.fonts.FontEvent.COMPLETE
20.     */
21.    [Event(name="complete", type="com.jidolstar.fonts.FontEvent")]
22.  
23.    /**
24.     * Font 컨텐츠가 로드 준비가 완료되었을때 송출
25.     * @eventType  com.jidolstar.fonts.FontEvent.SETUP
26.     */
27.    [Event(name="setup", type="com.jidolstar.fonts.FontEvent")]
28.  
29.    /**
30.     * 폰트 로드를 하기 위한 인터페이스
31.     */
32.    public interface IFontInfo extends IEventDispatcher
33.    {
34.        /**
35.         * Font 컨텐츠 로드 실시하면 true
36.         */
37.        function get start():Boolean;       
38.  
39.        /**
40.         * 에러발생시 true
41.         */
42.        function get error():Boolean;
43.  
44.        /**
45.         * Font 컨텐츠 로드 완료되고 적용되었을때 true
46.         */
47.        function get complete():Boolean;
48.  
49.        /**
50.         * Font 컨텐츠 로드 준비가 완료되었을때 true
51.         */
52.        function get setup():Boolean;
53.  
54.        /**
55.         * Font 컨텐츠를 로드할 URL
56.         */
57.        function get url():String;
58.  
59.        /**
60.         * Font 컨텐츠 로드 시작
61.         */
62.        function load():void;
63.    }
64.}

아래는 FontEvent.as 파일이다.


view source
print?
01.package com.jidolstar.fonts
02.{
03.    import flash.events.Event;
04.    import flash.events.ProgressEvent;
05.  
06.    /**
07.     * 폰트 이벤트
08.     * @author Yongho, Ji
09.     * @since 2009.03.09
10.     */
11.    public class FontEvent extends ProgressEvent
12.    {
13.        /**
14.         * 로드 성공시
15.         */
16.        public static const COMPLETE:String = "complete";
17.        /**
18.         * 로드 설정시
19.         */
20.        public static const SETUP:String = "setup";
21.  
22.        /**
23.         * 로드 진행시
24.         */
25.        public static const PROGRESS:String = "progress";
26.  
27.        /**
28.         * 로드 실패시
29.         */
30.        public static const ERROR:String = "error";
31.  
32.        /**
33.         * 에러 메시지
34.         */
35.        public var errorText:String;
36.  
37.        /**
38.         * 폰트 정보
39.         */
40.        public var fontInfo:IFontInfo;
41.  
42.        /**
43.         * 폰트 로더 이벤트
44.         * @param type 이벤트 Type
45.         * @param bubbles
46.         * @param cancelable
47.         * @param bytesLoaded
48.         * @param bytesTotal
49.         * @param errorText
50.         * @param fontInfo
51.         */
52.        public function FontEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false,
53.                                bytesLoaded:uint = 0, bytesTotal:uint = 0,
54.                                errorText:String = null, fontInfo:IFontInfo = null )
55.        {
56.            super(type, bubbles, cancelable, bytesLoaded, bytesTotal)
57.            this.errorText = errorText;
58.            this.fontInfo = fontInfo;
59.        }
60.  
61.        /**
62.         * 이벤트 클론
63.         */
64.        public override function clone():Event
65.        {
66.            return new FontEvent( type, bubbles, cancelable, bytesLoaded, bytesTotal, errorText, fontInfo );
67.        }
68.    }
69.}

마지막으로 FontManager.as이다


view source
print?
001.package com.jidolstar.fonts
002.{
003.    /**
004.     * Font를 동적으로 로드해서 적용하기 위한 매니저이다.
005.     * Font 컨텐츠는 SWF로 존재하며 이 SWF을 로드하기 위한 URL이 있다면 사용자는 FontManager.getInstance().getFontInfo( url )로
006.     * IFontInfo 인스턴스를 얻을 수 있으며 폰트 로드/적용 되는 상황을 FontEvent로 받을 수 있다.
007.     *
008.     * @author Yongho, Ji
009.     * @since 2009.03.09
010.     */
011.    public class FontManager
012.    {
013.        private static var _singleton:FontManager;
014.  
015.        private var fontInfoList:Object = {};
016.  
017.        /**
018.         * 싱글턴을 위한 생성자
019.         */
020.        public function FontManager( singletonForce:SingletonForce )
021.        {
022.        }
023.  
024.        /**
025.         * 싱글턴 인스턴스 생성
026.         */
027.        public static function getInstance():FontManager
028.        {
029.            if( !_singleton )
030.            {
031.                _singleton = new FontManager( new SingletonForce );
032.            }
033.            return _singleton;
034.        }
035.  
036.        /**
037.         * URL에 대한 Font 정보를 얻오온다.
038.         */
039.        public function getFontInfo( url:String ):IFontInfo
040.        {
041.            var info:FontInfo = fontInfoList[url] as FontInfo;
042.            if( !info )
043.            {
044.                info = new FontInfo( url );
045.                fontInfoList[url] = info;
046.            }
047.            return new FontInfoProxy( info );
048.        }
049.    }
050.}
051.  
052.import com.jidolstar.fonts.FontEvent;
053.import com.jidolstar.fonts.IFontInfo;
054.  
055.import flash.display.DisplayObject;
056.import flash.display.Loader;
057.import flash.events.ErrorEvent;
058.import flash.events.Event;
059.import flash.events.EventDispatcher;
060.import flash.events.IOErrorEvent;
061.import flash.events.ProgressEvent;
062.import flash.events.SecurityErrorEvent;
063.import flash.net.URLRequest;
064.import flash.system.ApplicationDomain;
065.import flash.system.LoaderContext;
066.import flash.system.Security;
067.import flash.system.SecurityDomain;
068.import flash.utils.getQualifiedClassName;
069.  
070./**
071. * @private
072. * FontManager 싱글톤 패턴을 위한 클래스
073. */
074.class SingletonForce {}
075.  
076./**
077. * @private
078. * 폰트를 담은 컨텐트를 로드하고 관리하는 클래스
079. */
080.class FontInfo extends EventDispatcher
081.{
082.    private var _url:String;
083.  
084.    private var loader:Loader;
085.    private var _error:Boolean = false;
086.    private var _start:Boolean = false;
087.    private var _complete:Boolean = false;
088.    private var _setup:Boolean = false;
089.    private var _bytesTotal:int = 0;
090.    private var reference:DisplayObject;
091.    private var applicationDomain:ApplicationDomain;
092.  
093.    public function FontInfo( url:String )
094.    {
095.        super();
096.        _url = url;
097.    }
098.  
099.    public function get start():Boolean
100.    {
101.        return _start;
102.    }
103.  
104.    public function get error():Boolean
105.    {
106.        return _error;
107.    }
108.  
109.    public function get complete():Boolean
110.    {
111.        return _complete;
112.    }   
113.  
114.    public function get setup():Boolean
115.    {
116.        return _setup;
117.    }       
118.  
119.    public function get url():String
120.    {
121.        return _url;
122.    }
123.  
124.    public function get size():int
125.    {
126.        return _bytesTotal;
127.    }   
128.  
129.    public function load():void
130.    {
131.        if( _start )
132.            return;
133.  
134.        _start = true
135.  
136.        var r:URLRequest = new URLRequest( _url );
137.        var c:LoaderContext = new LoaderContext;
138.        c.applicationDomain = new ApplicationDomain( ApplicationDomain.currentDomain );
139.        if( Security.sandboxType == Security.REMOTE )
140.        {
141.            c.securityDomain = SecurityDomain.currentDomain;
142.        }
143.  
144.        loader = new Loader;
145.        loader.contentLoaderInfo.addEventListener( Event.INIT, initHandler);
146.        loader.contentLoaderInfo.addEventListener( Event.COMPLETE, completeHandler);
147.        loader.contentLoaderInfo.addEventListener( ProgressEvent.PROGRESS, progressHandler);
148.        loader.contentLoaderInfo.addEventListener( IOErrorEvent.IO_ERROR, errorHandler);
149.        loader.contentLoaderInfo.addEventListener( SecurityErrorEvent.SECURITY_ERROR, errorHandler);
150.  
151.        loader.load(r, c);
152.    }
153.  
154.    /**
155.     *  @private
156.     */
157.    private function clearLoader():void
158.    {
159.        if (loader)
160.        {
161.            if (loader.contentLoaderInfo)
162.            {
163.                loader.contentLoaderInfo.removeEventListener(
164.                    Event.INIT, initHandler);
165.                loader.contentLoaderInfo.removeEventListener(
166.                    Event.COMPLETE, completeHandler);
167.                loader.contentLoaderInfo.removeEventListener(
168.                    ProgressEvent.PROGRESS, progressHandler);
169.                loader.contentLoaderInfo.removeEventListener(
170.                    IOErrorEvent.IO_ERROR, errorHandler);
171.                loader.contentLoaderInfo.removeEventListener(
172.                    SecurityErrorEvent.SECURITY_ERROR, errorHandler);
173.            }
174.  
175.            if (_complete)
176.            {
177.                try
178.                {
179.                    loader.close();
180.                }
181.                catch(error:Error)
182.                {
183.                }
184.            }
185.  
186.            try
187.            {
188.                loader.unload();
189.            }
190.            catch(error:Error)
191.            {
192.            }
193.  
194.            loader = null;
195.        }
196.    }   
197.  
198.    /**
199.     *  @private
200.     */
201.    public function initHandler(event:Event):void
202.    {
203.        reference = loader.content;
204.  
205.        if( !reference )
206.        {
207.            var fontEvent:FontEvent = new FontEvent(
208.                FontEvent.ERROR, event.bubbles, event.cancelable);
209.            fontEvent.bytesLoaded = 0;
210.            fontEvent.bytesTotal = 0;
211.            fontEvent.errorText = "SWF is not a loadable Font";
212.            dispatchEvent(fontEvent);
213.            return;
214.        }
215.  
216.        applicationDomain = loader.contentLoaderInfo.applicationDomain;
217.  
218.        _setup = true;
219.  
220.        dispatchEvent(new FontEvent(FontEvent.SETUP));
221.    }
222.  
223.    /**
224.     *  @private
225.     */
226.    public function progressHandler(event:ProgressEvent):void
227.    {
228.        var fontEvent:FontEvent = new FontEvent(
229.            FontEvent.PROGRESS, event.bubbles, event.cancelable);
230.        fontEvent.bytesLoaded = event.bytesLoaded;
231.        fontEvent.bytesTotal = event.bytesTotal;
232.        dispatchEvent(fontEvent);
233.    }
234.  
235.    /**
236.     *  @private
237.     */
238.    public function completeHandler(event:Event):void
239.    {
240.        _complete = true;
241.        var fontEvent:FontEvent;
242.  
243.        try
244.        {
245.            var className:String = getQualifiedClassName( reference );
246.            var mainClass:Class = applicationDomain.getDefinition( className ) as Class;
247.            new mainClass();
248.            fontEvent = new FontEvent( FontEvent.COMPLETE, event.bubbles, event.cancelable);
249.            fontEvent.bytesLoaded = loader.contentLoaderInfo.bytesLoaded;
250.            fontEvent.bytesTotal = loader.contentLoaderInfo.bytesTotal;
251.            dispatchEvent(fontEvent);
252.        }
253.        catch( e:Error )
254.        {
255.            fontEvent = new FontEvent( FontEvent.ERROR, event.bubbles, event.cancelable);
256.            fontEvent.errorText = e.message;
257.            dispatchEvent(fontEvent);
258.        }
259.  
260.        clearLoader();
261.    }
262.  
263.    /**
264.     *  @private
265.     */
266.    public function errorHandler(event:ErrorEvent):void
267.    {
268.        _error = true;
269.        var fontEvent:FontEvent = new FontEvent(
270.            FontEvent.ERROR, event.bubbles, event.cancelable);
271.        fontEvent.bytesLoaded = 0;
272.        fontEvent.bytesTotal = 0;
273.        fontEvent.errorText = event.text;
274.        dispatchEvent(fontEvent);
275.    }
276.}
277.  
278./**
279. * @private
280. * FontInfo 클래스를 한번 wraping하고 IFontInfo를 구현한 클래스이다.
281. *
282. *
283. * 사용자는 FontManager.getFontInfo( url ) 을 이용해 이 클래스의 인스턴스를 얻게된다.
284. * 한개의 url에 대해 여러개의 FontInfoProxy 인스턴스가 생성될 수 있다.
285. * 하지만 같은 url을 참조한 FontInfoProxy 인스턴스에 대해서는 단 한개의 FontInfo 인스턴스만 참조하도록 한다.
286. * 즉, URL에 대해서는 한개의 FontInfo가 된다.
287. *
288. *
289. * 이런 Proxy를 만든 이유는 폰트를 담은 컨텐츠를 url을 비교하여 중복 로드하는 것을 방지하기 위함이다.
290. * 사용자는  FontInfoProxy 존재를 알 필요없이 IFontInfo로 접근 접근만을 허용한다.
291. * 따로 Unload는 구현하지 않는다. 왜냐하면 Font가 등록되면 삭제되지는 않기 때문이다.
292. *
293. */
294.class FontInfoProxy extends EventDispatcher implements IFontInfo
295.{
296.    private var info:FontInfo;
297.  
298.    public function FontInfoProxy( info:FontInfo )
299.    {
300.        this.info = info;   
301.  
302.        info.addEventListener( FontEvent.SETUP, fontEventHandler, false, 0, true );
303.        info.addEventListener( FontEvent.PROGRESS, fontEventHandler, false, 0, true );
304.        info.addEventListener( FontEvent.COMPLETE, fontEventHandler, false, 0, true );
305.        info.addEventListener( FontEvent.ERROR, fontEventHandler, false, 0, true );
306.    }
307.  
308.    /**
309.     *  @private
310.     */
311.    public function get error():Boolean
312.    {
313.        return info.error;
314.    }
315.  
316.    /**
317.     * @private
318.     */
319.    public function get start():Boolean
320.    {
321.        return info.start;
322.    }    
323.  
324.    /**
325.     *  @private
326.     */
327.    public function get complete():Boolean
328.    {
329.        return info.complete;
330.    }
331.  
332.    /**
333.     *  @private
334.     */
335.    public function get setup():Boolean
336.    {
337.        return info.setup;
338.    }       
339.  
340.    /**
341.     *  @private
342.     */
343.    public function get url():String
344.    {
345.        return info.url;
346.    }     
347.  
348.    /**
349.     * Font Load 실시
350.     * 이미 로드가 진행진행중이거나 완료한 상태라면 따로 load를 실시하지 않도록 한다.
351.     * 즉, 실제 로드는 한번만 실시한다.
352.     */
353.    public function load():void
354.    {
355.        if( info.error )
356.        {
357.            dispatchEvent( new FontEvent( FontEvent.ERROR ) );
358.        }
359.        else if( info.start )
360.        {
361.            if( info.setup )
362.            {
363.                dispatchEvent( new FontEvent( FontEvent.SETUP ) );
364.                if( info.complete )
365.                {
366.                    var fontEvent:FontEvent;
367.                    fontEvent = new FontEvent( FontEvent.PROGRESS );
368.                    fontEvent.bytesTotal = info.size;
369.                    fontEvent.bytesLoaded = info.size;
370.                    dispatchEvent( fontEvent );
371.  
372.                    fontEvent = new FontEvent( FontEvent.COMPLETE );
373.                    fontEvent.bytesTotal = info.size;
374.                    fontEvent.bytesLoaded = info.size;
375.                    dispatchEvent( fontEvent );
376.                }
377.            }
378.        }
379.        else
380.        {
381.            info.load();
382.        }
383.    }
384.  
385.    /**
386.     *  @private
387.     */
388.    private function fontEventHandler(event:FontEvent):void
389.    {
390.        dispatchEvent(event);
391.    }
392.}

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


view source
print?
01.package
02.{
03.    import flash.display.Sprite;
04.    import flash.text.Font;
05.  
06.    public class MyFont extends Sprite
07.    {
08.        [Embed( mimeType='application/x-font', source='../assets/DinmedRegular.ttf', fontName='DinmedRegular')]
09.        static public var FontClass1:Class; 
10.  
11.        [Embed( mimeType='application/x-font', source='../assets/MetaPluBolRom.TTF', fontName='MetaPluBolRom')]
12.        static public var FontClass2:Class;
13.  
14.        public function MyFont()
15.        {
16.            super();
17.            Font.registerFont( FontClass1 );
18.            Font.registerFont( FontClass2 );
19.        }
20.    }
21.}

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


view source
print?
01.package {
02.    import com.jidolstar.fonts.FontEvent;
03.    import com.jidolstar.fonts.FontManager;
04.    import com.jidolstar.fonts.IFontInfo;
05.  
06.    import flash.display.Sprite;
07.    import flash.display.StageAlign;
08.    import flash.display.StageScaleMode;
09.    import flash.text.TextField;
10.    import flash.text.TextFormat;
11.  
12.    public class FontManagerTest extends Sprite
13.    {
14.        private var fontInfo1:IFontInfo;
15.        private var fontInfo2:IFontInfo;
16.        private var textField1:TextField;
17.        private var textField2:TextField;       
18.  
19.        public function FontManagerTest()
20.        {
21.            super();
22.            stage.scaleMode = StageScaleMode.NO_SCALE;
23.            stage.align = StageAlign.TOP_LEFT;
24.  
25.            fontInfo1 = FontManager.getInstance().getFontInfo( "MyFont.swf" );
26.            fontInfo1.addEventListener( FontEvent.COMPLETE, onComplete );
27.            fontInfo1.addEventListener( FontEvent.ERROR, onError );
28.            fontInfo1.load();
29.  
30.            //fontInfo1과 같은 경로의 SWF를 로드한다.
31.            //fontInfo1.load()를 한번 시도했기 때문에 fontInfo2.load()에서는 실제로드하지 않는다.
32.            //다만, 사용자는 이벤트를 통해 로드 되는 것처럼 사용하는 것 뿐이다. 즉, 인터페이스는 계속 유지시켜줌으로써 사용자의 오용을 방지시킨다.
33.            fontInfo2 = FontManager.getInstance().getFontInfo( "MyFont.swf" );
34.            fontInfo2.addEventListener( FontEvent.COMPLETE, onComplete );
35.            fontInfo2.addEventListener( FontEvent.ERROR, onError );
36.            fontInfo2.load();   
37.  
38.            textField1 = new TextField;
39.            textField1.embedFonts = true;
40.            textField1.defaultTextFormat = new TextFormat( "DinmedRegular" );
41.            textField1.text = "I'm jidolstar";
42.            textField1.x = 10;
43.            textField1.y = 10;
44.            textField1.rotation = 10;
45.            addChild( textField1 ); 
46.  
47.            textField2 = new TextField;
48.            textField2.embedFonts = true;
49.            textField2.defaultTextFormat = new TextFormat( "MetaPluBolRom" );
50.            textField2.text = "http://jidolstar.com";
51.            textField2.x = 10;
52.            textField2.y = 30;
53.            textField2.rotation = 10;
54.            addChild( textField2 );
55.        }
56.  
57.        private function onComplete( event:FontEvent ):void
58.        {
59.            trace( (event.target as IFontInfo).url );
60.        }
61.  
62.        private function onError( event:FontEvent ):void
63.        {
64.  
65.        }
66.    }
67.}

실행결과 



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


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

Trackback 0 and Comment 0