001: /*
002: * $Id: Text.java,v 1.5 2002/07/16 20:17:30 skavish Exp $
003: *
004: * ==========================================================================
005: *
006: * The JGenerator Software License, Version 1.0
007: *
008: * Copyright (c) 2000 Dmitry Skavish (skavish@usa.net). All rights reserved.
009: *
010: * Redistribution and use in source and binary forms, with or without
011: * modification, are permitted provided that the following conditions are met:
012: *
013: * 1. Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * 2. Redistributions in binary form must reproduce the above copyright
017: * notice, this list of conditions and the following disclaimer in
018: * the documentation and/or other materials provided with the
019: * distribution.
020: *
021: * 3. The end-user documentation included with the redistribution, if
022: * any, must include the following acknowlegement:
023: * "This product includes software developed by Dmitry Skavish
024: * (skavish@usa.net, http://www.flashgap.com/)."
025: * Alternately, this acknowlegement may appear in the software itself,
026: * if and wherever such third-party acknowlegements normally appear.
027: *
028: * 4. The name "The JGenerator" must not be used to endorse or promote
029: * products derived from this software without prior written permission.
030: * For written permission, please contact skavish@usa.net.
031: *
032: * 5. Products derived from this software may not be called "The JGenerator"
033: * nor may "The JGenerator" appear in their names without prior written
034: * permission of Dmitry Skavish.
035: *
036: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: * DISCLAIMED. IN NO EVENT SHALL DMITRY SKAVISH OR THE OTHER
040: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: *
049: */
050:
051: package org.openlaszlo.iv.flash.api.text;
052:
053: import java.io.*;
054: import java.util.*;
055: import java.awt.geom.*;
056:
057: import org.openlaszlo.iv.flash.parser.*;
058: import org.openlaszlo.iv.flash.util.*;
059:
060: import org.openlaszlo.iv.flash.api.*;
061: import org.openlaszlo.iv.flash.api.text.*;
062: import org.openlaszlo.iv.flash.context.Context;
063:
064: /**
065: * This class represent MM Generator undocumented text tag.
066: * <P>
067: * It contains vector of {@link TextItem}s.
068: *
069: * @author Dmitry Skavish
070: */
071: public final class Text extends FlashDef implements TextBlock {
072:
073: public static final int PROPERTY_CONTROLLED = 0;
074: public static final int MM_STYLE = 1;
075: public static final int JG_STYLE = 2;
076:
077: private boolean withAlpha; // true if color of change record has alpha
078: private Rectangle2D bounds; // original bounds of the text
079: private Rectangle2D genBounds; // bounds from MM Generator tag
080: private int boundsStyle = PROPERTY_CONTROLLED;// jgen or MM bounds style, by default controlled by property
081: private AffineTransform matrix; // matrix of the text
082: private IVVector items = new IVVector();// vector of TextItems
083: private TextLayout myLayout; // layout of this text
084:
085: public Text() {
086: }
087:
088: public Text(boolean withAlpha) {
089: this .withAlpha = withAlpha;
090: }
091:
092: public static Text newText() {
093: Text t = new Text(true);
094: t.matrix = new AffineTransform();
095: return t;
096: }
097:
098: public int getTag() {
099: if (withAlpha)
100: return Tag.DEFINETEXT2;
101: return Tag.DEFINETEXT;
102: }
103:
104: // ------------------------------------------------------- //
105: // TextBlock implementation //
106:
107: /**
108: * Layout this text
109: */
110: public void layout() {
111: if (myLayout != null)
112: return;
113: doLayout();
114: }
115:
116: /**
117: * Returns vector of {@link TextRecord}s from this text
118: * of specified font.
119: *
120: * @param font font of text records to be returned
121: * @return text records of specified font
122: */
123: public IVVector getTextRecords(Font font) {
124: layout();
125: return myLayout.getTextRecords(font);
126: }
127:
128: /**
129: * Returns vector of all {@link TextRecord}s and {@link TextStyleChangeRecord}s from this text
130: *
131: * @return all text records
132: */
133: public IVVector getAllTextRecords() {
134: layout();
135: return myLayout.getAllTextRecords();
136: }
137:
138: /**
139: * Updates records' font.
140: * <P>
141: * Changes one specified font into another in all records.
142: * In text records also updates indexes.
143: *
144: * @param old_font old font
145: * @param new_font new font
146: */
147: public void changeFont(Font old_font, Font new_font) {
148: layout();
149: myLayout.changeFont(old_font, new_font);
150: }
151:
152: // ------------------------------------------------------- //
153:
154: public void setBoundsStyle(int boundsStyle) {
155: this .boundsStyle = boundsStyle;
156: }
157:
158: public int getBoundsStyle() {
159: return boundsStyle;
160: }
161:
162: protected void doLayout() {
163: myLayout = new TextLayout(this , genBounds != null ? genBounds
164: : bounds);
165: myLayout.layout();
166: }
167:
168: public void collectDeps(DepsCollector dc) {
169: for (int i = 0; i < items.size(); i++) {
170: TextItem t = (TextItem) items.elementAt(i);
171: if (t.font != null)
172: dc.addDep(t.font);
173: }
174: }
175:
176: public void collectFonts(FontsCollector fc) {
177: for (int i = 0; i < items.size(); i++) {
178: TextItem t = (TextItem) items.elementAt(i);
179: if (t.font != null)
180: fc.addFont(t.font, this );
181: }
182: }
183:
184: public void apply(Context context) {
185: if (myLayout != null)
186: return;
187: super .apply(context);
188: for (int i = 0; i < items.size(); i++) {
189: TextItem t = (TextItem) items.elementAt(i);
190: t.apply(context);
191: }
192: // after applying context text bound could be changed, so we need to layout text
193: doLayout();
194: }
195:
196: public boolean isConstant() {
197: for (int i = 0; i < items.size(); i++) {
198: TextItem t = (TextItem) items.elementAt(i);
199: if (!t.isConstant())
200: return false;
201: }
202: return true;
203: }
204:
205: public Rectangle2D getBounds() {
206: // do I have to call layout here ? probably yes
207: layout();
208: return bounds;
209: }
210:
211: public void setGenBounds(Rectangle2D bounds) {
212: this .genBounds = bounds;
213: }
214:
215: public void setBounds(Rectangle2D bounds) {
216: this .bounds = bounds;
217: }
218:
219: public void setBounds(int x, int y, int width, int height) {
220: setBounds(GeomHelper.newRectangle(x, y, width, height));
221: }
222:
223: /**
224: * Returns vector of all text items ({@link TextItem}) of this text.
225: *
226: * @return vector of {@link TextItem}
227: */
228: public IVVector getTextItems() {
229: return items;
230: }
231:
232: /**
233: * Sets new vector of text items ({@link TextItem}) for this text.
234: *
235: * @param items new vector of {@link TextItem}
236: */
237: public void setTextItems(IVVector items) {
238: this .items = items;
239: }
240:
241: /**
242: * Adds new text item to this text
243: *
244: * @param item new text item to be added
245: * @return added text item
246: */
247: public TextItem addTextItem(TextItem item) {
248: items.addElement(item);
249: return item;
250: }
251:
252: public void setMatrix(AffineTransform matrix) {
253: this .matrix = matrix;
254: }
255:
256: public AffineTransform getMatrix() {
257: return matrix;
258: }
259:
260: /**
261: * Parses any DefineText tag.
262: * <P>
263: * If current parsed file is not generator template
264: * then parse text lazy ({@link LazyText}).
265: *
266: * @param p parser to parse with
267: * @param withAlpha with alpha or not
268: * @return LazyText or Text
269: */
270: public static FlashDef parse(Parser p, boolean withAlpha) {
271: if (!p.getFile().isTemplate()) {
272: return LazyText.parse(p, withAlpha);
273: } else {
274: Text text = new Text(withAlpha);
275: // get id
276: text.setID(p.getUWord());
277: // get bounds and matrix
278: text.bounds = p.getRect();
279: text.matrix = p.getMatrix();
280:
281: // do not parse futher, generator text tag will follow
282: p.skipLastTag();
283:
284: return text;
285: }
286: }
287:
288: /**
289: * Parses MM Generator define text tag: 0x2A.
290: * <P>
291: * Creates vector of TextItems
292: */
293: public void parseGenText(Parser p) {
294: genBounds = p.getRect();
295:
296: TextItem item = new TextItem();
297: boolean wasText = false;
298: for (;;) {
299: int code = p.getUByte();
300: if (code == 0) {
301: items.addElement(item);
302: break;
303: }
304: if ((code & 0x80) != 0) {
305: // control record
306: if (wasText) {
307: // save text item from previous record
308: items.addElement(item);
309: item = (TextItem) item.getCopy(null);
310: item.text = null;
311: wasText = false;
312: }
313: code = code & 0x7f;
314: switch (code) {
315: case 0: // bold, italic
316: item.style = p.getUByte();
317: break;
318: case 1: // font id
319: item.font = ((FontDef) p.getDef(p.getUWord()))
320: .getFont();
321: break;
322: case 2: // font height
323: item.height = p.getUWord();
324: break;
325: case 3: // color with alpha
326: item.color = Color.parseRGBA(p);
327: break;
328: case 4: // 0 - normal, 1 - superscript, 2 - subscript
329: item.script = p.getUByte();
330: break;
331: case 5: // kerning
332: item.kerning = p.getWord();
333: break;
334: case 8: // left, center, right, justify
335: item.align = p.getUByte();
336: break;
337: case 9: // indent
338: item.indent = p.getWord();
339: break;
340: case 10: // left margin
341: item.marginleft = p.getWord();
342: break;
343: case 11: // right margin
344: item.marginright = p.getWord();
345: break;
346: case 12: // line space
347: item.linesp = p.getWord();
348: break;
349: default:
350: // there is nothing we can do, so just eat one byte
351: Log.logRB(Resource.UNKNOWNGENTEXT,
352: new Object[] { Util.b2h(code) });
353: p.getUByte();
354: break;
355: }
356: } else {
357:
358: String encoding = p.getFile().getEncoding();
359: int length = code * 2;
360: String mystr;
361:
362: if (item.font != null
363: && (item.font.flags & Font.UNICODE) != 0) {
364: // font is Unicode, so try to convert to Unicode using specified or default
365: // encoding + we need to repair broken DBCS (if any)
366: byte[] buf = p.getTempByteBuf(length);
367: int size = gen2DBCSbytes(p.getBuf(), p.getPos(),
368: length, buf);
369: if (encoding == null) {
370: mystr = new String(buf, 0, size);
371: } else {
372: try {
373: mystr = new String(buf, 0, size, encoding);
374: } catch (UnsupportedEncodingException e) {
375: Log.log(e);
376: mystr = new String(buf, 0, size);
377: }
378: }
379: //System.out.println("unicode: '"+mystr+"'\n");
380: } else {
381: // font is not Unicode, so we must not convert to Unicode, just leave
382: // the characters as is + repair broken DBCS (if any)
383: char[] buf = p.getTempCharBuf(length / 2);
384: int size = gen2DBCSchars(p.getBuf(), p.getPos(),
385: length, buf);
386: mystr = new String(buf, 0, size);
387: //System.out.println("normal: '"+mystr+"'\n");
388: }
389:
390: item.text = item.text == null ? mystr : item.text
391: + mystr;
392:
393: p.skip(length);
394:
395: wasText = true;
396: }
397: }
398: }
399:
400: /**
401: * Converts array of bytes in Macromedia Generator character format
402: * into array of bytes suitable to be processed by encoding converters.
403: * <P>
404: * Chars are considered to be in broken DBCS, result is suitable to be
405: * converted to UNICODE.
406: *
407: * @param buf array of bytes in MM Generator char format
408: * @param pos position in this array
409: * @param length length of this array
410: * @param outbuf output buffer suitable to be converted to Unicode
411: * @return size of output buffer
412: */
413: public static int gen2DBCSbytes(byte[] buf, int pos, int length,
414: byte[] outbuf) {
415: int size = 0;
416: for (int i = 0; i < length; i += 2) {
417: byte hi = buf[pos++];
418: byte lo = buf[pos++];
419: if (lo == 0) {
420: outbuf[size++] = hi;
421: } else {
422: outbuf[size + 1] = hi;
423: outbuf[size] = lo;
424: size += 2;
425: }
426: }
427: return size;
428: }
429:
430: /**
431: * Converts array of bytes in Macromedia Generator character format
432: * into array of DBCS bytes
433: * <P>
434: * Chars are considered to be in broken DBCS, result is DBCS
435: *
436: * @param buf array of bytes in MM Generator char format
437: * @param pos position in this array
438: * @param length length of this array
439: * @param outbuf output buffer, array od DBCS chars
440: * @return size of output buffer
441: */
442: public static int gen2DBCSchars(byte[] buf, int pos, int length,
443: char[] outbuf) {
444: int size = 0;
445: for (int i = 0; i < length; i += 2) {
446: int hi = buf[pos++] & 0xff;
447: int lo = buf[pos++] & 0xff;
448: if (lo == 0) {
449: outbuf[size++] = (char) hi;
450: } else {
451: outbuf[size++] = (char) ((hi << 8) | lo);
452: }
453: }
454: return size;
455: }
456:
457: public void write(FlashOutput fob) {
458: write(fob, this );
459: }
460:
461: public void write(FlashOutput fob, FlashDef def) {
462: int start = fob.getPos(); // save for tag
463: fob.skip(6); // 6 - long tag
464: fob.writeDefID(def);
465:
466: layout();
467: fob.write(bounds);
468: fob.write(matrix);
469:
470: myLayout.write(fob);
471:
472: int size = fob.getPos() - start - 6; // 6 - long tag
473: // we need to generate Tag.DEFINETEXT2 all the time tag because from generator text we get rgba colors
474: fob.writeLongTagAt(Tag.DEFINETEXT2, size, start);
475: }
476:
477: public void printContent(PrintStream out, String indent) {
478: out.println(indent + "Text: id=" + getID());
479: out.println(indent + " bounds: " + bounds);
480: out.println(indent + " genBounds: " + genBounds);
481: out.println(indent + " " + matrix);
482:
483: TextItem last = new TextItem();
484: for (int i = 0; i < items.size(); i++) {
485: TextItem t = (TextItem) items.elementAt(i);
486: out.println(indent + " TextItem: '" + t.text + "'");
487: if (t.font != last.font)
488: out.println(indent + " font : '"
489: + t.font.getFontName() + "'");
490: if (t.height != last.height)
491: out.println(indent + " height: " + t.height);
492: if (t.style != last.style)
493: out.println(indent + " style : " + t.style);
494: if (t.align != last.align)
495: out.println(indent + " align : " + t.align);
496: if (t.indent != last.indent)
497: out.println(indent + " indent: " + t.indent);
498: if (t.linesp != last.linesp)
499: out.println(indent + " linesp: " + t.linesp);
500: if (t.kerning != last.kerning)
501: out.println(indent + " kerning: " + t.kerning);
502: if (t.script != last.script)
503: out.println(indent + " script: " + t.script);
504: if (t.marginleft != last.marginleft)
505: out.println(indent + " margin left: "
506: + t.marginleft);
507: if (t.marginright != last.marginright)
508: out.println(indent + " margin right : "
509: + t.marginright);
510: if (t.color != last.color)
511: t.color.printContent(out, indent + " ");
512: last = t;
513: }
514: }
515:
516: protected FlashItem copyInto(FlashItem item, ScriptCopier copier) {
517: super .copyInto(item, copier);
518: ((Text) item).withAlpha = withAlpha;
519: ((Text) item).bounds = (Rectangle2D) bounds.clone();
520: ((Text) item).genBounds = genBounds == null ? null
521: : (Rectangle2D) genBounds.clone();
522: ((Text) item).matrix = (AffineTransform) matrix.clone();
523: ((Text) item).items = items.getCopy(copier);
524: ((Text) item).myLayout = myLayout == null ? null : myLayout
525: .getCopy(copier);
526: return item;
527: }
528:
529: public FlashItem getCopy(ScriptCopier copier) {
530: return copyInto(new Text(), copier);
531: }
532: }
|