Exemple TextField : mise en forme du texte dans le style « article de journal »

Flash Player 9 et les versions ultérieures, Adobe AIR 1.0 et les versions ultérieures

L’exemple « Article de journal » met en forme le texte pour lui donner l’aspect d’un article de journal. Le texte saisi peut contenir un gros titre, un intertitre et le corps de l’article. En fonction d’une largeur et d’une hauteur d’affichage, cet exemple met en forme le gros titre et l’intertitre pour qu’ils occupent toute la largeur disponible. Le texte de l’article est réparti sur plusieurs colonnes.

Cet exemple illustre les techniques de programmation en ActionScript suivantes :

  • Extension de la classe TextField

  • Chargement et application d’un fichier CSS externe

  • Conversion de styles CSS en objets TextFormat

  • Utilisation de la classe TextLineMetrics pour obtenir des informations sur la taille d’affichage du texte

Pour obtenir les fichiers d’application de cet exemple, voir www.adobe.com/go/learn_programmingAS3samples_flash_fr. Les fichiers de l’application News Layout se trouvent dans le dossier Samples/NewsLayout. L’application se compose des fichiers suivants :

Fichier

Description

NewsLayout.mxml

ou

NewsLayout.fla

Interface utilisateur de l’application pour Flex (MXML) ou Flash (FLA).

com/example/programmingas3/newslayout/StoryLayoutComponent.as

Classe Flex UIComponent qui place l’occurrence de StoryLayout.

com/example/programmingas3/newslayout/StoryLayout.as

Principale classe ActionScript chargée d’organiser les composants d’un article pour leur affichage.

com/example/programmingas3/newslayout/FormattedTextField.as

Sous-classe de la classe TextField qui gère son propre objet TextFormat.

com/example/programmingas3/newslayout/HeadlineTextField.as

Sous-classe de la classe FormattedTextField qui ajuste la taille des polices en fonction de la largeur voulue.

com/example/programmingas3/newslayout/MultiColumnTextField.as

Classe ActionScript qui répartit le texte de l’article sur plusieurs colonnes.

story.css

Fichier CSS définissant les styles du texte pour la mise en page.

Lecture du fichier CSS externe

L’application News Layout commence par récupérer le texte de l’article à partir d’un fichier XML local. Elle lit ensuite un fichier CSS externe contenant les informations de mise en forme pour le gros titre, l’intertitre et le texte.

Ce fichier CSS définit trois styles, un style de paragraphe standard pour l’article et les styles h1 et h2, respectivement pour le gros titre et l’intertitre.

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

La technique utilisée pour lire le fichier CSS externe est identique à celle décrite à la section Chargement de fichiers CSS externes. Après le chargement du fichier CSS, l’application exécute la méthode onCSSFileLoaded(), représentée ci-dessous.

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

La méthode onCSSFileLoaded() crée un objet StyleSheet qui analyse les données du fichier CSS. Le texte principal de l’article est affiché dans un objet MultiColumnTextField, qui peut utiliser directement un objet StyleSheet. Toutefois, les champs du gros titre utilisent la classe HeadlineTextField, qui utilise un objet TextFormat pour sa mise en forme.

La méthode onCSSFileLoaded() appelle deux fois la méthode getTextStyle() pour convertir une déclaration de style CSS en un objet TextFormat destiné aux deux objets HeadlineTextField.

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

Les noms de propriétés et la signification de leurs valeurs diffèrent entre les déclarations de style CSS et les objets TextFormat. La méthode getTextStyle() transforme donc les valeurs des propriétés CSS dans les valeurs attendues par l’objet TextFormat.

Disposition des éléments de l’article sur la page

La classe StoryLayout formate et met en page les champs de texte dévolus au gros titre, à l’intertitre et à l’article dans le style d’une page de journal. La méthode displayText() crée et place les divers champs.

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

