Exempel på textfält: Textformatering i tidningsstil

Flash Player 9 och senare, Adobe AIR 1.0 och senare

Exemplet News Layout formaterar text så att den ser ut som en artikel i en tryckt tidning. Den inmatade texten kan innehålla en rubrik, en underrubrik och artikelns brödtext. I exemplet News Layout formateras rubriken och underrubriken så att visningsområdets hela bredd utnyttjas när visningsområdets bredd och höjd anges. Brödtexten fördelas på två eller flera kolumner.

Exemplet visar följande ActionScript-programmeringstekniker:

  • Utöka klassen TextField

  • Läsa in och använda en extern CSS-fil

  • Konvertera CSS-format till TextFormat-objekt

  • Hämta information om den visade textens storlek med klassen TextLineMetrics

Programfilerna för det här exemplet finns på www.adobe.com/go/learn_programmingAS3samples_flash_se. News Layout-programfilerna finns i mappen Samples/NewsLayout. Programmet består av följande filer:

Fil

Beskrivning

NewsLayout.mxml

eller

NewsLayout.fla

Användargränssnittet för programmet för Flex (MXML) eller Flash (FLA).

com/example/programmingas3/newslayout/StoryLayoutComponent.as

En Flex UIComponent-klass som placerar StoryLayout-instansen.

com/example/programmingas3/newslayout/StoryLayout.as

ActionScript-huvudklassen som arrangerar alla komponenter i en tidningsartikel för visning.

com/example/programmingas3/newslayout/FormattedTextField.as

En underklass av klassen TextField som hanterar sitt eget TextFormat-objekt.

com/example/programmingas3/newslayout/HeadlineTextField.as

En underklass av klassen FormattedTextField som justerar teckensnittsstorlekarna efter önskad bredd.

com/example/programmingas3/newslayout/MultiColumnTextField.as

En ActionScript-klass som fördelar text på två eller fler kolumner.

story.css

En CSS-fil som definierar layoutens textstilar.

Läsa den externa CSS-filen

News Layout-programmet börjar med att läsa artikeltexten från en lokal XML-fil. Sedan läser det en extern CSS-fil som innehåller formateringsinformation för rubriken, underrubriken och brödtexten.

I CSS-filen definieras tre stilar, ett vanligt styckeformat för artikeln samt stilarna h1 och h2 för rubriken respektive underrubriken.

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

Tekniken som används för att läsa den externa CSS-filen är densamma som tekniken som beskrivs i Läsa in en extern CSS-fil. Programmet kör metoden onCSSFileLoaded() när CSS-filen har lästs in, så som visas nedan.

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

Metoden onCSSFileLoaded() skapar ett StyleSheet-objekt och tolkar data från CSS-filen. Brödtexten i artikeln visas i ett MultiColumnTextField-objekt som kan använda ett StyleSheet-objekt direkt. Rubrikfälten använder klassen HeadlineTextField som använder ett TextFormat-objekt för formateringen.

Metoden onCSSFileLoaded()anropar metoden getTextStyle() två gånger för att konvertera en CSS-formatdeklaration till ett TextFormat-objekt som kan användas för de två HeadlineTextField-objekten.

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

Egenskapsnamnen och egenskapsvärdenas betydelse skiljer sig mellan CSS-formatdeklarationer och TextFormat-objekt. Metoden getTextStyle() översätter CSS-egenskapsvärden till värden som förväntas av TextFormat-objektet.

Arrangera artikelelementen på sidan

Klassen StoryLayout formaterar och arrangerar rubriken, underrubriken och brödtextfälten i en tidningslayout. Metoden displayText() skapar och placerar de olika fälten inledningsvis.

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

Varje fält placeras under det föregående fältet genom att dess y-egenskap ställs in på samma y-egenskap som det föregående fältet plus dess höjd. Denna dynamiska beräkning av placeringen krävs eftersom objekten HeadlineTextField och MultiColumnTextField kan ändra höjd beroende på innehållet.

Ändra teckensnittsstorleken så att den passar fältstorleken

HeadlineTextField ändrar teckensnittets storlek så att texten passar den bredd i pixlar och det maximala antalet rader som angetts för fältet. Om texten är kort blir teckenstorleken stor, vilket skapar en rubrik i tabloidstil. Om texten är lång blir teckensnittsstorleken mindre.

Metoden HeadlineTextField.fitText() som visas nedan ändrar teckensnittsstorleken:

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

Metoden HeadlineTextField.fitText() använder en enkel rekursiv teknik när teckensnittets storlek ändras. Först uppskattas det genomsnittliga antalet pixlar per tecken i texten och sedan beräknas en inledande punktstorlek baserat på detta. Därefter ändras teckensnittsstorleken och metoden kontrollerar om texten har automatisk radbrytning som skapar mer än det maximala antalet textrader. Metoden shrinkText() anropas för att minska teckensnittsstorleken och försöka igen om det finns för många rader. Metoden growText() anropas för att öka teckensnittsstorleken och försöka igen om det inte finns för många rader. Processen avbryts när för många rader skapas om teckensnittsstorleken ökas med en punkt.

Fördela text över flera kolumner

Klassen MultiColumnTextField fördelar text över flera TextField-objekt som sedan ordnas som tidningskolumner.

Konstruktorn MultiColumnTextField() skapar först en array av TextField-objekt, ett för varje kolumn, så som visas här:

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

Varje TextField-objekt läggs till i arrayen och i visningslistan med metoden addChild().

När egenskapen StoryLayout text eller styleSheetändras, anropas metoden layoutColumns() för att visa texten på nytt. Metoden layoutColumns() anropar metoden getOptimalHeight() som beräknar pixelhöjden som krävs för att få plats med all texten inom den angivna bredden.

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

Metoden getOptimalHeight() beräknar först bredden på varje kolumn. Sedan ställs bredden och egenskapen htmlText in för det första TextField-objektet i arrayen. Metoden getOptimalHeight() använder det första TextField-objektet för att identifiera det totala antalet rader med automatisk radbrytning i texten och identifierar med hjälp av detta hur många rader varje kolumn ska ha. Sedan anropas metoden TextField.getLineMetrics() som hämtar ett TextLineMetrics-objekt som innehåller information om storleken på texten på den första raden. Egenskapen TextLineMetrics.height representerar den totala höjden för en textrad i pixlar, inklusive nedstapel, uppstapel och radavstånd. Den optimala höjden för MultiColumnTextField-objektet är radhöjden multiplicerad med antalet rader per kolumn plus 4 för kanten på två pixlar längst upp och längst ned på ett TextField-objekt.

Det här är koden för den fullständiga layoutColumns()-metoden:

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

När egenskapen preferredHeight har ställts in genom att metoden getOptimalHeight() anropas, går metoden layoutColumns() igenom TextField-objekten och ställer in höjden för varje objekt på värdet preferredHeight. Metoden layoutColumns() distribuerar sedan tillräckligt med textrader till varje fält så att de enskilda fälten inte rullar och texten i nästa fält börjar där texten i det föregående fältet tar slut. Om textjustering har ställts in på ”justera” anropas metoden justifyLastLine() för att justera den sista textraden i ett fält. Annars behandlas den sista raden som ett styckeslut och justeras inte.