TextField 예제: 신문 스타일 텍스트 서식

Flash Player 9 이상, Adobe AIR 1.0 이상

News Layout 예제에서는 텍스트가 인쇄된 신문의 기사처럼 표시되도록 텍스트의 서식을 지정합니다. 입력 텍스트에는 헤드라인, 소제목 및 기사 본문이 포함될 수 있습니다. 표시 폭 및 높이가 주어진 상태에서 이 News Layout 예제에서는 헤드라인과 소제목이 표시 영역의 전체 폭을 차지하도록 서식이 지정됩니다. 기사 텍스트는 두 개 이상의 열에 나뉘어 배치됩니다.

이 예제에서는 다음과 같은 ActionScript 프로그래밍 기술을 보여 줍니다.

  • TextField 클래스 확장

  • 외부 CSS 파일 로드 및 적용

  • CSS 스타일을 TextFormat 객체로 변환

  • TextLineMetrics 클래스를 사용하여 텍스트 표시 크기에 대한 정보 확인

이 샘플에 대한 응용 프로그램 파일을 가져오려면 www.adobe.com/go/learn_programmingAS3samples_flash_kr 을 참조하십시오. News Layout 응용 프로그램 파일은 Samples/NewsLayout 폴더에 있습니다. 이 응용 프로그램은 다음과 같은 파일로 구성됩니다.

파일

설명

NewsLayout.mxml

또는

NewsLayout.fla

Flex(MXML) 또는 Flash(FLA)용 응용 프로그램의 사용자 인터페이스입니다.

com/example/programmingas3/newslayout/StoryLayoutComponent.as

StoryLayout 인스턴스를 배치하는 Flex UIComponent 클래스입니다.

com/example/programmingas3/newslayout/StoryLayout.as

표시를 위해 뉴스 기사의 모든 구성 요소를 배열하는 기본 ActionScript 클래스입니다.

com/example/programmingas3/newslayout/FormattedTextField.as

해당 TextFormat 객체를 관리하는 TextField 클래스의 하위 클래스입니다.

com/example/programmingas3/newslayout/HeadlineTextField.as

원하는 폭에 맞게 글꼴 크기를 조정하는 FormattedTextField 클래스의 하위 클래스입니다.

com/example/programmingas3/newslayout/MultiColumnTextField.as

텍스트를 두 개 이상의 열로 분할하는 ActionScript 클래스입니다.

story.css

해당 레이아웃용 텍스트 스타일을 정의하는 CSS 파일입니다.

외부 CSS 파일 읽기

News Layout 응용 프로그램에서는 우선 로컬 XML 파일에서 기사 텍스트를 읽습니다. 그런 다음 헤드라인, 소제목 및 주요 텍스트의 서식 정보를 제공하는 외부 CSS 파일을 읽습니다.

CSS 파일은 기사의 표준 단락 스타일, 헤드라인의 h1 스타일, 소제목의 h2 스타일 등 세 가지 스타일을 정의합니다.

p { 
    font-family: Georgia, "Times New Roman", Times, _serif; 
    font-size: 12; 
    leading: 2; 
    text-align: justify; 
    indent: 24; 
} 
 
h1 { 
    font-family: Verdana, Arial, Helvetica, _sans; 
    font-size: 20; 
    font-weight: bold; 
    color: #000099; 
    text-align: left; 
} 
 
h2 { 
    font-family: Verdana, Arial, Helvetica, _sans; 
    font-size: 16; 
    font-weight: normal; 
    text-align: left; 
}

외부 CSS 파일을 읽는 데 사용되는 기술은 외부 CSS 파일 로드 에서 설명한 기술과 동일합니다. CSS 파일이 로드되면 응용 프로그램에서 아래와 같이 onCSSFileLoaded() 메서드를 실행합니다.

public function onCSSFileLoaded(event:Event):void 
{ 
    this.sheet = new StyleSheet(); 
    this.sheet.parseCSS(loader.data); 
     
    h1Format = getTextStyle("h1", this.sheet); 
    if (h1Format == null) 
    { 
        h1Format = getDefaultHeadFormat(); 
    } 
    h2Format = getTextStyle("h2", this.sheet); 
    if (h2Format == null) 
    { 
        h2Format = getDefaultHeadFormat(); 
        h2Format.size = 16; 
    } 
    pFormat = getTextStyle("p", this.sheet); 
    if (pFormat == null) 
    { 
        pFormat = getDefaultTextFormat(); 
        pFormat.size = 12; 
    } 
    displayText(); 
}

