Exemplo de TextField: formatação de texto estilo jornal

Flash Player 9 e posterior, Adobe AIR 1.0 e posterior

O exemplo Layout de notícias formata o texto para uma aparência similar a uma matéria de jornal impresso. O texto de entrada pode conter um título, um subtítulo e o corpo da matéria. Com determinada largura e altura de exibição, o exemplo de Layout de notícias formata o título e o subtítulo de modo a ocupar toda a extensão da área de exibição. O texto da matéria é distribuído em duas ou mais colunas.

Este exemplo ilustra as seguintes técnicas de programação do ActionScript:

  • Extensão da classe TextField

  • Carregamento e aplicação de um arquivo CSS externo

  • Conversão de estilos CSS em objetos TextFormat

  • Uso da classe TextLineMetrics para obter informações sobre o tamanho de exibição de texto

Para obter os arquivos de aplicativo desse exemplo, consulte www.adobe.com/go/learn_programmingAS3samples_flash_br . Os arquivos do aplicativo Layout de notícias podem ser encontrados na pasta Samples/NewsLayout. O aplicativo consiste nos seguintes arquivos:

Arquivo

Descrição

NewsLayout.mxml

ou

NewsLayout.fla

A interface do usuário do aplicativo para Flex (MXML) ou Flash (FLA).

com/example/programmingas3/newslayout/StoryLayoutComponent.as

Uma classe UIComponent do Flex que substitui a instância do StoryLayout.

com/example/programmingas3/newslayout/StoryLayout.as

A principal classe do ActionScript que organiza todos os componentes de uma matéria jornalística para exibição.

com/example/programmingas3/newslayout/FormattedTextField.as

Uma subclasse da classe TextField que gerencia seus próprio TextFormat.

com/example/programmingas3/newslayout/HeadlineTextField.as

Uma subclasse da classe FormattedTextField que ajusta os tamanhos das fontes de modo a se ajustarem à largura desejada.

com/example/programmingas3/newslayout/MultiColumnTextField.as

Uma classe ActionScript que divide texto em duas ou mais colunas.

story.css

Um arquivo CSS que define estilos de texto para o layout.

Leitura do arquivo CSS externo

O aplicativo Layout de notícias inicia pela leitura do texto da matéria de um arquivo XML local. Em seguida, ele lê um arquivo CSS externo que fornece as informações de formatação para o título, subtítulo e texto principal.

O arquivo CSS define três estilos, um estilo de parágrafo padrão para a matéria, e os estilos h1 e h2 para título e subtítulo respectivamente.

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; 
}

A técnica usada par ler o arquivo CSS externo é a mesma descrita em Carregamento de um arquivo CSS externo Quando o arquivo CSS for carregado, o aplicativo executará o método onCSSFileLoaded() , como mostrado abaixo.

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(); 
}

O método onCSSFileLoaded() cria um objeto StyleSheet e o utiliza para analisar os dados das CSS de entrada. O principal texto da matéria é exibida em um objeto MultiColumnTextField, que pode usar um objeto StyleSheet diretamente. No entanto, os campos do título usam a classe HeadlineTextField, que usam um objeto TextFormat para formatação.

O método onCSSFileLoaded() chama o método getTextStyle() duas vezes para converter uma declaração de estilo CSS em um objeto TextFormat a ser utilizado com cada um dos dois objetos TextField do título.

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; 
}

Os nomes das propriedades e o significado dos valores das propriedades são diferentes entre as declarações de estilo CSS e os objetos TextFormat. O método getTextStyle() converte os valores das propriedades das CSS nos valores esperados pelo objeto TextFormat.

Organização dos elementos da matéria na página

A classe StoryLayout formata e dispõe o título, o subtítulo e os campos de texto principais em uma organização com estilo de jornal. O método displayText() cria inicialmente e posiciona os diversos campos.

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; 
...

Cada campo é posicionado abaixo do campo anterior definindo sua propriedade y de modo a igualar a propriedade y do campo anterior mais sua altura. Esse cálculo dinâmico de posicionamento é necessário porque os objetos HeadlineTextField e MultiColumnTextField podem alterar sua altura de modo a se adequarem ao conteúdo.

Alteração do tamanho da fonte para ajustar o tamanho do campo

Dada a largura em pixels e o número máximo de linhas, o HeadlineTextField altera o tamanho da fonte de modo a ajustar o texto ao campo. Se o texto for curto, o tamanho da fonte será grande, criando um título estilo tabloide. Se o texto for longo, o tamanho da fonte será menor.

O método HeadlineTextField.fitText() mostrado abaixo faz com que o dimensionamento de fonte funcione:

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; 
    } 
}

O método HeadlineTextField.fitText() usa uma técnica recursiva simples para dimensionar a fonte. Em primeiro lugar, ele advinha um número médio de pixels por caractere no texto e, então, calcula um tamanho de ponto inicial. Em seguida, altera o tamanho da fonte e verifica se o texto tem quebra de palavras para criar mais do que o número máximo de linhas de texto. Se houver linhas em excesso, ele chamará o método shrinkText() para diminuir o tamanho da fonte e tentar novamente. Se não houver linhas em excesso, ele chamará o método growText() para aumentar o tamanho da fonte e tentar novamente. O processo parará no ponto em que incrementar o tamanho da fonte em mais um criará linhas em excesso.

Divisão de texto em várias colunas

A classe MultiColumnTextField expande o texto entre vários objetos TextField que são organizados, por sua vez, como colunas de jornal.

O construtor MultiColumnTextField() cria, primeiro, uma matriz de objetos TextFields, uma para cada coluna, conforme mostrado aqui:

    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); 
    }

Cada objeto TextField é adicionado à matriz e à lista de exibição com o método addChild() .

Sempre que a propriedade text ou styleSheet do StoryLayout muda, ele chama o método layoutColumns() para re-exibir o texto. O método layoutColumns() chama o método getOptimalHeight() para calcular a altura em pixels correta necessária para ajustar todo o texto a determinada largura de layout.

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; 
    } 
}

Em primeiro lugar, o método getOptimalHeight() calcula a largura de cada coluna. Em seguida, define a largura e a propriedade htmlText do primeiro objeto TextField na matriz. O método getOptimalHeight() usa esse primeiro objeto TextField para descobrir o número total de linhas com quebra de palavras no texto e, com base nesse número, identifica quantas linhas deve haver em cada coluna. Em seguida, ele chama o método TextField.getLineMetrics() para recuperar um objeto TextLineMetrics contendo detalhes sobre o tamanho do texto na primeira linha. A propriedade TextLineMetrics.height representa a altura completa de uma linha de texto, em pixels, incluindo ascendente, descendente e entrelinha. A altura ideal do objeto MultiColumnTextField é a altura da linha multiplicada pelo número de linhas por coluna, mais 4 para computar a borda de dois pixels em cima e embaixo de um objeto TextField.

Este é o código do método layoutColumns() completo:

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); 
        } 
    } 
    } 
}

Depois da propriedade preferredHeight ter sido definida pela chamada do método getOptimalHeight() , o método layoutColumns() será iterado por meio dos objetos TextField definindo a altura de cada objeto para o valor preferredHeight . O método layoutColumns() distribui apenas linhas suficientes de texto para cada campo de modo que não ocorra rolamento em nenhum campo individual e o texto em cada campo sucessivo inicie onde termina o texto no campo anterior. Se o estilo de alinhamento de texto for definido como “justificar”, o método justifyLastLine() será chamado para justificar a linha final do texto em um campo. Caso contrário, a última linha será tratada como uma linha de fim de parágrafo e não justificada.