KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openlaszlo > iv > flash > util > TextLayout


1 /*
2  * $Id: TextLayout.java,v 1.5 2002/07/15 02:15:03 skavish Exp $
3  *
4  * ===========================================================================
5  *
6  * The JGenerator Software License, Version 1.0
7  *
8  * Copyright (c) 2000 Dmitry Skavish (skavish@usa.net). All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if
22  * any, must include the following acknowlegement:
23  * "This product includes software developed by Dmitry Skavish
24  * (skavish@usa.net, http://www.flashgap.com/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The name "The JGenerator" must not be used to endorse or promote
29  * products derived from this software without prior written permission.
30  * For written permission, please contact skavish@usa.net.
31  *
32  * 5. Products derived from this software may not be called "The JGenerator"
33  * nor may "The JGenerator" appear in their names without prior written
34  * permission of Dmitry Skavish.
35  *
36  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39  * DISCLAIMED. IN NO EVENT SHALL DMITRY SKAVISH OR THE OTHER
40  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47  * SUCH DAMAGE.
48  *
49  */

50
51 package org.openlaszlo.iv.flash.util;
52
53 import java.io.*;
54 import java.awt.geom.*;
55
56 import org.openlaszlo.iv.flash.api.*;
57 import org.openlaszlo.iv.flash.api.text.*;
58
59 /**
60  * This class does simple text layout in a box.
61  * <P>
62  *
63  * @author Dmitry Skavish
64  * @see org.openlaszlo.iv.flash.api.text.Text
65  * @see org.openlaszlo.iv.flash.api.text.Font
66  * @see org.openlaszlo.iv.flash.api.text.FontDef
67  * @see org.openlaszlo.iv.flash.api.text.TextRecord
68  * @see org.openlaszlo.iv.flash.api.text.TextStyleChangeRecord
69  */

