TekstField-voorbeeld: tekstopmaak in krantenstijl

Flash Player 9 of hoger, Adobe AIR 1.0 of hoger

In het voorbeeld met nieuwslay-out wordt tekst opgemaakt als een artikel in een gedrukte krant. De invoertekst kan een kop, een subkop en de hoofdtekst van het artikel bevatten. Bij een bepaalde weergavebreedte en -hoogte, worden met dit nieuwslay-outvoorbeeld de kop en subkop zodanig opgemaakt dat de volledige breedte van het weergavegebied wordt gebruikt. De tekst van het artikel wordt verspreid over twee of meer kolommen.

In dit voorbeeld worden de volgende ActionScript-programmeringstechnieken geïllustreerd:

  • De klasse TextField uitbreiden

  • Een extern CSS-bestand laden en toepassen

  • CSS-stijlen converteren naar TextFormat-objecten

  • De klasse TextLineMetrics gebruiken om informatie over de tekstweergavegrootte te krijgen

Zie www.adobe.com/go/learn_programmingAS3samples_flash_nl als u de toepassingsbestanden voor dit voorbeeld wilt downloaden. De toepassingsbestanden van News Layout vindt u in de map Samples/NewsLayout. De toepassing bestaat uit de volgende bestanden:

Bestand

Beschrijving

NewsLayout.mxml

of

NewsLayout.fla

De gebruikersinterface voor de toepassing voor Flex (MXML) of Flash (FLA).

com/example/programmingas3/newslayout/StoryLayoutComponent.as

Een Flex-klasse UIComponent die de instantie StoryLayout plaatst.

com/example/programmingas3/newslayout/StoryLayout.as

De hoofdklasse van ActionScript waarmee alle componenten van een nieuwsartikel worden gerangschikt voor weergave.

com/example/programmingas3/newslayout/FormattedTextField.as

Een subklasse van de klasse TextField waarmee het eigen TextFormat-object wordt beheerd.

com/example/programmingas3/newslayout/HeadlineTextField.as

Een subklasse van de klasse FormattedTextField waarmee de lettertypegrootte wordt aangepast aan de gewenste breedte.

com/example/programmingas3/newslayout/MultiColumnTextField.as

Een ActionScript-klasse waarmee tekst over twee of meer kolommen wordt verdeeld.

story.css

Een CSS-bestand dat tekststijlen definieert voor de lay-out.

Het externe CSS-bestand lezen

De nieuwslay-outtoepassing start met het lezen van de artikeltekst uit een lokaal XML-bestand. Vervolgens wordt een extern CSS-bestand gelezen dat de opmaakinformatie bevat voor de kop, subkop en hoofdtekst.

In het CSS-bestand worden drie stijlen gedefinieerd: een standaardalineastijl voor het artikel en de stijlen h1 en h2 voor respectievelijk de kop en de subkop.

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

De techniek die wordt gebruikt voor het lezen van het externe CSS-bestand is dezelfde als de techniek die wordt beschreven in Een extern CSS-bestand laden. Wanneer het CSS-bestand is geladen, voert de toepassing de hieronder getoonde methode onCSSFileLoaded() uit.

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

Met de methode onCSSFileLoaded() wordt een StyleSheet-object gemaakt, dat de CSS-invoergegevens parseert. De hoofdtekst van het artikel wordt weergegeven in een MultiColumnTextField-object, dat rechtstreeks gebruik kan maken van een StyleSheet-object. Het kopveld maakt echter gebruik van de klasse HeadlineTextField, die een TextFormat-object gebruikt voor de opmaak.

Met de methode onCSSFileLoaded() wordt tweemaal de methode getTextStyle() aangeroepen om een CSS-stijldeclaratie te converteren naar een TextFormat-object dat kan worden gebruikt bij elk van de twee HeadlineTextField-objecten.

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

De namen van de eigenschappen en de betekenis van de eigenschapwaarden verschillen tussen CSS-stijldeclaraties en TextFormat-objecten. Met de methode getTextStyle() worden CSS-eigenschapwaarden vertaald in de waarden die worden verwacht door het TextFormat-object.

Artikelelementen op de pagina rangschikken

Met de klasse StoryLayout worden de kop-, subkop- en hoofdtekstvelden opgemaakt en ingedeeld in een krantenopmaak. Met de methode displayText() worden in eerste instantie de verschillende velden gemaakt en geplaatst.

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