onCSSFileLoaded() 메서드는 StyleSheet 객체를 만들고 이 객체를 통해 입력 CSS 데이터를 파싱합니다. 기사의 주요 텍스트는 MultiColumnTextField 객체에 표시되며 이 객체는 StyleSheet 객체를 직접 사용할 수 있습니다. 그러나 헤드라인 필드에서는 서식 지정에 TextFormat 객체를 사용하는 HeadlineTextField 클래스를 사용합니다.

onCSSFileLoaded() 메서드는 getTextStyle() 메서드를 두 번 호출하여 두 HeadlineTextField 객체에 각각 사용할 수 있도록 CSS 스타일 선언을 TextFormat 객체로 변환합니다.

public function getTextStyle(styleName:String, ss:StyleSheet):TextFormat 
{ 
    var format:TextFormat = null; 
     
    var style:Object = ss.getStyle(styleName); 
    if (style != null) 
    { 
        var colorStr:String = style.color; 
        if (colorStr != null && colorStr.indexOf("#") == 0) 
        { 
            style.color = colorStr.substr(1); 
        } 
        format = new TextFormat(style.fontFamily,  
                        style.fontSize,  
                        style.color,  
                        (style.fontWeight == "bold"), 
                        (style.fontStyle == "italic"), 
                        (style.textDecoration == "underline"),  
                        style.url, 
                        style.target, 
                        style.textAlign, 
                        style.marginLeft, 
                        style.marginRight, 
                        style.indent, 
                        style.leading); 
         
        if (style.hasOwnProperty("letterSpacing"))         
        { 
            format.letterSpacing = style.letterSpacing; 
        } 
    } 
    return format; 
}

속성 이름과 속성 값의 의미는 CSS 스타일 선언과 TextFormat 객체 간에 서로 다릅니다. getTextStyle() 메서드는 CSS 속성 값을 TextFormat 객체에서 기대하는 값으로 변환합니다.

페이지에 기사 요소 배열

StoryLayout 클래스는 헤드라인, 소제목 및 주요 텍스트 필드의 서식과 레이아웃을 신문 스타일 배열로 지정합니다. displayText() 메서드는 먼저 다양한 필드를 만들고 배치합니다.

