Source code: com/anotherbigidea/flash/movie/Font.java
1 /****************************************************************
2 * Copyright (c) 2001, David N. Main, All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or
5 * without modification, are permitted provided that the
6 * following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
10 * disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following
14 * disclaimer in the documentation and/or other materials
15 * provided with the distribution.
16 *
17 * 3. The name of the author may not be used to endorse or
18 * promote products derived from this software without specific
19 * prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
22 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
23 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
31 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
32 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 ****************************************************************/
34 package com.anotherbigidea.flash.movie;
35
36 import java.io.IOException;
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.Iterator;
40 import java.util.List;
41
42 import com.anotherbigidea.flash.SWFConstants;
43 import com.anotherbigidea.flash.interfaces.SWFTagTypes;
44 import com.anotherbigidea.flash.interfaces.SWFVectors;
45 import com.anotherbigidea.flash.structs.Rect;
46
47 /**
48 * A Font Symbol.
49 *
50 * The Font references a FontDefinition object from which it takes the glyph
51 * definitions it needs.
52 */
53 public class Font extends Symbol
54 {
55 public class NoGlyphException extends Exception
56 {
57 public int code;
58
59 public NoGlyphException( int code )
60 {
61 super( "The font does not have a glyph definition for code " + code );
62 this.code = code;
63 }
64 }
65
66 /**
67 * A set of contiguous characters in one font.
68 */
69 public class Chars
70 {
71 protected String chars;
72 protected double size;
73 protected int[] indices;
74 protected int[] advances;
75
76 protected double totalAdvance; //total advance
77 protected double ascent;
78 protected double descent;
79 protected double leftMargin;
80 protected double rightMargin;
81
82 public String toString() { return chars; }
83 public Font getFont() { return Font.this; }
84 public double getSize() { return size; }
85 public double getTotalAdvance() { return totalAdvance; }
86 public double getAscent() { return ascent; }
87 public double getDescent() { return descent; }
88
89 /**
90 * The left margin is the difference between the origin of the
91 * first glyph and the left edge of its geometry
92 */
93 public double getLeftMargin() { return leftMargin; }
94
95 /**
96 * The right margin is the different between the total advance and
97 * the right edge of the geometry of the last glyph
98 */
99 public double getRightMargin() { return rightMargin; }
100
101 /**
102 * @param chars the characters to display (displayable chars only - i.e. no newlines, tabs etc..)
103 * @param font may be null if no change of font is required
104 * @param size point-size - only relevant if font is not null
105 * @param color may be null if no color change is required. May be AlphaColor.
106 * @param x new X position for text - only valid if hasX is true
107 * @param y new Y position for text - only valid if hasY is true
108 */
109 protected Chars( String chars, double size ) throws NoGlyphException
110 {
111 this.chars = chars;
112 this.size = size;
113 init();
114 }
115
116 protected void init() throws NoGlyphException
117 {
118 //--Figure out the indices and advances
119 char[] codes = chars.toCharArray();
120 indices = new int[ codes.length ];
121 advances = new int[ codes.length ];
122
123 double maxAscent = 0.0;
124 double maxDescent = 0.0;
125
126 double scale = size * SWFConstants.TWIPS / 1024.0;
127
128 for( int i = 0; i < codes.length; i++ )
129 {
130 int code = (int)codes[i];
131
132 int[] index = new int[1];
133 FontDefinition.Glyph glyph = getGlyph( code, index );
134
135 indices[i] = index[0];
136
137 if( glyph != null )
138 {
139 Shape shape = glyph.getShape();
140
141 double[] outline = shape.getBoundingRectangle();
142 double x1 = outline[0] * scale;
143 double y1 = outline[1] * scale;
144 double x2 = outline[2] * scale;
145 double y2 = outline[3] * scale;
146
147 if( maxAscent < -y1 ) maxAscent = -y1;
148 if( maxDescent < y2 ) maxDescent = y2;
149
150 double advance = glyph.getAdvance() * scale;
151 if( advance == 0 ) advance = x2 - x1;
152
153 //Kerning adjustment
154 if( i < codes.length-1 )
155 {
156 advance += (fontDef.getKerningOffset( code, (int)codes[i+1] ) * scale );
157 }
158
159 totalAdvance += advance;
160
161 advances[i] = (int)(advance * SWFConstants.TWIPS);
162
163 if( i == 0 ) leftMargin = -y1;
164 if( i == codes.length - 1 ) rightMargin = x2 - advance;
165 }
166 }
167
168 ascent = fontDef.getAscent() * scale;
169 if( ascent == 0.0 ) ascent = maxAscent;
170
171 descent = fontDef.getDescent() * scale;
172 if( descent == 0.0 ) descent = maxDescent;
173 }
174 }
175
176 protected Object font1Key = new Object(); //used in movie defined symbols lookup
177 protected Object font2Key = new Object(); //used in movie defined symbols lookup
178
179 protected FontDefinition fontDef;
180 protected HashMap glyphs = new HashMap(); //glyphs used by this font
181 protected HashMap indices = new HashMap(); //glyph indices
182 protected ArrayList glyphList = new ArrayList();
183 protected int languageCode = SWFConstants.LANGUAGE_CODE_NONE;
184
185 public int getLanguageCode() { return languageCode; }
186 public void setLanguageCode( int code ) { languageCode = code; }
187
188 public FontDefinition getDefinition() { return fontDef; }
189
190 public Font( FontDefinition fontDef )
191 {
192 this.fontDef = fontDef;
193 }
194
195 public List getGlyphList() { return glyphList; }
196
197 /**
198 * Load the glyphs for the characters in the given string from the
199 * FontDefinition into this font.
200 * @exception NoGlyphException
201 */
202 public void loadGlyphs( String chars ) throws NoGlyphException
203 {
204 char[] chs = chars.toCharArray();
205 for( int i = 0; i < chs.length; i++ )
206 {
207 getGlyph( chs[i], null );
208 }
209 }
210
211 /**
212 * Load all glyphs from the font definition
213 */
214 public void loadAllGlyphs()
215 {
216 ArrayList list = fontDef.getGlyphList();
217
218 for( Iterator it = list.iterator(); it.hasNext(); )
219 {
220 FontDefinition.Glyph g = (FontDefinition.Glyph)it.next();
221
222 addGlyph( g );
223 }
224 }
225
226 /**
227 * Get the Chars instance for the given string at the given font size
228 */
229 public Chars chars( String chars, double fontSize )
230 throws NoGlyphException
231 {
232 return new Chars( chars, fontSize );
233 }
234
235 protected FontDefinition.Glyph getGlyph( int code, int[] index )
236 throws NoGlyphException
237 {
238 Integer codeI = new Integer(code);
239 FontDefinition.Glyph g = (FontDefinition.Glyph)glyphs.get( codeI );
240
241 if( g != null )
242 {
243 if( index != null )
244 {
245 Integer idx = (Integer)indices.get( codeI );
246 index[0] = idx.intValue();
247 }
248
249 return g;
250 }
251
252 g = fontDef.getGlyph( code );
253
254 if( g == null ) throw new NoGlyphException( code );
255
256 int idx = addGlyph( g );
257 if( index != null ) index[0] = idx;
258
259 return g;
260 }
261
262 /**
263 * Add a glyph and return the index
264 */
265 public int addGlyph( FontDefinition.Glyph glyph )
266 {
267 int idx = glyphs.size();
268
269 if( glyph.getCode() > 0 )
270 {
271 Integer codeI = new Integer( glyph.getCode() );
272 indices.put( codeI, new Integer( idx ));
273 glyphs.put( codeI, glyph );
274 }
275
276 glyphList.add( glyph );
277
278 return idx;
279 }
280
281 /**
282 * Set the code for the glyph at the given index
283 */
284 public void setCode( int index, int code )
285 {
286 if( index >= glyphList.size() ) return;
287
288 FontDefinition.Glyph g = (FontDefinition.Glyph)glyphList.get( index );
289 g.setCode( code );
290
291 Integer codeI = new Integer( code );
292 indices.put( codeI, new Integer( index ));
293 glyphs.put( codeI, g );
294 }
295
296 protected int define( boolean textFont, Movie movie, SWFTagTypes tagwriter )
297 throws IOException
298 {
299 Integer integerId = textFont ?
300 (Integer)movie.definedSymbols.get( font1Key ) :
301 (Integer)movie.definedSymbols.get( font2Key );
302
303 if( integerId == null )
304 {
305 if( textFont )
306 {
307 integerId = new Integer( defineFont1( movie, tagwriter ) );
308 movie.definedSymbols.put( font1Key, integerId );
309 }
310 else
311 {
312 integerId = new Integer( defineFont2( movie, tagwriter ) );
313 movie.definedSymbols.put( font2Key, integerId );
314 }
315 }
316
317 id = integerId.intValue();
318 return id;
319 }
320
321 protected int defineFont1( Movie movie, SWFTagTypes tagwriter )
322 throws IOException
323 {
324 int id = getNextId( movie );
325
326 SWFVectors vecs = tagwriter.tagDefineFont( id, glyphList.size() );
327
328 for( Iterator it = glyphList.iterator(); it.hasNext(); )
329 {
330 FontDefinition.Glyph g = (FontDefinition.Glyph)it.next();
331
332 Shape s = g.getShape();
333
334 s.writeGlyph( vecs );
335 }
336
337 if( fontDef.getName() != null )
338 {
339 int flags = 0;
340
341 if( fontDef.isUnicode() ) flags |= SWFConstants.FONT_UNICODE;
342 if( fontDef.isShiftJIS() ) flags |= SWFConstants.FONT_SHIFTJIS;
343 if( fontDef.isAnsi() ) flags |= SWFConstants.FONT_ANSI;
344 if( fontDef.isItalic() ) flags |= SWFConstants.FONT_ITALIC;
345 if( fontDef.isBold() ) flags |= SWFConstants.FONT_BOLD;
346
347 //write fontInfo2 for Flash MX+, if needed
348 if( movie.getVersion() >= SWFConstants.FLASH_MX_VERSION
349 && languageCode != SWFConstants.LANGUAGE_CODE_NONE ) {
350 tagwriter.tagDefineFontInfo2( id, fontDef.getName(), flags, getCodes(), languageCode );
351 } else {
352 tagwriter.tagDefineFontInfo( id, fontDef.getName(), flags, getCodes() );
353 }
354 }
355
356 return id;
357 }
358
359 protected int defineFont2( Movie movie, SWFTagTypes tagwriter )
360 throws IOException
361 {
362 int id = getNextId( movie );
363
364 int glyphCount = glyphList.size();
365 int[] codes = new int [ glyphCount ];
366 Rect[] bounds = new Rect[ glyphCount ];
367 int[] advances = new int [ glyphCount ];
368
369 //--Gather glyph info
370 int i = 0;
371 for( Iterator it = glyphList.iterator(); it.hasNext(); )
372 {
373 FontDefinition.Glyph g = (FontDefinition.Glyph)it.next();
374
375 codes[i] = g.getCode();
376 advances[i] = (int)(g.getAdvance() * SWFConstants.TWIPS);
377
378 double[] bound = g.getShape().getBoundingRectangle();
379
380 bounds[i] = new Rect( (int)(bound[0] * SWFConstants.TWIPS),
381 (int)(bound[1] * SWFConstants.TWIPS),
382 (int)(bound[2] * SWFConstants.TWIPS),
383 (int)(bound[3] * SWFConstants.TWIPS) );
384
385 i++;
386 }
387
388 //--Gather kerning info
389 ArrayList kerns = fontDef.getKerningPairList();
390 int kernCount = kerns.size();
391 int[] kern1 = new int[ kernCount ];
392 int[] kern2 = new int[ kernCount ];
393 int[] kernOff = new int[ kernCount ];
394
395 i = 0;
396 for( Iterator it = kerns.iterator(); it.hasNext(); )
397 {
398 FontDefinition.KerningPair pair = (FontDefinition.KerningPair)it.next();
399
400 kern1[i] = pair.getCode1();
401 kern2[i] = pair.getCode2();
402 kernOff[i] = (int)(pair.getAdjustment() * SWFConstants.TWIPS );
403
404 i++;
405 }
406
407 int flags = 0;
408 if( fontDef.hasMetrics() ) flags |= SWFConstants.FONT2_HAS_LAYOUT;
409 if( fontDef.isShiftJIS() ) flags |= SWFConstants.FONT2_SHIFTJIS;
410 if( fontDef.isUnicode() ) flags |= SWFConstants.FONT2_UNICODE;
411 if( fontDef.isAnsi() ) flags |= SWFConstants.FONT2_ANSI;
412 if( fontDef.isItalic() ) flags |= SWFConstants.FONT2_ITALIC;
413 if( fontDef.isBold() ) flags |= SWFConstants.FONT2_BOLD;
414
415 SWFVectors vecs = tagwriter.tagDefineFont2(
416 id, flags, fontDef.getName(), glyphCount,
417 (int)(fontDef.getAscent() * SWFConstants.TWIPS),
418 (int)(fontDef.getDescent() * SWFConstants.TWIPS),
419 (int)(fontDef.getLeading() * SWFConstants.TWIPS),
420 codes, advances, bounds, kern1, kern2, kernOff );
421
422 for( Iterator it = glyphList.iterator(); it.hasNext(); )
423 {
424 FontDefinition.Glyph g = (FontDefinition.Glyph)it.next();
425
426 Shape s = g.getShape();
427
428 s.writeGlyph( vecs );
429 }
430
431 return id;
432 }
433
434 /**
435 * Get the codes of the current set of glyphs
436 */
437 protected int[] getCodes()
438 {
439 int[] codes = new int[glyphList.size()];
440
441 for( int i = 0; i < codes.length; i++ )
442 {
443 FontDefinition.Glyph g = (FontDefinition.Glyph)glyphList.get(i);
444 codes[i] = g.getCode();
445 }
446
447 return codes;
448 }
449
450 protected int defineSymbol( Movie movie,
451 SWFTagTypes timelineWriter,
452 SWFTagTypes definitionwriter ) throws IOException
453 {
454 return id;
455 }
456 }