Chaque champ est placé sous le champ précédent en effectuant le calcul suivant : la propriété y du champ est égale à la propriété y du champ précédent, plus sa hauteur. Ce calcul dynamique de position est nécessaire, car les objets HeadlineTextField et MultiColumnTextField peuvent changer de hauteur en fonction de leur contenu.

Modification de la taille de la police en fonction de la taille du champ

Sur la base d’une largeur en pixels et d’un nombre maximum de lignes à afficher, l’objet HeadlineTextField modifie la taille de la police pour adapter le texte aux dimensions du champ. Si le texte est court, la police est de grande taille, créant ainsi un gros titre de style tabloïde. Si le texte est long, la police est de plus petite taille.

La méthode HeadlineTextField.fitText() reproduite ci-dessous est chargée du redimensionnement du texte :

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

La méthode HeadlineTextField.fitText() utilise une technique récursive simple pour dimensionner le texte. Elle estime d’abord un nombre moyen de pixels par caractère pour le texte, puis calcule une taille de départ. Elle change alors la taille de la police et vérifie si le texte a renvoyé des mots à la ligne et si le nombre maximal de lignes est dépassé. Si c’est le cas, elle appelle la méthode shrinkText() pour réduire la taille du texte, et teste à nouveau. Si le nombre de lignes n’est pas trop important, elle appelle la méthode growText() pour augmenter la taille du texte, et teste à nouveau. Ce processus s’interrompt lorsque l’augmentation de taille du texte d’un seul point crée un nombre de lignes trop élevé.

Répartition du texte sur plusieurs colonnes

La classe MultiColumnTextField répartit le texte entre plusieurs objets TextField qui sont organisés comme des colonnes de texte sur une page de journal.

Le constructeur de MultiColumnTextField() crée tout d’abord un tableau d’objets TextField, un pour chaque colonne :

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

Chaque objet TextField est ajouté au tableau et à la liste d’affichage à l’aide de la méthode addChild().

Si la propriété text ou styleSheet de StoryLayout change, la méthode layoutColumns() est appelée pour réafficher le texte. La méthode layoutColumns() appelle la méthode getOptimalHeight() pour déterminer la hauteur correcte en pixels nécessaire pour adapter tout le texte en fonction de la largeur disponible.

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

La méthode getOptimalHeight() calcule d’abord la largeur de chaque colonne. Elle définit ensuite la propriété htmlText du premier objet TextField du tableau. La méthode getOptimalHeight() utilise ce premier objet TextField pour connaître le nombre total de lignes renvoyées à la ligne, et en déduit donc le nombre de lignes optimal pour chaque colonne. Elle appelle ensuite la méthode TextField.getLineMetrics() pour obtenir un objet TextLineMetrics contenant des informations sur la taille du texte de la première ligne. La propriété TextLineMetrics.height représente la hauteur totale (en pixels) d’une ligne de texte, avec les mesures ascendantes, descendantes et l’interligne. La hauteur optimale de l’objet MultiColumnTextField est donc la hauteur de ligne multipliée par le nombre de lignes par colonne, plus 4 pour tenir compte de la bordure de deux pixels en haut et en bas de l’objet TextField.

Voici le code complet de la méthode 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); 
        } 
    } 
    } 
}

Lorsque la propriété preferredHeight a été définie par un appel à la méthode getOptimalHeight, la méthode layoutColumns() parcourt les objets TextField et définit la hauteur de chacun avec la valeur preferredHeight. La méthode layoutColumns() distribue ensuite le nombre de lignes de texte adapté à chaque champ, de sorte qu’aucun défilement ne se produise dans l’un d’eux et que le texte de chaque champ enchaîne sur celui du champ précédent. Si le style d’alignement du texte a été défini sur « justifier », la méthode justifyLastLine() est appelée pour justifier la ligne finale du texte dans un champ. Dans le cas contraire, la dernière ligne est considérée comme une ligne de fin de paragraphe et n’est donc pas justifiée.