public function displayText():void 
{ 
    headlineTxt = new HeadlineTextField(h1Format);             
    headlineTxt.wordWrap = true; 
    headlineTxt.x = this.paddingLeft; 
    headlineTxt.y = this.paddingTop; 
    headlineTxt.width = this.preferredWidth; 
    this.addChild(headlineTxt); 
     
    headlineTxt.fitText(this.headline, 1, true); 
     
    subtitleTxt = new HeadlineTextField(h2Format);  
    subtitleTxt.wordWrap = true; 
    subtitleTxt.x = this.paddingLeft; 
    subtitleTxt.y = headlineTxt.y + headlineTxt.height; 
    subtitleTxt.width = this.preferredWidth; 
    this.addChild(subtitleTxt); 
     
    subtitleTxt.fitText(this.subtitle, 2, false); 
 
    storyTxt = new MultiColumnText(this.numColumns, 20,  
                        this.preferredWidth, 400, true, this.pFormat); 
    storyTxt.x = this.paddingLeft; 
    storyTxt.y = subtitleTxt.y + subtitleTxt.height + 10; 
    this.addChild(storyTxt); 
     
    storyTxt.text = this.content; 
...

각 필드는 해당 y 속성을 이전 필드의 y 속성에 높이를 더한 값과 동일하게 설정하여 이전 필드 아래에 배치됩니다. HeadlineTextField 객체와 MultiColumnTextField 객체는 내용에 맞게 높이를 변경할 수 있으므로 이러한 동적 위치 계산이 필요합니다.

필드 크기에 맞게 글꼴 크기 변경

폭(픽셀 단위)과 표시할 행의 최대 개수가 주어지면 HeadlineTextField는 텍스트가 필드에 맞도록 글꼴 크기를 변경합니다. 텍스트가 짧으면 글꼴 크기가 커져 타블로이드 스타일의 헤드라인이 만들어지고, 텍스트가 길면 글꼴 크기가 작아집니다.

아래에 나와 있는 HeadlineTextField.fitText() 메서드는 글꼴 크기 조정 작업을 수행합니다.

public function fitText(msg:String, maxLines:uint = 1, toUpper:Boolean = false, targetWidth:Number = -1):uint 
{ 
    this.text = toUpper ? msg.toUpperCase() : msg; 
     
    if (targetWidth == -1) 
    { 
        targetWidth = this.width; 
    } 
     
    var pixelsPerChar:Number = targetWidth / msg.length; 
     
    var pointSize:Number = Math.min(MAX_POINT_SIZE, Math.round(pixelsPerChar * 1.8 * maxLines)); 
     
    if (pointSize < 6) 
    { 
        // the point size is too small 
        return pointSize; 
    } 
     
    this.changeSize(pointSize); 
     
    if (this.numLines > maxLines) 
    { 
        return shrinkText(--pointSize, maxLines); 
    } 
    else 
    { 
        return growText(pointSize, maxLines); 
    } 
} 
 
public function growText(pointSize:Number, maxLines:uint = 1):Number 
{ 
    if (pointSize >= MAX_POINT_SIZE) 
    { 
        return pointSize; 
    } 
     
    this.changeSize(pointSize + 1); 
     
    if (this.numLines > maxLines) 
    { 
        // set it back to the last size 
        this.changeSize(pointSize); 
        return pointSize; 
    } 
    else 
    { 
        return growText(pointSize + 1, maxLines); 
    } 
} 
 
public function shrinkText(pointSize:Number, maxLines:uint=1):Number 
{ 
    if (pointSize <= MIN_POINT_SIZE) 
    { 
        return pointSize; 
    } 
     
    this.changeSize(pointSize); 
     
    if (this.numLines > maxLines) 
    { 
        return shrinkText(pointSize - 1, maxLines); 
    } 
    else 
    { 
        return pointSize; 
    } 
}

HeadlineTextField.fitText() 메서드는 간단한 재귀 기술을 사용하여 글꼴 크기를 조정합니다. 먼저 텍스트의 문자당 평균 픽셀 수를 추정하여 시작점 크기를 계산합니다. 그런 다음 글꼴 크기를 변경하고 텍스트가 최대 텍스트 행 수를 초과하도록 줄 바꿈되었는지 확인합니다. 행이 너무 많은 경우에는 shrinkText() 메서드를 호출하여 글꼴 크기를 줄인 다음 다시 시도합니다. 행의 수가 너무 많지 않은 경우에는 growText() 메서드를 호출하여 글꼴 크기를 늘린 다음 다시 시도합니다. 글꼴 크기를 한 포인트 더 늘리면 행 수가 제한을 초과하게 되는 지점에서 이러한 프로세스가 중단됩니다.

여러 열에 텍스트 분할

MultiColumnTextField 클래스는 여러 TextField 객체에 텍스트를 분산하며 이러한 객체는 신문의 열처럼 배열됩니다.

MultiColumnTextField() 생성자는 먼저 다음과 같이 각 열에 TextField 객체가 하나씩 해당하는 배열을 만듭니다.

    for (var i:int = 0; i < cols; i++) 
    { 
        var field:TextField = new TextField(); 
        field.multiline = true; 
        field.autoSize = TextFieldAutoSize.NONE; 
        field.wordWrap = true; 
        field.width = this.colWidth; 
        field.setTextFormat(this.format); 
        this.fieldArray.push(field); 
        this.addChild(field); 
    }

addChild() 메서드를 사용하여 각 TextField 객체가 배열에 추가되고 표시 목록에 추가됩니다.

StoryLayout text 속성 또는 styleSheet 속성이 변경될 때마다 layoutColumns() 메서드를 호출하여 텍스트를 다시 표시합니다. layoutColumns() 메서드는 getOptimalHeight() 메서드를 호출하여 주어진 레이아웃 폭 내에 모든 텍스트를 맞추기 위해 필요한 올바른 픽셀 높이를 계산합니다.

public function getOptimalHeight(str:String):int 
{ 
    if (field.text == "" || field.text == null) 
    { 
        return this.preferredHeight; 
    } 
    else 
    { 
        this.linesPerCol = Math.ceil(field.numLines / this.numColumns); 
         
        var metrics:TextLineMetrics = field.getLineMetrics(0); 
        this.lineHeight = metrics.height; 
        var prefHeight:int = linesPerCol * this.lineHeight; 
         
        return prefHeight + 4; 
    } 
}

먼저 getOptimalHeight() 메서드가 각 열의 폭을 계산합니다. 그런 다음 배열 내 첫 번째 TextField 객체의 폭과 htmlText 속성을 설정합니다. getOptimalHeight() 메서드가 이 첫 번째 TextField 객체를 사용하여 텍스트에서 줄 바꿈된 행의 총 수를 확인하고 이를 통해 각 열에 포함해야 하는 행의 수를 파악합니다. 그런 다음 TextField.getLineMetrics() 메서드를 호출하여 첫 번째 행의 텍스트 크기에 대한 세부 정보가 포함된 TextLineMetrics 객체를 검색합니다. TextLineMetrics.height 속성은 어센트, 디센트 및 행간을 포함한 텍스트 한 행의 전체 높이를 픽셀 단위로 나타냅니다. 그러면 MultiColumnTextField 객체의 최적 높이는 행 높이와 한 열의 행 수를 곱한 값에 4(TextField 객체의 위쪽 및 아래쪽 테두리 2픽셀씩)를 더한 값이 됩니다.

다음은 전체 layoutColumns() 메서드의 코드입니다.

public function layoutColumns():void 
{ 
    if (this._text == "" || this._text == null) 
    { 
        return; 
    } 
     
    var field:TextField = fieldArray[0] as TextField; 
    field.text = this._text; 
    field.setTextFormat(this.format); 
 
    this.preferredHeight = this.getOptimalHeight(field); 
     
    var remainder:String = this._text; 
    var fieldText:String = ""; 
    var lastLineEndedPara:Boolean = true; 
     
    var indent:Number = this.format.indent as Number; 
     
    for (var i:int = 0; i < fieldArray.length; i++) 
    { 
        field = this.fieldArray[i] as TextField; 
 
        field.height = this.preferredHeight; 
        field.text = remainder; 
 
        field.setTextFormat(this.format); 
 
        var lineLen:int; 
        if (indent > 0 && !lastLineEndedPara && field.numLines > 0) 
        { 
            lineLen = field.getLineLength(0); 
            if (lineLen > 0) 
            { 
                field.setTextFormat(this.firstLineFormat, 0, lineLen); 
                } 
            } 
         
        field.x = i * (colWidth + gutter); 
        field.y = 0; 
 
        remainder = ""; 
        fieldText = ""; 
 
        var linesRemaining:int = field.numLines;      
        var linesVisible:int = Math.min(this.linesPerCol, linesRemaining); 
 
        for (var j:int = 0; j < linesRemaining; j++) 
        { 
            if (j < linesVisible) 
            { 
                fieldText += field.getLineText(j); 
            } 
            else 
            { 
                remainder +=field.getLineText(j); 
            } 
        } 
 
        field.text = fieldText; 
 
        field.setTextFormat(this.format); 
         
        if (indent > 0 && !lastLineEndedPara) 
        { 
            lineLen = field.getLineLength(0); 
            if (lineLen > 0) 
            { 
                field.setTextFormat(this.firstLineFormat, 0, lineLen); 
            } 
        } 
 
        var lastLine:String = field.getLineText(field.numLines - 1); 
        var lastCharCode:Number = lastLine.charCodeAt(lastLine.length - 1); 
         
        if (lastCharCode == 10 || lastCharCode == 13) 
        { 
        lastLineEndedPara = true; 
        } 
        else 
        { 
        lastLineEndedPara = false; 
        } 
 
        if ((this.format.align == TextFormatAlign.JUSTIFY) && 
                (i < fieldArray.length - 1)) 
        { 
        if (!lastLineEndedPara) 
        { 
            justifyLastLine(field, lastLine); 
        } 
    } 
    } 
}

preferredHeight 속성이 설정( getOptimalHeight() 메서드 호출)된 후 여러 TextField 객체에 layoutColumns() 메서드가 반복되어 각각의 높이가 preferredHeight 값으로 설정됩니다. 그런 다음 layoutColumns() 메서드가 각 필드에 적정한 수의 텍스트 행을 분산하여 개별 필드에서 스크롤이 발생하지 않고 이전 필드의 텍스트가 끝난 지점에서 각 다음 필드의 텍스트가 시작될 수 있도록 합니다. 텍스트 정렬 스타일이 “justify”로 설정된 경우에는 justifyLastLine() 메서드가 호출되어 텍스트의 마지막 행이 필드에 양쪽 정렬됩니다. 텍스트 정렬 스타일이 “justify”로 설정되지 않은 경우에는 마지막 행이 단락의 마지막 행으로 간주되고 양쪽 정렬되지 않습니다.