/*
* JSwiff is an open source Java API for Macromedia Flash file generation
* and manipulation
*
* Copyright (C) 2004-2005 Ralf Terdic (contact@jswiff.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.jswiff.swfrecords;
import com.jswiff.io.InputBitStream;
import com.jswiff.io.OutputBitStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
/**
* This class is used for defining shapes, e.g. by the <code>DefineFont</code>
* tag in order to define the character glyphs of a font, or within a
* <code>DefineMorphShape</code> tag. Shapes contain one or more
* <code>ShapeRecord</code> instances which define style changes and
* primitives as lines and curves.
*
* @see com.jswiff.swfrecords.tags.DefineFont
* @see com.jswiff.swfrecords.tags.DefineMorphShape
*/
public class Shape implements Serializable {
// Byte number of fill/line styles - these are used as initial values for parsing,
// StyleChangeRecords may contain new values used for further parsing.
// However, these values aren't overwritten!
protected byte numFillBits;
protected byte numLineBits;
protected ShapeRecord[] shapeRecords;
/**
* Creates a new Shape instance.
*
* @param shapeRecords an array of one or more shape records
*/
public Shape(ShapeRecord[] shapeRecords) {
this.shapeRecords = shapeRecords;
}
/*
*/
public Shape(InputBitStream stream) throws IOException {
read(stream, false, false);
}
Shape() {
// empty
}
/**
* Returns the contained shape record array.
*
* @return shape records
*/
public ShapeRecord[] getShapeRecords() {
return shapeRecords;
}
/*
*/
public void write(OutputBitStream stream) throws IOException {
computeNumBits();
byte currentNumFillBits = numFillBits;
byte currentNumLineBits = numLineBits;
stream.writeUnsignedBits(currentNumFillBits, 4);
stream.writeUnsignedBits(currentNumLineBits, 4);
for (int i = 0; i < shapeRecords.length; i++) {
ShapeRecord record = shapeRecords[i];
if (record instanceof StyleChangeRecord) {
StyleChangeRecord changeRecord = (StyleChangeRecord) record;
changeRecord.write(stream, currentNumFillBits, currentNumLineBits);
if (changeRecord.hasNewStyles()) {
// following StyleChangeRecords are stored using num*Bits
// contained in this StyleChangeRecord
currentNumFillBits = changeRecord.getNumFillBits();
currentNumLineBits = changeRecord.getNumLineBits();
}
} else if (record instanceof StraightEdgeRecord) {
((StraightEdgeRecord) record).write(stream);
} else {
// record is CurvedEdgeRecord
((CurvedEdgeRecord) record).write(stream);
}
}
// now write EndShapeRecord
stream.writeUnsignedBits(0, 6);
stream.align();
}
protected void read(InputBitStream stream, boolean useNewLineStyle, boolean hasAlpha)
throws IOException {
numFillBits = (byte) stream.readUnsignedBits(4);
numLineBits = (byte) stream.readUnsignedBits(4);
// use values from Shape/ShapeWithStyle as initial values for parsing
// StyleChangeRecords may change these values
byte currentNumFillBits = numFillBits;
byte currentNumLineBits = numLineBits;
Vector shapeRecordVector = new Vector();
do {
// read type flag - edge record or style change? check TypeFlag
int typeFlag = (int) stream.readUnsignedBits(1);
if (typeFlag == 0) {
byte flags = (byte) stream.readUnsignedBits(5);
if (flags == 0) {
// EndShapeRecord, exit loop
break;
}
// we have a style change record here
StyleChangeRecord record = new StyleChangeRecord(
stream, flags, currentNumFillBits, currentNumLineBits, useNewLineStyle, hasAlpha);
// bit number may have changed (if stateNewStyles=true)
currentNumFillBits = record.getNumFillBits();
currentNumLineBits = record.getNumLineBits();
shapeRecordVector.add(record);
} else {
// we have an edge record here - curved or straight? check StraightFlag
int straightFlag = (int) stream.readUnsignedBits(1);
if (straightFlag == 1) {
StraightEdgeRecord record = new StraightEdgeRecord(stream);
shapeRecordVector.add(record);
} else {
CurvedEdgeRecord record = new CurvedEdgeRecord(stream);
shapeRecordVector.add(record);
}
}
} while (true);
stream.align();
shapeRecords = new ShapeRecord[shapeRecordVector.size()];
shapeRecordVector.copyInto(shapeRecords);
}
void setNumFillBits(byte numFillBits) {
this.numFillBits = numFillBits;
}
byte getNumFillBits() {
return numFillBits;
}
void setNumLineBits(byte numLineBits) {
this.numLineBits = numLineBits;
}
byte getNumLineBits() {
return numLineBits;
}
private void computeNumBits() {
// Iterate over shapeRecords, filter StyleChangeRecord instances.
List changeRecords = new ArrayList();
for (int i = 0; i < shapeRecords.length; i++) {
ShapeRecord record = shapeRecords[i];
if (record instanceof StyleChangeRecord) {
changeRecords.add(shapeRecords[i]);
}
}
if (changeRecords.size() == 0) {
return;
}
// Form groups of StyleChangeRecords, separated by StyleChangeRecord
// with StateNewStyles set. For each group, determine the number of
// bits needed to store fillStyle0, fillStyle1 and lineStyle
// (numFillBits and numLineBits).
// Set num*Bits of the shape = num*Bits of the first group.
// Set num*Bits of each StyleChangeRecord with StateNewStyles=true
// to num*Bits of the group it belongs to.
byte fillBits = 0;
byte lineBits = 0;
int groupStartIndex = -1;
for (int i = 0; i < changeRecords.size(); i++) {
StyleChangeRecord record = (StyleChangeRecord) changeRecords.get(i);
// compute num*Bits
if (record.hasFillStyle0()) {
fillBits = (byte) Math.max(
fillBits,
OutputBitStream.getUnsignedBitsLength(record.getFillStyle0()));
}
if (record.hasFillStyle1()) {
fillBits = (byte) Math.max(
fillBits,
OutputBitStream.getUnsignedBitsLength(record.getFillStyle1()));
}
if (record.hasLineStyle()) {
lineBits = (byte) Math.max(
lineBits,
OutputBitStream.getUnsignedBitsLength(record.getLineStyle()));
}
// check StateNewStyles
if (record.hasNewStyles()) {
// new group starts, store num*Bits for current group
storeNumBits(groupStartIndex, fillBits, lineBits, changeRecords);
groupStartIndex = i; // store record index
fillBits = 0;
lineBits = 0;
}
}
// store num*Bits for last group (no group start to toggle storing for last group)
storeNumBits(groupStartIndex, fillBits, lineBits, changeRecords);
// CAUTION: to store StyleChangeRecords with StateNewStyles=true,
// use num*Bits of preceding group
// (or of the shape instance for the first group), not their own num*Bits!
}
private void storeNumBits(
int groupStartIndex, byte fillBits, byte lineBits, List changeRecords) {
if (groupStartIndex > -1) {
// store in first change record of group
StyleChangeRecord groupStartRecord = (StyleChangeRecord) changeRecords.get(
groupStartIndex);
groupStartRecord.setNumFillBits(fillBits);
groupStartRecord.setNumLineBits(lineBits);
} else {
// first group - store in shape instance
this.numFillBits = fillBits;
this.numLineBits = lineBits;
}
}
}