70 public final class TextLayout {
71
72     private static final int MAX_CHARS_IN_LINE = 250;
73
74     /**
75      * One line of text.
76      * <p>
77      * Contains several style runs (text of different font and style)
78      */

79     public final class TextLine extends FlashItem {
80
81         /**
82          * Vector of TextRecords and TextStyleChangeRecords
83          * <P>
84          * TextStyleChangeRecords are at even positions,
85          * TextRecords are at odd positions.
86          */

87         public IVVector records = new IVVector(4);
88
89         private TextItem lastItem;
90         private TextRecord lastRecord;
91
92         public TextLine() {}
93
94         /**
95          * Creates new text record.
96          */

97         public void newRecord() {
98             flushLastRecord();
99             lastItem = curItem;
100             lastRecord = new TextRecord( MAX_CHARS_IN_LINE );
101         }
102
103         private void flushLastRecord() {
104             if( lastRecord != null && lastRecord.getSize() != 0 ) {
105                 int size = records.size();
106                 if( size%2 == 0 ) { // make sure we add stylechange only after textrecord
107
records.addElement( getStyleChange( lastItem ) );
108                 }
109                 records.addElement( lastRecord );
110                 lastRecord = null;
111             }
112         }
113
114         /**
115          * Creates text style change record from specified text item
116          * and sets all the properties, like font, color and height as
117          * current properties.
118          *
119          * @param item item for which to create style change record
120          * @return new style change record
121          */

122         private TextStyleChangeRecord getStyleChange( TextItem item ) {
123             // create text style change record
124
TextStyleChangeRecord ts = new TextStyleChangeRecord();
125
126             if( item != null ) {
127                 ts.setFont( item.font );
128                 ts.setHeight( item.height );
129                 ts.setColor( item.color );
130             }
131
132             return ts;
133         }
134
135         /**
136          * Adds new character to last text record.
137          * <P>
138          * If last text record is overflowed then creates new text record
139          *
140          * @param ch character to be added
141          * @param index index of character in the font
142          * @param adv advance value of the character
143          */

144         public void add( char ch, int index, int adv ) {
145             if( lastRecord.getSize() >= MAX_CHARS_IN_LINE ) {
146                 newRecord();
147             }
148             lastRecord.add( ch, index, adv );
149         }
150
151         /**
152          * Trims this line from the end
153          * <P>
154          * Removes all spaces from the end
155          *
156          * @return width in twixels of all removed spaces
157          */

158         public int trimEnd() {
159             flushLastRecord();
160
161             int w = 0;
162
163             for( int i=records.size(); --i>0; ) {
164                 TextRecord tr = (TextRecord) records.elementAt(i);
165                 w += tr.trimEnd();
166                 if( tr.getSize() > 0 ) break;
167                 records.removeElementAt(i); // remove text record
168
records.removeElementAt(--i); // remove stylechange record
169
}
170
171             return w;
172         }
173
174         /**
175          * Trims this line from the start
176          * <P>
177          * Removes all spaces from the start
178          *
179          * @return width in twixels of all removed spaces
180          */

181         public int trimStart() {
182             flushLastRecord();
183
184             int w = 0;
185
186             TextStyleChangeRecord lastts = null;
187
188             for(;records.size()>0;) {
189                 TextStyleChangeRecord ts = (TextStyleChangeRecord) records.elementAt(0);
190                 if( lastts != null ) {
191                     lastts.mergeTo(ts);
192                 }
193                 lastts = ts;
194                 TextRecord tr = (TextRecord) records.elementAt(1);
195                 w += tr.trimStart();
196                 if( tr.getSize() > 0 ) break;
197                 records.removeElementAt(1); // remove text record
198
records.removeElementAt(0); // remove stylechange record
199
}
200
201             return w;
202         }
203
204         /**
205          * Mark current position in current text record.
206          * <P>
207          * Used for rolling back if word does not fit in the text rectangle.
208          */

209         public void markPosition() {
210             markedPosition = lastRecord.getSize();
211         }
212
213         /**
214          * Rollbacks current position to the marked one.
215          */

216         public void rollBack() {
217             lastRecord.setSize( markedPosition );
218         }
219
220         /**
221          * Does end-of-line.
222          * <P>
223          * Flushes current text record
224          *
225          * @param x x position of this line
226          * @param y y position of this line
227          */

228         public void endLine( int x, int y ) {
229             flushLastRecord();
230             if( records.size() > 0 ) {
231                 TextStyleChangeRecord ts = (TextStyleChangeRecord) records.elementAt(0);
232                 ts.setX(x);
233                 ts.setY(y);
234             }
235         }
236
237         /**
238          * Returns maximum advance value from all the records.
239          *
240          * @return maximum advance value from all the records
241          */

242         public int getMaxAdvance() {
243             int max = Integer.MIN_VALUE;
244             // iterate over TextRecords only
245
for( int i=1; i<records.size(); i+=2 ) {
246                 TextRecord tr = (TextRecord) records.elementAt(i);
247                 int adv = tr.getMaxAdvance();
248                 if( adv > max ) max = adv;
249             }
250             if( max == Integer.MIN_VALUE ) return 0;
251             return max;
252         }
253
254         /**
255          * Returns maximum character index from all the records.
256          *
257          * @return maximum character index from all the records
258          */

259         public int getMaxIndex() {
260             int max = Integer.MIN_VALUE;
261             // iterate over TextRecords only
262
for( int i=1; i<records.size(); i+=2 ) {
263                 TextRecord tr = (TextRecord) records.elementAt(i);
264                 int idx = tr.getMaxIndex();
265                 if( idx > max ) max = idx;
266             }
267             if( max == Integer.MIN_VALUE ) return 0;
268             return max;
269         }
270
271         /**
272          * Returns width of this line.
273          *
274          * @return width if this line in twixels
275          */

276         public int getWidth() {
277             if( records.size() == 0 ) return 0;
278             TextStyleChangeRecord ts = (TextStyleChangeRecord) records.elementAt(0);
279             int width = ts.getX();
280             for( int i=1; i<records.size(); i+=2 ) {
281                 TextRecord tr = (TextRecord) records.elementAt(i);
282                 width += tr.getWidth();
283             }
284             return width;
285         }
286
287         /**
288          * Return number of characters in this line
289          *
290          * @return number of characters in this line
291          */

292         public int getSize() {
293             int size = 0;
294             // iterate over TextRecords only
295
for( int i=1; i<records.size(); i+=2 ) {
296                 TextRecord tr = (TextRecord) records.elementAt(i);
297                 size += tr.getSize();
298             }
299             if( lastRecord != null ) size += lastRecord.getSize();
300             return size;
301         }
302
303         public void printContent( PrintStream out, String JavaDoc indent ) {
304             out.println( indent+"TextLine: size="+records.size()+" x="+x+" y="+y );
305             records.printContent(out, indent+" ");
306         }
307
308         public void write( FlashOutput fob ) {
309             records.write(fob);
310         }
311
312         protected FlashItem copyInto( FlashItem item, ScriptCopier copier ) {
313             ((TextLine)item).records = records.getCopy(copier);
314             // there is no need to copy lastItem and lastRecord (?)
315
return item;
316         }
317
318         public FlashItem getCopy( ScriptCopier copier ) {
319             return copyInto( new TextLine(), copier );
320         }
321     }
322
323     private int markedPosition = 0; // used only by TextLine
324

325     private TextItem curItem;
326     private TextLine curLine;
327     private boolean line_continued;
328     private int line_width;
329     private int line_window;
330     private int x;
331     private int y;
332     private int max_ascent;
333     private int max_descent;
334     private int max_linesp;
335
336     private int rect_width;
337     private Text myText;
338     private Rectangle2D bounds;
339     private IVVector lines = new IVVector();
340
341     private void reCalcHeights() {
342         Font font = curItem.font;
343         int height = curItem.height;
344         max_ascent = (font.ascent * height) / 1024;
345         max_linesp = curItem.linesp;
346         max_descent = (font.descent * height) / 1024 + max_linesp;
347     }
348
349     private void endLine() {
350         if( curLine != null ) {
351             y += max_ascent; // advance y to baseline
352
curLine.endLine(x, y);
353             y += max_descent; // advance y to next line
354
curLine = null;
355         }
356     }
357
358     /**
359      *
360      * @param cont true if this new line is continued from previous, this affects
361      * alignment of this line
362      */

363     private void newLine( boolean cont ) {
364         endLine();
365         reCalcHeights();
366         curLine = new TextLine();
367         line_continued = cont;
368         lines.addElement( curLine );
369         curLine.newRecord();
370         line_width = 0;
371         x = curItem.marginleft;
372         line_window = rect_width-(curItem.marginleft+curItem.marginright);
373     }
374
375     private void newParagraph() {
376         newLine( false );
377         line_window -= curItem.indent;
378         x += curItem.indent;
379     }
380
381     /**
382      * Creates new text layout.
383      *
384      * @param myText text to be layed out
385      * @param bounds rectangle to be used for laying the text out in
386      */

387     public TextLayout( Text myText, Rectangle2D bounds ) {
388         this.myText = myText;
389         this.bounds = bounds;
390     }
391
392     /**
393      * Retrieves vector of text records layed out by this
394      * text layout for the specified font.
395      *
396      * @param font font of the records to be retrieved
397      * @return vector of {@link org.openlaszlo.iv.flash.api.text.TextRecord}s
398      */

399     public IVVector getTextRecords( Font font ) {
400         IVVector trs = new IVVector();
401         Font lastFont = null;
402         for( int i=0; i<lines.size(); i++ ) {
403             TextLine line = (TextLine) lines.elementAt(i);
404             for( int k=0; k<line.records.size(); k++ ) {
405                 Object JavaDoc o = line.records.elementAt(k);
406                 if( o instanceof TextStyleChangeRecord ) {
407                     Font f = ((TextStyleChangeRecord)o).getFont();
408                     if( f != null ) lastFont = f;
409                 } else {
410                     if( lastFont == font ) trs.addElement(o);
411                 }
412             }
413         }
414         return trs;
415     }
416
417     /**
418      * Retrieves vector of all text records
419      *
420      * @return vector of {@link org.openlaszlo.iv.flash.api.text.TextRecord}s and {@link org.openlaszlo.iv.flash.api.text.TextStyleChangeRecord}s
421      */

422     public IVVector getAllTextRecords() {
423         IVVector trs = new IVVector();
424         for( int i=0; i<lines.size(); i++ ) {
425             TextLine line = (TextLine) lines.elementAt(i);
426             for( int k=0; k<line.records.size(); k++ ) {
427                 trs.addElement( line.records.elementAt(k) );
428             }
429         }
430         return trs;
431     }
432
433     /**
434      * Updates records' font.
435      * <P>
436      * Changes one specified font into another in all records.
437      * In text records also updates indexes.
438      *
439      * @param old_font old font
440      * @param new_font new font
441      */

442     public void changeFont( Font old_font, Font new_font ) {
443         for( int i=0; i<lines.size(); i++ ) {
444             TextLine line = (TextLine) lines.elementAt(i);
445             IVVector records = line.records;
446             FontDef.changeRecordsFont(records, old_font, new_font);
447         }
448     }
449
450     /**
451      * Aligns current line
452      */

453     private void alignLine() {
454         switch( curItem.align ) {
455             case 0: // left
456
if( curLine != null /*&& line_continued*/ ) {
457                     line_width -= curLine.trimStart();
458                 }
459                 break;
460             case 1: // right
461
if( curLine != null ) {
462                     line_width -= curLine.trimEnd();
463                 }
464                 x += line_window-line_width;
465                 break;
466             case 2: // center
467
if( curLine != null ) {
468                     line_width -= curLine.trimEnd() + curLine.trimStart();
469                 }
470                 x += (line_window-line_width)/2;
471                 break;
472             case 3: // justify
473
// ........
474
break;
475         }
476     }
477
478     /**
479      * Removes whitespace at the end of the text
480      * <P>
481      * Text is specified by vector of TextItems
482      *
483      * @param items vector of TextItems
484      * @return
485      */

486     private void trimEnd( IVVector items ) {
487         for( int i=items.size(); --i>=0; ) {
488             TextItem item = (TextItem) items.elementAt(i);
489             String JavaDoc t = item.text;
490             int j=t.length();
491             for( ; --j>=0; ) {
492                 char ch = t.charAt(j);
493                 if( !Character.isWhitespace(ch) ) break;
494             }
495             if( j >= 0 ) {
496                 item.text = t.substring(0, j+1);
497                 return;
498             } else {
499                 items.removeElementAt(i);
500             }
501         }
502     }
503
504     /**
505      * Does text layout.
506      */

507     public void layout() {
508         IVVector items = myText.getTextItems();
509         trimEnd(items);
510 // System.out.println( "layout of '"+((TextItem)items.elementAt(0)).text+"'" );
511

512         rect_width = (int) bounds.getWidth();
513
514         y = 0;
515
516         if( items.size() > 0 ) {
517             curItem = (TextItem) items.elementAt(0);
518
519             newParagraph();
520
521             int i=0;
522             for(;;) {
523                 Font font = curItem.font;
524                 int height = curItem.height;
525                 int ascent = (font.ascent * height) / 1024;
526                 int descent = (font.descent * height) / 1024 + curItem.linesp;
527                 if( ascent > max_ascent ) max_ascent = ascent;
528                 if( descent > max_descent ) {
529                     max_descent = descent;
530                     max_linesp = curItem.linesp;
531                 }
532
533                 String JavaDoc text = curItem.text;
534                 boolean isNowWord = false;
535                 int word_width = 0;
536                 int start_word = 0;
537
538                 int text_len = text.length();
539                 for( int k=0; k<text_len; k++ ) {
540                     char ch = text.charAt(k);
541                     boolean isWord = isWord(ch);
542                     if( !isNowWord && isWord ) { // start new word
543
curLine.markPosition();
544                         word_width = 0;
545                         start_word = k;
546                         isNowWord = true;
547                     } else {
548                         isNowWord = isWord;
549                     }
550                     if( ch == '\r' || ch == '\n' ) {
551                         alignLine();
552                         if( k == text_len-1 ) endLine();
553                         else {
554                             char ch1 = text.charAt(k+1);
555                             if( ch != ch1 && (ch1 == '\r' || ch1 == '\n') ) k++;
556                             if( k == text_len-1 ) endLine();
557                             else newParagraph();
558                         }
559                     } else {
560                         int idx = font.getIndex(ch);
561                         int ch_adv = font.getAdvanceValue(idx);
562                         if( k != text_len-1 ) ch_adv += font.getKerning(ch, text.charAt(k+1));
563                         int adv = (ch_adv * height) / 1024;
564                         adv += curItem.kerning;
565                         line_width += adv;
566                         word_width += adv;
567                         if( line_width <= line_window ) {
568                             curLine.add(ch,idx,adv);
569                         } else {
570                             if( curLine.getSize() == 0 ) { // even one character does not fit
571
// if even one character does not fit the window, then expand the window
572
line_window = line_width;
573                                 curLine.add(ch,idx,adv);
574                                 alignLine();
575                                 newLine( true );
576                                 isNowWord = false;
577                             } else if( isNowWord ) {
578                                 if( word_width > line_window ) {
579                                     // split anyway, because the word does not fit the window at all
580
line_width -= adv;
581                                     k--;
582                                     alignLine();
583                                     newLine( true );
584                                 } else {
585                                     // rollback the word and send it to the next line
586
line_width -= word_width;
587                                     curLine.rollBack();
588                                     k = start_word-1;
589                                     alignLine();
590                                     newLine( true );
591                                     isNowWord = false;
592                                 }
593                             } else {
594                                 line_width -= adv;
595                                 k--;
596                                 alignLine();
597                                 newLine( true );
598                             }
599                         }
600                     }
601                 }
602
603                 i++;
604                 if( i >= items.size() ) break;
605                 curItem = (TextItem) items.elementAt(i);
606                 if( curLine == null ) newParagraph();
607                 else curLine.newRecord();
608             }
609             alignLine();
610             endLine();
611
612             y -= max_linesp;
613         }
614
615         // optimize text
616
optimize();
617
618         // calculate bounds
619
int maxX = 0;
620         for( int l=0; l<lines.size(); l++ ) {
621             TextLine line = (TextLine) lines.elementAt(l);
622             int max = line.getWidth();
623             if( max > maxX ) maxX = max;
624         }
625
626         // check what kind of behavior is expected: MMGen or JGen and
627
// create appropriate bounds
628
int bs = myText.getBoundsStyle();
629         boolean isMMStyle = bs == Text.PROPERTY_CONTROLLED?
630             PropertyManager.textMMStyle:
631             bs == Text.MM_STYLE;
632         if( isMMStyle ) {
633             // set bounds to be exactly equal to the text (MMGen style)
634
bounds.setFrame(bounds.getMinX(), bounds.getMinY(), maxX, y);
635         } else {
636             // set bounds to be equal whatever came from the template (JGen style)
637
double width = bounds.getWidth();
638             double height = bounds.getHeight();
639             if( maxX > width ) width = maxX;
640             if( y > height ) height = y;
641             bounds.setFrame(bounds.getMinX(), bounds.getMinY(), width, height);
642         }
643
644         myText.setBounds(bounds);
645     }
646
647     protected int getNGlyphBits() {
648         int maxIdx = 0;
649         for( int l=0; l<lines.size(); l++ ) {
650             TextLine line = (TextLine) lines.elementAt(l);
651             int max = line.getMaxIndex();
652             if( max > maxIdx ) maxIdx = max;
653         }
654
655         return Util.getMinBitsU(maxIdx);
656     }
657
658     protected int getNAdvanceBits() {
659         int maxAdv = 0;
660         for( int l=0; l<lines.size(); l++ ) {
661             TextLine line = (TextLine) lines.elementAt(l);
662             int max = Math.abs(line.getMaxAdvance());
663             if( max > maxAdv ) maxAdv = max;
664         }
665
666         return Util.getMinBitsS(maxAdv);
667     }
668
669     /**
670      * Removes unneccesary data from stylechangerecords
671      */

672     public void optimize() {
673         Font lastFont = null;
674         int lastHeight = -1;
675         Color lastColor = null;
676
677         for( int k=0; k<lines.size(); k++ ) {
678             TextLine line = (TextLine) lines.elementAt(k);
679             IVVector records = line.records;
680             for( int i=0; i<records.size(); i+=2 ) {
681                 TextStyleChangeRecord ts = (TextStyleChangeRecord) records.elementAt(i);
682                 if( lastFont != null && lastFont == ts.getFont() ) {
683                     if( lastHeight == ts.getHeight() ) {
684                         ts.setFont(null);
685                     } else {
686                         lastHeight = ts.getHeight();
687                     }
688                 } else {
689                     lastFont = ts.getFont();
690                     lastHeight = ts.getHeight();
691                 }
692                 if( lastColor != null && lastColor.equals(ts.getColor()) ) {
693                     ts.setColor(null);
694                 } else {
695                     lastColor = ts.getColor();
696                 }
697             }
698         }
699     }
700
701     public void write( FlashOutput fob ) {
702         int nGlyphBits = getNGlyphBits();
703         int nAdvanceBits = getNAdvanceBits();
704         fob.writeByte(nGlyphBits);
705         fob.writeByte(nAdvanceBits);
706         //System.out.println("Layout write: nGlyphBits="+nGlyphBits+", nAdvanceBits="+nAdvanceBits);
707
fob.setUserData( new int[] {nGlyphBits, nAdvanceBits} );
708
709         for( int i=0; i<lines.size(); i++ ) {
710             TextLine line = (TextLine) lines.elementAt(i);
711             line.write(fob);
712             //line.printContent(System.out, "Layout Write: ");
713
}
714         fob.writeByte(0);
715     }
716
717     public TextLayout getCopy( ScriptCopier copier ) {
718         TextLayout tl = new TextLayout(myText,bounds);
719         tl.lines = lines.getCopy(copier);
720         return tl;
721     }
722
723     private static boolean isWord( char ch ) {
724         //Text Flow by FX
725
//Wrap words at whitespace and '-' only!
726

727         if (ch == '-' )
728           return false;
729
730         return !Character.isWhitespace(ch);
731     }
732 }
733
Popular Tags