Elk veld wordt onder het vorige veld geplaatst door de eigenschap y ervan gelijk te maken aan de eigenschap y van het vorige veld plus de hoogte van het veld. Deze dynamische plaatsingsberekening is nodig omdat HeadlineTextField-objecten en MultiColumnTextField-objecten hun hoogte kunnen aanpassen aan de inhoud.

Tekengrootte aanpassen aan de veldgrootte

Bij een bepaalde breedte in pixels en een maximumaantal regels dat kan worden weergegeven, past HeadlineTextField de tekengrootte aan zodat de tekst in het veld past. Als de tekst kort is, is de tekengrootte groot waardoor een koptekst in tabloidstijl ontstaat. Als de tekst lang is, wordt de tekengrootte kleiner.

De hieronder getoonde methode HeadlineTextField.fitText() past de tekengrootte aan:

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

De methode HeadlineTextField.fitText() maakt gebruik van een eenvoudige recursieve techniek om de tekengrootte aan te passen. Eerst wordt een schatting gemaakt van het gemiddelde aantal pixels per teken in de tekst en op basis daarvan wordt een eerste puntgrootte berekend. Vervolgens wordt de tekengrootte gewijzigd en wordt gecontroleerd of er tekstomloop heeft plaatsgevonden om meer dan één regel tekst te maken. Als er te veel regels zijn, wordt de methode shrinkText() aangeroepen waarmee de tekengrootte wordt verkleind en opnieuw wordt gekeken. Als er niet te veel regels zijn, wordt de methode growText() aangeroepen waarmee de tekengrootte wordt vergroot en opnieuw wordt gekeken. Het proces stopt op het moment waarop het vergroten van de tekengrootte met één punt te veel regels zou opleveren.

Tekst splitsen over meerdere kolommen

Met de klasse MultiColumnTextField wordt tekst verspreid over meerdere TextField-objecten die vervolgens worden gerangschikt als krantenkolommen.

Met de constructor MultiColumnTextField() wordt eerst een array van TextField-objecten gemaakt, één voor elke kolom, zoals hier aangegeven:

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

Elk TextField-object wordt met de methode addChild() toegevoegd aan de array en aan de weergavelijst.

Telkens wanneer de StoryLayout-eigenschap text of de eigenschap styleSheet wijzigt, wordt de methode layoutColumns() aangeroepen om de tekst opnieuw weer te geven. Met de methode layoutColumns() wordt de methode getOptimalHeight() aangeroepen om de juiste pixelhoogte te berekenen die nodig is om alle tekst binnen de beschikbare lay-outbreedte te passen.

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

Met de methode getOptimalHeight() wordt eerst de breedte van elke kolom berekend. Vervolgens worden de breedte en de eigenschap htmlText van het eerste TextField-object in de array ingesteld. De methode getOptimalHeight() gebruikt dat eerste TextField-object om het totale aantal regels met regelomloop in de tekst te berekenen, en bepaalt op basis daarvan hoeveel regels elke kolom moet hebben. Vervolgens wordt de methode TextField.getLineMetrics() aangeroepen om een TextLineMetrics-object op te halen dat gegevens bevat over de grootte van de tekst op de eerste regel. De eigenschap TextLineMetrics.height geeft de volledige hoogte van een regel tekst weer (in pixels), inclusief stok, staart en regelafstand. De optimale hoogte voor het MultiColumnTextField-object is vervolgens de regelhoogte maal het aantal regels per kolom, plus 4 voor de rand van twee pixels boven en onder aan een TextField-object.

Hier is de code voor de volledige methode 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); 
        } 
    } 
    } 
}

Wanneer de eigenschap preferredHeight is ingesteld door het aanroepen van de methode getOptimalHeight(), worden met de methode layoutColumns() de TextField-objecten doorlopen en wordt de hoogte van elk van de objecten ingesteld op de waarde preferredHeight. Met de methode layoutColumns() worden vervolgens net voldoende regels tekst in elk veld verdeeld zodat er geen verschuiving optreedt in de afzonderlijke velden en de tekst in elk volgende veld begint waar de tekst in het vorige veld eindigde. Als de tekstuitlijningsstijl is ingesteld op “uitvullen”, wordt de methode justifyLastLine() aangeroepen om de laatste regel tekst in een veld uit te vullen. Anders zou de laatste regel worden behandeld als een regel aan het einde van een alinea en niet worden uitgevuld.