Index: build.xml =================================================================== RCS file: /home/cvspublic/xml-fop/build.xml,v retrieving revision 1.118 diff -u -r1.118 build.xml --- build.xml 14 Mar 2005 08:51:26 -0000 1.118 +++ build.xml 19 Mar 2005 00:47:43 -0000 @@ -177,7 +177,7 @@ - + @@ -984,6 +984,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1077,5 +1189,5 @@ - + Index: conf/fop.xconf =================================================================== RCS file: /home/cvspublic/xml-fop/conf/fop.xconf,v retrieving revision 1.4 diff -u -r1.4 fop.xconf --- conf/fop.xconf 9 Jul 2004 20:07:47 -0000 1.4 +++ conf/fop.xconf 19 Mar 2005 00:47:43 -0000 @@ -97,7 +97,25 @@ - + + + + + + + + + Index: src/java/org/apache/fop/apps/CommandLineOptions.java =================================================================== RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/apps/CommandLineOptions.java,v retrieving revision 1.32 diff -u -r1.32 CommandLineOptions.java --- src/java/org/apache/fop/apps/CommandLineOptions.java 18 Mar 2005 08:07:24 -0000 1.32 +++ src/java/org/apache/fop/apps/CommandLineOptions.java 19 Mar 2005 00:47:47 -0000 @@ -165,6 +165,10 @@ i = i + parseMIFOutputOption(args, i); } else if (args[i].equals("-rtf")) { i = i + parseRTFOutputOption(args, i); + } else if (args[i].equals("-tiff")) { + i = i + parseTIFFOutputOption(args, i); + } else if (args[i].equals("-png")) { + i = i + parsePNGOutputOption(args, i); } else if (args[i].equals("-print")) { i = i + parsePrintOutputOption(args, i); // show print help @@ -302,6 +306,28 @@ } } + private int parseTIFFOutputOption(String[] args, int i) throws FOPException { + setOutputMode(RENDER_TIFF); + if ((i + 1 == args.length) + || (args[i + 1].charAt(0) == '-')) { + throw new FOPException("you must specify the tiff output file"); + } else { + outfile = new File(args[i + 1]); + return 1; + } + } + + private int parsePNGOutputOption(String[] args, int i) throws FOPException { + setOutputMode(RENDER_PNG); + if ((i + 1 == args.length) + || (args[i + 1].charAt(0) == '-')) { + throw new FOPException("you must specify the png output file"); + } else { + outfile = new File(args[i + 1]); + return 1; + } + } + private int parsePrintOutputOption(String[] args, int i) throws FOPException { setOutputMode(RENDER_PRINT); return 0; @@ -493,6 +519,8 @@ case RENDER_TXT: case RENDER_SVG: case RENDER_RTF: + case RENDER_TIFF: + case RENDER_PNG: return outputmode; case RENDER_XML: foUserAgent.getRendererOptions().put("fineDetail", isCoarseAreaXml()); @@ -605,7 +633,7 @@ public static void printUsage() { System.err.println( "\nUSAGE\nFop [options] [-fo|-xml] infile [-xsl file] " - + "[-awt|-pdf|-mif|-rtf|-pcl|-ps|-txt|-at|-print] \n" + + "[-awt|-pdf|-mif|-rtf|-tiff|-png|-pcl|-ps|-txt|-at|-print] \n" + " [OPTIONS] \n" + " -d debug mode \n" + " -x dump configuration settings \n" @@ -627,6 +655,8 @@ + " -awt input will be displayed on screen \n" + " -mif outfile input will be rendered as mif file (outfile req'd)\n" + " -rtf outfile input will be rendered as rtf file (outfile req'd)\n" + + " -tiff outfile input will be rendered as tiff file (outfile req'd)\n" + + " -png outfile input will be rendered as png file (outfile req'd)\n" + " -pcl outfile input will be rendered as pcl file (outfile req'd) \n" + " -ps outfile input will be rendered as PostScript file (outfile req'd) \n" + " -txt outfile input will be rendered as text file (outfile req'd) \n" @@ -698,6 +728,14 @@ log.info("rtf"); log.info("output file: " + outfile.toString()); break; + case RENDER_TIFF: + log.info("tiff"); + log.info("output file: " + outfile.toString()); + break; + case RENDER_PNG: + log.info("png"); + log.info("output file: " + outfile.toString()); + break; case RENDER_PRINT: log.info("print directly"); if (outfile != null) { Index: src/java/org/apache/fop/apps/FOUserAgent.java =================================================================== RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/apps/FOUserAgent.java,v retrieving revision 1.22 diff -u -r1.22 FOUserAgent.java --- src/java/org/apache/fop/apps/FOUserAgent.java 2 Mar 2005 01:59:40 -0000 1.22 +++ src/java/org/apache/fop/apps/FOUserAgent.java 19 Mar 2005 00:47:48 -0000 @@ -19,12 +19,13 @@ package org.apache.fop.apps; // Java +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Map; -import java.io.IOException; -import java.io.InputStream; // avalon configuration import org.apache.avalon.framework.configuration.Configuration; @@ -73,6 +74,7 @@ private float px2mm = (25.4f / 72); //dpi (=25.4/dpi) private HashMap rendererOptions = new java.util.HashMap(); private InputHandler inputHandler = null; + private File outputFile = null; private Renderer rendererOverride = null; private FOEventHandler foEventHandlerOverride = null; private LayoutManagerMaker lmMakerOverride = null; @@ -418,6 +420,22 @@ } /** + * Sets the output File. + * @param the output File + */ + public void setOutputFile(File f){ + this.outputFile = f; + } + + /** + * Gets the output File. + * @return the output File + */ + public File getOutputFile(){ + return outputFile; + } + + /** * Returns the conversion factor from pixel units to millimeters. This * depends on the desired reolution. * @return float conversion factor Index: src/java/org/apache/fop/apps/Fop.java =================================================================== RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/apps/Fop.java,v retrieving revision 1.21 diff -u -r1.21 Fop.java --- src/java/org/apache/fop/apps/Fop.java 13 Sep 2004 07:08:49 -0000 1.21 +++ src/java/org/apache/fop/apps/Fop.java 19 Mar 2005 00:47:49 -0000 @@ -1,6 +1,6 @@ /* - * Copyright 1999-2004 The Apache Software Foundation. - * + * Copyright 1999-2005 The Apache Software Foundation. + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -69,6 +69,8 @@ *
  • Fop.RENDER_TXT
  • *
  • Fop.RENDER_SVG
  • *
  • Fop.RENDER_RTF
  • + *
  • Fop.RENDER_TIFF
  • + *
  • Fop.RENDER_PNG
  • * * @param ua FOUserAgent object * @throws IllegalArgumentException if an unsupported renderer type was requested. @@ -149,6 +151,7 @@ bos = new BufferedOutputStream(new FileOutputStream( options.getOutputFile())); fop.setOutputStream(bos); + foUserAgent.setOutputFile(options.getOutputFile()); } foUserAgent.getInputHandler().render(fop); } finally { Index: src/java/org/apache/fop/area/PageViewport.java =================================================================== RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/area/PageViewport.java,v retrieving revision 1.13 diff -u -r1.13 PageViewport.java --- src/java/org/apache/fop/area/PageViewport.java 12 Mar 2005 02:14:38 -0000 1.13 +++ src/java/org/apache/fop/area/PageViewport.java 19 Mar 2005 00:47:50 -0000 @@ -1,12 +1,12 @@ /* * Copyright 1999-2005 The Apache Software Foundation. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -87,7 +87,7 @@ public BodyRegion getBodyRegion() { return (BodyRegion) getPage().getRegionViewport(Constants.FO_REGION_BODY).getRegion(); - } + } /** * Set if this viewport should clip. @@ -167,7 +167,7 @@ * @return true if the page is resolved and can be rendered */ public boolean isResolved() { - return unresolvedIDRefs == null; + return unresolvedIDRefs.size() == 0; } /** @@ -219,7 +219,7 @@ * are starting, replacing earlier markers. * For "last-ending-within-page" it adds all markers that * are ending, replacing earlier markers. - * + * * Should this logic be placed in the Page layout manager. * * @param marks the map of markers to add @@ -294,7 +294,7 @@ * This will retrieve a marker with the class name * and position. * - * @param name The class name of the marker to retrieve + * @param name The class name of the marker to retrieve * @param pos the position to retrieve * @return Object the marker found or null */ @@ -339,8 +339,8 @@ } break; } - log.trace("page " + pageNumberString + ": " + "Retrieving marker " + name + "at position " + posName); - return mark; + log.trace("page " + pageNumberString + ": " + "Retrieving marker " + name + "at position " + posName); + return mark; } /** @@ -386,6 +386,7 @@ public Object clone() { Page p = (Page)page.clone(); PageViewport ret = new PageViewport(p, (Rectangle2D)viewArea.clone()); + ret.pageNumberString = pageNumberString; return ret; } Index: src/java/org/apache/fop/fo/Constants.java =================================================================== RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/fo/Constants.java,v retrieving revision 1.25 diff -u -r1.25 Constants.java --- src/java/org/apache/fop/fo/Constants.java 31 Jan 2005 22:27:19 -0000 1.25 +++ src/java/org/apache/fop/fo/Constants.java 19 Mar 2005 00:47:51 -0000 @@ -27,7 +27,7 @@ /** render constants for bounds checking */ int RENDER_MIN_CONST = 1; - int RENDER_MAX_CONST = 10; + int RENDER_MAX_CONST = 12; /** input / output not set */ int NOT_SET = 0; /** input: fo file */ @@ -54,7 +54,11 @@ int RENDER_XML = 9; /** output: RTF file */ int RENDER_RTF = 10; - + /** output: TIFF file */ + int RENDER_TIFF = 11; + /** output: PNG file */ + int RENDER_PNG = 12; + // element constants int FO_UNKNOWN_NODE = 0; // FObj base class int FO_BASIC_LINK = 1; Index: src/java/org/apache/fop/render/RendererFactory.java =================================================================== RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/render/RendererFactory.java,v retrieving revision 1.2 diff -u -r1.2 RendererFactory.java --- src/java/org/apache/fop/render/RendererFactory.java 5 Jan 2005 21:06:37 -0000 1.2 +++ src/java/org/apache/fop/render/RendererFactory.java 19 Mar 2005 00:47:54 -0000 @@ -52,10 +52,14 @@ return new org.apache.fop.render.pdf.PDFRenderer(); case Constants.RENDER_AWT: return new org.apache.fop.render.awt.AWTRenderer(); - case Constants.RENDER_PRINT: - return new org.apache.fop.render.awt.AWTPrintRenderer(); + case Constants.RENDER_PRINT: + return new org.apache.fop.render.print.Java2DPrintRenderer(); case Constants.RENDER_PCL: return new org.apache.fop.render.pcl.PCLRenderer(); + case Constants.RENDER_TIFF: + return new org.apache.fop.render.tiff.TIFFRenderer(); + case Constants.RENDER_PNG: + return new org.apache.fop.render.png.PNGRenderer(); case Constants.RENDER_PS: return new org.apache.fop.render.ps.PSRenderer(); case Constants.RENDER_TXT: Index: src/java/org/apache/fop/render/awt/AWTFontMetrics.java =================================================================== RCS file: src/java/org/apache/fop/render/awt/AWTFontMetrics.java diff -N src/java/org/apache/fop/render/awt/AWTFontMetrics.java --- src/java/org/apache/fop/render/awt/AWTFontMetrics.java 18 Mar 2004 04:49:07 -0000 1.3 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,300 +0,0 @@ -/* - * Copyright 1999-2004 The Apache Software Foundation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* $Id$ */ - -package org.apache.fop.render.awt; - -// Java -import java.awt.Font; -import java.awt.Graphics2D; -import java.awt.geom.Rectangle2D; -import java.awt.FontMetrics; -import java.awt.font.TextLayout; - -/** - * This is a FontMetrics to be used for AWT rendering. - * It instanciates a font, depening on family and style - * values. The java.awt.FontMetrics for this font is then - * created to be used for the actual measurement. - * Since layout is word by word and since it is expected that - * two subsequent words often share the same style, the - * Font and FontMetrics is buffered and only changed if needed. - *

    - * Since FontState and FontInfo multiply all factors by - * size, we assume a "standard" font of FONT_SIZE. - */ -public class AWTFontMetrics { - - /** - * Font size standard used for metric measurements - */ - public static final int FONT_SIZE = 1; - - /** - * This factor multiplies the calculated values to scale - * to FOP internal measurements - */ - public static final int FONT_FACTOR = (1000 * 1000) / FONT_SIZE; - - /** - * The width of all 256 character, if requested - */ - private int width[] = null; - - /** - * The typical height of a small cap latter - */ - private int xHeight = 0; - - /** - * Buffered font. - * f1 is bufferd for metric measurements during layout. - * fSized is buffered for display purposes - */ - private Font f1 = null; // , fSized = null; - - /** - * The family type of the font last used - */ - private String family = ""; - - /** - * The style of the font last used - */ - private int style = 0; - - /** - * The size of the font last used - */ - private float size = 0; - - /** - * The FontMetrics object used to calculate character width etc. - */ - private FontMetrics fmt = null; - - /** - * Temp graphics object needed to get the font metrics - */ - private Graphics2D graphics; - - /** - * Constructs a new Font-metrics. - * @param graphics a temp graphics object - this is needed so - * that we can get an instance of java.awt.FontMetrics - */ - public AWTFontMetrics(Graphics2D graphics) { - this.graphics = graphics; - } - - /** - * Determines the font ascent of the Font described by this - * FontMetrics object - * @param family font family (java name) to use - * @param style font style (java def.) to use - * @param size font size - * @return ascent in milliponts - */ - public int getAscender(String family, int style, int size) { - setFont(family, style, size); - // return (int)(FONT_FACTOR * fmt.getAscent()); - - // workaround for sun bug on FontMetrics.getAscent() - // http://developer.java.sun.com/developer/bugParade/bugs/4399887.html - /* - * Bug 4399887 has status Closed, not a bug. The comments on the bug - * are: - * The submitter is incorrectly assuming that the string he has used - * is displaying characters which represent those with the maximum - * ascent in the font. If (for example) the unicode character - * \u00c1 which is the A-acute character used in many European - * languages is placed in the bodu of the "Wrong" string it can be - * seen that the JDK is exactly correct in its determination of the - * ascent of the font. - * If the bounds of a particular string are interesting then the - * Rectangle FontMetrics.getStringBounds(..) method can be called. - * The y value of the rectangle is the offset from the origin - * (baseline) apparently needed by the sample test program - * - * xxxxx@xxxxx 2001-05-15 - */ - int realAscent = fmt.getAscent() - - (fmt.getDescent() + fmt.getLeading()); - return FONT_FACTOR * realAscent; - } - - - /** - * The size of a capital letter measured from the font's baseline - * @param family font family - * @param style font style - * @param size font size - * @return capital height in millipoints - */ - public int getCapHeight(String family, int style, int size) { - // currently just gets Ascent value but maybe should use - // getMaxAcent() at some stage - return getAscender(family, style, size); - } - - /** - * Determines the font descent of the Font described by this - * FontMetrics object - * @param family font family (jave name) to use - * @param style font style (jave def.) to use - * @param size font size - * @return descent in milliponts - */ - public int getDescender(String family, int style, int size) { - setFont(family, style, size); - return (-1 * FONT_FACTOR * fmt.getDescent()); - } - - /** - * Determines the typical font height of a small cap letter - * FontMetrics object - * @param family font family (jave name) to use - * @param style font style (jave def.) to use - * @param size font size - * @return font height in milliponts - */ - public int getXHeight(String family, int style, int size) { - setFont(family, style, size); - return (int)(FONT_FACTOR * xHeight); - } - - /** - * Returns width (in 1/1000ths of point size) of character at - * code point i - * @param i the character for which to get the width - * @param family font family (jave name) to use - * @param style font style (jave def.) to use - * @param size font size - * @return character width in millipoints - */ - public int width(int i, String family, int style, int size) { - int w; - setFont(family, style, size); - // the output seems to look a little better if the - // space is rendered larger than given by - // the FontMetrics object - if (i <= 32) { - w = (int)(1.4 * fmt.charWidth(i) * FONT_FACTOR); - } else { - w = (int)(fmt.charWidth(i) * FONT_FACTOR); - } - return w; - } - - /** - * Return widths (in 1/1000ths of point size) of all - * characters - * @param family font family (jave name) to use - * @param style font style (jave def.) to use - * @param size font size - * @return array of character widths in millipoints - */ - public int[] getWidths(String family, int style, int size) { - int i; - - if (width == null) { - width = new int[256]; - } - setFont(family, style, size); - for (i = 0; i < 256; i++) { - width[i] = FONT_FACTOR * fmt.charWidth(i); - } - return width; - } - - /** - * Checks whether the font for which values are - * requested is the one used immediately before or - * whether it is a new one - * @param family font family (jave name) to use - * @param style font style (jave def.) to use - * @param size font size - * @return true if the font was changed, false otherwise - */ - private boolean setFont(String family, int style, int size) { - boolean changed = false; - Rectangle2D rect; - TextLayout layout; - int s = (int)(size / 1000f); - - if (f1 == null) { - f1 = new Font(family, style, s); - fmt = graphics.getFontMetrics(f1); - changed = true; - } else { - if ((this.style != style) || !this.family.equals(family) - || this.size != s) { - if (family.equals(this.family)) { - f1 = f1.deriveFont(style, (float)s); - } else { - f1 = new Font(family, style, s); - } - fmt = graphics.getFontMetrics(f1); - changed = true; - } - // else the font is unchanged from last time - } - if (changed) { - layout = new TextLayout("m", f1, graphics.getFontRenderContext()); - rect = layout.getBounds(); - xHeight = (int)rect.getHeight(); - } - // save the family and style for later comparison - this.family = family; - this.style = style; - this.size = s; - return changed; - } - - - /** - * Returns a java.awt.Font instance for the desired - * family, style and size type. - * This is here, so that the font-mapping - * of FOP-defined fonts to java-fonts can be done - * in one place and does not need to occur in - * AWTFontRenderer. - * @param family font family (jave name) to use - * @param style font style (jave def.) to use - * @param size font size - * @return font with the desired characeristics. - */ - public java.awt.Font getFont(String family, int style, int size) { - setFont(family, style, size); - return f1; - /* - * if( setFont(family,style, size) ) fSized = null; - * if( fSized == null || this.size != size ) { - * fSized = f1.deriveFont( size / 1000f ); - * } - * this.size = size; - * return fSized; - */ - } - -} - - - - - - Index: src/java/org/apache/fop/render/awt/AWTGraphicsState.java =================================================================== RCS file: src/java/org/apache/fop/render/awt/AWTGraphicsState.java diff -N src/java/org/apache/fop/render/awt/AWTGraphicsState.java --- src/java/org/apache/fop/render/awt/AWTGraphicsState.java 8 Mar 2005 11:47:55 -0000 1.1 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,332 +0,0 @@ -/* - * Copyright 2005 The Apache Software Foundation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* $Id: AWTGraphicsState.java,v 1.1 2005/03/08 11:47:55 jeremias Exp $ */ - -package org.apache.fop.render.awt; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Shape; -import java.awt.geom.AffineTransform; -import java.awt.geom.Area; -import java.awt.geom.GeneralPath; -import java.util.List; - -import org.apache.fop.datatypes.ColorType; -import org.apache.fop.fo.Constants; -import org.apache.fop.fonts.FontInfo; - -/** - * Keeps information about the current state of the Graphics2D currentGraphics. - * It is also used as a stack to hold a graphics context. - *

    - * The graphics context is updated with the updateXXX() methods. - */ -public class AWTGraphicsState implements Constants, RendererState { - - /** Holds the datas of the current state */ - private Graphics2D currentGraphics; - - private BasicStroke currentStroke; - - private float currentStrokeWidth; - - private int currentStrokeStyle; - - private List stateStack = new java.util.ArrayList(); - - /** Font configuration, passed from AWTRenderer */ - private FontInfo fontInfo; - - /** State for storing graphics state. */ - public AWTGraphicsState(Graphics2D graphics, FontInfo fontInfo) { - this.fontInfo = fontInfo; - this.currentGraphics = graphics; - } - - /** - * @return the currently valid state - */ - public Graphics2D getGraph() { - return currentGraphics; - } - - /** @see org.apache.fop.render.awt.RendererState#push() */ - public void push() { - Graphics2D tmpGraphics = (Graphics2D) currentGraphics.create(); - stateStack.add(tmpGraphics); - } - - /** @see org.apache.fop.render.awt.RendererState#pop() */ - public Graphics2D pop() { - if (getStackLevel() > 0) { - Graphics2D popped = (Graphics2D) stateStack.remove(stateStack - .size() - 1); - - currentGraphics = popped; - return popped; - } else { - return null; - } - } - - /** @see org.apache.fop.render.awt.RendererState#getStackLevel() */ - public int getStackLevel() { - return stateStack.size(); - } - - /** - * Restore the state to a particular level. this can be used to restore to a - * known level without making multiple pop calls. - * - * @param stack the level to restore to - */ - /* - * public void restoreLevel(int stack) { int pos = stack; while - * (stateStack.size() > pos + 1) { stateStack.remove(stateStack.size() - 1); } - * if (stateStack.size() > pos) { pop(); } } - */ - - /** - * Set the current background color. Check if the background color will - * change and then set the new color. - * - * @param col the new color as a java.awt.Color - * @return true if the background color has changed - */ - public boolean updateColor(Color col) { - if (!col.equals(getGraph().getColor())) { - getGraph().setColor(col); - return true; - } else { - return false; - } - } - - /** - * Converts a ColorType to a java.awt.Color (sRGB). - * - * @param col the color as a org.apache.fop.datatypes.ColorType - * @return the converted color as a java.awt.Color - */ - public Color toColor(ColorType col) { - return new Color(col.getRed(), col.getGreen(), col.getBlue()); - } - - /** - * @see org.apache.fop.render.awt.RendererState#updateColor(org.apache.fop.datatypes.ColorType, - * boolean, java.lang.StringBuffer) - */ - public boolean updateColor(ColorType col, boolean fill, StringBuffer pdf) { - if (col == null) { - return false; - } - Color newCol = toColor(col); - return updateColor(newCol); - } - - /** - * Update the current Color - * @param col the ColorType - */ - public void updateColor(ColorType col) { - if (col == null) { - return; - } - Color newCol = toColor(col); - updateColor(newCol); - } - - /** - * @return the current java.awt.Color - */ - public java.awt.Color getColor() { - return currentGraphics.getColor(); - } - - /** - * @see org.apache.fop.render.awt.RendererState#updateFont(java.lang.String, - * int, java.lang.StringBuffer) - */ - public boolean updateFont(String name, int size, StringBuffer pdf) { - - boolean updateName = (!name.equals(getGraph().getFont().getFontName())); - boolean updateSize = (size != (getGraph().getFont().getSize())); - - if (updateName || updateSize) { - // the font name and/or the font size have changed - FontMetricsMapper mapper = (FontMetricsMapper) fontInfo - .getMetricsFor(name); - java.awt.Font font = mapper.getFont(size); - - currentGraphics.setFont(font); - return true; - } else { - return false; - } - } - - /** - * @return the current java.awt.Font - */ - public java.awt.Font getFont() { - return currentGraphics.getFont(); - } - - /** - * @see org.apache.fop.render.awt.RendererState#updateStroke(float, int) - */ - public boolean updateStroke(float width, int style) { - - boolean update = false; - - // only update if necessary - if ((width != currentStrokeWidth) || (style != currentStrokeStyle)) { - - update = true; - - switch (style) { - case EN_DOTTED: - - currentStroke = new BasicStroke(width, BasicStroke.CAP_BUTT, - BasicStroke.JOIN_BEVEL, 0f, new float[] { 2f }, 0f); - currentGraphics.setStroke(currentStroke); - - currentStrokeWidth = width; - currentStrokeStyle = style; - - break; - - case EN_DASHED: - - currentStroke = new BasicStroke(width, BasicStroke.CAP_BUTT, - BasicStroke.JOIN_BEVEL, 0f, new float[] { 8f, 2f }, 0f); - currentGraphics.setStroke(currentStroke); - - currentStrokeWidth = width; - currentStrokeStyle = style; - - break; - - default: // EN_SOLID: - - currentStroke = new BasicStroke(width); - currentGraphics.setStroke(currentStroke); - - currentStrokeWidth = width; - currentStrokeStyle = style; - - break; - } - } - - return update; - } - - public BasicStroke getStroke() { - return (BasicStroke) currentGraphics.getStroke(); - } - - /** @see org.apache.fop.render.awt.RendererState#updatePaint(java.awt.Paint) */ - public boolean updatePaint(Paint p) { - if (getGraph().getPaint() == null) { - if (p != null) { - getGraph().setPaint(p); - return true; - } - } else if (p.equals(getGraph().getPaint())) { - getGraph().setPaint(p); - return true; - } - return false; - } - - /** @see org.apache.fop.render.awt.RendererState#checkClip(java.awt.Shape) */ - // TODO implement and test - public boolean checkClip(Shape cl) { - if (getGraph().getClip() == null) { - if (cl != null) { - return true; - } - } else if (cl.equals(getGraph().getClip())) { - return true; - } - // TODO check for clips that are larger than the current - return false; - } - - /** - * @see org.apache.fop.render.awt.RendererState#updateClip(java.awt.Shape) - */ - public boolean updateClip(Shape cl) { - if (getGraph().getClip() != null) { - Area newClip = new Area(getGraph().getClip()); - newClip.intersect(new Area(cl)); - getGraph().setClip(new GeneralPath(newClip)); - } else { - getGraph().setClip(cl); - } - return true; // TODO only update if necessary - } - - /** - * @see org.apache.fop.render.awt.RendererState#checkTransform(java.awt.geom.AffineTransform) - */ - public boolean checkTransform(AffineTransform tf) { - return !tf.equals(getGraph().getTransform()); - } - - /** - * @see org.apache.fop.render.awt.RendererState#setTransform(java.awt.geom.AffineTransform) - */ - public void setTransform(AffineTransform tf) { - getGraph().setTransform(tf); - } - - /** - * @see org.apache.fop.render.awt.RendererState#transform(java.awt.geom.AffineTransform) - */ - public void transform(AffineTransform tf) { - getGraph().transform(tf); - } - - /** - * @see org.apache.fop.render.awt.RendererState#getTransform() - */ - public AffineTransform getTransform() { - /* - * AffineTransform tf; AffineTransform at = new AffineTransform(); for - * (Iterator iter = stateStack.iterator(); iter.hasNext();) { Data d = - * (Data) iter.next(); tf = d.transform; at.concatenate(tf); } - * at.concatenate(getCurrentGraphics().transform); - * - * return at; - */ - return getGraph().getTransform(); - } - - /** a verbose description of the current state */ - public String toString() { - String s = "AWTGraphicsState " + currentGraphics.toString() - + ", Stroke (width: " + currentStrokeWidth + " style: " - + currentStrokeStyle + "), " + getTransform() - + ", StackLevel: " + getStackLevel(); - return s; - } -} Index: src/java/org/apache/fop/render/awt/AWTPrintRenderer.java =================================================================== RCS file: src/java/org/apache/fop/render/awt/AWTPrintRenderer.java diff -N src/java/org/apache/fop/render/awt/AWTPrintRenderer.java --- src/java/org/apache/fop/render/awt/AWTPrintRenderer.java 27 Feb 2004 17:51:22 -0000 1.7 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,121 +0,0 @@ -/* - * Copyright 1999-2004 The Apache Software Foundation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* $Id: AWTPrintRenderer.java,v 1.7 2004/02/27 17:51:22 jeremias Exp $ */ - -package org.apache.fop.render.awt; - -import java.awt.print.PrinterException; -import java.awt.print.PrinterJob; -import java.io.IOException; -import java.util.Vector; - -public class AWTPrintRenderer extends AWTRenderer { - - private static final int EVEN_AND_ALL = 0; - private static final int EVEN = 1; - private static final int ODD = 2; - - private int startNumber; - private int endNumber; - private int mode = EVEN_AND_ALL; - private int copies = 1; - private PrinterJob printerJob; - - public AWTPrintRenderer() { - initialize(); - } - - private void initialize() throws IllegalArgumentException { - // read from command-line options - copies = getIntProperty("copies", 1); - startNumber = getIntProperty("start", 1) - 1; - endNumber = getIntProperty("end", -1); - String str = System.getProperty("even"); - if (str != null) { - mode = Boolean.valueOf(str).booleanValue() ? EVEN : ODD; - } - - printerJob = PrinterJob.getPrinterJob(); - printerJob.setJobName("FOP Document"); - printerJob.setCopies(copies); - if (System.getProperty("dialog") != null) { - if (!printerJob.printDialog()) { - throw new IllegalArgumentException("Printing cancelled by operator"); - } - } - printerJob.setPageable(this); - } - - public void stopRenderer() throws IOException { - super.stopRenderer(); - - if (endNumber == -1) { - endNumber = getNumberOfPages(); - } - - Vector numbers = getInvalidPageNumbers(); - for (int i = numbers.size() - 1; i > -1; i--) { - // removePage(Integer.parseInt((String)numbers.elementAt(i))); - } - - try { - printerJob.print(); - } catch (PrinterException e) { - e.printStackTrace(); - throw new IOException("Unable to print: " - + e.getClass().getName() - + ": " + e.getMessage()); - } - } - - public static int getIntProperty(String name, int def) { - String propValue = System.getProperty(name); - if (propValue != null) { - try { - return Integer.parseInt(propValue); - } catch (Exception e) { - return def; - } - } else { - return def; - } - } - - private Vector getInvalidPageNumbers() { - Vector vec = new Vector(); - int max = getNumberOfPages(); - boolean isValid; - for (int i = 0; i < max; i++) { - isValid = true; - if (i < startNumber || i > endNumber) { - isValid = false; - } else if (mode != EVEN_AND_ALL) { - if (mode == EVEN && ((i + 1) % 2 != 0)) { - isValid = false; - } else if (mode == ODD && ((i + 1) % 2 != 1)) { - isValid = false; - } - } - - if (!isValid) { - vec.add(i + ""); - } - } - return vec; - } -} // class AWTPrintRenderer - Index: src/java/org/apache/fop/render/awt/AWTRenderer.java =================================================================== RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/render/awt/AWTRenderer.java,v retrieving revision 1.34 diff -u -r1.34 AWTRenderer.java --- src/java/org/apache/fop/render/awt/AWTRenderer.java 8 Mar 2005 11:47:55 -0000 1.34 +++ src/java/org/apache/fop/render/awt/AWTRenderer.java 19 Mar 2005 00:47:55 -0000 @@ -26,145 +26,46 @@ */ // Java -import java.awt.Color; -import java.awt.Component; -import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; -import java.awt.RenderingHints; import java.awt.Toolkit; -import java.awt.color.ColorSpace; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; -import java.awt.geom.AffineTransform; -import java.awt.geom.Line2D; -import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.ComponentColorModel; -import java.awt.image.DataBuffer; -import java.awt.image.DataBufferByte; -import java.awt.image.MemoryImageSource; -import java.awt.image.PixelInterleavedSampleModel; -import java.awt.image.Raster; -import java.awt.image.SampleModel; -import java.awt.image.WritableRaster; import java.awt.print.PageFormat; import java.awt.print.Pageable; +import java.awt.print.Paper; import java.awt.print.Printable; +import java.awt.print.PrinterException; import java.io.IOException; -import java.io.OutputStream; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Vector; - -import org.apache.batik.bridge.BridgeContext; -import org.apache.batik.bridge.GVTBuilder; -import org.apache.batik.bridge.ViewBox; -import org.apache.batik.gvt.GraphicsNode; + import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.area.Area; -import org.apache.fop.area.Block; -import org.apache.fop.area.BlockViewport; -import org.apache.fop.area.CTM; -import org.apache.fop.area.Page; import org.apache.fop.area.PageViewport; -import org.apache.fop.area.Trait; -import org.apache.fop.area.inline.Character; -import org.apache.fop.area.inline.ForeignObject; -import org.apache.fop.area.inline.Image; -import org.apache.fop.area.inline.InlineArea; -import org.apache.fop.area.inline.Leader; -import org.apache.fop.area.inline.TextArea; import org.apache.fop.datatypes.ColorType; -import org.apache.fop.fo.Constants; import org.apache.fop.fo.properties.ColorTypeProperty; -import org.apache.fop.fonts.Font; -import org.apache.fop.fonts.FontInfo; -import org.apache.fop.fonts.FontMetrics; -import org.apache.fop.image.FopImage; -import org.apache.fop.image.ImageFactory; -import org.apache.fop.image.XMLImage; -import org.apache.fop.render.AbstractRenderer; -import org.apache.fop.render.RendererContext; import org.apache.fop.render.awt.viewer.PreviewDialog; import org.apache.fop.render.awt.viewer.Translator; -import org.apache.fop.render.pdf.CTMHelper; -import org.apache.fop.svg.SVGUserAgent; -import org.apache.fop.traits.BorderProps; -import org.w3c.dom.Document; -import org.w3c.dom.svg.SVGDocument; -import org.w3c.dom.svg.SVGSVGElement; +import org.apache.fop.render.java2d.Java2DRenderer; /** - * The Java2DRenderer class provides the abstract technical - * foundation for all rendering with the Java2D API. Renderers like - * AWTRenderer subclass it and provide the concrete output paths. - *

    - * A lot of the logic is performed by AbstractRenderer. The - * class-variables currentIPPosition and - * currentBPPosition hold the position of the currently rendered - * area. - *

    - * AWTGraphicsState state holds the Graphics2D, - * which is used along the whole rendering. state also acts as a - * stack (state.push() and state.pop()). - *

    - * The rendering process is basically always the same: - *

    - * void renderXXXXX(Area area) { - * //calculate the currentPosition - * state.updateFont(name, size, null); - * state.updateColor(ct, false, null); - * state.getGraph.draw(new Shape(args)); - * } - * + * The AWTRender outputs the pages generated by the layout engine to a Swing + * window. This Swing window serves as default viewer for the -awt switch and as + * an example of how to embed the AWTRenderer into an AWT/Swing application. */ -public class AWTRenderer extends AbstractRenderer implements Printable, - Pageable { +public class AWTRenderer extends Java2DRenderer implements Pageable, Printable { /** The MIME type for AWT-Rendering */ - public static final String MIME_TYPE = "application/awt"; - - protected double scaleFactor = 100.0; - - protected int pageNumber = 0; - - private int pageWidth = 0; - - private int pageHeight = 0; - - private Vector pageViewportList = new java.util.Vector(); - - private Vector pageList = new java.util.Vector(); - - private Vector bufferedImageList = new java.util.Vector(); - - protected BufferedImage currentPageImage = null; - - protected boolean antialiasing = true; - - protected boolean qualityRendering = true; - - /** The current state, holds a Graphics2D and its context */ - protected AWTGraphicsState state; - - /** a Line2D.Float used to draw text decorations and leaders */ - protected Line2D.Float line = new Line2D.Float(); - - /** Font configuration */ - protected FontInfo fontInfo; + public static final String MIME_TYPE = "application/X-awt"; /** The resource bundle used for AWT messages. */ protected Translator translator = null; - private Map fontNames = new java.util.Hashtable(); - - private Map fontStyles = new java.util.Hashtable(); + /** flag for debugging */ + public boolean debug; /** * The preview dialog frame used for display of the documents. Also used as @@ -172,95 +73,34 @@ */ protected PreviewDialog frame; - /** Flag for visual-debugging */ - public boolean debug = false; - public AWTRenderer() { translator = new Translator(); } public void setUserAgent(FOUserAgent foUserAgent) { super.setUserAgent(foUserAgent); - userAgent.setRendererOverride(this); // for document regeneration createPreviewDialog(); } - public FOUserAgent getUserAgent() { - return userAgent; - } - - /** - * @see org.apache.fop.render.Renderer - */ - public boolean supportsOutOfOrder() { - return false; - } - - public Translator getTranslator() { - return translator; - } - - /** @see org.apache.fop.render.AbstractRenderer */ - public String getMimeType() { - return MIME_TYPE; - } - - public void setupFontInfo(FontInfo inFontInfo) { - // create a temp Image to test font metrics on - fontInfo = inFontInfo; - BufferedImage fontImage = new BufferedImage(100, 100, - BufferedImage.TYPE_INT_RGB); - FontSetup.setup(fontInfo, fontImage.createGraphics()); - } - - public int getPageNumber() { - return pageNumber; - } - - public void setPageNumber(int aValue) { - pageNumber = aValue; - } + public void renderPage(PageViewport pageViewport) throws IOException, + FOPException { - public void setScaleFactor(double newScaleFactor) { - scaleFactor = newScaleFactor; - } + super.renderPage(pageViewport); - public double getScaleFactor() { - return scaleFactor; - } + // Shows the page if it's the first one + if (getCurrentPageNumber() == 1) { + frame.showPage(); + } + frame.setInfo(); - public void startRenderer(OutputStream out) throws IOException { - // empty pageViewportList, in case of a reload from PreviewDialog - pageViewportList.removeAllElements(); - pageList.removeAllElements(); - bufferedImageList.removeAllElements(); - System.out.println("\nRegion Types: 0-Before/Top, 1-Start/Left," - + " 2-Body, 3-End/Right, 4-After/Bottom"); } public void stopRenderer() throws IOException { + super.stopRenderer(); frame.setStatus(translator.getString("Status.Show")); - frame.showPage(); - // TODO set all vars to null for gc - } - - // Printable Interface - public PageFormat getPageFormat(int pos) { - return null; - } - - public Printable getPrintable(int pos) { - return null; - } - - public int getNumberOfPages() { - return pageViewportList.size(); - } - - public int print(Graphics g, PageFormat format, int pos) { - return 0; } + /** Creates and initialize the AWT Viewer main window */ private PreviewDialog createPreviewDialog() { frame = new PreviewDialog(userAgent); frame.addWindowListener(new WindowAdapter() { @@ -280,320 +120,92 @@ } frame.setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2); - frame.setVisible(true); frame.setStatus(translator.getString("Status.Build.FO.tree")); + frame.setVisible(true); return frame; } /** - * This method override only stores the PageViewport in a vector. No actual - * rendering performed -- this is done by getPageImage(pageNum) instead. - * - * @param pageViewport the PageViewport object supplied by - * the Area Tree - * @see org.apache.fop.render.Renderer - */ - public void renderPage(PageViewport pageViewport) throws IOException, - FOPException { - pageViewportList.add(pageViewport); - pageList.add(pageViewport.getPage().clone()); - bufferedImageList - .add(getPageImage(pageViewport, pageViewport.getPage())); - } - - public BufferedImage getBufferedPageImage(int pageNum) throws FOPException { - return (BufferedImage) bufferedImageList.get(pageNum); - } - - /** - * Generates a desired page from the renderer's page viewport vector. - * - * @param pageNum the 0-based page number to generate - * @return the java.awt.image.BufferedImage corresponding to - * the page - * @throws FOPException in case of an out-of-range page number requested + * @see java.awt.print.Printable#print(java.awt.Graphics, + * java.awt.print.PageFormat, int) */ - public BufferedImage getPageImage(PageViewport pageViewport, Page page) - throws FOPException { - - Rectangle2D bounds = pageViewport.getViewArea(); - pageWidth = (int) Math.round(bounds.getWidth() / 1000f); - pageHeight = (int) Math.round(bounds.getHeight() / 1000f); - - getLogger().info( - "Rendering Page " + pageViewport.getPageNumberString() - + " (pageWidth " + pageWidth + ", pageHeight " - + pageHeight + ")"); - - currentPageImage = new BufferedImage( - (int) ((pageWidth * (int) scaleFactor) / 100), - (int) ((pageHeight * (int) scaleFactor) / 100), - BufferedImage.TYPE_INT_RGB); - - Graphics2D graphics = currentPageImage.createGraphics(); - graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, - RenderingHints.VALUE_FRACTIONALMETRICS_ON); - if (antialiasing) { - graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - } - if (qualityRendering) { - graphics.setRenderingHint(RenderingHints.KEY_RENDERING, - RenderingHints.VALUE_RENDER_QUALITY); + public int print(Graphics g, PageFormat pageFormat, int pageIndex) + throws PrinterException { + if (pageIndex >= getNumberOfPages()) { + return NO_SUCH_PAGE; } - // transform page based on scale factor supplied - AffineTransform at = graphics.getTransform(); - at.scale(scaleFactor / 100.0, scaleFactor / 100.0); - graphics.setTransform(at); - - // draw page frame - graphics.setColor(Color.white); - graphics.fillRect(0, 0, pageWidth, pageHeight); - graphics.setColor(Color.black); - graphics.drawRect(-1, -1, pageWidth + 2, pageHeight + 2); - graphics.drawLine(pageWidth + 2, 0, pageWidth + 2, pageHeight + 2); - graphics.drawLine(pageWidth + 3, 1, pageWidth + 3, pageHeight + 3); - graphics.drawLine(0, pageHeight + 2, pageWidth + 2, pageHeight + 2); - graphics.drawLine(1, pageHeight + 3, pageWidth + 3, pageHeight + 3); - - state = new AWTGraphicsState(graphics, this.fontInfo); - - // reset the current Positions - currentBPPosition = 0; - currentIPPosition = 0; - - // this toggles the rendering of all areas - renderPageAreas(page); - return currentPageImage; - } + Graphics2D g2 = (Graphics2D) g; - /** - * Generates a desired page from the renderer's page viewport vector. - * - * @param pageNum the 0-based page number to generate - * @return the java.awt.image.BufferedImage corresponding to - * the page - * @throws FOPException in case of an out-of-range page number requested - */ - public BufferedImage getPageImage(int pageNum) throws FOPException { - if (pageNum < 0 || pageNum >= pageViewportList.size()) { - throw new FOPException("out-of-range page number (" + pageNum - + ") requested; only " + pageViewportList.size() - + " page(s) available."); + BufferedImage image; + try { + image = getPageImage(pageIndex); + } catch (FOPException e) { + e.printStackTrace(); + return NO_SUCH_PAGE; } - PageViewport pageViewport = (PageViewport) pageViewportList - .get(pageNum); - Page page = (Page) pageList.get(pageNum); - return getPageImage(pageViewport, page); - } - - /** - * @see org.apache.fop.render.AbstractRenderer#startVParea(CTM) - */ - protected void startVParea(CTM ctm) { - - // push (and save) the current graphics state - state.push(); - // Set the given CTM in the graphics state - state.setTransform(new AffineTransform(CTMHelper.toPDFArray(ctm))); + g2.drawImage(image, null, 0, 0); - // TODO Set clip? - } - - /** - * @see org.apache.fop.render.AbstractRenderer#endVParea() - */ - protected void endVParea() { - state.pop(); + return PAGE_EXISTS; } - /** - * @see org.apache.fop.render.AbstractRenderer#renderBlockViewport(BlockViewport, - * List) - */ - protected void renderBlockViewport(BlockViewport bv, List children) { - // clip and position viewport if necessary - - // save positions - int saveIP = currentIPPosition; - int saveBP = currentBPPosition; - - CTM ctm = bv.getCTM(); - int borderPaddingStart = bv.getBorderAndPaddingWidthStart(); - int borderPaddingBefore = bv.getBorderAndPaddingWidthBefore(); - float x, y; - x = (float) (bv.getXOffset() + containingIPPosition) / 1000f; - y = (float) (bv.getYOffset() + containingBPPosition) / 1000f; - - if (bv.getPositioning() == Block.ABSOLUTE - || bv.getPositioning() == Block.FIXED) { - // TODO not tested yet - // For FIXED, we need to break out of the current viewports to the - // one established by the page. We save the state stack for - // restoration - // after the block-container has been painted. See below. - List breakOutList = null; - if (bv.getPositioning() == Block.FIXED) { - getLogger().debug("Block.FIXED --> break out"); - breakOutList = new java.util.ArrayList(); - Graphics2D graph; - while (true) { - graph = state.getGraph(); - if (state.pop() == null) { - break; - } - breakOutList.add(0, graph); // Insert because of - // stack-popping - getLogger().debug("Adding to break out list: " + graph); - } - } - - CTM tempctm = new CTM(containingIPPosition, containingBPPosition); - ctm = tempctm.multiply(ctm); - - // This is the content-rect - float width = (float) bv.getIPD() / 1000f; - float height = (float) bv.getBPD() / 1000f; - - // Adjust for spaces (from margin or indirectly by start-indent etc. - Integer spaceStart = (Integer) bv.getTrait(Trait.SPACE_START); - if (spaceStart != null) { - x += spaceStart.floatValue() / 1000; - } - Integer spaceBefore = (Integer) bv.getTrait(Trait.SPACE_BEFORE); - if (spaceBefore != null) { - y += spaceBefore.floatValue() / 1000; - } - - float bpwidth = (borderPaddingStart + bv - .getBorderAndPaddingWidthEnd()) / 1000f; - float bpheight = (borderPaddingBefore + bv - .getBorderAndPaddingWidthAfter()) / 1000f; - - drawBackAndBorders(bv, x, y, width + bpwidth, height + bpheight); - - // Now adjust for border/padding - x += borderPaddingStart / 1000f; - y += borderPaddingBefore / 1000f; - - if (bv.getClip()) { - // saves the graphics state in a stack - state.push(); - - clip(x, y, width, height); - } - - startVParea(ctm); - - renderBlocks(bv, children); - endVParea(); + /** @see java.awt.print.Pageable#getPageFormat(int) */ + public PageFormat getPageFormat(int pageIndex) + throws IndexOutOfBoundsException { + if (pageIndex >= getNumberOfPages()) + return null; - if (bv.getClip()) { - // restores the last graphics state from the stack - state.pop(); - } + PageFormat pageFormat = new PageFormat(); - // clip if necessary + Paper paper = new Paper(); + pageFormat.setPaper(paper); - if (breakOutList != null) { - getLogger().debug( - "Block.FIXED --> restoring context after break-out"); - Graphics2D graph; - Iterator i = breakOutList.iterator(); - while (i.hasNext()) { - graph = (Graphics2D) i.next(); - getLogger().debug("Restoring: " + graph); - state.push(); - } - } + Rectangle2D dim = getPageViewport(pageIndex).getViewArea(); + double width = dim.getWidth(); + double height = dim.getHeight(); - currentIPPosition = saveIP; - currentBPPosition = saveBP; - - } else { // orientation = Block.STACK or RELATIVE - - Integer spaceBefore = (Integer) bv.getTrait(Trait.SPACE_BEFORE); - if (spaceBefore != null) { - currentBPPosition += spaceBefore.intValue(); - } - - // borders and background in the old coordinate system - handleBlockTraits(bv); - - CTM tempctm = new CTM(containingIPPosition, currentBPPosition - + containingBPPosition); - ctm = tempctm.multiply(ctm); - - // Now adjust for border/padding - x += borderPaddingStart / 1000f; - y += borderPaddingBefore / 1000f; - - // clip if necessary - if (bv.getClip()) { - // saves the graphics state in a stack - state.push(); - float width = (float) bv.getIPD() / 1000f; - float height = (float) bv.getBPD() / 1000f; - clip(x, y, width, height); - } - - if (ctm != null) { - startVParea(ctm); - } - renderBlocks(bv, children); - if (ctm != null) { - endVParea(); - } - - if (bv.getClip()) { - // restores the last graphics state from the stack - state.pop(); - } + // if the width is greater than the height assume lanscape mode + // and swap the width and height values in the paper format + if (width > height) { + paper.setImageableArea(0, 0, height / 1000d, width / 1000d); + paper.setSize(height / 1000d, width / 1000d); + pageFormat.setOrientation(PageFormat.LANDSCAPE); + } else { + paper.setImageableArea(0, 0, width / 1000d, height / 1000d); + paper.setSize(width / 1000d, height / 1000d); + pageFormat.setOrientation(PageFormat.PORTRAIT); + } + return pageFormat; + } - currentIPPosition = saveIP; - currentBPPosition = saveBP; + /** @see java.awt.print.Pageable#getPrintable(int) */ + public Printable getPrintable(int pageIndex) + throws IndexOutOfBoundsException { + return this; + } - // Adjust BP position (alloc BPD + spaces) - if (spaceBefore != null) { - currentBPPosition += spaceBefore.intValue(); - } - currentBPPosition += (int) (bv.getAllocBPD()); - Integer spaceAfter = (Integer) bv.getTrait(Trait.SPACE_AFTER); - if (spaceAfter != null) { - currentBPPosition += spaceAfter.intValue(); - } - } + /** @see org.apache.fop.render.Renderer */ + public boolean supportsOutOfOrder() { + return true; // TODO true? } - /** - * Clip an area. - */ - protected void clip() { - // TODO via AWTGraphicsState.updateClip(); - // currentStream.add("W\n"); - // currentStream.add("n\n"); + /** @return the Translator for this renderer */ + public Translator getTranslator() { + return translator; } - /** - * Clip an area. write a clipping operation given coordinates in the current - * transform. - * @param x the x coordinate - * @param y the y coordinate - * @param width the width of the area - * @param height the height of the area - */ - protected void clip(float x, float y, float width, float height) { - // TODO via AWTGraphicsState.updateClip(); - // currentStream.add(x + " " + y + " " + width + " " + height + " - // re "); - clip(); + /** @see org.apache.fop.render.AbstractRenderer */ + public String getMimeType() { + return MIME_TYPE; } /** - * Draw the background and borders. This draws the background and border - * traits for an area given the position. + * Draws the background and borders and adds a basic debug view // TODO + * implement visual-debugging as standalone + * + * @see org.apache.fop.render.java2d.Java2DRenderer#drawBackAndBorders(org.apache.fop.area.Area, + * float, float, float, float) * * @param block the area to get the traits from * @param startx the start x position @@ -604,130 +216,11 @@ protected void drawBackAndBorders(Area area, float startx, float starty, float width, float height) { - if (debug) { // TODO implement visual-debugging as standalone - // Renderer + if (debug) { debugBackAndBorders(area, startx, starty, width, height); } - BorderProps bpsBefore = (BorderProps) area - .getTrait(Trait.BORDER_BEFORE); - BorderProps bpsAfter = (BorderProps) area.getTrait(Trait.BORDER_AFTER); - BorderProps bpsStart = (BorderProps) area.getTrait(Trait.BORDER_START); - BorderProps bpsEnd = (BorderProps) area.getTrait(Trait.BORDER_END); - - // draw background - Trait.Background back; - back = (Trait.Background) area.getTrait(Trait.BACKGROUND); - if (back != null) { - - // Calculate padding rectangle - float sx = startx; - float sy = starty; - float paddRectWidth = width; - float paddRectHeight = height; - - if (bpsStart != null) { - sx += bpsStart.width / 1000f; - paddRectWidth -= bpsStart.width / 1000f; - } - if (bpsBefore != null) { - sy += bpsBefore.width / 1000f; - paddRectHeight -= bpsBefore.width / 1000f; - } - if (bpsEnd != null) { - paddRectWidth -= bpsEnd.width / 1000f; - } - if (bpsAfter != null) { - paddRectHeight -= bpsAfter.width / 1000f; - } - - if (back.getColor() != null) { - drawBackground(back, sx, sy, paddRectWidth, paddRectHeight); - } - - // background image - if (back.getFopImage() != null) { - FopImage fopimage = back.getFopImage(); - if (fopimage != null && fopimage.load(FopImage.DIMENSIONS)) { - clip(sx, sy, paddRectWidth, paddRectHeight); - int horzCount = (int) ((paddRectWidth * 1000 / fopimage - .getIntrinsicWidth()) + 1.0f); - int vertCount = (int) ((paddRectHeight * 1000 / fopimage - .getIntrinsicHeight()) + 1.0f); - if (back.getRepeat() == EN_NOREPEAT) { - horzCount = 1; - vertCount = 1; - } else if (back.getRepeat() == EN_REPEATX) { - vertCount = 1; - } else if (back.getRepeat() == EN_REPEATY) { - horzCount = 1; - } - // change from points to millipoints - sx *= 1000; - sy *= 1000; - if (horzCount == 1) { - sx += back.getHoriz(); - } - if (vertCount == 1) { - sy += back.getVertical(); - } - for (int x = 0; x < horzCount; x++) { - for (int y = 0; y < vertCount; y++) { - // place once - Rectangle2D pos; - pos = new Rectangle2D.Float(sx - + (x * fopimage.getIntrinsicWidth()), sy - + (y * fopimage.getIntrinsicHeight()), - fopimage.getIntrinsicWidth(), fopimage - .getIntrinsicHeight()); - putImage(back.getURL(), pos); // TODO test - } - } - - } else { - getLogger().warn( - "Can't find background image: " + back.getURL()); - } - } - } - - // draw border - // BORDER_BEFORE - if (bpsBefore != null) { - int borderWidth = (int) Math.round((bpsBefore.width / 1000f) - * (scaleFactor / 100f)); - state.updateColor(bpsBefore.color); - state.getGraph().fillRect((int) startx, (int) starty, (int) width, - borderWidth); - } - // BORDER_AFTER - if (bpsAfter != null) { - int borderWidth = (int) Math.round((bpsAfter.width / 1000f) - * (scaleFactor / 100f)); - float sy = starty + height; - state.updateColor(bpsAfter.color); - state.getGraph().fillRect((int) startx, - (int) (starty + height - borderWidth), (int) width, - borderWidth); - } - // BORDER_START - if (bpsStart != null) { - int borderWidth = (int) Math.round((bpsStart.width / 1000f) - * (scaleFactor / 100f)); - state.updateColor(bpsStart.color); - state.getGraph().fillRect((int) startx, (int) starty, borderWidth, - (int) height); - } - // BORDER_END - if (bpsEnd != null) { - int borderWidth = (int) Math.round((bpsEnd.width / 1000f) - * (scaleFactor / 100f)); - float sx = startx + width; - state.updateColor(bpsEnd.color); - state.getGraph().fillRect((int) (startx + width - borderWidth), - (int) starty, borderWidth, (int) height); - } - + super.drawBackAndBorders(area, startx, starty, width, height); } /** Draws a thin border around every area to help debugging */ @@ -746,452 +239,4 @@ // restores the last graphics state from the stack state.pop(); } - - /** - * Draw the Background Rectangle of a given area. - * - * @param back the Trait.Background - * @param sx x coordinate of the rectangle to be filled. - * @param sy y the y coordinate of the rectangle to be filled. - * @param paddRectWidth the width of the rectangle to be filled. - * @param paddRectHeight the height of the rectangle to be filled. - */ - protected void drawBackground(Trait.Background back, float sx, float sy, - float paddRectWidth, float paddRectHeight) { - - state.updateColor(back.getColor()); - state.getGraph().fillRect((int) sx, (int) sy, (int) paddRectWidth, - (int) paddRectHeight); - } - - /** - * Handle block traits. The block could be any sort of block with any - * positioning so this should render the traits such as border and - * background in its position. - * - * @param block the block to render the traits - */ - protected void handleBlockTraits(Block block) { - // copied from pdf - int borderPaddingStart = block.getBorderAndPaddingWidthStart(); - int borderPaddingBefore = block.getBorderAndPaddingWidthBefore(); - - float startx = currentIPPosition / 1000f; - float starty = currentBPPosition / 1000f; - float width = block.getIPD() / 1000f; - float height = block.getBPD() / 1000f; - - startx += block.getStartIndent() / 1000f; - startx -= block.getBorderAndPaddingWidthStart() / 1000f; - width += borderPaddingStart / 1000f; - width += block.getBorderAndPaddingWidthEnd() / 1000f; - height += borderPaddingBefore / 1000f; - height += block.getBorderAndPaddingWidthAfter() / 1000f; - - drawBackAndBorders(block, startx, starty, width, height); - } - - /** - * @see org.apache.fop.render.Renderer#renderText(TextArea) - */ - public void renderText(TextArea text) { - - float x = currentIPPosition; - float y = currentBPPosition + text.getOffset(); // baseline - - String name = (String) text.getTrait(Trait.FONT_NAME); - int size = ((Integer) text.getTrait(Trait.FONT_SIZE)).intValue(); - state.updateFont(name, size, null); - - ColorType ct = (ColorType) text.getTrait(Trait.COLOR); - state.updateColor(ct, false, null); - - String s = text.getTextArea(); - state.getGraph().drawString(s, x / 1000f, y / 1000f); - - getLogger().debug( - "renderText(): \"" + s + "\", x: " + x + ", y: " + y + state); - - // rendering text decorations - FontMetrics metrics = fontInfo.getMetricsFor(name); - Font fs = new Font(name, metrics, size); - renderTextDecoration(fs, text, y, x); - - super.renderText(text); - } - - /** - * @see org.apache.fop.render.Renderer#renderCharacter(Character) - */ - public void renderCharacter(Character ch) { - - float x = currentIPPosition; - float y = currentBPPosition + ch.getOffset(); // baseline - - String name = (String) ch.getTrait(Trait.FONT_NAME); - int size = ((Integer) ch.getTrait(Trait.FONT_SIZE)).intValue(); - state.updateFont(name, size, null); - - ColorType ct = (ColorType) ch.getTrait(Trait.COLOR); - state.updateColor(ct, false, null); - - String s = ch.getChar(); - state.getGraph().drawString(s, x / 1000f, y / 1000f); - - getLogger().debug( - "renderCharacter(): \"" + s + "\", x: " + x + ", y: " + y - + state); - - // rendering text decorations - FontMetrics metrics = fontInfo.getMetricsFor(name); - Font fs = new Font(name, metrics, size); - renderTextDecoration(fs, ch, y, x); - - super.renderCharacter(ch); - } - - /** - * Paints the text decoration marks. - * @param fs Current font - * @param inline inline area to paint the marks for - * @param baseline position of the baseline - * @param startIPD start IPD - */ - protected void renderTextDecoration(Font fs, InlineArea inline, - float baseline, float startIPD) { - - boolean hasTextDeco = inline.hasUnderline() || inline.hasOverline() - || inline.hasLineThrough(); - - if (hasTextDeco) { - state.updateStroke((fs.getDescender() / (-8 * 1000f)), - Constants.EN_SOLID); - float endIPD = startIPD + inline.getIPD(); - if (inline.hasUnderline()) { - ColorType ct = (ColorType) inline - .getTrait(Trait.UNDERLINE_COLOR); - state.updateColor(ct, false, null); - float y = baseline - fs.getDescender() / 2; - line.setLine(startIPD / 1000f, y / 1000f, endIPD / 1000f, - y / 1000f); - state.getGraph().draw(line); - } - if (inline.hasOverline()) { - ColorType ct = (ColorType) inline - .getTrait(Trait.OVERLINE_COLOR); - state.updateColor(ct, false, null); - float y = (float) (baseline - (1.1 * fs.getCapHeight())); - line.setLine(startIPD / 1000f, y / 1000f, endIPD / 1000f, - y / 1000f); - state.getGraph().draw(line); - } - if (inline.hasLineThrough()) { - ColorType ct = (ColorType) inline - .getTrait(Trait.LINETHROUGH_COLOR); - state.updateColor(ct, false, null); - float y = (float) (baseline - (0.45 * fs.getCapHeight())); - line.setLine(startIPD / 1000f, y / 1000f, endIPD / 1000f, - y / 1000f); - state.getGraph().draw(line); - } - } - } - - /** - * Render leader area. This renders a leader area which is an area with a - * rule. - * - * @param area the leader area to render - */ - public void renderLeader(Leader area) { - - // TODO leader-length: 25%, 50%, 75%, 100% not working yet - // TODO Colors do not work on Leaders yet - - float startx = ((float) currentIPPosition) / 1000f; - float starty = ((currentBPPosition + area.getOffset()) / 1000f); - float endx = (currentIPPosition + area.getIPD()) / 1000f; - - ColorType ct = (ColorType) area.getTrait(Trait.COLOR); - state.updateColor(ct, true, null); - - line.setLine(startx, starty, endx, starty); - float thickness = area.getRuleThickness() / 1000f; - - int style = area.getRuleStyle(); - switch (style) { - case EN_SOLID: - case EN_DOTTED: - case EN_DASHED: - state.updateStroke(thickness, style); - state.getGraph().draw(line); - break; - case EN_DOUBLE: - - state.updateStroke(thickness / 3f, EN_SOLID); // only a third - - // upper Leader - line.setLine(startx, starty, endx, starty); - state.getGraph().draw(line); - // lower Leader - line.setLine(startx, starty + 2 * thickness, endx, starty + 2 - * thickness); - state.getGraph().draw(line); - - break; - - case EN_GROOVE: - // The rule looks as though it were carved into the canvas. - // (Top/left half of the rule's thickness is the - // color specified; the other half is white.) - - state.updateStroke(thickness / 2f, EN_SOLID); // only the half - - // upper Leader - line.setLine(startx, starty, endx, starty); - state.getGraph().draw(line); - // lower Leader - line.setLine(startx, starty + thickness, endx, starty + thickness); - state.getGraph().setColor(Color.WHITE); - state.getGraph().draw(line); - - // TODO the implementation could be nicer, f.eg. with triangles at - // the tip of the lines. See also RenderX's implementation (looks - // like a button) - - break; - - case EN_RIDGE: - // The opposite of "groove", the rule looks as though it were - // coming out of the canvas. (Bottom/right half of the rule's - // thickness is the color specified; the other half is white.) - - state.updateStroke(thickness / 2f, EN_SOLID); // only the half - - // lower Leader - line.setLine(startx, starty + thickness, endx, starty + thickness); - state.getGraph().draw(line); - // upperLeader - line.setLine(startx, starty, endx, starty); - state.getGraph().setColor(Color.WHITE); - state.getGraph().draw(line); - - // TODO the implementation could be nicer, f.eg. with triangles at - // the tip of the lines. See also RenderX's implementation (looks - // like a button) - - break; - - case EN_NONE: - // No rule is drawn - break; - - } // end switch - - super.renderLeader(area); - } - - /** - * @see org.apache.fop.render.AbstractRenderer#renderImage(Image, - * Rectangle2D) - */ - public void renderImage(Image image, Rectangle2D pos) { - // endTextObject(); - String url = image.getURL(); - putImage(url, pos); - } - - /** - * draws an image - * - * @param url URL of the bitmap - * @param pos Position of the bitmap - */ - protected void putImage(String pUrl, Rectangle2D pos) { - - int x = currentIPPosition; // TODO + area.getXOffset(); - int y = currentBPPosition; - String url = ImageFactory.getURL(pUrl); - - ImageFactory fact = ImageFactory.getInstance(); - FopImage fopimage = fact.getImage(url, userAgent); - - if (fopimage == null) { - return; - } - if (!fopimage.load(FopImage.DIMENSIONS)) { - return; - } - int w = fopimage.getWidth(); - int h = fopimage.getHeight(); - String mime = fopimage.getMimeType(); - if ("text/xml".equals(mime)) { - if (!fopimage.load(FopImage.ORIGINAL_DATA)) { - return; - } - Document doc = ((XMLImage) fopimage).getDocument(); - String ns = ((XMLImage) fopimage).getNameSpace(); - renderDocument(doc, ns, pos); - - } else if ("image/svg+xml".equals(mime)) { - if (!fopimage.load(FopImage.ORIGINAL_DATA)) { - return; - } - Document doc = ((XMLImage) fopimage).getDocument(); - renderSVGDocument(doc, pos); // TODO check if ok. - - } else if ("image/eps".equals(mime)) { - getLogger().warn("EPS images are not supported by this renderer"); - currentBPPosition += (h * 1000); - } else if ("image/jpeg".equals(mime)) { - if (!fopimage.load(FopImage.ORIGINAL_DATA)) { - return; - } - - // TODO Load JPEGs rather through fopimage.load(FopImage.BITMAP), - // but JpegImage will need to be extended for that - - //url = url.substring(7); - //url = "C:/eclipse/myWorkbenches/fop4/xml-fop/examples/fo" + url; - java.awt.Image awtImage = new javax.swing.ImageIcon(url).getImage(); - - state.getGraph().drawImage(awtImage, (int) (x / 1000f), - (int) (y / 1000f), (int) w, h, null); - currentBPPosition += (h * 1000); - } else { - if (!fopimage.load(FopImage.BITMAP)) { - getLogger().warn("Loading of bitmap failed: " + url); - return; - } - - byte[] raw = fopimage.getBitmaps(); - - //TODO Hardcoded color and sample models, FIX ME! - ColorModel cm = new ComponentColorModel( - ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB), - false, false, ColorModel.OPAQUE, DataBuffer.TYPE_BYTE); - SampleModel sampleModel = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, - w, h, 3, w * 3, new int[] {0, 1, 2}); - DataBuffer dbuf = new DataBufferByte(raw, w * h * 3); - - WritableRaster raster = Raster.createWritableRaster(sampleModel, dbuf, null); - - java.awt.Image awtImage; - // Combine the color model and raster into a buffered image - awtImage = new BufferedImage(cm, raster, false, null); - - state.getGraph().drawImage(awtImage, (int) (x / 1000f), - (int) (y / 1000f), (int) w, h, null); - currentBPPosition += (h * 1000); - } - } - - /** - * @see org.apache.fop.render.AbstractRenderer#renderForeignObject(ForeignObject, - * Rectangle2D) - */ - public void renderForeignObject(ForeignObject fo, Rectangle2D pos) { - Document doc = fo.getDocument(); - String ns = fo.getNameSpace(); - if (ns.equals("http://www.w3.org/2000/svg")) { - renderSVGDocument(doc, pos); - } else { - renderDocument(doc, ns, pos); - } - // this.currentXPosition += area.getContentWidth(); - } - - /** - * Renders an XML document (SVG for example). - * - * @param doc DOM document representing the XML document - * @param ns Namespace for the document - * @param pos Position on the page - */ - public void renderDocument(Document doc, String ns, Rectangle2D pos) { - RendererContext context; - context = new RendererContext(MIME_TYPE); - context.setUserAgent(userAgent); - // TODO implement - /* - * context.setProperty(PDFXMLHandler.PDF_DOCUMENT, pdfDoc); - * context.setProperty(PDFXMLHandler.OUTPUT_STREAM, ostream); - * context.setProperty(PDFXMLHandler.PDF_STATE, currentState); - * context.setProperty(PDFXMLHandler.PDF_PAGE, currentPage); - * context.setProperty(PDFXMLHandler.PDF_CONTEXT, currentContext == null ? - * currentPage : currentContext); - * context.setProperty(PDFXMLHandler.PDF_CONTEXT, currentContext); - * context.setProperty(PDFXMLHandler.PDF_STREAM, currentStream); - * context.setProperty(PDFXMLHandler.PDF_XPOS, new - * Integer(currentIPPosition + (int) pos.getX())); - * context.setProperty(PDFXMLHandler.PDF_YPOS, new - * Integer(currentBPPosition + (int) pos.getY())); - * context.setProperty(PDFXMLHandler.PDF_FONT_INFO, fontInfo); - * context.setProperty(PDFXMLHandler.PDF_FONT_NAME, currentFontName); - * context.setProperty(PDFXMLHandler.PDF_FONT_SIZE, new - * Integer(currentFontSize)); - * context.setProperty(PDFXMLHandler.PDF_WIDTH, new Integer((int) - * pos.getWidth())); context.setProperty(PDFXMLHandler.PDF_HEIGHT, new - * Integer((int) pos.getHeight())); renderXML(userAgent, context, doc, - * ns); - */ - } - - protected void renderSVGDocument(Document doc, Rectangle2D pos) { - - int x = currentIPPosition; // TODO + area.getXOffset(); - int y = currentBPPosition; - - RendererContext context; - context = new RendererContext(MIME_TYPE); - context.setUserAgent(userAgent); - - SVGUserAgent ua = new SVGUserAgent(context.getUserAgent() - .getPixelUnitToMillimeter(), new AffineTransform()); - - GVTBuilder builder = new GVTBuilder(); - BridgeContext ctx = new BridgeContext(ua); - - GraphicsNode root; - try { - root = builder.build(ctx, doc); - } catch (Exception e) { - getLogger().error( - "svg graphic could not be built: " + e.getMessage(), e); - return; - } - float w = (float) ctx.getDocumentSize().getWidth() * 1000f; - float h = (float) ctx.getDocumentSize().getHeight() * 1000f; - - // correct integer roundoff - state.getGraph().translate(x / 1000, y / 1000); - - SVGSVGElement svg = ((SVGDocument) doc).getRootElement(); - AffineTransform at = ViewBox.getPreserveAspectRatioTransform(svg, - w / 1000f, h / 1000f); - AffineTransform inverse = null; - try { - inverse = at.createInverse(); - } catch (NoninvertibleTransformException e) { - getLogger().warn(e); - } - if (!at.isIdentity()) { - state.getGraph().transform(at); - } - - try { - root.paint(state.getGraph()); - } catch (Exception e) { - e.printStackTrace(); - } - - if (inverse != null && !inverse.isIdentity()) { - state.getGraph().transform(inverse); - } - // correct integer roundoff - // currentState.getCurrentGraphics().translate(-x / 1000f, y / 1000f - - // pageHeight); - state.getGraph().translate(-(x + 500) / 1000, - (y + 500) / 1000 - pageHeight); - } } Index: src/java/org/apache/fop/render/awt/FontMetricsMapper.java =================================================================== RCS file: src/java/org/apache/fop/render/awt/FontMetricsMapper.java diff -N src/java/org/apache/fop/render/awt/FontMetricsMapper.java --- src/java/org/apache/fop/render/awt/FontMetricsMapper.java 27 Feb 2004 17:51:22 -0000 1.2 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,166 +0,0 @@ -/* - * Copyright 1999-2004 The Apache Software Foundation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* $Id$ */ - -package org.apache.fop.render.awt; - -// Java -import java.awt.Graphics2D; -import java.util.Map; - -// FOP -import org.apache.fop.fonts.FontMetrics; -import org.apache.fop.fonts.FontType; - - -/** - * This class implements org.apache.fop.layout.FontMetrics and - * is added to the hash table in FontInfo. It deferes the - * actual calculation of the metrics to - * AWTFontMetrics. It only keeps the java name and - * style as member varibles - */ - -public class FontMetricsMapper implements FontMetrics { - - /** - * The first and last non space-character - */ - private static final int FIRST_CHAR = 32; - private static final int LAST_CHAR = 255; - - /** - * This is a AWTFontMetrics that does the real calculation. - * It is only one class that dynamically determines the font-size. - */ - private static AWTFontMetrics metric = null; - - /** - * The java name of the font. - * # Make the family name immutable. - */ - private final String family; - - /** - * The java style of the font. - * # Make the style immutable. - */ - private final int style; - - /** - * Constructs a new Font-metrics. - * @param family the family name of the font (java value) - * @param style the java type style value of the font - * @param graphics a Graphics2D object - this is needed so - * that we can get an instance of java.awt.FontMetrics - */ - public FontMetricsMapper(String family, int style, Graphics2D graphics) { - this.family = family; - this.style = style; - if (metric == null) { - metric = new AWTFontMetrics(graphics); - } - } - - /** - * @see org.apache.fop.fonts.FontMetrics#getFontName() - */ - public String getFontName() { - return family; - } - - /** - * @see org.apache.fop.fonts.FontMetrics#getFontType() - */ - public FontType getFontType() { - return FontType.OTHER; - } - - /** - * @see org.apache.fop.fonts.FontMetrics#getAscender(int) - */ - public int getAscender(int size) { - return metric.getAscender(family, style, size); - } - - /** - * @see org.apache.fop.fonts.FontMetrics#getCapHeight(int) - */ - public int getCapHeight(int size) { - return metric.getCapHeight(family, style, size); - } - - /** - * @see org.apache.fop.fonts.FontMetrics#getDescender(int) - */ - public int getDescender(int size) { - return metric.getDescender(family, style, size); - } - - /** - * @see org.apache.fop.fonts.FontMetrics#getXHeight(int) - */ - public int getXHeight(int size) { - return metric.getXHeight(family, style, size); - } - - /** - * @see org.apache.fop.fonts.FontMetrics#getWidth(int, int) - */ - public int getWidth(int i, int size) { - return metric.width(i, family, style, size); - } - - - /** - * @see org.apache.fop.fonts.FontMetrics#getWidths() - */ - public int[] getWidths() { - return metric.getWidths(family, style, AWTFontMetrics.FONT_SIZE); - } - - /** - * Gets a Font instance of the Font that this - * FontMetrics describes in the desired size. - * @param size font size - * @return font with the desired characeristics. - */ - public java.awt.Font getFont(int size) { - return metric.getFont(family, style, size); - } - - /** - * @see org.apache.fop.fonts.FontMetrics#getKerningInfo() - */ - public Map getKerningInfo() { - return java.util.Collections.EMPTY_MAP; - } - - /** - * @see org.apache.fop.fonts.FontMetrics#hasKerningInfo() - */ - public boolean hasKerningInfo() { - return false; - } - - -} - - - - - Index: src/java/org/apache/fop/render/awt/FontSetup.java =================================================================== RCS file: src/java/org/apache/fop/render/awt/FontSetup.java diff -N src/java/org/apache/fop/render/awt/FontSetup.java --- src/java/org/apache/fop/render/awt/FontSetup.java 22 Apr 2004 21:38:40 -0000 1.6 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,182 +0,0 @@ -/* - * Copyright 1999-2004 The Apache Software Foundation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* $Id$ */ - -package org.apache.fop.render.awt; - -// FOP -import org.apache.fop.fonts.FontInfo; -import org.apache.fop.fonts.Font; - -// Java -import java.awt.Graphics2D; - -/** - * Sets up the AWT fonts. It is similar to - * org.apache.fop.render.pdf.FontSetup. - * Assigns the font (with metrics) to internal names like "F1" and - * assigns family-style-weight triplets to the fonts. - */ -public class FontSetup { - - - /** - * Sets up the font info object. - * - * Adds metrics for basic fonts and useful family-style-weight - * triplets for lookup. - * - * @param fontInfo the font info object to set up - * @param graphics needed for acces to font metrics - */ - public static void setup(FontInfo fontInfo, Graphics2D graphics) { - FontMetricsMapper metric; - int normal, bold, bolditalic, italic; - - /* - * available java fonts are: - * Serif - bold, normal, italic, bold-italic - * SansSerif - bold, normal, italic, bold-italic - * MonoSpaced - bold, normal, italic, bold-italic - */ - normal = java.awt.Font.PLAIN; - bold = java.awt.Font.BOLD; - italic = java.awt.Font.ITALIC; - bolditalic = java.awt.Font.BOLD + java.awt.Font.ITALIC; - - metric = new FontMetricsMapper("SansSerif", normal, graphics); - // --> goes to F1 - fontInfo.addMetrics("F1", metric); - metric = new FontMetricsMapper("SansSerif", italic, graphics); - // --> goes to F2 - fontInfo.addMetrics("F2", metric); - metric = new FontMetricsMapper("SansSerif", bold, graphics); - // --> goes to F3 - fontInfo.addMetrics("F3", metric); - metric = new FontMetricsMapper("SansSerif", bolditalic, graphics); - // --> goes to F4 - fontInfo.addMetrics("F4", metric); - - - metric = new FontMetricsMapper("Serif", normal, graphics); - // --> goes to F5 - fontInfo.addMetrics("F5", metric); - metric = new FontMetricsMapper("Serif", italic, graphics); - // --> goes to F6 - fontInfo.addMetrics("F6", metric); - metric = new FontMetricsMapper("Serif", bold, graphics); - // --> goes to F7 - fontInfo.addMetrics("F7", metric); - metric = new FontMetricsMapper("Serif", bolditalic, graphics); - // --> goes to F8 - fontInfo.addMetrics("F8", metric); - - metric = new FontMetricsMapper("MonoSpaced", normal, graphics); - // --> goes to F9 - fontInfo.addMetrics("F9", metric); - metric = new FontMetricsMapper("MonoSpaced", italic, graphics); - // --> goes to F10 - fontInfo.addMetrics("F10", metric); - metric = new FontMetricsMapper("MonoSpaced", bold, graphics); - // --> goes to F11 - fontInfo.addMetrics("F11", metric); - metric = new FontMetricsMapper("MonoSpaced", bolditalic, graphics); - // --> goes to F12 - fontInfo.addMetrics("F12", metric); - - metric = new FontMetricsMapper("Symbol", bolditalic, graphics); - // --> goes to F13 and F14 - fontInfo.addMetrics("F13", metric); - fontInfo.addMetrics("F14", metric); - - // Custom type 1 fonts step 1/2 - // fontInfo.addMetrics("F15", new OMEP()); - // fontInfo.addMetrics("F16", new GaramondLightCondensed()); - // fontInfo.addMetrics("F17", new BauerBodoniBoldItalic()); - - /* any is treated as serif */ - fontInfo.addFontProperties("F5", "any", "normal", Font.NORMAL); - fontInfo.addFontProperties("F6", "any", "italic", Font.NORMAL); - fontInfo.addFontProperties("F6", "any", "oblique", Font.NORMAL); - fontInfo.addFontProperties("F7", "any", "normal", Font.BOLD); - fontInfo.addFontProperties("F8", "any", "italic", Font.BOLD); - fontInfo.addFontProperties("F8", "any", "oblique", Font.BOLD); - - fontInfo.addFontProperties("F1", "sans-serif", "normal", Font.NORMAL); - fontInfo.addFontProperties("F2", "sans-serif", "oblique", Font.NORMAL); - fontInfo.addFontProperties("F2", "sans-serif", "italic", Font.NORMAL); - fontInfo.addFontProperties("F3", "sans-serif", "normal", Font.BOLD); - fontInfo.addFontProperties("F4", "sans-serif", "oblique", Font.BOLD); - fontInfo.addFontProperties("F4", "sans-serif", "italic", Font.BOLD); - fontInfo.addFontProperties("F5", "serif", "normal", Font.NORMAL); - fontInfo.addFontProperties("F6", "serif", "oblique", Font.NORMAL); - fontInfo.addFontProperties("F6", "serif", "italic", Font.NORMAL); - fontInfo.addFontProperties("F7", "serif", "normal", Font.BOLD); - fontInfo.addFontProperties("F8", "serif", "oblique", Font.BOLD); - fontInfo.addFontProperties("F8", "serif", "italic", Font.BOLD); - fontInfo.addFontProperties("F9", "monospace", "normal", Font.NORMAL); - fontInfo.addFontProperties("F10", "monospace", "oblique", Font.NORMAL); - fontInfo.addFontProperties("F10", "monospace", "italic", Font.NORMAL); - fontInfo.addFontProperties("F11", "monospace", "normal", Font.BOLD); - fontInfo.addFontProperties("F12", "monospace", "oblique", Font.BOLD); - fontInfo.addFontProperties("F12", "monospace", "italic", Font.BOLD); - - fontInfo.addFontProperties("F1", "Helvetica", "normal", Font.NORMAL); - fontInfo.addFontProperties("F2", "Helvetica", "oblique", Font.NORMAL); - fontInfo.addFontProperties("F2", "Helvetica", "italic", Font.NORMAL); - fontInfo.addFontProperties("F3", "Helvetica", "normal", Font.BOLD); - fontInfo.addFontProperties("F4", "Helvetica", "oblique", Font.BOLD); - fontInfo.addFontProperties("F4", "Helvetica", "italic", Font.BOLD); - fontInfo.addFontProperties("F5", "Times", "normal", Font.NORMAL); - fontInfo.addFontProperties("F6", "Times", "oblique", Font.NORMAL); - fontInfo.addFontProperties("F6", "Times", "italic", Font.NORMAL); - fontInfo.addFontProperties("F7", "Times", "normal", Font.BOLD); - fontInfo.addFontProperties("F8", "Times", "oblique", Font.BOLD); - fontInfo.addFontProperties("F8", "Times", "italic", Font.BOLD); - fontInfo.addFontProperties("F9", "Courier", "normal", Font.NORMAL); - fontInfo.addFontProperties("F10", "Courier", "oblique", Font.NORMAL); - fontInfo.addFontProperties("F10", "Courier", "italic", Font.NORMAL); - fontInfo.addFontProperties("F11", "Courier", "normal", Font.BOLD); - fontInfo.addFontProperties("F12", "Courier", "oblique", Font.BOLD); - fontInfo.addFontProperties("F12", "Courier", "italic", Font.BOLD); - fontInfo.addFontProperties("F13", "Symbol", "normal", Font.NORMAL); - fontInfo.addFontProperties("F14", "ZapfDingbats", "normal", Font.NORMAL); - - // Custom type 1 fonts step 2/2 - // fontInfo.addFontProperties("F15", "OMEP", "normal", FontInfo.NORMAL); - // fontInfo.addFontProperties("F16", "Garamond-LightCondensed", "normal", FontInfo.NORMAL); - // fontInfo.addFontProperties("F17", "BauerBodoni", "italic", FontInfo.BOLD); - - /* for compatibility with PassiveTex */ - fontInfo.addFontProperties("F5", "Times-Roman", "normal", Font.NORMAL); - fontInfo.addFontProperties("F6", "Times-Roman", "oblique", Font.NORMAL); - fontInfo.addFontProperties("F6", "Times-Roman", "italic", Font.NORMAL); - fontInfo.addFontProperties("F7", "Times-Roman", "normal", Font.BOLD); - fontInfo.addFontProperties("F8", "Times-Roman", "oblique", Font.BOLD); - fontInfo.addFontProperties("F8", "Times-Roman", "italic", Font.BOLD); - fontInfo.addFontProperties("F5", "Times Roman", "normal", Font.NORMAL); - fontInfo.addFontProperties("F6", "Times Roman", "oblique", Font.NORMAL); - fontInfo.addFontProperties("F6", "Times Roman", "italic", Font.NORMAL); - fontInfo.addFontProperties("F7", "Times Roman", "normal", Font.BOLD); - fontInfo.addFontProperties("F8", "Times Roman", "oblique", Font.BOLD); - fontInfo.addFontProperties("F8", "Times Roman", "italic", Font.BOLD); - fontInfo.addFontProperties("F9", "Computer-Modern-Typewriter", - "normal", Font.NORMAL); - } - -} - Index: src/java/org/apache/fop/render/awt/RendererState.java =================================================================== RCS file: src/java/org/apache/fop/render/awt/RendererState.java diff -N src/java/org/apache/fop/render/awt/RendererState.java --- src/java/org/apache/fop/render/awt/RendererState.java 8 Mar 2005 11:47:55 -0000 1.1 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,145 +0,0 @@ -/* - * Copyright 2005 The Apache Software Foundation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* $Id: RendererState.java,v 1.1 2005/03/08 11:47:55 jeremias Exp $ */ - -package org.apache.fop.render.awt; - -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.Shape; -import java.awt.geom.AffineTransform; - -import org.apache.fop.datatypes.ColorType; - -/** - * An interface for the classes which hold the state of the current graphics context. - */ -public interface RendererState { - - /** - * Push the current state onto the stack. - */ - public abstract void push(); - - /** - * Pop the state from the stack and restore the graphics context. - * @return the restored state, null if the stack is empty. - */ - public abstract Graphics2D pop(); - - /** - * Get the current stack level. - * - * @return the current stack level - */ - public abstract int getStackLevel(); - - /** - * Establishes a new foreground or fill color. - * @param col the color to apply (null skips this operation) - * @param fill true to set the fill color, false for the foreground color - * @param pdf only used by the PDFRenderer, is set to null. - * @return true if the new Color changes the current Color - */ - public abstract boolean updateColor(ColorType col, boolean fill, StringBuffer pdf); - - /** - * Set the current font name. Check if the font name will change and then - * set the new name. - * - * @param name the new font name - * @param size - * @param pdf - * @return true if the new Font changes the current Font - */ - public abstract boolean updateFont(String name, int size, StringBuffer pdf); - - /** - * Sets the current Stroke. The line width should be set with - * updateLineWidth() before calling this method - * - * @param style the constant for the style of the line as an int - * @return true if the new Stroke changes the current Stroke - */ - public abstract boolean updateStroke(float width, int style); - - /** - * Set the current paint. This checks if the paint will change and then sets - * the current paint. - * - * @param p the new paint - * @return true if the new paint changes the current paint - */ - public abstract boolean updatePaint(Paint p); - - /** - * Check if the clip will change the current state. A clip is assumed to be - * used in a situation where it will add to any clip in the current or - * parent states. A clip cannot be cleared, this can only be achieved by - * going to a parent level with the correct clip. If the clip is different - * then it may start a new state so that it can return to the previous clip. - * - * @param cl the clip shape to check - * @return true if the clip will change the current clip. - */ - // TODO test - public abstract boolean checkClip(Shape cl); - - /** - * Set the current clip. This either sets a new clip or sets the clip to the - * intersect of the old clip and the new clip. - * - * @param cl the new clip in the current state - */ - public abstract boolean updateClip(Shape cl); - - /** - * Check the current transform. The transform for the current state is the - * combination of all transforms in the current state. The parameter is - * compared against this current transform. - * - * @param tf the transform to check against - * @return true if the new transform is different from the current transform - */ - public abstract boolean checkTransform(AffineTransform tf); - - /** - * Overwrites the Transform in the Graphics2D context. Use transform() if you - * wish to compose with the current Affinetransform instead. - * @see java.awt.Graphics2D.setTransform(). - * @param tf the transform to concatonate to the current level transform - */ - public abstract void setTransform(AffineTransform tf); - - /** - * Composes an AffineTransform object with the Transform in this Graphics2D - * according to the rule last-specified-first-applied. - * @see java.awt.Graphics2D.transform(). - * - * @param tf the transform to concatonate to the current level transform - */ - public abstract void transform(AffineTransform tf); - - /** - * Get the current transform. This gets the combination of all transforms in - * the current state. - * - * @return the calculate combined transform for the current state - */ - public abstract AffineTransform getTransform(); - -} Index: src/java/org/apache/fop/render/awt/viewer/PreviewDialog.java =================================================================== RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/render/awt/viewer/PreviewDialog.java,v retrieving revision 1.8 diff -u -r1.8 PreviewDialog.java --- src/java/org/apache/fop/render/awt/viewer/PreviewDialog.java 8 Mar 2005 11:47:55 -0000 1.8 +++ src/java/org/apache/fop/render/awt/viewer/PreviewDialog.java 19 Mar 2005 00:47:56 -0000 @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,14 +15,17 @@ */ /* $Id: PreviewDialog.java,v 1.8 2005/03/08 11:47:55 jeremias Exp $ */ - + +// Originally contributed by: +// Juergen Verwohlt: Juergen.Verwohlt@jCatalog.com, +// Rainer Steinkuhle: Rainer.Steinkuhle@jCatalog.com, +// Stanislav Gorkhover: Stanislav.Gorkhover@jCatalog.com package org.apache.fop.render.awt.viewer; -//Java +// Java import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; -import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; @@ -44,9 +47,11 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; +import javax.swing.JButton; import javax.swing.JToolBar; import javax.swing.SwingUtilities; import javax.swing.UIManager; +import javax.swing.border.EmptyBorder; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; @@ -54,15 +59,9 @@ import org.apache.fop.fo.Constants; import org.apache.fop.render.awt.AWTRenderer; -/** - * AWT Viewer main window. - * Originally contributed by: - * Juergen Verwohlt: Juergen.Verwohlt@jCatalog.com, - * Rainer Steinkuhle: Rainer.Steinkuhle@jCatalog.com, - * Stanislav Gorkhover: Stanislav.Gorkhover@jCatalog.com - */ +/** AWT Viewer main window. */ public class PreviewDialog extends JFrame { - + /** The Translator for localization */ protected Translator translator; /** The AWT renderer */ @@ -72,12 +71,22 @@ /** The Fop object used for refreshing/reloading the view */ protected Fop fop; + /** The number of the page which is currently visible */ private int currentPage = 0; - private int pageCount = 0; + + /** The Reloader, when the user clicks on menu "reload" */ private Reloader reloader; + + /** The JCombobox to rescale the rendered page view */ private JComboBox scale; + + /** The JLabel for the process status bar */ private JLabel processStatus; + + /** The JLabel that holds the rendered page */ private JLabel pageLabel; + + /** The JLabel information status bar */ private JLabel infoStatus; /** @@ -92,7 +101,7 @@ //Commands aka Actions Command printAction = new Command(translator.getString("Menu.Print"), "Print") { public void doit() { - print(); + startPrinterJob(); } }; Command firstPageAction = new Command(translator.getString("Menu.First.page"), @@ -124,11 +133,16 @@ } }; Command debugAction = new Command(" Debug") { - //TODO use Translator + // TODO use Translator public void doit() { debug(); } }; + Command aboutAction = new Command("About FOP", "fopLogo") { + public void doit() { + startHelpAbout(); + } + }; //set the system look&feel try { @@ -146,6 +160,8 @@ //Page view stuff pageLabel = new JLabel(); + pageLabel.setHorizontalAlignment(0 /* CENTER */); + pageLabel.setBorder(new EmptyBorder(20, 0, 20, 0)); JScrollPane previewArea = new JScrollPane(pageLabel); previewArea.getViewport().setBackground(Color.gray); previewArea.setMinimumSize(new Dimension(50, 50)); @@ -159,20 +175,20 @@ scale.addItem("100%"); scale.addItem("150%"); scale.addItem("200%"); + scale.addItem("400%"); scale.setMaximumSize(new Dimension(80, 24)); scale.setPreferredSize(new Dimension(80, 24)); + scale.setSelectedItem("100%"); scale.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scaleActionPerformed(e); } }); - scale.setSelectedItem("100%"); - renderer.setScaleFactor(100.0); //Menu setJMenuBar(setupMenu()); - //Toolbar + // Toolbar JToolBar toolBar = new JToolBar(); toolBar.add(printAction); toolBar.add(reloadAction); @@ -181,21 +197,24 @@ toolBar.add(previousPageAction); toolBar.add(nextPageAction); toolBar.add(lastPageAction); - toolBar.addSeparator(); + toolBar.addSeparator(new Dimension(20,0)); toolBar.add(new JLabel(translator.getString("Menu.Zoom"))); toolBar.add(scale); toolBar.addSeparator(); toolBar.add(debugAction); + toolBar.addSeparator(new Dimension(60,0)); + toolBar.add(aboutAction); getContentPane().add(toolBar, BorderLayout.NORTH); - //Status bar + + // Status bar JPanel statusBar = new JPanel(); processStatus = new JLabel(); processStatus.setBorder(BorderFactory.createCompoundBorder( - BorderFactory.createEtchedBorder(), + BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(0, 3, 0, 0))); infoStatus = new JLabel(); infoStatus.setBorder(BorderFactory.createCompoundBorder( - BorderFactory.createEtchedBorder(), + BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(0, 3, 0, 0))); statusBar.setLayout(new GridBagLayout()); @@ -229,7 +248,7 @@ //Adds mostly the same actions, but without icons menu.add(new Command(translator.getString("Menu.Print")) { public void doit() { - print(); + startPrinterJob(); } }); // inputHandler must be set to allow reloading @@ -305,6 +324,11 @@ setScale(200.0); } }); + subMenu.add(new Command("400%") { + public void doit() { + setScale(400.0); + } + }); menu.add(subMenu); menu.addSeparator(); menu.add(new Command(translator.getString("Menu.Default.zoom")) { @@ -323,9 +347,7 @@ return menuBar; } - /** - * Shows the About box - */ + /** Shows the About box */ private void startHelpAbout() { PreviewDialogAboutBox dlg = new PreviewDialogAboutBox(this, translator); //Centers the box @@ -343,8 +365,8 @@ */ private void goToPage(int number) { currentPage = number; - renderer.setPageNumber(number); showPage(); + setInfo(); } /** @@ -358,12 +380,11 @@ goToPage(currentPage); } - /** * Shows the next page. */ private void goToNextPage() { - if (currentPage >= pageCount - 1) { + if (currentPage >= renderer.getNumberOfPages() - 1) { return; } currentPage++; @@ -374,10 +395,10 @@ * Shows the last page. */ private void goToLastPage() { - if (currentPage == pageCount - 1) { + if (currentPage == renderer.getNumberOfPages() - 1) { return; } - currentPage = pageCount - 1; + currentPage = renderer.getNumberOfPages() - 1; goToPage(currentPage); } @@ -396,26 +417,38 @@ */ private void debug(){ renderer.debug = !renderer.debug; - showPage(); + reload(); } /** - * This class is used to reload document in - * a thread safe way. + * This class is used to reload document in a thread safe way. */ private class Reloader extends Thread { public void run() { + + if (!renderer.renderingDone) { + // do not allow the reloading while FOP is + // still rendering + JOptionPane.showMessageDialog(getContentPane(), + "Cannot perform the requested operation until " + + "all page are rendererd. Please wait", + "Please wait ", 1 /* INFORMATION_MESSAGE */); + return; + } + if (fop == null) { fop = new Fop(Constants.RENDER_AWT, foUserAgent); } - + pageLabel.setIcon(null); - infoStatus.setText(""); + int savedCurrentPage = currentPage; currentPage = 0; + renderer.clearViewportList(); try { setStatus(translator.getString("Status.Build.FO.tree")); foUserAgent.getInputHandler().render(fop); + goToPage(savedCurrentPage); setStatus(translator.getString("Status.Show")); } catch (FOPException e) { reportException(e); @@ -433,16 +466,14 @@ (int)getLocation().getY() + 50); d.setVisible(true); currentPage = d.getPageNumber(); - if (currentPage < 1 || currentPage > pageCount) { + if (currentPage < 1 || currentPage > renderer.getNumberOfPages()) { return; } currentPage--; goToPage(currentPage); } - /** - * Shows the first page. - */ + /** Shows the first page. */ private void goToFirstPage() { if (currentPage == 0) { return; @@ -451,24 +482,20 @@ goToPage(currentPage); } - /** - * Prints the document - */ - private void print() { + /** Prints the document */ + private void startPrinterJob() { PrinterJob pj = PrinterJob.getPrinterJob(); pj.setPageable(renderer); if (pj.printDialog()) { try { pj.print(); - } catch (PrinterException pe) { - pe.printStackTrace(); + } catch (PrinterException e) { + e.printStackTrace(); } } } - /** - * Scales page image - */ + /** Scales page image */ private void setScale(double scaleFactor) { if (scaleFactor == 25.0) { scale.setSelectedIndex(0); @@ -482,11 +509,11 @@ scale.setSelectedIndex(4); } else if (scaleFactor == 200.0) { scale.setSelectedIndex(5); + } else if (scaleFactor == 400.0) { + scale.setSelectedIndex(6); } - renderer.setScaleFactor(scaleFactor); - if (renderer.getNumberOfPages() != 0) { - showPage(); - } + renderer.setScaleFactor(scaleFactor / 100d); + reload(); } private void scaleActionPerformed(ActionEvent e) { @@ -502,32 +529,50 @@ SwingUtilities.invokeLater(new ShowStatus(message)); } - /** - * This class is used to show status in a thread safe way. - */ + /** This class is used to show status in a thread safe way. */ private class ShowStatus implements Runnable { - /** - * The message to display - */ + + /** The message to display */ private String message; + /** - * Constructs ShowStatus thread + * Constructs ShowStatus thread * @param message message to display */ public ShowStatus(String message) { this.message = message; } - + public void run() { processStatus.setText(message.toString()); } } /** - * Starts rendering process and shows the current page. + * Updates the message to be shown in the info bar in a thread safe way. */ - public void showPage() { + public void setInfo() { + SwingUtilities.invokeLater(new ShowInfo()); + } + + /** This class is used to show info in a thread safe way. */ + private class ShowInfo implements Runnable { + + public void run() { + + String message = translator.getString("Status.Page") + " " + + (currentPage + 1) + " " + + translator.getString("Status.of") + " " + + renderer.getCurrentPageNumber(); + + infoStatus.setText(message); + } + } + + /** Starts rendering process and shows the current page. */ + public synchronized void showPage() { ShowPageImage viewer = new ShowPageImage(); + if (SwingUtilities.isEventDispatchThread()) { viewer.run(); } else { @@ -535,38 +580,25 @@ } } - - /** - * This class is used to update the page image - * in a thread safe way. - */ + /** This class is used to render the page image in a thread safe way. */ private class ShowPageImage implements Runnable { + /** - * The run method that does the actual updating + * The run method that does the actual rendering of the viewed page */ public void run() { + + setStatus(translator.getString("Status.Build.FO.tree")); + + BufferedImage pageImage = null; try { - BufferedImage pageImage = null; - Graphics graphics = null; - pageImage = renderer.getPageImage(currentPage); - if (pageImage == null) - return; - graphics = pageImage.getGraphics(); - graphics.setColor(Color.black); - graphics.drawRect(0, 0, pageImage.getWidth() - 1, - pageImage.getHeight() - 1); - - pageLabel.setIcon(new ImageIcon(pageImage)); - pageCount = renderer.getNumberOfPages(); - - // Update status bar - infoStatus.setText(translator.getString("Status.Page") + " " - + (currentPage + 1) + " " - + translator.getString("Status.of") + " " + pageCount); } catch (FOPException e) { reportException(e); } + pageLabel.setIcon(new ImageIcon(pageImage)); + + setStatus(translator.getString("Status.Show")); } } @@ -581,10 +613,9 @@ getContentPane(), "" + msg + ":
    " + e.getClass().getName() + "
    " - + e.getMessage() + "", + + e.getMessage() + "", translator.getString("Exception.Error"), JOptionPane.ERROR_MESSAGE ); } } - Index: src/java/org/apache/fop/render/awt/viewer/PreviewDialogAboutBox.java =================================================================== RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/render/awt/viewer/PreviewDialogAboutBox.java,v retrieving revision 1.3 diff -u -r1.3 PreviewDialogAboutBox.java --- src/java/org/apache/fop/render/awt/viewer/PreviewDialogAboutBox.java 23 Jun 2004 00:25:27 -0000 1.3 +++ src/java/org/apache/fop/render/awt/viewer/PreviewDialogAboutBox.java 19 Mar 2005 00:47:57 -0000 @@ -1,12 +1,12 @@ /* * Copyright 1999-2004 The Apache Software Foundation. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,7 +15,7 @@ */ /* $Id: PreviewDialogAboutBox.java,v 1.3 2004/06/23 00:25:27 gmazza Exp $ */ - + package org.apache.fop.render.awt.viewer; //Java @@ -65,9 +65,9 @@ JPanel insetsPanel3 = new JPanel(); okButton = new JButton(); JLabel imageControl1 = new JLabel(); - imageControl1.setIcon(new ImageIcon(getClass().getResource("images/fop.gif"))); + imageControl1.setIcon(new ImageIcon(getClass().getResource("images/logo_big.jpg"))); JLabel label1 = new JLabel(translator.getString("About.Product")); - JLabel label2 = new JLabel(translator.getString("About.Version") + JLabel label2 = new JLabel(translator.getString("About.Version") + " " + Fop.getVersion()); JLabel label3 = new JLabel(translator.getString("About.Copyright")); panel1.setLayout(new BorderLayout()); Index: src/java/org/apache/fop/render/svg/SVGRenderer.java =================================================================== RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/render/svg/SVGRenderer.java,v retrieving revision 1.21 diff -u -r1.21 SVGRenderer.java --- src/java/org/apache/fop/render/svg/SVGRenderer.java 24 Nov 2004 21:07:31 -0000 1.21 +++ src/java/org/apache/fop/render/svg/SVGRenderer.java 19 Mar 2005 00:47:58 -0000 @@ -137,7 +137,7 @@ // create a temp Image to test font metrics on BufferedImage fontImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); - org.apache.fop.render.awt.FontSetup.setup(fontInfo, + org.apache.fop.render.java2d.FontSetup.setup(fontInfo, fontImage.createGraphics()); } Index: src/java/org/apache/fop/tools/anttasks/Fop.java =================================================================== RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/tools/anttasks/Fop.java,v retrieving revision 1.19 diff -u -r1.19 Fop.java --- src/java/org/apache/fop/tools/anttasks/Fop.java 25 Jul 2004 17:04:44 -0000 1.19 +++ src/java/org/apache/fop/tools/anttasks/Fop.java 19 Mar 2005 00:48:00 -0000 @@ -1,12 +1,12 @@ /* * Copyright 1999-2004 The Apache Software Foundation. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -50,7 +50,7 @@ *

  • format -> MIME type of the format to generate ex. "application/pdf"
  • *
  • outfile -> output filename
  • *
  • baseDir -> directory to work from
  • - *
  • relativebase -> (true | false) control whether to use each FO's + *
  • relativebase -> (true | false) control whether to use each FO's * directory as base directory. false uses the baseDir parameter.
  • *
  • userconfig -> file with user configuration (same as the "-c" command * line option)
  • @@ -132,7 +132,7 @@ public void setRelativebase(boolean relbase) { this.relativebase = relbase; } - + /** * Gets the relative base attribute * @return the relative base attribute @@ -271,7 +271,7 @@ public boolean getLogFiles() { return this.logFiles; } - + /** * @see org.apache.tools.ant.Task#execute() */ @@ -354,6 +354,11 @@ || format.equalsIgnoreCase("at") || format.equalsIgnoreCase("xml")) { return Constants.RENDER_XML; + } else if (format.equalsIgnoreCase("tiff") + || format.equalsIgnoreCase("tif")) { + return Constants.RENDER_TIFF; + } else if (format.equalsIgnoreCase("png")) { + return Constants.RENDER_PNG; } else { String err = "Couldn't determine renderer to use: " + format; throw new BuildException(err); @@ -376,6 +381,10 @@ return ".txt"; case Constants.RENDER_XML: return ".xml"; + case Constants.RENDER_TIFF: + return ".tiff"; + case Constants.RENDER_PNG: + return ".png"; default: String err = "Unknown renderer: " + renderer; throw new BuildException(err); @@ -427,7 +436,7 @@ // actioncount = # of fofiles actually processed through FOP int actioncount = 0; // skippedcount = # of fofiles which haven't changed (force = "false") - int skippedcount = 0; + int skippedcount = 0; // deal with single source file if (task.getFofile() != null) { @@ -439,14 +448,14 @@ if (task.getOutdir() != null) { outf = new File(task.getOutdir(), outf.getName()); } - // Render if "force" flag is set OR + // Render if "force" flag is set OR // OR output file doesn't exist OR // output file is older than input file - if (task.getForce() || !outf.exists() + if (task.getForce() || !outf.exists() || (task.getFofile().lastModified() > outf.lastModified() )) { render(task.getFofile(), outf, rint); actioncount++; - } else if (outf.exists() + } else if (outf.exists() && (task.getFofile().lastModified() <= outf.lastModified() )) { skippedcount++; } @@ -491,10 +500,10 @@ task.log("Error setting base URL", Project.MSG_DEBUG); } - // Render if "force" flag is set OR + // Render if "force" flag is set OR // OR output file doesn't exist OR // output file is older than input file - if (task.getForce() || !outf.exists() + if (task.getForce() || !outf.exists() || (f.lastModified() > outf.lastModified() )) { render(f, outf, rint); actioncount++; @@ -503,11 +512,11 @@ } } } - + if (actioncount + skippedcount == 0) { task.log("No files processed. No files were selected by the filesets " + "and no fofile was set." , Project.MSG_WARN); - } else if (skippedcount > 0) { + } else if (skippedcount > 0) { task.log(skippedcount + " xslfo file(s) skipped (no change found" + " since last generation; set force=\"true\" to override)." , Project.MSG_INFO); @@ -532,7 +541,7 @@ try { FOUserAgent userAgent = new FOUserAgent(); userAgent.setBaseURL(this.baseURL); - org.apache.fop.apps.Fop fop = + org.apache.fop.apps.Fop fop = new org.apache.fop.apps.Fop(renderer, userAgent); fop.setOutputStream(out); inputHandler.render(fop); Index: .settings/org.eclipse.jdt.core.prefs =================================================================== RCS file: .settings/org.eclipse.jdt.core.prefs diff -N .settings/org.eclipse.jdt.core.prefs --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ .settings/org.eclipse.jdt.core.prefs 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,12 @@ +#Fri Mar 11 12:12:12 CET 2005 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=disabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.4 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning +org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning +org.eclipse.jdt.core.compiler.source=1.3 Index: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFDecodeParam.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFDecodeParam.java diff -N src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFDecodeParam.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFDecodeParam.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,157 @@ +/* + + Copyright 2001 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.codec.myTiff; + +import org.apache.batik.ext.awt.image.codec.ImageDecodeParam; + +/** + * An instance of ImageDecodeParam for decoding images in + * the TIFF format. + * + *

    To determine the number of images present in a TIFF file, use + * the getNumPages() method on the + * ImageDecoder object that will be used to perform the + * decoding. The desired page number may be passed as an argument to + * the ImageDecoder.decodeAsRaster)() or + * decodeAsRenderedImage() methods. + * + *

    For TIFF Palette color images, the colorMap always has entries + * of short data type, the color Black being represented by 0,0,0 and + * White by 65536,65536,65536. In order to display these images, the + * default behavior is to dither the short values down to 8 bits. + * The dithering is done by calling the decode16BitsTo8Bits + * method for each short value that needs to be dithered. The method has + * the following implementation: + * + * byte b; + * short s; + * s = s & 0xffff; + * b = (byte)((s >> 8) & 0xff); + * + * If a different algorithm is to be used for the dithering, this class + * should be subclassed and an appropriate implementation should be + * provided for the decode16BitsTo8Bits method in the subclass. + * + *

    If the palette contains image data that is signed short, as specified + * by the SampleFormat tag, the dithering is done by calling + * decodeSigned16BitsTo8Bits instead. The method has the + * following implementation: + * + * byte b; + * short s; + * b = (byte)((s + Short.MIN_VALUE) >> 8); + * + * In order to use a different algorithm for the dithering, this class + * should be subclassed and the method overridden. + * + *

    If it is desired that the Palette be decoded such that the output + * image is of short data type and no dithering is performed, the + * setDecodePaletteAsShorts method should be used. + * + *

    This class is not a committed part of the JAI API. It may + * be removed or changed in future releases of JAI. + * + * @see TIFFDirectory + */ +public class TIFFDecodeParam implements ImageDecodeParam { + + private boolean decodePaletteAsShorts = false; + private Long ifdOffset = null; + private boolean convertJPEGYCbCrToRGB = true; + + /** Constructs a default instance of TIFFDecodeParam. */ + public TIFFDecodeParam() { + } + + /** + * If set, the entries in the palette will be decoded as shorts + * and no short to byte lookup will be applied to them. + */ + public void setDecodePaletteAsShorts(boolean decodePaletteAsShorts) { + this.decodePaletteAsShorts = decodePaletteAsShorts; + } + + /** + * Returns true if palette entries will be decoded as + * shorts, resulting in an output image with short datatype. + */ + public boolean getDecodePaletteAsShorts() { + return decodePaletteAsShorts; + } + + /** + * Returns an unsigned 8 bit value computed by dithering the unsigned + * 16 bit value. Note that the TIFF specified short datatype is an + * unsigned value, while Java's short datatype is a + * signed value. Therefore the Java short datatype cannot + * be used to store the TIFF specified short value. A Java + * int is used as input instead to this method. The method + * deals correctly only with 16 bit unsigned values. + */ + public byte decode16BitsTo8Bits(int s) { + return (byte)((s >> 8) & 0xffff); + } + + /** + * Returns an unsigned 8 bit value computed by dithering the signed + * 16 bit value. This method deals correctly only with values in the + * 16 bit signed range. + */ + public byte decodeSigned16BitsTo8Bits(short s) { + return (byte)((s + Short.MIN_VALUE) >> 8); + } + + /** + * Sets the offset in the stream from which to read the image. There + * must be an Image File Directory (IFD) at that position or an error + * will occur. If setIFDOffset() is never invoked then + * the decoder will assume that the TIFF stream is at the beginning of + * the 8-byte image header. If the directory offset is set and a page + * number is supplied to the TIFF ImageDecoder then the + * page will be the zero-relative index of the IFD in linked list of + * IFDs beginning at the specified offset with a page of zero indicating + * the directory at that offset. + */ + public void setIFDOffset(long offset) { + ifdOffset = new Long(offset); + } + + /** + * Returns the value set by setIFDOffset() or + * null if no value has been set. + */ + public Long getIFDOffset() { + return ifdOffset; + } + + /** + * Sets a flag indicating whether to convert JPEG-compressed YCbCr data + * to RGB. The default value is true. This flag is + * ignored if the image data are not JPEG-compressed. + */ + public void setJPEGDecompressYCbCrToRGB(boolean convertJPEGYCbCrToRGB) { + this.convertJPEGYCbCrToRGB = convertJPEGYCbCrToRGB; + } + + /** + * Whether JPEG-compressed YCbCr data will be converted to RGB. + */ + public boolean getJPEGDecompressYCbCrToRGB() { + return convertJPEGYCbCrToRGB; + } +} Index: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFDirectory.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFDirectory.java diff -N src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFDirectory.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFDirectory.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,629 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.codec.myTiff; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Iterator; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +import org.apache.batik.ext.awt.image.codec.SeekableStream; + +/** + * A class representing an Image File Directory (IFD) from a TIFF 6.0 + * stream. The TIFF file format is described in more detail in the + * comments for the TIFFDescriptor class. + * + *

    A TIFF IFD consists of a set of TIFFField tags. Methods are + * provided to query the set of tags and to obtain the raw field + * array. In addition, convenience methods are provided for acquiring + * the values of tags that contain a single value that fits into a + * byte, int, long, float, or double. + * + *

    Every TIFF file is made up of one or more public IFDs that are + * joined in a linked list, rooted in the file header. A file may + * also contain so-called private IFDs that are referenced from + * tag data and do not appear in the main list. + * + *

    This class is not a committed part of the JAI API. It may + * be removed or changed in future releases of JAI. + * + * @see TIFFField + */ +public class TIFFDirectory extends Object implements Serializable { + + /** A boolean storing the endianness of the stream. */ + boolean isBigEndian; + + /** The number of entries in the IFD. */ + int numEntries; + + /** An array of TIFFFields. */ + TIFFField[] fields; + + /** A Hashtable indexing the fields by tag number. */ + Map fieldIndex = new HashMap(); + + /** The offset of this IFD. */ + long IFDOffset = 8; + + /** The offset of the next IFD. */ + long nextIFDOffset = 0; + + /** The default constructor. */ + TIFFDirectory() {} + + private static boolean isValidEndianTag(int endian) { + return ((endian == 0x4949) || (endian == 0x4d4d)); + } + + /** + * Constructs a TIFFDirectory from a SeekableStream. + * The directory parameter specifies which directory to read from + * the linked list present in the stream; directory 0 is normally + * read but it is possible to store multiple images in a single + * TIFF file by maintaing multiple directories. + * + * @param stream a SeekableStream to read from. + * @param directory the index of the directory to read. + */ + public TIFFDirectory(SeekableStream stream, int directory) + throws IOException { + + long global_save_offset = stream.getFilePointer(); + long ifd_offset; + + // Read the TIFF header + stream.seek(0L); + int endian = stream.readUnsignedShort(); + if (!isValidEndianTag(endian)) { + throw new + IllegalArgumentException("TIFFDirectory1"); + } + isBigEndian = (endian == 0x4d4d); + + int magic = readUnsignedShort(stream); + if (magic != 42) { + throw new + IllegalArgumentException("TIFFDirectory2"); + } + + // Get the initial ifd offset as an unsigned int (using a long) + ifd_offset = readUnsignedInt(stream); + + for (int i = 0; i < directory; i++) { + if (ifd_offset == 0L) { + throw new + IllegalArgumentException("TIFFDirectory3"); + } + + stream.seek(ifd_offset); + int entries = readUnsignedShort(stream); + stream.skip(12*entries); + + ifd_offset = readUnsignedInt(stream); + } + + stream.seek(ifd_offset); + initialize(stream); + stream.seek(global_save_offset); + } + + /** + * Constructs a TIFFDirectory by reading a SeekableStream. + * The ifd_offset parameter specifies the stream offset from which + * to begin reading; this mechanism is sometimes used to store + * private IFDs within a TIFF file that are not part of the normal + * sequence of IFDs. + * + * @param stream a SeekableStream to read from. + * @param ifd_offset the long byte offset of the directory. + * @param directory the index of the directory to read beyond the + * one at the current stream offset; zero indicates the IFD + * at the current offset. + */ + public TIFFDirectory(SeekableStream stream, long ifd_offset, int directory) + throws IOException { + + long global_save_offset = stream.getFilePointer(); + stream.seek(0L); + int endian = stream.readUnsignedShort(); + if (!isValidEndianTag(endian)) { + throw new + IllegalArgumentException("TIFFDirectory1"); + } + isBigEndian = (endian == 0x4d4d); + + // Seek to the first IFD. + stream.seek(ifd_offset); + + // Seek to desired IFD if necessary. + int dirNum = 0; + while(dirNum < directory) { + // Get the number of fields in the current IFD. + int numEntries = readUnsignedShort(stream); + + // Skip to the next IFD offset value field. + stream.seek(ifd_offset + 12*numEntries); + + // Read the offset to the next IFD beyond this one. + ifd_offset = readUnsignedInt(stream); + + // Seek to the next IFD. + stream.seek(ifd_offset); + + // Increment the directory. + dirNum++; + } + + initialize(stream); + stream.seek(global_save_offset); + } + + private static final int[] sizeOfType = { + 0, // 0 = n/a + 1, // 1 = byte + 1, // 2 = ascii + 2, // 3 = short + 4, // 4 = long + 8, // 5 = rational + 1, // 6 = sbyte + 1, // 7 = undefined + 2, // 8 = sshort + 4, // 9 = slong + 8, // 10 = srational + 4, // 11 = float + 8 // 12 = double + }; + + private void initialize(SeekableStream stream) throws IOException { + long nextTagOffset; + int i, j; + + IFDOffset = stream.getFilePointer(); + + numEntries = readUnsignedShort(stream); + fields = new TIFFField[numEntries]; + + for (i = 0; i < numEntries; i++) { + int tag = readUnsignedShort(stream); + int type = readUnsignedShort(stream); + int count = (int)(readUnsignedInt(stream)); + int value = 0; + + // The place to return to to read the next tag + nextTagOffset = stream.getFilePointer() + 4; + + try { + // If the tag data can't fit in 4 bytes, the next 4 bytes + // contain the starting offset of the data + if (count*sizeOfType[type] > 4) { + value = (int)(readUnsignedInt(stream)); + stream.seek(value); + } + } catch (ArrayIndexOutOfBoundsException ae) { + + System.err.println(tag + " " + "TIFFDirectory4"); + // if the data type is unknown we should skip this TIFF Field + stream.seek(nextTagOffset); + continue; + } + + fieldIndex.put(new Integer(tag), new Integer(i)); + Object obj = null; + + switch (type) { + case TIFFField.TIFF_BYTE: + case TIFFField.TIFF_SBYTE: + case TIFFField.TIFF_UNDEFINED: + case TIFFField.TIFF_ASCII: + byte[] bvalues = new byte[count]; + stream.readFully(bvalues, 0, count); + + if (type == TIFFField.TIFF_ASCII) { + + // Can be multiple strings + int index = 0, prevIndex = 0; + Vector v = new Vector(); + + while (index < count) { + + while ((index < count) && (bvalues[index++] != 0)); + + // When we encountered zero, means one string has ended + v.add(new String(bvalues, prevIndex, + (index - prevIndex)) ); + prevIndex = index; + } + + count = v.size(); + String strings[] = new String[count]; + for (int c = 0 ; c < count; c++) { + strings[c] = (String)v.elementAt(c); + } + + obj = strings; + } else { + obj = bvalues; + } + + break; + + case TIFFField.TIFF_SHORT: + char[] cvalues = new char[count]; + for (j = 0; j < count; j++) { + cvalues[j] = (char)(readUnsignedShort(stream)); + } + obj = cvalues; + break; + + case TIFFField.TIFF_LONG: + long[] lvalues = new long[count]; + for (j = 0; j < count; j++) { + lvalues[j] = readUnsignedInt(stream); + } + obj = lvalues; + break; + + case TIFFField.TIFF_RATIONAL: + long[][] llvalues = new long[count][2]; + for (j = 0; j < count; j++) { + llvalues[j][0] = readUnsignedInt(stream); + llvalues[j][1] = readUnsignedInt(stream); + } + obj = llvalues; + break; + + case TIFFField.TIFF_SSHORT: + short[] svalues = new short[count]; + for (j = 0; j < count; j++) { + svalues[j] = readShort(stream); + } + obj = svalues; + break; + + case TIFFField.TIFF_SLONG: + int[] ivalues = new int[count]; + for (j = 0; j < count; j++) { + ivalues[j] = readInt(stream); + } + obj = ivalues; + break; + + case TIFFField.TIFF_SRATIONAL: + int[][] iivalues = new int[count][2]; + for (j = 0; j < count; j++) { + iivalues[j][0] = readInt(stream); + iivalues[j][1] = readInt(stream); + } + obj = iivalues; + break; + + case TIFFField.TIFF_FLOAT: + float[] fvalues = new float[count]; + for (j = 0; j < count; j++) { + fvalues[j] = readFloat(stream); + } + obj = fvalues; + break; + + case TIFFField.TIFF_DOUBLE: + double[] dvalues = new double[count]; + for (j = 0; j < count; j++) { + dvalues[j] = readDouble(stream); + } + obj = dvalues; + break; + + default: + System.err.println("TIFFDirectory0"); + break; + } + + fields[i] = new TIFFField(tag, type, count, obj); + stream.seek(nextTagOffset); + } + + // Read the offset of the next IFD. + nextIFDOffset = readUnsignedInt(stream); + } + + /** Returns the number of directory entries. */ + public int getNumEntries() { + return numEntries; + } + + /** + * Returns the value of a given tag as a TIFFField, + * or null if the tag is not present. + */ + public TIFFField getField(int tag) { + Integer i = (Integer)fieldIndex.get(new Integer(tag)); + if (i == null) { + return null; + } else { + return fields[i.intValue()]; + } + } + + /** + * Returns true if a tag appears in the directory. + */ + public boolean isTagPresent(int tag) { + return fieldIndex.containsKey(new Integer(tag)); + } + + /** + * Returns an ordered array of ints indicating the tag + * values. + */ + public int[] getTags() { + int[] tags = new int[fieldIndex.size()]; + Iterator iter = fieldIndex.keySet().iterator(); + int i = 0; + + while (iter.hasNext()) { + tags[i++] = ((Integer)iter.next()).intValue(); + } + + return tags; + } + + /** + * Returns an array of TIFFFields containing all the fields + * in this directory. + */ + public TIFFField[] getFields() { + return fields; + } + + /** + * Returns the value of a particular index of a given tag as a + * byte. The caller is responsible for ensuring that the tag is + * present and has type TIFFField.TIFF_SBYTE, TIFF_BYTE, or + * TIFF_UNDEFINED. + */ + public byte getFieldAsByte(int tag, int index) { + Integer i = (Integer)fieldIndex.get(new Integer(tag)); + byte [] b = (fields[i.intValue()]).getAsBytes(); + return b[index]; + } + + /** + * Returns the value of index 0 of a given tag as a + * byte. The caller is responsible for ensuring that the tag is + * present and has type TIFFField.TIFF_SBYTE, TIFF_BYTE, or + * TIFF_UNDEFINED. + */ + public byte getFieldAsByte(int tag) { + return getFieldAsByte(tag, 0); + } + + /** + * Returns the value of a particular index of a given tag as a + * long. The caller is responsible for ensuring that the tag is + * present and has type TIFF_BYTE, TIFF_SBYTE, TIFF_UNDEFINED, + * TIFF_SHORT, TIFF_SSHORT, TIFF_SLONG or TIFF_LONG. + */ + public long getFieldAsLong(int tag, int index) { + Integer i = (Integer)fieldIndex.get(new Integer(tag)); + return (fields[i.intValue()]).getAsLong(index); + } + + /** + * Returns the value of index 0 of a given tag as a + * long. The caller is responsible for ensuring that the tag is + * present and has type TIFF_BYTE, TIFF_SBYTE, TIFF_UNDEFINED, + * TIFF_SHORT, TIFF_SSHORT, TIFF_SLONG or TIFF_LONG. + */ + public long getFieldAsLong(int tag) { + return getFieldAsLong(tag, 0); + } + + /** + * Returns the value of a particular index of a given tag as a + * float. The caller is responsible for ensuring that the tag is + * present and has numeric type (all but TIFF_UNDEFINED and + * TIFF_ASCII). + */ + public float getFieldAsFloat(int tag, int index) { + Integer i = (Integer)fieldIndex.get(new Integer(tag)); + return fields[i.intValue()].getAsFloat(index); + } + + /** + * Returns the value of index 0 of a given tag as a float. The + * caller is responsible for ensuring that the tag is present and + * has numeric type (all but TIFF_UNDEFINED and TIFF_ASCII). + */ + public float getFieldAsFloat(int tag) { + return getFieldAsFloat(tag, 0); + } + + /** + * Returns the value of a particular index of a given tag as a + * double. The caller is responsible for ensuring that the tag is + * present and has numeric type (all but TIFF_UNDEFINED and + * TIFF_ASCII). + */ + public double getFieldAsDouble(int tag, int index) { + Integer i = (Integer)fieldIndex.get(new Integer(tag)); + return fields[i.intValue()].getAsDouble(index); + } + + /** + * Returns the value of index 0 of a given tag as a double. The + * caller is responsible for ensuring that the tag is present and + * has numeric type (all but TIFF_UNDEFINED and TIFF_ASCII). + */ + public double getFieldAsDouble(int tag) { + return getFieldAsDouble(tag, 0); + } + + // Methods to read primitive data types from the stream + + private short readShort(SeekableStream stream) + throws IOException { + if (isBigEndian) { + return stream.readShort(); + } else { + return stream.readShortLE(); + } + } + + private int readUnsignedShort(SeekableStream stream) + throws IOException { + if (isBigEndian) { + return stream.readUnsignedShort(); + } else { + return stream.readUnsignedShortLE(); + } + } + + private int readInt(SeekableStream stream) + throws IOException { + if (isBigEndian) { + return stream.readInt(); + } else { + return stream.readIntLE(); + } + } + + private long readUnsignedInt(SeekableStream stream) + throws IOException { + if (isBigEndian) { + return stream.readUnsignedInt(); + } else { + return stream.readUnsignedIntLE(); + } + } + + private long readLong(SeekableStream stream) + throws IOException { + if (isBigEndian) { + return stream.readLong(); + } else { + return stream.readLongLE(); + } + } + + private float readFloat(SeekableStream stream) + throws IOException { + if (isBigEndian) { + return stream.readFloat(); + } else { + return stream.readFloatLE(); + } + } + + private double readDouble(SeekableStream stream) + throws IOException { + if (isBigEndian) { + return stream.readDouble(); + } else { + return stream.readDoubleLE(); + } + } + + private static int readUnsignedShort(SeekableStream stream, + boolean isBigEndian) + throws IOException { + if (isBigEndian) { + return stream.readUnsignedShort(); + } else { + return stream.readUnsignedShortLE(); + } + } + + private static long readUnsignedInt(SeekableStream stream, + boolean isBigEndian) + throws IOException { + if (isBigEndian) { + return stream.readUnsignedInt(); + } else { + return stream.readUnsignedIntLE(); + } + } + + // Utilities + + /** + * Returns the number of image directories (subimages) stored in a + * given TIFF file, represented by a SeekableStream. + */ + public static int getNumDirectories(SeekableStream stream) + throws IOException{ + long pointer = stream.getFilePointer(); // Save stream pointer + + stream.seek(0L); + int endian = stream.readUnsignedShort(); + if (!isValidEndianTag(endian)) { + throw new + IllegalArgumentException("TIFFDirectory1"); + } + boolean isBigEndian = (endian == 0x4d4d); + int magic = readUnsignedShort(stream, isBigEndian); + if (magic != 42) { + throw new + IllegalArgumentException("TIFFDirectory2"); + } + + stream.seek(4L); + long offset = readUnsignedInt(stream, isBigEndian); + + int numDirectories = 0; + while (offset != 0L) { + ++numDirectories; + + stream.seek(offset); + int entries = readUnsignedShort(stream, isBigEndian); + stream.skip(12*entries); + offset = readUnsignedInt(stream, isBigEndian); + } + + stream.seek(pointer); // Reset stream pointer + return numDirectories; + } + + /** + * Returns a boolean indicating whether the byte order used in the + * the TIFF file is big-endian (i.e. whether the byte order is from + * the most significant to the least significant) + */ + public boolean isBigEndian() { + return isBigEndian; + } + + /** + * Returns the offset of the IFD corresponding to this + * TIFFDirectory. + */ + public long getIFDOffset() { + return IFDOffset; + } + + /** + * Returns the offset of the next IFD after the IFD corresponding to this + * TIFFDirectory. + */ + public long getNextIFDOffset() { + return nextIFDOffset; + } +} Index: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFEncodeParam.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFEncodeParam.java diff -N src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFEncodeParam.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFEncodeParam.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,340 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.codec.myTiff; + +import java.util.Iterator; +import java.util.zip.Deflater; + +import org.apache.batik.ext.awt.image.codec.ImageEncodeParam; + +import com.sun.image.codec.jpeg.JPEGEncodeParam; + +/** + * An instance of ImageEncodeParam for encoding images in + * the TIFF format. + * + *

    This class allows for the specification of encoding parameters. By + * default, the image is encoded without any compression, and is written + * out consisting of strips, not tiles. The particular compression scheme + * to be used can be specified by using the setCompression() + * method. The compression scheme specified will be honored only if it is + * compatible with the type of image being written out. For example, + * Group3 and Group4 compressions can only be used with Bilevel images. + * Writing of tiled TIFF images can be enabled by calling the + * setWriteTiled() method. + * + *

    This class is not a committed part of the JAI API. It may + * be removed or changed in future releases of JAI. + * + */ +public class TIFFEncodeParam implements ImageEncodeParam { + + /** No compression. */ + public static final int COMPRESSION_NONE = 1; + + /** + * Modified Huffman Compression (CCITT Group 3 1D facsimile compression). + *

    Not currently supported. + */ + public static final int COMPRESSION_GROUP3_1D = 2; + + /** + * CCITT T.4 bilevel compression (CCITT Group 3 2D facsimile compression). + *

    Not currently supported. + */ + public static final int COMPRESSION_GROUP3_2D = 3; + + /** + * CCITT T.6 bilevel compression (CCITT Group 4 facsimile compression). + *

    Not currently supported. + */ + public static final int COMPRESSION_GROUP4 = 4; + + /** + * LZW compression. + *

    Not supported. + */ + public static final int COMPRESSION_LZW = 5; + + /** + * Code for original JPEG-in-TIFF compression which has been + * depricated (for many good reasons) in favor of Tech Note 2 + * JPEG compression (compression scheme 7). + *

    Not supported. + */ + public static final int COMPRESSION_JPEG_BROKEN = 6; + + /** + * + * JPEG-in-TIFF compression. + */ + public static final int COMPRESSION_JPEG_TTN2 = 7; + + /** Byte-oriented run-length encoding "PackBits" compression. */ + public static final int COMPRESSION_PACKBITS = 32773; + + /** + * + * DEFLATE lossless compression (also known as "Zip-in-TIFF"). + */ + public static final int COMPRESSION_DEFLATE = 32946; + + private int compression = COMPRESSION_NONE; + + private boolean writeTiled = false; + private int tileWidth; + private int tileHeight; + + private Iterator extraImages; + + private TIFFField[] extraFields; + + private boolean convertJPEGRGBToYCbCr = true; + private JPEGEncodeParam jpegEncodeParam = null; + + private int deflateLevel = Deflater.DEFAULT_COMPRESSION; + + /** + * Constructs a TIFFEncodeParam object with default values for + * all parameters. + */ + public TIFFEncodeParam() {} + + /** + * Returns the value of the compression parameter. + */ + public int getCompression() { + return compression; + } + + /** + * Specifies the type of compression to be used. The compression type + * specified will be honored only if it is compatible with the image + * being written out. Currently only PackBits, JPEG, and DEFLATE + * compression schemes are supported. + * + *

    If compression is set to any value but + * COMPRESSION_NONE and the OutputStream + * supplied to the ImageEncoder is not a + * SeekableOutputStream, then the encoder will use either + * a temporary file or a memory cache when compressing the data + * depending on whether the file system is accessible. Compression + * will therefore be more efficient if a SeekableOutputStream + * is supplied. + * + * @param compression The compression type. + */ + public void setCompression(int compression) { + + switch(compression) { + case COMPRESSION_NONE: + case COMPRESSION_PACKBITS: + case COMPRESSION_JPEG_TTN2: + case COMPRESSION_DEFLATE: + // Do nothing. + break; + default: + throw new Error("TIFFEncodeParam0"); + } + + this.compression = compression; + } + + /** + * Returns the value of the writeTiled parameter. + */ + public boolean getWriteTiled() { + return writeTiled; + } + + /** + * If set, the data will be written out in tiled format, instead of + * in strips. + * + * @param writeTiled Specifies whether the image data should be + * wriiten out in tiled format. + */ + public void setWriteTiled(boolean writeTiled) { + this.writeTiled = writeTiled; + } + + /** + * Sets the dimensions of the tiles to be written. If either + * value is non-positive, the encoder will use a default value. + * + *

    If the data are being written as tiles, i.e., + * getWriteTiled() returns true, then the + * default tile dimensions used by the encoder are those of the tiles + * of the image being encoded. + * + *

    If the data are being written as strips, i.e., + * getWriteTiled() returns false, the width + * of each strip is always the width of the image and the default + * number of rows per strip is 8. + * + *

    If JPEG compession is being used, the dimensions of the strips or + * tiles may be modified to conform to the JPEG-in-TIFF specification. + * + * @param tileWidth The tile width; ignored if strips are used. + * @param tileHeight The tile height or number of rows per strip. + */ + public void setTileSize(int tileWidth, int tileHeight) { + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + } + + /** + * Retrieves the tile width set via setTileSize(). + */ + public int getTileWidth() { + return tileWidth; + } + + /** + * Retrieves the tile height set via setTileSize(). + */ + public int getTileHeight() { + return tileHeight; + } + + /** + * Sets an Iterator of additional images to be written + * after the image passed as an argument to the ImageEncoder. + * The methods on the supplied Iterator must only be invoked + * by the ImageEncoder which will exhaust the available + * values unless an error occurs. + * + *

    The value returned by an invocation of next() on the + * Iterator must return either a RenderedImage + * or an Object[] of length 2 wherein the element at index + * zero is a RenderedImage amd the other element is a + * TIFFEncodeParam. If no TIFFEncodeParam is + * supplied in this manner for an additional image, the parameters used + * to create the ImageEncoder will be used. The extra + * image Iterator set on any TIFFEncodeParam + * of an additional image will in all cases be ignored. + */ + public synchronized void setExtraImages(Iterator extraImages) { + this.extraImages = extraImages; + } + + /** + * Returns the additional image Iterator specified via + * setExtraImages() or null if none was + * supplied or if a null value was supplied. + */ + public synchronized Iterator getExtraImages() { + return extraImages; + } + + /** + * Sets the compression level for DEFLATE-compressed data which should + * either be java.util.Deflater.DEFAULT_COMPRESSION or a + * value in the range [1,9] where larger values indicate more compression. + * The default setting is Deflater.DEFAULT_COMPRESSION. This + * setting is ignored if the compression type is not DEFLATE. + */ + public void setDeflateLevel(int deflateLevel) { + if(deflateLevel < 1 && deflateLevel > 9 && + deflateLevel != Deflater.DEFAULT_COMPRESSION) { + throw new Error("TIFFEncodeParam1"); + } + + this.deflateLevel = deflateLevel; + } + + /** + * Gets the compression level for DEFLATE compression. + */ + public int getDeflateLevel() { + return deflateLevel; + } + + /** + * Sets flag indicating whether to convert RGB data to YCbCr when the + * compression type is JPEG. The default value is true. + * This flag is ignored if the compression type is not JPEG. + */ + public void setJPEGCompressRGBToYCbCr(boolean convertJPEGRGBToYCbCr) { + this.convertJPEGRGBToYCbCr = convertJPEGRGBToYCbCr; + } + + /** + * Whether RGB data will be converted to YCbCr when using JPEG compression. + */ + public boolean getJPEGCompressRGBToYCbCr() { + return convertJPEGRGBToYCbCr; + } + + /** + * Sets the JPEG compression parameters. These parameters are ignored + * if the compression type is not JPEG. The argument may be + * null to indicate that default compression parameters + * are to be used. For maximum conformance with the specification it + * is recommended in most cases that only the quality compression + * parameter be set. + * + *

    The writeTablesOnly and JFIFHeader + * flags of the JPEGEncodeParam are ignored. The + * writeImageOnly flag is used to determine whether the + * JPEGTables field will be written to the TIFF stream: if + * writeImageOnly is true, then the JPEGTables + * field will be written and will contain a valid JPEG abbreviated + * table specification datastream. In this case the data in each data + * segment (strip or tile) will contain an abbreviated JPEG image + * datastream. If the writeImageOnly flag is + * false, then the JPEGTables field will not be written and + * each data segment will contain a complete JPEG interchange datastream. + */ + public void setJPEGEncodeParam(JPEGEncodeParam jpegEncodeParam) { + if(jpegEncodeParam != null) { + jpegEncodeParam = (JPEGEncodeParam)jpegEncodeParam.clone(); + jpegEncodeParam.setTableInfoValid(false); + jpegEncodeParam.setImageInfoValid(true); + } + this.jpegEncodeParam = jpegEncodeParam; + } + + /** + * Retrieves the JPEG compression parameters. + */ + public JPEGEncodeParam getJPEGEncodeParam() { + return jpegEncodeParam; + } + + /** + * Sets an array of extra fields to be written to the TIFF Image File + * Directory (IFD). Fields with tags equal to the tag of any + * automatically generated fields are ignored. No error checking is + * performed with respect to the validity of the field contents or + * the appropriateness of the field for the image being encoded. + * + * @param extraFields An array of extra fields; the parameter is + * copied by reference. + */ + public void setExtraFields(TIFFField[] extraFields) { + this.extraFields = extraFields; + } + + /** + * Returns the value set by setExtraFields(). + */ + public TIFFField[] getExtraFields() { + return extraFields; + } +} Index: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFFaxDecoder.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFFaxDecoder.java diff -N src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFFaxDecoder.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFFaxDecoder.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,1446 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.codec.myTiff; + + +class TIFFFaxDecoder { + + private int bitPointer, bytePointer; + private byte[] data; + private int w, h; + private int fillOrder; + + // Data structures needed to store changing elements for the previous + // and the current scanline + private int changingElemSize = 0; + private int prevChangingElems[]; + private int currChangingElems[]; + + // Element at which to start search in getNextChangingElement + private int lastChangingElement = 0; + + private int compression = 2; + + // Variables set by T4Options + private int uncompressedMode = 0; + private int fillBits = 0; + private int oneD; + + static int table1[] = { + 0x00, // 0 bits are left in first byte - SHOULD NOT HAPPEN + 0x01, // 1 bits are left in first byte + 0x03, // 2 bits are left in first byte + 0x07, // 3 bits are left in first byte + 0x0f, // 4 bits are left in first byte + 0x1f, // 5 bits are left in first byte + 0x3f, // 6 bits are left in first byte + 0x7f, // 7 bits are left in first byte + 0xff // 8 bits are left in first byte + }; + + static int table2[] = { + 0x00, // 0 + 0x80, // 1 + 0xc0, // 2 + 0xe0, // 3 + 0xf0, // 4 + 0xf8, // 5 + 0xfc, // 6 + 0xfe, // 7 + 0xff // 8 + }; + + // Table to be used when fillOrder = 2, for flipping bytes. + static byte flipTable[] = { + 0, -128, 64, -64, 32, -96, 96, -32, + 16, -112, 80, -48, 48, -80, 112, -16, + 8, -120, 72, -56, 40, -88, 104, -24, + 24, -104, 88, -40, 56, -72, 120, -8, + 4, -124, 68, -60, 36, -92, 100, -28, + 20, -108, 84, -44, 52, -76, 116, -12, + 12, -116, 76, -52, 44, -84, 108, -20, + 28, -100, 92, -36, 60, -68, 124, -4, + 2, -126, 66, -62, 34, -94, 98, -30, + 18, -110, 82, -46, 50, -78, 114, -14, + 10, -118, 74, -54, 42, -86, 106, -22, + 26, -102, 90, -38, 58, -70, 122, -6, + 6, -122, 70, -58, 38, -90, 102, -26, + 22, -106, 86, -42, 54, -74, 118, -10, + 14, -114, 78, -50, 46, -82, 110, -18, + 30, -98, 94, -34, 62, -66, 126, -2, + 1, -127, 65, -63, 33, -95, 97, -31, + 17, -111, 81, -47, 49, -79, 113, -15, + 9, -119, 73, -55, 41, -87, 105, -23, + 25, -103, 89, -39, 57, -71, 121, -7, + 5, -123, 69, -59, 37, -91, 101, -27, + 21, -107, 85, -43, 53, -75, 117, -11, + 13, -115, 77, -51, 45, -83, 109, -19, + 29, -99, 93, -35, 61, -67, 125, -3, + 3, -125, 67, -61, 35, -93, 99, -29, + 19, -109, 83, -45, 51, -77, 115, -13, + 11, -117, 75, -53, 43, -85, 107, -21, + 27, -101, 91, -37, 59, -69, 123, -5, + 7, -121, 71, -57, 39, -89, 103, -25, + 23, -105, 87, -41, 55, -73, 119, -9, + 15, -113, 79, -49, 47, -81, 111, -17, + 31, -97, 95, -33, 63, -65, 127, -1, + }; + + // The main 10 bit white runs lookup table + static short white[] = { + // 0 - 7 + 6430, 6400, 6400, 6400, 3225, 3225, 3225, 3225, + // 8 - 15 + 944, 944, 944, 944, 976, 976, 976, 976, + // 16 - 23 + 1456, 1456, 1456, 1456, 1488, 1488, 1488, 1488, + // 24 - 31 + 718, 718, 718, 718, 718, 718, 718, 718, + // 32 - 39 + 750, 750, 750, 750, 750, 750, 750, 750, + // 40 - 47 + 1520, 1520, 1520, 1520, 1552, 1552, 1552, 1552, + // 48 - 55 + 428, 428, 428, 428, 428, 428, 428, 428, + // 56 - 63 + 428, 428, 428, 428, 428, 428, 428, 428, + // 64 - 71 + 654, 654, 654, 654, 654, 654, 654, 654, + // 72 - 79 + 1072, 1072, 1072, 1072, 1104, 1104, 1104, 1104, + // 80 - 87 + 1136, 1136, 1136, 1136, 1168, 1168, 1168, 1168, + // 88 - 95 + 1200, 1200, 1200, 1200, 1232, 1232, 1232, 1232, + // 96 - 103 + 622, 622, 622, 622, 622, 622, 622, 622, + // 104 - 111 + 1008, 1008, 1008, 1008, 1040, 1040, 1040, 1040, + // 112 - 119 + 44, 44, 44, 44, 44, 44, 44, 44, + // 120 - 127 + 44, 44, 44, 44, 44, 44, 44, 44, + // 128 - 135 + 396, 396, 396, 396, 396, 396, 396, 396, + // 136 - 143 + 396, 396, 396, 396, 396, 396, 396, 396, + // 144 - 151 + 1712, 1712, 1712, 1712, 1744, 1744, 1744, 1744, + // 152 - 159 + 846, 846, 846, 846, 846, 846, 846, 846, + // 160 - 167 + 1264, 1264, 1264, 1264, 1296, 1296, 1296, 1296, + // 168 - 175 + 1328, 1328, 1328, 1328, 1360, 1360, 1360, 1360, + // 176 - 183 + 1392, 1392, 1392, 1392, 1424, 1424, 1424, 1424, + // 184 - 191 + 686, 686, 686, 686, 686, 686, 686, 686, + // 192 - 199 + 910, 910, 910, 910, 910, 910, 910, 910, + // 200 - 207 + 1968, 1968, 1968, 1968, 2000, 2000, 2000, 2000, + // 208 - 215 + 2032, 2032, 2032, 2032, 16, 16, 16, 16, + // 216 - 223 + 10257, 10257, 10257, 10257, 12305, 12305, 12305, 12305, + // 224 - 231 + 330, 330, 330, 330, 330, 330, 330, 330, + // 232 - 239 + 330, 330, 330, 330, 330, 330, 330, 330, + // 240 - 247 + 330, 330, 330, 330, 330, 330, 330, 330, + // 248 - 255 + 330, 330, 330, 330, 330, 330, 330, 330, + // 256 - 263 + 362, 362, 362, 362, 362, 362, 362, 362, + // 264 - 271 + 362, 362, 362, 362, 362, 362, 362, 362, + // 272 - 279 + 362, 362, 362, 362, 362, 362, 362, 362, + // 280 - 287 + 362, 362, 362, 362, 362, 362, 362, 362, + // 288 - 295 + 878, 878, 878, 878, 878, 878, 878, 878, + // 296 - 303 + 1904, 1904, 1904, 1904, 1936, 1936, 1936, 1936, + // 304 - 311 + -18413, -18413, -16365, -16365, -14317, -14317, -10221, -10221, + // 312 - 319 + 590, 590, 590, 590, 590, 590, 590, 590, + // 320 - 327 + 782, 782, 782, 782, 782, 782, 782, 782, + // 328 - 335 + 1584, 1584, 1584, 1584, 1616, 1616, 1616, 1616, + // 336 - 343 + 1648, 1648, 1648, 1648, 1680, 1680, 1680, 1680, + // 344 - 351 + 814, 814, 814, 814, 814, 814, 814, 814, + // 352 - 359 + 1776, 1776, 1776, 1776, 1808, 1808, 1808, 1808, + // 360 - 367 + 1840, 1840, 1840, 1840, 1872, 1872, 1872, 1872, + // 368 - 375 + 6157, 6157, 6157, 6157, 6157, 6157, 6157, 6157, + // 376 - 383 + 6157, 6157, 6157, 6157, 6157, 6157, 6157, 6157, + // 384 - 391 + -12275, -12275, -12275, -12275, -12275, -12275, -12275, -12275, + // 392 - 399 + -12275, -12275, -12275, -12275, -12275, -12275, -12275, -12275, + // 400 - 407 + 14353, 14353, 14353, 14353, 16401, 16401, 16401, 16401, + // 408 - 415 + 22547, 22547, 24595, 24595, 20497, 20497, 20497, 20497, + // 416 - 423 + 18449, 18449, 18449, 18449, 26643, 26643, 28691, 28691, + // 424 - 431 + 30739, 30739, -32749, -32749, -30701, -30701, -28653, -28653, + // 432 - 439 + -26605, -26605, -24557, -24557, -22509, -22509, -20461, -20461, + // 440 - 447 + 8207, 8207, 8207, 8207, 8207, 8207, 8207, 8207, + // 448 - 455 + 72, 72, 72, 72, 72, 72, 72, 72, + // 456 - 463 + 72, 72, 72, 72, 72, 72, 72, 72, + // 464 - 471 + 72, 72, 72, 72, 72, 72, 72, 72, + // 472 - 479 + 72, 72, 72, 72, 72, 72, 72, 72, + // 480 - 487 + 72, 72, 72, 72, 72, 72, 72, 72, + // 488 - 495 + 72, 72, 72, 72, 72, 72, 72, 72, + // 496 - 503 + 72, 72, 72, 72, 72, 72, 72, 72, + // 504 - 511 + 72, 72, 72, 72, 72, 72, 72, 72, + // 512 - 519 + 104, 104, 104, 104, 104, 104, 104, 104, + // 520 - 527 + 104, 104, 104, 104, 104, 104, 104, 104, + // 528 - 535 + 104, 104, 104, 104, 104, 104, 104, 104, + // 536 - 543 + 104, 104, 104, 104, 104, 104, 104, 104, + // 544 - 551 + 104, 104, 104, 104, 104, 104, 104, 104, + // 552 - 559 + 104, 104, 104, 104, 104, 104, 104, 104, + // 560 - 567 + 104, 104, 104, 104, 104, 104, 104, 104, + // 568 - 575 + 104, 104, 104, 104, 104, 104, 104, 104, + // 576 - 583 + 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107, + // 584 - 591 + 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107, + // 592 - 599 + 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107, + // 600 - 607 + 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107, + // 608 - 615 + 266, 266, 266, 266, 266, 266, 266, 266, + // 616 - 623 + 266, 266, 266, 266, 266, 266, 266, 266, + // 624 - 631 + 266, 266, 266, 266, 266, 266, 266, 266, + // 632 - 639 + 266, 266, 266, 266, 266, 266, 266, 266, + // 640 - 647 + 298, 298, 298, 298, 298, 298, 298, 298, + // 648 - 655 + 298, 298, 298, 298, 298, 298, 298, 298, + // 656 - 663 + 298, 298, 298, 298, 298, 298, 298, 298, + // 664 - 671 + 298, 298, 298, 298, 298, 298, 298, 298, + // 672 - 679 + 524, 524, 524, 524, 524, 524, 524, 524, + // 680 - 687 + 524, 524, 524, 524, 524, 524, 524, 524, + // 688 - 695 + 556, 556, 556, 556, 556, 556, 556, 556, + // 696 - 703 + 556, 556, 556, 556, 556, 556, 556, 556, + // 704 - 711 + 136, 136, 136, 136, 136, 136, 136, 136, + // 712 - 719 + 136, 136, 136, 136, 136, 136, 136, 136, + // 720 - 727 + 136, 136, 136, 136, 136, 136, 136, 136, + // 728 - 735 + 136, 136, 136, 136, 136, 136, 136, 136, + // 736 - 743 + 136, 136, 136, 136, 136, 136, 136, 136, + // 744 - 751 + 136, 136, 136, 136, 136, 136, 136, 136, + // 752 - 759 + 136, 136, 136, 136, 136, 136, 136, 136, + // 760 - 767 + 136, 136, 136, 136, 136, 136, 136, 136, + // 768 - 775 + 168, 168, 168, 168, 168, 168, 168, 168, + // 776 - 783 + 168, 168, 168, 168, 168, 168, 168, 168, + // 784 - 791 + 168, 168, 168, 168, 168, 168, 168, 168, + // 792 - 799 + 168, 168, 168, 168, 168, 168, 168, 168, + // 800 - 807 + 168, 168, 168, 168, 168, 168, 168, 168, + // 808 - 815 + 168, 168, 168, 168, 168, 168, 168, 168, + // 816 - 823 + 168, 168, 168, 168, 168, 168, 168, 168, + // 824 - 831 + 168, 168, 168, 168, 168, 168, 168, 168, + // 832 - 839 + 460, 460, 460, 460, 460, 460, 460, 460, + // 840 - 847 + 460, 460, 460, 460, 460, 460, 460, 460, + // 848 - 855 + 492, 492, 492, 492, 492, 492, 492, 492, + // 856 - 863 + 492, 492, 492, 492, 492, 492, 492, 492, + // 864 - 871 + 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, + // 872 - 879 + 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, + // 880 - 887 + 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, + // 888 - 895 + 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, + // 896 - 903 + 200, 200, 200, 200, 200, 200, 200, 200, + // 904 - 911 + 200, 200, 200, 200, 200, 200, 200, 200, + // 912 - 919 + 200, 200, 200, 200, 200, 200, 200, 200, + // 920 - 927 + 200, 200, 200, 200, 200, 200, 200, 200, + // 928 - 935 + 200, 200, 200, 200, 200, 200, 200, 200, + // 936 - 943 + 200, 200, 200, 200, 200, 200, 200, 200, + // 944 - 951 + 200, 200, 200, 200, 200, 200, 200, 200, + // 952 - 959 + 200, 200, 200, 200, 200, 200, 200, 200, + // 960 - 967 + 232, 232, 232, 232, 232, 232, 232, 232, + // 968 - 975 + 232, 232, 232, 232, 232, 232, 232, 232, + // 976 - 983 + 232, 232, 232, 232, 232, 232, 232, 232, + // 984 - 991 + 232, 232, 232, 232, 232, 232, 232, 232, + // 992 - 999 + 232, 232, 232, 232, 232, 232, 232, 232, + // 1000 - 1007 + 232, 232, 232, 232, 232, 232, 232, 232, + // 1008 - 1015 + 232, 232, 232, 232, 232, 232, 232, 232, + // 1016 - 1023 + 232, 232, 232, 232, 232, 232, 232, 232, + }; + + // Additional make up codes for both White and Black runs + static short additionalMakeup[] = { + 28679, 28679, 31752, (short)32777, + (short)33801, (short)34825, (short)35849, (short)36873, + (short)29703, (short)29703, (short)30727, (short)30727, + (short)37897, (short)38921, (short)39945, (short)40969 + }; + + // Initial black run look up table, uses the first 4 bits of a code + static short initBlack[] = { + // 0 - 7 + 3226, 6412, 200, 168, 38, 38, 134, 134, + // 8 - 15 + 100, 100, 100, 100, 68, 68, 68, 68 + }; + + // + static short twoBitBlack[] = {292, 260, 226, 226}; // 0 - 3 + + // Main black run table, using the last 9 bits of possible 13 bit code + static short black[] = { + // 0 - 7 + 62, 62, 30, 30, 0, 0, 0, 0, + // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, + // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, + // 24 - 31 + 0, 0, 0, 0, 0, 0, 0, 0, + // 32 - 39 + 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225, + // 40 - 47 + 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225, + // 48 - 55 + 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225, + // 56 - 63 + 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225, + // 64 - 71 + 588, 588, 588, 588, 588, 588, 588, 588, + // 72 - 79 + 1680, 1680, 20499, 22547, 24595, 26643, 1776, 1776, + // 80 - 87 + 1808, 1808, -24557, -22509, -20461, -18413, 1904, 1904, + // 88 - 95 + 1936, 1936, -16365, -14317, 782, 782, 782, 782, + // 96 - 103 + 814, 814, 814, 814, -12269, -10221, 10257, 10257, + // 104 - 111 + 12305, 12305, 14353, 14353, 16403, 18451, 1712, 1712, + // 112 - 119 + 1744, 1744, 28691, 30739, -32749, -30701, -28653, -26605, + // 120 - 127 + 2061, 2061, 2061, 2061, 2061, 2061, 2061, 2061, + // 128 - 135 + 424, 424, 424, 424, 424, 424, 424, 424, + // 136 - 143 + 424, 424, 424, 424, 424, 424, 424, 424, + // 144 - 151 + 424, 424, 424, 424, 424, 424, 424, 424, + // 152 - 159 + 424, 424, 424, 424, 424, 424, 424, 424, + // 160 - 167 + 750, 750, 750, 750, 1616, 1616, 1648, 1648, + // 168 - 175 + 1424, 1424, 1456, 1456, 1488, 1488, 1520, 1520, + // 176 - 183 + 1840, 1840, 1872, 1872, 1968, 1968, 8209, 8209, + // 184 - 191 + 524, 524, 524, 524, 524, 524, 524, 524, + // 192 - 199 + 556, 556, 556, 556, 556, 556, 556, 556, + // 200 - 207 + 1552, 1552, 1584, 1584, 2000, 2000, 2032, 2032, + // 208 - 215 + 976, 976, 1008, 1008, 1040, 1040, 1072, 1072, + // 216 - 223 + 1296, 1296, 1328, 1328, 718, 718, 718, 718, + // 224 - 231 + 456, 456, 456, 456, 456, 456, 456, 456, + // 232 - 239 + 456, 456, 456, 456, 456, 456, 456, 456, + // 240 - 247 + 456, 456, 456, 456, 456, 456, 456, 456, + // 248 - 255 + 456, 456, 456, 456, 456, 456, 456, 456, + // 256 - 263 + 326, 326, 326, 326, 326, 326, 326, 326, + // 264 - 271 + 326, 326, 326, 326, 326, 326, 326, 326, + // 272 - 279 + 326, 326, 326, 326, 326, 326, 326, 326, + // 280 - 287 + 326, 326, 326, 326, 326, 326, 326, 326, + // 288 - 295 + 326, 326, 326, 326, 326, 326, 326, 326, + // 296 - 303 + 326, 326, 326, 326, 326, 326, 326, 326, + // 304 - 311 + 326, 326, 326, 326, 326, 326, 326, 326, + // 312 - 319 + 326, 326, 326, 326, 326, 326, 326, 326, + // 320 - 327 + 358, 358, 358, 358, 358, 358, 358, 358, + // 328 - 335 + 358, 358, 358, 358, 358, 358, 358, 358, + // 336 - 343 + 358, 358, 358, 358, 358, 358, 358, 358, + // 344 - 351 + 358, 358, 358, 358, 358, 358, 358, 358, + // 352 - 359 + 358, 358, 358, 358, 358, 358, 358, 358, + // 360 - 367 + 358, 358, 358, 358, 358, 358, 358, 358, + // 368 - 375 + 358, 358, 358, 358, 358, 358, 358, 358, + // 376 - 383 + 358, 358, 358, 358, 358, 358, 358, 358, + // 384 - 391 + 490, 490, 490, 490, 490, 490, 490, 490, + // 392 - 399 + 490, 490, 490, 490, 490, 490, 490, 490, + // 400 - 407 + 4113, 4113, 6161, 6161, 848, 848, 880, 880, + // 408 - 415 + 912, 912, 944, 944, 622, 622, 622, 622, + // 416 - 423 + 654, 654, 654, 654, 1104, 1104, 1136, 1136, + // 424 - 431 + 1168, 1168, 1200, 1200, 1232, 1232, 1264, 1264, + // 432 - 439 + 686, 686, 686, 686, 1360, 1360, 1392, 1392, + // 440 - 447 + 12, 12, 12, 12, 12, 12, 12, 12, + // 448 - 455 + 390, 390, 390, 390, 390, 390, 390, 390, + // 456 - 463 + 390, 390, 390, 390, 390, 390, 390, 390, + // 464 - 471 + 390, 390, 390, 390, 390, 390, 390, 390, + // 472 - 479 + 390, 390, 390, 390, 390, 390, 390, 390, + // 480 - 487 + 390, 390, 390, 390, 390, 390, 390, 390, + // 488 - 495 + 390, 390, 390, 390, 390, 390, 390, 390, + // 496 - 503 + 390, 390, 390, 390, 390, 390, 390, 390, + // 504 - 511 + 390, 390, 390, 390, 390, 390, 390, 390, + }; + + static byte twoDCodes[] = { + // 0 - 7 + 80, 88, 23, 71, 30, 30, 62, 62, + // 8 - 15 + 4, 4, 4, 4, 4, 4, 4, 4, + // 16 - 23 + 11, 11, 11, 11, 11, 11, 11, 11, + // 24 - 31 + 11, 11, 11, 11, 11, 11, 11, 11, + // 32 - 39 + 35, 35, 35, 35, 35, 35, 35, 35, + // 40 - 47 + 35, 35, 35, 35, 35, 35, 35, 35, + // 48 - 55 + 51, 51, 51, 51, 51, 51, 51, 51, + // 56 - 63 + 51, 51, 51, 51, 51, 51, 51, 51, + // 64 - 71 + 41, 41, 41, 41, 41, 41, 41, 41, + // 72 - 79 + 41, 41, 41, 41, 41, 41, 41, 41, + // 80 - 87 + 41, 41, 41, 41, 41, 41, 41, 41, + // 88 - 95 + 41, 41, 41, 41, 41, 41, 41, 41, + // 96 - 103 + 41, 41, 41, 41, 41, 41, 41, 41, + // 104 - 111 + 41, 41, 41, 41, 41, 41, 41, 41, + // 112 - 119 + 41, 41, 41, 41, 41, 41, 41, 41, + // 120 - 127 + 41, 41, 41, 41, 41, 41, 41, 41, + }; + + /** + * @param fillOrder The fill order of the compressed data bytes. + * @param compData Array containing compressed data. + * @param w + * @param h + */ + public TIFFFaxDecoder(int fillOrder, int w, int h) { + this.fillOrder = fillOrder; + this.w = w; + this.h = h; + + this.bitPointer = 0; + this.bytePointer = 0; + this.prevChangingElems = new int[w]; + this.currChangingElems = new int[w]; + } + + // One-dimensional decoding methods + + public void decode1D(byte[] buffer, byte[] compData, + int startX, int height) { + this.data = compData; + + int lineOffset = 0; + int scanlineStride = (w + 7)/8; + + bitPointer = 0; + bytePointer = 0; + + for (int i = 0; i < height; i++) { + decodeNextScanline(buffer, lineOffset, startX); + lineOffset += scanlineStride; + } + } + + public void decodeNextScanline(byte[] buffer, + int lineOffset, int bitOffset) { + int bits = 0, code = 0, isT = 0; + int current, entry, twoBits; + boolean isWhite = true; + + // Initialize starting of the changing elements array + changingElemSize = 0; + + // While scanline not complete + while (bitOffset < w) { + while (isWhite) { + // White run + current = nextNBits(10); + entry = white[current]; + + // Get the 3 fields from the entry + isT = entry & 0x0001; + bits = (entry >>> 1) & 0x0f; + + if (bits == 12) { // Additional Make up code + // Get the next 2 bits + twoBits = nextLesserThan8Bits(2); + // Consolidate the 2 new bits and last 2 bits into 4 bits + current = ((current << 2) & 0x000c) | twoBits; + entry = additionalMakeup[current]; + bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111 + code = (entry >>> 4) & 0x0fff; // 12 bits + bitOffset += code; // Skip white run + + updatePointer(4 - bits); + } else if (bits == 0) { // ERROR + throw new Error("TIFFFaxDecoder0"); + } else if (bits == 15) { // EOL + throw new Error("TIFFFaxDecoder1"); + } else { + // 11 bits - 0000 0111 1111 1111 = 0x07ff + code = (entry >>> 5) & 0x07ff; + bitOffset += code; + + updatePointer(10 - bits); + if (isT == 0) { + isWhite = false; + currChangingElems[changingElemSize++] = bitOffset; + } + } + } + + // Check whether this run completed one width, if so + // advance to next byte boundary for compression = 2. + if (bitOffset == w) { + if (compression == 2) { + advancePointer(); + } + break; + } + + while (isWhite == false) { + // Black run + current = nextLesserThan8Bits(4); + entry = initBlack[current]; + + // Get the 3 fields from the entry + isT = entry & 0x0001; + bits = (entry >>> 1) & 0x000f; + code = (entry >>> 5) & 0x07ff; + + if (code == 100) { + current = nextNBits(9); + entry = black[current]; + + // Get the 3 fields from the entry + isT = entry & 0x0001; + bits = (entry >>> 1) & 0x000f; + code = (entry >>> 5) & 0x07ff; + + if (bits == 12) { + // Additional makeup codes + updatePointer(5); + current = nextLesserThan8Bits(4); + entry = additionalMakeup[current]; + bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111 + code = (entry >>> 4) & 0x0fff; // 12 bits + + setToBlack(buffer, lineOffset, bitOffset, code); + bitOffset += code; + + updatePointer(4 - bits); + } else if (bits == 15) { + // EOL code + throw new Error("TIFFFaxDecoder2"); + } else { + setToBlack(buffer, lineOffset, bitOffset, code); + bitOffset += code; + + updatePointer(9 - bits); + if (isT == 0) { + isWhite = true; + currChangingElems[changingElemSize++] = bitOffset; + } + } + } else if (code == 200) { + // Is a Terminating code + current = nextLesserThan8Bits(2); + entry = twoBitBlack[current]; + code = (entry >>> 5) & 0x07ff; + bits = (entry >>> 1) & 0x0f; + + setToBlack(buffer, lineOffset, bitOffset, code); + bitOffset += code; + + updatePointer(2 - bits); + isWhite = true; + currChangingElems[changingElemSize++] = bitOffset; + } else { + // Is a Terminating code + setToBlack(buffer, lineOffset, bitOffset, code); + bitOffset += code; + + updatePointer(4 - bits); + isWhite = true; + currChangingElems[changingElemSize++] = bitOffset; + } + } + + // Check whether this run completed one width + if (bitOffset == w) { + if (compression == 2) { + advancePointer(); + } + break; + } + } + + currChangingElems[changingElemSize++] = bitOffset; + } + + // Two-dimensional decoding methods + + public void decode2D(byte[] buffer, + byte compData[], + int startX, + int height, + long tiffT4Options) { + this.data = compData; + compression = 3; + + bitPointer = 0; + bytePointer = 0; + + int scanlineStride = (w + 7)/8; + + int a0, a1, b1, b2; + int[] b = new int[2]; + int entry, code, bits; + boolean isWhite; + int currIndex = 0; + int temp[]; + + // fillBits - dealt with this in readEOL + // 1D/2D encoding - dealt with this in readEOL + + // uncompressedMode - haven't dealt with this yet. + + + oneD = (int)(tiffT4Options & 0x01); + uncompressedMode = (int)((tiffT4Options & 0x02) >> 1); + fillBits = (int)((tiffT4Options & 0x04) >> 2); + + // The data must start with an EOL code + if (readEOL() != 1) { + throw new Error("TIFFFaxDecoder3"); + } + + int lineOffset = 0; + int bitOffset; + + // Then the 1D encoded scanline data will occur, changing elements + // array gets set. + decodeNextScanline(buffer, lineOffset, startX); + lineOffset += scanlineStride; + + for (int lines = 1; lines < height; lines++) { + + // Every line must begin with an EOL followed by a bit which + // indicates whether the following scanline is 1D or 2D encoded. + if (readEOL() == 0) { + // 2D encoded scanline follows + + // Initialize previous scanlines changing elements, and + // initialize current scanline's changing elements array + temp = prevChangingElems; + prevChangingElems = currChangingElems; + currChangingElems = temp; + currIndex = 0; + + // a0 has to be set just before the start of this scanline. + a0 = -1; + isWhite = true; + bitOffset = startX; + + lastChangingElement = 0; + + while (bitOffset < w) { + // Get the next changing element + getNextChangingElement(a0, isWhite, b); + + b1 = b[0]; + b2 = b[1]; + + // Get the next seven bits + entry = nextLesserThan8Bits(7); + + // Run these through the 2DCodes table + entry = (int)(twoDCodes[entry] & 0xff); + + // Get the code and the number of bits used up + code = (entry & 0x78) >>> 3; + bits = entry & 0x07; + + if (code == 0) { + if (!isWhite) { + setToBlack(buffer, lineOffset, bitOffset, + b2 - bitOffset); + } + bitOffset = a0 = b2; + + // Set pointer to consume the correct number of bits. + updatePointer(7 - bits); + } else if (code == 1) { + // Horizontal + updatePointer(7 - bits); + + // identify the next 2 codes. + int number; + if (isWhite) { + number = decodeWhiteCodeWord(); + bitOffset += number; + currChangingElems[currIndex++] = bitOffset; + + number = decodeBlackCodeWord(); + setToBlack(buffer, lineOffset, bitOffset, number); + bitOffset += number; + currChangingElems[currIndex++] = bitOffset; + } else { + number = decodeBlackCodeWord(); + setToBlack(buffer, lineOffset, bitOffset, number); + bitOffset += number; + currChangingElems[currIndex++] = bitOffset; + + number = decodeWhiteCodeWord(); + bitOffset += number; + currChangingElems[currIndex++] = bitOffset; + } + + a0 = bitOffset; + } else if (code <= 8) { + // Vertical + a1 = b1 + (code - 5); + + currChangingElems[currIndex++] = a1; + + // We write the current color till a1 - 1 pos, + // since a1 is where the next color starts + if (!isWhite) { + setToBlack(buffer, lineOffset, bitOffset, + a1 - bitOffset); + } + bitOffset = a0 = a1; + isWhite = !isWhite; + + updatePointer(7 - bits); + } else { + throw new Error("TIFFFaxDecoder4"); + } + } + + // Add the changing element beyond the current scanline for the + // other color too + currChangingElems[currIndex++] = bitOffset; + changingElemSize = currIndex; + } else { + // 1D encoded scanline follows + decodeNextScanline(buffer, lineOffset, startX); + } + + lineOffset += scanlineStride; + } + } + + public synchronized void decodeT6(byte[] buffer, + byte[] compData, + int startX, + int height, + long tiffT6Options) { + this.data = compData; + compression = 4; + + bitPointer = 0; + bytePointer = 0; + + int scanlineStride = (w + 7)/8; + + int a0, a1, b1, b2; + int entry, code, bits; + boolean isWhite; + int currIndex; + int temp[]; + + // Return values from getNextChangingElement + int[] b = new int[2]; + + // uncompressedMode - have written some code for this, but this + // has not been tested due to lack of test images using this optional + + uncompressedMode = (int)((tiffT6Options & 0x02) >> 1); + + // Local cached reference + int[] cce = currChangingElems; + + // Assume invisible preceding row of all white pixels and insert + // both black and white changing elements beyond the end of this + // imaginary scanline. + changingElemSize = 0; + cce[changingElemSize++] = w; + cce[changingElemSize++] = w; + + int lineOffset = 0; + int bitOffset; + + for (int lines = 0; lines < height; lines++) { + // a0 has to be set just before the start of the scanline. + a0 = -1; + isWhite = true; + + // Assign the changing elements of the previous scanline to + // prevChangingElems and start putting this new scanline's + // changing elements into the currChangingElems. + temp = prevChangingElems; + prevChangingElems = currChangingElems; + cce = currChangingElems = temp; + currIndex = 0; + + // Start decoding the scanline at startX in the raster + bitOffset = startX; + + // Reset search start position for getNextChangingElement + lastChangingElement = 0; + + // Till one whole scanline is decoded + while (bitOffset < w) { + // Get the next changing element + getNextChangingElement(a0, isWhite, b); + b1 = b[0]; + b2 = b[1]; + + // Get the next seven bits + entry = nextLesserThan8Bits(7); + // Run these through the 2DCodes table + entry = (int)(twoDCodes[entry] & 0xff); + + // Get the code and the number of bits used up + code = (entry & 0x78) >>> 3; + bits = entry & 0x07; + + if (code == 0) { // Pass + // We always assume WhiteIsZero format for fax. + if (!isWhite) { + setToBlack(buffer, lineOffset, bitOffset, + b2 - bitOffset); + } + bitOffset = a0 = b2; + + // Set pointer to only consume the correct number of bits. + updatePointer(7 - bits); + } else if (code == 1) { // Horizontal + // Set pointer to only consume the correct number of bits. + updatePointer(7 - bits); + + // identify the next 2 alternating color codes. + int number; + if (isWhite) { + // Following are white and black runs + number = decodeWhiteCodeWord(); + bitOffset += number; + cce[currIndex++] = bitOffset; + + number = decodeBlackCodeWord(); + setToBlack(buffer, lineOffset, bitOffset, number); + bitOffset += number; + cce[currIndex++] = bitOffset; + } else { + // First a black run and then a white run follows + number = decodeBlackCodeWord(); + setToBlack(buffer, lineOffset, bitOffset, number); + bitOffset += number; + cce[currIndex++] = bitOffset; + + number = decodeWhiteCodeWord(); + bitOffset += number; + cce[currIndex++] = bitOffset; + } + + a0 = bitOffset; + } else if (code <= 8) { // Vertical + a1 = b1 + (code - 5); + cce[currIndex++] = a1; + + // We write the current color till a1 - 1 pos, + // since a1 is where the next color starts + if (!isWhite) { + setToBlack(buffer, lineOffset, bitOffset, + a1 - bitOffset); + } + bitOffset = a0 = a1; + isWhite = !isWhite; + + updatePointer(7 - bits); + } else if (code == 11) { + if (nextLesserThan8Bits(3) != 7) { + throw new Error("TIFFFaxDecoder5"); + } + + int zeros = 0; + boolean exit = false; + + while (!exit) { + while (nextLesserThan8Bits(1) != 1) { + zeros++; + } + + if (zeros > 5) { + // Exit code + + // Zeros before exit code + zeros = zeros - 6; + + if (!isWhite && (zeros > 0)) { + cce[currIndex++] = bitOffset; + } + + // Zeros before the exit code + bitOffset += zeros; + if (zeros > 0) { + // Some zeros have been written + isWhite = true; + } + + // Read in the bit which specifies the color of + // the following run + if (nextLesserThan8Bits(1) == 0) { + if (!isWhite) { + cce[currIndex++] = bitOffset; + } + isWhite = true; + } else { + if (isWhite) { + cce[currIndex++] = bitOffset; + } + isWhite = false; + } + + exit = true; + } + + if (zeros == 5) { + if (!isWhite) { + cce[currIndex++] = bitOffset; + } + bitOffset += zeros; + + // Last thing written was white + isWhite = true; + } else { + bitOffset += zeros; + + cce[currIndex++] = bitOffset; + setToBlack(buffer, lineOffset, bitOffset, 1); + ++bitOffset; + + // Last thing written was black + isWhite = false; + } + + } + } else { + throw new Error("TIFFFaxDecoder5"); + } + } + + // Add the changing element beyond the current scanline for the + // other color too + cce[currIndex++] = bitOffset; + + // Number of changing elements in this scanline. + changingElemSize = currIndex; + + lineOffset += scanlineStride; + } + } + + private void setToBlack(byte[] buffer, + int lineOffset, int bitOffset, + int numBits) { + int bitNum = 8*lineOffset + bitOffset; + int lastBit = bitNum + numBits; + + int byteNum = bitNum >> 3; + + // Handle bits in first byte + int shift = bitNum & 0x7; + if (shift > 0) { + int maskVal = 1 << (7 - shift); + byte val = buffer[byteNum]; + while (maskVal > 0 && bitNum < lastBit) { + val |= maskVal; + maskVal >>= 1; + ++bitNum; + } + buffer[byteNum] = val; + } + + // Fill in 8 bits at a time + byteNum = bitNum >> 3; + while (bitNum < lastBit - 7) { + buffer[byteNum++] = (byte)255; + bitNum += 8; + } + + // Fill in remaining bits + while (bitNum < lastBit) { + byteNum = bitNum >> 3; + buffer[byteNum] |= 1 << (7 - (bitNum & 0x7)); + ++bitNum; + } + } + + // Returns run length + private int decodeWhiteCodeWord() { + int current, entry, bits, isT, twoBits, code = -1; + int runLength = 0; + boolean isWhite = true; + + while (isWhite) { + current = nextNBits(10); + entry = white[current]; + + // Get the 3 fields from the entry + isT = entry & 0x0001; + bits = (entry >>> 1) & 0x0f; + + if (bits == 12) { // Additional Make up code + // Get the next 2 bits + twoBits = nextLesserThan8Bits(2); + // Consolidate the 2 new bits and last 2 bits into 4 bits + current = ((current << 2) & 0x000c) | twoBits; + entry = additionalMakeup[current]; + bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111 + code = (entry >>> 4) & 0x0fff; // 12 bits + runLength += code; + updatePointer(4 - bits); + } else if (bits == 0) { // ERROR + throw new Error("TIFFFaxDecoder0"); + } else if (bits == 15) { // EOL + throw new Error("TIFFFaxDecoder1"); + } else { + // 11 bits - 0000 0111 1111 1111 = 0x07ff + code = (entry >>> 5) & 0x07ff; + runLength += code; + updatePointer(10 - bits); + if (isT == 0) { + isWhite = false; + } + } + } + + return runLength; + } + + // Returns run length + private int decodeBlackCodeWord() { + int current, entry, bits, isT, code = -1; + int runLength = 0; + boolean isWhite = false; + + while (!isWhite) { + current = nextLesserThan8Bits(4); + entry = initBlack[current]; + + // Get the 3 fields from the entry + isT = entry & 0x0001; + bits = (entry >>> 1) & 0x000f; + code = (entry >>> 5) & 0x07ff; + + if (code == 100) { + current = nextNBits(9); + entry = black[current]; + + // Get the 3 fields from the entry + isT = entry & 0x0001; + bits = (entry >>> 1) & 0x000f; + code = (entry >>> 5) & 0x07ff; + + if (bits == 12) { + // Additional makeup codes + updatePointer(5); + current = nextLesserThan8Bits(4); + entry = additionalMakeup[current]; + bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111 + code = (entry >>> 4) & 0x0fff; // 12 bits + runLength += code; + + updatePointer(4 - bits); + } else if (bits == 15) { + // EOL code + throw new Error("TIFFFaxDecoder2"); + } else { + runLength += code; + updatePointer(9 - bits); + if (isT == 0) { + isWhite = true; + } + } + } else if (code == 200) { + // Is a Terminating code + current = nextLesserThan8Bits(2); + entry = twoBitBlack[current]; + code = (entry >>> 5) & 0x07ff; + runLength += code; + bits = (entry >>> 1) & 0x0f; + updatePointer(2 - bits); + isWhite = true; + } else { + // Is a Terminating code + runLength += code; + updatePointer(4 - bits); + isWhite = true; + } + } + + return runLength; + } + + private int readEOL() { + if (fillBits == 0) { + if (nextNBits(12) != 1) { + throw new Error("TIFFFaxDecoder6"); + } + } else if (fillBits == 1) { + + // First EOL code word xxxx 0000 0000 0001 will occur + // As many fill bits will be present as required to make + // the EOL code of 12 bits end on a byte boundary. + + int bitsLeft = 8 - bitPointer; + + if (nextNBits(bitsLeft) != 0) { + throw new Error("TIFFFaxDecoder8"); + } + + // If the number of bitsLeft is less than 8, then to have a 12 + // bit EOL sequence, two more bytes are certainly going to be + // required. The first of them has to be all zeros, so ensure + // that. + if (bitsLeft < 4) { + if (nextNBits(8) != 0) { + throw new Error("TIFFFaxDecoder8"); + } + } + + // There might be a random number of fill bytes with 0s, so + // loop till the EOL of 0000 0001 is found, as long as all + // the bytes preceding it are 0's. + int n; + while ((n = nextNBits(8)) != 1) { + + // If not all zeros + if (n != 0) { + throw new Error("TIFFFaxDecoder8"); + } + } + } + + // If one dimensional encoding mode, then always return 1 + if (oneD == 0) { + return 1; + } else { + // Otherwise for 2D encoding mode, + // The next one bit signifies 1D/2D encoding of next line. + return nextLesserThan8Bits(1); + } + } + + private void getNextChangingElement(int a0, boolean isWhite, int[] ret) { + // Local copies of instance variables + int[] pce = this.prevChangingElems; + int ces = this.changingElemSize; + + // If the previous match was at an odd element, we still + // have to search the preceeding element. + // int start = lastChangingElement & ~0x1; + int start = lastChangingElement > 0 ? lastChangingElement - 1 : 0; + if (isWhite) { + start &= ~0x1; // Search even numbered elements + } else { + start |= 0x1; // Search odd numbered elements + } + + int i = start; + for (; i < ces; i += 2) { + int temp = pce[i]; + if (temp > a0) { + lastChangingElement = i; + ret[0] = temp; + break; + } + } + + if (i + 1 < ces) { + ret[1] = pce[i + 1]; + } + } + + private int nextNBits(int bitsToGet) { + byte b, next, next2next; + int l = data.length - 1; + int bp = this.bytePointer; + + if (fillOrder == 1) { + b = data[bp]; + + if (bp == l) { + next = 0x00; + next2next = 0x00; + } else if ((bp + 1) == l) { + next = data[bp + 1]; + next2next = 0x00; + } else { + next = data[bp + 1]; + next2next = data[bp + 2]; + } + } else if (fillOrder == 2) { + b = flipTable[data[bp] & 0xff]; + + if (bp == l) { + next = 0x00; + next2next = 0x00; + } else if ((bp + 1) == l) { + next = flipTable[data[bp + 1] & 0xff]; + next2next = 0x00; + } else { + next = flipTable[data[bp + 1] & 0xff]; + next2next = flipTable[data[bp + 2] & 0xff]; + } + } else { + throw new Error("TIFFFaxDecoder7"); + } + + int bitsLeft = 8 - bitPointer; + int bitsFromNextByte = bitsToGet - bitsLeft; + int bitsFromNext2NextByte = 0; + if (bitsFromNextByte > 8) { + bitsFromNext2NextByte = bitsFromNextByte - 8; + bitsFromNextByte = 8; + } + + bytePointer++; + + int i1 = (b & table1[bitsLeft]) << (bitsToGet - bitsLeft); + int i2 = (next & table2[bitsFromNextByte]) >>> (8 - bitsFromNextByte); + + int i3 = 0; + if (bitsFromNext2NextByte != 0) { + i2 <<= bitsFromNext2NextByte; + i3 = (next2next & table2[bitsFromNext2NextByte]) >>> + (8 - bitsFromNext2NextByte); + i2 |= i3; + bytePointer++; + bitPointer = bitsFromNext2NextByte; + } else { + if (bitsFromNextByte == 8) { + bitPointer = 0; + bytePointer++; + } else { + bitPointer = bitsFromNextByte; + } + } + + int i = i1 | i2; + return i; + } + + private int nextLesserThan8Bits(int bitsToGet) { + byte b, next; + int l = data.length - 1; + int bp = this.bytePointer; + + if (fillOrder == 1) { + b = data[bp]; + if (bp == l) { + next = 0x00; + } else { + next = data[bp + 1]; + } + } else if (fillOrder == 2) { + b = flipTable[data[bp] & 0xff]; + if (bp == l) { + next = 0x00; + } else { + next = flipTable[data[bp + 1] & 0xff]; + } + } else { + throw new Error("TIFFFaxDecoder7"); + } + + int bitsLeft = 8 - bitPointer; + int bitsFromNextByte = bitsToGet - bitsLeft; + + int shift = bitsLeft - bitsToGet; + int i1, i2; + if (shift >= 0) { + i1 = (b & table1[bitsLeft]) >>> shift; + bitPointer += bitsToGet; + if (bitPointer == 8) { + bitPointer = 0; + bytePointer++; + } + } else { + i1 = (b & table1[bitsLeft]) << (-shift); + i2 = (next & table2[bitsFromNextByte]) >>> (8 - bitsFromNextByte); + + i1 |= i2; + bytePointer++; + bitPointer = bitsFromNextByte; + } + + return i1; + } + + // Move pointer backwards by given amount of bits + private void updatePointer(int bitsToMoveBack) { + int i = bitPointer - bitsToMoveBack; + + if (i < 0) { + bytePointer--; + bitPointer = 8 + i; + } else { + bitPointer = i; + } + } + + // Move to the next byte boundary + private boolean advancePointer() { + if (bitPointer != 0) { + bytePointer++; + bitPointer = 0; + } + + return true; + } +} + Index: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFField.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFField.java diff -N src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFField.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFField.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,457 @@ +/* + + Copyright 2001 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.codec.myTiff; + +import java.io.Serializable; + +/** + * A class representing a field in a TIFF 6.0 Image File Directory. + * + *

    The TIFF file format is described in more detail in the + * comments for the TIFFDescriptor class. + * + *

    A field in a TIFF Image File Directory (IFD). A field is defined + * as a sequence of values of identical data type. TIFF 6.0 defines + * 12 data types, which are mapped internally onto the Java datatypes + * byte, int, long, float, and double. + * + *

    This class is not a committed part of the JAI API. It may + * be removed or changed in future releases of JAI. + * + * @see TIFFDirectory + */ +public class TIFFField extends Object implements Comparable, Serializable { + + /** Flag for 8 bit unsigned integers. */ + public static final int TIFF_BYTE = 1; + + /** Flag for null-terminated ASCII strings. */ + public static final int TIFF_ASCII = 2; + + /** Flag for 16 bit unsigned integers. */ + public static final int TIFF_SHORT = 3; + + /** Flag for 32 bit unsigned integers. */ + public static final int TIFF_LONG = 4; + + /** Flag for pairs of 32 bit unsigned integers. */ + public static final int TIFF_RATIONAL = 5; + + /** Flag for 8 bit signed integers. */ + public static final int TIFF_SBYTE = 6; + + /** Flag for 8 bit uninterpreted bytes. */ + public static final int TIFF_UNDEFINED = 7; + + /** Flag for 16 bit signed integers. */ + public static final int TIFF_SSHORT = 8; + + /** Flag for 32 bit signed integers. */ + public static final int TIFF_SLONG = 9; + + /** Flag for pairs of 32 bit signed integers. */ + public static final int TIFF_SRATIONAL = 10; + + /** Flag for 32 bit IEEE floats. */ + public static final int TIFF_FLOAT = 11; + + /** Flag for 64 bit IEEE doubles. */ + public static final int TIFF_DOUBLE = 12; + + /** The tag number. */ + int tag; + + /** The tag type. */ + int type; + + /** The number of data items present in the field. */ + int count; + + /** The field data. */ + Object data; + + /** The default constructor. */ + TIFFField() {} + + /** + * Constructs a TIFFField with arbitrary data. The data + * parameter must be an array of a Java type appropriate for the + * type of the TIFF field. Since there is no available 32-bit + * unsigned datatype, long is used. The mapping between types is + * as follows: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    TIFF type Java type
    TIFF_BYTE byte
    TIFF_ASCII String
    TIFF_SHORT char
    TIFF_LONG long
    TIFF_RATIONAL long[2]
    TIFF_SBYTE byte
    TIFF_UNDEFINED byte
    TIFF_SSHORT short
    TIFF_SLONG int
    TIFF_SRATIONAL int[2]
    TIFF_FLOAT float
    TIFF_DOUBLE double
    + */ + public TIFFField(int tag, int type, int count, Object data) { + this.tag = tag; + this.type = type; + this.count = count; + this.data = data; + } + + /** + * Returns the tag number, between 0 and 65535. + */ + public int getTag() { + return tag; + } + + /** + * Returns the type of the data stored in the IFD. + * For a TIFF6.0 file, the value will equal one of the + * TIFF_ constants defined in this class. For future + * revisions of TIFF, higher values are possible. + * + */ + public int getType() { + return type; + } + + /** + * Returns the number of elements in the IFD. + */ + public int getCount() { + return count; + } + + /** + * Returns the data as an uninterpreted array of bytes. + * The type of the field must be one of TIFF_BYTE, TIFF_SBYTE, + * or TIFF_UNDEFINED; + * + *

    For data in TIFF_BYTE format, the application must take + * care when promoting the data to longer integral types + * to avoid sign extension. + * + *

    A ClassCastException will be thrown if the field is not + * of type TIFF_BYTE, TIFF_SBYTE, or TIFF_UNDEFINED. + */ + public byte[] getAsBytes() { + return (byte[])data; + } + + /** + * Returns TIFF_SHORT data as an array of chars (unsigned 16-bit + * integers). + * + *

    A ClassCastException will be thrown if the field is not + * of type TIFF_SHORT. + */ + public char[] getAsChars() { + return (char[])data; + } + + /** + * Returns TIFF_SSHORT data as an array of shorts (signed 16-bit + * integers). + * + *

    A ClassCastException will be thrown if the field is not + * of type TIFF_SSHORT. + */ + public short[] getAsShorts() { + return (short[])data; + } + + /** + * Returns TIFF_SLONG data as an array of ints (signed 32-bit + * integers). + * + *

    A ClassCastException will be thrown if the field is not + * of type TIFF_SLONG. + */ + public int[] getAsInts() { + return (int[])data; + } + + /** + * Returns TIFF_LONG data as an array of longs (signed 64-bit + * integers). + * + *

    A ClassCastException will be thrown if the field is not + * of type TIFF_LONG. + */ + public long[] getAsLongs() { + return (long[])data; + } + + /** + * Returns TIFF_FLOAT data as an array of floats. + * + *

    A ClassCastException will be thrown if the field is not + * of type TIFF_FLOAT. + */ + public float[] getAsFloats() { + return (float[])data; + } + + /** + * Returns TIFF_DOUBLE data as an array of doubles. + * + *

    A ClassCastException will be thrown if the field is not + * of type TIFF_DOUBLE. + */ + public double[] getAsDoubles() { + return (double[])data; + } + + /** + * Returns TIFF_SRATIONAL data as an array of 2-element arrays of ints. + * + *

    A ClassCastException will be thrown if the field is not + * of type TIFF_SRATIONAL. + */ + public int[][] getAsSRationals() { + return (int[][])data; + } + + /** + * Returns TIFF_RATIONAL data as an array of 2-element arrays of longs. + * + *

    A ClassCastException will be thrown if the field is not + * of type TIFF_RATTIONAL. + */ + public long[][] getAsRationals() { + return (long[][])data; + } + + /** + * Returns data in TIFF_BYTE, TIFF_SBYTE, TIFF_UNDEFINED, TIFF_SHORT, + * TIFF_SSHORT, or TIFF_SLONG format as an int. + * + *

    TIFF_BYTE and TIFF_UNDEFINED data are treated as unsigned; + * that is, no sign extension will take place and the returned + * value will be in the range [0, 255]. TIFF_SBYTE data will + * be returned in the range [-128, 127]. + * + *

    A ClassCastException will be thrown if the field is not of + * type TIFF_BYTE, TIFF_SBYTE, TIFF_UNDEFINED, TIFF_SHORT, + * TIFF_SSHORT, or TIFF_SLONG. + */ + public int getAsInt(int index) { + switch (type) { + case TIFF_BYTE: case TIFF_UNDEFINED: + return ((byte[])data)[index] & 0xff; + case TIFF_SBYTE: + return ((byte[])data)[index]; + case TIFF_SHORT: + return ((char[])data)[index] & 0xffff; + case TIFF_SSHORT: + return ((short[])data)[index]; + case TIFF_SLONG: + return ((int[])data)[index]; + default: + throw new ClassCastException(); + } + } + + /** + * Returns data in TIFF_BYTE, TIFF_SBYTE, TIFF_UNDEFINED, TIFF_SHORT, + * TIFF_SSHORT, TIFF_SLONG, or TIFF_LONG format as a long. + * + *

    TIFF_BYTE and TIFF_UNDEFINED data are treated as unsigned; + * that is, no sign extension will take place and the returned + * value will be in the range [0, 255]. TIFF_SBYTE data will + * be returned in the range [-128, 127]. + * + *

    A ClassCastException will be thrown if the field is not of + * type TIFF_BYTE, TIFF_SBYTE, TIFF_UNDEFINED, TIFF_SHORT, + * TIFF_SSHORT, TIFF_SLONG, or TIFF_LONG. + */ + public long getAsLong(int index) { + switch (type) { + case TIFF_BYTE: case TIFF_UNDEFINED: + return ((byte[])data)[index] & 0xff; + case TIFF_SBYTE: + return ((byte[])data)[index]; + case TIFF_SHORT: + return ((char[])data)[index] & 0xffff; + case TIFF_SSHORT: + return ((short[])data)[index]; + case TIFF_SLONG: + return ((int[])data)[index]; + case TIFF_LONG: + return ((long[])data)[index]; + default: + throw new ClassCastException(); + } + } + + /** + * Returns data in any numerical format as a float. Data in + * TIFF_SRATIONAL or TIFF_RATIONAL format are evaluated by + * dividing the numerator into the denominator using + * double-precision arithmetic and then truncating to single + * precision. Data in TIFF_SLONG, TIFF_LONG, or TIFF_DOUBLE + * format may suffer from truncation. + * + *

    A ClassCastException will be thrown if the field is + * of type TIFF_UNDEFINED or TIFF_ASCII. + */ + public float getAsFloat(int index) { + switch (type) { + case TIFF_BYTE: + return ((byte[])data)[index] & 0xff; + case TIFF_SBYTE: + return ((byte[])data)[index]; + case TIFF_SHORT: + return ((char[])data)[index] & 0xffff; + case TIFF_SSHORT: + return ((short[])data)[index]; + case TIFF_SLONG: + return ((int[])data)[index]; + case TIFF_LONG: + return ((long[])data)[index]; + case TIFF_FLOAT: + return ((float[])data)[index]; + case TIFF_DOUBLE: + return (float)((double[])data)[index]; + case TIFF_SRATIONAL: + int[] ivalue = getAsSRational(index); + return (float)((double)ivalue[0]/ivalue[1]); + case TIFF_RATIONAL: + long[] lvalue = getAsRational(index); + return (float)((double)lvalue[0]/lvalue[1]); + default: + throw new ClassCastException(); + } + } + + /** + * Returns data in any numerical format as a float. Data in + * TIFF_SRATIONAL or TIFF_RATIONAL format are evaluated by + * dividing the numerator into the denominator using + * double-precision arithmetic. + * + *

    A ClassCastException will be thrown if the field is of + * type TIFF_UNDEFINED or TIFF_ASCII. + */ + public double getAsDouble(int index) { + switch (type) { + case TIFF_BYTE: + return ((byte[])data)[index] & 0xff; + case TIFF_SBYTE: + return ((byte[])data)[index]; + case TIFF_SHORT: + return ((char[])data)[index] & 0xffff; + case TIFF_SSHORT: + return ((short[])data)[index]; + case TIFF_SLONG: + return ((int[])data)[index]; + case TIFF_LONG: + return ((long[])data)[index]; + case TIFF_FLOAT: + return ((float[])data)[index]; + case TIFF_DOUBLE: + return ((double[])data)[index]; + case TIFF_SRATIONAL: + int[] ivalue = getAsSRational(index); + return (double)ivalue[0]/ivalue[1]; + case TIFF_RATIONAL: + long[] lvalue = getAsRational(index); + return (double)lvalue[0]/lvalue[1]; + default: + throw new ClassCastException(); + } + } + + /** + * Returns a TIFF_ASCII data item as a String. + * + *

    A ClassCastException will be thrown if the field is not + * of type TIFF_ASCII. + */ + public String getAsString(int index) { + return ((String[])data)[index]; + } + + /** + * Returns a TIFF_SRATIONAL data item as a two-element array + * of ints. + * + *

    A ClassCastException will be thrown if the field is not + * of type TIFF_SRATIONAL. + */ + public int[] getAsSRational(int index) { + return ((int[][])data)[index]; + } + + /** + * Returns a TIFF_RATIONAL data item as a two-element array + * of ints. + * + *

    A ClassCastException will be thrown if the field is not + * of type TIFF_RATIONAL. + */ + public long[] getAsRational(int index) { + return ((long[][])data)[index]; + } + + /** + * Compares this TIFFField with another + * TIFFField by comparing the tags. + * + *

    Note: this class has a natural ordering that is inconsistent + * with equals(). + * + * @throws IllegalArgumentException if the parameter is null. + * @throws ClassCastException if the parameter is not a + * TIFFField. + */ + public int compareTo(Object o) { + if(o == null) { + throw new IllegalArgumentException(); + } + + int oTag = ((TIFFField)o).getTag(); + + if(tag < oTag) { + return -1; + } else if(tag > oTag) { + return 1; + } else { + return 0; + } + } +} Index: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFImage.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFImage.java diff -N src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFImage.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFImage.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,1813 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.codec.myTiff; + +import java.awt.Rectangle; +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.awt.image.DataBufferShort; +import java.awt.image.DataBufferUShort; +import java.awt.image.IndexColorModel; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.Raster; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import org.apache.batik.ext.awt.image.codec.SeekableStream; +import org.apache.batik.ext.awt.image.rendered.AbstractRed; +import org.apache.batik.ext.awt.image.rendered.CachableRed; + +import com.sun.image.codec.jpeg.JPEGCodec; +import com.sun.image.codec.jpeg.JPEGDecodeParam; +import com.sun.image.codec.jpeg.JPEGImageDecoder; + +public class TIFFImage extends AbstractRed { + + // Compression types + public static final int COMP_NONE = 1; + public static final int COMP_FAX_G3_1D = 2; + public static final int COMP_FAX_G3_2D = 3; + public static final int COMP_FAX_G4_2D = 4; + public static final int COMP_LZW = 5; + public static final int COMP_JPEG_OLD = 6; + public static final int COMP_JPEG_TTN2 = 7; + public static final int COMP_PACKBITS = 32773; + public static final int COMP_DEFLATE = 32946; + + // Image types + private static final int TYPE_UNSUPPORTED = -1; + private static final int TYPE_BILEVEL = 0; + private static final int TYPE_GRAY_4BIT = 1; + private static final int TYPE_GRAY = 2; + private static final int TYPE_GRAY_ALPHA = 3; + private static final int TYPE_PALETTE = 4; + private static final int TYPE_RGB = 5; + private static final int TYPE_RGB_ALPHA = 6; + private static final int TYPE_YCBCR_SUB = 7; + private static final int TYPE_GENERIC = 8; + + // Incidental tags + private static final int TIFF_JPEG_TABLES = 347; + private static final int TIFF_YCBCR_SUBSAMPLING = 530; + + SeekableStream stream; + int tileSize; + int tilesX, tilesY; + long[] tileOffsets; + long[] tileByteCounts; + char[] colormap; + int sampleSize; + int compression; + byte[] palette; + int numBands; + + int chromaSubH; + int chromaSubV; + + // Fax compression related variables + long tiffT4Options; + long tiffT6Options; + int fillOrder; + + // LZW compression related variable + int predictor; + + // TTN2 JPEG related variables + JPEGDecodeParam decodeParam = null; + boolean colorConvertJPEG = false; + + // DEFLATE variables + Inflater inflater = null; + + // Endian-ness indicator + boolean isBigEndian; + + int imageType; + boolean isWhiteZero = false; + int dataType; + + boolean decodePaletteAsShorts; + boolean tiled; + + // Decoders + private TIFFFaxDecoder decoder = null; + private TIFFLZWDecoder lzwDecoder = null; + + /** + * Decode a buffer of data into a Raster with the specified location. + * + * @param data buffer contain an interchange or abbreviated datastream. + * @param decodeParam decoding parameters; may be null unless the + * data buffer contains an abbreviated datastream in which case + * it may not be null or an error will occur. + * @param colorConvert whether to perform color conversion; in this + * case that would be limited to YCbCr-to-RGB. + * @param minX the X position of the returned Raster. + * @param minY the Y position of the returned Raster. + */ + private static final Raster decodeJPEG(byte[] data, + JPEGDecodeParam decodeParam, + boolean colorConvert, + int minX, + int minY) { + // Create an InputStream from the compressed data array. + ByteArrayInputStream jpegStream = new ByteArrayInputStream(data); + + // Create a decoder. + JPEGImageDecoder decoder = decodeParam == null ? + JPEGCodec.createJPEGDecoder(jpegStream) : + JPEGCodec.createJPEGDecoder(jpegStream, + decodeParam); + + // Decode the compressed data into a Raster. + Raster jpegRaster; + try { + jpegRaster = colorConvert ? + decoder.decodeAsBufferedImage().getWritableTile(0, 0) : + decoder.decodeAsRaster(); + } catch (IOException ioe) { + throw new RuntimeException("TIFFImage13"); + } + + // Translate the decoded Raster to the specified location and return. + return jpegRaster.createTranslatedChild(minX, minY); + } + + /** + * Inflates deflated into inflated using the + * Inflater constructed during class instantiation. + */ + private final void inflate(byte[] deflated, byte[] inflated) { + inflater.setInput(deflated); + try { + inflater.inflate(inflated); + } catch(DataFormatException dfe) { + throw new RuntimeException("TIFFImage17"+": "+ + dfe.getMessage()); + } + inflater.reset(); + } + + private static SampleModel createPixelInterleavedSampleModel + (int dataType, int tileWidth, int tileHeight, int bands) { + int [] bandOffsets = new int[bands]; + for (int i=0; i= tilesX) || + (tileY < 0) || (tileY >= tilesY)) { + throw new IllegalArgumentException("TIFFImage12"); + } + + // System.out.println("Called TIFF getTile:" + tileX + "," + tileY); + + + // Get the data array out of the DataBuffer + byte bdata[] = null; + short sdata[] = null; + int idata[] = null; + + SampleModel sampleModel = getSampleModel(); + WritableRaster tile = makeTile(tileX,tileY); + + DataBuffer buffer = tile.getDataBuffer(); + + int dataType = sampleModel.getDataType(); + if (dataType == DataBuffer.TYPE_BYTE) { + bdata = ((DataBufferByte)buffer).getData(); + } else if (dataType == DataBuffer.TYPE_USHORT) { + sdata = ((DataBufferUShort)buffer).getData(); + } else if (dataType == DataBuffer.TYPE_SHORT) { + sdata = ((DataBufferShort)buffer).getData(); + } else if (dataType == DataBuffer.TYPE_INT) { + idata = ((DataBufferInt)buffer).getData(); + } + + // Variables used for swapping when converting from RGB to BGR + byte bswap; + short sswap; + int iswap; + + // Save original file pointer position and seek to tile data location. + long save_offset = 0; + try { + save_offset = stream.getFilePointer(); + stream.seek(tileOffsets[tileY*tilesX + tileX]); + } catch (IOException ioe) { + throw new RuntimeException("TIFFImage13"); + } + + // Number of bytes in this tile (strip) after compression. + int byteCount = (int)tileByteCounts[tileY*tilesX + tileX]; + + // Find out the number of bytes in the current tile + Rectangle newRect; + if (!tiled) + newRect = tile.getBounds(); + else + newRect = new Rectangle(tile.getMinX(), tile.getMinY(), + tileWidth, tileHeight); + + int unitsInThisTile = newRect.width * newRect.height * numBands; + + // Allocate read buffer if needed. + byte data[] = compression != COMP_NONE || imageType == TYPE_PALETTE ? + new byte[byteCount] : null; + + // Read the data, uncompressing as needed. There are four cases: + // bilevel, palette-RGB, 4-bit grayscale, and everything else. + if(imageType == TYPE_BILEVEL) { // bilevel + try { + if (compression == COMP_PACKBITS) { + stream.readFully(data, 0, byteCount); + + // Since the decompressed data will still be packed + // 8 pixels into 1 byte, calculate bytesInThisTile + int bytesInThisTile; + if ((newRect.width % 8) == 0) { + bytesInThisTile = (newRect.width/8) * newRect.height; + } else { + bytesInThisTile = + (newRect.width/8 + 1) * newRect.height; + } + decodePackbits(data, bytesInThisTile, bdata); + } else if (compression == COMP_LZW) { + stream.readFully(data, 0, byteCount); + lzwDecoder.decode(data, bdata, newRect.height); + } else if (compression == COMP_FAX_G3_1D) { + stream.readFully(data, 0, byteCount); + decoder.decode1D(bdata, data, 0, newRect.height); + } else if (compression == COMP_FAX_G3_2D) { + stream.readFully(data, 0, byteCount); + decoder.decode2D(bdata, data, 0, newRect.height, + tiffT4Options); + } else if (compression == COMP_FAX_G4_2D) { + stream.readFully(data, 0, byteCount); + decoder.decodeT6(bdata, data, 0, newRect.height, + tiffT6Options); + } else if (compression == COMP_DEFLATE) { + stream.readFully(data, 0, byteCount); + inflate(data, bdata); + } else if (compression == COMP_NONE) { + stream.readFully(bdata, 0, byteCount); + } + + stream.seek(save_offset); + } catch (IOException ioe) { + throw new RuntimeException("TIFFImage13"); + } + } else if(imageType == TYPE_PALETTE) { // palette-RGB + if (sampleSize == 16) { + + if (decodePaletteAsShorts) { + + short tempData[]= null; + + // At this point the data is 1 banded and will + // become 3 banded only after we've done the palette + // lookup, since unitsInThisTile was calculated with + // 3 bands, we need to divide this by 3. + int unitsBeforeLookup = unitsInThisTile / 3; + + // Since unitsBeforeLookup is the number of shorts, + // but we do our decompression in terms of bytes, we + // need to multiply it by 2 in order to figure out + // how many bytes we'll get after decompression. + int entries = unitsBeforeLookup * 2; + + // Read the data, if compressed, decode it, reset the pointer + try { + + if (compression == COMP_PACKBITS) { + + stream.readFully(data, 0, byteCount); + + byte byteArray[] = new byte[entries]; + decodePackbits(data, entries, byteArray); + tempData = new short[unitsBeforeLookup]; + interpretBytesAsShorts(byteArray, tempData, + unitsBeforeLookup); + + } else if (compression == COMP_LZW) { + + // Read in all the compressed data for this tile + stream.readFully(data, 0, byteCount); + + byte byteArray[] = new byte[entries]; + lzwDecoder.decode(data, byteArray, newRect.height); + tempData = new short[unitsBeforeLookup]; + interpretBytesAsShorts(byteArray, tempData, + unitsBeforeLookup); + + } else if (compression == COMP_DEFLATE) { + + stream.readFully(data, 0, byteCount); + byte byteArray[] = new byte[entries]; + inflate(data, byteArray); + tempData = new short[unitsBeforeLookup]; + interpretBytesAsShorts(byteArray, tempData, + unitsBeforeLookup); + + } else if (compression == COMP_NONE) { + + // byteCount tells us how many bytes are there + // in this tile, but we need to read in shorts, + // which will take half the space, so while + // allocating we divide byteCount by 2. + tempData = new short[byteCount/2]; + readShorts(byteCount/2, tempData); + } + + stream.seek(save_offset); + + } catch (IOException ioe) { + throw new RuntimeException("TIFFImage13"); + } + + if (dataType == DataBuffer.TYPE_USHORT) { + + // Expand the palette image into an rgb image with ushort + // data type. + int cmapValue; + int count = 0, lookup, len = colormap.length/3; + int len2 = len * 2; + for (int i=0; i> 4); + data[dstCount++] = + (byte)(tempData[srcCount++] & 0x0f); + } + + if (padding == 1) { + data[dstCount++] = + (byte)((tempData[srcCount++] & 0xf0) >> 4); + } + } + + int len = colormap.length/3; + int len2 = len*2; + int cmapValue, lookup; + int count = 0; + for (int i=0; i= 0 && b <= 127) { + + // literal run packet + for (int i=0; i<(b + 1); i++) { + dst[dstCount++] = data[srcCount++]; + } + + } else if (b <= -1 && b >= -127) { + + // 2 byte encoded run packet + repeat = data[srcCount++]; + for (int i=0; i<(-b + 1); i++) { + dst[dstCount++] = repeat; + } + + } else { + // no-op packet. Do nothing + srcCount++; + } + } + } catch (java.lang.ArrayIndexOutOfBoundsException ae) { + throw new RuntimeException("TIFFImage14"); + } + + return dst; + } + + // Need a createColorModel(). + // Create ComponentColorModel for TYPE_RGB images + private ComponentColorModel createAlphaComponentColorModel + (int dataType, int numBands, + boolean isAlphaPremultiplied, int transparency) { + + ComponentColorModel ccm = null; + int RGBBits[] = null; + ColorSpace cs = null; + switch(numBands) { + case 2: // gray+alpha + cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); + break; + case 4: // RGB+alpha + cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); + break; + default: + throw new IllegalArgumentException(); + } + + int componentSize = 0; + switch(dataType) { + case DataBuffer.TYPE_BYTE: + componentSize = 8; + break; + case DataBuffer.TYPE_USHORT: + case DataBuffer.TYPE_SHORT: + componentSize = 16; + break; + case DataBuffer.TYPE_INT: + componentSize = 32; + break; + default: + throw new IllegalArgumentException(); + } + + RGBBits = new int[numBands]; + for(int i = 0; i < numBands; i++) { + RGBBits[i] = componentSize; + } + + ccm = new ComponentColorModel(cs, + RGBBits, + true, + isAlphaPremultiplied, + transparency, + dataType); + + + return ccm; + } + +} Index: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFImageDecoder.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFImageDecoder.java diff -N src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFImageDecoder.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFImageDecoder.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,87 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.codec.myTiff; + +import java.awt.image.RenderedImage; +import java.io.IOException; + +import org.apache.batik.ext.awt.image.codec.ImageDecodeParam; +import org.apache.batik.ext.awt.image.codec.ImageDecoderImpl; +import org.apache.batik.ext.awt.image.codec.SeekableStream; + +/** + * A baseline TIFF reader. The reader has some functionality in addition to + * the baseline specifications for Bilevel images, for which the group 3 and + * group 4 decompression schemes have been implemented. Support for LZW + * decompression has also been added. Support for Horizontal differencing + * predictor decoding is also included, when used with LZW compression. + * However, this support is limited to data with bitsPerSample value of 8. + * When reading in RGB images, support for alpha and extraSamples being + * present has been added. Support for reading in images with 16 bit samples + * has been added. Support for the SampleFormat tag (signed samples as well + * as floating-point samples) has also been added. In all other cases, support + * is limited to Baseline specifications. + * + * + */ +public class TIFFImageDecoder extends ImageDecoderImpl { + + // All the TIFF tags that we care about + public static final int TIFF_IMAGE_WIDTH = 256; + public static final int TIFF_IMAGE_LENGTH = 257; + public static final int TIFF_BITS_PER_SAMPLE = 258; + public static final int TIFF_COMPRESSION = 259; + public static final int TIFF_PHOTOMETRIC_INTERPRETATION = 262; + public static final int TIFF_FILL_ORDER = 266; + public static final int TIFF_STRIP_OFFSETS = 273; + public static final int TIFF_SAMPLES_PER_PIXEL = 277; + public static final int TIFF_ROWS_PER_STRIP = 278; + public static final int TIFF_STRIP_BYTE_COUNTS = 279; + public static final int TIFF_X_RESOLUTION = 282; + public static final int TIFF_Y_RESOLUTION = 283; + public static final int TIFF_PLANAR_CONFIGURATION = 284; + public static final int TIFF_T4_OPTIONS = 292; + public static final int TIFF_T6_OPTIONS = 293; + public static final int TIFF_RESOLUTION_UNIT = 296; + public static final int TIFF_PREDICTOR = 317; + public static final int TIFF_COLORMAP = 320; + public static final int TIFF_TILE_WIDTH = 322; + public static final int TIFF_TILE_LENGTH = 323; + public static final int TIFF_TILE_OFFSETS = 324; + public static final int TIFF_TILE_BYTE_COUNTS = 325; + public static final int TIFF_EXTRA_SAMPLES = 338; + public static final int TIFF_SAMPLE_FORMAT = 339; + public static final int TIFF_S_MIN_SAMPLE_VALUE = 340; + public static final int TIFF_S_MAX_SAMPLE_VALUE = 341; + + public TIFFImageDecoder(SeekableStream input, + ImageDecodeParam param) { + super(input, param); + } + + public int getNumPages() throws IOException { + return TIFFDirectory.getNumDirectories(input); + } + + public RenderedImage decodeAsRenderedImage(int page) throws IOException { + if ((page < 0) || (page >= getNumPages())) { + throw new IOException("TIFFImageDecoder0"); + } + return new TIFFImage(input, (TIFFDecodeParam)param, page); + } +} Index: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFImageEncoder.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFImageEncoder.java diff -N src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFImageEncoder.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFImageEncoder.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,1713 @@ +/* + + Copyright 2001-2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.codec.myTiff; + +import java.awt.Rectangle; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.IndexColorModel; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.zip.Deflater; + +import org.apache.batik.ext.awt.image.codec.ImageEncodeParam; +import org.apache.batik.ext.awt.image.codec.ImageEncoderImpl; +import org.apache.batik.ext.awt.image.codec.SeekableOutputStream; + +import com.sun.image.codec.jpeg.JPEGEncodeParam; +import com.sun.image.codec.jpeg.JPEGQTable; + +/** + * A baseline TIFF writer. The writer outputs TIFF images in either Bilevel, + * Greyscale, Palette color or Full Color modes. + * + */ +public class TIFFImageEncoder extends ImageEncoderImpl { + + // Image Types + private static final int TIFF_UNSUPPORTED = -1; + private static final int TIFF_BILEVEL_WHITE_IS_ZERO = 0; + private static final int TIFF_BILEVEL_BLACK_IS_ZERO = 1; + private static final int TIFF_GRAY = 2; + private static final int TIFF_PALETTE = 3; + private static final int TIFF_RGB = 4; + private static final int TIFF_CMYK = 5; + private static final int TIFF_YCBCR = 6; + private static final int TIFF_CIELAB = 7; + private static final int TIFF_GENERIC = 8; + + // Compression types + private static final int COMP_NONE = 1; + private static final int COMP_JPEG_TTN2 = 7; + private static final int COMP_PACKBITS = 32773; + private static final int COMP_DEFLATE = 32946; + + // Incidental tags + private static final int TIFF_JPEG_TABLES = 347; + private static final int TIFF_YCBCR_SUBSAMPLING = 530; + private static final int TIFF_YCBCR_POSITIONING = 531; + private static final int TIFF_REF_BLACK_WHITE = 532; + + // ExtraSamples types + private static final int EXTRA_SAMPLE_UNSPECIFIED = 0; + private static final int EXTRA_SAMPLE_ASSOCIATED_ALPHA = 1; + private static final int EXTRA_SAMPLE_UNASSOCIATED_ALPHA = 2; + + // Default values + private static final int DEFAULT_ROWS_PER_STRIP = 8; + + public TIFFImageEncoder(OutputStream output, ImageEncodeParam param) { + super(output, param); + if (this.param == null) { + this.param = new TIFFEncodeParam(); + } + } + + /** + * Encodes a RenderedImage and writes the output to the + * OutputStream associated with this ImageEncoder. + */ + public void encode(RenderedImage im) throws IOException { + // Write the file header (8 bytes). + writeFileHeader(); + + // Get the encoding parameters. + TIFFEncodeParam encodeParam = (TIFFEncodeParam)param; + + Iterator iter = encodeParam.getExtraImages(); + if(iter != null) { + int ifdOffset = 8; + RenderedImage nextImage = im; + TIFFEncodeParam nextParam = encodeParam; + boolean hasNext; + do { + hasNext = iter.hasNext(); + ifdOffset = encode(nextImage, nextParam, ifdOffset, !hasNext); + if(hasNext) { + Object obj = iter.next(); + if(obj instanceof RenderedImage) { + nextImage = (RenderedImage)obj; + nextParam = encodeParam; + } else if(obj instanceof Object[]) { + Object[] o = (Object[])obj; + nextImage = (RenderedImage)o[0]; + nextParam = (TIFFEncodeParam)o[1]; + } + } + } while(hasNext); + } else { + encode(im, encodeParam, 8, true); + } + } + + private int encode(RenderedImage im, TIFFEncodeParam encodeParam, + int ifdOffset, boolean isLast) throws IOException { + // Currently all images are stored uncompressed. + int compression = encodeParam.getCompression(); + + // Get tiled output preference. + boolean isTiled = encodeParam.getWriteTiled(); + + // Set bounds. + int minX = im.getMinX(); + int minY = im.getMinY(); + int width = im.getWidth(); + int height = im.getHeight(); + + // Get SampleModel. + SampleModel sampleModel = im.getSampleModel(); + + // Retrieve and verify sample size. + int sampleSize[] = sampleModel.getSampleSize(); + for(int i = 1; i < sampleSize.length; i++) { + if(sampleSize[i] != sampleSize[0]) { + throw new Error("TIFFImageEncoder0"); + } + } + + // Check low bit limits. + int numBands = sampleModel.getNumBands(); + if((sampleSize[0] == 1 || sampleSize[0] == 4) && numBands != 1) { + throw new Error("TIFFImageEncoder1"); + } + + // Retrieve and verify data type. + int dataType = sampleModel.getDataType(); + switch(dataType) { + case DataBuffer.TYPE_BYTE: + if(sampleSize[0] != 1 && sampleSize[0] == 4 && + sampleSize[0] != 8) { + throw new Error("TIFFImageEncoder2"); + } + break; + case DataBuffer.TYPE_SHORT: + case DataBuffer.TYPE_USHORT: + if(sampleSize[0] != 16) { + throw new Error("TIFFImageEncoder3"); + } + break; + case DataBuffer.TYPE_INT: + case DataBuffer.TYPE_FLOAT: + if(sampleSize[0] != 32) { + throw new Error("TIFFImageEncoder4"); + } + break; + default: + throw new Error("TIFFImageEncoder5"); + } + + boolean dataTypeIsShort = + dataType == DataBuffer.TYPE_SHORT || + dataType == DataBuffer.TYPE_USHORT; + + ColorModel colorModel = im.getColorModel(); + if (colorModel != null && + colorModel instanceof IndexColorModel && + dataType != DataBuffer.TYPE_BYTE) { + // Don't support (unsigned) short palette-color images. + throw new Error("TIFFImageEncoder6"); + } + IndexColorModel icm = null; + int sizeOfColormap = 0; + char colormap[] = null; + + // Set image type. + int imageType = TIFF_UNSUPPORTED; + int numExtraSamples = 0; + int extraSampleType = EXTRA_SAMPLE_UNSPECIFIED; + if(colorModel instanceof IndexColorModel) { // Bilevel or palette + icm = (IndexColorModel)colorModel; + int mapSize = icm.getMapSize(); + + if(sampleSize[0] == 1 && numBands == 1) { // Bilevel image + + if (mapSize != 2) { + throw new IllegalArgumentException( + "TIFFImageEncoder7"); + } + + byte r[] = new byte[mapSize]; + icm.getReds(r); + byte g[] = new byte[mapSize]; + icm.getGreens(g); + byte b[] = new byte[mapSize]; + icm.getBlues(b); + + if ((r[0] & 0xff) == 0 && + (r[1] & 0xff) == 255 && + (g[0] & 0xff) == 0 && + (g[1] & 0xff) == 255 && + (b[0] & 0xff) == 0 && + (b[1] & 0xff) == 255) { + + imageType = TIFF_BILEVEL_BLACK_IS_ZERO; + + } else if ((r[0] & 0xff) == 255 && + (r[1] & 0xff) == 0 && + (g[0] & 0xff) == 255 && + (g[1] & 0xff) == 0 && + (b[0] & 0xff) == 255 && + (b[1] & 0xff) == 0) { + + imageType = TIFF_BILEVEL_WHITE_IS_ZERO; + + } else { + imageType = TIFF_PALETTE; + } + + } else if(numBands == 1) { // Non-bilevel image. + // Palette color image. + imageType = TIFF_PALETTE; + } + } else if(colorModel == null) { + + if(sampleSize[0] == 1 && numBands == 1) { // bilevel + imageType = TIFF_BILEVEL_BLACK_IS_ZERO; + } else { // generic image + imageType = TIFF_GENERIC; + if(numBands > 1) { + numExtraSamples = numBands - 1; + } + } + + } else { // colorModel is non-null but not an IndexColorModel + ColorSpace colorSpace = colorModel.getColorSpace(); + + switch(colorSpace.getType()) { + case ColorSpace.TYPE_CMYK: + imageType = TIFF_CMYK; + break; + case ColorSpace.TYPE_GRAY: + imageType = TIFF_GRAY; + break; + case ColorSpace.TYPE_Lab: + imageType = TIFF_CIELAB; + break; + case ColorSpace.TYPE_RGB: + if(compression == COMP_JPEG_TTN2 && + encodeParam.getJPEGCompressRGBToYCbCr()) { + imageType = TIFF_YCBCR; + } else { + imageType = TIFF_RGB; + } + break; + case ColorSpace.TYPE_YCbCr: + imageType = TIFF_YCBCR; + break; + default: + imageType = TIFF_GENERIC; // generic + break; + } + + if(imageType == TIFF_GENERIC) { + numExtraSamples = numBands - 1; + } else if(numBands > 1) { + numExtraSamples = numBands - colorSpace.getNumComponents(); + } + + if(numExtraSamples == 1 && colorModel.hasAlpha()) { + extraSampleType = colorModel.isAlphaPremultiplied() ? + EXTRA_SAMPLE_ASSOCIATED_ALPHA : + EXTRA_SAMPLE_UNASSOCIATED_ALPHA; + } + } + + if(imageType == TIFF_UNSUPPORTED) { + throw new Error("TIFFImageEncoder8"); + } + + // Check JPEG compatibility. + if(compression == COMP_JPEG_TTN2) { + if(imageType == TIFF_PALETTE) { + throw new Error("TIFFImageEncoder11"); + } else if(!(sampleSize[0] == 8 && + (imageType == TIFF_GRAY || + imageType == TIFF_RGB || + imageType == TIFF_YCBCR))) { + throw new Error("TIFFImageEncoder9"); + } + } + + int photometricInterpretation = -1; + switch (imageType) { + + case TIFF_BILEVEL_WHITE_IS_ZERO: + photometricInterpretation = 0; + break; + + case TIFF_BILEVEL_BLACK_IS_ZERO: + photometricInterpretation = 1; + break; + + case TIFF_GRAY: + case TIFF_GENERIC: + // Since the CS_GRAY colorspace is always of type black_is_zero + photometricInterpretation = 1; + break; + + case TIFF_PALETTE: + photometricInterpretation = 3; + + icm = (IndexColorModel)colorModel; + sizeOfColormap = icm.getMapSize(); + + byte r[] = new byte[sizeOfColormap]; + icm.getReds(r); + byte g[] = new byte[sizeOfColormap]; + icm.getGreens(g); + byte b[] = new byte[sizeOfColormap]; + icm.getBlues(b); + + int redIndex = 0, greenIndex = sizeOfColormap; + int blueIndex = 2 * sizeOfColormap; + colormap = new char[sizeOfColormap * 3]; + for (int i=0; i 0 ? + encodeParam.getTileWidth() : im.getTileWidth(); + tileHeight = encodeParam.getTileHeight() > 0 ? + encodeParam.getTileHeight() : im.getTileHeight(); + } else { + tileWidth = width; + + tileHeight = encodeParam.getTileHeight() > 0 ? + encodeParam.getTileHeight() : DEFAULT_ROWS_PER_STRIP; + } + + // Re-tile for JPEG conformance if needed. + JPEGEncodeParam jep = null; + if(compression == COMP_JPEG_TTN2) { + // Get JPEGEncodeParam from encodeParam. + jep = encodeParam.getJPEGEncodeParam(); + + // Determine maximum subsampling. + int maxSubH = jep.getHorizontalSubsampling(0); + int maxSubV = jep.getVerticalSubsampling(0); + for(int i = 1; i < numBands; i++) { + int subH = jep.getHorizontalSubsampling(i); + if(subH > maxSubH) { + maxSubH = subH; + } + int subV = jep.getVerticalSubsampling(i); + if(subV > maxSubV) { + maxSubV = subV; + } + } + + int factorV = 8*maxSubV; + tileHeight = + (int)((float)tileHeight/(float)factorV + 0.5F)*factorV; + if(tileHeight < factorV) { + tileHeight = factorV; + } + + if(isTiled) { + int factorH = 8*maxSubH; + tileWidth = + (int)((float)tileWidth/(float)factorH + 0.5F)*factorH; + if(tileWidth < factorH) { + tileWidth = factorH; + } + } + } + + int numTiles; + if(isTiled) { + // NB: Parentheses are used in this statement for correct rounding. + numTiles = + ((width + tileWidth - 1)/tileWidth) * + ((height + tileHeight - 1)/tileHeight); + } else { + numTiles = (int)Math.ceil((double)height/(double)tileHeight); + } + + long tileByteCounts[] = new long[numTiles]; + + long bytesPerRow = + (long)Math.ceil((sampleSize[0] / 8.0) * tileWidth * numBands); + + long bytesPerTile = bytesPerRow * tileHeight; + + for (int i=0; i 0) { + char[] extraSamples = new char[numExtraSamples]; + for(int i = 0; i < numExtraSamples; i++) { + extraSamples[i] = (char)extraSampleType; + } + fields.add(new TIFFField(TIFFImageDecoder.TIFF_EXTRA_SAMPLES, + TIFFField.TIFF_SHORT, numExtraSamples, + extraSamples)); + } + + // Data Sample Format Extension fields. + if(dataType != DataBuffer.TYPE_BYTE) { + // SampleFormat + char[] sampleFormat = new char[numBands]; + if(dataType == DataBuffer.TYPE_FLOAT) { + sampleFormat[0] = 3; + } else if(dataType == DataBuffer.TYPE_USHORT) { + sampleFormat[0] = 1; + } else { + sampleFormat[0] = 2; + } + for(int b = 1; b < numBands; b++) { + sampleFormat[b] = sampleFormat[0]; + } + fields.add(new TIFFField(TIFFImageDecoder.TIFF_SAMPLE_FORMAT, + TIFFField.TIFF_SHORT, numBands, + sampleFormat)); + + // NOTE: We don't bother setting the SMinSampleValue and + // SMaxSampleValue fields as these both default to the + // extrema of the respective data types. Probably we should + // check for the presence of the "extrema" property and + // use it if available. + } + + // Initialize some JPEG variables. + com.sun.image.codec.jpeg.JPEGEncodeParam jpegEncodeParam = null; + com.sun.image.codec.jpeg.JPEGImageEncoder jpegEncoder = null; + int jpegColorID = 0; + + if(compression == COMP_JPEG_TTN2) { + + // Initialize JPEG color ID. + jpegColorID = + com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_UNKNOWN; + switch(imageType) { + case TIFF_GRAY: + case TIFF_PALETTE: + jpegColorID = + com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_GRAY; + break; + case TIFF_RGB: + jpegColorID = + com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_RGB; + break; + case TIFF_YCBCR: + jpegColorID = + com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_YCbCr; + break; + } + + // Get the JDK encoding parameters. + Raster tile00 = im.getTile(0, 0); + jpegEncodeParam = + com.sun.image.codec.jpeg.JPEGCodec.getDefaultJPEGEncodeParam( + tile00, jpegColorID); + + modifyEncodeParam(jep, jpegEncodeParam, numBands); + + // Write an abbreviated tables-only stream to JPEGTables field. + jpegEncodeParam.setImageInfoValid(false); + jpegEncodeParam.setTableInfoValid(true); + ByteArrayOutputStream tableStream = + new ByteArrayOutputStream(); + jpegEncoder = + com.sun.image.codec.jpeg.JPEGCodec.createJPEGEncoder( + tableStream, + jpegEncodeParam); + jpegEncoder.encode(tile00); + byte[] tableData = tableStream.toByteArray(); + fields.add(new TIFFField(TIFF_JPEG_TABLES, + TIFFField.TIFF_UNDEFINED, + tableData.length, + tableData)); + + // Reset encoder so it's recreated below. + jpegEncoder = null; + } + + if(imageType == TIFF_YCBCR) { + // YCbCrSubSampling: 2 is the default so we must write 1 as + // we do not (yet) do any subsampling. + char subsampleH = 1; + char subsampleV = 1; + + // If JPEG, update values. + if(compression == COMP_JPEG_TTN2) { + // Determine maximum subsampling. + subsampleH = (char)jep.getHorizontalSubsampling(0); + subsampleV = (char)jep.getVerticalSubsampling(0); + for(int i = 1; i < numBands; i++) { + char subH = (char)jep.getHorizontalSubsampling(i); + if(subH > subsampleH) { + subsampleH = subH; + } + char subV = (char)jep.getVerticalSubsampling(i); + if(subV > subsampleV) { + subsampleV = subV; + } + } + } + + fields.add(new TIFFField(TIFF_YCBCR_SUBSAMPLING, + TIFFField.TIFF_SHORT, 2, + new char[] {subsampleH, subsampleV})); + + + // YCbCr positioning. + fields.add(new TIFFField(TIFF_YCBCR_POSITIONING, + TIFFField.TIFF_SHORT, 1, + new int[] {compression == COMP_JPEG_TTN2 ? + 1 : 2})); + + // Reference black/white. + long[][] refbw; + if(compression == COMP_JPEG_TTN2) { + refbw = + new long[][] { // no headroon/footroom + {0, 1}, {255, 1}, {128, 1}, {255, 1}, {128, 1}, {255, 1} + }; + } else { + refbw = + new long[][] { // CCIR 601.1 headroom/footroom (presumptive) + {15, 1}, {235, 1}, {128, 1}, {240, 1}, {128, 1}, {240, 1} + }; + } + fields.add(new TIFFField(TIFF_REF_BLACK_WHITE, + TIFFField.TIFF_RATIONAL, 6, + refbw)); + } + + // ---- No more automatically generated fields should be added + // after this point. ---- + + // Add extra fields specified via the encoding parameters. + TIFFField[] extraFields = encodeParam.getExtraFields(); + if(extraFields != null) { + ArrayList extantTags = new ArrayList(fields.size()); + Iterator fieldIter = fields.iterator(); + while(fieldIter.hasNext()) { + TIFFField fld = (TIFFField)fieldIter.next(); + extantTags.add(new Integer(fld.getTag())); + } + + int numExtraFields = extraFields.length; + for(int i = 0; i < numExtraFields; i++) { + TIFFField fld = extraFields[i]; + Integer tagValue = new Integer(fld.getTag()); + if(!extantTags.contains(tagValue)) { + fields.add(fld); + extantTags.add(tagValue); + } + } + } + + // ---- No more fields of any type should be added after this. ---- + + // Determine the size of the IFD which is written after the header + // of the stream or after the data of the previous image in a + // multi-page stream. + int dirSize = getDirectorySize(fields); + + // The first data segment is written after the field overflow + // following the IFD so initialize the first offset accordingly. + tileOffsets[0] = ifdOffset + dirSize; + + // Branch here depending on whether data are being comrpressed. + // If not, then the IFD is written immediately. + // If so then there are three possibilities: + // A) the OutputStream is a SeekableOutputStream (outCache null); + // B) the OutputStream is not a SeekableOutputStream and a file cache + // is used (outCache non-null, tempFile non-null); + // C) the OutputStream is not a SeekableOutputStream and a memory cache + // is used (outCache non-null, tempFile null). + + OutputStream outCache = null; + byte[] compressBuf = null; + File tempFile = null; + + int nextIFDOffset = 0; + boolean skipByte = false; + + Deflater deflater = null; + boolean jpegRGBToYCbCr = false; + + if(compression == COMP_NONE) { + // Determine the number of bytes of padding necessary between + // the end of the IFD and the first data segment such that the + // alignment of the data conforms to the specification (required + // for uncompressed data only). + int numBytesPadding = 0; + if(sampleSize[0] == 16 && tileOffsets[0] % 2 != 0) { + numBytesPadding = 1; + tileOffsets[0]++; + } else if(sampleSize[0] == 32 && tileOffsets[0] % 4 != 0) { + numBytesPadding = (int)(4 - tileOffsets[0] % 4); + tileOffsets[0] += numBytesPadding; + } + + // Update the data offsets (which TIFFField stores by reference). + for (int i = 1; i < numTiles; i++) { + tileOffsets[i] = tileOffsets[i-1] + tileByteCounts[i-1]; + } + + if(!isLast) { + // Determine the offset of the next IFD. + nextIFDOffset = (int)(tileOffsets[0] + totalBytesOfData); + + // IFD offsets must be on a word boundary. + if ((nextIFDOffset&0x01) != 0) { + nextIFDOffset++; + skipByte = true; + } + } + + // Write the IFD and field overflow before the image data. + writeDirectory(ifdOffset, fields, nextIFDOffset); + + // Write any padding bytes needed between the end of the IFD + // and the start of the actual image data. + if(numBytesPadding != 0) { + for(int padding = 0; padding < numBytesPadding; padding++) { + output.write((byte)0); + } + } + } else { + // If compressing, the cannot be written yet as the size of the + // data segments is unknown. + + if((output instanceof SeekableOutputStream)) { + // Simply seek to the first data segment position. + ((SeekableOutputStream)output).seek(tileOffsets[0]); + } else { + // Cache the original OutputStream. + outCache = output; + + try { + // Attempt to create a temporary file. + tempFile = File.createTempFile("jai-SOS-", ".tmp"); + tempFile.deleteOnExit(); + RandomAccessFile raFile = + new RandomAccessFile(tempFile, "rw"); + output = new SeekableOutputStream(raFile); + + // this method is exited! + } catch(Exception e) { + // Allocate memory for the entire image data (!). + output = new ByteArrayOutputStream((int)totalBytesOfData); + } + } + + int bufSize = 0; + switch(compression) { + case COMP_PACKBITS: + bufSize = (int)(bytesPerTile + + ((bytesPerRow+127)/128)*tileHeight); + break; + case COMP_JPEG_TTN2: + bufSize = 0; + + // Set color conversion flag. + if(imageType == TIFF_YCBCR && + colorModel != null && + colorModel.getColorSpace().getType() == + ColorSpace.TYPE_RGB) { + jpegRGBToYCbCr = true; + } + case COMP_DEFLATE: + bufSize = (int)bytesPerTile; + deflater = new Deflater(encodeParam.getDeflateLevel()); + break; + default: + bufSize = 0; + } + if(bufSize != 0) { + compressBuf = new byte[bufSize]; + } + } + + // ---- Writing of actual image data ---- + + // Buffer for up to tileHeight rows of pixels + int[] pixels = null; + float[] fpixels = null; + + // Whether to test for contiguous data. + boolean checkContiguous = + ((sampleSize[0] == 1 && + sampleModel instanceof MultiPixelPackedSampleModel && + dataType == DataBuffer.TYPE_BYTE) || + (sampleSize[0] == 8 && + sampleModel instanceof ComponentSampleModel)); + + // Also create a buffer to hold tileHeight lines of the + // data to be written to the file, so we can use array writes. + byte[] bpixels = null; + if(compression != COMP_JPEG_TTN2) { + if(dataType == DataBuffer.TYPE_BYTE) { + bpixels = new byte[tileHeight * tileWidth * numBands]; + } else if(dataTypeIsShort) { + bpixels = new byte[2 * tileHeight * tileWidth * numBands]; + } else if(dataType == DataBuffer.TYPE_INT || + dataType == DataBuffer.TYPE_FLOAT) { + bpixels = new byte[4 * tileHeight * tileWidth * numBands]; + } + } + + // Process tileHeight rows at a time + int lastRow = minY + height; + int lastCol = minX + width; + int tileNum = 0; + for (int row = minY; row < lastRow; row += tileHeight) { + int rows = isTiled ? + tileHeight : Math.min(tileHeight, lastRow - row); + int size = rows * tileWidth * numBands; + + for(int col = minX; col < lastCol; col += tileWidth) { + // Grab the pixels + Raster src = + im.getData(new Rectangle(col, row, tileWidth, rows)); + + boolean useDataBuffer = false; + if(compression != COMP_JPEG_TTN2) { // JPEG access Raster + if(checkContiguous) { + if(sampleSize[0] == 8) { // 8-bit + ComponentSampleModel csm = + (ComponentSampleModel)src.getSampleModel(); + int[] bankIndices = csm.getBankIndices(); + int[] bandOffsets = csm.getBandOffsets(); + int pixelStride = csm.getPixelStride(); + int lineStride = csm.getScanlineStride(); + + if(pixelStride != numBands || + lineStride != bytesPerRow) { + useDataBuffer = false; + } else { + useDataBuffer = true; + for(int i = 0; + useDataBuffer && i < numBands; + i++) { + if(bankIndices[i] != 0 || + bandOffsets[i] != i) { + useDataBuffer = false; + } + } + } + } else { // 1-bit + MultiPixelPackedSampleModel mpp = + (MultiPixelPackedSampleModel)src.getSampleModel(); + if(mpp.getNumBands() == 1 && + mpp.getDataBitOffset() == 0 && + mpp.getPixelBitStride() == 1) { + useDataBuffer = true; + } + } + } + + if(!useDataBuffer) { + if(dataType == DataBuffer.TYPE_FLOAT) { + fpixels = src.getPixels(col, row, tileWidth, rows, + fpixels); + } else { + pixels = src.getPixels(col, row, tileWidth, rows, + pixels); + } + } + } + + int index; + + int pixel = 0; + int k = 0; + switch(sampleSize[0]) { + + case 1: + + if(useDataBuffer) { + byte[] btmp = + ((DataBufferByte)src.getDataBuffer()).getData(); + MultiPixelPackedSampleModel mpp = + (MultiPixelPackedSampleModel)src.getSampleModel(); + int lineStride = mpp.getScanlineStride(); + int inOffset = + mpp.getOffset(col - + src.getSampleModelTranslateX(), + row - + src.getSampleModelTranslateY()); + if(lineStride == (int)bytesPerRow) { + System.arraycopy(btmp, inOffset, + bpixels, 0, + (int)bytesPerRow*rows); + } else { + int outOffset = 0; + for(int j = 0; j < rows; j++) { + System.arraycopy(btmp, inOffset, + bpixels, outOffset, + (int)bytesPerRow); + inOffset += lineStride; + outOffset += (int)bytesPerRow; + } + } + } else { + index = 0; + + // For each of the rows in a strip + for (int i=0; i 0) { + pixel = 0; + for (int j=0; j> 8); + bpixels[ls++] = (byte)(value & 0x00ff); + } + + if(compression == COMP_NONE) { + output.write(bpixels, 0, size*2); + } else if(compression == COMP_PACKBITS) { + int numCompressedBytes = + compressPackBits(bpixels, rows, + (int)bytesPerRow, + compressBuf); + tileByteCounts[tileNum++] = numCompressedBytes; + output.write(compressBuf, 0, numCompressedBytes); + } else if(compression == COMP_DEFLATE) { + int numCompressedBytes = + deflate(deflater, bpixels, compressBuf); + tileByteCounts[tileNum++] = numCompressedBytes; + output.write(compressBuf, 0, numCompressedBytes); + } + break; + + case 32: + if(dataType == DataBuffer.TYPE_INT) { + int li = 0; + for (int i = 0; i < size; i++) { + int value = pixels[i]; + bpixels[li++] = (byte)((value & 0xff000000) >> 24); + bpixels[li++] = (byte)((value & 0x00ff0000) >> 16); + bpixels[li++] = (byte)((value & 0x0000ff00) >> 8); + bpixels[li++] = (byte)(value & 0x000000ff); + } + } else { // DataBuffer.TYPE_FLOAT + int lf = 0; + for (int i = 0; i < size; i++) { + int value = Float.floatToIntBits(fpixels[i]); + bpixels[lf++] = (byte)((value & 0xff000000) >> 24); + bpixels[lf++] = (byte)((value & 0x00ff0000) >> 16); + bpixels[lf++] = (byte)((value & 0x0000ff00) >> 8); + bpixels[lf++] = (byte)(value & 0x000000ff); + } + } + if(compression == COMP_NONE) { + output.write(bpixels, 0, size*4); + } else if(compression == COMP_PACKBITS) { + int numCompressedBytes = + compressPackBits(bpixels, rows, + (int)bytesPerRow, + compressBuf); + tileByteCounts[tileNum++] = numCompressedBytes; + output.write(compressBuf, 0, numCompressedBytes); + } else if(compression == COMP_DEFLATE) { + int numCompressedBytes = + deflate(deflater, bpixels, compressBuf); + tileByteCounts[tileNum++] = numCompressedBytes; + output.write(compressBuf, 0, numCompressedBytes); + } + break; + + } + } + } + + if(compression == COMP_NONE) { + // Write an extra byte for IFD word alignment if needed. + if(skipByte) { + output.write((byte)0); + } + } else { + // Recompute the tile offsets the size of the compressed tiles. + int totalBytes = 0; + for (int i=1; i 4 bytes. + int dirSize = 2 + numEntries*12 + 4; + + // Loop over fields adding the size of all values > 4 bytes. + Iterator iter = fields.iterator(); + while(iter.hasNext()) { + // Get the field. + TIFFField field = (TIFFField)iter.next(); + + // Determine the size of the field value. + int valueSize = field.getCount()*sizeOfType[field.getType()]; + + // Add any excess size. + if(valueSize > 4) { + dirSize += valueSize; + } + } + + return dirSize; + } + + private void writeFileHeader() throws IOException { + // 8 byte image file header + + // Byte order used within the file - Big Endian + output.write('M'); + output.write('M'); + + // Magic value + output.write(0); + output.write(42); + + // Offset in bytes of the first IFD. + writeLong(8); + } + + private void writeDirectory(int thisIFDOffset, SortedSet fields, + int nextIFDOffset) + throws IOException { + + // 2 byte count of number of directory entries (fields) + int numEntries = fields.size(); + + long offsetBeyondIFD = thisIFDOffset + 12 * numEntries + 4 + 2; + ArrayList tooBig = new ArrayList(); + + // Write number of fields in the IFD + writeUnsignedShort(numEntries); + + Iterator iter = fields.iterator(); + while(iter.hasNext()) { + + // 12 byte field entry TIFFField + TIFFField field = (TIFFField)iter.next(); + + // byte 0-1 Tag that identifies a field + int tag = field.getTag(); + writeUnsignedShort(tag); + + // byte 2-3 The field type + int type = field.getType(); + writeUnsignedShort(type); + + // bytes 4-7 the number of values of the indicated type except + // ASCII-valued fields which require the total number of bytes. + int count = field.getCount(); + int valueSize = getValueSize(field); + writeLong(type == TIFFField.TIFF_ASCII ? valueSize : count); + + // bytes 8 - 11 the value or value offset + if (valueSize > 4) { + + // We need an offset as data won't fit into 4 bytes + writeLong(offsetBeyondIFD); + offsetBeyondIFD += valueSize; + tooBig.add(field); + + } else { + + writeValuesAsFourBytes(field); + } + + } + + // Address of next IFD + writeLong(nextIFDOffset); + + // Write the tag values that did not fit into 4 bytes + for (int i = 0; i < tooBig.size(); i++) { + writeValues((TIFFField)tooBig.get(i)); + } + } + + /** + * Determine the number of bytes in the value portion of the field. + */ + private static final int getValueSize(TIFFField field) { + int type = field.getType(); + int count = field.getCount(); + int valueSize = 0; + if(type == TIFFField.TIFF_ASCII) { + for(int i = 0; i < count; i++) { + byte[] stringBytes = field.getAsString(i).getBytes(); + valueSize += stringBytes.length; + if(stringBytes[stringBytes.length-1] != (byte)0) { + valueSize++; + } + } + } else { + valueSize = count * sizeOfType[type]; + } + return valueSize; + } + + private static final int[] sizeOfType = { + 0, // 0 = n/a + 1, // 1 = byte + 1, // 2 = ascii + 2, // 3 = short + 4, // 4 = long + 8, // 5 = rational + 1, // 6 = sbyte + 1, // 7 = undefined + 2, // 8 = sshort + 4, // 9 = slong + 8, // 10 = srational + 4, // 11 = float + 8 // 12 = double + }; + + private void writeValuesAsFourBytes(TIFFField field) throws IOException { + + int dataType = field.getType(); + int count = field.getCount(); + + switch (dataType) { + + // unsigned 8 bits + case TIFFField.TIFF_BYTE: + byte bytes[] = field.getAsBytes(); + if (count > 4) count =4; + for (int i=0; i 2) count=2; + for (int i=0; i>> 32); + writeLong(longBits & 0xffffffff); + } + break; + + case TIFFField.TIFF_RATIONAL: + case TIFFField.TIFF_SRATIONAL: + long rationals[][] = field.getAsRationals(); + for (int i=0; i>> 8); + output.write(s & 0x00ff); + } + + private void writeLong(long l) throws IOException { + output.write( (int)((l & 0xff000000) >>> 24)); + output.write( (int)((l & 0x00ff0000) >>> 16)); + output.write( (int)((l & 0x0000ff00) >>> 8)); + output.write( ((int)l & 0x000000ff)); + } + + /** + * Returns the current offset in the supplied OutputStream. + * This method should only be used if compressing data. + */ + private long getOffset(OutputStream out) throws IOException { + if(out instanceof ByteArrayOutputStream) { + return ((ByteArrayOutputStream)out).size(); + } else if(out instanceof SeekableOutputStream) { + return ((SeekableOutputStream)out).getFilePointer(); + } else { + // Shouldn't happen. + throw new IllegalStateException(); + } + } + + /** + * Performs PackBits compression on a tile of data. + */ + private static int compressPackBits(byte[] data, int numRows, + int bytesPerRow, byte[] compData) { + int inOffset = 0; + int outOffset = 0; + + for(int i = 0; i < numRows; i++) { + outOffset = packBits(data, inOffset, bytesPerRow, + compData, outOffset); + inOffset += bytesPerRow; + } + + return outOffset; + } + + /** + * Performs PackBits compression for a single buffer of data. + * This should be called for each row of each tile. The returned + * value is the offset into the output buffer after compression. + */ + private static int packBits(byte[] input, int inOffset, int inCount, + byte[] output, int outOffset) { + int inMax = inOffset + inCount - 1; + int inMaxMinus1 = inMax - 1; + + while(inOffset <= inMax) { + int run = 1; + byte replicate = input[inOffset]; + while(run < 127 && inOffset < inMax && + input[inOffset] == input[inOffset+1]) { + run++; + inOffset++; + } + if(run > 1) { + inOffset++; + output[outOffset++] = (byte)(-(run - 1)); + output[outOffset++] = replicate; + } + + run = 0; + int saveOffset = outOffset; + while(run < 128 && + ((inOffset < inMax && + input[inOffset] != input[inOffset+1]) || + (inOffset < inMaxMinus1 && + input[inOffset] != input[inOffset+2]))) { + run++; + output[++outOffset] = input[inOffset++]; + } + if(run > 0) { + output[saveOffset] = (byte)(run - 1); + outOffset++; + } + + if(inOffset == inMax) { + if(run > 0 && run < 128) { + output[saveOffset]++; + output[outOffset++] = input[inOffset++]; + } else { + output[outOffset++] = (byte)0; + output[outOffset++] = input[inOffset++]; + } + } + } + + return outOffset; + } + + private static int deflate(Deflater deflater, + byte[] inflated, byte[] deflated) { + deflater.setInput(inflated); + deflater.finish(); + int numCompressedBytes = deflater.deflate(deflated); + deflater.reset(); + return numCompressedBytes; + } + + private static void modifyEncodeParam(JPEGEncodeParam src, + JPEGEncodeParam dst, + int nbands) { + dst.setDensityUnit (src.getDensityUnit()); + dst.setXDensity (src.getXDensity()); + dst.setYDensity (src.getYDensity()); + dst.setRestartInterval(src.getRestartInterval()); + for (int i=0; i<4; i++) { + JPEGQTable tbl = src.getQTable(i); + if (tbl != null) + dst.setQTable(i, tbl); + } + } +} + Index: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFLZWDecoder.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFLZWDecoder.java diff -N src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFLZWDecoder.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/codec/myTiff/TIFFLZWDecoder.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,241 @@ +/* + + Copyright 1999-2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +package org.apache.batik.ext.awt.image.codec.myTiff; + +/** + * A class for performing LZW decoding. + * + * + */ +public class TIFFLZWDecoder { + + byte stringTable[][]; + byte data[] = null, uncompData[]; + int tableIndex, bitsToGet = 9; + int bytePointer, bitPointer; + int dstIndex; + int w, h; + int predictor, samplesPerPixel; + int nextData = 0; + int nextBits = 0; + + int andTable[] = { + 511, + 1023, + 2047, + 4095 + }; + + public TIFFLZWDecoder(int w, int predictor, int samplesPerPixel) { + this.w = w; + this.predictor = predictor; + this.samplesPerPixel = samplesPerPixel; + } + + /** + * Method to decode LZW compressed data. + * + * @param data The compressed data. + * @param uncompData Array to return the uncompressed data in. + * @param h The number of rows the compressed data contains. + */ + public byte[] decode(byte data[], byte uncompData[], int h) { + + if(data[0] == (byte)0x00 && data[1] == (byte)0x01) { + throw new UnsupportedOperationException("TIFFLZWDecoder0"); + } + + initializeStringTable(); + + this.data = data; + this.h = h; + this.uncompData = uncompData; + + // Initialize pointers + bytePointer = 0; + bitPointer = 0; + dstIndex = 0; + + + nextData = 0; + nextBits = 0; + + int code, oldCode = 0; + byte string[]; + + while ( ((code = getNextCode()) != 257) && + dstIndex != uncompData.length) { + + if (code == 256) { + + initializeStringTable(); + code = getNextCode(); + + if (code == 257) { + break; + } + + writeString(stringTable[code]); + oldCode = code; + + } else { + + if (code < tableIndex) { + + string = stringTable[code]; + + writeString(string); + addStringToTable(stringTable[oldCode], string[0]); + oldCode = code; + + } else { + + string = stringTable[oldCode]; + string = composeString(string, string[0]); + writeString(string); + addStringToTable(string); + oldCode = code; + } + + } + + } + + // Horizontal Differencing Predictor + if (predictor == 2) { + + int count; + for (int j = 0; j < h; j++) { + + count = samplesPerPixel * (j * w + 1); + + for (int i = samplesPerPixel; i < w * samplesPerPixel; i++) { + + uncompData[count] += uncompData[count - samplesPerPixel]; + count++; + } + } + } + + return uncompData; + } + + + /** + * Initialize the string table. + */ + public void initializeStringTable() { + + stringTable = new byte[4096][]; + + for (int i=0; i<256; i++) { + stringTable[i] = new byte[1]; + stringTable[i][0] = (byte)i; + } + + tableIndex = 258; + bitsToGet = 9; + } + + /** + * Write out the string just uncompressed. + */ + public void writeString(byte string[]) { + + for (int i=0; inewString to the end of oldString. + */ + public byte[] composeString(byte oldString[], byte newString) { + int length = oldString.length; + byte string[] = new byte[length + 1]; + System.arraycopy(oldString, 0, string, 0, length); + string[length] = newString; + + return string; + } + + // Returns the next 9, 10, 11 or 12 bits + public int getNextCode() { + // Attempt to get the next code. The exception is caught to make + // this robust to cases wherein the EndOfInformation code has been + // omitted from a strip. Examples of such cases have been observed + // in practice. + try { + nextData = (nextData << 8) | (data[bytePointer++] & 0xff); + nextBits += 8; + + if (nextBits < bitsToGet) { + nextData = (nextData << 8) | (data[bytePointer++] & 0xff); + nextBits += 8; + } + + int code = + (nextData >> (nextBits - bitsToGet)) & andTable[bitsToGet-9]; + nextBits -= bitsToGet; + + return code; + } catch(ArrayIndexOutOfBoundsException e) { + // Strip not terminated as expected: return EndOfInformation code. + return 257; + } + } +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/FileCacheSeekableStream.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/FileCacheSeekableStream.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/FileCacheSeekableStream.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/FileCacheSeekableStream.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,260 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; + +/** + * A subclass of SeekableStream that may be used to wrap + * a regular InputStream. Seeking backwards is supported + * by means of a file cache. In circumstances that do not allow the + * creation of a temporary file (for example, due to security + * consideration or the absence of local disk), the + * MemoryCacheSeekableStream class may be used instead. + * + *

    The mark() and reset() methods are + * supported. + * + *

    This class is not a committed part of the JAI API. It may + * be removed or changed in future releases of JAI. + */ +public final class FileCacheSeekableStream extends SeekableStream { + + /** The source stream. */ + private InputStream stream; + + /** The cache File. */ + private File cacheFile; + + /** The cache as a RandomAcessFile. */ + private RandomAccessFile cache; + + /** The length of the read buffer. */ + private int bufLen = 1024; + + /** The read buffer. */ + private byte[] buf = new byte[bufLen]; + + /** Number of bytes in the cache. */ + private long length = 0; + + /** Next byte to be read. */ + private long pointer = 0; + + /** True if we've encountered the end of the source stream. */ + private boolean foundEOF = false; + + /** + * Constructs a MemoryCacheSeekableStream that takes + * its source data from a regular InputStream. + * Seeking backwards is supported by means of an file cache. + * + *

    An IOException will be thrown if the + * attempt to create the cache file fails for any reason. + */ + public FileCacheSeekableStream(InputStream stream) + throws IOException { + this.stream = stream; + this.cacheFile = File.createTempFile("jai-FCSS-", ".tmp"); + cacheFile.deleteOnExit(); + this.cache = new RandomAccessFile(cacheFile, "rw"); + } + + /** + * Ensures that at least pos bytes are cached, + * or the end of the source is reached. The return value + * is equal to the smaller of pos and the + * length of the source file. + */ + private long readUntil(long pos) throws IOException { + // We've already got enough data cached + if (pos < length) { + return pos; + } + // pos >= length but length isn't getting any bigger, so return it + if (foundEOF) { + return length; + } + + long len = pos - length; + cache.seek(length); + while (len > 0) { + // Copy a buffer's worth of data from the source to the cache + // bufLen will always fit into an int so this is safe + int nbytes = stream.read(buf, 0, (int)Math.min(len, bufLen)); + if (nbytes == -1) { + foundEOF = true; + return length; + } + + cache.setLength(cache.length() + nbytes); + cache.write(buf, 0, nbytes); + len -= nbytes; + length += nbytes; + } + + return pos; + } + + /** + * Returns true since all + * FileCacheSeekableStream instances support seeking + * backwards. + */ + public boolean canSeekBackwards() { + return true; + } + + /** + * Returns the current offset in this file. + * + * @return the offset from the beginning of the file, in bytes, + * at which the next read occurs. + */ + public long getFilePointer() { + return pointer; + } + + /** + * Sets the file-pointer offset, measured from the beginning of this + * file, at which the next read occurs. + * + * @param pos the offset position, measured in bytes from the + * beginning of the file, at which to set the file + * pointer. + * @exception IOException if pos is less than + * 0 or if an I/O error occurs. + */ + public void seek(long pos) throws IOException { + if (pos < 0) { + throw new IOException(PropertyUtil.getString("FileCacheSeekableStream0")); + } + pointer = pos; + } + + /** + * Reads the next byte of data from the input stream. The value byte is + * returned as an int in the range 0 to + * 255. If no byte is available because the end of the stream + * has been reached, the value -1 is returned. This method + * blocks until input data is available, the end of the stream is detected, + * or an exception is thrown. + * + * @return the next byte of data, or -1 if the end of the + * stream is reached. + * @exception IOException if an I/O error occurs. + */ + public int read() throws IOException { + long next = pointer + 1; + long pos = readUntil(next); + if (pos >= next) { + cache.seek(pointer++); + return cache.read(); + } else { + return -1; + } + } + + /** + * Reads up to len bytes of data from the input stream into + * an array of bytes. An attempt is made to read as many as + * len bytes, but a smaller number may be read, possibly + * zero. The number of bytes actually read is returned as an integer. + * + *

    This method blocks until input data is available, end of file is + * detected, or an exception is thrown. + * + *

    If b is null, a + * NullPointerException is thrown. + * + *

    If off is negative, or len is negative, or + * off+len is greater than the length of the array + * b, then an IndexOutOfBoundsException is + * thrown. + * + *

    If len is zero, then no bytes are read and + * 0 is returned; otherwise, there is an attempt to read at + * least one byte. If no byte is available because the stream is at end of + * file, the value -1 is returned; otherwise, at least one + * byte is read and stored into b. + * + *

    The first byte read is stored into element b[off], the + * next one into b[off+1], and so on. The number of bytes read + * is, at most, equal to len. Let k be the number of + * bytes actually read; these bytes will be stored in elements + * b[off] through b[off+k-1], + * leaving elements b[off+k] through + * b[off+len-1] unaffected. + * + *

    In every case, elements b[0] through + * b[off] and elements b[off+len] through + * b[b.length-1] are unaffected. + * + *

    If the first byte cannot be read for any reason other than end of + * file, then an IOException is thrown. In particular, an + * IOException is thrown if the input stream has been closed. + * + * @param b the buffer into which the data is read. + * @param off the start offset in array b + * at which the data is written. + * @param len the maximum number of bytes to read. + * @return the total number of bytes read into the buffer, or + * -1 if there is no more data because the end of + * the stream has been reached. + * @exception IOException if an I/O error occurs. + */ + public int read(byte[] b, int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } + if ((off < 0) || (len < 0) || (off + len > b.length)) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return 0; + } + + long pos = readUntil(pointer + len); + + // len will always fit into an int so this is safe + len = (int)Math.min(len, pos - pointer); + if (len > 0) { + cache.seek(pointer); + cache.readFully(b, off, len); + pointer += len; + return len; + } else { + return -1; + } + } + + /** + * Closes this stream and releases any system resources + * associated with the stream. + * + * @throws IOException if an I/O error occurs. + */ + public void close() throws IOException { + super.close(); + cache.close(); + cacheFile.delete(); + } +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/ForwardSeekableStream.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/ForwardSeekableStream.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/ForwardSeekableStream.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/ForwardSeekableStream.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,123 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.io.IOException; +import java.io.InputStream; + +/** + * A subclass of SeekableStream that may be used + * to wrap a regular InputStream efficiently. + * Seeking backwards is not supported. + * + */ +public class ForwardSeekableStream extends SeekableStream { + + /** The source InputStream. */ + private InputStream src; + + /** The current position. */ + long pointer = 0L; + + /** The marked position. */ + long markPos = -1L; + + /** + * Constructs a InputStreamForwardSeekableStream from a + * regular InputStream. + */ + public ForwardSeekableStream(InputStream src) { + this.src = src; + } + + /** Forwards the request to the real InputStream. */ + public final int read() throws IOException { + int result = src.read(); + if (result != -1) { + ++pointer; + } + return result; + } + + /** Forwards the request to the real InputStream. */ + public final int read(byte[] b, int off, int len) throws IOException { + int result = src.read(b, off, len); + if (result != -1) { + pointer += result; + } + return result; + } + + /** Forwards the request to the real InputStream. */ + public final long skip(long n) throws IOException { + long skipped = src.skip(n); + pointer += skipped; + return skipped; + } + + /** Forwards the request to the real InputStream. */ + public final int available() throws IOException { + return src.available(); + } + + /** Forwards the request to the real InputStream. */ + public final void close() throws IOException { + src.close(); + } + + /** Forwards the request to the real InputStream. */ + public synchronized final void mark(int readLimit) { + markPos = pointer; + src.mark(readLimit); + } + + /** Forwards the request to the real InputStream. */ + public synchronized final void reset() throws IOException { + if (markPos != -1) { + pointer = markPos; + } + src.reset(); + } + + /** Forwards the request to the real InputStream. */ + public boolean markSupported() { + return src.markSupported(); + } + + /** Returns false since seking backwards is not supported. */ + public final boolean canSeekBackwards() { + return false; + } + + /** Returns the current position in the stream (bytes read). */ + public final long getFilePointer() { + return pointer; + } + + /** + * Seeks forward to the given position in the stream. + * If pos is smaller than the current position + * as returned by getFilePointer(), nothing + * happens. + */ + public final void seek(long pos) throws IOException { + while (pos - pointer > 0) { + pointer += src.skip(pos - pointer); + } + } +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/ImageDecodeParam.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/ImageDecodeParam.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/ImageDecodeParam.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/ImageDecodeParam.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,30 @@ +/* + + Copyright 2001 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.io.Serializable; + +/** + * An empty (marker) interface to be implemented by all image decoder + * parameter classes. + * + *

    This interface is not a committed part of the JAI API. It may + * be removed or changed in future releases of JAI. + */ +public interface ImageDecodeParam extends Cloneable, Serializable { +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/ImageDecoder.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/ImageDecoder.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/ImageDecoder.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/ImageDecoder.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,91 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.io.IOException; + +/** + * An interface describing objects that transform an InputStream into a + * BufferedImage or Raster. + * + */ +public interface ImageDecoder { + + /** + * Returns the current parameters as an instance of the + * ImageDecodeParam interface. Concrete implementations of this + * interface will return corresponding concrete implementations of + * the ImageDecodeParam interface. For example, a JPEGImageDecoder + * will return an instance of JPEGDecodeParam. + */ + ImageDecodeParam getParam(); + + /** + * Sets the current parameters to an instance of the + * ImageDecodeParam interface. Concrete implementations + * of ImageDecoder may throw a RuntimeException if the + * param argument is not an instance of the appropriate + * subclass or subinterface. For example, a JPEGImageDecoder + * will expect param to be an instance of JPEGDecodeParam. + */ + void setParam(ImageDecodeParam param); + + /** Returns the SeekableStream associated with this ImageDecoder. */ + SeekableStream getInputStream(); + + /** Returns the number of pages present in the current stream. */ + int getNumPages() throws IOException; + + /** + * Returns a Raster that contains the decoded contents of the + * SeekableStream associated with this ImageDecoder. Only + * the first page of a multi-page image is decoded. + */ + Raster decodeAsRaster() throws IOException; + + /** + * Returns a Raster that contains the decoded contents of the + * SeekableStream associated with this ImageDecoder. + * The given page of a multi-page image is decoded. If + * the page does not exist, an IOException will be thrown. + * Page numbering begins at zero. + * + * @param page The page to be decoded. + */ + Raster decodeAsRaster(int page) throws IOException; + + /** + * Returns a RenderedImage that contains the decoded contents of the + * SeekableStream associated with this ImageDecoder. Only + * the first page of a multi-page image is decoded. + */ + RenderedImage decodeAsRenderedImage() throws IOException; + + /** + * Returns a RenderedImage that contains the decoded contents of the + * SeekableStream associated with this ImageDecoder. + * The given page of a multi-page image is decoded. If + * the page does not exist, an IOException will be thrown. + * Page numbering begins at zero. + * + * @param page The page to be decoded. + */ + RenderedImage decodeAsRenderedImage(int page) throws IOException; +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/ImageDecoderImpl.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/ImageDecoderImpl.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/ImageDecoderImpl.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/ImageDecoderImpl.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,160 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.io.IOException; +import java.io.InputStream; + +/** + * A partial implementation of the ImageDecoder interface + * useful for subclassing. + * + */ +public abstract class ImageDecoderImpl implements ImageDecoder { + + /** + * The SeekableStream associcted with this + * ImageEncoder. + */ + protected SeekableStream input; + + /** + * The ImageDecodeParam object associated with this + * ImageEncoder. + */ + protected ImageDecodeParam param; + + /** + * Constructs an ImageDecoderImpl with a given + * SeekableStream and ImageDecodeParam + * instance. + */ + public ImageDecoderImpl(SeekableStream input, + ImageDecodeParam param) { + this.input = input; + this.param = param; + } + + /** + * Constructs an ImageDecoderImpl with a given + * InputStream and ImageDecodeParam + * instance. The input parameter will be used to + * construct a ForwardSeekableStream; if the ability + * to seek backwards is required, the caller should construct + * an instance of SeekableStream and + * make use of the other contructor. + */ + public ImageDecoderImpl(InputStream input, + ImageDecodeParam param) { + this.input = new ForwardSeekableStream(input); + this.param = param; + } + + /** + * Returns the current parameters as an instance of the + * ImageDecodeParam interface. Concrete + * implementations of this interface will return corresponding + * concrete implementations of the ImageDecodeParam + * interface. For example, a JPEGImageDecoder will + * return an instance of JPEGDecodeParam. + */ + public ImageDecodeParam getParam() { + return param; + } + + /** + * Sets the current parameters to an instance of the + * ImageDecodeParam interface. Concrete + * implementations of ImageDecoder may throw a + * RuntimeException if the param + * argument is not an instance of the appropriate subclass or + * subinterface. For example, a JPEGImageDecoder + * will expect param to be an instance of + * JPEGDecodeParam. + */ + public void setParam(ImageDecodeParam param) { + this.param = param; + } + + /** + * Returns the SeekableStream associated with + * this ImageDecoder. + */ + public SeekableStream getInputStream() { + return input; + } + + /** + * Returns the number of pages present in the current stream. + * By default, the return value is 1. Subclasses that deal with + * multi-page formats should override this method. + */ + public int getNumPages() throws IOException { + return 1; + } + + /** + * Returns a Raster that contains the decoded + * contents of the SeekableStream associated + * with this ImageDecoder. Only + * the first page of a multi-page image is decoded. + */ + public Raster decodeAsRaster() throws IOException { + return decodeAsRaster(0); + } + + /** + * Returns a Raster that contains the decoded + * contents of the SeekableStream associated + * with this ImageDecoder. + * The given page of a multi-page image is decoded. If + * the page does not exist, an IOException will be thrown. + * Page numbering begins at zero. + * + * @param page The page to be decoded. + */ + public Raster decodeAsRaster(int page) throws IOException { + RenderedImage im = decodeAsRenderedImage(page); + return im.getData(); + } + + /** + * Returns a RenderedImage that contains the decoded + * contents of the SeekableStream associated + * with this ImageDecoder. Only + * the first page of a multi-page image is decoded. + */ + public RenderedImage decodeAsRenderedImage() throws IOException { + return decodeAsRenderedImage(0); + } + + /** + * Returns a RenderedImage that contains the decoded + * contents of the SeekableStream associated + * with this ImageDecoder. + * The given page of a multi-page image is decoded. If + * the page does not exist, an IOException will be thrown. + * Page numbering begins at zero. + * + * @param page The page to be decoded. + */ + public abstract RenderedImage decodeAsRenderedImage(int page) + throws IOException; +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/ImageEncodeParam.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/ImageEncodeParam.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/ImageEncodeParam.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/ImageEncodeParam.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,31 @@ +/* + + Copyright 2001 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.io.Serializable; + +/** + * An empty (marker) interface to be implemented by all image encoder + * parameter classes. + * + *

    This interface is not a committed part of the JAI API. It may + * be removed or changed in future releases of JAI. + */ +public interface ImageEncodeParam extends + ImageDecodeParam, Cloneable, Serializable { +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/ImageEncoder.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/ImageEncoder.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/ImageEncoder.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/ImageEncoder.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,68 @@ +/* + + Copyright 2001 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.awt.image.ColorModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.io.IOException; +import java.io.OutputStream; + +/** + * An interface describing objects that transform a BufferedImage or + * Raster into an OutputStream. + * + *

    This interface is not a committed part of the JAI API. It may + * be removed or changed in future releases of JAI. + */ +public interface ImageEncoder { + + /** + * Returns the current parameters as an instance of the + * ImageEncodeParam interface. Concrete implementations of this + * interface will return corresponding concrete implementations of + * the ImageEncodeParam interface. For example, a JPEGImageEncoder + * will return an instance of JPEGEncodeParam. + */ + public ImageEncodeParam getParam(); + + /** + * Sets the current parameters to an instance of the + * ImageEncodeParam interface. Concrete implementations + * of ImageEncoder may throw a RuntimeException if the + * params argument is not an instance of the appropriate + * subclass or subinterface. For example, a JPEGImageEncoder + * will expect param to be an instance of JPEGEncodeParam. + */ + public void setParam(ImageEncodeParam param); + + /** Returns the OutputStream associated with this ImageEncoder. */ + public OutputStream getOutputStream(); + + /** + * Encodes a Raster with a given ColorModel and writes the output + * to the OutputStream associated with this ImageEncoder. + */ + public void encode(Raster ras, ColorModel cm) throws IOException; + + /** + * Encodes a RenderedImage and writes the output to the + * OutputStream associated with this ImageEncoder. + */ + public void encode(RenderedImage im) throws IOException; +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/ImageEncoderImpl.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/ImageEncoderImpl.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/ImageEncoderImpl.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/ImageEncoderImpl.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,93 @@ +/* + + Copyright 2001 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.awt.image.ColorModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.io.IOException; +import java.io.OutputStream; + +/** + * A partial implementation of the ImageEncoder interface useful for + * subclassing. + * + *

    This class is not a committed part of the JAI API. It may + * be removed or changed in future releases of JAI. + */ +public abstract class ImageEncoderImpl implements ImageEncoder { + + /** The OutputStream associcted with this ImageEncoder. */ + protected OutputStream output; + + /** The ImageEncodeParam object associcted with this ImageEncoder. */ + protected ImageEncodeParam param; + + /** + * Constructs an ImageEncoderImpl with a given OutputStream + * and ImageEncoderParam instance. + */ + public ImageEncoderImpl(OutputStream output, + ImageEncodeParam param) { + this.output = output; + this.param = param; + } + + /** + * Returns the current parameters as an instance of the + * ImageEncodeParam interface. Concrete implementations of this + * interface will return corresponding concrete implementations of + * the ImageEncodeParam interface. For example, a JPEGImageEncoder + * will return an instance of JPEGEncodeParam. + */ + public ImageEncodeParam getParam() { + return param; + } + + /** + * Sets the current parameters to an instance of the + * ImageEncodeParam interface. Concrete implementations + * of ImageEncoder may throw a RuntimeException if the + * params argument is not an instance of the appropriate + * subclass or subinterface. For example, a JPEGImageEncoder + * will expect param to be an instance of JPEGEncodeParam. + */ + public void setParam(ImageEncodeParam param) { + this.param = param; + } + + /** Returns the OutputStream associated with this ImageEncoder. */ + public OutputStream getOutputStream() { + return output; + } + + /** + * Encodes a Raster with a given ColorModel and writes the output + * to the OutputStream associated with this ImageEncoder. + */ + public void encode(Raster ras, ColorModel cm) throws IOException { + RenderedImage im = new SingleTileRenderedImage(ras, cm); + encode(im); + } + + /** + * Encodes a RenderedImage and writes the output to the + * OutputStream associated with this ImageEncoder. + */ + public abstract void encode(RenderedImage im) throws IOException; +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/MemoryCacheSeekableStream.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/MemoryCacheSeekableStream.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/MemoryCacheSeekableStream.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/MemoryCacheSeekableStream.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,252 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; + +/** + * A subclass of SeekableStream that may be used to wrap + * a regular InputStream. Seeking backwards is supported + * by means of an in-memory cache. For greater efficiency, + * FileCacheSeekableStream should be used in + * circumstances that allow the creation of a temporary file. + * + *

    The mark() and reset() methods are + * supported. + * + *

    This class is not a committed part of the JAI API. It may + * be removed or changed in future releases of JAI. + */ +public final class MemoryCacheSeekableStream extends SeekableStream { + + /** The source input stream. */ + private InputStream src; + + /** Position of first unread byte. */ + private long pointer = 0; + + /** Log_2 of the sector size. */ + private static final int SECTOR_SHIFT = 9; + + /** The sector size. */ + private static final int SECTOR_SIZE = 1 << SECTOR_SHIFT; + + /** A mask to determine the offset within a sector. */ + private static final int SECTOR_MASK = SECTOR_SIZE - 1; + + /** A Vector of source sectors. */ + private Vector data = new Vector(); + + /** Number of sectors stored. */ + int sectors = 0; + + /** Number of bytes read. */ + int length = 0; + + /** True if we've previously reached the end of the source stream */ + boolean foundEOS = false; + + /** + * Constructs a MemoryCacheSeekableStream that takes + * its source data from a regular InputStream. + * Seeking backwards is supported by means of an in-memory cache. + */ + public MemoryCacheSeekableStream(InputStream src) { + this.src = src; + } + + /** + * Ensures that at least pos bytes are cached, + * or the end of the source is reached. The return value + * is equal to the smaller of pos and the + * length of the source stream. + */ + private long readUntil(long pos) throws IOException { + // We've already got enough data cached + if (pos < length) { + return pos; + } + // pos >= length but length isn't getting any bigger, so return it + if (foundEOS) { + return length; + } + + int sector = (int)(pos >> SECTOR_SHIFT); + + // First unread sector + int startSector = length >> SECTOR_SHIFT; + + // Read sectors until the desired sector + for (int i = startSector; i <= sector; i++) { + byte[] buf = new byte[SECTOR_SIZE]; + data.addElement(buf); + + // Read up to SECTOR_SIZE bytes + int len = SECTOR_SIZE; + int off = 0; + while (len > 0) { + int nbytes = src.read(buf, off, len); + // Found the end-of-stream + if (nbytes == -1) { + foundEOS = true; + return length; + } + off += nbytes; + len -= nbytes; + + // Record new data length + length += nbytes; + } + } + + return length; + } + + /** + * Returns true since all + * MemoryCacheSeekableStream instances support seeking + * backwards. + */ + public boolean canSeekBackwards() { + return true; + } + + /** + * Returns the current offset in this file. + * + * @return the offset from the beginning of the file, in bytes, + * at which the next read occurs. + */ + public long getFilePointer() { + return pointer; + } + + /** + * Sets the file-pointer offset, measured from the beginning of this + * file, at which the next read occurs. + * + * @param pos the offset position, measured in bytes from the + * beginning of the file, at which to set the file + * pointer. + * @exception IOException if pos is less than + * 0 or if an I/O error occurs. + */ + public void seek(long pos) throws IOException { + if (pos < 0) { + throw new IOException(PropertyUtil.getString("MemoryCacheSeekableStream0")); + } + pointer = pos; + } + + /** + * Reads the next byte of data from the input stream. The value byte is + * returned as an int in the range 0 to + * 255. If no byte is available because the end of the stream + * has been reached, the value -1 is returned. This method + * blocks until input data is available, the end of the stream is detected, + * or an exception is thrown. + * + * @return the next byte of data, or -1 if the end of the + * stream is reached. + */ + public int read() throws IOException { + long next = pointer + 1; + long pos = readUntil(next); + if (pos >= next) { + byte[] buf = + (byte[])data.elementAt((int)(pointer >> SECTOR_SHIFT)); + return buf[(int)(pointer++ & SECTOR_MASK)] & 0xff; + } else { + return -1; + } + } + + /** + * Reads up to len bytes of data from the input stream into + * an array of bytes. An attempt is made to read as many as + * len bytes, but a smaller number may be read, possibly + * zero. The number of bytes actually read is returned as an integer. + * + *

    This method blocks until input data is available, end of file is + * detected, or an exception is thrown. + * + *

    If b is null, a + * NullPointerException is thrown. + * + *

    If off is negative, or len is negative, or + * off+len is greater than the length of the array + * b, then an IndexOutOfBoundsException is + * thrown. + * + *

    If len is zero, then no bytes are read and + * 0 is returned; otherwise, there is an attempt to read at + * least one byte. If no byte is available because the stream is at end of + * file, the value -1 is returned; otherwise, at least one + * byte is read and stored into b. + * + *

    The first byte read is stored into element b[off], the + * next one into b[off+1], and so on. The number of bytes read + * is, at most, equal to len. Let k be the number of + * bytes actually read; these bytes will be stored in elements + * b[off] through b[off+k-1], + * leaving elements b[off+k] through + * b[off+len-1] unaffected. + * + *

    In every case, elements b[0] through + * b[off] and elements b[off+len] through + * b[b.length-1] are unaffected. + * + *

    If the first byte cannot be read for any reason other than end of + * file, then an IOException is thrown. In particular, an + * IOException is thrown if the input stream has been closed. + * + * @param b the buffer into which the data is read. + * @param off the start offset in array b + * at which the data is written. + * @param len the maximum number of bytes to read. + * @return the total number of bytes read into the buffer, or + * -1 if there is no more data because the end of + * the stream has been reached. + */ + public int read(byte[] b, int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } + if ((off < 0) || (len < 0) || (off + len > b.length)) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return 0; + } + + long pos = readUntil(pointer + len); + // End-of-stream + if (pos <= pointer) { + return -1; + } + + byte[] buf = (byte[])data.elementAt((int)(pointer >> SECTOR_SHIFT)); + int nbytes = Math.min(len, SECTOR_SIZE - (int)(pointer & SECTOR_MASK)); + System.arraycopy(buf, (int)(pointer & SECTOR_MASK), + b, off, nbytes); + pointer += nbytes; + return nbytes; + } +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/PNGDecodeParam.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/PNGDecodeParam.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/PNGDecodeParam.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/PNGDecodeParam.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,348 @@ +/* + + Copyright 2001 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +/** + * An instance of ImageDecodeParam for decoding images in + * the PNG format. + * + * PNGDecodeParam allows several aspects of the decoding + * process for PNG images to be controlled. By default, decoding produces + * output images with the following properties: + * + *

    Images with a bit depth of 8 or less use a + * DataBufferByte to hold the pixel data. 16-bit images + * use a DataBufferUShort. + * + *

    Palette color images and non-transparent grayscale images with + * bit depths of 1, 2, or 4 will have a + * MultiPixelPackedSampleModel and an + * IndexColorModel. For palette color images, the + * ColorModel palette contains the red, green, blue, and + * optionally alpha palette information. For grayscale images, the + * palette is used to expand the pixel data to cover the range 0-255. + * The pixels are stored packed 8, 4, or 2 to the byte. + * + *

    All other images are stored using a + * PixelInterleavedSampleModel with each sample of a pixel + * occupying its own byte or short within + * the DataBuffer. A ComponentColorModel is + * used which simply extracts the red, green, blue, gray, and/or alpha + * information from separate DataBuffer entries. + * + *

    Five aspects of this process may be altered by means of methods + * in this class. + * + *

    setSuppressAlpha() prevents an alpha channel + * from appearing in the output. + * + *

    setExpandPalette() turns palette-color images into + * 3-or 4-channel full-color images. + * + *

    setOutput8BitGray() causes 1, 2, or 4 bit + * grayscale images to be output in 8-bit form, using a + * ComponentSampleModel and + * ComponentColorModel. + * + *

    setDecodingExponent() causes the output image to be + * gamma-corrected using a supplied output gamma value. + * + *

    setExpandGrayAlpha() causes 2-channel gray/alpha + * (GA) images to be output as full-color (GGGA) images, which may + * simplify further processing and display. + * + *

    This class is not a committed part of the JAI API. It may + * be removed or changed in future releases of JAI. + */ +public class PNGDecodeParam implements ImageDecodeParam { + + /** + * Constructs a default instance of PNGDecodeParam. + */ + public PNGDecodeParam() {} + + private boolean suppressAlpha = false; + + /** + * Returns true if alpha (transparency) will + * be prevented from appearing in the output. + */ + public boolean getSuppressAlpha() { + return suppressAlpha; + } + + /** + * If set, no alpha (transparency) channel will appear in the + * output image. + * + *

    The default is to allow transparency to appear in the + * output image. + */ + public void setSuppressAlpha(boolean suppressAlpha) { + this.suppressAlpha = suppressAlpha; + } + + private boolean expandPalette = false; + + /** + * Returns true if palette-color images will be expanded to + * produce full-color output. + */ + public boolean getExpandPalette() { + return expandPalette; + } + + /** + * If set, palette color images (PNG color type 3) will + * be decoded into full-color (RGB) output images. The output + * image may have 3 or 4 channels, depending on the presence of + * transparency information. + * + *

    The default is to output palette images using a single + * channel. The palette information is used to construct the + * output image's ColorModel. + */ + public void setExpandPalette(boolean expandPalette) { + this.expandPalette = expandPalette; + } + + private boolean output8BitGray = false; + + /** + * Returns the current value of the 8-bit gray output parameter. + */ + public boolean getOutput8BitGray() { + return output8BitGray; + } + + /** + * If set, grayscale images with a bit depth less than 8 + * (1, 2, or 4) will be output in 8 bit form. The output values + * will occupy the full 8-bit range. For example, gray values + * 0, 1, 2, and 3 of a 2-bit image will be output as + * 0, 85, 170, and 255. + * + *

    The decoding of non-grayscale images and grayscale images + * with a bit depth of 8 or 16 are unaffected by this setting. + * + *

    The default is not to perform expansion. Grayscale images + * with a depth of 1, 2, or 4 bits will be represented using + * a MultiPixelPackedSampleModel and an + * IndexColorModel. + */ + public void setOutput8BitGray(boolean output8BitGray) { + this.output8BitGray = output8BitGray; + } + + private boolean performGammaCorrection = true; + + /** + * Returns true if gamma correction is to be performed + * on the image data. The default is true. + * + *

    If gamma correction is to be performed, the + * getUserExponent() and + * getDisplayExponent() methods are used in addition to + * the gamma value stored within the file (or the default value of + * 1/2.2 used if no value is found) to produce a single exponent + * using the formula: + *

    +     * decoding_exponent = user_exponent/(gamma_from_file * display_exponent)
    +     * 
    + */ + public boolean getPerformGammaCorrection() { + return performGammaCorrection; + } + + /** + * Turns gamma corection of the image data on or off. + */ + public void setPerformGammaCorrection(boolean performGammaCorrection) { + this.performGammaCorrection = performGammaCorrection; + } + + private float userExponent = 1.0F; + + /** + * Returns the current value of the user exponent parameter. + * By default, the user exponent is equal to 1.0F. + */ + public float getUserExponent() { + return userExponent; + } + + /** + * Sets the user exponent to a given value. The exponent + * must be positive. If not, an + * IllegalArgumentException will be thrown. + * + *

    The output image pixels will be placed through a transformation + * of the form: + * + *

    +     * sample = integer_sample / (2^bitdepth - 1.0)
    +     * decoding_exponent = user_exponent/(gamma_from_file * display_exponent)
    +     * output = sample ^ decoding_exponent
    +     * 
    + * + * where gamma_from_file is the gamma of the file + * data, as determined by the gAMA, sRGB, + * and/or iCCP chunks, and display_exponent + * is the exponent of the intrinsic transfer curve of the display, + * generally 2.2. + * + *

    Input files which do not specify any gamma are assumed to + * have a gamma of 1/2.2; such images may be displayed + * on a CRT with an exponent of 2.2 using the default user + * exponent of 1.0. + * + *

    The user exponent may be used in order to change the + * effective gamma of a file. If a file has a stored gamma of + * X, but the decoder believes that the true file gamma is Y, + * setting a user exponent of Y/X will produce the same result + * as changing the file gamma. + * + *

    This parameter affects the decoding of all image types. + * + * @throws IllegalArgumentException if userExponent is + * negative. + */ + public void setUserExponent(float userExponent) { + if (userExponent <= 0.0F) { + throw new IllegalArgumentException(PropertyUtil.getString("PNGDecodeParam0")); + } + this.userExponent = userExponent; + } + + private float displayExponent = 2.2F; + + /** + * Returns the current value of the display exponent parameter. + * By default, the display exponent is equal to 2.2F. + */ + public float getDisplayExponent() { + return displayExponent; + } + + /** + * Sets the display exponent to a given value. The exponent + * must be positive. If not, an + * IllegalArgumentException will be thrown. + * + *

    The output image pixels will be placed through a transformation + * of the form: + * + *

    +     * sample = integer_sample / (2^bitdepth - 1.0)
    +     * decoding_exponent = user_exponent/(gamma_from_file * display_exponent)
    +     * output = sample ^ decoding_exponent
    +     * 
    + * + * where gamma_from_file is the gamma of the file + * data, as determined by the gAMA, sRGB, + * and/or iCCP chunks, and user_exponent + * is an additional user-supplied parameter. + * + *

    Input files which do not specify any gamma are assumed to + * have a gamma of 1/2.2; such images should be + * decoding using the default display exponent of 2.2. + * + *

    If an image is to be processed further before being displayed, + * it may be preferable to set the display exponent to 1.0 in order + * to produce a linear output image. + * + *

    This parameter affects the decoding of all image types. + * + * @throws IllegalArgumentException if userExponent is + * negative. + */ + public void setDisplayExponent(float displayExponent) { + if (displayExponent <= 0.0F) { + throw new IllegalArgumentException(PropertyUtil.getString("PNGDecodeParam1")); + } + this.displayExponent = displayExponent; + } + + private boolean expandGrayAlpha = false; + + /** + * Returns the current setting of the gray/alpha expansion. + */ + public boolean getExpandGrayAlpha() { + return expandGrayAlpha; + } + + /** + * If set, images containing one channel of gray and one channel of + * alpha (GA) will be output in a 4-channel format (GGGA). This + * produces output that may be simpler to process and display. + * + *

    This setting affects both images of color type 4 (explicit + * alpha) and images of color type 0 (grayscale) that contain + * transparency information. + * + *

    By default, no expansion is performed. + */ + public void setExpandGrayAlpha(boolean expandGrayAlpha) { + this.expandGrayAlpha = expandGrayAlpha; + } + + private boolean generateEncodeParam = false; + + private PNGEncodeParam encodeParam = null; + + /** + * Returns true if an instance of + * PNGEncodeParam will be available after an image + * has been decoded via the getEncodeParam method. + */ + public boolean getGenerateEncodeParam() { + return generateEncodeParam; + } + + /** + * If set, an instance of PNGEncodeParam will be + * available after an image has been decoded via the + * getEncodeParam method that encapsulates information + * about the contents of the PNG file. If not set, this information + * will not be recorded and getEncodeParam() will + * return null. + */ + public void setGenerateEncodeParam(boolean generateEncodeParam) { + this.generateEncodeParam = generateEncodeParam; + } + + /** + * If getGenerateEncodeParam() is true, + * this method may be called after decoding has completed, and + * will return an instance of PNGEncodeParam containing + * information about the contents of the PNG file just decoded. + */ + public PNGEncodeParam getEncodeParam() { + return encodeParam; + } + + /** + * Sets the current encoder param instance. This method is + * intended to be called by the PNG decoder and will overwrite the + * current instance returned by getEncodeParam. + */ + public void setEncodeParam(PNGEncodeParam encodeParam) { + this.encodeParam = encodeParam; + } +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/PNGEncodeParam.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/PNGEncodeParam.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/PNGEncodeParam.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/PNGEncodeParam.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,1498 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.util.Date; +import java.util.Vector; + +/** + * An instance of ImageEncodeParam for encoding images in + * the PNG format. + * + *

    This class is not a committed part of the JAI API. It may + * be removed or changed in future releases of JAI. + */ +public abstract class PNGEncodeParam implements ImageEncodeParam { + + /** Constant for use with the sRGB chunk. */ + public static final int INTENT_PERCEPTUAL = 0; + + /** Constant for use with the sRGB chunk. */ + public static final int INTENT_RELATIVE = 1; + + /** Constant for use with the sRGB chunk. */ + public static final int INTENT_SATURATION = 2; + + /** Constant for use with the sRGB chunk. */ + public static final int INTENT_ABSOLUTE = 3; + + /** Constant for use in filtering. */ + public static final int PNG_FILTER_NONE = 0; + + /** Constant for use in filtering. */ + public static final int PNG_FILTER_SUB = 1; + + /** Constant for use in filtering. */ + public static final int PNG_FILTER_UP = 2; + + /** Constant for use in filtering. */ + public static final int PNG_FILTER_AVERAGE = 3; + + /** Constant for use in filtering. */ + public static final int PNG_FILTER_PAETH = 4; + + + /** + * Returns an instance of PNGEncodeParam.Palette, + * PNGEncodeParam.Gray, or + * PNGEncodeParam.RGB appropriate for encoding + * the given image. + * + *

    If the image has an IndexColorModel, an + * instance of PNGEncodeParam.Palette is returned. + * Otherwise, if the image has 1 or 2 bands an instance of + * PNGEncodeParam.Gray is returned. In all other + * cases an instance of PNGEncodeParam.RGB is + * returned. + * + *

    Note that this method does not provide any guarantee that + * the given image will be successfully encoded by the PNG + * encoder, as it only performs a very superficial analysis of + * the image structure. + */ + public static PNGEncodeParam getDefaultEncodeParam(RenderedImage im) { + ColorModel colorModel = im.getColorModel(); + if (colorModel instanceof IndexColorModel) { + return new PNGEncodeParam.Palette(); + } + + SampleModel sampleModel = im.getSampleModel(); + int numBands = sampleModel.getNumBands(); + + if (numBands == 1 || numBands == 2) { + return new PNGEncodeParam.Gray(); + } else { + return new PNGEncodeParam.RGB(); + } + } + + public static class Palette extends PNGEncodeParam { + + /** Constructs an instance of PNGEncodeParam.Palette. */ + public Palette() {} + + // bKGD chunk + + private boolean backgroundSet = false; + + /** + * Suppresses the 'bKGD' chunk from being output. + */ + public void unsetBackground() { + backgroundSet = false; + } + + /** + * Returns true if a 'bKGD' chunk will be output. + */ + public boolean isBackgroundSet() { + return backgroundSet; + } + + /** + * Sets the desired bit depth for a palette image. The bit + * depth must be one of 1, 2, 4, or 8, or else an + * IllegalArgumentException will be thrown. + */ + public void setBitDepth(int bitDepth) { + if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && + bitDepth != 8) { + throw new IllegalArgumentException(PropertyUtil.getString("PNGEncodeParam2")); + } + this.bitDepth = bitDepth; + bitDepthSet = true; + } + + // PLTE chunk + + private int[] palette = null; + private boolean paletteSet = false; + + /** + * Sets the RGB palette of the image to be encoded. + * The rgb parameter contains alternating + * R, G, B values for each color index used in the image. + * The number of elements must be a multiple of 3 between + * 3 and 3*256. + * + *

    The 'PLTE' chunk will encode this information. + * + * @param rgb An array of ints. + */ + public void setPalette(int[] rgb) { + if (rgb.length < 1*3 || rgb.length > 256*3) { + throw new + IllegalArgumentException(PropertyUtil.getString("PNGEncodeParam0")); + } + if ((rgb.length % 3) != 0) { + throw new + IllegalArgumentException(PropertyUtil.getString("PNGEncodeParam1")); + } + + palette = (int[])(rgb.clone()); + paletteSet = true; + } + + /** + * Returns the current RGB palette. + * + *

    If the palette has not previously been set, or has been + * unset, an IllegalStateException will be thrown. + * + * @throws IllegalStateException if the palette is not set. + * + * @return An array of ints. + */ + public int[] getPalette() { + if (!paletteSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam3")); + } + return (int[])(palette.clone()); + } + + /** + * Suppresses the 'PLTE' chunk from being output. + */ + public void unsetPalette() { + palette = null; + paletteSet = false; + } + + /** + * Returns true if a 'PLTE' chunk will be output. + */ + public boolean isPaletteSet() { + return paletteSet; + } + + // bKGD chunk + + private int backgroundPaletteIndex; + + /** + * Sets the palette index of the suggested background color. + * + *

    The 'bKGD' chunk will encode this information. + */ + public void setBackgroundPaletteIndex(int index) { + backgroundPaletteIndex = index; + backgroundSet = true; + } + + /** + * Returns the palette index of the suggested background color. + * + *

    If the background palette index has not previously been + * set, or has been unset, an + * IllegalStateException will be thrown. + * + * @throws IllegalStateException if the palette index is not set. + */ + public int getBackgroundPaletteIndex() { + if (!backgroundSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam4")); + } + return backgroundPaletteIndex; + } + + // tRNS chunk + + private int[] transparency; + + /** + * Sets the alpha values associated with each palette entry. + * The alpha parameter should have as many entries + * as there are RGB triples in the palette. + * + *

    The 'tRNS' chunk will encode this information. + */ + public void setPaletteTransparency(byte[] alpha) { + transparency = new int[alpha.length]; + for (int i = 0; i < alpha.length; i++) { + transparency[i] = alpha[i] & 0xff; + } + transparencySet = true; + } + + /** + * Returns the alpha values associated with each palette entry. + * + *

    If the palette transparency has not previously been + * set, or has been unset, an + * IllegalStateException will be thrown. + * + * @throws IllegalStateException if the palette transparency is + * not set. + */ + public byte[] getPaletteTransparency() { + if (!transparencySet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam5")); + } + byte[] alpha = new byte[transparency.length]; + for (int i = 0; i < alpha.length; i++) { + alpha[i] = (byte)transparency[i]; + } + return alpha; + } + } + + public static class Gray extends PNGEncodeParam { + + /** Constructs an instance of PNGEncodeParam.Gray. */ + public Gray() {} + + // bKGD chunk + + private boolean backgroundSet = false; + + /** + * Suppresses the 'bKGD' chunk from being output. + */ + public void unsetBackground() { + backgroundSet = false; + } + + /** + * Returns true if a 'bKGD' chunk will be output. + */ + public boolean isBackgroundSet() { + return backgroundSet; + } + + /** + * Sets the desired bit depth for a grayscale image. The bit + * depth must be one of 1, 2, 4, 8, or 16. + * + *

    When encoding a source image of a greater bit depth, + * pixel values will be clamped to the smaller range after + * shifting by the value given by getBitShift(). + * When encoding a source image of a smaller bit depth, pixel + * values will be shifted and left-filled with zeroes. + */ + public void setBitDepth(int bitDepth) { + if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && + bitDepth != 8 && bitDepth != 16) { + throw new IllegalArgumentException(); + } + this.bitDepth = bitDepth; + bitDepthSet = true; + } + + // bKGD chunk + + private int backgroundPaletteGray; + + /** + * Sets the suggested gray level of the background. + * + *

    The 'bKGD' chunk will encode this information. + */ + public void setBackgroundGray(int gray) { + backgroundPaletteGray = gray; + backgroundSet = true; + } + + /** + * Returns the suggested gray level of the background. + * + *

    If the background gray level has not previously been + * set, or has been unset, an + * IllegalStateException will be thrown. + * + * @throws IllegalStateException if the background gray level + * is not set. + */ + public int getBackgroundGray() { + if (!backgroundSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam6")); + } + return backgroundPaletteGray; + } + + // tRNS chunk + + private int[] transparency; + + /** + * Sets the gray value to be used to denote transparency. + * + *

    Setting this attribute will cause the alpha channel + * of the input image to be ignored. + * + *

    The 'tRNS' chunk will encode this information. + */ + public void setTransparentGray(int transparentGray) { + transparency = new int[1]; + transparency[0] = transparentGray; + transparencySet = true; + } + + /** + * Returns the gray value to be used to denote transparency. + * + *

    If the transparent gray value has not previously been + * set, or has been unset, an + * IllegalStateException will be thrown. + * + * @throws IllegalStateException if the transparent gray value + * is not set. + */ + public int getTransparentGray() { + if (!transparencySet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam7")); + } + int gray = transparency[0]; + return gray; + } + + private int bitShift; + private boolean bitShiftSet = false; + + /** + * Sets the desired bit shift for a grayscale image. + * Pixels in the source image will be shifted right by + * the given amount prior to being clamped to the maximum + * value given by the encoded image's bit depth. + */ + public void setBitShift(int bitShift) { + if (bitShift < 0) { + throw new RuntimeException(); + } + this.bitShift = bitShift; + bitShiftSet = true; + } + + /** + * Returns the desired bit shift for a grayscale image. + * + *

    If the bit shift has not previously been set, or has been + * unset, an IllegalStateException will be thrown. + * + * @throws IllegalStateException if the bit shift is not set. + */ + public int getBitShift() { + if (!bitShiftSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam8")); + } + return bitShift; + } + + /** + * Suppresses the setting of the bit shift of a grayscale image. + * Pixels in the source image will not be shifted prior to encoding. + */ + public void unsetBitShift() { + bitShiftSet = false; + } + + /** + * Returns true if the bit shift has been set. + */ + public boolean isBitShiftSet() { + return bitShiftSet; + } + + /** + * Returns true if the bit depth has been set. + */ + public boolean isBitDepthSet() { + return bitDepthSet; + } + } + + public static class RGB extends PNGEncodeParam { + + /** Constructs an instance of PNGEncodeParam.RGB. */ + public RGB() {} + + // bKGD chunk + + private boolean backgroundSet = false; + + /** + * Suppresses the 'bKGD' chunk from being output. + */ + public void unsetBackground() { + backgroundSet = false; + } + + /** + * Returns true if a 'bKGD' chunk will be output. + */ + public boolean isBackgroundSet() { + return backgroundSet; + } + + /** + * Sets the desired bit depth for an RGB image. The bit + * depth must be 8 or 16. + */ + public void setBitDepth(int bitDepth) { + if (bitDepth != 8 && bitDepth != 16) { + throw new RuntimeException(); + } + this.bitDepth = bitDepth; + bitDepthSet = true; + } + + // bKGD chunk + + private int[] backgroundRGB; + + /** + * Sets the RGB value of the suggested background color. + * The rgb parameter should have 3 entries. + * + *

    The 'bKGD' chunk will encode this information. + */ + public void setBackgroundRGB(int[] rgb) { + if (rgb.length != 3) { + throw new RuntimeException(); + } + backgroundRGB = rgb; + backgroundSet = true; + } + + /** + * Returns the RGB value of the suggested background color. + * + *

    If the background color has not previously been set, or has been + * unset, an IllegalStateException will be thrown. + * + * @throws IllegalStateException if the background color is not set. + */ + public int[] getBackgroundRGB() { + if (!backgroundSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam9")); + } + return backgroundRGB; + } + + // tRNS chunk + + private int[] transparency; + + /** + * Sets the RGB value to be used to denote transparency. + * + *

    Setting this attribute will cause the alpha channel + * of the input image to be ignored. + * + *

    The 'tRNS' chunk will encode this information. + */ + public void setTransparentRGB(int[] transparentRGB) { + transparency = (int[])(transparentRGB.clone()); + transparencySet = true; + } + + /** + * Returns the RGB value to be used to denote transparency. + * + *

    If the transparent color has not previously been set, + * or has been unset, an IllegalStateException + * will be thrown. + * + * @throws IllegalStateException if the transparent color is not set. + */ + public int[] getTransparentRGB() { + if (!transparencySet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam10")); + } + return (int[])(transparency.clone()); + } + } + + protected int bitDepth; + protected boolean bitDepthSet = false; + + /** + * Sets the desired bit depth of an image. + */ + public abstract void setBitDepth(int bitDepth); + + /** + * Returns the desired bit depth for a grayscale image. + * + *

    If the bit depth has not previously been set, or has been + * unset, an IllegalStateException will be thrown. + * + * @throws IllegalStateException if the bit depth is not set. + */ + public int getBitDepth() { + if (!bitDepthSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam11")); + } + return bitDepth; + } + + /** + * Suppresses the setting of the bit depth of a grayscale image. + * The depth of the encoded image will be inferred from the source + * image bit depth, rounded up to the next power of 2 between 1 + * and 16. + */ + public void unsetBitDepth() { + bitDepthSet = false; + } + + private boolean useInterlacing = false; + + /** + * Turns Adam7 interlacing on or off. + */ + public void setInterlacing(boolean useInterlacing) { + this.useInterlacing = useInterlacing; + } + + /** + * Returns true if Adam7 interlacing will be used. + */ + public boolean getInterlacing() { + return useInterlacing; + } + + // bKGD chunk - delegate to subclasses + + // In JAI 1.0, 'backgroundSet' was private. The JDK 1.2 compiler + // was lenient and incorrectly allowed this variable to be + // accessed from the subclasses. The JDK 1.3 compiler correctly + // flags this as a use of a non-static variable in a static + // context. Changing 'backgroundSet' to protected would have + // solved the problem, but would have introduced a visible API + // change. Thus we are forced to adopt the solution of placing a + // separate private variable in each subclass and providing + // separate implementations of 'unsetBackground' and + // 'isBackgroundSet' in each concrete subclass. + + /** + * Suppresses the 'bKGD' chunk from being output. + * For API compatibility with JAI 1.0, the superclass + * defines this method to throw a RuntimeException; + * accordingly, subclasses must provide their own implementations. + */ + public void unsetBackground() { + throw new RuntimeException(PropertyUtil.getString("PNGEncodeParam23")); + } + + /** + * Returns true if a 'bKGD' chunk will be output. + * For API compatibility with JAI 1.0, the superclass + * defines this method to throw a RuntimeException; + * accordingly, subclasses must provide their own implementations. + */ + public boolean isBackgroundSet() { + throw new RuntimeException(PropertyUtil.getString("PNGEncodeParam24")); + } + + // cHRM chunk + + private float[] chromaticity = null; + private boolean chromaticitySet = false; + + /** + * Sets the white point and primary chromaticities in CIE (x, y) + * space. + * + *

    The chromaticity parameter should be a + * float array of length 8 containing the white point + * X and Y, red X and Y, green X and Y, and blue X and Y values in + * order. + * + *

    The 'cHRM' chunk will encode this information. + */ + public void setChromaticity(float[] chromaticity) { + if (chromaticity.length != 8) { + throw new IllegalArgumentException(); + } + this.chromaticity = (float[])(chromaticity.clone()); + chromaticitySet = true; + } + + /** + * A convenience method that calls the array version. + */ + public void setChromaticity(float whitePointX, float whitePointY, + float redX, float redY, + float greenX, float greenY, + float blueX, float blueY) { + float[] chroma = new float[8]; + chroma[0] = whitePointX; + chroma[1] = whitePointY; + chroma[2] = redX; + chroma[3] = redY; + chroma[4] = greenX; + chroma[5] = greenY; + chroma[6] = blueX; + chroma[7] = blueY; + setChromaticity(chroma); + } + + /** + * Returns the white point and primary chromaticities in + * CIE (x, y) space. + * + *

    See the documentation for the setChromaticity + * method for the format of the returned data. + * + *

    If the chromaticity has not previously been set, or has been + * unset, an IllegalStateException will be thrown. + * + * @throws IllegalStateException if the chromaticity is not set. + */ + public float[] getChromaticity() { + if (!chromaticitySet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam12")); + } + return (float[])(chromaticity.clone()); + } + + /** + * Suppresses the 'cHRM' chunk from being output. + */ + public void unsetChromaticity() { + chromaticity = null; + chromaticitySet = false; + } + + /** + * Returns true if a 'cHRM' chunk will be output. + */ + public boolean isChromaticitySet() { + return chromaticitySet; + } + + // gAMA chunk + + private float gamma; + private boolean gammaSet = false; + + /** + * Sets the file gamma value for the image. + * + *

    The 'gAMA' chunk will encode this information. + */ + public void setGamma(float gamma) { + this.gamma = gamma; + gammaSet = true; + } + + /** + * Returns the file gamma value for the image. + * + *

    If the file gamma has not previously been set, or has been + * unset, an IllegalStateException will be thrown. + * + * @throws IllegalStateException if the gamma is not set. + */ + public float getGamma() { + if (!gammaSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam13")); + } + return gamma; + } + + /** + * Suppresses the 'gAMA' chunk from being output. + */ + public void unsetGamma() { + gammaSet = false; + } + + /** + * Returns true if a 'gAMA' chunk will be output. + */ + public boolean isGammaSet() { + return gammaSet; + } + + // hIST chunk + + private int[] paletteHistogram = null; + private boolean paletteHistogramSet = false; + + /** + * Sets the palette histogram to be stored with this image. + * The histogram consists of an array of integers, one per + * palette entry. + * + *

    The 'hIST' chunk will encode this information. + */ + public void setPaletteHistogram(int[] paletteHistogram) { + this.paletteHistogram = (int[])(paletteHistogram.clone()); + paletteHistogramSet = true; + } + + /** + * Returns the palette histogram to be stored with this image. + * + *

    If the histogram has not previously been set, or has been + * unset, an IllegalStateException will be thrown. + * + * @throws IllegalStateException if the histogram is not set. + */ + public int[] getPaletteHistogram() { + if (!paletteHistogramSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam14")); + } + return paletteHistogram; + } + + /** + * Suppresses the 'hIST' chunk from being output. + */ + public void unsetPaletteHistogram() { + paletteHistogram = null; + paletteHistogramSet = false; + } + + /** + * Returns true if a 'hIST' chunk will be output. + */ + public boolean isPaletteHistogramSet() { + return paletteHistogramSet; + } + + // iCCP chunk + + private byte[] ICCProfileData = null; + private boolean ICCProfileDataSet = false; + + /** + * Sets the ICC profile data to be stored with this image. + * The profile is represented in raw binary form. + * + *

    The 'iCCP' chunk will encode this information. + */ + public void setICCProfileData(byte[] ICCProfileData) { + this.ICCProfileData = (byte[])(ICCProfileData.clone()); + ICCProfileDataSet = true; + } + + /** + * Returns the ICC profile data to be stored with this image. + * + *

    If the ICC profile has not previously been set, or has been + * unset, an IllegalStateException will be thrown. + * + * @throws IllegalStateException if the ICC profile is not set. + */ + public byte[] getICCProfileData() { + if (!ICCProfileDataSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam15")); + } + return (byte[])(ICCProfileData.clone()); + } + + /** + * Suppresses the 'iCCP' chunk from being output. + */ + public void unsetICCProfileData() { + ICCProfileData = null; + ICCProfileDataSet = false; + } + + /** + * Returns true if a 'iCCP' chunk will be output. + */ + public boolean isICCProfileDataSet() { + return ICCProfileDataSet; + } + + // pHYS chunk + + private int[] physicalDimension = null; + private boolean physicalDimensionSet = false; + + /** + * Sets the physical dimension information to be stored with this + * image. The physicalDimension parameter should be a 3-entry + * array containing the number of pixels per unit in the X + * direction, the number of pixels per unit in the Y direction, + * and the unit specifier (0 = unknown, 1 = meters). + * + *

    The 'pHYS' chunk will encode this information. + */ + public void setPhysicalDimension(int[] physicalDimension) { + this.physicalDimension = (int[])(physicalDimension.clone()); + physicalDimensionSet = true; + } + + /** + * A convenience method that calls the array version. + */ + public void setPhysicalDimension(int xPixelsPerUnit, + int yPixelsPerUnit, + int unitSpecifier) { + int[] pd = new int[3]; + pd[0] = xPixelsPerUnit; + pd[1] = yPixelsPerUnit; + pd[2] = unitSpecifier; + + setPhysicalDimension(pd); + } + + /** + * Returns the physical dimension information to be stored + * with this image. + * + *

    If the physical dimension information has not previously + * been set, or has been unset, an + * IllegalStateException will be thrown. + * + * @throws IllegalStateException if the physical dimension information + * is not set. + */ + public int[] getPhysicalDimension() { + if (!physicalDimensionSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam16")); + } + return (int[])(physicalDimension.clone()); + } + + /** + * Suppresses the 'pHYS' chunk from being output. + */ + public void unsetPhysicalDimension() { + physicalDimension = null; + physicalDimensionSet = false; + } + + /** + * Returns true if a 'pHYS' chunk will be output. + */ + public boolean isPhysicalDimensionSet() { + return physicalDimensionSet; + } + + // sPLT chunk + + private PNGSuggestedPaletteEntry[] suggestedPalette = null; + private boolean suggestedPaletteSet = false; + + /** + * Sets the suggested palette information to be stored with this + * image. The information is passed to this method as an array of + * PNGSuggestedPaletteEntry objects. + * + *

    The 'sPLT' chunk will encode this information. + */ + public void setSuggestedPalette(PNGSuggestedPaletteEntry[] palette) { + suggestedPalette = (PNGSuggestedPaletteEntry[])(palette.clone()); + suggestedPaletteSet = true; + } + + /** + * Returns the suggested palette information to be stored with this + * image. + * + *

    If the suggested palette information has not previously + * been set, or has been unset, an + * IllegalStateException will be thrown. + * + * @throws IllegalStateException if the suggested palette + * information is not set. + */ + public PNGSuggestedPaletteEntry[] getSuggestedPalette() { + if (!suggestedPaletteSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam17")); + } + return (PNGSuggestedPaletteEntry[])(suggestedPalette.clone()); + } + + /** + * Suppresses the 'sPLT' chunk from being output. + */ + public void unsetSuggestedPalette() { + suggestedPalette = null; + suggestedPaletteSet = false; + } + + /** + * Returns true if a 'sPLT' chunk will be output. + */ + public boolean isSuggestedPaletteSet() { + return suggestedPaletteSet; + } + + // sBIT chunk + + private int[] significantBits = null; + private boolean significantBitsSet = false; + + /** + * Sets the number of significant bits for each band of the image. + * + *

    The number of entries in the significantBits + * array must be equal to the number of output bands in the image: + * 1 for a gray image, 2 for gray+alpha, 3 for index or truecolor, + * and 4 for truecolor+alpha. + * + *

    The 'sBIT' chunk will encode this information. + */ + public void setSignificantBits(int[] significantBits) { + this.significantBits = (int[])(significantBits.clone()); + significantBitsSet = true; + } + + /** + * Returns the number of significant bits for each band of the image. + * + *

    If the significant bits values have not previously been + * set, or have been unset, an IllegalStateException + * will be thrown. + * + * @throws IllegalStateException if the significant bits values are + * not set. + */ + public int[] getSignificantBits() { + if (!significantBitsSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam18")); + } + return (int[])significantBits.clone(); + } + + /** + * Suppresses the 'sBIT' chunk from being output. + */ + public void unsetSignificantBits() { + significantBits = null; + significantBitsSet = false; + } + + /** + * Returns true if an 'sBIT' chunk will be output. + */ + public boolean isSignificantBitsSet() { + return significantBitsSet; + } + + // sRGB chunk + + private int SRGBIntent; + private boolean SRGBIntentSet = false; + + /** + * Sets the sRGB rendering intent to be stored with this image. + * The legal values are 0 = Perceptual, 1 = Relative Colorimetric, + * 2 = Saturation, and 3 = Absolute Colorimetric. Refer to the + * PNG specification for information on these values. + * + *

    The 'sRGB' chunk will encode this information. + */ + public void setSRGBIntent(int SRGBIntent) { + this.SRGBIntent = SRGBIntent; + SRGBIntentSet = true; + } + + /** + * Returns the sRGB rendering intent to be stored with this image. + * + *

    If the sRGB intent has not previously been set, or has been + * unset, an IllegalStateException will be thrown. + * + * @throws IllegalStateException if the sRGB intent is not set. + */ + public int getSRGBIntent() { + if (!SRGBIntentSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam19")); + } + return SRGBIntent; + } + + /** + * Suppresses the 'sRGB' chunk from being output. + */ + public void unsetSRGBIntent() { + SRGBIntentSet = false; + } + + /** + * Returns true if an 'sRGB' chunk will be output. + */ + public boolean isSRGBIntentSet() { + return SRGBIntentSet; + } + + // tEXt chunk + + private String[] text = null; + private boolean textSet = false; + + /** + * Sets the textual data to be stored in uncompressed form with this + * image. The data is passed to this method as an array of + * Strings. + * + *

    The 'tEXt' chunk will encode this information. + */ + public void setText(String[] text) { + this.text = text; + textSet = true; + } + + /** + * Returns the text strings to be stored in uncompressed form with this + * image as an array of Strings. + * + *

    If the text strings have not previously been set, or have been + * unset, an IllegalStateException will be thrown. + * + * @throws IllegalStateException if the text strings are not set. + */ + public String[] getText() { + if (!textSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam20")); + } + return text; + } + + /** + * Suppresses the 'tEXt' chunk from being output. + */ + public void unsetText() { + text = null; + textSet = false; + } + + /** + * Returns true if a 'tEXt' chunk will be output. + */ + public boolean isTextSet() { + return textSet; + } + + // tIME chunk + + private Date modificationTime; + private boolean modificationTimeSet = false; + + /** + * Sets the modification time, as a Date, to be + * stored with this image. The internal storage format will use + * UTC regardless of how the modificationTime + * parameter was created. + * + *

    The 'tIME' chunk will encode this information. + */ + public void setModificationTime(Date modificationTime) { + this.modificationTime = modificationTime; + modificationTimeSet = true; + } + + /** + * Returns the modification time to be stored with this image. + * + *

    If the bit depth has not previously been set, or has been + * unset, an IllegalStateException will be thrown. + * + * @throws IllegalStateException if the bit depth is not set. + */ + public Date getModificationTime() { + if (!modificationTimeSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam21")); + } + return modificationTime; + } + + /** + * Suppresses the 'tIME' chunk from being output. + */ + public void unsetModificationTime() { + modificationTime = null; + modificationTimeSet = false; + } + + /** + * Returns true if a 'tIME' chunk will be output. + */ + public boolean isModificationTimeSet() { + return modificationTimeSet; + } + + // tRNS chunk + + boolean transparencySet = false; + + /** + * Suppresses the 'tRNS' chunk from being output. + */ + public void unsetTransparency() { + transparencySet = false; + } + + /** + * Returns true if a 'tRNS' chunk will be output. + */ + public boolean isTransparencySet() { + return transparencySet; + } + + // zTXT chunk + + private String[] zText = null; + private boolean zTextSet = false; + + /** + * Sets the text strings to be stored in compressed form with this + * image. The data is passed to this method as an array of + * Strings. + * + *

    The 'zTXt' chunk will encode this information. + */ + public void setCompressedText(String[] text) { + this.zText = text; + zTextSet = true; + } + + /** + * Returns the text strings to be stored in compressed form with + * this image as an array of Strings. + * + *

    If the compressed text strings have not previously been + * set, or have been unset, an IllegalStateException + * will be thrown. + * + * @throws IllegalStateException if the compressed text strings are + * not set. + */ + public String[] getCompressedText() { + if (!zTextSet) { + throw new IllegalStateException(PropertyUtil.getString("PNGEncodeParam22")); + } + return zText; + } + + /** + * Suppresses the 'zTXt' chunk from being output. + */ + public void unsetCompressedText() { + zText = null; + zTextSet = false; + } + + /** + * Returns true if a 'zTXT' chunk will be output. + */ + public boolean isCompressedTextSet() { + return zTextSet; + } + + // Other chunk types + + Vector chunkType = new Vector(); + Vector chunkData = new Vector(); + + /** + * Adds a private chunk, in binary form, to the list of chunks to + * be stored with this image. + * + * @param type a 4-character String giving the chunk type name. + * @param data an array of bytes containing the + * chunk data. + */ + public synchronized void addPrivateChunk(String type, byte[] data) { + chunkType.add(type); + chunkData.add(data.clone()); + } + + /** + * Returns the number of private chunks to be written to the + * output file. + */ + public synchronized int getNumPrivateChunks() { + return chunkType.size(); + } + + /** + * Returns the type of the private chunk at a given index, as a + * 4-character String. The index must be smaller + * than the return value of getNumPrivateChunks. + */ + public synchronized String getPrivateChunkType(int index) { + return (String)chunkType.elementAt(index); + } + + /** + * Returns the data associated of the private chunk at a given + * index, as an array of bytes. The index must be + * smaller than the return value of + * getNumPrivateChunks. + */ + public synchronized byte[] getPrivateChunkData(int index) { + return (byte[])chunkData.elementAt(index); + } + + /** + * Remove all private chunks associated with this parameter instance + * whose 'safe-to-copy' bit is not set. This may be advisable when + * transcoding PNG images. + */ + public synchronized void removeUnsafeToCopyPrivateChunks() { + Vector newChunkType = new Vector(); + Vector newChunkData = new Vector(); + + int len = getNumPrivateChunks(); + for (int i = 0; i < len; i++) { + String type = getPrivateChunkType(i); + char lastChar = type.charAt(3); + if (lastChar >= 'a' && lastChar <= 'z') { + newChunkType.add(type); + newChunkData.add(getPrivateChunkData(i)); + } + } + + chunkType = newChunkType; + chunkData = newChunkData; + } + + /** + * Remove all private chunks associated with this parameter instance. + */ + public synchronized void removeAllPrivateChunks() { + chunkType = new Vector(); + chunkData = new Vector(); + } + + /** + * An abs() function for use by the Paeth predictor. + */ + private static final int abs(int x) { + return (x < 0) ? -x : x; + } + + /** + * The Paeth predictor routine used in PNG encoding. This routine + * is included as a convenience to subclasses that override the + * filterRow method. + */ + public static final int paethPredictor(int a, int b, int c) { + int p = a + b - c; + int pa = abs(p - a); + int pb = abs(p - b); + int pc = abs(p - c); + + if ((pa <= pb) && (pa <= pc)) { + return a; + } else if (pb <= pc) { + return b; + } else { + return c; + } + } + + /** + * Performs filtering on a row of an image. This method may be + * overridden in order to provide a custom algorithm for choosing + * the filter type for a given row. + * + *

    The method is supplied with the current and previous rows + * of the image. For the first row of the image, or of an + * interlacing pass, the previous row array will be filled with + * zeros as required by the PNG specification. + * + *

    The method is also supplied with five scratch arrays. + * These arrays may be used within the method for any purpose. + * At method exit, the array at the index given by the return + * value of the method should contain the filtered data. The + * return value will also be used as the filter type. + * + *

    The default implementation of the method performs a trial + * encoding with each of the filter types, and computes the sum of + * absolute values of the differences between the raw bytes of the + * current row and the predicted values. The index of the filter + * producing the smallest result is returned. + * + *

    As an example, to perform only 'sub' filtering, this method + * could be implemented (non-optimally) as follows: + * + *

    +     * for (int i = bytesPerPixel; i < bytesPerRow + bytesPerPixel; i++) {
    +     *     int curr = currRow[i] & 0xff;
    +     *     int left = currRow[i - bytesPerPixel] & 0xff;
    +     *     scratchRow[PNG_FILTER_SUB][i] = (byte)(curr - left);
    +     * }
    +     * return PNG_FILTER_SUB;
    +     * 
    + * + * @param currRow The current row as an array of bytes + * of length at least bytesPerRow + bytesPerPixel. + * The pixel data starts at index bytesPerPixel; + * the initial bytesPerPixel bytes are zero. + * @param prevRow The current row as an array of bytes + * The pixel data starts at index bytesPerPixel; + * the initial bytesPerPixel bytes are zero. + * @param scratchRows An array of 5 byte arrays of + * length at least bytesPerRow + + * bytesPerPixel, useable to hold temporary results. + * The filtered row will be returned as one of the entries + * of this array. The returned filtered data should start + * at index bytesPerPixel; The initial + * bytesPerPixel bytes are not used. + * @param bytesPerRow The number of bytes in the image row. + * This value will always be greater than 0. + * @param bytesPerPixel The number of bytes representing a single + * pixel, rounded up to an integer. This is the 'bpp' parameter + * described in the PNG specification. + * + * @return The filter type to be used. The entry of + * scratchRows[] at this index holds the + * filtered data. */ + public int filterRow(byte[] currRow, + byte[] prevRow, + byte[][] scratchRows, + int bytesPerRow, + int bytesPerPixel) { + + int [] badness = {0, 0, 0, 0, 0}; + int curr, left, up, upleft, diff; + int pa, pb, pc; + for (int i = bytesPerPixel; i < bytesPerRow + bytesPerPixel; i++) { + curr = currRow[i] & 0xff; + left = currRow[i - bytesPerPixel] & 0xff; + up = prevRow[i] & 0xff; + upleft = prevRow[i - bytesPerPixel] & 0xff; + + // no filter + badness[0] += curr; + + // sub filter + diff = curr - left; + scratchRows[1][i] = (byte)diff; + badness [1] += (diff>0)?diff:-diff; + + // up filter + diff = curr - up; + scratchRows[2][i] = (byte)diff; + badness [2] += (diff>=0)?diff:-diff; + + // average filter + diff = curr - ((left+up)>>1); + scratchRows[3][i] = (byte)diff; + badness [3] += (diff>=0)?diff:-diff; + + // paeth filter + + // Original code much simplier but doesn't take full + // advantage of relationship between pa/b/c and + // information gleaned in abs operations. + /// pa = up -upleft; + /// pb = left-upleft; + /// pc = pa+pb; + /// pa = abs(pa); + /// pb = abs(pb); + /// pc = abs(pc); + /// if ((pa <= pb) && (pa <= pc)) + /// diff = curr-left; + /// else if (pb <= pc) + /// diff = curr-up; + /// else + /// diff = curr-upleft; + + pa = up -upleft; + pb = left-upleft; + if (pa<0) { + if (pb<0) { + // both pa & pb neg so pc is always greater than or + // equal to pa or pb; + if (pa >= pb) // since pa & pb neg check sense is reversed. + diff = curr-left; + else + diff = curr-up; + } else { + // pa neg pb pos so we must compute pc... + pc = pa+pb; + pa=-pa; + if (pa <= pb) // pc is positive and less than pb + if (pa <= pc) + diff = curr-left; + else + diff = curr-upleft; + else + // pc is negative and less than or equal to pa, + // but since pa is greater than pb this isn't an issue... + if (pb <= -pc) + diff = curr-up; + else + diff = curr-upleft; + } + } else { + if (pb<0) { + pb =-pb; // make it positive... + if (pa <= pb) { + // pc would be negative and less than or equal to pb + pc = pb-pa; + if (pa <= pc) + diff = curr-left; + else if (pb == pc) + // if pa is zero then pc==pb otherwise + // pc must be less than pb. + diff = curr-up; + else + diff = curr-upleft; + } else { + // pc would be positive and less than pa. + pc = pa-pb; + if (pb <= pc) + diff = curr-up; + else + diff = curr-upleft; + } + } else { + // both pos so pa+pb is always greater than pa/pb + if (pa <= pb) + diff = curr-left; + else + diff = curr-up; + } + } + scratchRows[4][i] = (byte)diff; + badness [4] += (diff>=0)?diff:-diff; + } + int filterType = 0; + int minBadness = badness[0]; + + for (int i = 1; i < 5; i++) { + if (badness[i] < minBadness) { + minBadness = badness[i]; + filterType = i; + } + } + + if (filterType == 0) { + System.arraycopy(currRow, bytesPerPixel, + scratchRows[0], bytesPerPixel, + bytesPerRow); + } + + return filterType; + } +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/PNGImageDecoder.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/PNGImageDecoder.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/PNGImageDecoder.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/PNGImageDecoder.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,1855 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.awt.Color; +import java.awt.Point; +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferUShort; +import java.awt.image.IndexColorModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.SequenceInputStream; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; +import java.util.Vector; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +/** + */ +public class PNGImageDecoder extends ImageDecoderImpl { + + public PNGImageDecoder(InputStream input, + PNGDecodeParam param) { + super(input, param); + } + + public RenderedImage decodeAsRenderedImage(int page) throws IOException { + if (page != 0) { + throw new IOException(PropertyUtil.getString("PNGImageDecoder19")); + } + return new PNGImage(input, (PNGDecodeParam)param); + } +} + +class PNGChunk { + int length; + int type; + byte[] data; + int crc; + + String typeString; + + public PNGChunk(int length, int type, byte[] data, int crc) { + this.length = length; + this.type = type; + this.data = data; + this.crc = crc; + + typeString = new String(); + typeString += (char)(type >> 24); + typeString += (char)((type >> 16) & 0xff); + typeString += (char)((type >> 8) & 0xff); + typeString += (char)(type & 0xff); + } + + public int getLength() { + return length; + } + + public int getType() { + return type; + } + + public String getTypeString() { + return typeString; + } + + public byte[] getData() { + return data; + } + + public byte getByte(int offset) { + return data[offset]; + } + + public int getInt1(int offset) { + return data[offset] & 0xff; + } + + public int getInt2(int offset) { + return ((data[offset] & 0xff) << 8) | + (data[offset + 1] & 0xff); + } + + public int getInt4(int offset) { + return ((data[offset] & 0xff) << 24) | + ((data[offset + 1] & 0xff) << 16) | + ((data[offset + 2] & 0xff) << 8) | + (data[offset + 3] & 0xff); + } + + public String getString4(int offset) { + String s = new String(); + s += (char)data[offset]; + s += (char)data[offset + 1]; + s += (char)data[offset + 2]; + s += (char)data[offset + 3]; + return s; + } + + public boolean isType(String typeName) { + return typeString.equals(typeName); + } +} + +/** + * TO DO: + * + * zTXt chunks + * + */ +class PNGImage extends SimpleRenderedImage { + + public static final int PNG_COLOR_GRAY = 0; + public static final int PNG_COLOR_RGB = 2; + public static final int PNG_COLOR_PALETTE = 3; + public static final int PNG_COLOR_GRAY_ALPHA = 4; + public static final int PNG_COLOR_RGB_ALPHA = 6; + + private static final String[] colorTypeNames = { + "Grayscale", "Error", "Truecolor", "Index", + "Grayscale with alpha", "Error", "Truecolor with alpha" + }; + + public static final int PNG_FILTER_NONE = 0; + public static final int PNG_FILTER_SUB = 1; + public static final int PNG_FILTER_UP = 2; + public static final int PNG_FILTER_AVERAGE = 3; + public static final int PNG_FILTER_PAETH = 4; + + private static final int RED_OFFSET = 2; + private static final int GREEN_OFFSET = 1; + private static final int BLUE_OFFSET = 0; + + private int[][] bandOffsets = { + null, + { 0 }, // G + { 0, 1 }, // GA in GA order + { 0, 1, 2 }, // RGB in RGB order + { 0, 1, 2, 3 } // RGBA in RGBA order + }; + + private int bitDepth; + private int colorType; + + private int compressionMethod; + private int filterMethod; + private int interlaceMethod; + + private int paletteEntries; + private byte[] redPalette; + private byte[] greenPalette; + private byte[] bluePalette; + private byte[] alphaPalette; + + private int bkgdRed; + private int bkgdGreen; + private int bkgdBlue; + + private int grayTransparentAlpha; + private int redTransparentAlpha; + private int greenTransparentAlpha; + private int blueTransparentAlpha; + + private int maxOpacity; + + private int[] significantBits = null; + + private boolean hasBackground = false; + + // Parameter information + + // If true, the user wants destination alpha where applicable. + private boolean suppressAlpha = false; + + // If true, perform palette lookup internally + private boolean expandPalette = false; + + // If true, output < 8 bit gray images in 8 bit components format + private boolean output8BitGray = false; + + // Create an alpha channel in the destination color model. + private boolean outputHasAlphaPalette = false; + + // Perform gamma correction on the image + private boolean performGammaCorrection = false; + + // Expand GA to GGGA for compatbility with Java2D + private boolean expandGrayAlpha = false; + + // Produce an instance of PNGEncodeParam + private boolean generateEncodeParam = false; + + // PNGDecodeParam controlling decode process + private PNGDecodeParam decodeParam = null; + + // PNGEncodeParam to store file details in + private PNGEncodeParam encodeParam = null; + + private boolean emitProperties = true; + + private float fileGamma = 45455/100000.0F; + + private float userExponent = 1.0F; + + private float displayExponent = 2.2F; + + private float[] chromaticity = null; + + private int sRGBRenderingIntent = -1; + + // Post-processing step implied by above parameters + private int postProcess = POST_NONE; + + // Possible post-processing steps + + // Do nothing + private static final int POST_NONE = 0; + + // Gamma correct only + private static final int POST_GAMMA = 1; + + // Push gray values through grayLut to expand to 8 bits + private static final int POST_GRAY_LUT = 2; + + // Push gray values through grayLut to expand to 8 bits, add alpha + private static final int POST_GRAY_LUT_ADD_TRANS = 3; + + // Push palette value through R,G,B lookup tables + private static final int POST_PALETTE_TO_RGB = 4; + + // Push palette value through R,G,B,A lookup tables + private static final int POST_PALETTE_TO_RGBA = 5; + + // Add transparency to a given gray value (w/ optional gamma) + private static final int POST_ADD_GRAY_TRANS = 6; + + // Add transparency to a given RGB value (w/ optional gamma) + private static final int POST_ADD_RGB_TRANS = 7; + + // Remove the alpha channel from a gray image (w/ optional gamma) + private static final int POST_REMOVE_GRAY_TRANS = 8; + + // Remove the alpha channel from an RGB image (w/optional gamma) + private static final int POST_REMOVE_RGB_TRANS = 9; + + // Mask to add expansion of GA -> GGGA + private static final int POST_EXP_MASK = 16; + + // Expand gray to G/G/G + private static final int POST_GRAY_ALPHA_EXP = + POST_NONE | POST_EXP_MASK; + + // Expand gray to G/G/G through a gamma lut + private static final int POST_GAMMA_EXP = + POST_GAMMA | POST_EXP_MASK; + + // Push gray values through grayLut to expand to 8 bits, expand, add alpha + private static final int POST_GRAY_LUT_ADD_TRANS_EXP = + POST_GRAY_LUT_ADD_TRANS | POST_EXP_MASK; + + // Add transparency to a given gray value, expand + private static final int POST_ADD_GRAY_TRANS_EXP = + POST_ADD_GRAY_TRANS | POST_EXP_MASK; + + private Vector streamVec = new Vector(); + private DataInputStream dataStream; + + private int bytesPerPixel; // number of bytes per input pixel + private int inputBands; + private int outputBands; + + // Number of private chunks + private int chunkIndex = 0; + + private Vector textKeys = new Vector(); + private Vector textStrings = new Vector(); + + private Vector ztextKeys = new Vector(); + private Vector ztextStrings = new Vector(); + + private WritableRaster theTile; + + private int[] gammaLut = null; + + private void initGammaLut(int bits) { + double exp = (double)userExponent/(fileGamma*displayExponent); + int numSamples = 1 << bits; + int maxOutSample = (bits == 16) ? 65535 : 255; + + gammaLut = new int[numSamples]; + for (int i = 0; i < numSamples; i++) { + double gbright = (double)i/(numSamples - 1); + double gamma = Math.pow(gbright, exp); + int igamma = (int)(gamma*maxOutSample + 0.5); + if (igamma > maxOutSample) { + igamma = maxOutSample; + } + gammaLut[i] = igamma; + } + } + + private final byte[][] expandBits = { + null, + { (byte)0x00, (byte)0xff }, + { (byte)0x00, (byte)0x55, (byte)0xaa, (byte)0xff }, + null, + { (byte)0x00, (byte)0x11, (byte)0x22, (byte)0x33, + (byte)0x44, (byte)0x55, (byte)0x66, (byte)0x77, + (byte)0x88, (byte)0x99, (byte)0xaa, (byte)0xbb, + (byte)0xcc, (byte)0xdd, (byte)0xee, (byte)0xff } + }; + + private int[] grayLut = null; + + private void initGrayLut(int bits) { + int len = 1 << bits; + grayLut = new int[len]; + + if (performGammaCorrection) { + for (int i = 0; i < len; i++) { + grayLut[i] = gammaLut[i]; + } + } else { + for (int i = 0; i < len; i++) { + grayLut[i] = expandBits[bits][i]; + } + } + } + + public PNGImage(InputStream stream, PNGDecodeParam decodeParam) + throws IOException { + + if (!stream.markSupported()) { + stream = new BufferedInputStream(stream); + } + DataInputStream distream = new DataInputStream(stream); + + if (decodeParam == null) { + decodeParam = new PNGDecodeParam(); + } + this.decodeParam = decodeParam; + + // Get parameter values + this.suppressAlpha = decodeParam.getSuppressAlpha(); + this.expandPalette = decodeParam.getExpandPalette(); + this.output8BitGray = decodeParam.getOutput8BitGray(); + this.expandGrayAlpha = decodeParam.getExpandGrayAlpha(); + if (decodeParam.getPerformGammaCorrection()) { + this.userExponent = decodeParam.getUserExponent(); + this.displayExponent = decodeParam.getDisplayExponent(); + performGammaCorrection = true; + output8BitGray = true; + } + this.generateEncodeParam = decodeParam.getGenerateEncodeParam(); + + if (emitProperties) { + properties.put("file_type", "PNG v. 1.0"); + } + + try { + long magic = distream.readLong(); + if (magic != 0x89504e470d0a1a0aL) { + String msg = PropertyUtil.getString("PNGImageDecoder0"); + throw new RuntimeException(msg); + } + } catch (Exception e) { + e.printStackTrace(); + String msg = PropertyUtil.getString("PNGImageDecoder1"); + throw new RuntimeException(msg); + } + + do { + try { + PNGChunk chunk; + + String chunkType = getChunkType(distream); + if (chunkType.equals("IHDR")) { + chunk = readChunk(distream); + parse_IHDR_chunk(chunk); + } else if (chunkType.equals("PLTE")) { + chunk = readChunk(distream); + parse_PLTE_chunk(chunk); + } else if (chunkType.equals("IDAT")) { + chunk = readChunk(distream); + streamVec.add(new ByteArrayInputStream(chunk.getData())); + } else if (chunkType.equals("IEND")) { + chunk = readChunk(distream); + parse_IEND_chunk(chunk); + break; // fall through to the bottom + } else if (chunkType.equals("bKGD")) { + chunk = readChunk(distream); + parse_bKGD_chunk(chunk); + } else if (chunkType.equals("cHRM")) { + chunk = readChunk(distream); + parse_cHRM_chunk(chunk); + } else if (chunkType.equals("gAMA")) { + chunk = readChunk(distream); + parse_gAMA_chunk(chunk); + } else if (chunkType.equals("hIST")) { + chunk = readChunk(distream); + parse_hIST_chunk(chunk); + } else if (chunkType.equals("iCCP")) { + chunk = readChunk(distream); + parse_iCCP_chunk(chunk); + } else if (chunkType.equals("pHYs")) { + chunk = readChunk(distream); + parse_pHYs_chunk(chunk); + } else if (chunkType.equals("sBIT")) { + chunk = readChunk(distream); + parse_sBIT_chunk(chunk); + } else if (chunkType.equals("sRGB")) { + chunk = readChunk(distream); + parse_sRGB_chunk(chunk); + } else if (chunkType.equals("tEXt")) { + chunk = readChunk(distream); + parse_tEXt_chunk(chunk); + } else if (chunkType.equals("tIME")) { + chunk = readChunk(distream); + parse_tIME_chunk(chunk); + } else if (chunkType.equals("tRNS")) { + chunk = readChunk(distream); + parse_tRNS_chunk(chunk); + } else if (chunkType.equals("zTXt")) { + chunk = readChunk(distream); + parse_zTXt_chunk(chunk); + } else { + chunk = readChunk(distream); + // Output the chunk data in raw form + + String type = chunk.getTypeString(); + byte[] data = chunk.getData(); + if (encodeParam != null) { + encodeParam.addPrivateChunk(type, data); + } + if (emitProperties) { + String key = "chunk_" + chunkIndex++ + ":" + type; + properties.put(key.toLowerCase(), data); + } + } + } catch (Exception e) { + e.printStackTrace(); + String msg = PropertyUtil.getString("PNGImageDecoder2"); + throw new RuntimeException(msg); + } + } while (true); + + // Final post-processing + + if (significantBits == null) { + significantBits = new int[inputBands]; + for (int i = 0; i < inputBands; i++) { + significantBits[i] = bitDepth; + } + + if (emitProperties) { + properties.put("significant_bits", significantBits); + } + } + } + + private static String getChunkType(DataInputStream distream) { + try { + distream.mark(8); + /* int length = */ distream.readInt(); + int type = distream.readInt(); + distream.reset(); + + String typeString = new String(); + typeString += (char)(type >> 24); + typeString += (char)((type >> 16) & 0xff); + typeString += (char)((type >> 8) & 0xff); + typeString += (char)(type & 0xff); + return typeString; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private static PNGChunk readChunk(DataInputStream distream) { + try { + int length = distream.readInt(); + int type = distream.readInt(); + byte[] data = new byte[length]; + distream.readFully(data); + int crc = distream.readInt(); + + return new PNGChunk(length, type, data, crc); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private void parse_IHDR_chunk(PNGChunk chunk) { + tileWidth = width = chunk.getInt4(0); + tileHeight = height = chunk.getInt4(4); + + bitDepth = chunk.getInt1(8); + + if ((bitDepth != 1) && (bitDepth != 2) && (bitDepth != 4) && + (bitDepth != 8) && (bitDepth != 16)) { + // Error -- bad bit depth + String msg = PropertyUtil.getString("PNGImageDecoder3"); + throw new RuntimeException(msg); + } + maxOpacity = (1 << bitDepth) - 1; + + colorType = chunk.getInt1(9); + if ((colorType != PNG_COLOR_GRAY) && + (colorType != PNG_COLOR_RGB) && + (colorType != PNG_COLOR_PALETTE) && + (colorType != PNG_COLOR_GRAY_ALPHA) && + (colorType != PNG_COLOR_RGB_ALPHA)) { + System.out.println(PropertyUtil.getString("PNGImageDecoder4")); + } + + if ((colorType == PNG_COLOR_RGB) && (bitDepth < 8)) { + // Error -- RGB images must have 8 or 16 bits + String msg = PropertyUtil.getString("PNGImageDecoder5"); + throw new RuntimeException(msg); + } + + if ((colorType == PNG_COLOR_PALETTE) && (bitDepth == 16)) { + // Error -- palette images must have < 16 bits + String msg = PropertyUtil.getString("PNGImageDecoder6"); + throw new RuntimeException(msg); + } + + if ((colorType == PNG_COLOR_GRAY_ALPHA) && (bitDepth < 8)) { + // Error -- gray/alpha images must have >= 8 bits + String msg = PropertyUtil.getString("PNGImageDecoder7"); + throw new RuntimeException(msg); + } + + if ((colorType == PNG_COLOR_RGB_ALPHA) && (bitDepth < 8)) { + // Error -- RGB/alpha images must have >= 8 bits + String msg = PropertyUtil.getString("PNGImageDecoder8"); + throw new RuntimeException(msg); + } + + if (emitProperties) { + properties.put("color_type", colorTypeNames[colorType]); + } + + if (generateEncodeParam) { + if (colorType == PNG_COLOR_PALETTE) { + encodeParam = new PNGEncodeParam.Palette(); + } else if (colorType == PNG_COLOR_GRAY || + colorType == PNG_COLOR_GRAY_ALPHA) { + encodeParam = new PNGEncodeParam.Gray(); + } else { + encodeParam = new PNGEncodeParam.RGB(); + } + decodeParam.setEncodeParam(encodeParam); + } + + if (encodeParam != null) { + encodeParam.setBitDepth(bitDepth); + } + if (emitProperties) { + properties.put("bit_depth", new Integer(bitDepth)); + } + + if (performGammaCorrection) { + // Assume file gamma is 1/2.2 unless we get a gAMA chunk + float gamma = (1.0F/2.2F)*(displayExponent/userExponent); + if (encodeParam != null) { + encodeParam.setGamma(gamma); + } + if (emitProperties) { + properties.put("gamma", new Float(gamma)); + } + } + + compressionMethod = chunk.getInt1(10); + if (compressionMethod != 0) { + // Error -- only know about compression method 0 + String msg = PropertyUtil.getString("PNGImageDecoder9"); + throw new RuntimeException(msg); + } + + filterMethod = chunk.getInt1(11); + if (filterMethod != 0) { + // Error -- only know about filter method 0 + String msg = PropertyUtil.getString("PNGImageDecoder10"); + throw new RuntimeException(msg); + } + + interlaceMethod = chunk.getInt1(12); + if (interlaceMethod == 0) { + if (encodeParam != null) { + encodeParam.setInterlacing(false); + } + if (emitProperties) { + properties.put("interlace_method", "None"); + } + } else if (interlaceMethod == 1) { + if (encodeParam != null) { + encodeParam.setInterlacing(true); + } + if (emitProperties) { + properties.put("interlace_method", "Adam7"); + } + } else { + // Error -- only know about Adam7 interlacing + String msg = PropertyUtil.getString("PNGImageDecoder11"); + throw new RuntimeException(msg); + } + + bytesPerPixel = (bitDepth == 16) ? 2 : 1; + + switch (colorType) { + case PNG_COLOR_GRAY: + inputBands = 1; + outputBands = 1; + + if (output8BitGray && (bitDepth < 8)) { + postProcess = POST_GRAY_LUT; + } else if (performGammaCorrection) { + postProcess = POST_GAMMA; + } else { + postProcess = POST_NONE; + } + break; + + case PNG_COLOR_RGB: + inputBands = 3; + bytesPerPixel *= 3; + outputBands = 3; + + if (performGammaCorrection) { + postProcess = POST_GAMMA; + } else { + postProcess = POST_NONE; + } + break; + + case PNG_COLOR_PALETTE: + inputBands = 1; + bytesPerPixel = 1; + outputBands = expandPalette ? 3 : 1; + + if (expandPalette) { + postProcess = POST_PALETTE_TO_RGB; + } else { + postProcess = POST_NONE; + } + break; + + case PNG_COLOR_GRAY_ALPHA: + inputBands = 2; + bytesPerPixel *= 2; + + if (suppressAlpha) { + outputBands = 1; + postProcess = POST_REMOVE_GRAY_TRANS; + } else { + if (performGammaCorrection) { + postProcess = POST_GAMMA; + } else { + postProcess = POST_NONE; + } + if (expandGrayAlpha) { + postProcess |= POST_EXP_MASK; + outputBands = 4; + } else { + outputBands = 2; + } + } + break; + + case PNG_COLOR_RGB_ALPHA: + inputBands = 4; + bytesPerPixel *= 4; + outputBands = (!suppressAlpha) ? 4 : 3; + + if (suppressAlpha) { + postProcess = POST_REMOVE_RGB_TRANS; + } else if (performGammaCorrection) { + postProcess = POST_GAMMA; + } else { + postProcess = POST_NONE; + } + break; + } + } + + private void parse_IEND_chunk(PNGChunk chunk) throws Exception { + // Store text strings + int textLen = textKeys.size(); + String[] textArray = new String[2*textLen]; + for (int i = 0; i < textLen; i++) { + String key = (String)textKeys.elementAt(i); + String val = (String)textStrings.elementAt(i); + textArray[2*i] = key; + textArray[2*i + 1] = val; + if (emitProperties) { + String uniqueKey = "text_" + i + ":" + key; + properties.put(uniqueKey.toLowerCase(), val); + } + } + if (encodeParam != null) { + encodeParam.setText(textArray); + } + + // Store compressed text strings + int ztextLen = ztextKeys.size(); + String[] ztextArray = new String[2*ztextLen]; + for (int i = 0; i < ztextLen; i++) { + String key = (String)ztextKeys.elementAt(i); + String val = (String)ztextStrings.elementAt(i); + ztextArray[2*i] = key; + ztextArray[2*i + 1] = val; + if (emitProperties) { + String uniqueKey = "ztext_" + i + ":" + key; + properties.put(uniqueKey.toLowerCase(), val); + } + } + if (encodeParam != null) { + encodeParam.setCompressedText(ztextArray); + } + + // Parse prior IDAT chunks + InputStream seqStream = + new SequenceInputStream(streamVec.elements()); + InputStream infStream = + new InflaterInputStream(seqStream, new Inflater()); + dataStream = new DataInputStream(infStream); + + // Create an empty WritableRaster + int depth = bitDepth; + if ((colorType == PNG_COLOR_GRAY) && + (bitDepth < 8) && output8BitGray) { + depth = 8; + } + if ((colorType == PNG_COLOR_PALETTE) && expandPalette) { + depth = 8; + } + int bytesPerRow = (outputBands*width*depth + 7)/8; + int scanlineStride = + (depth == 16) ? (bytesPerRow/2) : bytesPerRow; + + theTile = createRaster(width, height, outputBands, + scanlineStride, + depth); + + if (performGammaCorrection && (gammaLut == null)) { + initGammaLut(bitDepth); + } + if ((postProcess == POST_GRAY_LUT) || + (postProcess == POST_GRAY_LUT_ADD_TRANS) || + (postProcess == POST_GRAY_LUT_ADD_TRANS_EXP)) { + initGrayLut(bitDepth); + } + + decodeImage(interlaceMethod == 1); + sampleModel = theTile.getSampleModel(); + + if ((colorType == PNG_COLOR_PALETTE) && !expandPalette) { + if (outputHasAlphaPalette) { + colorModel = new IndexColorModel(bitDepth, + paletteEntries, + redPalette, + greenPalette, + bluePalette, + alphaPalette); + } else { + colorModel = new IndexColorModel(bitDepth, + paletteEntries, + redPalette, + greenPalette, + bluePalette); + } + } else if ((colorType == PNG_COLOR_GRAY) && + (bitDepth < 8) && !output8BitGray) { + byte[] palette = expandBits[bitDepth]; + colorModel = new IndexColorModel(bitDepth, + palette.length, + palette, + palette, + palette); + } else { + colorModel = + createComponentColorModel(sampleModel); + } + } + + private static final int[] GrayBits8 = { 8 }; + private static final ComponentColorModel colorModelGray8 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), + GrayBits8, false, false, + Transparency.OPAQUE, + DataBuffer.TYPE_BYTE); + + private static final int[] GrayAlphaBits8 = { 8, 8 }; + private static final ComponentColorModel colorModelGrayAlpha8 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), + GrayAlphaBits8, true, false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_BYTE); + + private static final int[] GrayBits16 = { 16 }; + private static final ComponentColorModel colorModelGray16 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), + GrayBits16, false, false, + Transparency.OPAQUE, + DataBuffer.TYPE_USHORT); + + private static final int[] GrayAlphaBits16 = { 16, 16 }; + private static final ComponentColorModel colorModelGrayAlpha16 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), + GrayAlphaBits16, true, false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_USHORT); + + private static final int[] GrayBits32 = { 32 }; + private static final ComponentColorModel colorModelGray32 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), + GrayBits32, false, false, + Transparency.OPAQUE, + DataBuffer.TYPE_INT); + + private static final int[] GrayAlphaBits32 = { 32, 32 }; + private static final ComponentColorModel colorModelGrayAlpha32 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), + GrayAlphaBits32, true, false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_INT); + + private static final int[] RGBBits8 = { 8, 8, 8 }; + private static final ComponentColorModel colorModelRGB8 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + RGBBits8, false, false, + Transparency.OPAQUE, + DataBuffer.TYPE_BYTE); + + private static final int[] RGBABits8 = { 8, 8, 8, 8 }; + private static final ComponentColorModel colorModelRGBA8 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + RGBABits8, true, false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_BYTE); + + private static final int[] RGBBits16 = { 16, 16, 16 }; + private static final ComponentColorModel colorModelRGB16 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + RGBBits16, false, false, + Transparency.OPAQUE, + DataBuffer.TYPE_USHORT); + + private static final int[] RGBABits16 = { 16, 16, 16, 16 }; + private static final ComponentColorModel colorModelRGBA16 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + RGBABits16, true, false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_USHORT); + + private static final int[] RGBBits32 = { 32, 32, 32 }; + private static final ComponentColorModel colorModelRGB32 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + RGBBits32, false, false, + Transparency.OPAQUE, + DataBuffer.TYPE_INT); + + private static final int[] RGBABits32 = { 32, 32, 32, 32 }; + private static final ComponentColorModel colorModelRGBA32 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + RGBABits32, true, false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_INT); + /** + * A convenience method to create an instance of + * ComponentColorModel suitable for use with the + * given SampleModel. The SampleModel + * should have a data type of DataBuffer.TYPE_BYTE, + * TYPE_USHORT, or TYPE_INT and between + * 1 and 4 bands. Depending on the number of bands of the + * SampleModel, either a gray, gray+alpha, rgb, or + * rgb+alpha ColorModel is returned. + */ + public static ColorModel createComponentColorModel(SampleModel sm) { + int type = sm.getDataType(); + int bands = sm.getNumBands(); + ComponentColorModel cm = null; + + if (type == DataBuffer.TYPE_BYTE) { + switch (bands) { + case 1: + cm = colorModelGray8; + break; + case 2: + cm = colorModelGrayAlpha8; + break; + case 3: + cm = colorModelRGB8; + break; + case 4: + cm = colorModelRGBA8; + break; + } + } else if (type == DataBuffer.TYPE_USHORT) { + switch (bands) { + case 1: + cm = colorModelGray16; + break; + case 2: + cm = colorModelGrayAlpha16; + break; + case 3: + cm = colorModelRGB16; + break; + case 4: + cm = colorModelRGBA16; + break; + } + } else if (type == DataBuffer.TYPE_INT) { + switch (bands) { + case 1: + cm = colorModelGray32; + break; + case 2: + cm = colorModelGrayAlpha32; + break; + case 3: + cm = colorModelRGB32; + break; + case 4: + cm = colorModelRGBA32; + break; + } + } + + return cm; + } + + private void parse_PLTE_chunk(PNGChunk chunk) { + paletteEntries = chunk.getLength()/3; + redPalette = new byte[paletteEntries]; + greenPalette = new byte[paletteEntries]; + bluePalette = new byte[paletteEntries]; + + int pltIndex = 0; + + // gAMA chunk must precede PLTE chunk + if (performGammaCorrection) { + if (gammaLut == null) { + initGammaLut(bitDepth == 16 ? 16 : 8); + } + + for (int i = 0; i < paletteEntries; i++) { + byte r = chunk.getByte(pltIndex++); + byte g = chunk.getByte(pltIndex++); + byte b = chunk.getByte(pltIndex++); + + redPalette[i] = (byte)gammaLut[r & 0xff]; + greenPalette[i] = (byte)gammaLut[g & 0xff]; + bluePalette[i] = (byte)gammaLut[b & 0xff]; + } + } else { + for (int i = 0; i < paletteEntries; i++) { + redPalette[i] = chunk.getByte(pltIndex++); + greenPalette[i] = chunk.getByte(pltIndex++); + bluePalette[i] = chunk.getByte(pltIndex++); + } + } + } + + private void parse_bKGD_chunk(PNGChunk chunk) { + hasBackground = true; + + switch (colorType) { + case PNG_COLOR_PALETTE: + int bkgdIndex = chunk.getByte(0) & 0xff; + + bkgdRed = redPalette[bkgdIndex] & 0xff; + bkgdGreen = greenPalette[bkgdIndex] & 0xff; + bkgdBlue = bluePalette[bkgdIndex] & 0xff; + + if (encodeParam != null) { + ((PNGEncodeParam.Palette)encodeParam). + setBackgroundPaletteIndex(bkgdIndex); + } + break; + case PNG_COLOR_GRAY: case PNG_COLOR_GRAY_ALPHA: + int bkgdGray = chunk.getInt2(0); + bkgdRed = bkgdGreen = bkgdBlue = bkgdGray; + + if (encodeParam != null) { + ((PNGEncodeParam.Gray)encodeParam). + setBackgroundGray(bkgdGray); + } + break; + case PNG_COLOR_RGB: case PNG_COLOR_RGB_ALPHA: + bkgdRed = chunk.getInt2(0); + bkgdGreen = chunk.getInt2(2); + bkgdBlue = chunk.getInt2(4); + + int[] bkgdRGB = new int[3]; + bkgdRGB[0] = bkgdRed; + bkgdRGB[1] = bkgdGreen; + bkgdRGB[2] = bkgdBlue; + if (encodeParam != null) { + ((PNGEncodeParam.RGB)encodeParam). + setBackgroundRGB(bkgdRGB); + } + break; + } + + int r = 0, g = 0, b = 0; + if (bitDepth < 8) { + r = expandBits[bitDepth][bkgdRed]; + g = expandBits[bitDepth][bkgdGreen]; + b = expandBits[bitDepth][bkgdBlue]; + } else if (bitDepth == 8) { + r = bkgdRed; + g = bkgdGreen; + b = bkgdBlue; + } else if (bitDepth == 16) { + r = bkgdRed >> 8; + g = bkgdGreen >> 8; + b = bkgdBlue >> 8; + } + if (emitProperties) { + properties.put("background_color", new Color(r, g, b)); + } + } + + private void parse_cHRM_chunk(PNGChunk chunk) { + // If an sRGB chunk exists, ignore cHRM chunks + if (sRGBRenderingIntent != -1) { + return; + } + + chromaticity = new float[8]; + chromaticity[0] = chunk.getInt4(0)/100000.0F; + chromaticity[1] = chunk.getInt4(4)/100000.0F; + chromaticity[2] = chunk.getInt4(8)/100000.0F; + chromaticity[3] = chunk.getInt4(12)/100000.0F; + chromaticity[4] = chunk.getInt4(16)/100000.0F; + chromaticity[5] = chunk.getInt4(20)/100000.0F; + chromaticity[6] = chunk.getInt4(24)/100000.0F; + chromaticity[7] = chunk.getInt4(28)/100000.0F; + + if (encodeParam != null) { + encodeParam.setChromaticity(chromaticity); + } + if (emitProperties) { + properties.put("white_point_x", new Float(chromaticity[0])); + properties.put("white_point_y", new Float(chromaticity[1])); + properties.put("red_x", new Float(chromaticity[2])); + properties.put("red_y", new Float(chromaticity[3])); + properties.put("green_x", new Float(chromaticity[4])); + properties.put("green_y", new Float(chromaticity[5])); + properties.put("blue_x", new Float(chromaticity[6])); + properties.put("blue_y", new Float(chromaticity[7])); + } + } + + private void parse_gAMA_chunk(PNGChunk chunk) { + // If an sRGB chunk exists, ignore gAMA chunks + if (sRGBRenderingIntent != -1) { + return; + } + + fileGamma = chunk.getInt4(0)/100000.0F; + + float exp = + performGammaCorrection ? displayExponent/userExponent : 1.0F; + if (encodeParam != null) { + encodeParam.setGamma(fileGamma*exp); + } + if (emitProperties) { + properties.put("gamma", new Float(fileGamma*exp)); + } + } + + private void parse_hIST_chunk(PNGChunk chunk) { + if (redPalette == null) { + String msg = PropertyUtil.getString("PNGImageDecoder18"); + throw new RuntimeException(msg); + } + + int length = redPalette.length; + int[] hist = new int[length]; + for (int i = 0; i < length; i++) { + hist[i] = chunk.getInt2(2*i); + } + + if (encodeParam != null) { + encodeParam.setPaletteHistogram(hist); + } + } + + private void parse_iCCP_chunk(PNGChunk chunk) { + String name = new String(); + byte b; + + int textIndex = 0; + while ((b = chunk.getByte(textIndex++)) != 0) { + name += (char)b; + } + } + + private void parse_pHYs_chunk(PNGChunk chunk) { + int xPixelsPerUnit = chunk.getInt4(0); + int yPixelsPerUnit = chunk.getInt4(4); + int unitSpecifier = chunk.getInt1(8); + + if (encodeParam != null) { + encodeParam.setPhysicalDimension(xPixelsPerUnit, + yPixelsPerUnit, + unitSpecifier); + } + if (emitProperties) { + properties.put("x_pixels_per_unit", new Integer(xPixelsPerUnit)); + properties.put("y_pixels_per_unit", new Integer(yPixelsPerUnit)); + properties.put("pixel_aspect_ratio", + new Float((float)xPixelsPerUnit/yPixelsPerUnit)); + if (unitSpecifier == 1) { + properties.put("pixel_units", "Meters"); + } else if (unitSpecifier != 0) { + // Error -- unit specifier must be 0 or 1 + String msg = PropertyUtil.getString("PNGImageDecoder12"); + throw new RuntimeException(msg); + } + } + } + + private void parse_sBIT_chunk(PNGChunk chunk) { + if (colorType == PNG_COLOR_PALETTE) { + significantBits = new int[3]; + } else { + significantBits = new int[inputBands]; + } + for (int i = 0; i < significantBits.length; i++) { + int bits = chunk.getByte(i); + int depth = (colorType == PNG_COLOR_PALETTE) ? 8 : bitDepth; + if (bits <= 0 || bits > depth) { + // Error -- significant bits must be between 0 and + // image bit depth. + String msg = PropertyUtil.getString("PNGImageDecoder13"); + throw new RuntimeException(msg); + } + significantBits[i] = bits; + } + + if (encodeParam != null) { + encodeParam.setSignificantBits(significantBits); + } + if (emitProperties) { + properties.put("significant_bits", significantBits); + } + } + + private void parse_sRGB_chunk(PNGChunk chunk) { + sRGBRenderingIntent = chunk.getByte(0); + + // The presence of an sRGB chunk implies particular + // settings for gamma and chroma. + fileGamma = 45455/100000.0F; + + chromaticity = new float[8]; + chromaticity[0] = 31270/10000.0F; + chromaticity[1] = 32900/10000.0F; + chromaticity[2] = 64000/10000.0F; + chromaticity[3] = 33000/10000.0F; + chromaticity[4] = 30000/10000.0F; + chromaticity[5] = 60000/10000.0F; + chromaticity[6] = 15000/10000.0F; + chromaticity[7] = 6000/10000.0F; + + if (performGammaCorrection) { + // File gamma is 1/2.2 + float gamma = fileGamma*(displayExponent/userExponent); + if (encodeParam != null) { + encodeParam.setGamma(gamma); + encodeParam.setChromaticity(chromaticity); + } + if (emitProperties) { + properties.put("gamma", new Float(gamma)); + properties.put("white_point_x", new Float(chromaticity[0])); + properties.put("white_point_y", new Float(chromaticity[1])); + properties.put("red_x", new Float(chromaticity[2])); + properties.put("red_y", new Float(chromaticity[3])); + properties.put("green_x", new Float(chromaticity[4])); + properties.put("green_y", new Float(chromaticity[5])); + properties.put("blue_x", new Float(chromaticity[6])); + properties.put("blue_y", new Float(chromaticity[7])); + } + } + } + + private void parse_tEXt_chunk(PNGChunk chunk) { + String key = new String(); + String value = new String(); + byte b; + + int textIndex = 0; + while ((b = chunk.getByte(textIndex++)) != 0) { + key += (char)b; + } + + for (int i = textIndex; i < chunk.getLength(); i++) { + value += (char)chunk.getByte(i); + } + + textKeys.add(key); + textStrings.add(value); + } + + private void parse_tIME_chunk(PNGChunk chunk) { + int year = chunk.getInt2(0); + int month = chunk.getInt1(2) - 1; + int day = chunk.getInt1(3); + int hour = chunk.getInt1(4); + int minute = chunk.getInt1(5); + int second = chunk.getInt1(6); + + TimeZone gmt = TimeZone.getTimeZone("GMT"); + + GregorianCalendar cal = new GregorianCalendar(gmt); + cal.set(year, month, day, + hour, minute, second); + Date date = cal.getTime(); + + if (encodeParam != null) { + encodeParam.setModificationTime(date); + } + if (emitProperties) { + properties.put("timestamp", date); + } + } + + private void parse_tRNS_chunk(PNGChunk chunk) { + if (colorType == PNG_COLOR_PALETTE) { + int entries = chunk.getLength(); + if (entries > paletteEntries) { + // Error -- mustn't have more alpha than RGB palette entries + String msg = PropertyUtil.getString("PNGImageDecoder14"); + throw new RuntimeException(msg); + } + + // Load beginning of palette from the chunk + alphaPalette = new byte[paletteEntries]; + for (int i = 0; i < entries; i++) { + alphaPalette[i] = chunk.getByte(i); + } + + // Fill rest of palette with 255 + for (int i = entries; i < paletteEntries; i++) { + alphaPalette[i] = (byte)255; + } + + if (!suppressAlpha) { + if (expandPalette) { + postProcess = POST_PALETTE_TO_RGBA; + outputBands = 4; + } else { + outputHasAlphaPalette = true; + } + } + } else if (colorType == PNG_COLOR_GRAY) { + grayTransparentAlpha = chunk.getInt2(0); + + if (!suppressAlpha) { + if (bitDepth < 8) { + output8BitGray = true; + maxOpacity = 255; + postProcess = POST_GRAY_LUT_ADD_TRANS; + } else { + postProcess = POST_ADD_GRAY_TRANS; + } + + if (expandGrayAlpha) { + outputBands = 4; + postProcess |= POST_EXP_MASK; + } else { + outputBands = 2; + } + + if (encodeParam != null) { + ((PNGEncodeParam.Gray)encodeParam). + setTransparentGray(grayTransparentAlpha); + } + } + } else if (colorType == PNG_COLOR_RGB) { + redTransparentAlpha = chunk.getInt2(0); + greenTransparentAlpha = chunk.getInt2(2); + blueTransparentAlpha = chunk.getInt2(4); + + if (!suppressAlpha) { + outputBands = 4; + postProcess = POST_ADD_RGB_TRANS; + + if (encodeParam != null) { + int[] rgbTrans = new int[3]; + rgbTrans[0] = redTransparentAlpha; + rgbTrans[1] = greenTransparentAlpha; + rgbTrans[2] = blueTransparentAlpha; + ((PNGEncodeParam.RGB)encodeParam). + setTransparentRGB(rgbTrans); + } + } + } else if (colorType == PNG_COLOR_GRAY_ALPHA || + colorType == PNG_COLOR_RGB_ALPHA) { + // Error -- GA or RGBA image can't have a tRNS chunk. + String msg = PropertyUtil.getString("PNGImageDecoder15"); + throw new RuntimeException(msg); + } + } + + private void parse_zTXt_chunk(PNGChunk chunk) { + String key = new String(); + String value = new String(); + byte b; + + int textIndex = 0; + while ((b = chunk.getByte(textIndex++)) != 0) { + key += (char)b; + } + /* int method = */ chunk.getByte(textIndex++); + + try { + int length = chunk.getLength() - textIndex; + byte[] data = chunk.getData(); + InputStream cis = + new ByteArrayInputStream(data, textIndex, length); + InputStream iis = new InflaterInputStream(cis); + + int c; + while ((c = iis.read()) != -1) { + value += (char)c; + } + + ztextKeys.add(key); + ztextStrings.add(value); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private WritableRaster createRaster(int width, int height, int bands, + int scanlineStride, + int bitDepth) { + + DataBuffer dataBuffer; + WritableRaster ras = null; + Point origin = new Point(0, 0); + if ((bitDepth < 8) && (bands == 1)) { + dataBuffer = new DataBufferByte(height*scanlineStride); + ras = Raster.createPackedRaster(dataBuffer, + width, height, + bitDepth, + origin); + } else if (bitDepth <= 8) { + dataBuffer = new DataBufferByte(height*scanlineStride); + ras = Raster.createInterleavedRaster(dataBuffer, + width, height, + scanlineStride, + bands, + bandOffsets[bands], + origin); + } else { + dataBuffer = new DataBufferUShort(height*scanlineStride); + ras = Raster.createInterleavedRaster(dataBuffer, + width, height, + scanlineStride, + bands, + bandOffsets[bands], + origin); + } + + return ras; + } + + // Data filtering methods + + private static void decodeSubFilter(byte[] curr, int count, int bpp) { + for (int i = bpp; i < count; i++) { + int val; + + val = curr[i] & 0xff; + val += curr[i - bpp] & 0xff; + + curr[i] = (byte)val; + } + } + + private static void decodeUpFilter(byte[] curr, byte[] prev, + int count) { + for (int i = 0; i < count; i++) { + int raw = curr[i] & 0xff; + int prior = prev[i] & 0xff; + + curr[i] = (byte)(raw + prior); + } + } + + private static void decodeAverageFilter(byte[] curr, byte[] prev, + int count, int bpp) { + int raw, priorPixel, priorRow; + + for (int i = 0; i < bpp; i++) { + raw = curr[i] & 0xff; + priorRow = prev[i] & 0xff; + + curr[i] = (byte)(raw + priorRow/2); + } + + for (int i = bpp; i < count; i++) { + raw = curr[i] & 0xff; + priorPixel = curr[i - bpp] & 0xff; + priorRow = prev[i] & 0xff; + + curr[i] = (byte)(raw + (priorPixel + priorRow)/2); + } + } + + private static int paethPredictor(int a, int b, int c) { + int p = a + b - c; + int pa = Math.abs(p - a); + int pb = Math.abs(p - b); + int pc = Math.abs(p - c); + + if ((pa <= pb) && (pa <= pc)) { + return a; + } else if (pb <= pc) { + return b; + } else { + return c; + } + } + + private static void decodePaethFilter(byte[] curr, byte[] prev, + int count, int bpp) { + int raw, priorPixel, priorRow, priorRowPixel; + + for (int i = 0; i < bpp; i++) { + raw = curr[i] & 0xff; + priorRow = prev[i] & 0xff; + + curr[i] = (byte)(raw + priorRow); + } + + for (int i = bpp; i < count; i++) { + raw = curr[i] & 0xff; + priorPixel = curr[i - bpp] & 0xff; + priorRow = prev[i] & 0xff; + priorRowPixel = prev[i - bpp] & 0xff; + + curr[i] = (byte)(raw + paethPredictor(priorPixel, + priorRow, + priorRowPixel)); + } + } + + private void processPixels(int process, + Raster src, WritableRaster dst, + int xOffset, int step, int y, int width) { + int srcX, dstX; + + // Create an array suitable for holding one pixel + int[] ps = src.getPixel(0, 0, (int[])null); + int[] pd = dst.getPixel(0, 0, (int[])null); + + dstX = xOffset; + switch (process) { + case POST_NONE: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + dst.setPixel(dstX, y, ps); + dstX += step; + } + break; + + case POST_GAMMA: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + for (int i = 0; i < inputBands; i++) { + int x = ps[i]; + ps[i] = gammaLut[x]; + } + + dst.setPixel(dstX, y, ps); + dstX += step; + } + break; + + case POST_GRAY_LUT: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + pd[0] = grayLut[ps[0]]; + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_GRAY_LUT_ADD_TRANS: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + pd[0] = grayLut[val]; + if (val == grayTransparentAlpha) { + pd[1] = 0; + } else { + pd[1] = maxOpacity; + } + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_PALETTE_TO_RGB: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + pd[0] = redPalette[val]; + pd[1] = greenPalette[val]; + pd[2] = bluePalette[val]; + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_PALETTE_TO_RGBA: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + pd[0] = redPalette[val]; + pd[1] = greenPalette[val]; + pd[2] = bluePalette[val]; + pd[3] = alphaPalette[val]; + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_ADD_GRAY_TRANS: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + if (performGammaCorrection) { + val = gammaLut[val]; + } + pd[0] = val; + if (val == grayTransparentAlpha) { + pd[1] = 0; + } else { + pd[1] = maxOpacity; + } + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_ADD_RGB_TRANS: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int r = ps[0]; + int g = ps[1]; + int b = ps[2]; + if (performGammaCorrection) { + pd[0] = gammaLut[r]; + pd[1] = gammaLut[g]; + pd[2] = gammaLut[b]; + } else { + pd[0] = r; + pd[1] = g; + pd[2] = b; + } + if ((r == redTransparentAlpha) && + (g == greenTransparentAlpha) && + (b == blueTransparentAlpha)) { + pd[3] = 0; + } else { + pd[3] = maxOpacity; + } + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_REMOVE_GRAY_TRANS: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int g = ps[0]; + if (performGammaCorrection) { + pd[0] = gammaLut[g]; + } else { + pd[0] = g; + } + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_REMOVE_RGB_TRANS: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int r = ps[0]; + int g = ps[1]; + int b = ps[2]; + if (performGammaCorrection) { + pd[0] = gammaLut[r]; + pd[1] = gammaLut[g]; + pd[2] = gammaLut[b]; + } else { + pd[0] = r; + pd[1] = g; + pd[2] = b; + } + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_GAMMA_EXP: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + int alpha = ps[1]; + int gamma = gammaLut[val]; + pd[0] = gamma; + pd[1] = gamma; + pd[2] = gamma; + pd[3] = alpha; + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_GRAY_ALPHA_EXP: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + int alpha = ps[1]; + pd[0] = val; + pd[1] = val; + pd[2] = val; + pd[3] = alpha; + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_ADD_GRAY_TRANS_EXP: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + if (performGammaCorrection) { + val = gammaLut[val]; + } + pd[0] = val; + pd[1] = val; + pd[2] = val; + if (val == grayTransparentAlpha) { + pd[3] = 0; + } else { + pd[3] = maxOpacity; + } + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_GRAY_LUT_ADD_TRANS_EXP: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + int val2 = grayLut[val]; + pd[0] = val2; + pd[1] = val2; + pd[2] = val2; + if (val == grayTransparentAlpha) { + pd[3] = 0; + } else { + pd[3] = maxOpacity; + } + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + } + } + + /** + * Reads in an image of a given size and returns it as a + * WritableRaster. + */ + private void decodePass(WritableRaster imRas, + int xOffset, int yOffset, + int xStep, int yStep, + int passWidth, int passHeight) { + if ((passWidth == 0) || (passHeight == 0)) { + return; + } + + int bytesPerRow = (inputBands*passWidth*bitDepth + 7)/8; + int eltsPerRow = (bitDepth == 16) ? bytesPerRow/2 : bytesPerRow; + byte[] curr = new byte[bytesPerRow]; + byte[] prior = new byte[bytesPerRow]; + + // Create a 1-row tall Raster to hold the data + WritableRaster passRow = + createRaster(passWidth, 1, inputBands, + eltsPerRow, + bitDepth); + DataBuffer dataBuffer = passRow.getDataBuffer(); + int type = dataBuffer.getDataType(); + byte[] byteData = null; + short[] shortData = null; + if (type == DataBuffer.TYPE_BYTE) { + byteData = ((DataBufferByte)dataBuffer).getData(); + } else { + shortData = ((DataBufferUShort)dataBuffer).getData(); + } + + // Decode the (sub)image row-by-row + int srcY, dstY; + for (srcY = 0, dstY = yOffset; + srcY < passHeight; + srcY++, dstY += yStep) { + // Read the filter type byte and a row of data + int filter = 0; + try { + filter = dataStream.read(); + dataStream.readFully(curr, 0, bytesPerRow); + } catch (Exception e) { + e.printStackTrace(); + } + + switch (filter) { + case PNG_FILTER_NONE: + break; + case PNG_FILTER_SUB: + decodeSubFilter(curr, bytesPerRow, bytesPerPixel); + break; + case PNG_FILTER_UP: + decodeUpFilter(curr, prior, bytesPerRow); + break; + case PNG_FILTER_AVERAGE: + decodeAverageFilter(curr, prior, bytesPerRow, bytesPerPixel); + break; + case PNG_FILTER_PAETH: + decodePaethFilter(curr, prior, bytesPerRow, bytesPerPixel); + break; + default: + // Error -- uknown filter type + String msg = PropertyUtil.getString("PNGImageDecoder16"); + throw new RuntimeException(msg); + } + + // Copy data into passRow byte by byte + if (bitDepth < 16) { + System.arraycopy(curr, 0, byteData, 0, bytesPerRow); + } else { + int idx = 0; + for (int j = 0; j < eltsPerRow; j++) { + shortData[j] = + (short)((curr[idx] << 8) | (curr[idx + 1] & 0xff)); + idx += 2; + } + } + + processPixels(postProcess, + passRow, imRas, xOffset, xStep, dstY, passWidth); + + // Swap curr and prior + byte[] tmp = prior; + prior = curr; + curr = tmp; + } + } + + private void decodeImage(boolean useInterlacing) { + if (!useInterlacing) { + decodePass(theTile, 0, 0, 1, 1, width, height); + } else { + decodePass(theTile, 0, 0, 8, 8, (width + 7)/8, (height + 7)/8); + decodePass(theTile, 4, 0, 8, 8, (width + 3)/8, (height + 7)/8); + decodePass(theTile, 0, 4, 4, 8, (width + 3)/4, (height + 3)/8); + decodePass(theTile, 2, 0, 4, 4, (width + 1)/4, (height + 3)/4); + decodePass(theTile, 0, 2, 2, 4, (width + 1)/2, (height + 1)/4); + decodePass(theTile, 1, 0, 2, 2, width/2, (height + 1)/2); + decodePass(theTile, 0, 1, 1, 2, width, height/2); + } + } + + // RenderedImage stuff + + public Raster getTile(int tileX, int tileY) { + if (tileX != 0 || tileY != 0) { + // Error -- bad tile requested + String msg = PropertyUtil.getString("PNGImageDecoder17"); + throw new IllegalArgumentException(msg); + } + return theTile; + } +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/PNGImageEncoder.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/PNGImageEncoder.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/PNGImageEncoder.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/PNGImageEncoder.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,998 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.awt.Rectangle; +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.io.ByteArrayOutputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +class CRC { + + private static int[] crcTable = new int[256]; + + static { + // Initialize CRC table + for (int n = 0; n < 256; n++) { + int c = n; + for (int k = 0; k < 8; k++) { + if ((c & 1) == 1) { + c = 0xedb88320 ^ (c >>> 1); + } else { + c >>>= 1; + } + + crcTable[n] = c; + } + } + } + + public static int updateCRC(int crc, byte[] data, int off, int len) { + int c = crc; + + for (int n = 0; n < len; n++) { + c = crcTable[(c ^ data[off + n]) & 0xff] ^ (c >>> 8); + } + + return c; + } +} + + +class ChunkStream extends OutputStream implements DataOutput { + + private String type; + private ByteArrayOutputStream baos; + private DataOutputStream dos; + + public ChunkStream(String type) throws IOException { + this.type = type; + + this.baos = new ByteArrayOutputStream(); + this.dos = new DataOutputStream(baos); + } + + public void write(byte[] b) throws IOException { + dos.write(b); + } + + public void write(byte[] b, int off, int len) throws IOException { + dos.write(b, off, len); + } + + public void write(int b) throws IOException { + dos.write(b); + } + + public void writeBoolean(boolean v) throws IOException { + dos.writeBoolean(v); + } + + public void writeByte(int v) throws IOException { + dos.writeByte(v); + } + + public void writeBytes(String s) throws IOException { + dos.writeBytes(s); + } + + public void writeChar(int v) throws IOException { + dos.writeChar(v); + } + + public void writeChars(String s) throws IOException { + dos.writeChars(s); + } + + public void writeDouble(double v) throws IOException { + dos.writeDouble(v); + } + + public void writeFloat(float v) throws IOException { + dos.writeFloat(v); + } + + public void writeInt(int v) throws IOException { + dos.writeInt(v); + } + + public void writeLong(long v) throws IOException { + dos.writeLong(v); + } + + public void writeShort(int v) throws IOException { + dos.writeShort(v); + } + + public void writeUTF(String str) throws IOException { + dos.writeUTF(str); + } + + public void writeToStream(DataOutputStream output) throws IOException { + byte[] typeSignature = new byte[4]; + typeSignature[0] = (byte)type.charAt(0); + typeSignature[1] = (byte)type.charAt(1); + typeSignature[2] = (byte)type.charAt(2); + typeSignature[3] = (byte)type.charAt(3); + + dos.flush(); + baos.flush(); + + byte[] data = baos.toByteArray(); + int len = data.length; + + output.writeInt(len); + output.write(typeSignature); + output.write(data, 0, len); + + int crc = 0xffffffff; + crc = CRC.updateCRC(crc, typeSignature, 0, 4); + crc = CRC.updateCRC(crc, data, 0, len); + output.writeInt(crc ^ 0xffffffff); + } +} + + +class IDATOutputStream extends FilterOutputStream { + + private static final byte[] typeSignature = + {(byte)'I', (byte)'D', (byte)'A', (byte)'T'}; + + private int bytesWritten = 0; + private int segmentLength; + byte[] buffer; + + public IDATOutputStream(OutputStream output, + int segmentLength) { + super(output); + this.segmentLength = segmentLength; + this.buffer = new byte[segmentLength]; + } + + public void close() throws IOException { + flush(); + } + + private void writeInt(int x) throws IOException { + out.write(x >> 24); + out.write((x >> 16) & 0xff); + out.write((x >> 8) & 0xff); + out.write(x & 0xff); + } + + public void flush() throws IOException { + // Length + writeInt(bytesWritten); + // 'IDAT' signature + out.write(typeSignature); + // Data + out.write(buffer, 0, bytesWritten); + + int crc = 0xffffffff; + crc = CRC.updateCRC(crc, typeSignature, 0, 4); + crc = CRC.updateCRC(crc, buffer, 0, bytesWritten); + + // CRC + writeInt(crc ^ 0xffffffff); + + // Reset buffer + bytesWritten = 0; + } + + public void write(byte[] b) throws IOException { + this.write(b, 0, b.length); + } + + public void write(byte[] b, int off, int len) throws IOException { + while (len > 0) { + int bytes = Math.min(segmentLength - bytesWritten, len); + System.arraycopy(b, off, buffer, bytesWritten, bytes); + off += bytes; + len -= bytes; + bytesWritten += bytes; + + if (bytesWritten == segmentLength) { + flush(); + } + } + } + + public void write(int b) throws IOException { + buffer[bytesWritten++] = (byte)b; + if (bytesWritten == segmentLength) { + flush(); + } + } +} + +/** + * An ImageEncoder for the PNG file format. + * + * @since EA4 + */ +public class PNGImageEncoder extends ImageEncoderImpl { + + private static final int PNG_COLOR_GRAY = 0; + private static final int PNG_COLOR_RGB = 2; + private static final int PNG_COLOR_PALETTE = 3; + private static final int PNG_COLOR_GRAY_ALPHA = 4; + private static final int PNG_COLOR_RGB_ALPHA = 6; + + private static final byte[] magic = { + (byte)137, (byte) 80, (byte) 78, (byte) 71, + (byte) 13, (byte) 10, (byte) 26, (byte) 10 + }; + + private PNGEncodeParam param; + + private RenderedImage image; + private int width; + private int height; + private int bitDepth; + private int bitShift; + private int numBands; + private int colorType; + + private int bpp; // bytes per pixel, rounded up + + private boolean skipAlpha = false; + private boolean compressGray = false; + + private boolean interlace; + + private byte[] redPalette = null; + private byte[] greenPalette = null; + private byte[] bluePalette = null; + private byte[] alphaPalette = null; + + private DataOutputStream dataOutput; + + public PNGImageEncoder(OutputStream output, + PNGEncodeParam param) { + super(output, param); + + if (param != null) { + this.param = param; + } + this.dataOutput = new DataOutputStream(output); + } + + private void writeMagic() throws IOException { + dataOutput.write(magic); + } + + private void writeIHDR() throws IOException { + ChunkStream cs = new ChunkStream("IHDR"); + cs.writeInt(width); + cs.writeInt(height); + cs.writeByte((byte)bitDepth); + cs.writeByte((byte)colorType); + cs.writeByte((byte)0); + cs.writeByte((byte)0); + cs.writeByte(interlace ? (byte)1 : (byte)0); + + cs.writeToStream(dataOutput); + } + + private byte[] prevRow = null; + private byte[] currRow = null; + + private byte[][] filteredRows = null; + + private static int clamp(int val, int maxValue) { + return (val > maxValue) ? maxValue : val; + } + + private void encodePass(OutputStream os, Raster ras, + int xOffset, int yOffset, + int xSkip, int ySkip) + throws IOException { + int minX = ras.getMinX(); + int minY = ras.getMinY(); + int width = ras.getWidth(); + int height = ras.getHeight(); + + xOffset *= numBands; + xSkip *= numBands; + + int samplesPerByte = 8/bitDepth; + + int numSamples = width*numBands; + int[] samples = new int[numSamples]; + + int pixels = (numSamples - xOffset + xSkip - 1)/xSkip; + int bytesPerRow = pixels*numBands; + if (bitDepth < 8) { + bytesPerRow = (bytesPerRow + samplesPerByte - 1)/samplesPerByte; + } else if (bitDepth == 16) { + bytesPerRow *= 2; + } + + if (bytesPerRow == 0) { + return; + } + + currRow = new byte[bytesPerRow + bpp]; + prevRow = new byte[bytesPerRow + bpp]; + + filteredRows = new byte[5][bytesPerRow + bpp]; + + int maxValue = (1 << bitDepth) - 1; + + for (int row = minY + yOffset; row < minY + height; row += ySkip) { + ras.getPixels(minX, row, width, 1, samples); + + if (compressGray) { + int shift = 8 - bitDepth; + for (int i = 0; i < width; i++) { + samples[i] >>= shift; + } + } + + int count = bpp; // leave first 'bpp' bytes zero + int pos = 0; + int tmp = 0; + + switch (bitDepth) { + case 1: case 2: case 4: + // Image can only have a single band + + int mask = samplesPerByte - 1; + for (int s = xOffset; s < numSamples; s += xSkip) { + int val = clamp(samples[s] >> bitShift, maxValue); + tmp = (tmp << bitDepth) | val; + + if (pos++ == mask) { + currRow[count++] = (byte)tmp; + tmp = 0; + pos = 0; + } + } + + // Left shift the last byte + if (pos != 0) { + tmp <<= (samplesPerByte - pos)*bitDepth; + currRow[count++] = (byte)tmp; + } + break; + + case 8: + for (int s = xOffset; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + currRow[count++] = + (byte)clamp(samples[s + b] >> bitShift, maxValue); + } + } + break; + + case 16: + for (int s = xOffset; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + int val = clamp(samples[s + b] >> bitShift, maxValue); + currRow[count++] = (byte)(val >> 8); + currRow[count++] = (byte)(val & 0xff); + } + } + break; + } + + // Perform filtering + int filterType = param.filterRow(currRow, prevRow, + filteredRows, + bytesPerRow, bpp); + + os.write(filterType); + os.write(filteredRows[filterType], bpp, bytesPerRow); + + // Swap current and previous rows + byte[] swap = currRow; + currRow = prevRow; + prevRow = swap; + } + } + + private void writeIDAT() throws IOException { + IDATOutputStream ios = new IDATOutputStream(dataOutput, 8192); + DeflaterOutputStream dos = + new DeflaterOutputStream(ios, new Deflater(9)); + + // Future work - don't convert entire image to a Raster It + // might seem that you could just call image.getData() but + // 'BufferedImage.subImage' doesn't appear to set the Width + // and height properly of the Child Raster, so the Raster + // you get back here appears larger than it should. + // This solves that problem by bounding the raster to the + // image's bounds... + Raster ras = image.getData(new Rectangle(image.getMinX(), + image.getMinY(), + image.getWidth(), + image.getHeight())); + // System.out.println("Image: [" + + // image.getMinY() + ", " + + // image.getMinX() + ", " + + // image.getWidth() + ", " + + // image.getHeight() + "]"); + // System.out.println("Ras: [" + + // ras.getMinX() + ", " + + // ras.getMinY() + ", " + + // ras.getWidth() + ", " + + // ras.getHeight() + "]"); + + if (skipAlpha) { + int numBands = ras.getNumBands() - 1; + int[] bandList = new int[numBands]; + for (int i = 0; i < numBands; i++) { + bandList[i] = i; + } + ras = ras.createChild(0, 0, + ras.getWidth(), ras.getHeight(), + 0, 0, + bandList); + } + + if (interlace) { + // Interlacing pass 1 + encodePass(dos, ras, 0, 0, 8, 8); + // Interlacing pass 2 + encodePass(dos, ras, 4, 0, 8, 8); + // Interlacing pass 3 + encodePass(dos, ras, 0, 4, 4, 8); + // Interlacing pass 4 + encodePass(dos, ras, 2, 0, 4, 4); + // Interlacing pass 5 + encodePass(dos, ras, 0, 2, 2, 4); + // Interlacing pass 6 + encodePass(dos, ras, 1, 0, 2, 2); + // Interlacing pass 7 + encodePass(dos, ras, 0, 1, 1, 2); + } else { + encodePass(dos, ras, 0, 0, 1, 1); + } + + dos.finish(); + ios.flush(); + } + + private void writeIEND() throws IOException { + ChunkStream cs = new ChunkStream("IEND"); + cs.writeToStream(dataOutput); + } + + private static final float[] srgbChroma = { + 0.31270F, 0.329F, 0.64F, 0.33F, 0.3F, 0.6F, 0.15F, 0.06F + }; + + private void writeCHRM() throws IOException { + if (param.isChromaticitySet() || param.isSRGBIntentSet()) { + ChunkStream cs = new ChunkStream("cHRM"); + + float[] chroma; + if (!param.isSRGBIntentSet()) { + chroma = param.getChromaticity(); + } else { + chroma = srgbChroma; // SRGB chromaticities + } + + for (int i = 0; i < 8; i++) { + cs.writeInt((int)(chroma[i]*100000)); + } + cs.writeToStream(dataOutput); + } + } + + private void writeGAMA() throws IOException { + if (param.isGammaSet() || param.isSRGBIntentSet()) { + ChunkStream cs = new ChunkStream("gAMA"); + + float gamma; + if (!param.isSRGBIntentSet()) { + gamma = param.getGamma(); + } else { + gamma = 1.0F/2.2F; // SRGB gamma + } + // TD should include the .5 but causes regard to say + // everything is different. + cs.writeInt((int)(gamma*100000/*+0.5*/)); + cs.writeToStream(dataOutput); + } + } + + private void writeICCP() throws IOException { + if (param.isICCProfileDataSet()) { + ChunkStream cs = new ChunkStream("iCCP"); + byte[] ICCProfileData = param.getICCProfileData(); + cs.write(ICCProfileData); + cs.writeToStream(dataOutput); + } + } + + private void writeSBIT() throws IOException { + if (param.isSignificantBitsSet()) { + ChunkStream cs = new ChunkStream("sBIT"); + int[] significantBits = param.getSignificantBits(); + int len = significantBits.length; + for (int i = 0; i < len; i++) { + cs.writeByte(significantBits[i]); + } + cs.writeToStream(dataOutput); + } + } + + private void writeSRGB() throws IOException { + if (param.isSRGBIntentSet()) { + ChunkStream cs = new ChunkStream("sRGB"); + + int intent = param.getSRGBIntent(); + cs.write(intent); + cs.writeToStream(dataOutput); + } + } + + private void writePLTE() throws IOException { + if (redPalette == null) { + return; + } + + ChunkStream cs = new ChunkStream("PLTE"); + for (int i = 0; i < redPalette.length; i++) { + cs.writeByte(redPalette[i]); + cs.writeByte(greenPalette[i]); + cs.writeByte(bluePalette[i]); + } + + cs.writeToStream(dataOutput); + } + + private void writeBKGD() throws IOException { + if (param.isBackgroundSet()) { + ChunkStream cs = new ChunkStream("bKGD"); + + switch (colorType) { + case PNG_COLOR_GRAY: + case PNG_COLOR_GRAY_ALPHA: + int gray = ((PNGEncodeParam.Gray)param).getBackgroundGray(); + cs.writeShort(gray); + break; + + case PNG_COLOR_PALETTE: + int index = + ((PNGEncodeParam.Palette)param).getBackgroundPaletteIndex(); + cs.writeByte(index); + break; + + case PNG_COLOR_RGB: + case PNG_COLOR_RGB_ALPHA: + int[] rgb = ((PNGEncodeParam.RGB)param).getBackgroundRGB(); + cs.writeShort(rgb[0]); + cs.writeShort(rgb[1]); + cs.writeShort(rgb[2]); + break; + } + + cs.writeToStream(dataOutput); + } + } + + private void writeHIST() throws IOException { + if (param.isPaletteHistogramSet()) { + ChunkStream cs = new ChunkStream("hIST"); + + int[] hist = param.getPaletteHistogram(); + for (int i = 0; i < hist.length; i++) { + cs.writeShort(hist[i]); + } + + cs.writeToStream(dataOutput); + } + } + + private void writeTRNS() throws IOException { + if (param.isTransparencySet() && + (colorType != PNG_COLOR_GRAY_ALPHA) && + (colorType != PNG_COLOR_RGB_ALPHA)) { + ChunkStream cs = new ChunkStream("tRNS"); + + if (param instanceof PNGEncodeParam.Palette) { + byte[] t = + ((PNGEncodeParam.Palette)param).getPaletteTransparency(); + for (int i = 0; i < t.length; i++) { + cs.writeByte(t[i]); + } + } else if (param instanceof PNGEncodeParam.Gray) { + int t = ((PNGEncodeParam.Gray)param).getTransparentGray(); + cs.writeShort(t); + } else if (param instanceof PNGEncodeParam.RGB) { + int[] t = ((PNGEncodeParam.RGB)param).getTransparentRGB(); + cs.writeShort(t[0]); + cs.writeShort(t[1]); + cs.writeShort(t[2]); + } + + cs.writeToStream(dataOutput); + } else if (colorType == PNG_COLOR_PALETTE) { + int lastEntry = Math.min(255, alphaPalette.length - 1); + int nonOpaque; + for (nonOpaque = lastEntry; nonOpaque >= 0; nonOpaque--) { + if (alphaPalette[nonOpaque] != (byte)255) { + break; + } + } + + if (nonOpaque >= 0) { + ChunkStream cs = new ChunkStream("tRNS"); + for (int i = 0; i <= nonOpaque; i++) { + cs.writeByte(alphaPalette[i]); + } + cs.writeToStream(dataOutput); + } + } + } + + private void writePHYS() throws IOException { + if (param.isPhysicalDimensionSet()) { + ChunkStream cs = new ChunkStream("pHYs"); + + int[] dims = param.getPhysicalDimension(); + cs.writeInt(dims[0]); + cs.writeInt(dims[1]); + cs.writeByte((byte)dims[2]); + + cs.writeToStream(dataOutput); + } + } + + private void writeSPLT() throws IOException { + if (param.isSuggestedPaletteSet()) { + ChunkStream cs = new ChunkStream("sPLT"); + + System.out.println("sPLT not supported yet."); + + cs.writeToStream(dataOutput); + } + } + + private void writeTIME() throws IOException { + if (param.isModificationTimeSet()) { + ChunkStream cs = new ChunkStream("tIME"); + + Date date = param.getModificationTime(); + TimeZone gmt = TimeZone.getTimeZone("GMT"); + + GregorianCalendar cal = new GregorianCalendar(gmt); + cal.setTime(date); + + int year = cal.get(Calendar.YEAR); + int month = cal.get(Calendar.MONTH); + int day = cal.get(Calendar.DAY_OF_MONTH); + int hour = cal.get(Calendar.HOUR_OF_DAY); + int minute = cal.get(Calendar.MINUTE); + int second = cal.get(Calendar.SECOND); + + cs.writeShort(year); + cs.writeByte(month + 1); + cs.writeByte(day); + cs.writeByte(hour); + cs.writeByte(minute); + cs.writeByte(second); + + cs.writeToStream(dataOutput); + } + } + + private void writeTEXT() throws IOException { + if (param.isTextSet()) { + String[] text = param.getText(); + + for (int i = 0; i < text.length/2; i++) { + byte[] keyword = text[2*i].getBytes(); + byte[] value = text[2*i + 1].getBytes(); + + ChunkStream cs = new ChunkStream("tEXt"); + + cs.write(keyword, 0, Math.min(keyword.length, 79)); + cs.write(0); + cs.write(value); + + cs.writeToStream(dataOutput); + } + } + } + + private void writeZTXT() throws IOException { + if (param.isCompressedTextSet()) { + String[] text = param.getCompressedText(); + + for (int i = 0; i < text.length/2; i++) { + byte[] keyword = text[2*i].getBytes(); + byte[] value = text[2*i + 1].getBytes(); + + ChunkStream cs = new ChunkStream("zTXt"); + + cs.write(keyword, 0, Math.min(keyword.length, 79)); + cs.write(0); + cs.write(0); + + DeflaterOutputStream dos = new DeflaterOutputStream(cs); + dos.write(value); + dos.finish(); + + cs.writeToStream(dataOutput); + } + } + } + + private void writePrivateChunks() throws IOException { + int numChunks = param.getNumPrivateChunks(); + for (int i = 0; i < numChunks; i++) { + String type = param.getPrivateChunkType(i); + byte[] data = param.getPrivateChunkData(i); + + ChunkStream cs = new ChunkStream(type); + cs.write(data); + cs.writeToStream(dataOutput); + } + } + + /** + * Analyzes a set of palettes and determines if it can be expressed + * as a standard set of gray values, with zero or one values being + * fully transparent and the rest being fully opaque. If it + * is possible to express the data thusly, the method returns + * a suitable instance of PNGEncodeParam.Gray; otherwise it + * returns null. + */ + private PNGEncodeParam.Gray createGrayParam(byte[] redPalette, + byte[] greenPalette, + byte[] bluePalette, + byte[] alphaPalette) { + PNGEncodeParam.Gray param = new PNGEncodeParam.Gray(); + int numTransparent = 0; + + int grayFactor = 255/((1 << bitDepth) - 1); + int entries = 1 << bitDepth; + for (int i = 0; i < entries; i++) { + byte red = redPalette[i]; + if ((red != i*grayFactor) || + (red != greenPalette[i]) || + (red != bluePalette[i])) { + return null; + } + + // All alphas must be 255 except at most 1 can be 0 + byte alpha = alphaPalette[i]; + if (alpha == (byte)0) { + param.setTransparentGray(i); + + ++numTransparent; + if (numTransparent > 1) { + return null; + } + } else if (alpha != (byte)255) { + return null; + } + } + + return param; + } + + /** + * This method encodes a RenderedImage into PNG. + * The stream into which the PNG is dumped is not closed at + * the end of the operation, this should be done if needed + * by the caller of this method. + */ + public void encode(RenderedImage im) throws IOException { + this.image = im; + this.width = image.getWidth(); + this.height = image.getHeight(); + + SampleModel sampleModel = image.getSampleModel(); + + int[] sampleSize = sampleModel.getSampleSize(); + + // Set bitDepth to a sentinel value + this.bitDepth = -1; + this.bitShift = 0; + + // Allow user to override the bit depth of gray images + if (param instanceof PNGEncodeParam.Gray) { + PNGEncodeParam.Gray paramg = (PNGEncodeParam.Gray)param; + if (paramg.isBitDepthSet()) { + this.bitDepth = paramg.getBitDepth(); + } + + if (paramg.isBitShiftSet()) { + this.bitShift = paramg.getBitShift(); + } + } + + // Get bit depth from image if not set in param + if (this.bitDepth == -1) { + // Get bit depth from channel 0 of the image + + this.bitDepth = sampleSize[0]; + // Ensure all channels have the same bit depth + for (int i = 1; i < sampleSize.length; i++) { + if (sampleSize[i] != bitDepth) { + throw new RuntimeException(); + } + } + + // Round bit depth up to a power of 2 + if (bitDepth > 2 && bitDepth < 4) { + bitDepth = 4; + } else if (bitDepth > 4 && bitDepth < 8) { + bitDepth = 8; + } else if (bitDepth > 8 && bitDepth < 16) { + bitDepth = 16; + } else if (bitDepth > 16) { + throw new RuntimeException(); + } + } + + this.numBands = sampleModel.getNumBands(); + this.bpp = numBands*((bitDepth == 16) ? 2 : 1); + + ColorModel colorModel = image.getColorModel(); + if (colorModel instanceof IndexColorModel) { + if (bitDepth < 1 || bitDepth > 8) { + throw new RuntimeException(); + } + if (sampleModel.getNumBands() != 1) { + throw new RuntimeException(); + } + + IndexColorModel icm = (IndexColorModel)colorModel; + int size = icm.getMapSize(); + + redPalette = new byte[size]; + greenPalette = new byte[size]; + bluePalette = new byte[size]; + alphaPalette = new byte[size]; + + icm.getReds(redPalette); + icm.getGreens(greenPalette); + icm.getBlues(bluePalette); + icm.getAlphas(alphaPalette); + + this.bpp = 1; + + if (param == null) { + param = createGrayParam(redPalette, + greenPalette, + bluePalette, + alphaPalette); + } + + // If param is still null, it can't be expressed as gray + if (param == null) { + param = new PNGEncodeParam.Palette(); + } + + if (param instanceof PNGEncodeParam.Palette) { + // If palette not set in param, create one from the ColorModel. + PNGEncodeParam.Palette parami = (PNGEncodeParam.Palette)param; + if (parami.isPaletteSet()) { + int[] palette = parami.getPalette(); + size = palette.length/3; + + int index = 0; + for (int i = 0; i < size; i++) { + redPalette[i] = (byte)palette[index++]; + greenPalette[i] = (byte)palette[index++]; + bluePalette[i] = (byte)palette[index++]; + alphaPalette[i] = (byte)255; + } + } + this.colorType = PNG_COLOR_PALETTE; + } else if (param instanceof PNGEncodeParam.Gray) { + redPalette = greenPalette = bluePalette = alphaPalette = null; + this.colorType = PNG_COLOR_GRAY; + } else { + throw new RuntimeException(); + } + } else if (numBands == 1) { + if (param == null) { + param = new PNGEncodeParam.Gray(); + } + this.colorType = PNG_COLOR_GRAY; + } else if (numBands == 2) { + if (param == null) { + param = new PNGEncodeParam.Gray(); + } + + if (param.isTransparencySet()) { + skipAlpha = true; + numBands = 1; + if ((sampleSize[0] == 8) && (bitDepth < 8)) { + compressGray = true; + } + bpp = (bitDepth == 16) ? 2 : 1; + this.colorType = PNG_COLOR_GRAY; + } else { + if (this.bitDepth < 8) { + this.bitDepth = 8; + } + this.colorType = PNG_COLOR_GRAY_ALPHA; + } + } else if (numBands == 3) { + if (param == null) { + param = new PNGEncodeParam.RGB(); + } + this.colorType = PNG_COLOR_RGB; + } else if (numBands == 4) { + if (param == null) { + param = new PNGEncodeParam.RGB(); + } + if (param.isTransparencySet()) { + skipAlpha = true; + numBands = 3; + bpp = (bitDepth == 16) ? 6 : 3; + this.colorType = PNG_COLOR_RGB; + } else { + this.colorType = PNG_COLOR_RGB_ALPHA; + } + } + + interlace = param.getInterlacing(); + + writeMagic(); + + writeIHDR(); + + writeCHRM(); + writeGAMA(); + writeICCP(); + writeSBIT(); + writeSRGB(); + + writePLTE(); + + writeHIST(); + writeTRNS(); + writeBKGD(); + + writePHYS(); + writeSPLT(); + writeTIME(); + writeTEXT(); + writeZTXT(); + + writePrivateChunks(); + + writeIDAT(); + + writeIEND(); + + dataOutput.flush(); + } +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/PNGRed.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/PNGRed.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/PNGRed.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/PNGRed.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,1860 @@ +/* + + Copyright 2001-2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.awt.Color; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferUShort; +import java.awt.image.IndexColorModel; +import java.awt.image.Raster; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.SequenceInputStream; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Hashtable; +import java.util.TimeZone; +import java.util.Vector; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +import org.apache.batik.ext.awt.image.GraphicsUtil; +import org.apache.batik.ext.awt.image.rendered.AbstractRed; +import org.apache.batik.ext.awt.image.rendered.CachableRed; + +public class PNGRed extends AbstractRed { + + static class PNGChunk { + int length; + int type; + byte[] data; + int crc; + + String typeString; + + public PNGChunk(int length, int type, byte[] data, int crc) { + this.length = length; + this.type = type; + this.data = data; + this.crc = crc; + + typeString = new String(); + typeString += (char)(type >> 24); + typeString += (char)((type >> 16) & 0xff); + typeString += (char)((type >> 8) & 0xff); + typeString += (char)(type & 0xff); + } + + public int getLength() { + return length; + } + + public int getType() { + return type; + } + + public String getTypeString() { + return typeString; + } + + public byte[] getData() { + return data; + } + + public byte getByte(int offset) { + return data[offset]; + } + + public int getInt1(int offset) { + return data[offset] & 0xff; + } + + public int getInt2(int offset) { + return ((data[offset] & 0xff) << 8) | + (data[offset + 1] & 0xff); + } + + public int getInt4(int offset) { + return ((data[offset] & 0xff) << 24) | + ((data[offset + 1] & 0xff) << 16) | + ((data[offset + 2] & 0xff) << 8) | + (data[offset + 3] & 0xff); + } + + public String getString4(int offset) { + String s = new String(); + s += (char)data[offset]; + s += (char)data[offset + 1]; + s += (char)data[offset + 2]; + s += (char)data[offset + 3]; + return s; + } + + public boolean isType(String typeName) { + return typeString.equals(typeName); + } + } + + public static final int PNG_COLOR_GRAY = 0; + public static final int PNG_COLOR_RGB = 2; + public static final int PNG_COLOR_PALETTE = 3; + public static final int PNG_COLOR_GRAY_ALPHA = 4; + public static final int PNG_COLOR_RGB_ALPHA = 6; + + private static final String[] colorTypeNames = { + "Grayscale", "Error", "Truecolor", "Index", + "Grayscale with alpha", "Error", "Truecolor with alpha" + }; + + public static final int PNG_FILTER_NONE = 0; + public static final int PNG_FILTER_SUB = 1; + public static final int PNG_FILTER_UP = 2; + public static final int PNG_FILTER_AVERAGE = 3; + public static final int PNG_FILTER_PAETH = 4; + + private static final int RED_OFFSET = 2; + private static final int GREEN_OFFSET = 1; + private static final int BLUE_OFFSET = 0; + + private int[][] bandOffsets = { + null, + { 0 }, // G + { 0, 1 }, // GA in GA order + { 0, 1, 2 }, // RGB in RGB order + { 0, 1, 2, 3 } // RGBA in RGBA order + }; + + private int bitDepth; + private int colorType; + + private int compressionMethod; + private int filterMethod; + private int interlaceMethod; + + private int paletteEntries; + private byte[] redPalette; + private byte[] greenPalette; + private byte[] bluePalette; + private byte[] alphaPalette; + + private int bkgdRed; + private int bkgdGreen; + private int bkgdBlue; + + private int grayTransparentAlpha; + private int redTransparentAlpha; + private int greenTransparentAlpha; + private int blueTransparentAlpha; + + private int maxOpacity; + + private int[] significantBits = null; + + private boolean hasBackground = false; + + // Parameter information + + // If true, the user wants destination alpha where applicable. + private boolean suppressAlpha = false; + + // If true, perform palette lookup internally + private boolean expandPalette = false; + + // If true, output < 8 bit gray images in 8 bit components format + private boolean output8BitGray = false; + + // Create an alpha channel in the destination color model. + private boolean outputHasAlphaPalette = false; + + // Perform gamma correction on the image + private boolean performGammaCorrection = false; + + // Expand GA to GGGA for compatbility with Java2D + private boolean expandGrayAlpha = false; + + // Produce an instance of PNGEncodeParam + private boolean generateEncodeParam = false; + + // PNGDecodeParam controlling decode process + private PNGDecodeParam decodeParam = null; + + // PNGEncodeParam to store file details in + private PNGEncodeParam encodeParam = null; + + private boolean emitProperties = true; + + private float fileGamma = 45455/100000.0F; + + private float userExponent = 1.0F; + + private float displayExponent = 2.2F; + + private float[] chromaticity = null; + + private int sRGBRenderingIntent = -1; + + // Post-processing step implied by above parameters + private int postProcess = POST_NONE; + + // Possible post-processing steps + + // Do nothing + private static final int POST_NONE = 0; + + // Gamma correct only + private static final int POST_GAMMA = 1; + + // Push gray values through grayLut to expand to 8 bits + private static final int POST_GRAY_LUT = 2; + + // Push gray values through grayLut to expand to 8 bits, add alpha + private static final int POST_GRAY_LUT_ADD_TRANS = 3; + + // Push palette value through R,G,B lookup tables + private static final int POST_PALETTE_TO_RGB = 4; + + // Push palette value through R,G,B,A lookup tables + private static final int POST_PALETTE_TO_RGBA = 5; + + // Add transparency to a given gray value (w/ optional gamma) + private static final int POST_ADD_GRAY_TRANS = 6; + + // Add transparency to a given RGB value (w/ optional gamma) + private static final int POST_ADD_RGB_TRANS = 7; + + // Remove the alpha channel from a gray image (w/ optional gamma) + private static final int POST_REMOVE_GRAY_TRANS = 8; + + // Remove the alpha channel from an RGB image (w/optional gamma) + private static final int POST_REMOVE_RGB_TRANS = 9; + + // Mask to add expansion of GA -> GGGA + private static final int POST_EXP_MASK = 16; + + // Expand gray to G/G/G + private static final int POST_GRAY_ALPHA_EXP = + POST_NONE | POST_EXP_MASK; + + // Expand gray to G/G/G through a gamma lut + private static final int POST_GAMMA_EXP = + POST_GAMMA | POST_EXP_MASK; + + // Push gray values through grayLut to expand to 8 bits, expand, add alpha + private static final int POST_GRAY_LUT_ADD_TRANS_EXP = + POST_GRAY_LUT_ADD_TRANS | POST_EXP_MASK; + + // Add transparency to a given gray value, expand + private static final int POST_ADD_GRAY_TRANS_EXP = + POST_ADD_GRAY_TRANS | POST_EXP_MASK; + + private Vector streamVec = new Vector(); + private DataInputStream dataStream; + + private int bytesPerPixel; // number of bytes per input pixel + private int inputBands; + private int outputBands; + + // Number of private chunks + private int chunkIndex = 0; + + private Vector textKeys = new Vector(); + private Vector textStrings = new Vector(); + + private Vector ztextKeys = new Vector(); + private Vector ztextStrings = new Vector(); + + private WritableRaster theTile; + private Rectangle bounds; + /** A Hashtable containing the image properties. */ + private Hashtable properties = new Hashtable(); + + + private int[] gammaLut = null; + + private void initGammaLut(int bits) { + double exp = (double)userExponent/(fileGamma*displayExponent); + int numSamples = 1 << bits; + int maxOutSample = (bits == 16) ? 65535 : 255; + + gammaLut = new int[numSamples]; + for (int i = 0; i < numSamples; i++) { + double gbright = (double)i/(numSamples - 1); + double gamma = Math.pow(gbright, exp); + int igamma = (int)(gamma*maxOutSample + 0.5); + if (igamma > maxOutSample) { + igamma = maxOutSample; + } + gammaLut[i] = igamma; + } + } + + private final byte[][] expandBits = { + null, + { (byte)0x00, (byte)0xff }, + { (byte)0x00, (byte)0x55, (byte)0xaa, (byte)0xff }, + null, + { (byte)0x00, (byte)0x11, (byte)0x22, (byte)0x33, + (byte)0x44, (byte)0x55, (byte)0x66, (byte)0x77, + (byte)0x88, (byte)0x99, (byte)0xaa, (byte)0xbb, + (byte)0xcc, (byte)0xdd, (byte)0xee, (byte)0xff } + }; + + private int[] grayLut = null; + + private void initGrayLut(int bits) { + int len = 1 << bits; + grayLut = new int[len]; + + if (performGammaCorrection) { + for (int i = 0; i < len; i++) { + grayLut[i] = gammaLut[i]; + } + } else { + for (int i = 0; i < len; i++) { + grayLut[i] = expandBits[bits][i]; + } + } + } + + public PNGRed(InputStream stream) throws IOException { + this(stream, null); + } + + public PNGRed(InputStream stream, PNGDecodeParam decodeParam) + throws IOException { + + if (!stream.markSupported()) { + stream = new BufferedInputStream(stream); + } + DataInputStream distream = new DataInputStream(stream); + + if (decodeParam == null) { + decodeParam = new PNGDecodeParam(); + } + this.decodeParam = decodeParam; + + // Get parameter values + this.suppressAlpha = decodeParam.getSuppressAlpha(); + this.expandPalette = decodeParam.getExpandPalette(); + this.output8BitGray = decodeParam.getOutput8BitGray(); + this.expandGrayAlpha = decodeParam.getExpandGrayAlpha(); + if (decodeParam.getPerformGammaCorrection()) { + this.userExponent = decodeParam.getUserExponent(); + this.displayExponent = decodeParam.getDisplayExponent(); + performGammaCorrection = true; + output8BitGray = true; + } + this.generateEncodeParam = decodeParam.getGenerateEncodeParam(); + + if (emitProperties) { + properties.put("file_type", "PNG v. 1.0"); + } + + try { + long magic = distream.readLong(); + if (magic != 0x89504e470d0a1a0aL) { + String msg = PropertyUtil.getString("PNGImageDecoder0"); + throw new RuntimeException(msg); + } + } catch (Exception e) { + e.printStackTrace(); + String msg = PropertyUtil.getString("PNGImageDecoder1"); + throw new RuntimeException(msg); + } + + do { + try { + PNGChunk chunk; + + String chunkType = getChunkType(distream); + if (chunkType.equals("IHDR")) { + chunk = readChunk(distream); + parse_IHDR_chunk(chunk); + } else if (chunkType.equals("PLTE")) { + chunk = readChunk(distream); + parse_PLTE_chunk(chunk); + } else if (chunkType.equals("IDAT")) { + chunk = readChunk(distream); + streamVec.add(new ByteArrayInputStream(chunk.getData())); + } else if (chunkType.equals("IEND")) { + chunk = readChunk(distream); + parse_IEND_chunk(chunk); + break; // fall through to the bottom + } else if (chunkType.equals("bKGD")) { + chunk = readChunk(distream); + parse_bKGD_chunk(chunk); + } else if (chunkType.equals("cHRM")) { + chunk = readChunk(distream); + parse_cHRM_chunk(chunk); + } else if (chunkType.equals("gAMA")) { + chunk = readChunk(distream); + parse_gAMA_chunk(chunk); + } else if (chunkType.equals("hIST")) { + chunk = readChunk(distream); + parse_hIST_chunk(chunk); + } else if (chunkType.equals("iCCP")) { + chunk = readChunk(distream); + parse_iCCP_chunk(chunk); + } else if (chunkType.equals("pHYs")) { + chunk = readChunk(distream); + parse_pHYs_chunk(chunk); + } else if (chunkType.equals("sBIT")) { + chunk = readChunk(distream); + parse_sBIT_chunk(chunk); + } else if (chunkType.equals("sRGB")) { + chunk = readChunk(distream); + parse_sRGB_chunk(chunk); + } else if (chunkType.equals("tEXt")) { + chunk = readChunk(distream); + parse_tEXt_chunk(chunk); + } else if (chunkType.equals("tIME")) { + chunk = readChunk(distream); + parse_tIME_chunk(chunk); + } else if (chunkType.equals("tRNS")) { + chunk = readChunk(distream); + parse_tRNS_chunk(chunk); + } else if (chunkType.equals("zTXt")) { + chunk = readChunk(distream); + parse_zTXt_chunk(chunk); + } else { + chunk = readChunk(distream); + // Output the chunk data in raw form + + String type = chunk.getTypeString(); + byte[] data = chunk.getData(); + if (encodeParam != null) { + encodeParam.addPrivateChunk(type, data); + } + if (emitProperties) { + String key = "chunk_" + chunkIndex++ + ":" + type; + properties.put(key.toLowerCase(), data); + } + } + } catch (Exception e) { + e.printStackTrace(); + String msg = PropertyUtil.getString("PNGImageDecoder2"); + throw new RuntimeException(msg); + } + } while (true); + + // Final post-processing + + if (significantBits == null) { + significantBits = new int[inputBands]; + for (int i = 0; i < inputBands; i++) { + significantBits[i] = bitDepth; + } + + if (emitProperties) { + properties.put("significant_bits", significantBits); + } + } + } + + private static String getChunkType(DataInputStream distream) { + try { + distream.mark(8); + /* int length = */ distream.readInt(); + int type = distream.readInt(); + distream.reset(); + + String typeString = new String(); + typeString += (char)(type >> 24); + typeString += (char)((type >> 16) & 0xff); + typeString += (char)((type >> 8) & 0xff); + typeString += (char)(type & 0xff); + return typeString; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private static PNGChunk readChunk(DataInputStream distream) { + try { + int length = distream.readInt(); + int type = distream.readInt(); + byte[] data = new byte[length]; + distream.readFully(data); + int crc = distream.readInt(); + + return new PNGChunk(length, type, data, crc); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private void parse_IHDR_chunk(PNGChunk chunk) { + int width = chunk.getInt4(0); + int height = chunk.getInt4(4); + + bounds = new Rectangle(0, 0, width, height); + + bitDepth = chunk.getInt1(8); + + if ((bitDepth != 1) && (bitDepth != 2) && (bitDepth != 4) && + (bitDepth != 8) && (bitDepth != 16)) { + // Error -- bad bit depth + String msg = PropertyUtil.getString("PNGImageDecoder3"); + throw new RuntimeException(msg); + } + maxOpacity = (1 << bitDepth) - 1; + + colorType = chunk.getInt1(9); + if ((colorType != PNG_COLOR_GRAY) && + (colorType != PNG_COLOR_RGB) && + (colorType != PNG_COLOR_PALETTE) && + (colorType != PNG_COLOR_GRAY_ALPHA) && + (colorType != PNG_COLOR_RGB_ALPHA)) { + System.out.println(PropertyUtil.getString("PNGImageDecoder4")); + } + + if ((colorType == PNG_COLOR_RGB) && (bitDepth < 8)) { + // Error -- RGB images must have 8 or 16 bits + String msg = PropertyUtil.getString("PNGImageDecoder5"); + throw new RuntimeException(msg); + } + + if ((colorType == PNG_COLOR_PALETTE) && (bitDepth == 16)) { + // Error -- palette images must have < 16 bits + String msg = PropertyUtil.getString("PNGImageDecoder6"); + throw new RuntimeException(msg); + } + + if ((colorType == PNG_COLOR_GRAY_ALPHA) && (bitDepth < 8)) { + // Error -- gray/alpha images must have >= 8 bits + String msg = PropertyUtil.getString("PNGImageDecoder7"); + throw new RuntimeException(msg); + } + + if ((colorType == PNG_COLOR_RGB_ALPHA) && (bitDepth < 8)) { + // Error -- RGB/alpha images must have >= 8 bits + String msg = PropertyUtil.getString("PNGImageDecoder8"); + throw new RuntimeException(msg); + } + + if (emitProperties) { + properties.put("color_type", colorTypeNames[colorType]); + } + + if (generateEncodeParam) { + if (colorType == PNG_COLOR_PALETTE) { + encodeParam = new PNGEncodeParam.Palette(); + } else if (colorType == PNG_COLOR_GRAY || + colorType == PNG_COLOR_GRAY_ALPHA) { + encodeParam = new PNGEncodeParam.Gray(); + } else { + encodeParam = new PNGEncodeParam.RGB(); + } + decodeParam.setEncodeParam(encodeParam); + } + + if (encodeParam != null) { + encodeParam.setBitDepth(bitDepth); + } + if (emitProperties) { + properties.put("bit_depth", new Integer(bitDepth)); + } + + if (performGammaCorrection) { + // Assume file gamma is 1/2.2 unless we get a gAMA chunk + float gamma = (1.0F/2.2F)*(displayExponent/userExponent); + if (encodeParam != null) { + encodeParam.setGamma(gamma); + } + if (emitProperties) { + properties.put("gamma", new Float(gamma)); + } + } + + compressionMethod = chunk.getInt1(10); + if (compressionMethod != 0) { + // Error -- only know about compression method 0 + String msg = PropertyUtil.getString("PNGImageDecoder9"); + throw new RuntimeException(msg); + } + + filterMethod = chunk.getInt1(11); + if (filterMethod != 0) { + // Error -- only know about filter method 0 + String msg = PropertyUtil.getString("PNGImageDecoder10"); + throw new RuntimeException(msg); + } + + interlaceMethod = chunk.getInt1(12); + if (interlaceMethod == 0) { + if (encodeParam != null) { + encodeParam.setInterlacing(false); + } + if (emitProperties) { + properties.put("interlace_method", "None"); + } + } else if (interlaceMethod == 1) { + if (encodeParam != null) { + encodeParam.setInterlacing(true); + } + if (emitProperties) { + properties.put("interlace_method", "Adam7"); + } + } else { + // Error -- only know about Adam7 interlacing + String msg = PropertyUtil.getString("PNGImageDecoder11"); + throw new RuntimeException(msg); + } + + bytesPerPixel = (bitDepth == 16) ? 2 : 1; + + switch (colorType) { + case PNG_COLOR_GRAY: + inputBands = 1; + outputBands = 1; + + if (output8BitGray && (bitDepth < 8)) { + postProcess = POST_GRAY_LUT; + } else if (performGammaCorrection) { + postProcess = POST_GAMMA; + } else { + postProcess = POST_NONE; + } + break; + + case PNG_COLOR_RGB: + inputBands = 3; + bytesPerPixel *= 3; + outputBands = 3; + + if (performGammaCorrection) { + postProcess = POST_GAMMA; + } else { + postProcess = POST_NONE; + } + break; + + case PNG_COLOR_PALETTE: + inputBands = 1; + bytesPerPixel = 1; + outputBands = expandPalette ? 3 : 1; + + if (expandPalette) { + postProcess = POST_PALETTE_TO_RGB; + } else { + postProcess = POST_NONE; + } + break; + + case PNG_COLOR_GRAY_ALPHA: + inputBands = 2; + bytesPerPixel *= 2; + + if (suppressAlpha) { + outputBands = 1; + postProcess = POST_REMOVE_GRAY_TRANS; + } else { + if (performGammaCorrection) { + postProcess = POST_GAMMA; + } else { + postProcess = POST_NONE; + } + if (expandGrayAlpha) { + postProcess |= POST_EXP_MASK; + outputBands = 4; + } else { + outputBands = 2; + } + } + break; + + case PNG_COLOR_RGB_ALPHA: + inputBands = 4; + bytesPerPixel *= 4; + outputBands = (!suppressAlpha) ? 4 : 3; + + if (suppressAlpha) { + postProcess = POST_REMOVE_RGB_TRANS; + } else if (performGammaCorrection) { + postProcess = POST_GAMMA; + } else { + postProcess = POST_NONE; + } + break; + } + } + + private void parse_IEND_chunk(PNGChunk chunk) throws Exception { + // Store text strings + int textLen = textKeys.size(); + String[] textArray = new String[2*textLen]; + for (int i = 0; i < textLen; i++) { + String key = (String)textKeys.elementAt(i); + String val = (String)textStrings.elementAt(i); + textArray[2*i] = key; + textArray[2*i + 1] = val; + if (emitProperties) { + String uniqueKey = "text_" + i + ":" + key; + properties.put(uniqueKey.toLowerCase(), val); + } + } + if (encodeParam != null) { + encodeParam.setText(textArray); + } + + // Store compressed text strings + int ztextLen = ztextKeys.size(); + String[] ztextArray = new String[2*ztextLen]; + for (int i = 0; i < ztextLen; i++) { + String key = (String)ztextKeys.elementAt(i); + String val = (String)ztextStrings.elementAt(i); + ztextArray[2*i] = key; + ztextArray[2*i + 1] = val; + if (emitProperties) { + String uniqueKey = "ztext_" + i + ":" + key; + properties.put(uniqueKey.toLowerCase(), val); + } + } + if (encodeParam != null) { + encodeParam.setCompressedText(ztextArray); + } + + // Parse prior IDAT chunks + InputStream seqStream = + new SequenceInputStream(streamVec.elements()); + InputStream infStream = + new InflaterInputStream(seqStream, new Inflater()); + dataStream = new DataInputStream(infStream); + + // Create an empty WritableRaster + int depth = bitDepth; + if ((colorType == PNG_COLOR_GRAY) && + (bitDepth < 8) && output8BitGray) { + depth = 8; + } + if ((colorType == PNG_COLOR_PALETTE) && expandPalette) { + depth = 8; + } + int width = bounds.width; + int height = bounds.height; + + int bytesPerRow = (outputBands*width*depth + 7)/8; + int scanlineStride = + (depth == 16) ? (bytesPerRow/2) : bytesPerRow; + + theTile = createRaster(width, height, outputBands, + scanlineStride, + depth); + + if (performGammaCorrection && (gammaLut == null)) { + initGammaLut(bitDepth); + } + if ((postProcess == POST_GRAY_LUT) || + (postProcess == POST_GRAY_LUT_ADD_TRANS) || + (postProcess == POST_GRAY_LUT_ADD_TRANS_EXP)) { + initGrayLut(bitDepth); + } + + decodeImage(interlaceMethod == 1); + SampleModel sm = theTile.getSampleModel(); + ColorModel cm; + + if ((colorType == PNG_COLOR_PALETTE) && !expandPalette) { + if (outputHasAlphaPalette) { + cm = new IndexColorModel(bitDepth, + paletteEntries, + redPalette, + greenPalette, + bluePalette, + alphaPalette); + } else { + cm = new IndexColorModel(bitDepth, + paletteEntries, + redPalette, + greenPalette, + bluePalette); + } + } else if ((colorType == PNG_COLOR_GRAY) && + (bitDepth < 8) && !output8BitGray) { + byte[] palette = expandBits[bitDepth]; + cm = new IndexColorModel(bitDepth, + palette.length, + palette, + palette, + palette); + } else { + cm = + createComponentColorModel(sm); + } + + init((CachableRed)null, bounds, cm, sm, 0, 0, properties); + } + + private static final int[] GrayBits8 = { 8 }; + private static final ComponentColorModel colorModelGray8 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), + GrayBits8, false, false, + Transparency.OPAQUE, + DataBuffer.TYPE_BYTE); + + private static final int[] GrayAlphaBits8 = { 8, 8 }; + private static final ComponentColorModel colorModelGrayAlpha8 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), + GrayAlphaBits8, true, false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_BYTE); + + private static final int[] GrayBits16 = { 16 }; + private static final ComponentColorModel colorModelGray16 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), + GrayBits16, false, false, + Transparency.OPAQUE, + DataBuffer.TYPE_USHORT); + + private static final int[] GrayAlphaBits16 = { 16, 16 }; + private static final ComponentColorModel colorModelGrayAlpha16 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), + GrayAlphaBits16, true, false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_USHORT); + + private static final int[] GrayBits32 = { 32 }; + private static final ComponentColorModel colorModelGray32 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), + GrayBits32, false, false, + Transparency.OPAQUE, + DataBuffer.TYPE_INT); + + private static final int[] GrayAlphaBits32 = { 32, 32 }; + private static final ComponentColorModel colorModelGrayAlpha32 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), + GrayAlphaBits32, true, false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_INT); + + private static final int[] RGBBits8 = { 8, 8, 8 }; + private static final ComponentColorModel colorModelRGB8 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + RGBBits8, false, false, + Transparency.OPAQUE, + DataBuffer.TYPE_BYTE); + + private static final int[] RGBABits8 = { 8, 8, 8, 8 }; + private static final ComponentColorModel colorModelRGBA8 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + RGBABits8, true, false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_BYTE); + + private static final int[] RGBBits16 = { 16, 16, 16 }; + private static final ComponentColorModel colorModelRGB16 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + RGBBits16, false, false, + Transparency.OPAQUE, + DataBuffer.TYPE_USHORT); + + private static final int[] RGBABits16 = { 16, 16, 16, 16 }; + private static final ComponentColorModel colorModelRGBA16 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + RGBABits16, true, false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_USHORT); + + private static final int[] RGBBits32 = { 32, 32, 32 }; + private static final ComponentColorModel colorModelRGB32 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + RGBBits32, false, false, + Transparency.OPAQUE, + DataBuffer.TYPE_INT); + + private static final int[] RGBABits32 = { 32, 32, 32, 32 }; + private static final ComponentColorModel colorModelRGBA32 = + new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + RGBABits32, true, false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_INT); + /** + * A convenience method to create an instance of + * ComponentColorModel suitable for use with the + * given SampleModel. The SampleModel + * should have a data type of DataBuffer.TYPE_BYTE, + * TYPE_USHORT, or TYPE_INT and between + * 1 and 4 bands. Depending on the number of bands of the + * SampleModel, either a gray, gray+alpha, rgb, or + * rgb+alpha ColorModel is returned. + */ + public static ColorModel createComponentColorModel(SampleModel sm) { + int type = sm.getDataType(); + int bands = sm.getNumBands(); + ComponentColorModel cm = null; + + if (type == DataBuffer.TYPE_BYTE) { + switch (bands) { + case 1: + cm = colorModelGray8; + break; + case 2: + cm = colorModelGrayAlpha8; + break; + case 3: + cm = colorModelRGB8; + break; + case 4: + cm = colorModelRGBA8; + break; + } + } else if (type == DataBuffer.TYPE_USHORT) { + switch (bands) { + case 1: + cm = colorModelGray16; + break; + case 2: + cm = colorModelGrayAlpha16; + break; + case 3: + cm = colorModelRGB16; + break; + case 4: + cm = colorModelRGBA16; + break; + } + } else if (type == DataBuffer.TYPE_INT) { + switch (bands) { + case 1: + cm = colorModelGray32; + break; + case 2: + cm = colorModelGrayAlpha32; + break; + case 3: + cm = colorModelRGB32; + break; + case 4: + cm = colorModelRGBA32; + break; + } + } + + return cm; + } + + private void parse_PLTE_chunk(PNGChunk chunk) { + paletteEntries = chunk.getLength()/3; + redPalette = new byte[paletteEntries]; + greenPalette = new byte[paletteEntries]; + bluePalette = new byte[paletteEntries]; + + int pltIndex = 0; + + // gAMA chunk must precede PLTE chunk + if (performGammaCorrection) { + if (gammaLut == null) { + initGammaLut(bitDepth == 16 ? 16 : 8); + } + + for (int i = 0; i < paletteEntries; i++) { + byte r = chunk.getByte(pltIndex++); + byte g = chunk.getByte(pltIndex++); + byte b = chunk.getByte(pltIndex++); + + redPalette[i] = (byte)gammaLut[r & 0xff]; + greenPalette[i] = (byte)gammaLut[g & 0xff]; + bluePalette[i] = (byte)gammaLut[b & 0xff]; + } + } else { + for (int i = 0; i < paletteEntries; i++) { + redPalette[i] = chunk.getByte(pltIndex++); + greenPalette[i] = chunk.getByte(pltIndex++); + bluePalette[i] = chunk.getByte(pltIndex++); + } + } + } + + private void parse_bKGD_chunk(PNGChunk chunk) { + hasBackground = true; + + switch (colorType) { + case PNG_COLOR_PALETTE: + int bkgdIndex = chunk.getByte(0) & 0xff; + + bkgdRed = redPalette[bkgdIndex] & 0xff; + bkgdGreen = greenPalette[bkgdIndex] & 0xff; + bkgdBlue = bluePalette[bkgdIndex] & 0xff; + + if (encodeParam != null) { + ((PNGEncodeParam.Palette)encodeParam). + setBackgroundPaletteIndex(bkgdIndex); + } + break; + case PNG_COLOR_GRAY: case PNG_COLOR_GRAY_ALPHA: + int bkgdGray = chunk.getInt2(0); + bkgdRed = bkgdGreen = bkgdBlue = bkgdGray; + + if (encodeParam != null) { + ((PNGEncodeParam.Gray)encodeParam). + setBackgroundGray(bkgdGray); + } + break; + case PNG_COLOR_RGB: case PNG_COLOR_RGB_ALPHA: + bkgdRed = chunk.getInt2(0); + bkgdGreen = chunk.getInt2(2); + bkgdBlue = chunk.getInt2(4); + + int[] bkgdRGB = new int[3]; + bkgdRGB[0] = bkgdRed; + bkgdRGB[1] = bkgdGreen; + bkgdRGB[2] = bkgdBlue; + if (encodeParam != null) { + ((PNGEncodeParam.RGB)encodeParam). + setBackgroundRGB(bkgdRGB); + } + break; + } + + int r = 0, g = 0, b = 0; + if ((colorType == PNG_COLOR_PALETTE) || (bitDepth == 8)) { + r = bkgdRed; + g = bkgdGreen; + b = bkgdBlue; + } else if (bitDepth < 8) { + r = expandBits[bitDepth][bkgdRed]; + g = expandBits[bitDepth][bkgdGreen]; + b = expandBits[bitDepth][bkgdBlue]; + } else if (bitDepth == 16) { + r = bkgdRed >> 8; + g = bkgdGreen >> 8; + b = bkgdBlue >> 8; + } + if (emitProperties) { + properties.put("background_color", new Color(r, g, b)); + } + } + + private void parse_cHRM_chunk(PNGChunk chunk) { + // If an sRGB chunk exists, ignore cHRM chunks + if (sRGBRenderingIntent != -1) { + return; + } + + chromaticity = new float[8]; + chromaticity[0] = chunk.getInt4(0)/100000.0F; + chromaticity[1] = chunk.getInt4(4)/100000.0F; + chromaticity[2] = chunk.getInt4(8)/100000.0F; + chromaticity[3] = chunk.getInt4(12)/100000.0F; + chromaticity[4] = chunk.getInt4(16)/100000.0F; + chromaticity[5] = chunk.getInt4(20)/100000.0F; + chromaticity[6] = chunk.getInt4(24)/100000.0F; + chromaticity[7] = chunk.getInt4(28)/100000.0F; + + if (encodeParam != null) { + encodeParam.setChromaticity(chromaticity); + } + if (emitProperties) { + properties.put("white_point_x", new Float(chromaticity[0])); + properties.put("white_point_y", new Float(chromaticity[1])); + properties.put("red_x", new Float(chromaticity[2])); + properties.put("red_y", new Float(chromaticity[3])); + properties.put("green_x", new Float(chromaticity[4])); + properties.put("green_y", new Float(chromaticity[5])); + properties.put("blue_x", new Float(chromaticity[6])); + properties.put("blue_y", new Float(chromaticity[7])); + } + } + + private void parse_gAMA_chunk(PNGChunk chunk) { + // If an sRGB chunk exists, ignore gAMA chunks + if (sRGBRenderingIntent != -1) { + return; + } + + fileGamma = chunk.getInt4(0)/100000.0F; + // System.out.println("Gamma: " + fileGamma); + float exp = + performGammaCorrection ? displayExponent/userExponent : 1.0F; + if (encodeParam != null) { + encodeParam.setGamma(fileGamma*exp); + } + if (emitProperties) { + properties.put("gamma", new Float(fileGamma*exp)); + } + } + + private void parse_hIST_chunk(PNGChunk chunk) { + if (redPalette == null) { + String msg = PropertyUtil.getString("PNGImageDecoder18"); + throw new RuntimeException(msg); + } + + int length = redPalette.length; + int[] hist = new int[length]; + for (int i = 0; i < length; i++) { + hist[i] = chunk.getInt2(2*i); + } + + if (encodeParam != null) { + encodeParam.setPaletteHistogram(hist); + } + } + + private void parse_iCCP_chunk(PNGChunk chunk) { + String name = new String(); + byte b; + + int textIndex = 0; + while ((b = chunk.getByte(textIndex++)) != 0) { + name += (char)b; + } + } + + private void parse_pHYs_chunk(PNGChunk chunk) { + int xPixelsPerUnit = chunk.getInt4(0); + int yPixelsPerUnit = chunk.getInt4(4); + int unitSpecifier = chunk.getInt1(8); + + if (encodeParam != null) { + encodeParam.setPhysicalDimension(xPixelsPerUnit, + yPixelsPerUnit, + unitSpecifier); + } + if (emitProperties) { + properties.put("x_pixels_per_unit", new Integer(xPixelsPerUnit)); + properties.put("y_pixels_per_unit", new Integer(yPixelsPerUnit)); + properties.put("pixel_aspect_ratio", + new Float((float)xPixelsPerUnit/yPixelsPerUnit)); + if (unitSpecifier == 1) { + properties.put("pixel_units", "Meters"); + } else if (unitSpecifier != 0) { + // Error -- unit specifier must be 0 or 1 + String msg = PropertyUtil.getString("PNGImageDecoder12"); + throw new RuntimeException(msg); + } + } + } + + private void parse_sBIT_chunk(PNGChunk chunk) { + if (colorType == PNG_COLOR_PALETTE) { + significantBits = new int[3]; + } else { + significantBits = new int[inputBands]; + } + for (int i = 0; i < significantBits.length; i++) { + int bits = chunk.getByte(i); + int depth = (colorType == PNG_COLOR_PALETTE) ? 8 : bitDepth; + if (bits <= 0 || bits > depth) { + // Error -- significant bits must be between 0 and + // image bit depth. + String msg = PropertyUtil.getString("PNGImageDecoder13"); + throw new RuntimeException(msg); + } + significantBits[i] = bits; + } + + if (encodeParam != null) { + encodeParam.setSignificantBits(significantBits); + } + if (emitProperties) { + properties.put("significant_bits", significantBits); + } + } + + private void parse_sRGB_chunk(PNGChunk chunk) { + sRGBRenderingIntent = chunk.getByte(0); + + // The presence of an sRGB chunk implies particular + // settings for gamma and chroma. + fileGamma = 45455/100000.0F; + + chromaticity = new float[8]; + chromaticity[0] = 31270/10000.0F; + chromaticity[1] = 32900/10000.0F; + chromaticity[2] = 64000/10000.0F; + chromaticity[3] = 33000/10000.0F; + chromaticity[4] = 30000/10000.0F; + chromaticity[5] = 60000/10000.0F; + chromaticity[6] = 15000/10000.0F; + chromaticity[7] = 6000/10000.0F; + + if (performGammaCorrection) { + // File gamma is 1/2.2 + float gamma = fileGamma*(displayExponent/userExponent); + if (encodeParam != null) { + encodeParam.setGamma(gamma); + encodeParam.setChromaticity(chromaticity); + } + if (emitProperties) { + properties.put("gamma", new Float(gamma)); + properties.put("white_point_x", new Float(chromaticity[0])); + properties.put("white_point_y", new Float(chromaticity[1])); + properties.put("red_x", new Float(chromaticity[2])); + properties.put("red_y", new Float(chromaticity[3])); + properties.put("green_x", new Float(chromaticity[4])); + properties.put("green_y", new Float(chromaticity[5])); + properties.put("blue_x", new Float(chromaticity[6])); + properties.put("blue_y", new Float(chromaticity[7])); + } + } + } + + private void parse_tEXt_chunk(PNGChunk chunk) { + String key = new String(); + String value = new String(); + byte b; + + int textIndex = 0; + while ((b = chunk.getByte(textIndex++)) != 0) { + key += (char)b; + } + + for (int i = textIndex; i < chunk.getLength(); i++) { + value += (char)chunk.getByte(i); + } + + textKeys.add(key); + textStrings.add(value); + } + + private void parse_tIME_chunk(PNGChunk chunk) { + int year = chunk.getInt2(0); + int month = chunk.getInt1(2) - 1; + int day = chunk.getInt1(3); + int hour = chunk.getInt1(4); + int minute = chunk.getInt1(5); + int second = chunk.getInt1(6); + + TimeZone gmt = TimeZone.getTimeZone("GMT"); + + GregorianCalendar cal = new GregorianCalendar(gmt); + cal.set(year, month, day, + hour, minute, second); + Date date = cal.getTime(); + + if (encodeParam != null) { + encodeParam.setModificationTime(date); + } + if (emitProperties) { + properties.put("timestamp", date); + } + } + + private void parse_tRNS_chunk(PNGChunk chunk) { + if (colorType == PNG_COLOR_PALETTE) { + int entries = chunk.getLength(); + if (entries > paletteEntries) { + // Error -- mustn't have more alpha than RGB palette entries + String msg = PropertyUtil.getString("PNGImageDecoder14"); + throw new RuntimeException(msg); + } + + // Load beginning of palette from the chunk + alphaPalette = new byte[paletteEntries]; + for (int i = 0; i < entries; i++) { + alphaPalette[i] = chunk.getByte(i); + } + + // Fill rest of palette with 255 + for (int i = entries; i < paletteEntries; i++) { + alphaPalette[i] = (byte)255; + } + + if (!suppressAlpha) { + if (expandPalette) { + postProcess = POST_PALETTE_TO_RGBA; + outputBands = 4; + } else { + outputHasAlphaPalette = true; + } + } + } else if (colorType == PNG_COLOR_GRAY) { + grayTransparentAlpha = chunk.getInt2(0); + + if (!suppressAlpha) { + if (bitDepth < 8) { + output8BitGray = true; + maxOpacity = 255; + postProcess = POST_GRAY_LUT_ADD_TRANS; + } else { + postProcess = POST_ADD_GRAY_TRANS; + } + + if (expandGrayAlpha) { + outputBands = 4; + postProcess |= POST_EXP_MASK; + } else { + outputBands = 2; + } + + if (encodeParam != null) { + ((PNGEncodeParam.Gray)encodeParam). + setTransparentGray(grayTransparentAlpha); + } + } + } else if (colorType == PNG_COLOR_RGB) { + redTransparentAlpha = chunk.getInt2(0); + greenTransparentAlpha = chunk.getInt2(2); + blueTransparentAlpha = chunk.getInt2(4); + + if (!suppressAlpha) { + outputBands = 4; + postProcess = POST_ADD_RGB_TRANS; + + if (encodeParam != null) { + int[] rgbTrans = new int[3]; + rgbTrans[0] = redTransparentAlpha; + rgbTrans[1] = greenTransparentAlpha; + rgbTrans[2] = blueTransparentAlpha; + ((PNGEncodeParam.RGB)encodeParam). + setTransparentRGB(rgbTrans); + } + } + } else if (colorType == PNG_COLOR_GRAY_ALPHA || + colorType == PNG_COLOR_RGB_ALPHA) { + // Error -- GA or RGBA image can't have a tRNS chunk. + String msg = PropertyUtil.getString("PNGImageDecoder15"); + throw new RuntimeException(msg); + } + } + + private void parse_zTXt_chunk(PNGChunk chunk) { + String key = new String(); + String value = new String(); + byte b; + + int textIndex = 0; + while ((b = chunk.getByte(textIndex++)) != 0) { + key += (char)b; + } + /* int method = */ chunk.getByte(textIndex++); + + try { + int length = chunk.getLength() - textIndex; + byte[] data = chunk.getData(); + InputStream cis = + new ByteArrayInputStream(data, textIndex, length); + InputStream iis = new InflaterInputStream(cis); + + int c; + while ((c = iis.read()) != -1) { + value += (char)c; + } + + ztextKeys.add(key); + ztextStrings.add(value); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private WritableRaster createRaster(int width, int height, int bands, + int scanlineStride, + int bitDepth) { + + DataBuffer dataBuffer; + WritableRaster ras = null; + Point origin = new Point(0, 0); + if ((bitDepth < 8) && (bands == 1)) { + dataBuffer = new DataBufferByte(height*scanlineStride); + ras = Raster.createPackedRaster(dataBuffer, + width, height, + bitDepth, + origin); + } else if (bitDepth <= 8) { + dataBuffer = new DataBufferByte(height*scanlineStride); + ras = Raster.createInterleavedRaster(dataBuffer, + width, height, + scanlineStride, + bands, + bandOffsets[bands], + origin); + } else { + dataBuffer = new DataBufferUShort(height*scanlineStride); + ras = Raster.createInterleavedRaster(dataBuffer, + width, height, + scanlineStride, + bands, + bandOffsets[bands], + origin); + } + + return ras; + } + + // Data filtering methods + + private static void decodeSubFilter(byte[] curr, int count, int bpp) { + for (int i = bpp; i < count; i++) { + int val; + + val = curr[i] & 0xff; + val += curr[i - bpp] & 0xff; + + curr[i] = (byte)val; + } + } + + private static void decodeUpFilter(byte[] curr, byte[] prev, + int count) { + for (int i = 0; i < count; i++) { + int raw = curr[i] & 0xff; + int prior = prev[i] & 0xff; + + curr[i] = (byte)(raw + prior); + } + } + + private static void decodeAverageFilter(byte[] curr, byte[] prev, + int count, int bpp) { + int raw, priorPixel, priorRow; + + for (int i = 0; i < bpp; i++) { + raw = curr[i] & 0xff; + priorRow = prev[i] & 0xff; + + curr[i] = (byte)(raw + priorRow/2); + } + + for (int i = bpp; i < count; i++) { + raw = curr[i] & 0xff; + priorPixel = curr[i - bpp] & 0xff; + priorRow = prev[i] & 0xff; + + curr[i] = (byte)(raw + (priorPixel + priorRow)/2); + } + } + + private static int paethPredictor(int a, int b, int c) { + int p = a + b - c; + int pa = Math.abs(p - a); + int pb = Math.abs(p - b); + int pc = Math.abs(p - c); + + if ((pa <= pb) && (pa <= pc)) { + return a; + } else if (pb <= pc) { + return b; + } else { + return c; + } + } + + private static void decodePaethFilter(byte[] curr, byte[] prev, + int count, int bpp) { + int raw, priorPixel, priorRow, priorRowPixel; + + for (int i = 0; i < bpp; i++) { + raw = curr[i] & 0xff; + priorRow = prev[i] & 0xff; + + curr[i] = (byte)(raw + priorRow); + } + + for (int i = bpp; i < count; i++) { + raw = curr[i] & 0xff; + priorPixel = curr[i - bpp] & 0xff; + priorRow = prev[i] & 0xff; + priorRowPixel = prev[i - bpp] & 0xff; + + curr[i] = (byte)(raw + paethPredictor(priorPixel, + priorRow, + priorRowPixel)); + } + } + + private void processPixels(int process, + Raster src, WritableRaster dst, + int xOffset, int step, int y, int width) { + int srcX, dstX; + + // Create an array suitable for holding one pixel + int[] ps = src.getPixel(0, 0, (int[])null); + int[] pd = dst.getPixel(0, 0, (int[])null); + + dstX = xOffset; + switch (process) { + case POST_NONE: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + dst.setPixel(dstX, y, ps); + dstX += step; + } + break; + + case POST_GAMMA: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + for (int i = 0; i < inputBands; i++) { + int x = ps[i]; + ps[i] = gammaLut[x]; + } + + dst.setPixel(dstX, y, ps); + dstX += step; + } + break; + + case POST_GRAY_LUT: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + pd[0] = grayLut[ps[0]]; + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_GRAY_LUT_ADD_TRANS: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + pd[0] = grayLut[val]; + if (val == grayTransparentAlpha) { + pd[1] = 0; + } else { + pd[1] = maxOpacity; + } + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_PALETTE_TO_RGB: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + pd[0] = redPalette[val]; + pd[1] = greenPalette[val]; + pd[2] = bluePalette[val]; + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_PALETTE_TO_RGBA: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + pd[0] = redPalette[val]; + pd[1] = greenPalette[val]; + pd[2] = bluePalette[val]; + pd[3] = alphaPalette[val]; + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_ADD_GRAY_TRANS: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + if (performGammaCorrection) { + val = gammaLut[val]; + } + pd[0] = val; + if (val == grayTransparentAlpha) { + pd[1] = 0; + } else { + pd[1] = maxOpacity; + } + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_ADD_RGB_TRANS: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int r = ps[0]; + int g = ps[1]; + int b = ps[2]; + if (performGammaCorrection) { + pd[0] = gammaLut[r]; + pd[1] = gammaLut[g]; + pd[2] = gammaLut[b]; + } else { + pd[0] = r; + pd[1] = g; + pd[2] = b; + } + if ((r == redTransparentAlpha) && + (g == greenTransparentAlpha) && + (b == blueTransparentAlpha)) { + pd[3] = 0; + } else { + pd[3] = maxOpacity; + } + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_REMOVE_GRAY_TRANS: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int g = ps[0]; + if (performGammaCorrection) { + pd[0] = gammaLut[g]; + } else { + pd[0] = g; + } + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_REMOVE_RGB_TRANS: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int r = ps[0]; + int g = ps[1]; + int b = ps[2]; + if (performGammaCorrection) { + pd[0] = gammaLut[r]; + pd[1] = gammaLut[g]; + pd[2] = gammaLut[b]; + } else { + pd[0] = r; + pd[1] = g; + pd[2] = b; + } + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_GAMMA_EXP: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + int alpha = ps[1]; + int gamma = gammaLut[val]; + pd[0] = gamma; + pd[1] = gamma; + pd[2] = gamma; + pd[3] = alpha; + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_GRAY_ALPHA_EXP: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + int alpha = ps[1]; + pd[0] = val; + pd[1] = val; + pd[2] = val; + pd[3] = alpha; + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_ADD_GRAY_TRANS_EXP: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + if (performGammaCorrection) { + val = gammaLut[val]; + } + pd[0] = val; + pd[1] = val; + pd[2] = val; + if (val == grayTransparentAlpha) { + pd[3] = 0; + } else { + pd[3] = maxOpacity; + } + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + + case POST_GRAY_LUT_ADD_TRANS_EXP: + for (srcX = 0; srcX < width; srcX++) { + src.getPixel(srcX, 0, ps); + + int val = ps[0]; + int val2 = grayLut[val]; + pd[0] = val2; + pd[1] = val2; + pd[2] = val2; + if (val == grayTransparentAlpha) { + pd[3] = 0; + } else { + pd[3] = maxOpacity; + } + + dst.setPixel(dstX, y, pd); + dstX += step; + } + break; + } + } + + /** + * Reads in an image of a given size and returns it as a + * WritableRaster. + */ + private void decodePass(WritableRaster imRas, + int xOffset, int yOffset, + int xStep, int yStep, + int passWidth, int passHeight) { + if ((passWidth == 0) || (passHeight == 0)) { + return; + } + + int bytesPerRow = (inputBands*passWidth*bitDepth + 7)/8; + int eltsPerRow = (bitDepth == 16) ? bytesPerRow/2 : bytesPerRow; + byte[] curr = new byte[bytesPerRow]; + byte[] prior = new byte[bytesPerRow]; + + // Create a 1-row tall Raster to hold the data + WritableRaster passRow = + createRaster(passWidth, 1, inputBands, + eltsPerRow, + bitDepth); + DataBuffer dataBuffer = passRow.getDataBuffer(); + int type = dataBuffer.getDataType(); + byte[] byteData = null; + short[] shortData = null; + if (type == DataBuffer.TYPE_BYTE) { + byteData = ((DataBufferByte)dataBuffer).getData(); + } else { + shortData = ((DataBufferUShort)dataBuffer).getData(); + } + + // Decode the (sub)image row-by-row + int srcY, dstY; + for (srcY = 0, dstY = yOffset; + srcY < passHeight; + srcY++, dstY += yStep) { + // Read the filter type byte and a row of data + int filter = 0; + try { + filter = dataStream.read(); + dataStream.readFully(curr, 0, bytesPerRow); + } catch (Exception e) { + e.printStackTrace(); + } + + switch (filter) { + case PNG_FILTER_NONE: + break; + case PNG_FILTER_SUB: + decodeSubFilter(curr, bytesPerRow, bytesPerPixel); + break; + case PNG_FILTER_UP: + decodeUpFilter(curr, prior, bytesPerRow); + break; + case PNG_FILTER_AVERAGE: + decodeAverageFilter(curr, prior, bytesPerRow, bytesPerPixel); + break; + case PNG_FILTER_PAETH: + decodePaethFilter(curr, prior, bytesPerRow, bytesPerPixel); + break; + default: + // Error -- uknown filter type + String msg = PropertyUtil.getString("PNGImageDecoder16"); + throw new RuntimeException(msg); + } + + // Copy data into passRow byte by byte + if (bitDepth < 16) { + System.arraycopy(curr, 0, byteData, 0, bytesPerRow); + } else { + int idx = 0; + for (int j = 0; j < eltsPerRow; j++) { + shortData[j] = + (short)((curr[idx] << 8) | (curr[idx + 1] & 0xff)); + idx += 2; + } + } + + processPixels(postProcess, + passRow, imRas, xOffset, xStep, dstY, passWidth); + + // Swap curr and prior + byte[] tmp = prior; + prior = curr; + curr = tmp; + } + } + + private void decodeImage(boolean useInterlacing) { + int width = bounds.width; + int height = bounds.height; + + if (!useInterlacing) { + decodePass(theTile, 0, 0, 1, 1, width, height); + } else { + decodePass(theTile, 0, 0, 8, 8, (width + 7)/8, (height + 7)/8); + decodePass(theTile, 4, 0, 8, 8, (width + 3)/8, (height + 7)/8); + decodePass(theTile, 0, 4, 4, 8, (width + 3)/4, (height + 3)/8); + decodePass(theTile, 2, 0, 4, 4, (width + 1)/4, (height + 3)/4); + decodePass(theTile, 0, 2, 2, 4, (width + 1)/2, (height + 1)/4); + decodePass(theTile, 1, 0, 2, 2, width/2, (height + 1)/2); + decodePass(theTile, 0, 1, 1, 2, width, height/2); + } + } + + public WritableRaster copyData(WritableRaster wr) { + GraphicsUtil.copyData(theTile, wr); + return wr; + } + + // RenderedImage stuff + public Raster getTile(int tileX, int tileY) { + if (tileX != 0 || tileY != 0) { + // Error -- bad tile requested + String msg = PropertyUtil.getString("PNGImageDecoder17"); + throw new IllegalArgumentException(msg); + } + return theTile; + } +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/PNGSuggestedPaletteEntry.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/PNGSuggestedPaletteEntry.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/PNGSuggestedPaletteEntry.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/PNGSuggestedPaletteEntry.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,50 @@ +/* + + Copyright 2001 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.io.Serializable; + +/** + * A class representing the fields of a PNG suggested palette entry. + * + *

    This class is not a committed part of the JAI API. It may + * be removed or changed in future releases of JAI. + */ +public class PNGSuggestedPaletteEntry implements Serializable { + + /** The name of the entry. */ + public String name; + + /** The depth of the color samples. */ + public int sampleDepth; + + /** The red color value of the entry. */ + public int red; + + /** The green color value of the entry. */ + public int green; + + /** The blue color value of the entry. */ + public int blue; + + /** The alpha opacity value of the entry. */ + public int alpha; + + /** The probable frequency of the color in the image. */ + public int frequency; +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/PropertyUtil.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/PropertyUtil.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/PropertyUtil.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/PropertyUtil.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,40 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.util.MissingResourceException; + +import org.apache.batik.i18n.LocalizableSupport; + +public class PropertyUtil { + protected final static String RESOURCES = + "org.apache.batik.bridge.resources.properties"; + + + protected static LocalizableSupport localizableSupport = + new LocalizableSupport + (RESOURCES, PropertyUtil.class.getClassLoader()); + + public static String getString(String key) { + try{ + return localizableSupport.formatMessage(key, null); + }catch(MissingResourceException e){ + return key; + } + } +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/SeekableOutputStream.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/SeekableOutputStream.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/SeekableOutputStream.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/SeekableOutputStream.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,80 @@ +/* + + Copyright 2001 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; + +/** + * An OutputStream which can seek to an arbitrary offset. + */ +public class SeekableOutputStream extends OutputStream { + + private RandomAccessFile file; + + /** + * Constructs a SeekableOutputStream from a + * RandomAccessFile. Unless otherwise indicated, + * all method invocations are fowarded to the underlying + * RandomAccessFile. + * + * @param file The RandomAccessFile to which calls + * will be forwarded. + * @exception IllegalArgumentException if file is + * null. + */ + public SeekableOutputStream(RandomAccessFile file) { + if(file == null) { + throw new IllegalArgumentException("SeekableOutputStream0"); + } + this.file = file; + } + + public void write(int b) throws IOException { + file.write(b); + } + + public void write(byte b[]) throws IOException { + file.write(b); + } + + public void write(byte b[], int off, int len) throws IOException { + file.write(b, off, len); + } + + /** + * Invokes getFD().sync() on the underlying + * RandomAccessFile. + */ + public void flush() throws IOException { + file.getFD().sync(); + } + + public void close() throws IOException { + file.close(); + } + + public long getFilePointer() throws IOException { + return file.getFilePointer(); + } + + public void seek(long pos) throws IOException { + file.seek(pos); + } +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/SeekableStream.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/SeekableStream.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/SeekableStream.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/SeekableStream.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,930 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +/** + * An abstract subclass of java.io.InputStream that allows seeking + * within the input, similar to the RandomAccessFile class. + * Additionally, the DataInput interface is supported and extended + * to include support for little-endian representations of fundamental data + * types. + * + *

    In addition to the familiar methods from InputStream, the + * methods getFilePointer(), seek(), are defined as in + * the RandomAccessFile class. The canSeekBackwards() + * method will return true if it is permissible to seek to a + * position earlier in the stream than the current value of + * getFilePointer(). Some subclasses of + * SeekableStream guarantee the ability to seek backwards while + * others may not offer this feature in the interest of providing greater + * efficiency for those users who do not require it. + * + *

    The DataInput interface is supported as well. This included + * the skipBytes() and readFully() methods and a + * variety of read methods for various data types. + * + *

    A number of concrete subclasses of SeekableStream are + * supplied in the com.sun.media.jai.codec package. + * + *

    Three classes are provided for the purpose of adapting a standard + * InputStream to the SeekableStream interface. + * ForwardSeekableStream does not allows seeking backwards, but is + * inexpensive to use. FileCacheSeekableStream maintains a copy of + * all of the data read from the input in a temporary file; this file will be + * discarded automatically when the FileSeekableStream is + * finalized, or when the JVM exits normally. + * FileCacheSeekableStream is intended to be reasonably efficient + * apart from the unavoidable use of disk space. In circumstances where the + * creation of a temporary file is not possible, + * MemoryCacheSeekableStream may be used. + * MemoryCacheSeekableStream creates a potentially large in-memory + * buffer to store the stream data and so should be avoided when possible. + * + *

    The FileSeekableStream class wraps a File or + * RandomAccessFile. It forwards requests to the real underlying + * file. It performs a limited amount of caching in order to avoid excessive + * I/O costs. + * + *

    The SegmentedSeekableStream class performs a different sort + * of function. It creates a SeekableStream from another + * SeekableStream by selecting a series of portions or "segments". + * Each segment starts at a specified location within the source + * SeekableStream and extends for a specified number of bytes. The + * StreamSegmentMapper interface and StreamSegment + * class may be used to compute the segment positions dynamically. + * + *

    A convenience methods, wrapInputStream is provided to + * construct a suitable SeekableStream instance whose data is + * supplied by a given InputStream. The caller, by means of the + * canSeekBackwards parameter, determines whether support for + * seeking backwards is required. + * + */ +public abstract class SeekableStream extends InputStream implements DataInput { + + /** + * Returns a SeekableStream that will read from a + * given InputStream, optionally including support + * for seeking backwards. This is a convenience method that + * avoids the need to instantiate specific subclasses of + * SeekableStream depending on the current security + * model. + * + * @param is An InputStream. + * @param canSeekBackwards true if the ability to seek + * backwards in the output is required. + * @return An instance of SeekableStream. + */ + public static SeekableStream wrapInputStream(InputStream is, + boolean canSeekBackwards) { + SeekableStream stream = null; + + if (canSeekBackwards) { + try { + stream = new FileCacheSeekableStream(is); + } catch (Exception e) { + stream = new MemoryCacheSeekableStream(is); + } + } else { + stream = new ForwardSeekableStream(is); + } + return stream; + } + + // Methods from InputStream + + /** + * Reads the next byte of data from the input stream. The value byte is + * returned as an int in the range 0 to + * 255. If no byte is available because the end of the stream + * has been reached, the value -1 is returned. This method + * blocks until input data is available, the end of the stream is detected, + * or an exception is thrown. + * + *

    A subclass must provide an implementation of this method. + * + * @return the next byte of data, or -1 if the end of the + * stream is reached. + * @exception IOException if an I/O error occurs. + */ + public abstract int read() throws IOException; + + /** + * Reads up to len bytes of data from the input stream into + * an array of bytes. An attempt is made to read as many as + * len bytes, but a smaller number may be read, possibly + * zero. The number of bytes actually read is returned as an integer. + * + *

    This method blocks until input data is available, end of stream is + * detected, or an exception is thrown. + * + *

    If b is null, a + * NullPointerException is thrown. + * + *

    If off is negative, or len is negative, or + * off+len is greater than the length of the array + * b, then an IndexOutOfBoundsException is + * thrown. + * + *

    If len is zero, then no bytes are read and + * 0 is returned; otherwise, there is an attempt to read at + * least one byte. If no byte is available because the stream is at end of + * stream, the value -1 is returned; otherwise, at least one + * byte is read and stored into b. + * + *

    The first byte read is stored into element b[off], the + * next one into b[off+1], and so on. The number of bytes read + * is, at most, equal to len. Let k be the number of + * bytes actually read; these bytes will be stored in elements + * b[off] through b[off+k-1], + * leaving elements b[off+k] through + * b[off+len-1] unaffected. + * + *

    In every case, elements b[0] through + * b[off] and elements b[off+len] through + * b[b.length-1] are unaffected. + * + *

    If the first byte cannot be read for any reason other than end of + * stream, then an IOException is thrown. In particular, an + * IOException is thrown if the input stream has been closed. + * + *

    A subclass must provide an implementation of this method. + * + * @param b the buffer into which the data is read. + * @param off the start offset in array b + * at which the data is written. + * @param len the maximum number of bytes to read. + * @return the total number of bytes read into the buffer, or + * -1 if there is no more data because the end of + * the stream has been reached. + * @exception IOException if an I/O error occurs. + */ + public abstract int read(byte[] b, int off, int len) throws IOException; + + // Implemented in InputStream: + // + // public int read(byte[] b) throws IOException { + // public long skip(long n) throws IOException + // public int available) throws IOException + // public void close() throws IOException; + + /** Marked position */ + protected long markPos = -1L; + + /** + * Marks the current file position for later return using + * the reset() method. + */ + public synchronized void mark(int readLimit) { + try { + markPos = getFilePointer(); + } catch (IOException e) { + markPos = -1L; + } + } + + /** + * Returns the file position to its position at the time of + * the immediately previous call to the mark() + * method. + */ + public synchronized void reset() throws IOException { + if (markPos != -1) { + seek(markPos); + } + } + + /** + * Returns true if marking is supported. + * Marking is automatically supported for SeekableStream + * subclasses that support seeking backeards. Subclasses that do + * not support seeking backwards but do support marking must override + * this method. + */ + public boolean markSupported() { + return canSeekBackwards(); + } + + /** + * Returns true if this object supports calls to + * seek(pos) with an offset pos smaller + * than the current offset, as returned by getFilePointer. + */ + public boolean canSeekBackwards() { + return false; + } + + /** + * Returns the current offset in this stream. + * + * @return the offset from the beginning of the stream, in bytes, + * at which the next read occurs. + * @exception IOException if an I/O error occurs. + */ + public abstract long getFilePointer() throws IOException; + + /** + * Sets the offset, measured from the beginning of this + * stream, at which the next read occurs. + * + *

    If canSeekBackwards() returns false, + * then setting pos to an offset smaller than + * the current value of getFilePointer() will have + * no effect. + * + * @param pos the offset position, measured in bytes from the + * beginning of the stream, at which to set the stream + * pointer. + * @exception IOException if pos is less than + * 0 or if an I/O error occurs. + */ + public abstract void seek(long pos) throws IOException; + + // Methods from RandomAccessFile + + /** + * Reads b.length bytes from this stream into the byte + * array, starting at the current stream pointer. This method reads + * repeatedly from the stream until the requested number of bytes are + * read. This method blocks until the requested number of bytes are + * read, the end of the stream is detected, or an exception is thrown. + * + * @param b the buffer into which the data is read. + * @exception EOFException if this stream reaches the end before reading + * all the bytes. + * @exception IOException if an I/O error occurs. + */ + public final void readFully(byte[] b) throws IOException { + readFully(b, 0, b.length); + } + + /** + * Reads exactly len bytes from this stream into the byte + * array, starting at the current stream pointer. This method reads + * repeatedly from the stream until the requested number of bytes are + * read. This method blocks until the requested number of bytes are + * read, the end of the stream is detected, or an exception is thrown. + * + * @param b the buffer into which the data is read. + * @param off the start offset of the data. + * @param len the number of bytes to read. + * @exception EOFException if this stream reaches the end before reading + * all the bytes. + * @exception IOException if an I/O error occurs. + */ + public final void readFully(byte[] b, int off, int len) + throws IOException { + int n = 0; + do { + int count = this.read(b, off + n, len - n); + if (count < 0) + throw new EOFException(); + n += count; + } while (n < len); + } + + // Methods from DataInput, plus little-endian versions + + /** + * Attempts to skip over n bytes of input discarding the + * skipped bytes. + *

    + * + * This method may skip over some smaller number of bytes, possibly zero. + * This may result from any of a number of conditions; reaching end of + * stream before n bytes have been skipped is only one + * possibility. This method never throws an EOFException. + * The actual number of bytes skipped is returned. If n + * is negative, no bytes are skipped. + * + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. + * @exception IOException if an I/O error occurs. + */ + public int skipBytes(int n) throws IOException { + if (n <= 0) { + return 0; + } + return (int)skip(n); + } + + /** + * Reads a boolean from this stream. This method reads a + * single byte from the stream, starting at the current stream pointer. + * A value of 0 represents + * false. Any other value represents true. + * This method blocks until the byte is read, the end of the stream + * is detected, or an exception is thrown. + * + * @return the boolean value read. + * @exception EOFException if this stream has reached the end. + * @exception IOException if an I/O error occurs. + */ + public final boolean readBoolean() throws IOException { + int ch = this.read(); + if (ch < 0) + throw new EOFException(); + return (ch != 0); + } + + /** + * Reads a signed eight-bit value from this stream. This method reads a + * byte from the stream, starting from the current stream pointer. + * If the byte read is b, where + * 0 <= b <= 255, + * then the result is: + *

    +     *     (byte)(b)
    +     * 
    + *

    + * This method blocks until the byte is read, the end of the stream + * is detected, or an exception is thrown. + * + * @return the next byte of this stream as a signed eight-bit + * byte. + * @exception EOFException if this stream has reached the end. + * @exception IOException if an I/O error occurs. + */ + public final byte readByte() throws IOException { + int ch = this.read(); + if (ch < 0) + throw new EOFException(); + return (byte)(ch); + } + + /** + * Reads an unsigned eight-bit number from this stream. This method reads + * a byte from this stream, starting at the current stream pointer, + * and returns that byte. + *

    + * This method blocks until the byte is read, the end of the stream + * is detected, or an exception is thrown. + * + * @return the next byte of this stream, interpreted as an unsigned + * eight-bit number. + * @exception EOFException if this stream has reached the end. + * @exception IOException if an I/O error occurs. + */ + public final int readUnsignedByte() throws IOException { + int ch = this.read(); + if (ch < 0) + throw new EOFException(); + return ch; + } + + /** + * Reads a signed 16-bit number from this stream. + * The method reads two + * bytes from this stream, starting at the current stream pointer. + * If the two bytes read, in order, are + * b1 and b2, where each of the two values is + * between 0 and 255, inclusive, then the + * result is equal to: + *

    +     *     (short)((b1 << 8) | b2)
    +     * 
    + *

    + * This method blocks until the two bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next two bytes of this stream, interpreted as a signed + * 16-bit number. + * @exception EOFException if this stream reaches the end before reading + * two bytes. + * @exception IOException if an I/O error occurs. + */ + public final short readShort() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + if ((ch1 | ch2) < 0) + throw new EOFException(); + return (short)((ch1 << 8) + (ch2 << 0)); + } + + /** + * Reads a signed 16-bit number from this stream in little-endian order. + * The method reads two + * bytes from this stream, starting at the current stream pointer. + * If the two bytes read, in order, are + * b1 and b2, where each of the two values is + * between 0 and 255, inclusive, then the + * result is equal to: + *

    +     *     (short)((b2 << 8) | b1)
    +     * 
    + *

    + * This method blocks until the two bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next two bytes of this stream, interpreted as a signed + * 16-bit number. + * @exception EOFException if this stream reaches the end before reading + * two bytes. + * @exception IOException if an I/O error occurs. + */ + public final short readShortLE() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + if ((ch1 | ch2) < 0) + throw new EOFException(); + return (short)((ch2 << 8) + (ch1 << 0)); + } + + /** + * Reads an unsigned 16-bit number from this stream. This method reads + * two bytes from the stream, starting at the current stream pointer. + * If the bytes read, in order, are + * b1 and b2, where + * 0 <= b1, b2 <= 255, + * then the result is equal to: + *

    +     *     (b1 << 8) | b2
    +     * 
    + *

    + * This method blocks until the two bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next two bytes of this stream, interpreted as an + * unsigned 16-bit integer. + * @exception EOFException if this stream reaches the end before reading + * two bytes. + * @exception IOException if an I/O error occurs. + */ + public final int readUnsignedShort() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + if ((ch1 | ch2) < 0) + throw new EOFException(); + return (ch1 << 8) + (ch2 << 0); + } + + /** + * Reads an unsigned 16-bit number from this stream in little-endian order. + * This method reads + * two bytes from the stream, starting at the current stream pointer. + * If the bytes read, in order, are + * b1 and b2, where + * 0 <= b1, b2 <= 255, + * then the result is equal to: + *

    +     *     (b2 << 8) | b1
    +     * 
    + *

    + * This method blocks until the two bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next two bytes of this stream, interpreted as an + * unsigned 16-bit integer. + * @exception EOFException if this stream reaches the end before reading + * two bytes. + * @exception IOException if an I/O error occurs. + */ + public final int readUnsignedShortLE() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + if ((ch1 | ch2) < 0) + throw new EOFException(); + return (ch2 << 8) + (ch1 << 0); + } + + /** + * Reads a Unicode character from this stream. This method reads two + * bytes from the stream, starting at the current stream pointer. + * If the bytes read, in order, are + * b1 and b2, where + * 0 <= b1, b2 <= 255, + * then the result is equal to: + *

    +     *     (char)((b1 << 8) | b2)
    +     * 
    + *

    + * This method blocks until the two bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next two bytes of this stream as a Unicode character. + * @exception EOFException if this stream reaches the end before reading + * two bytes. + * @exception IOException if an I/O error occurs. + */ + public final char readChar() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + if ((ch1 | ch2) < 0) + throw new EOFException(); + return (char)((ch1 << 8) + (ch2 << 0)); + } + + /** + * Reads a Unicode character from this stream in little-endian order. + * This method reads two + * bytes from the stream, starting at the current stream pointer. + * If the bytes read, in order, are + * b1 and b2, where + * 0 <= b1, b2 <= 255, + * then the result is equal to: + *

    +     *     (char)((b2 << 8) | b1)
    +     * 
    + *

    + * This method blocks until the two bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next two bytes of this stream as a Unicode character. + * @exception EOFException if this stream reaches the end before reading + * two bytes. + * @exception IOException if an I/O error occurs. + */ + public final char readCharLE() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + if ((ch1 | ch2) < 0) + throw new EOFException(); + return (char)((ch2 << 8) + (ch1 << 0)); + } + + /** + * Reads a signed 32-bit integer from this stream. This method reads 4 + * bytes from the stream, starting at the current stream pointer. + * If the bytes read, in order, are b1, + * b2, b3, and b4, where + * 0 <= b1, b2, b3, b4 <= 255, + * then the result is equal to: + *

    +     *     (b1 << 24) | (b2 << 16) + (b3 << 8) + b4
    +     * 
    + *

    + * This method blocks until the four bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next four bytes of this stream, interpreted as an + * int. + * @exception EOFException if this stream reaches the end before reading + * four bytes. + * @exception IOException if an I/O error occurs. + */ + public final int readInt() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + int ch3 = this.read(); + int ch4 = this.read(); + if ((ch1 | ch2 | ch3 | ch4) < 0) + throw new EOFException(); + return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); + } + + /** + * Reads a signed 32-bit integer from this stream in little-endian order. + * This method reads 4 + * bytes from the stream, starting at the current stream pointer. + * If the bytes read, in order, are b1, + * b2, b3, and b4, where + * 0 <= b1, b2, b3, b4 <= 255, + * then the result is equal to: + *

    +     *     (b4 << 24) | (b3 << 16) + (b2 << 8) + b1
    +     * 
    + *

    + * This method blocks until the four bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next four bytes of this stream, interpreted as an + * int. + * @exception EOFException if this stream reaches the end before reading + * four bytes. + * @exception IOException if an I/O error occurs. + */ + public final int readIntLE() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + int ch3 = this.read(); + int ch4 = this.read(); + if ((ch1 | ch2 | ch3 | ch4) < 0) + throw new EOFException(); + return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0)); + } + + /** + * Reads an unsigned 32-bit integer from this stream. This method reads 4 + * bytes from the stream, starting at the current stream pointer. + * If the bytes read, in order, are b1, + * b2, b3, and b4, where + * 0 <= b1, b2, b3, b4 <= 255, + * then the result is equal to: + *

    +     *     (b1 << 24) | (b2 << 16) + (b3 << 8) + b4
    +     * 
    + *

    + * This method blocks until the four bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next four bytes of this stream, interpreted as a + * long. + * @exception EOFException if this stream reaches the end before reading + * four bytes. + * @exception IOException if an I/O error occurs. + */ + public final long readUnsignedInt() throws IOException { + long ch1 = this.read(); + long ch2 = this.read(); + long ch3 = this.read(); + long ch4 = this.read(); + if ((ch1 | ch2 | ch3 | ch4) < 0) + throw new EOFException(); + return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); + } + + private byte[] ruileBuf = new byte[4]; + + /** + * Reads an unsigned 32-bit integer from this stream in little-endian + * order. This method reads 4 + * bytes from the stream, starting at the current stream pointer. + * If the bytes read, in order, are b1, + * b2, b3, and b4, where + * 0 <= b1, b2, b3, b4 <= 255, + * then the result is equal to: + *

    +     *     (b4 << 24) | (b3 << 16) + (b2 << 8) + b1
    +     * 
    + *

    + * This method blocks until the four bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next four bytes of this stream, interpreted as a + * long. + * @exception EOFException if this stream reaches the end before reading + * four bytes. + * @exception IOException if an I/O error occurs. + */ + public final long readUnsignedIntLE() throws IOException { + this.readFully(ruileBuf); + long ch1 = (ruileBuf[0] & 0xff); + long ch2 = (ruileBuf[1] & 0xff); + long ch3 = (ruileBuf[2] & 0xff); + long ch4 = (ruileBuf[3] & 0xff); + + return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0)); + } + + /** + * Reads a signed 64-bit integer from this stream. This method reads eight + * bytes from the stream, starting at the current stream pointer. + * If the bytes read, in order, are + * b1, b2, b3, + * b4, b5, b6, + * b7, and b8, where: + *

    +     *     0 <= b1, b2, b3, b4, b5, b6, b7, b8 <=255,
    +     * 
    + *

    + * then the result is equal to: + *

    +     *     ((long)b1 << 56) + ((long)b2 << 48)
    +     *     + ((long)b3 << 40) + ((long)b4 << 32)
    +     *     + ((long)b5 << 24) + ((long)b6 << 16)
    +     *     + ((long)b7 << 8) + b8
    +     * 
    + *

    + * This method blocks until the eight bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next eight bytes of this stream, interpreted as a + * long. + * @exception EOFException if this stream reaches the end before reading + * eight bytes. + * @exception IOException if an I/O error occurs. + */ + public final long readLong() throws IOException { + return ((long)(readInt()) << 32) + (readInt() & 0xFFFFFFFFL); + } + + /** + * Reads a signed 64-bit integer from this stream in little-endian + * order. This method reads eight + * bytes from the stream, starting at the current stream pointer. + * If the bytes read, in order, are + * b1, b2, b3, + * b4, b5, b6, + * b7, and b8, where: + *

    +     *     0 <= b1, b2, b3, b4, b5, b6, b7, b8 <=255,
    +     * 
    + *

    + * then the result is equal to: + *

    +     *     ((long)b1 << 56) + ((long)b2 << 48)
    +     *     + ((long)b3 << 40) + ((long)b4 << 32)
    +     *     + ((long)b5 << 24) + ((long)b6 << 16)
    +     *     + ((long)b7 << 8) + b8
    +     * 
    + *

    + * This method blocks until the eight bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next eight bytes of this stream, interpreted as a + * long. + * @exception EOFException if this stream reaches the end before reading + * eight bytes. + * @exception IOException if an I/O error occurs. + */ + public final long readLongLE() throws IOException { + int i1 = readIntLE(); + int i2 = readIntLE(); + return ((long)i2 << 32) + (i1 & 0xFFFFFFFFL); + } + + /** + * Reads a float from this stream. This method reads an + * int value, starting at the current stream pointer, + * as if by the readInt method + * and then converts that int to a float + * using the intBitsToFloat method in class + * Float. + *

    + * This method blocks until the four bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next four bytes of this stream, interpreted as a + * float. + * @exception EOFException if this stream reaches the end before reading + * four bytes. + * @exception IOException if an I/O error occurs. + */ + public final float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + /** + * Reads a float from this stream in little-endian order. + * This method reads an + * int value, starting at the current stream pointer, + * as if by the readInt method + * and then converts that int to a float + * using the intBitsToFloat method in class + * Float. + *

    + * This method blocks until the four bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next four bytes of this stream, interpreted as a + * float. + * @exception EOFException if this stream reaches the end before reading + * four bytes. + * @exception IOException if an I/O error occurs. + */ + public final float readFloatLE() throws IOException { + return Float.intBitsToFloat(readIntLE()); + } + + /** + * Reads a double from this stream. This method reads a + * long value, starting at the current stream pointer, + * as if by the readLong method + * and then converts that long to a double + * using the longBitsToDouble method in + * class Double. + *

    + * This method blocks until the eight bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next eight bytes of this stream, interpreted as a + * double. + * @exception EOFException if this stream reaches the end before reading + * eight bytes. + * @exception IOException if an I/O error occurs. + */ + public final double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + /** + * Reads a double from this stream in little-endian order. + * This method reads a + * long value, starting at the current stream pointer, + * as if by the readLong method + * and then converts that long to a double + * using the longBitsToDouble method in + * class Double. + *

    + * This method blocks until the eight bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next eight bytes of this stream, interpreted as a + * double. + * @exception EOFException if this stream reaches the end before reading + * eight bytes. + * @exception IOException if an I/O error occurs. + */ + public final double readDoubleLE() throws IOException { + return Double.longBitsToDouble(readLongLE()); + } + + /** + * Reads the next line of text from this stream. This method successively + * reads bytes from the stream, starting at the current stream pointer, + * until it reaches a line terminator or the end + * of the stream. Each byte is converted into a character by taking the + * byte's value for the lower eight bits of the character and setting the + * high eight bits of the character to zero. This method does not, + * therefore, support the full Unicode character set. + * + *

    A line of text is terminated by a carriage-return character + * ('\r'), a newline character ('\n'), a + * carriage-return character immediately followed by a newline character, + * or the end of the stream. Line-terminating characters are discarded and + * are not included as part of the string returned. + * + *

    This method blocks until a newline character is read, a carriage + * return and the byte following it are read (to see if it is a newline), + * the end of the stream is reached, or an exception is thrown. + * + * @return the next line of text from this stream, or null if end + * of stream is encountered before even one byte is read. + * @exception IOException if an I/O error occurs. + */ + public final String readLine() throws IOException { + StringBuffer input = new StringBuffer(); + int c = -1; + boolean eol = false; + + while (!eol) { + switch (c = read()) { + case -1: + case '\n': + eol = true; + break; + case '\r': + eol = true; + long cur = getFilePointer(); + if ((read()) != '\n') { + seek(cur); + } + break; + default: + input.append((char)c); + break; + } + } + + if ((c == -1) && (input.length() == 0)) { + return null; + } + return input.toString(); + } + + /** + * Reads in a string from this stream. The string has been encoded + * using a modified UTF-8 format. + *

    + * The first two bytes are read, starting from the current stream + * pointer, as if by + * readUnsignedShort. This value gives the number of + * following bytes that are in the encoded string, not + * the length of the resulting string. The following bytes are then + * interpreted as bytes encoding characters in the UTF-8 format + * and are converted into characters. + *

    + * This method blocks until all the bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return a Unicode string. + * @exception EOFException if this stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + * @exception UTFDataFormatException if the bytes do not represent + * valid UTF-8 encoding of a Unicode string. + */ + public final String readUTF() throws IOException { + return DataInputStream.readUTF(this); + } + + /** + * Releases any system resources associated with this stream + * by calling the close() method. + */ + protected void finalize() throws Throwable { + super.finalize(); + close(); + } +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/SimpleRenderedImage.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/SimpleRenderedImage.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/SimpleRenderedImage.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/SimpleRenderedImage.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,536 @@ +/* + + Copyright 2001,2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package org.apache.batik.ext.awt.image.myCodec; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.image.ColorModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Vector; + +/** + * A simple class implemented the RenderedImage + * interface. Only the getTile() method needs to be + * implemented by subclasses. The instance variables must also be + * filled in properly. + * + *

    Normally in JAI PlanarImage is used for this + * purpose, but in the interest of modularity the + * use of PlanarImage has been avoided. + */ +public abstract class SimpleRenderedImage implements RenderedImage { + + /** The X coordinate of the image's upper-left pixel. */ + protected int minX; + + /** The Y coordinate of the image's upper-left pixel. */ + protected int minY; + + /** The image's width in pixels. */ + protected int width; + + /** The image's height in pixels. */ + protected int height; + + /** The width of a tile. */ + protected int tileWidth; + + /** The height of a tile. */ + protected int tileHeight; + + /** The X coordinate of the upper-left pixel of tile (0, 0). */ + protected int tileGridXOffset = 0; + + /** The Y coordinate of the upper-left pixel of tile (0, 0). */ + protected int tileGridYOffset = 0; + + /** The image's SampleModel. */ + protected SampleModel sampleModel = null; + + /** The image's ColorModel. */ + protected ColorModel colorModel = null; + + /** The image's sources, stored in a Vector. */ + protected Vector sources = new Vector(); + + /** A Hashtable containing the image properties. */ + protected Hashtable properties = new Hashtable(); + + public SimpleRenderedImage() {} + + /** Returns the X coordinate of the leftmost column of the image. */ + public int getMinX() { + return minX; + } + + /** + * Returns the X coordinate of the column immediatetely to the + * right of the rightmost column of the image. getMaxX() is + * implemented in terms of getMinX() and getWidth() and so does + * not need to be implemented by subclasses. + */ + public final int getMaxX() { + return getMinX() + getWidth(); + } + + /** Returns the X coordinate of the uppermost row of the image. */ + public int getMinY() { + return minY; + } + + /** + * Returns the Y coordinate of the row immediately below the + * bottom row of the image. getMaxY() is implemented in terms of + * getMinY() and getHeight() and so does not need to be + * implemented by subclasses. + */ + public final int getMaxY() { + return getMinY() + getHeight(); + } + + /** Returns the width of the image. */ + public int getWidth() { + return width; + } + + /** Returns the height of the image. */ + public int getHeight() { + return height; + } + + /** Returns a Rectangle indicating the image bounds. */ + public Rectangle getBounds() { + return new Rectangle(getMinX(), getMinY(), + getWidth(), getHeight()); + } + + /** Returns the width of a tile. */ + public int getTileWidth() { + return tileWidth; + } + + /** Returns the height of a tile. */ + public int getTileHeight() { + return tileHeight; + } + + /** + * Returns the X coordinate of the upper-left pixel of tile (0, 0). + */ + public int getTileGridXOffset() { + return tileGridXOffset; + } + + /** + * Returns the Y coordinate of the upper-left pixel of tile (0, 0). + */ + public int getTileGridYOffset() { + return tileGridYOffset; + } + + /** + * Returns the horizontal index of the leftmost column of tiles. + * getMinTileX() is implemented in terms of getMinX() + * and so does not need to be implemented by subclasses. + */ + public int getMinTileX() { + return XToTileX(getMinX()); + } + + /** + * Returns the horizontal index of the rightmost column of tiles. + * getMaxTileX() is implemented in terms of getMaxX() + * and so does not need to be implemented by subclasses. + */ + public int getMaxTileX() { + return XToTileX(getMaxX() - 1); + } + + /** + * Returns the number of tiles along the tile grid in the + * horizontal direction. getNumXTiles() is implemented in terms + * of getMinTileX() and getMaxTileX() and so does not need to be + * implemented by subclasses. + */ + public int getNumXTiles() { + return getMaxTileX() - getMinTileX() + 1; + } + + /** + * Returns the vertical index of the uppermost row of tiles. getMinTileY() + * is implemented in terms of getMinY() and so does not need to be + * implemented by subclasses. + */ + public int getMinTileY() { + return YToTileY(getMinY()); + } + + /** + * Returns the vertical index of the bottom row of tiles. getMaxTileY() + * is implemented in terms of getMaxY() and so does not need to + * be implemented by subclasses. + */ + public int getMaxTileY() { + return YToTileY(getMaxY() - 1); + } + + /** + * Returns the number of tiles along the tile grid in the vertical + * direction. getNumYTiles() is implemented in terms + * of getMinTileY() and getMaxTileY() and so does not need to be + * implemented by subclasses. + */ + public int getNumYTiles() { + return getMaxTileY() - getMinTileY() + 1; + } + + /** Returns the SampleModel of the image. */ + public SampleModel getSampleModel() { + return sampleModel; + } + + /** Returns the ColorModel of the image. */ + public ColorModel getColorModel() { + return colorModel; + } + + /** + * Gets a property from the property set of this image. If the + * property name is not recognized, + * java.awt.Image.UndefinedProperty will be returned. + * + * @param name the name of the property to get, as a + * String. @return a reference to the property + * Object, or the value + * java.awt.Image.UndefinedProperty. + */ + public Object getProperty(String name) { + name = name.toLowerCase(); + return properties.get(name); + } + + /** + * Returns a list of the properties recognized by this image. If + * no properties are available, null will be + * returned. + * + * @return an array of Strings representing valid + * property names. + */ + public String[] getPropertyNames() { + String[] names = new String[properties.size()]; + int index = 0; + + Enumeration e = properties.keys(); + while (e.hasMoreElements()) { + String name = (String)e.nextElement(); + names[index++] = name; + } + + return names; + } + + /** + * Returns an array of Strings recognized as names by + * this property source that begin with the supplied prefix. If + * no property names match, null will be returned. + * The comparison is done in a case-independent manner. + * + *

    The default implementation calls + * getPropertyNames() and searches the list of names + * for matches. + * + * @return an array of Strings giving the valid + * property names. + */ + public String[] getPropertyNames(String prefix) { + String propertyNames[] = getPropertyNames(); + if (propertyNames == null) { + return null; + } + + prefix = prefix.toLowerCase(); + + Vector names = new Vector(); + for (int i = 0; i < propertyNames.length; i++) { + if (propertyNames[i].startsWith(prefix)) { + names.addElement(propertyNames[i]); + } + } + + if (names.size() == 0) { + return null; + } + + // Copy the strings from the Vector over to a String array. + String prefixNames[] = new String[names.size()]; + int count = 0; + for (Iterator it = names.iterator(); it.hasNext(); ) { + prefixNames[count++] = (String)it.next(); + } + + return prefixNames; + } + + // Utility methods. + + /** + * Converts a pixel's X coordinate into a horizontal tile index + * relative to a given tile grid layout specified by its X offset + * and tile width. + */ + public static int XToTileX(int x, int tileGridXOffset, int tileWidth) { + x -= tileGridXOffset; + if (x < 0) { + x += 1 - tileWidth; // Force round to -infinity + } + return x/tileWidth; + } + + /** + * Converts a pixel's Y coordinate into a vertical tile index + * relative to a given tile grid layout specified by its Y offset + * and tile height. + */ + public static int YToTileY(int y, int tileGridYOffset, int tileHeight) { + y -= tileGridYOffset; + if (y < 0) { + y += 1 - tileHeight; // Force round to -infinity + } + return y/tileHeight; + } + + /** + * Converts a pixel's X coordinate into a horizontal tile index. + * This is a convenience method. No attempt is made to detect + * out-of-range coordinates. + * + * @param x the X coordinate of a pixel. + * @return the X index of the tile containing the pixel. + */ + public int XToTileX(int x) { + return XToTileX(x, getTileGridXOffset(), getTileWidth()); + } + + /** + * Converts a pixel's Y coordinate into a vertical tile index. + * This is a convenience method. No attempt is made to detect + * out-of-range coordinates. + * + * @param y the Y coordinate of a pixel. + * @return the Y index of the tile containing the pixel. + */ + public int YToTileY(int y) { + return YToTileY(y, getTileGridYOffset(), getTileHeight()); + } + + /** + * Converts a horizontal tile index into the X coordinate of its + * upper left pixel relative to a given tile grid layout specified + * by its X offset and tile width. + */ + public static int tileXToX(int tx, int tileGridXOffset, int tileWidth) { + return tx*tileWidth + tileGridXOffset; + } + + /** + * Converts a vertical tile index into the Y coordinate of + * its upper left pixel relative to a given tile grid layout + * specified by its Y offset and tile height. + */ + public static int tileYToY(int ty, int tileGridYOffset, int tileHeight) { + return ty*tileHeight + tileGridYOffset; + } + + /** + * Converts a horizontal tile index into the X coordinate of its + * upper left pixel. This is a convenience method. No attempt is made + * to detect out-of-range indices. + * + * @param tx the horizontal index of a tile. + * @return the X coordinate of the tile's upper left pixel. + */ + public int tileXToX(int tx) { + return tx*tileWidth + tileGridXOffset; + } + + /** + * Converts a vertical tile index into the Y coordinate of its + * upper left pixel. This is a convenience method. No attempt is made + * to detect out-of-range indices. + * + * @param ty the vertical index of a tile. + * @return the Y coordinate of the tile's upper left pixel. + */ + public int tileYToY(int ty) { + return ty*tileHeight + tileGridYOffset; + } + + public Vector getSources() { + return null; + } + + /** + * Returns the entire image in a single Raster. For images with + * multiple tiles this will require making a copy. + * + *

    The returned Raster is semantically a copy. This means + * that updates to the source image will not be reflected in the + * returned Raster. For non-writable (immutable) source images, + * the returned value may be a reference to the image's internal + * data. The returned Raster should be considered non-writable; + * any attempt to alter its pixel data (such as by casting it to + * WritableRaster or obtaining and modifying its DataBuffer) may + * result in undefined behavior. The copyData method should be + * used if the returned Raster is to be modified. + * + * @return a Raster containing a copy of this image's data. + */ + public Raster getData() { + Rectangle rect = new Rectangle(getMinX(), getMinY(), + getWidth(), getHeight()); + return getData(rect); + } + + /** + * Returns an arbitrary rectangular region of the RenderedImage + * in a Raster. The rectangle of interest will be clipped against + * the image bounds. + * + *

    The returned Raster is semantically a copy. This means + * that updates to the source image will not be reflected in the + * returned Raster. For non-writable (immutable) source images, + * the returned value may be a reference to the image's internal + * data. The returned Raster should be considered non-writable; + * any attempt to alter its pixel data (such as by casting it to + * WritableRaster or obtaining and modifying its DataBuffer) may + * result in undefined behavior. The copyData method should be + * used if the returned Raster is to be modified. + * + * @param bounds the region of the RenderedImage to be returned. + */ + public Raster getData(Rectangle bounds) { + int startX = XToTileX(bounds.x); + int startY = YToTileY(bounds.y); + int endX = XToTileX(bounds.x + bounds.width - 1); + int endY = YToTileY(bounds.y + bounds.height - 1); + Raster tile; + + if ((startX == endX) && (startY == endY)) { + tile = getTile(startX, startY); + return tile.createChild(bounds.x, bounds.y, + bounds.width, bounds.height, + bounds.x, bounds.y, null); + } else { + // Create a WritableRaster of the desired size + SampleModel sm = + sampleModel.createCompatibleSampleModel(bounds.width, + bounds.height); + + // Translate it + WritableRaster dest = + Raster.createWritableRaster(sm, bounds.getLocation()); + + for (int j = startY; j <= endY; j++) { + for (int i = startX; i <= endX; i++) { + tile = getTile(i, j); + Rectangle intersectRect = + bounds.intersection(tile.getBounds()); + Raster liveRaster = tile.createChild(intersectRect.x, + intersectRect.y, + intersectRect.width, + intersectRect.height, + intersectRect.x, + intersectRect.y, + null); + dest.setDataElements(0, 0, liveRaster); + } + } + return dest; + } + } + + /** + * Copies an arbitrary rectangular region of the RenderedImage + * into a caller-supplied WritableRaster. The region to be + * computed is determined by clipping the bounds of the supplied + * WritableRaster against the bounds of the image. The supplied + * WritableRaster must have a SampleModel that is compatible with + * that of the image. + * + *

    If the raster argument is null, the entire image will + * be copied into a newly-created WritableRaster with a SampleModel + * that is compatible with that of the image. + * + * @param dest a WritableRaster to hold the returned portion of + * the image. + * @return a reference to the supplied WritableRaster, or to a + * new WritableRaster if the supplied one was null. + */ + public WritableRaster copyData(WritableRaster dest) { + Rectangle bounds; + Raster tile; + + if (dest == null) { + bounds = getBounds(); + Point p = new Point(minX, minY); + /* A SampleModel to hold the entire image. */ + SampleModel sm = sampleModel.createCompatibleSampleModel( + width, height); + dest = Raster.createWritableRaster(sm, p); + } else { + bounds = dest.getBounds(); + } + + int startX = XToTileX(bounds.x); + int startY = YToTileY(bounds.y); + int endX = XToTileX(bounds.x + bounds.width - 1); + int endY = YToTileY(bounds.y + bounds.height - 1); + + for (int j = startY; j <= endY; j++) { + for (int i = startX; i <= endX; i++) { + tile = getTile(i, j); + Rectangle intersectRect = + bounds.intersection(tile.getBounds()); + Raster liveRaster = tile.createChild(intersectRect.x, + intersectRect.y, + intersectRect.width, + intersectRect.height, + intersectRect.x, + intersectRect.y, + null); + + /* + * WritableRaster.setDataElements takes into account of + * inRaster's minX and minY and add these to x and y. Since + * liveRaster has the origin at the correct location, the + * following call should not again give these coordinates in + * places of x and y. + */ + dest.setDataElements(0, 0, liveRaster); + } + } + return dest; + } +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/SingleTileRenderedImage.java =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/SingleTileRenderedImage.java diff -N src/java/org/apache/batik/ext/awt/image/myCodec/SingleTileRenderedImage.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/SingleTileRenderedImage.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,60 @@ +/* + + Copyright 1999-2003 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +package org.apache.batik.ext.awt.image.myCodec; + +import java.awt.image.ColorModel; +import java.awt.image.Raster; + +/** + * A simple class that provides RenderedImage functionality + * given a Raster and a ColorModel. + */ +public class SingleTileRenderedImage extends SimpleRenderedImage { + + Raster ras; + + /** + * Constructs a SingleTileRenderedImage based on a Raster + * and a ColorModel. + * + * @param ras A Raster that will define tile (0, 0) of the image. + * @param cm A ColorModel that will serve as the image's + * ColorModel. + */ + public SingleTileRenderedImage(Raster ras, ColorModel colorModel) { + this.ras = ras; + + this.tileGridXOffset = this.minX = ras.getMinX(); + this.tileGridYOffset = this.minY = ras.getMinY(); + this.tileWidth = this.width = ras.getWidth(); + this.tileHeight = this.height = ras.getHeight(); + this.sampleModel = ras.getSampleModel(); + this.colorModel = colorModel; + } + + /** + * Returns the image's Raster as tile (0, 0). + */ + public Raster getTile(int tileX, int tileY) { + if (tileX != 0 || tileY != 0) { + throw new IllegalArgumentException(PropertyUtil.getString("SingleTileRenderedImage0")); + } + return ras; + } +} Index: src/java/org/apache/batik/ext/awt/image/myCodec/package.html =================================================================== RCS file: src/java/org/apache/batik/ext/awt/image/myCodec/package.html diff -N src/java/org/apache/batik/ext/awt/image/myCodec/package.html --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/batik/ext/awt/image/myCodec/package.html 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,11 @@ + + + + org.apache.batik.ext.awt.image.codec + + + +Provides a set of classes to encode/decode images in various raster +image file formats. + + Index: src/java/org/apache/fop/render/awt/viewer/images/fopLogo.gif =================================================================== RCS file: src/java/org/apache/fop/render/awt/viewer/images/fopLogo.gif diff -N src/java/org/apache/fop/render/awt/viewer/images/fopLogo.gif --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/fop/render/awt/viewer/images/fopLogo.gif 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,11 @@ +GIF89a,÷ Ù#GÄ<¾¾¾¸]k}}}RRXÊÊÊ666wptóóóîîî%%%æææäää\\\222iiibbb§¦¦ÒÒÒBBBýþþW:ËËÌ???DED===pppœœœ˜˜˜ºººÉ$BWVW¶¶¶""!ddd÷÷÷ªªª• =```ttu•••‘‘‘ÈÈÈRRRÆÆÆ´´´xxx‹KšššllljjjCBE!*&OOO„„„‚‚‚KKK$---ŠŠŠ,,,fff)))**+^^^dcc[Z[oooÃÃÃþþþ¥¥¥ÐÐÐüüüëëëvvvÙÙÙíííÝÝÝôôôÖÖÖÔÔÔ———¤¤¤ˆˆˆøøøããã–––³³³ýýýssséééÑÑÑêêꇇ‡ÞÞÞùùùŒŒŒ   ØØØþÿÿÍÍÍìììÜÜÜÏÏÏÎÎÎááᬬ¬¯¯¯£££HHH‰‰‰ßßß6 JÿþþÓÓÓŒ-6= @pvq'('’”—N///NNRXW[l8-IJKíïíGTN±±±¨¨¨ƒ+! %ñññÐÏÑ©=¹¹¹âââÅÅÅLLL9=?C/DùíëqsuÉÆÅÊÌÇ“”“ghg\D:ggl?ÐÓÐ@GE›š?ÑÂw‡ˆ‡ñæÔŠŠ‡´´³º‹‹³†—¦|*­2`îÀÅÀÂÁu5fµEaÏp_üþý€€oyuŸŸŸ"!KPTô÷÷“““^EEëëí©7LûûûÖq}½R— .ˆ??T V 433··¸ËË˶°?Ã>PdKMrxvFFLçççúõ貚˜#$#½·¨hfl×××˵uúùû,85–‹ˆ¤¢¢W\_Œ‰‹o?O 8•¡ž]]]‚Fefg?‘‘餧,,.^e_à +?ÊAÌÊÏÊÌ̳°«F+?@=>¶›J?0³ 5Â?YŠUV¥N¯Et>Ey5IpR?­¨1$+‰‰ˆóÎÓh)ÌÌÌÿÿÿ!ù,,ÿ?øH° Áƒ5P§)™4„{4ùG±¢Å‹-î™õïR5w§2V|2Q¤ÉŒmÚì#ÀªT›“$Åœ¤è¤¦É6ã ñ“õ줓'`þ±ÙâË-Z\p!A“¢5FHE"Î9üà¹ú÷ˆˆ¸œù@…â–&|ô³Qâ„ )l˜¨è…F± Š 2rk 1n°Àý²†(A?+’bÓ˜9$a˜ègd€":ÖDræä–!ý,t`±‰“ËvÙÈ·ŠbœoýFä™4Àý(‘<¡ß '8z£à_ÑBê  Œ‚±Óѯ]®Žô{ðÏI)t +ÿ€§‰%¾Ó\tb…G? ›§Òç\† ö–#Á€EB¿oQ±ÁýŒÑøÖEE?]‚ +6pÀB +K€²„!ÜwN=£¬……þX‘$ZHÃ? ÅEVÄÀ\DSÎ5ž ˆô(óG8›ôC?Hlg‘3Ø?‚ ¶ÐÏU( @hø3@ýL`ÆT’H/5tS:ØS@rx?1Ù +A±$e1… +„ ¤PBôÃp€‹<Àè1Œà“?)ƒô3…?ôsTı6±ÀYnö?l „ A?€¡ÿ ô6Œï­{¿ ôû‡îWqÿ¥rJ}'öoNÿ¸´ÿÛmÿȪ¹ÿ°ºv;²sjǦ¦þs˜ÝOî·Ûîrã±ÆöI£¬tëº~D{Lײַå{YkYýVZ¹~½ÔzÇ]êº÷z”Ø Ñ« ÿƒÇÿÉ¥¯Ù¹lò¼¸Í*2§éËû¿÷sýºÇº¾»uC˜øî!Ï-­»¶ƒ»émýߧÿm.§öoNÿ¸´ÿÛmÿȬo«½:Ÿ«Ý–äËo¶=Rã%¤ý +¿ëk™§üpý£7ì8ÝÜŒ‚÷1•×p.qlýú?ÉQâ„£Äe###Ý"Ç›€LÆÆ$€yïÿfôïû‹Oý¶ßüŠ_³zwýŧþÛoþEqŽÿÝpqõ[.<ì?ý«³üpâÑ?)êý#+{Èy?ÞØöã©ž»­ôüô\÷7?F5Ä[dMÿÉI +þ¯ÓºÇÕŒìÞ›soÇ~-ÞáÈ>›ý?i÷1é$§ÿÐÝúæÍÝ +¡ã„áÿ?.;üMQ^GZêÚ%§]á+]§ÖàOC øŽÿÏK?ÿ_òîþÿÑ•¤§ÔÏAÀ?šáý¢‰‹Òqq]¹’g³Œ?Å]??LÜê)¢Í¶°ØKY¸n?Ù””ò?ã뻺Ù°]úöD¶§~àNïü‚òjŸèRz®oë9™.qÇm¾íGÓÊ»wóžÿ ¬}uÍ·;ë@'p¤ŠYýŸ¥ÿ‚¹éu\]Ù¦†ÎÌf6–àƆ¤§2˲2ì6äÚç¼ò\Ous ·jÓ-lnpí2¬ãô¦Úïq- +Ç€#É/%µTÜ 0Y&y']wÞéH"#©ì\§/““'ó8Ϫ¾l“?.(ZL]‘SÏØno¯‚4|™uN:o¡ÿé>®u«>©ý`­¹m-xsÚFáµßÍæcOÑ·aßÿA_è} \ f¶ŽðeÇä²þ¶bzUbÚG¹†Ê ñ +"ÊÿóâZl6{¯çáy™ÕÎù1q|¸‡ø/µ}c²»ºS/©Áõ¼±ìsx ûšåã¿S˜øÅÅc ?µÜíÅ˶ú£Ô_?þ.éÎÁµØÄÿ%¥¶WþeV±qŸSÁoøÊǶeÿ’äšO¶¿£ôçˆ4·UÃýzú»Kqm¨{©}o²­Úšì­¦Æ¹‡û;½q?ã+©ãàt·¾ÇP±õÒÞî{Ágý +ÛÞ’Ÿ=ÿýO"‹ú¯Nœ|¾Ÿ?ç³°}ls™gù»Ø’±þ-º.Eµu~²æ–ãcàäRÇöu¯f­oü]_M$”ÿÿÑèþ¶ȘßøTÿ碼·ê/Õ¦}bÍÊÆu–Ôi§Ôo¢CI;˜ÈvàïÞ^Ÿõ¼ÿ?©ÿÂŽÿÏK?ÿø£ÍÿÂNÿÏ´$§Mßâ›þ0ùzŒÿÒhý;ü^Óõ}ç¬ ‹]m,{=+ZÒ±®«é·èý%骗VÇûGOº¡ÉiIOÎýcÛõ—$»@2I? +Òº\¬ê99y+ëž èêŸjˆfPÿÆ0 +íoýýuoéç§âukìÚ̶4}V +·³Oå5%9Ù­88F×4Šßí6A??ÁîYݧ_›’Öµ³u§AØ4äWAe˜]]ìØ÷âá1Ù6Q½G0þŽ‘ˆßçYôíµ^é”9½'¨u>‘UØíǵì.´ßxpi{Y¶£·•þãßë§ pÆ@o>¾ +ÜäqÂÃ\BGé<Óÿ)“ûŽÇKé•SŽÊk!â¿kœ Ë¿=qã +¡U5·Ç!ä˜ÕÔtŽ§~Oø8ØÕ¾Ž”Þ¯”èpeŽ±­ÛS×9cYúl?ü q_:³3òqXÆ–E_hµ“%¯¼5â·ÅÕ±5©)Jr2‘¹H™â^¯ü]µÃê&s?8ÇœWJãzí?ùîd¹•çýªïAÖ?`3fíú?üåéÝ£ÙѾ¡bc\6ßiõîiä:Ó¿iþ¥[¼ëêŸÿ”|oü;oþ?IkÛž£þ4í›oÀa:ok?ô«r¯‹þ-:ŸZÍßZ:•™g»#OÜwólÿŠ©zpcG)$§7¦ýYÌé´cÕ‰sY[ú7¤­ußù¨á[¿óÛÒIOÿÒÜúèý¿WØxÛ„ãÿ?.+üLÙ·ë]¬ýüKÜúœ½;? WÖzf5v:èµ®o‹\Ƈ5.?õ3¢ôKþ׋‹]y{K=V—N×FáîrJzÄHÕ:I)ó߯ŸU¨~^Ušb5†ç< uohö½Ÿ×ú ˯ºŸ²Õ??iÖïQÁçp6?ûÉö¯hÿuu+þ«]‹ÓqíÊ»"ÊØöRÒ÷ê½ÛZ?à׈Ýõw¯cÿ=Ó²«îwS`ü¬J”èàææ`b»3,TàÂiöݨÝíÝ»ôË^©õI?ëBgUË¢£•eOÄêýù-õœÂÍþ­~ÿzò.?…–j»Ê,k‹LK?﫸ú­þ-2ó:]oëmulqÝN0{˜C]ûÚßÎwî$¦ç×?¬ŸSºf;1:uUfçÓOÙ)m.%Œ§OÕòm­ÿ¦£Ûýô«;ê'ø¿Ïê]@uÿ¬L-¤?Ö«Áµó¹¯µŸ™Csü/üRîº7Ô^?Ò,ÇŬZÞ,#{ÇýrÝî]hh? INOÖcþOÅíü¡x?Õ{cëö +‡‡gÄÿYå¿÷åï=W§Ž¡Šhݰ̇y¬.•þ/ºS2Ý‹[ò*x²»eÒÓ¹¯ú¼’ž©$’IM»ÿ"uü+wþ{zIußù¨á[¿óÛÒIOÿÓ˧ùšÿª?"šó´’Sè‰/;I%>ˆ’ó´’Sèi×?¤’ŸDIyÚI)ôD—?¤’ŸDIyÚI)ï²ÿ¢]ÿïÈR\ +I)ÿÙÿízPhotoshop 3.08BIM%8BIMíHH8BIM&?€8BIM +8BIM8BIMó 8BIM +8BIM' +8BIMõH/fflff/ff¡™š2Z5-8BIMøpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿè8BIM8BIM8BIM@@8BIM8BIMEAxlogo_bigxAnullboundsObjcRct1Top longLeftlongBtomlongARghtlongxslicesVlLsObjcslicesliceIDlonggroupIDlongoriginenum ESliceOrigin +autoGeneratedTypeenum +ESliceTypeImg boundsObjcRct1Top longLeftlongBtomlongARghtlongxurlTEXTnullTEXTMsgeTEXTaltTagTEXTcellTextIsHTMLboolcellTextTEXT horzAlignenumESliceHorzAligndefault vertAlignenumESliceVertAligndefault bgColorTypeenumESliceBGColorTypeNone topOutsetlong +leftOutsetlong bottomOutsetlong rightOutsetlong8BIM8BIM8BIM ²xAh[h –ÿØÿàJFIFHHÿí Adobe_CMÿîAdobed€ÿÛ„    +     + + +   ÿÀAx"ÿÝÿÄ? +  +  3!1AQa"q?2‘¡±B#$RÁb34r‚ÑC%’Sðáñcs5¢²ƒ&D“TdE£t6ÒUâeò³„ÃÓuãóF'”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö7GWgw‡—§·Ç×ç÷5!1AQaq"2?‘¡±B#ÁRÑð3$bár‚’CScs4ñ%¢²ƒ&5ÂÒD“T£dEU6teâò³„ÃÓuãóF”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö'7GWgw‡—§·ÇÿÚ ?ô\?ÓÎ98´’jdŸM¿º?’?û7§ÜZí¶ÿäVfw\§£t!—c}GӉ궩ۻc¶î÷.Cürœ¢[GB¾ç7W6«wÀñ;hIO¡~Íéß÷Ÿûm¿ù¿fôïû‹Oý¶ßüŠà?þ72[ô¾®å·ââ?ôBlOñ¿öž¡?„z5”ý¦ÖU½öýîî#ÐüÝÉ)ï¿fôïû‹Oý¶ßüŠ_³zwýŧþÛoþEX˜åsÿX¾¼ý^ú»,Í¿Ôɉ´?Ök]µ×RS±û7§ÜZí¶ÿäRý›Ó¿î-?öÛò+ήÿýjñêtîŽÊh?FìËv‚>¡ÿ ô6Œï­{¿ ôû‡îWqÿ¥rJ}'öoNÿ¸´ÿÛmÿȪ¹ÿ°ºv;²sjǦ¦þs˜ÝOî·Ûîrã±ÆöI£¬tëº~D{Lײַå{YkYýVZ¹~½ÔzÇ]êº÷z”Ø Ñ« ÿƒÇÿÉ¥¯Ù¹lò¼¸Í*2§éËû¿÷sýºÇº¾»uC˜øî!Ï-­»¶ƒ»émýߧÿm.§öoNÿ¸´ÿÛmÿȬo«½:Ÿ«Ý–äËo¶=Rã%¤ý +¿ëk™§üpý£7ì8ÝÜŒ‚÷1•×p.qlýú?ÉQâ„£Äe###Ý"Ç›€LÆÆ$€yïÿfôïû‹Oý¶ßüŠ_³zwýŧþÛoþEqŽÿÝpqõ[.<ì?ý«³üpâÑ?)êý#+{Èy?ÞØöã©ž»­ôüô\÷7?F5Ä[dMÿÉI +þ¯ÓºÇÕŒìÞ›soÇ~-ÞáÈ>›ý?i÷1é$§ÿÐÝúæÍÝ +¡ã„áÿ?.;üMQ^GZêÚ%§]á+]§ÖàOC øŽÿÏK?ÿ_òîþÿÑ•¤§ÔÏAÀ?šáý¢‰‹Òqq]¹’g³Œ?Å]??LÜê)¢Í¶°ØKY¸n?Ù””ò?ã뻺Ù°]úöD¶§~àNïü‚òjŸèRz®oë9™.qÇm¾íGÓÊ»wóžÿ ¬}uÍ·;ë@'p¤ŠYýŸ¥ÿ‚¹éu\]Ù¦†ÎÌf6–àƆ¤§2˲2ì6äÚç¼ò\Ous ·jÓ-lnpí2¬ãô¦Úïq- +Ç€#É/%µTÜ 0Y&y']wÞéH"#©ì\§/““'ó8Ϫ¾l“?.(ZL]‘SÏØno¯‚4|™uN:o¡ÿé>®u«>©ý`­¹m-xsÚFáµßÍæcOÑ·aßÿA_è} \ f¶ŽðeÇä²þ¶bzUbÚG¹†Ê ñ +"ÊÿóâZl6{¯çáy™ÕÎù1q|¸‡ø/µ}c²»ºS/©Áõ¼±ìsx ûšåã¿S˜øÅÅc ?µÜíÅ˶ú£Ô_?þ.éÎÁµØÄÿ%¥¶WþeV±qŸSÁoøÊǶeÿ’äšO¶¿£ôçˆ4·UÃýzú»Kqm¨{©}o²­Úšì­¦Æ¹‡û;½q?ã+©ãàt·¾ÇP±õÒÞî{Ágý +ÛÞ’Ÿ=ÿýO"‹ú¯Nœ|¾Ÿ?ç³°}ls™gù»Ø’±þ-º.Eµu~²æ–ãcàäRÇöu¯f­oü]_M$”ÿÿÑèþ¶ȘßøTÿ碼·ê/Õ¦}bÍÊÆu–Ôi§Ôo¢CI;˜ÈvàïÞ^Ÿõ¼ÿ?©ÿÂŽÿÏK?ÿø£ÍÿÂNÿÏ´$§Mßâ›þ0ùzŒÿÒhý;ü^Óõ}ç¬ ‹]m,{=+ZÒ±®«é·èý%骗VÇûGOº¡ÉiIOÎýcÛõ—$»@2I? +Òº\¬ê99y+ëž èêŸjˆfPÿÆ0 +íoýýuoéç§âukìÚ̶4}V +·³Oå5%9Ù­88F×4Šßí6A??ÁîYݧ_›’Öµ³u§AØ4äWAe˜]]ìØ÷âá1Ù6Q½G0þŽ‘ˆßçYôíµ^é”9½'¨u>‘UØíǵì.´ßxpi{Y¶£·•þãßë§ pÆ@o>¾ +ÜäqÂÃ\BGé<Óÿ)“ûŽÇKé•SŽÊk!â¿kœ Ë¿=qã +¡U5·Ç!ä˜ÕÔtŽ§~Oø8ØÕ¾Ž”Þ¯”èpeŽ±­ÛS×9cYúl?ü q_:³3òqXÆ–E_hµ“%¯¼5â·ÅÕ±5©)Jr2‘¹H™â^¯ü]µÃê&s?8ÇœWJãzí?ùîd¹•çýªïAÖ?`3fíú?üåéÝ£ÙѾ¡bc\6ßiõîiä:Ó¿iþ¥[¼ëêŸÿ”|oü;oþ?IkÛž£þ4í›oÀa:ok?ô«r¯‹þ-:ŸZÍßZ:•™g»#OÜwólÿŠ©zpcG)$§7¦ýYÌé´cÕ‰sY[ú7¤­ußù¨á[¿óÛÒIOÿÒÜúèý¿WØxÛ„ãÿ?.+üLÙ·ë]¬ýüKÜúœ½;? WÖzf5v:èµ®o‹\Ƈ5.?õ3¢ôKþ׋‹]y{K=V—N×FáîrJzÄHÕ:I)ó߯ŸU¨~^Ušb5†ç< uohö½Ÿ×ú ˯ºŸ²Õ??iÖïQÁçp6?ûÉö¯hÿuu+þ«]‹ÓqíÊ»"ÊØöRÒ÷ê½ÛZ?à׈Ýõw¯cÿ=Ó²«îwS`ü¬J”èàææ`b»3,TàÂiöݨÝíÝ»ôË^©õI?ëBgUË¢£•eOÄêýù-õœÂÍþ­~ÿzò.?…–j»Ê,k‹LK?﫸ú­þ-2ó:]oëmulqÝN0{˜C]ûÚßÎwî$¦ç×?¬ŸSºf;1:uUfçÓOÙ)m.%Œ§OÕòm­ÿ¦£Ûýô«;ê'ø¿Ïê]@uÿ¬L-¤?Ö«Áµó¹¯µŸ™Csü/üRîº7Ô^?Ò,ÇŬZÞ,#{ÇýrÝî]hh? INOÖcþOÅíü¡x?Õ{cëö +‡‡gÄÿYå¿÷åï=W§Ž¡Šhݰ̇y¬.•þ/ºS2Ý‹[ò*x²»eÒÓ¹¯ú¼’ž©$’IM»ÿ"uü+wþ{zIußù¨á[¿óÛÒIOÿÓ˧ùšÿª?"šó´’Sè‰/;I%>ˆ’ó´’Sèi×?¤’ŸDIyÚI)ôD—?¤’ŸDIyÚI)ï²ÿ¢]ÿïÈR\ +I)ÿÙ8BIM!UAdobe PhotoshopAdobe Photoshop 7.08BIMÿáHhttp://ns.adobe.com/xap/1.0/ + + + + + + adobe:docid:photoshop:47579060-97f4-11d9-a2dc-e4c74314cde2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ÿîAdobed@ÿÛ„ÿÀAxÿÝÿÄ¢  +  +  u!"1A2# QBa$3Rq?b‘%C¡±ð&4r +ÁÑ5'áS6‚ñ’¢DTsEF7Gc(UVW²ÂÒâòdƒt“„e£³ÃÓã)8fóu*9:HIJXYZghijvwxyz…†‡ˆ‰Š”•–—˜™š¤¥¦§¨©ª´µ¶·¸¹ºÄÅÆÇÈÉÊÔÕÖ×ØÙÚäåæçèéêôõö÷øùúm!1"AQ2aqB?#‘R¡b3 ±$ÁÑCrðá‚4%’ScDñ¢²&5T6Ed' +sƒ“FtÂÒâòUeuV7„…£³ÃÓãó)”¤´ÄÔäô•¥µÅÕåõ(GWf8v†–¦¶ÆÖæögw‡—§·Ç×ç÷HXhxˆ˜¨¸ÈØèø9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêúÿÚ ?Ü÷¢>¦ôWì?þ±û÷^ëßì¹|zÿŸÓ?ú+öGÿXýû¯uïö\¾=χéŸýû#ÿ¬~ý׺÷û._¿çÃôÏþŠý‘ÿÖ?~ëÝ{ý—/?_óáúgÿE~Èÿë¿uî€~û_‚ßúÿ'Ú=é±¾\ó$ð}µô©ÇU +×ÛÏoÿ0Ý°èº{¥ö?QõnN¿—ÜUxn°ØÑî04yS˜jVÌ® +†·ø?Á}·ñJš[PPVÿ¸‘÷U_wïw~÷›=Û<ƒµÖÏo-¨žâÇñyüú‘·¾LÙyî͹Rö{QJÓüTŸõyW«ñÿeËã×üø~™ÿÑ_²?úÇï/z„z÷û._¿çÃôÏþŠý‘ÿÖ?~ëÝ{ý—/?_óáúgÿE~Èÿë¿uî‹ÍŸ?½ ?øeò⾃¤z†Šº‹ã}ÕÑVÒu®Ì¦«¤«¦ê­×5=U-D8Tšž¢ždŽ„20G¿uî¿ÿÐÚǼþpìσ1Ýøp‡wæºãâ\]£‡Ø°å??¾ë­±9ƒ†þ,h2ÿÚ·íÈ-LÊ¿Ðû÷^ë\¶ÿ…–MÛ5<^ÿ*~òßù|=1Èdñ½qÜŸßÊœn8„¥ÕÑáº7ËGBk*‡"Ãúû÷^èA¯ÿ…pö&/ŒŸòiùiŽúM|†îÍRƒLoÏ« xãþóïÝ{¨½Sÿ +ùNÑïþšèÚßå¯Ùxý½ÚÝwÕ«¹·‡wý°Ûµ;óyc6?ñŠêÒ:è0g.* + Åob>¾ý׺ÝIc÷yñOÏû÷^êž¿˜§óÊþ_ËB9ð]ãÙõ;§·¿‡5~?¡:¢Ž?wvƒ ³º3?¡ÁìJ\‚ÄBå]¯À?Cî½Ö¼û¿þqóO±#þðüjþ[›'`u­YÀ{#åoq¶Þ¢ËRéÕ­ücZ:å¿ã“¯ÿ\û÷^éŠÿ…95£[?â×òêì¾’m½‘ß5¸ÍÏU~ ¥«Ë÷ +m¿¯ù+­ïÝ{£aÕÿð¯> ÄÕVu÷ÍŸ‡=ÃñC´ÿ…ŠŒcV6ûëL¦P´¯—«;{lo\«-þSE‹Î'ú&Îtô·k·³º»U¾“é¶úäæ´ý€Wù}¼:¡Ïž">_ÿ0¯?;/%¿²§xl?ßKGˆëî—ª9^»¬§ÊVW%ÔêFZåþ!’ÈšÑKU•?å¤ÛüµÑmC–¶aÍû‘²ºæ!?þZV§×ç×]½¢“Úû‹!íï´ +{kìßÑ ®`Ý?>±ˆÏü¢ÛPg§[¿.Ï?;ùjü=ë|og¥V±7ŸðtßµYŒ‰ÍÕíêÜ¥Sÿwö5%m8û*LfÔ aJ”Ô« Ö}/p@W—yvÃhÿ þìnNO¯?>À)_\þ\üûÈûµiŽû nJµ¥¥?ôâO™&£ÓHÍI£§ÿ =?ÝstW,.Ùí>ά޻ƒhm]«²;ã”ÏnÚ¼™ Nz{ïI’‹Õ66ûõ?ý ‡ü(Ïç%þÛù üµ1Eþxä;`R?ÂÝWïÝ{ {ÿ êþ¿ì +?óCùwü£ø¹÷$7ñ•¡Þuhx9s·7>ÛêúÌhà’šÛ߃õ÷î½Õöïo—~n,Ÿ”]ññ‡²ð½¥Ö‡âÏÈH¢ÌâL´õXœ¥7RÛÛ‡ Z)3[gpãd`*©*éÑ×ð,Eý׺ÿѵoç-Š|ŸÁž¿…loÂ?çAåþ—èyïùþ¾ý׺Öÿþ­°öçbüÌùw¶7L-Y‡¬ø–<ðÁTi›þg_ÿAø׿uU:¯þ]™ØüÙÜ£ñïÝ{¥_X|Pë¥ÉÍ”Àÿ¬Žao³ÏUÿ¥¿ýV}×ߺ÷Zøÿˆ?ÆCà/M°øÿ–‚/‘}±üW²2àŠ‡Ùœc"î^Ç•z×]ß +\]?~okRŸ~ëÝ|ó¶¾UºûfOòï¼Híþõî öàÉu3±êWt–RÕÿ¿“½»³KY&ìlj¥ÆRÕ5žµj*Íô©ëÝýÁ»7çoî +½ÛÚ»ï7¸ó5å“gòÕ—¥û¯õÏù¿uîŒWMôÞ/|Zª‚6ö¿Ç³æ“[ìïÿ/ò?e;¶íô?NÀûÌÿxOp¿©ü½ÿ$ïø•wÿ(–?ì?`팵L¿÷®&§³~:Q“gˆUœ¦ééÌõPû#¹:¯qÖYC»è/÷5t¿ð +ºÿiV=ˆìyrúÏeþ°s§S÷½>ÎòYÞ?µþÀÞ£jãwòõÒãùu|ÍÏ&ÿæ³ñ½ËŒÇvïÇ ^öÃf72¿»‚š“jn`ÛOä_KRd|§¿(öýrdÓjØÃQT°p?LˆO“Œu…­{¼lãwØAÁÁÿ/í¯ú©Sôºþc‹o›c~m­6ãÚÙÜ®ÊÝûo=ˆªܶ$Ô9\Ff’¯›ã²4A?ü«~è?¯›òtÂA/ü(›¡°Hi«iËNú ¬†×¥ªûm¹Ûàý?ý׺ú¤å>|wÌQÍKWÖøOßÑOû{þ=û¯uª×óÍþ]»2‡«ûgÃ|?_n^¶ìMï׿ÇÏÝÕu§guÖÛÈîü>cmW ÖЮDaÿ†déi¿àu ä{÷^ëZßøM÷É^ÀØ{Ûù€üj¤ÉUMÕ½÷ü»þWn,öÞga‹¤ß]]Õù¬¶Øݴ먼‹W”ÆÿŠ×ÿ€÷î½×ÿÒ¸OæãG=_©¥‡üåoÄM×?úOµÿyý׺×þ3ÿeÕòÏÿ,ïàëï~ëÝ}*dž8còË(†?ɘ[ëþÄ~ëÝŽêï=‹³6.ðzí´§Ý?`²µ8Ý»âÃýcSR6¯³ÃŠõ­®°ÿõÿaïÝ{¯?oó¨î½Ïòù?vìUS6J¿ªÛý=¶"7 +Eþ0€µn÷ËäÏûïÝ{®_)zÊ:þ웯¨>çû¹ÓûKiôþ{ÿÀ]›¶ñØzºÏÇü®ûªŸ~ëÝ9슔·'ßÖd±ÑMMIO˜ÈýßÚÒÒc1u˜úϼ¬ÿ ÿ”Gí‹ë?ŸGœ«ÊÛß8s>ÏÉܼ~¯˜·[ߥèJíŽÈ¢Ú[c +ñ÷¤áªÁÅWT2/½¬Ê¹02rv–\Ö©­½wÞÀnGü¢®!öçۛ߮þ¸oÿò°Ýÿ¸Ÿòéi×YyÆã–~îš>îÈ«÷Wuÿr÷_ûJÿGWáÄ:}å‹ÈÅU?ûͯ‡¦«óMPhê¿‹nz‘ÿ)‚³÷¿ôÕì=Ì{¯õ“yú +¿þUÛ^µï÷1Ù}Õþï»?µü¿ÿOš¿íþ%ÝôAÿ›T6ÕÚ½»§†ùLG³:C!Wr**ñ›Z»¼v€b,mD›Â¬¯íþ¸ÿÖÞÊGä^_äü'‹­(·Kø–cãWmîŽõ•s›ÔÚùŒðÙÔ€ÎcoÌe-)þ”ߺ÷ZÈÿ(:yháJ=5M7Áó'äh˜ÿ三ãߺ÷_\O~ëÝjóÿ +Sù1×ÿ>.gó›‡1Ž‹r×ìÝó°zãå^íß»ÿnä6Ø¢¤¥øͽ?ÌTä²Wú(<{÷^ëTOøM§ÂþÃÝÛoù‹üâËà²î ê/?Ÿ)z?knIÔ¥.èí}ûÖU?]‰Ä=—îÓilj¦ÉXC‘£ü“ïÝ{¯ÿÓº?æ¹ÿdMÒ¿øª™ßýñ?~ëÝhYüŒ–¾+ùš÷W{õ–Czö¾Ê«ëŽ”^ÁÃ?Te±,¶V«ûõ´¶Ócòµyœ~Z1?4y¢l÷=û¯u±½ü$ãÈŸ¹Û_1+?æÉìζo¯âǯÈ÷î½ÐµñÏþÛ´?–ö[#ón.Ú휖ñØ[K°¶xØ}?·vvO—¥ì}Ÿ¸v%em&åà +:üB‚‡3õ4ÆÞý׺Ò?æñŸÌ£ºåÉÿ’EÊì…@Ÿ?4ßë&Ö·Ú7ûÏ¿uî®Ç´z>J¯??µ,´ç·¶Z£ÿVÿÛû÷^êGucê>>ôÅfñ®ÀäèöÎ{î°Õû½qŸÃ©i8fùñ +ÿ¯Úý}”ÜXÞïÎÏaÿ,ߟ§Yõ÷ >ßru?¼>øsŽð?¬<¿²ý.Õkÿ¾ªëþ%ôK>üzß?çÙŠ ^¦³~o¼?ìÁoò]¿?¥6µUg P`h?ʪ½ÏíóUîÏd9þv-×þÍ:šþï[^ËÉû78}è=àK·ZÿÙßüºÚÿËÝß[¦|_øǶönÃÀlݱ‘ÆnJ]­þáòùŒ}]%WÝîqÿ'Þ}ŸÞ}†Cï¿åþP}ÁÖV?GeQîsûÅî¦õïg¹ÜáÏüÁÿ%¿û4´ÿ”Nµhÿ…mº}¥³6†3O‹îþGbØÒì=¾*¹ÿƒV?kúŒº´ÿøO +l?ȧåFBx¼4Ù?5kG1ÿ”¿´ê®žƒþAû»{÷^ëZï?ìͯóº¬ÿdã;²v·É¤ùgß?è«;Ù?S³h²¢¿°ÿŠ¾ã¤þ¸?Çÿu~ôòfþ +­î½ÖßÕ"ÿáR»¶šM½?í?åû·*j‰§þñí޶ߵJPx5tƒ1×Ùœy6üšCo~ëÝ}[ÿ §ù7ó§ºñß ›_Í^Æïê¸Ö‘fÚûf’·Oö„¦ÛT[‹2ÔI´öù /MƒÀÑ@ ƒïÝ{­¨»—¡:—ãòÏùÒ°¶÷Zu–Ãø‘߸?¿´6Í¥ÆÒêmÚõ• +#0©Èdkkª**ª UU1,×>ý׺ÿÔ¸_æõRñüë8Ŭ~!nr?Çþ0<ÿ_~ëÝk;ÿ£wÿ‡ù.—ô…;—Ñø㽺;ž “ïÝ{¯¦¯¿uî‹gË=„ý•ñÿ²v”@KU[·jü#úŸø­‡ûÇ¿uî¾C¿Îc£³}ò‡ý+:ˆp½Ó…£ª¬?©+KØû?³÷Þ!Ïâ·U&Hóÿ)þý׺Ø>ûè +®€øíó#°w…>ß›Gocçûz?â•C¸ön#ø7jmϳ£úd(3¸ŠšŸú‚÷î½ÓnpôŸÍæׇnomÙžé¿Ž;#pü‡Þ=Vq{§ý,gvndRm·¡øùˆµûë@W'œÏL +¾Ô’íø'ëÜ:4_ö%mÄÿ˜¿*~õ÷EôÏWã>;÷vçÛY,ÏqÖ|ƒùˆÎâö~CráöÕ^aä??ô>ÞÚF“ü—“É×n?¾4Ÿwí‰À»½ýá¸ÉG©Sž=é÷Ü?—gäþ`ÝÇõwkÿq-:þ!ü˜ß}!ñóÖ?Ò6~s«•ßþk½ó“›¾ð{;²·?bí­¾Ø?‹¶ð¸?Ä¿a»÷n mýÎçÞUUUׯ6¤ÅOuõ«§óÝù_ˆùÙ=†ÂãjpÃÕ´½ã¾öµE_ÝUí=çò‹¹èvv_ò2[OdPãýW{÷^ëu…7Á¯ä-ñ窷®;øGdïʯôßÙت€«YŠÏvÖe·-µ°LŽc^6¨\ÿ–Ñ{÷^ëL¯å9$‡þsѲo!ùÃÜ—$MêûBãú{÷^ëë©ù¬v>?ú¤Aÿ×ߺ÷NÞý׺+_9ì‰þaâ­|‚ÿßM»}û¯uÿÕµ_çG˜þðkÕ |Ã>o:ó7ôÿŒQþÃߺ÷ZÂÂ4sÿ¿š¯`bÓrü@ìÚüãßÝC—ÿã~ëÝ}G½û¯uHÒXåŽ_órÜõ¸ÿln=û¯u§‡óÝþW;1Ô? û_s…Çô†ieû'¸è)>ï;Ö{ïkÑiÅn-¹H5ÿÇþóø]U(±¬ûî>ý׺ÐÏ|ïšzÃb|zë­ùUÃÚ¹¡¿óÔ{£,sج¯qÕaqømÅ»°ûsø‰¡À?áôkÏÔ{÷^èÄttw'ÇŽ³ÏwOR÷ö7aÅ€ÍÑìúÊΧÿpÝþå(ÿŒ¸ãÆ+¨6}Ù}­VR›þ¡=û¯u¿¯ò˜Åü7þeŸ¶¯Ëîßê¾®›ºw‡RvÅ—{ð¿+ØTÕXîæü£ìŒ¾ÛÈm³žÇïÍ©WM“û¬Ÿùuß’}§¿uî«SùËÿ2äññ_¯ö·N|dØý_òGäÏ^ôŸû+=e‚ê-Ù¸r{ct…;ãþ˧{«}ìýÛeõ†>»IP6eUNpÖÖ€OÚr}û¯tL‘òïo” 1¿Ìoù™mÌÆ+cÓnæíN¿ê-ýDô›Ã¼wé®|Ö#soݶD'jõv U&.ZUlØT_¶?>ïÝ{­Ò™œŸñ€¨©åwzmÚ{ÿ¯˜ ÷î½×Ë[ù^îxéŸ?ÅÍÉ,£Å–þ`iM÷S7ÛêþôöFOroeo÷08ÿ~ëÝ}?=û¯uï~ëݯœ¿öDÿ0¿ñV¾Aï¦Ý¾ý׺ÿÖÙÿ¾~mÿœz7mî\˜ƒzKgà3ÉÉû\¶=°ðÔY¬EWÛzôWQöý׺ëàOòhøYð3z/ouOEl-«Ý+…ËmxûnÕîóVvþtP ÅÚf³õ´+÷æ?›S?Ãߺ÷VõïÝ{¯{÷^ë_¯øR¾ÛùØÊÛ°º‡ãPv‡uožÚì~²Úû“mõÑÈo-ÁC°py£¿·b³ˆ ®­8áY´))[H<Öÿ€ݲòÿK׺ùoïOåå󻮊ÿ}þ|£Ú¿°*'—p||íìP¥Ÿ¼5»<·ûoj>‹ç׫нñó¥ûfM¯ÙÝi¹ú¯²0õõ»j¬ÑÃ?ëÝßLxÿ+7{Ž}ïè/ÕN½Ž¶¥þWðšNØî¿‹û;=óß¹ö~ÝÌUÕgúߣ¨7þåÙ™Zµ?‹+n^ÕÄán;›ûKRc‰”TVû»1Òõî¶tøkü‹~|2Éc÷G^t_Ñï<`cE¼*q5›ÓxQ9ZMÝ¿qg(~ƒŠb/ýG×ߺ÷W?GGOC¦¥‹ÃÿaõúŸ~ëÝ?”ÿbù!Ösl#˜l ©kè²P ýµU9²›Åýû¯uT_á>ß>?v†Øî|·BõÖw³6&êÅvÎÞÑUoº|¶#}à²Ã5†ÜÊxqÈÐçU)jRº€½ýû¯uý׺÷¿uîŠ×Î_û"˜_ø«_ ¿÷Ónß~ëÝÿ×!{7þ= +«ÿ†Þÿu”¾ý׺R{÷^ëÞý׺÷¿uî½ïÝ{¯{÷^ë„Ÿ¡¿Ö÷î½×?~ëÝ{ߺ÷^÷î½×½û¯uï~ëÝ{ߺ÷^÷î½Ð{ÛŸó*{;ÿîôÿÞo%ïÝ{¯ÿÙ Index: src/java/org/apache/fop/render/java2d/FontMetricsMapper.java =================================================================== RCS file: src/java/org/apache/fop/render/java2d/FontMetricsMapper.java diff -N src/java/org/apache/fop/render/java2d/FontMetricsMapper.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/fop/render/java2d/FontMetricsMapper.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,166 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.java2d; + +// Java +import java.awt.Graphics2D; +import java.util.Map; + +// FOP +import org.apache.fop.fonts.FontMetrics; +import org.apache.fop.fonts.FontType; + + +/** + * This class implements org.apache.fop.layout.FontMetrics and + * is added to the hash table in FontInfo. It deferes the + * actual calculation of the metrics to + * Java2DFontMetrics. It only keeps the java name and + * style as member varibles + */ + +public class FontMetricsMapper implements FontMetrics { + + /** + * The first and last non space-character + */ + private static final int FIRST_CHAR = 32; + private static final int LAST_CHAR = 255; + + /** + * This is a Java2DFontMetrics that does the real calculation. + * It is only one class that dynamically determines the font-size. + */ + private static Java2DFontMetrics metric = null; + + /** + * The java name of the font. + * # Make the family name immutable. + */ + private final String family; + + /** + * The java style of the font. + * # Make the style immutable. + */ + private final int style; + + /** + * Constructs a new Font-metrics. + * @param family the family name of the font (java value) + * @param style the java type style value of the font + * @param graphics a Graphics2D object - this is needed so + * that we can get an instance of java.awt.FontMetrics + */ + public FontMetricsMapper(String family, int style, Graphics2D graphics) { + this.family = family; + this.style = style; + if (metric == null) { + metric = new Java2DFontMetrics(graphics); + } + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getFontName() + */ + public String getFontName() { + return family; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getFontType() + */ + public FontType getFontType() { + return FontType.OTHER; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getAscender(int) + */ + public int getAscender(int size) { + return metric.getAscender(family, style, size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getCapHeight(int) + */ + public int getCapHeight(int size) { + return metric.getCapHeight(family, style, size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getDescender(int) + */ + public int getDescender(int size) { + return metric.getDescender(family, style, size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getXHeight(int) + */ + public int getXHeight(int size) { + return metric.getXHeight(family, style, size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidth(int, int) + */ + public int getWidth(int i, int size) { + return metric.width(i, family, style, size); + } + + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidths() + */ + public int[] getWidths() { + return metric.getWidths(family, style, Java2DFontMetrics.FONT_SIZE); + } + + /** + * Gets a Font instance of the Font that this + * FontMetrics describes in the desired size. + * @param size font size + * @return font with the desired characeristics. + */ + public java.awt.Font getFont(int size) { + return metric.getFont(family, style, size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getKerningInfo() + */ + public Map getKerningInfo() { + return java.util.Collections.EMPTY_MAP; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#hasKerningInfo() + */ + public boolean hasKerningInfo() { + return false; + } + + +} + + + + + Index: src/java/org/apache/fop/render/java2d/FontSetup.java =================================================================== RCS file: src/java/org/apache/fop/render/java2d/FontSetup.java diff -N src/java/org/apache/fop/render/java2d/FontSetup.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/fop/render/java2d/FontSetup.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,182 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.java2d; + +// FOP +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.Font; + +// Java +import java.awt.Graphics2D; + +/** + * Sets up the AWT fonts. It is similar to + * org.apache.fop.render.pdf.FontSetup. + * Assigns the font (with metrics) to internal names like "F1" and + * assigns family-style-weight triplets to the fonts. + */ +public class FontSetup { + + + /** + * Sets up the font info object. + * + * Adds metrics for basic fonts and useful family-style-weight + * triplets for lookup. + * + * @param fontInfo the font info object to set up + * @param graphics needed for acces to font metrics + */ + public static void setup(FontInfo fontInfo, Graphics2D graphics) { + FontMetricsMapper metric; + int normal, bold, bolditalic, italic; + + /* + * available java fonts are: + * Serif - bold, normal, italic, bold-italic + * SansSerif - bold, normal, italic, bold-italic + * MonoSpaced - bold, normal, italic, bold-italic + */ + normal = java.awt.Font.PLAIN; + bold = java.awt.Font.BOLD; + italic = java.awt.Font.ITALIC; + bolditalic = java.awt.Font.BOLD + java.awt.Font.ITALIC; + + metric = new FontMetricsMapper("SansSerif", normal, graphics); + // --> goes to F1 + fontInfo.addMetrics("F1", metric); + metric = new FontMetricsMapper("SansSerif", italic, graphics); + // --> goes to F2 + fontInfo.addMetrics("F2", metric); + metric = new FontMetricsMapper("SansSerif", bold, graphics); + // --> goes to F3 + fontInfo.addMetrics("F3", metric); + metric = new FontMetricsMapper("SansSerif", bolditalic, graphics); + // --> goes to F4 + fontInfo.addMetrics("F4", metric); + + + metric = new FontMetricsMapper("Serif", normal, graphics); + // --> goes to F5 + fontInfo.addMetrics("F5", metric); + metric = new FontMetricsMapper("Serif", italic, graphics); + // --> goes to F6 + fontInfo.addMetrics("F6", metric); + metric = new FontMetricsMapper("Serif", bold, graphics); + // --> goes to F7 + fontInfo.addMetrics("F7", metric); + metric = new FontMetricsMapper("Serif", bolditalic, graphics); + // --> goes to F8 + fontInfo.addMetrics("F8", metric); + + metric = new FontMetricsMapper("MonoSpaced", normal, graphics); + // --> goes to F9 + fontInfo.addMetrics("F9", metric); + metric = new FontMetricsMapper("MonoSpaced", italic, graphics); + // --> goes to F10 + fontInfo.addMetrics("F10", metric); + metric = new FontMetricsMapper("MonoSpaced", bold, graphics); + // --> goes to F11 + fontInfo.addMetrics("F11", metric); + metric = new FontMetricsMapper("MonoSpaced", bolditalic, graphics); + // --> goes to F12 + fontInfo.addMetrics("F12", metric); + + metric = new FontMetricsMapper("Symbol", bolditalic, graphics); + // --> goes to F13 and F14 + fontInfo.addMetrics("F13", metric); + fontInfo.addMetrics("F14", metric); + + // Custom type 1 fonts step 1/2 + // fontInfo.addMetrics("F15", new OMEP()); + // fontInfo.addMetrics("F16", new GaramondLightCondensed()); + // fontInfo.addMetrics("F17", new BauerBodoniBoldItalic()); + + /* any is treated as serif */ + fontInfo.addFontProperties("F5", "any", "normal", Font.NORMAL); + fontInfo.addFontProperties("F6", "any", "italic", Font.NORMAL); + fontInfo.addFontProperties("F6", "any", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F7", "any", "normal", Font.BOLD); + fontInfo.addFontProperties("F8", "any", "italic", Font.BOLD); + fontInfo.addFontProperties("F8", "any", "oblique", Font.BOLD); + + fontInfo.addFontProperties("F1", "sans-serif", "normal", Font.NORMAL); + fontInfo.addFontProperties("F2", "sans-serif", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F2", "sans-serif", "italic", Font.NORMAL); + fontInfo.addFontProperties("F3", "sans-serif", "normal", Font.BOLD); + fontInfo.addFontProperties("F4", "sans-serif", "oblique", Font.BOLD); + fontInfo.addFontProperties("F4", "sans-serif", "italic", Font.BOLD); + fontInfo.addFontProperties("F5", "serif", "normal", Font.NORMAL); + fontInfo.addFontProperties("F6", "serif", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F6", "serif", "italic", Font.NORMAL); + fontInfo.addFontProperties("F7", "serif", "normal", Font.BOLD); + fontInfo.addFontProperties("F8", "serif", "oblique", Font.BOLD); + fontInfo.addFontProperties("F8", "serif", "italic", Font.BOLD); + fontInfo.addFontProperties("F9", "monospace", "normal", Font.NORMAL); + fontInfo.addFontProperties("F10", "monospace", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F10", "monospace", "italic", Font.NORMAL); + fontInfo.addFontProperties("F11", "monospace", "normal", Font.BOLD); + fontInfo.addFontProperties("F12", "monospace", "oblique", Font.BOLD); + fontInfo.addFontProperties("F12", "monospace", "italic", Font.BOLD); + + fontInfo.addFontProperties("F1", "Helvetica", "normal", Font.NORMAL); + fontInfo.addFontProperties("F2", "Helvetica", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F2", "Helvetica", "italic", Font.NORMAL); + fontInfo.addFontProperties("F3", "Helvetica", "normal", Font.BOLD); + fontInfo.addFontProperties("F4", "Helvetica", "oblique", Font.BOLD); + fontInfo.addFontProperties("F4", "Helvetica", "italic", Font.BOLD); + fontInfo.addFontProperties("F5", "Times", "normal", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times", "italic", Font.NORMAL); + fontInfo.addFontProperties("F7", "Times", "normal", Font.BOLD); + fontInfo.addFontProperties("F8", "Times", "oblique", Font.BOLD); + fontInfo.addFontProperties("F8", "Times", "italic", Font.BOLD); + fontInfo.addFontProperties("F9", "Courier", "normal", Font.NORMAL); + fontInfo.addFontProperties("F10", "Courier", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F10", "Courier", "italic", Font.NORMAL); + fontInfo.addFontProperties("F11", "Courier", "normal", Font.BOLD); + fontInfo.addFontProperties("F12", "Courier", "oblique", Font.BOLD); + fontInfo.addFontProperties("F12", "Courier", "italic", Font.BOLD); + fontInfo.addFontProperties("F13", "Symbol", "normal", Font.NORMAL); + fontInfo.addFontProperties("F14", "ZapfDingbats", "normal", Font.NORMAL); + + // Custom type 1 fonts step 2/2 + // fontInfo.addFontProperties("F15", "OMEP", "normal", FontInfo.NORMAL); + // fontInfo.addFontProperties("F16", "Garamond-LightCondensed", "normal", FontInfo.NORMAL); + // fontInfo.addFontProperties("F17", "BauerBodoni", "italic", FontInfo.BOLD); + + /* for compatibility with PassiveTex */ + fontInfo.addFontProperties("F5", "Times-Roman", "normal", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times-Roman", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times-Roman", "italic", Font.NORMAL); + fontInfo.addFontProperties("F7", "Times-Roman", "normal", Font.BOLD); + fontInfo.addFontProperties("F8", "Times-Roman", "oblique", Font.BOLD); + fontInfo.addFontProperties("F8", "Times-Roman", "italic", Font.BOLD); + fontInfo.addFontProperties("F5", "Times Roman", "normal", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times Roman", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times Roman", "italic", Font.NORMAL); + fontInfo.addFontProperties("F7", "Times Roman", "normal", Font.BOLD); + fontInfo.addFontProperties("F8", "Times Roman", "oblique", Font.BOLD); + fontInfo.addFontProperties("F8", "Times Roman", "italic", Font.BOLD); + fontInfo.addFontProperties("F9", "Computer-Modern-Typewriter", + "normal", Font.NORMAL); + } + +} + Index: src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java =================================================================== RCS file: src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java diff -N src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,300 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.java2d; + +// Java +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; +import java.awt.FontMetrics; +import java.awt.font.TextLayout; + +/** + * This is a FontMetrics to be used for AWT rendering. + * It instanciates a font, depening on family and style + * values. The java.awt.FontMetrics for this font is then + * created to be used for the actual measurement. + * Since layout is word by word and since it is expected that + * two subsequent words often share the same style, the + * Font and FontMetrics is buffered and only changed if needed. + *

    + * Since FontState and FontInfo multiply all factors by + * size, we assume a "standard" font of FONT_SIZE. + */ +public class Java2DFontMetrics { + + /** + * Font size standard used for metric measurements + */ + public static final int FONT_SIZE = 1; + + /** + * This factor multiplies the calculated values to scale + * to FOP internal measurements + */ + public static final int FONT_FACTOR = (1000 * 1000) / FONT_SIZE; + + /** + * The width of all 256 character, if requested + */ + private int width[] = null; + + /** + * The typical height of a small cap latter + */ + private int xHeight = 0; + + /** + * Buffered font. + * f1 is bufferd for metric measurements during layout. + * fSized is buffered for display purposes + */ + private Font f1 = null; // , fSized = null; + + /** + * The family type of the font last used + */ + private String family = ""; + + /** + * The style of the font last used + */ + private int style = 0; + + /** + * The size of the font last used + */ + private float size = 0; + + /** + * The FontMetrics object used to calculate character width etc. + */ + private FontMetrics fmt = null; + + /** + * Temp graphics object needed to get the font metrics + */ + private Graphics2D graphics; + + /** + * Constructs a new Font-metrics. + * @param graphics a temp graphics object - this is needed so + * that we can get an instance of java.awt.FontMetrics + */ + public Java2DFontMetrics(Graphics2D graphics) { + this.graphics = graphics; + } + + /** + * Determines the font ascent of the Font described by this + * FontMetrics object + * @param family font family (java name) to use + * @param style font style (java def.) to use + * @param size font size + * @return ascent in milliponts + */ + public int getAscender(String family, int style, int size) { + setFont(family, style, size); + // return (int)(FONT_FACTOR * fmt.getAscent()); + + // workaround for sun bug on FontMetrics.getAscent() + // http://developer.java.sun.com/developer/bugParade/bugs/4399887.html + /* + * Bug 4399887 has status Closed, not a bug. The comments on the bug + * are: + * The submitter is incorrectly assuming that the string he has used + * is displaying characters which represent those with the maximum + * ascent in the font. If (for example) the unicode character + * \u00c1 which is the A-acute character used in many European + * languages is placed in the bodu of the "Wrong" string it can be + * seen that the JDK is exactly correct in its determination of the + * ascent of the font. + * If the bounds of a particular string are interesting then the + * Rectangle FontMetrics.getStringBounds(..) method can be called. + * The y value of the rectangle is the offset from the origin + * (baseline) apparently needed by the sample test program + * + * xxxxx@xxxxx 2001-05-15 + */ + int realAscent = fmt.getAscent() + - (fmt.getDescent() + fmt.getLeading()); + return FONT_FACTOR * realAscent; + } + + + /** + * The size of a capital letter measured from the font's baseline + * @param family font family + * @param style font style + * @param size font size + * @return capital height in millipoints + */ + public int getCapHeight(String family, int style, int size) { + // currently just gets Ascent value but maybe should use + // getMaxAcent() at some stage + return getAscender(family, style, size); + } + + /** + * Determines the font descent of the Font described by this + * FontMetrics object + * @param family font family (jave name) to use + * @param style font style (jave def.) to use + * @param size font size + * @return descent in milliponts + */ + public int getDescender(String family, int style, int size) { + setFont(family, style, size); + return (-1 * FONT_FACTOR * fmt.getDescent()); + } + + /** + * Determines the typical font height of a small cap letter + * FontMetrics object + * @param family font family (jave name) to use + * @param style font style (jave def.) to use + * @param size font size + * @return font height in milliponts + */ + public int getXHeight(String family, int style, int size) { + setFont(family, style, size); + return (int)(FONT_FACTOR * xHeight); + } + + /** + * Returns width (in 1/1000ths of point size) of character at + * code point i + * @param i the character for which to get the width + * @param family font family (jave name) to use + * @param style font style (jave def.) to use + * @param size font size + * @return character width in millipoints + */ + public int width(int i, String family, int style, int size) { + int w; + setFont(family, style, size); + // the output seems to look a little better if the + // space is rendered larger than given by + // the FontMetrics object + if (i <= 32) { + w = (int)(1.4 * fmt.charWidth(i) * FONT_FACTOR); + } else { + w = (int)(fmt.charWidth(i) * FONT_FACTOR); + } + return w; + } + + /** + * Return widths (in 1/1000ths of point size) of all + * characters + * @param family font family (jave name) to use + * @param style font style (jave def.) to use + * @param size font size + * @return array of character widths in millipoints + */ + public int[] getWidths(String family, int style, int size) { + int i; + + if (width == null) { + width = new int[256]; + } + setFont(family, style, size); + for (i = 0; i < 256; i++) { + width[i] = FONT_FACTOR * fmt.charWidth(i); + } + return width; + } + + /** + * Checks whether the font for which values are + * requested is the one used immediately before or + * whether it is a new one + * @param family font family (jave name) to use + * @param style font style (jave def.) to use + * @param size font size + * @return true if the font was changed, false otherwise + */ + private boolean setFont(String family, int style, int size) { + boolean changed = false; + Rectangle2D rect; + TextLayout layout; + int s = (int)(size / 1000f); + + if (f1 == null) { + f1 = new Font(family, style, s); + fmt = graphics.getFontMetrics(f1); + changed = true; + } else { + if ((this.style != style) || !this.family.equals(family) + || this.size != s) { + if (family.equals(this.family)) { + f1 = f1.deriveFont(style, (float)s); + } else { + f1 = new Font(family, style, s); + } + fmt = graphics.getFontMetrics(f1); + changed = true; + } + // else the font is unchanged from last time + } + if (changed) { + layout = new TextLayout("m", f1, graphics.getFontRenderContext()); + rect = layout.getBounds(); + xHeight = (int)rect.getHeight(); + } + // save the family and style for later comparison + this.family = family; + this.style = style; + this.size = s; + return changed; + } + + + /** + * Returns a java.awt.Font instance for the desired + * family, style and size type. + * This is here, so that the font-mapping + * of FOP-defined fonts to java-fonts can be done + * in one place and does not need to occur in + * AWTFontRenderer. + * @param family font family (jave name) to use + * @param style font style (jave def.) to use + * @param size font size + * @return font with the desired characeristics. + */ + public java.awt.Font getFont(String family, int style, int size) { + setFont(family, style, size); + return f1; + /* + * if( setFont(family,style, size) ) fSized = null; + * if( fSized == null || this.size != size ) { + * fSized = f1.deriveFont( size / 1000f ); + * } + * this.size = size; + * return fSized; + */ + } + +} + + + + + + Index: src/java/org/apache/fop/render/java2d/Java2DGraphicsState.java =================================================================== RCS file: src/java/org/apache/fop/render/java2d/Java2DGraphicsState.java diff -N src/java/org/apache/fop/render/java2d/Java2DGraphicsState.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/fop/render/java2d/Java2DGraphicsState.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,345 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id: AWTGraphicsState.java,v 1.1 2005/03/08 11:47:55 jeremias Exp $ */ + +package org.apache.fop.render.java2d; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Area; +import java.awt.geom.GeneralPath; +import java.util.List; + +import org.apache.fop.datatypes.ColorType; +import org.apache.fop.fo.Constants; +import org.apache.fop.fonts.FontInfo; + +/** + * Keeps information about the current state of the Graphics2D currentGraphics. + * It is also used as a stack to hold a graphics context. + *

    + * The graphics context is updated with the updateXXX() methods. + */ +public class Java2DGraphicsState implements Constants, RendererState { + + /** Holds the datas of the current state */ + private Graphics2D currentGraphics; + + private BasicStroke currentStroke; + + private float currentStrokeWidth; + + private int currentStrokeStyle; + + private List stateStack = new java.util.ArrayList(); + + /** Font configuration, passed from AWTRenderer */ + private FontInfo fontInfo; + + /** Initial AffinTransform passed by the renderer, includes scaling-info */ + private AffineTransform initialTransform; + + /** + * State for storing graphics state. + * @param graphics the graphics associated with the BufferedImage + * @param fontInfo the FontInfo from the renderer + * @param at the initial AffineTransform containing the scale transformation + */ + public Java2DGraphicsState(Graphics2D graphics, FontInfo fontInfo, + AffineTransform at) { + this.fontInfo = fontInfo; + this.currentGraphics = graphics; + this.initialTransform = at; + currentGraphics.setTransform(at); + } + + /** + * @return the currently valid state + */ + public Graphics2D getGraph() { + return currentGraphics; + } + + /** @see org.apache.fop.render.java2d.RendererState#push() */ + public void push() { + Graphics2D tmpGraphics = (Graphics2D) currentGraphics.create(); + stateStack.add(tmpGraphics); + } + + /** @see org.apache.fop.render.java2d.RendererState#pop() */ + public Graphics2D pop() { + if (getStackLevel() > 0) { + Graphics2D popped = (Graphics2D) stateStack.remove(stateStack + .size() - 1); + + currentGraphics = popped; + return popped; + } else { + return null; + } + } + + /** @see org.apache.fop.render.java2d.RendererState#getStackLevel() */ + public int getStackLevel() { + return stateStack.size(); + } + + /** + * Restore the state to a particular level. this can be used to restore to a + * known level without making multiple pop calls. + * + * @param stack the level to restore to + */ + /* + * public void restoreLevel(int stack) { int pos = stack; while + * (stateStack.size() > pos + 1) { stateStack.remove(stateStack.size() - 1); } + * if (stateStack.size() > pos) { pop(); } } + */ + + /** + * Set the current background color. Check if the background color will + * change and then set the new color. + * + * @param col the new color as a java.awt.Color + * @return true if the background color has changed + */ + public boolean updateColor(Color col) { + if (!col.equals(getGraph().getColor())) { + getGraph().setColor(col); + return true; + } else { + return false; + } + } + + /** + * Converts a ColorType to a java.awt.Color (sRGB). + * + * @param col the color as a org.apache.fop.datatypes.ColorType + * @return the converted color as a java.awt.Color + */ + public Color toColor(ColorType col) { + return new Color(col.getRed(), col.getGreen(), col.getBlue()); + } + + /** + * @see org.apache.fop.render.java2d.RendererState#updateColor(org.apache.fop.datatypes.ColorType, + * boolean, java.lang.StringBuffer) + */ + public boolean updateColor(ColorType col, boolean fill, StringBuffer pdf) { + if (col == null) { + return false; + } + Color newCol = toColor(col); + return updateColor(newCol); + } + + /** + * Update the current Color + * @param col the ColorType + */ + public void updateColor(ColorType col) { + if (col == null) { + return; + } + Color newCol = toColor(col); + updateColor(newCol); + } + + /** + * @return the current java.awt.Color + */ + public java.awt.Color getColor() { + return currentGraphics.getColor(); + } + + /** + * @see org.apache.fop.render.java2d.RendererState#updateFont(java.lang.String, + * int, java.lang.StringBuffer) + */ + public boolean updateFont(String name, int size, StringBuffer pdf) { + + boolean updateName = (!name.equals(getGraph().getFont().getFontName())); + boolean updateSize = (size != (getGraph().getFont().getSize())); + + if (updateName || updateSize) { + // the font name and/or the font size have changed + FontMetricsMapper mapper = (FontMetricsMapper) fontInfo + .getMetricsFor(name); + java.awt.Font font = mapper.getFont(size); + + currentGraphics.setFont(font); + return true; + } else { + return false; + } + } + + /** + * @return the current java.awt.Font + */ + public java.awt.Font getFont() { + return currentGraphics.getFont(); + } + + /** + * @see org.apache.fop.render.java2d.RendererState#updateStroke(float, int) + */ + public boolean updateStroke(float width, int style) { + + boolean update = false; + + // only update if necessary + if ((width != currentStrokeWidth) || (style != currentStrokeStyle)) { + + update = true; + + switch (style) { + case EN_DOTTED: + + currentStroke = new BasicStroke(width, BasicStroke.CAP_BUTT, + BasicStroke.JOIN_BEVEL, 0f, new float[] { 2f }, 0f); + currentGraphics.setStroke(currentStroke); + + currentStrokeWidth = width; + currentStrokeStyle = style; + + break; + + case EN_DASHED: + + currentStroke = new BasicStroke(width, BasicStroke.CAP_BUTT, + BasicStroke.JOIN_BEVEL, 0f, new float[] { 8f, 2f }, 0f); + currentGraphics.setStroke(currentStroke); + + currentStrokeWidth = width; + currentStrokeStyle = style; + + break; + + default: // EN_SOLID: + + currentStroke = new BasicStroke(width); + currentGraphics.setStroke(currentStroke); + + currentStrokeWidth = width; + currentStrokeStyle = style; + + break; + } + } + + return update; + } + + public BasicStroke getStroke() { + return (BasicStroke) currentGraphics.getStroke(); + } + + /** @see org.apache.fop.render.java2d.RendererState#updatePaint(java.awt.Paint) */ + public boolean updatePaint(Paint p) { + if (getGraph().getPaint() == null) { + if (p != null) { + getGraph().setPaint(p); + return true; + } + } else if (p.equals(getGraph().getPaint())) { + getGraph().setPaint(p); + return true; + } + return false; + } + + /** @see org.apache.fop.render.java2d.RendererState#checkClip(java.awt.Shape) */ + // TODO implement and test + public boolean checkClip(Shape cl) { + if (getGraph().getClip() == null) { + if (cl != null) { + return true; + } + } else if (cl.equals(getGraph().getClip())) { + return true; + } + // TODO check for clips that are larger than the current + return false; + } + + /** + * @see org.apache.fop.render.java2d.RendererState#updateClip(java.awt.Shape) + */ + public boolean updateClip(Shape cl) { + if (getGraph().getClip() != null) { + Area newClip = new Area(getGraph().getClip()); + newClip.intersect(new Area(cl)); + getGraph().setClip(new GeneralPath(newClip)); + } else { + getGraph().setClip(cl); + } + return true; // TODO only update if necessary + } + + /** + * @see org.apache.fop.render.java2d.RendererState#checkTransform(java.awt.geom.AffineTransform) + */ + public boolean checkTransform(AffineTransform tf) { + return !tf.equals(getGraph().getTransform()); + } + + /** + * @see org.apache.fop.render.java2d.RendererState#setTransform(java.awt.geom.AffineTransform) + */ + public void setTransform(AffineTransform tf) { + //apply initial transformation + getGraph().setTransform(initialTransform); + getGraph().transform(tf); + } + + /** + * @see org.apache.fop.render.java2d.RendererState#transform(java.awt.geom.AffineTransform) + */ + public void transform(AffineTransform tf) { + getGraph().transform(tf); + } + + /** + * @see org.apache.fop.render.java2d.RendererState#getTransform() + */ + public AffineTransform getTransform() { + /* + * AffineTransform tf; AffineTransform at = new AffineTransform(); for + * (Iterator iter = stateStack.iterator(); iter.hasNext();) { Data d = + * (Data) iter.next(); tf = d.transform; at.concatenate(tf); } + * at.concatenate(getCurrentGraphics().transform); + * + * return at; + */ + return getGraph().getTransform(); + } + + /** a verbose description of the current state */ + public String toString() { + String s = "AWTGraphicsState " + currentGraphics.toString() + + ", Stroke (width: " + currentStrokeWidth + " style: " + + currentStrokeStyle + "), " + getTransform() + + ", StackLevel: " + getStackLevel(); + return s; + } +} Index: src/java/org/apache/fop/render/java2d/Java2DRenderer.java =================================================================== RCS file: src/java/org/apache/fop/render/java2d/Java2DRenderer.java diff -N src/java/org/apache/fop/render/java2d/Java2DRenderer.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/fop/render/java2d/Java2DRenderer.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,1114 @@ +/* + * Copyright 1999-2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.java2d; + +// Java +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.color.ColorSpace; +import java.awt.geom.AffineTransform; +import java.awt.geom.Line2D; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.Raster; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.GVTBuilder; +import org.apache.batik.bridge.ViewBox; +import org.apache.batik.gvt.GraphicsNode; +import org.apache.fop.apps.FOPException; +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.area.Area; +import org.apache.fop.area.Block; +import org.apache.fop.area.BlockViewport; +import org.apache.fop.area.CTM; +import org.apache.fop.area.PageViewport; +import org.apache.fop.area.Trait; +import org.apache.fop.area.inline.Character; +import org.apache.fop.area.inline.ForeignObject; +import org.apache.fop.area.inline.Image; +import org.apache.fop.area.inline.InlineArea; +import org.apache.fop.area.inline.Leader; +import org.apache.fop.area.inline.TextArea; +import org.apache.fop.datatypes.ColorType; +import org.apache.fop.fo.Constants; +import org.apache.fop.fonts.Font; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.FontMetrics; +import org.apache.fop.image.FopImage; +import org.apache.fop.image.ImageFactory; +import org.apache.fop.image.XMLImage; +import org.apache.fop.render.AbstractRenderer; +import org.apache.fop.render.RendererContext; +import org.apache.fop.render.pdf.CTMHelper; +import org.apache.fop.svg.SVGUserAgent; +import org.apache.fop.traits.BorderProps; +import org.w3c.dom.Document; +import org.w3c.dom.svg.SVGDocument; +import org.w3c.dom.svg.SVGSVGElement; + +/** + * The Java2DRenderer class provides the abstract technical + * foundation for all rendering with the Java2D API. Renderers like + * AWTRenderer subclass it and provide the concrete output paths. + *

    + * A lot of the logic is performed by AbstractRenderer. The + * class-variables currentIPPosition and + * currentBPPosition hold the position of the currently rendered + * area. + *

    + * Java2DGraphicsState state holds the Graphics2D, + * which is used along the whole rendering. state also acts as a + * stack (state.push() and state.pop()). + *

    + * The rendering process is basically always the same: + *

    + * void renderXXXXX(Area area) { + * //calculate the currentPosition + * state.updateFont(name, size, null); + * state.updateColor(ct, false, null); + * state.getGraph.draw(new Shape(args)); + * } + * + */ +public abstract class Java2DRenderer extends AbstractRenderer { + + /** The MIME type for Java2D-Rendering */ + public static final String MIME_TYPE = "application/X-Java2D"; + + /** The scale factor for the image size, values: ]0 ; 1] */ + protected double scaleFactor = 1; + + /** The page width in pixels */ + protected int pageWidth = 0; + + /** The page height in pixels */ + protected int pageHeight = 0; + + /** List of Viewports */ + protected List pageViewportList = new java.util.ArrayList(); + + /** The 0-based current page number */ + private int currentPageNumber = 0; + + /** The 0-based total number of rendered pages */ + private int numberOfPages; + + /** true if antialiasing is set */ + protected boolean antialiasing = true; + + /** true if qualityRendering is set */ + protected boolean qualityRendering = true; + + /** The current state, holds a Graphics2D and its context */ + protected Java2DGraphicsState state; + + /** a Line2D.Float used to draw text decorations and leaders */ + protected Line2D.Float line = new Line2D.Float(); + + /** Font configuration */ + protected FontInfo fontInfo; + + protected Map fontNames = new java.util.Hashtable(); + + protected Map fontStyles = new java.util.Hashtable(); + + /** true if the renderer has finished rendering all the pages */ + public boolean renderingDone; + + /** Default constructor */ + public Java2DRenderer() {} + + /** + * @see org.apache.fop.render.Renderer#setUserAgent(org.apache.fop.apps.FOUserAgent) + */ + public void setUserAgent(FOUserAgent foUserAgent) { + super.setUserAgent(foUserAgent); + userAgent.setRendererOverride(this); // for document regeneration + } + + /** @return the FOUserAgent */ + public FOUserAgent getUserAgent() { + return userAgent; + } + + /** @see org.apache.fop.render.AbstractRenderer */ + public String getMimeType() { + return MIME_TYPE; + } + + /** + * @see org.apache.fop.render.Renderer#setupFontInfo(org.apache.fop.fonts.FontInfo) + */ + public void setupFontInfo(FontInfo inFontInfo) { + // create a temp Image to test font metrics on + fontInfo = inFontInfo; + BufferedImage fontImage = new BufferedImage(100, 100, + BufferedImage.TYPE_INT_RGB); + FontSetup.setup(fontInfo, fontImage.createGraphics()); + } + + /** + * Sets the new scale factor. + * @param newScaleFactor ]0 ; 1] + */ + public void setScaleFactor(double newScaleFactor) { + scaleFactor = newScaleFactor; + } + + public double getScaleFactor() { + return scaleFactor; + } + + public void startRenderer(OutputStream out) throws IOException { + // do nothing by default + } + + public void stopRenderer() throws IOException { + getLogger().debug("Java2DRenderer stopped"); + renderingDone = true; + numberOfPages = currentPageNumber; + // TODO set all vars to null for gc + if (numberOfPages == 0) { + new FOPException("No page could be rendered"); + } + } + + /** @return The 0-based current page number */ + public int getCurrentPageNumber() { + return currentPageNumber; + } + + /** @param The 0-based current page number */ + public void setCurrentPageNumber(int c) { + this.currentPageNumber = c; + } + + /** @return The 0-based total number of rendered pages */ + public int getNumberOfPages() { + return numberOfPages; + } + + /** clears the ViewportList, in case the document is reloaded */ + public void clearViewportList() { + pageViewportList.clear(); + setCurrentPageNumber(0); + } + + /** + * @param the 0-based pageIndex + * @return the corresponding PageViewport + */ + public PageViewport getPageViewport(int pageIndex) { + return (PageViewport) pageViewportList.get(pageIndex); + } + + /** + * This method override only stores the PageViewport in a List. No actual + * rendering is performed here. A renderer override renderPage() to get the + * freshly produced PageViewport, and rendere them on the fly (producing the + * desired BufferedImages by calling getPageImage(), which lazily starts the + * rendering process). + * + * @param pageViewport the PageViewport object supplied by + * the Area Tree + * @see org.apache.fop.render.Renderer + */ + public void renderPage(PageViewport pageViewport) throws IOException, + FOPException { + pageViewportList.add(pageViewport.clone());// FIXME clone + currentPageNumber++; + } + + /** + * Generates a desired page from the renderer's page viewport list. + * + * @param pageViewport the PageViewport to be rendered + * @return the java.awt.image.BufferedImage corresponding to + * the page or null if the page doesn't exist. + */ + public BufferedImage getPageImage(PageViewport pageViewport) { + + Rectangle2D bounds = pageViewport.getViewArea(); + pageWidth = (int) Math.round(bounds.getWidth() / 1000f); + pageHeight = (int) Math.round(bounds.getHeight() / 1000f); + + getLogger().info( + "Rendering Page " + pageViewport.getPageNumberString() + + " (pageWidth " + pageWidth + ", pageHeight " + + pageHeight + ")"); + + BufferedImage currentPageImage = new BufferedImage( + (int) ((pageWidth * scaleFactor)), + (int) ((pageHeight * scaleFactor)), BufferedImage.TYPE_INT_RGB); + // FIXME TYPE_BYTE_BINARY ? + + Graphics2D graphics = currentPageImage.createGraphics(); + graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, + RenderingHints.VALUE_FRACTIONALMETRICS_ON); + if (antialiasing) { + graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + } + if (qualityRendering) { + graphics.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + } + + // transform page based on scale factor supplied + AffineTransform at = graphics.getTransform(); + at.scale(scaleFactor, scaleFactor); + graphics.setTransform(at); + + // draw page frame + graphics.setColor(Color.white); + graphics.fillRect(0, 0, pageWidth, pageHeight); + graphics.setColor(Color.black); + graphics.drawRect(-1, -1, pageWidth + 2, pageHeight + 2); + graphics.drawLine(pageWidth + 2, 0, pageWidth + 2, pageHeight + 2); + graphics.drawLine(pageWidth + 3, 1, pageWidth + 3, pageHeight + 3); + graphics.drawLine(0, pageHeight + 2, pageWidth + 2, pageHeight + 2); + graphics.drawLine(1, pageHeight + 3, pageWidth + 3, pageHeight + 3); + + state = new Java2DGraphicsState(graphics, this.fontInfo, at); + + // reset the current Positions + currentBPPosition = 0; + currentIPPosition = 0; + + // this toggles the rendering of all areas + renderPageAreas(pageViewport.getPage()); + return currentPageImage; + } + + /** + * Generates a desired page from the renderer's page viewport list. + * + * @param pageNum the 0-based page number to generate + * @return the java.awt.image.BufferedImage corresponding to + * the page or null if the page doesn't exist. + * @throws FOPException + */ + public BufferedImage getPageImage(int pageNum) throws FOPException { + if (pageNum < 0 || pageNum >= pageViewportList.size()) { + throw new FOPException("out-of-range page number (" + pageNum + + ") requested; only " + pageViewportList.size() + + " page(s) available."); + } + PageViewport pageViewport = (PageViewport) pageViewportList + .get(pageNum); + return getPageImage(pageViewport); + } + + /** + * @see org.apache.fop.render.AbstractRenderer#startVParea(CTM) + */ + protected void startVParea(CTM ctm) { + + // push (and save) the current graphics state + state.push(); + + // Set the given CTM in the graphics state + state.setTransform(new AffineTransform(CTMHelper.toPDFArray(ctm))); + + // TODO Set clip? + } + + /** + * @see org.apache.fop.render.AbstractRenderer#endVParea() + */ + protected void endVParea() { + state.pop(); + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderBlockViewport(BlockViewport, + * List) + */ + protected void renderBlockViewport(BlockViewport bv, List children) { + // clip and position viewport if necessary + + // save positions + int saveIP = currentIPPosition; + int saveBP = currentBPPosition; + + CTM ctm = bv.getCTM(); + int borderPaddingStart = bv.getBorderAndPaddingWidthStart(); + int borderPaddingBefore = bv.getBorderAndPaddingWidthBefore(); + float x, y; + x = (float) (bv.getXOffset() + containingIPPosition) / 1000f; + y = (float) (bv.getYOffset() + containingBPPosition) / 1000f; + + if (bv.getPositioning() == Block.ABSOLUTE + || bv.getPositioning() == Block.FIXED) { + // TODO not tested yet + // For FIXED, we need to break out of the current viewports to the + // one established by the page. We save the state stack for + // restoration + // after the block-container has been painted. See below. + List breakOutList = null; + if (bv.getPositioning() == Block.FIXED) { + getLogger().debug("Block.FIXED --> break out"); + breakOutList = new java.util.ArrayList(); + Graphics2D graph; + while (true) { + graph = state.getGraph(); + if (state.pop() == null) { + break; + } + breakOutList.add(0, graph); // Insert because of + // stack-popping + getLogger().debug("Adding to break out list: " + graph); + } + } + + CTM tempctm = new CTM(containingIPPosition, containingBPPosition); + ctm = tempctm.multiply(ctm); + + // This is the content-rect + float width = (float) bv.getIPD() / 1000f; + float height = (float) bv.getBPD() / 1000f; + + // Adjust for spaces (from margin or indirectly by start-indent etc. + Integer spaceStart = (Integer) bv.getTrait(Trait.SPACE_START); + if (spaceStart != null) { + x += spaceStart.floatValue() / 1000; + } + Integer spaceBefore = (Integer) bv.getTrait(Trait.SPACE_BEFORE); + if (spaceBefore != null) { + y += spaceBefore.floatValue() / 1000; + } + + float bpwidth = (borderPaddingStart + bv + .getBorderAndPaddingWidthEnd()) / 1000f; + float bpheight = (borderPaddingBefore + bv + .getBorderAndPaddingWidthAfter()) / 1000f; + + drawBackAndBorders(bv, x, y, width + bpwidth, height + bpheight); + + // Now adjust for border/padding + x += borderPaddingStart / 1000f; + y += borderPaddingBefore / 1000f; + + if (bv.getClip()) { + // saves the graphics state in a stack + state.push(); + + clip(x, y, width, height); + } + + startVParea(ctm); + + renderBlocks(bv, children); + endVParea(); + + if (bv.getClip()) { + // restores the last graphics state from the stack + state.pop(); + } + + // clip if necessary + + if (breakOutList != null) { + getLogger().debug( + "Block.FIXED --> restoring context after break-out"); + Graphics2D graph; + Iterator i = breakOutList.iterator(); + while (i.hasNext()) { + graph = (Graphics2D) i.next(); + getLogger().debug("Restoring: " + graph); + state.push(); + } + } + + currentIPPosition = saveIP; + currentBPPosition = saveBP; + + } else { // orientation = Block.STACK or RELATIVE + + Integer spaceBefore = (Integer) bv.getTrait(Trait.SPACE_BEFORE); + if (spaceBefore != null) { + currentBPPosition += spaceBefore.intValue(); + } + + // borders and background in the old coordinate system + handleBlockTraits(bv); + + CTM tempctm = new CTM(containingIPPosition, currentBPPosition + + containingBPPosition); + ctm = tempctm.multiply(ctm); + + // Now adjust for border/padding + x += borderPaddingStart / 1000f; + y += borderPaddingBefore / 1000f; + + // clip if necessary + if (bv.getClip()) { + // saves the graphics state in a stack + state.push(); + float width = (float) bv.getIPD() / 1000f; + float height = (float) bv.getBPD() / 1000f; + clip(x, y, width, height); + } + + if (ctm != null) { + startVParea(ctm); + } + renderBlocks(bv, children); + if (ctm != null) { + endVParea(); + } + + if (bv.getClip()) { + // restores the last graphics state from the stack + state.pop(); + } + + currentIPPosition = saveIP; + currentBPPosition = saveBP; + + // Adjust BP position (alloc BPD + spaces) + if (spaceBefore != null) { + currentBPPosition += spaceBefore.intValue(); + } + currentBPPosition += (int) (bv.getAllocBPD()); + Integer spaceAfter = (Integer) bv.getTrait(Trait.SPACE_AFTER); + if (spaceAfter != null) { + currentBPPosition += spaceAfter.intValue(); + } + } + } + + /** + * Clip an area. + */ + protected void clip() { + // TODO via AWTGraphicsState.updateClip(); + // currentStream.add("W\n"); + // currentStream.add("n\n"); + } + + /** + * Clip an area. write a clipping operation given coordinates in the current + * transform. + * + * @param x the x coordinate + * @param y the y coordinate + * @param width the width of the area + * @param height the height of the area + */ + protected void clip(float x, float y, float width, float height) { + // TODO via AWTGraphicsState.updateClip(); + // currentStream.add(x + " " + y + " " + width + " " + height + " + // re "); + clip(); + } + + /** + * Draw the background and borders. This draws the background and border + * traits for an area given the position. + * + * @param block the area to get the traits from + * @param startx the start x position + * @param starty the start y position + * @param width the width of the area + * @param height the height of the area + */ + protected void drawBackAndBorders(Area area, float startx, float starty, + float width, float height) { + + BorderProps bpsBefore = (BorderProps) area + .getTrait(Trait.BORDER_BEFORE); + BorderProps bpsAfter = (BorderProps) area.getTrait(Trait.BORDER_AFTER); + BorderProps bpsStart = (BorderProps) area.getTrait(Trait.BORDER_START); + BorderProps bpsEnd = (BorderProps) area.getTrait(Trait.BORDER_END); + + // draw background + Trait.Background back; + back = (Trait.Background) area.getTrait(Trait.BACKGROUND); + if (back != null) { + + // Calculate padding rectangle + float sx = startx; + float sy = starty; + float paddRectWidth = width; + float paddRectHeight = height; + + if (bpsStart != null) { + sx += bpsStart.width / 1000f; + paddRectWidth -= bpsStart.width / 1000f; + } + if (bpsBefore != null) { + sy += bpsBefore.width / 1000f; + paddRectHeight -= bpsBefore.width / 1000f; + } + if (bpsEnd != null) { + paddRectWidth -= bpsEnd.width / 1000f; + } + if (bpsAfter != null) { + paddRectHeight -= bpsAfter.width / 1000f; + } + + if (back.getColor() != null) { + drawBackground(back, sx, sy, paddRectWidth, paddRectHeight); + } + + // background image + if (back.getFopImage() != null) { + FopImage fopimage = back.getFopImage(); + if (fopimage != null && fopimage.load(FopImage.DIMENSIONS)) { + clip(sx, sy, paddRectWidth, paddRectHeight); + int horzCount = (int) ((paddRectWidth * 1000 / fopimage + .getIntrinsicWidth()) + 1.0f); + int vertCount = (int) ((paddRectHeight * 1000 / fopimage + .getIntrinsicHeight()) + 1.0f); + if (back.getRepeat() == EN_NOREPEAT) { + horzCount = 1; + vertCount = 1; + } else if (back.getRepeat() == EN_REPEATX) { + vertCount = 1; + } else if (back.getRepeat() == EN_REPEATY) { + horzCount = 1; + } + // change from points to millipoints + sx *= 1000; + sy *= 1000; + if (horzCount == 1) { + sx += back.getHoriz(); + } + if (vertCount == 1) { + sy += back.getVertical(); + } + for (int x = 0; x < horzCount; x++) { + for (int y = 0; y < vertCount; y++) { + // place once + Rectangle2D pos; + pos = new Rectangle2D.Float(sx + + (x * fopimage.getIntrinsicWidth()), sy + + (y * fopimage.getIntrinsicHeight()), + fopimage.getIntrinsicWidth(), fopimage + .getIntrinsicHeight()); + putImage(back.getURL(), pos); // TODO test + } + } + + } else { + getLogger().warn( + "Can't find background image: " + back.getURL()); + } + } + } + + // draw border + // BORDER_BEFORE + if (bpsBefore != null) { + int borderWidth = (int) Math.round((bpsBefore.width / 1000f)); + state.updateColor(bpsBefore.color); + state.getGraph().fillRect((int) startx, (int) starty, (int) width, + borderWidth); + } + // BORDER_AFTER + if (bpsAfter != null) { + int borderWidth = (int) Math.round((bpsAfter.width / 1000f)); + float sy = starty + height; + state.updateColor(bpsAfter.color); + state.getGraph().fillRect((int) startx, + (int) (starty + height - borderWidth), (int) width, + borderWidth); + } + // BORDER_START + if (bpsStart != null) { + int borderWidth = (int) Math.round((bpsStart.width / 1000f)); + state.updateColor(bpsStart.color); + state.getGraph().fillRect((int) startx, (int) starty, borderWidth, + (int) height); + } + // BORDER_END + if (bpsEnd != null) { + int borderWidth = (int) Math.round((bpsEnd.width / 1000f)); + float sx = startx + width; + state.updateColor(bpsEnd.color); + state.getGraph().fillRect((int) (startx + width - borderWidth), + (int) starty, borderWidth, (int) height); + } + } + + /** + * Draw the Background Rectangle of a given area. + * + * @param back the Trait.Background + * @param sx x coordinate of the rectangle to be filled. + * @param sy y the y coordinate of the rectangle to be filled. + * @param paddRectWidth the width of the rectangle to be filled. + * @param paddRectHeight the height of the rectangle to be filled. + */ + protected void drawBackground(Trait.Background back, float sx, float sy, + float paddRectWidth, float paddRectHeight) { + + state.updateColor(back.getColor()); + state.getGraph().fillRect((int) sx, (int) sy, (int) paddRectWidth, + (int) paddRectHeight); + } + + /** + * Handle block traits. The block could be any sort of block with any + * positioning so this should render the traits such as border and + * background in its position. + * + * @param block the block to render the traits + */ + protected void handleBlockTraits(Block block) { + // copied from pdf + int borderPaddingStart = block.getBorderAndPaddingWidthStart(); + int borderPaddingBefore = block.getBorderAndPaddingWidthBefore(); + + float startx = currentIPPosition / 1000f; + float starty = currentBPPosition / 1000f; + float width = block.getIPD() / 1000f; + float height = block.getBPD() / 1000f; + + startx += block.getStartIndent() / 1000f; + startx -= block.getBorderAndPaddingWidthStart() / 1000f; + width += borderPaddingStart / 1000f; + width += block.getBorderAndPaddingWidthEnd() / 1000f; + height += borderPaddingBefore / 1000f; + height += block.getBorderAndPaddingWidthAfter() / 1000f; + + drawBackAndBorders(block, startx, starty, width, height); + } + + /** + * @see org.apache.fop.render.Renderer#renderText(TextArea) + */ + public void renderText(TextArea text) { + + float x = currentIPPosition; + float y = currentBPPosition + text.getOffset(); // baseline + + String name = (String) text.getTrait(Trait.FONT_NAME); + int size = ((Integer) text.getTrait(Trait.FONT_SIZE)).intValue(); + state.updateFont(name, size, null); + + ColorType ct = (ColorType) text.getTrait(Trait.COLOR); + state.updateColor(ct, false, null); + + String s = text.getTextArea(); + state.getGraph().drawString(s, x / 1000f, y / 1000f); + + // getLogger().debug("renderText(): \"" + s + "\", x: " + // + x + ", y: " + y + state); + + // rendering text decorations + FontMetrics metrics = fontInfo.getMetricsFor(name); + Font fs = new Font(name, metrics, size); + renderTextDecoration(fs, text, y, x); + + super.renderText(text); + } + + /** + * @see org.apache.fop.render.Renderer#renderCharacter(Character) + */ + public void renderCharacter(Character ch) { + + float x = currentIPPosition; + float y = currentBPPosition + ch.getOffset(); // baseline + + String name = (String) ch.getTrait(Trait.FONT_NAME); + int size = ((Integer) ch.getTrait(Trait.FONT_SIZE)).intValue(); + state.updateFont(name, size, null); + + ColorType ct = (ColorType) ch.getTrait(Trait.COLOR); + state.updateColor(ct, false, null); + + String s = ch.getChar(); + state.getGraph().drawString(s, x / 1000f, y / 1000f); + + // getLogger().debug( "renderCharacter(): \"" + s + "\", x: " + // + x + ", y: " + y + state); + + // rendering text decorations + FontMetrics metrics = fontInfo.getMetricsFor(name); + Font fs = new Font(name, metrics, size); + renderTextDecoration(fs, ch, y, x); + + super.renderCharacter(ch); + } + + /** + * Paints the text decoration marks. + * + * @param fs Current font + * @param inline inline area to paint the marks for + * @param baseline position of the baseline + * @param startIPD start IPD + */ + protected void renderTextDecoration(Font fs, InlineArea inline, + float baseline, float startIPD) { + + boolean hasTextDeco = inline.hasUnderline() || inline.hasOverline() + || inline.hasLineThrough(); + + if (hasTextDeco) { + state.updateStroke((fs.getDescender() / (-8 * 1000f)), + Constants.EN_SOLID); + float endIPD = startIPD + inline.getIPD(); + if (inline.hasUnderline()) { + ColorType ct = (ColorType) inline + .getTrait(Trait.UNDERLINE_COLOR); + state.updateColor(ct, false, null); + float y = baseline - fs.getDescender() / 2; + line.setLine(startIPD / 1000f, y / 1000f, endIPD / 1000f, + y / 1000f); + state.getGraph().draw(line); + } + if (inline.hasOverline()) { + ColorType ct = (ColorType) inline + .getTrait(Trait.OVERLINE_COLOR); + state.updateColor(ct, false, null); + float y = (float) (baseline - (1.1 * fs.getCapHeight())); + line.setLine(startIPD / 1000f, y / 1000f, endIPD / 1000f, + y / 1000f); + state.getGraph().draw(line); + } + if (inline.hasLineThrough()) { + ColorType ct = (ColorType) inline + .getTrait(Trait.LINETHROUGH_COLOR); + state.updateColor(ct, false, null); + float y = (float) (baseline - (0.45 * fs.getCapHeight())); + line.setLine(startIPD / 1000f, y / 1000f, endIPD / 1000f, + y / 1000f); + state.getGraph().draw(line); + } + } + } + + /** + * Render leader area. This renders a leader area which is an area with a + * rule. + * + * @param area the leader area to render + */ + public void renderLeader(Leader area) { + + // TODO leader-length: 25%, 50%, 75%, 100% not working yet + // TODO Colors do not work on Leaders yet + + float startx = ((float) currentIPPosition) / 1000f; + float starty = ((currentBPPosition + area.getOffset()) / 1000f); + float endx = (currentIPPosition + area.getIPD()) / 1000f; + + ColorType ct = (ColorType) area.getTrait(Trait.COLOR); + state.updateColor(ct, true, null); + + line.setLine(startx, starty, endx, starty); + float thickness = area.getRuleThickness() / 1000f; + + int style = area.getRuleStyle(); + switch (style) { + case EN_SOLID: + case EN_DOTTED: + case EN_DASHED: + state.updateStroke(thickness, style); + state.getGraph().draw(line); + break; + case EN_DOUBLE: + + state.updateStroke(thickness / 3f, EN_SOLID); // only a third + + // upper Leader + line.setLine(startx, starty, endx, starty); + state.getGraph().draw(line); + // lower Leader + line.setLine(startx, starty + 2 * thickness, endx, starty + 2 + * thickness); + state.getGraph().draw(line); + + break; + + case EN_GROOVE: + // The rule looks as though it were carved into the canvas. + // (Top/left half of the rule's thickness is the + // color specified; the other half is white.) + + state.updateStroke(thickness / 2f, EN_SOLID); // only the half + + // upper Leader + line.setLine(startx, starty, endx, starty); + state.getGraph().draw(line); + // lower Leader + line.setLine(startx, starty + thickness, endx, starty + thickness); + state.getGraph().setColor(Color.WHITE); + state.getGraph().draw(line); + + // TODO the implementation could be nicer, f.eg. with triangles at + // the tip of the lines. See also RenderX's implementation (looks + // like a button) + + break; + + case EN_RIDGE: + // The opposite of "groove", the rule looks as though it were + // coming out of the canvas. (Bottom/right half of the rule's + // thickness is the color specified; the other half is white.) + + state.updateStroke(thickness / 2f, EN_SOLID); // only the half + + // lower Leader + line.setLine(startx, starty + thickness, endx, starty + thickness); + state.getGraph().draw(line); + // upperLeader + line.setLine(startx, starty, endx, starty); + state.getGraph().setColor(Color.WHITE); + state.getGraph().draw(line); + + // TODO the implementation could be nicer, f.eg. with triangles at + // the tip of the lines. See also RenderX's implementation (looks + // like a button) + + break; + + case EN_NONE: + // No rule is drawn + break; + + } // end switch + + super.renderLeader(area); + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderImage(Image, + * Rectangle2D) + */ + public void renderImage(Image image, Rectangle2D pos) { + // endTextObject(); + String url = image.getURL(); + putImage(url, pos); + } + + /** + * draws an image + * + * @param url URL of the bitmap + * @param pos Position of the bitmap + */ + protected void putImage(String pUrl, Rectangle2D pos) { + + int x = currentIPPosition; // TODO + area.getXOffset(); + int y = currentBPPosition; + String url = ImageFactory.getURL(pUrl); + + ImageFactory fact = ImageFactory.getInstance(); + FopImage fopimage = fact.getImage(url, userAgent); + + if (fopimage == null) { + return; + } + if (!fopimage.load(FopImage.DIMENSIONS)) { + return; + } + int w = fopimage.getWidth(); + int h = fopimage.getHeight(); + String mime = fopimage.getMimeType(); + if ("text/xml".equals(mime)) { + if (!fopimage.load(FopImage.ORIGINAL_DATA)) { + return; + } + Document doc = ((XMLImage) fopimage).getDocument(); + String ns = ((XMLImage) fopimage).getNameSpace(); + renderDocument(doc, ns, pos); + + } else if ("image/svg+xml".equals(mime)) { + if (!fopimage.load(FopImage.ORIGINAL_DATA)) { + return; + } + Document doc = ((XMLImage) fopimage).getDocument(); + renderSVGDocument(doc, pos); // TODO check if ok. + + } else if ("image/eps".equals(mime)) { + getLogger().warn("EPS images are not supported by this renderer"); + currentBPPosition += (h * 1000); + } else if ("image/jpeg".equals(mime)) { + if (!fopimage.load(FopImage.ORIGINAL_DATA)) { + return; + } + + // TODO Load JPEGs rather through fopimage.load(FopImage.BITMAP), + // but JpegImage will need to be extended for that + + // url = url.substring(7); + // url = "C:/eclipse/myWorkbenches/fop4/xml-fop/examples/fo" + url; + java.awt.Image awtImage = new javax.swing.ImageIcon(url).getImage(); + + state.getGraph().drawImage(awtImage, (int) (x / 1000f), + (int) (y / 1000f), (int) w, h, null); + currentBPPosition += (h * 1000); + } else { + if (!fopimage.load(FopImage.BITMAP)) { + getLogger().warn("Loading of bitmap failed: " + url); + return; + } + + byte[] raw = fopimage.getBitmaps(); + + // TODO Hardcoded color and sample models, FIX ME! + ColorModel cm = new ComponentColorModel(ColorSpace + .getInstance(ColorSpace.CS_LINEAR_RGB), false, false, + ColorModel.OPAQUE, DataBuffer.TYPE_BYTE); + SampleModel sampleModel = new PixelInterleavedSampleModel( + DataBuffer.TYPE_BYTE, w, h, 3, w * 3, new int[] { 0, 1, 2 }); + DataBuffer dbuf = new DataBufferByte(raw, w * h * 3); + + WritableRaster raster = Raster.createWritableRaster(sampleModel, + dbuf, null); + + java.awt.Image awtImage; + // Combine the color model and raster into a buffered image + awtImage = new BufferedImage(cm, raster, false, null); + + state.getGraph().drawImage(awtImage, (int) (x / 1000f), + (int) (y / 1000f), (int) w, h, null); + currentBPPosition += (h * 1000); + } + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderForeignObject(ForeignObject, + * Rectangle2D) + */ + public void renderForeignObject(ForeignObject fo, Rectangle2D pos) { + Document doc = fo.getDocument(); + String ns = fo.getNameSpace(); + if (ns.equals("http://www.w3.org/2000/svg")) { + renderSVGDocument(doc, pos); + } else { + renderDocument(doc, ns, pos); + } + // this.currentXPosition += area.getContentWidth(); + } + + /** + * Renders an XML document (SVG for example). + * + * @param doc DOM document representing the XML document + * @param ns Namespace for the document + * @param pos Position on the page + */ + public void renderDocument(Document doc, String ns, Rectangle2D pos) { + RendererContext context; + context = new RendererContext(MIME_TYPE); + context.setUserAgent(userAgent); + // TODO implement + /* + * context.setProperty(PDFXMLHandler.PDF_DOCUMENT, pdfDoc); + * context.setProperty(PDFXMLHandler.OUTPUT_STREAM, ostream); + * context.setProperty(PDFXMLHandler.PDF_STATE, currentState); + * context.setProperty(PDFXMLHandler.PDF_PAGE, currentPage); + * context.setProperty(PDFXMLHandler.PDF_CONTEXT, currentContext == null ? + * currentPage : currentContext); + * context.setProperty(PDFXMLHandler.PDF_CONTEXT, currentContext); + * context.setProperty(PDFXMLHandler.PDF_STREAM, currentStream); + * context.setProperty(PDFXMLHandler.PDF_XPOS, new + * Integer(currentIPPosition + (int) pos.getX())); + * context.setProperty(PDFXMLHandler.PDF_YPOS, new + * Integer(currentBPPosition + (int) pos.getY())); + * context.setProperty(PDFXMLHandler.PDF_FONT_INFO, fontInfo); + * context.setProperty(PDFXMLHandler.PDF_FONT_NAME, currentFontName); + * context.setProperty(PDFXMLHandler.PDF_FONT_SIZE, new + * Integer(currentFontSize)); + * context.setProperty(PDFXMLHandler.PDF_WIDTH, new Integer((int) + * pos.getWidth())); context.setProperty(PDFXMLHandler.PDF_HEIGHT, new + * Integer((int) pos.getHeight())); renderXML(userAgent, context, doc, + * ns); + */ + } + + protected void renderSVGDocument(Document doc, Rectangle2D pos) { + + int x = currentIPPosition; // TODO + area.getXOffset(); + int y = currentBPPosition; + + RendererContext context; + context = new RendererContext(MIME_TYPE); + context.setUserAgent(userAgent); + + SVGUserAgent ua = new SVGUserAgent(context.getUserAgent() + .getPixelUnitToMillimeter(), new AffineTransform()); + + GVTBuilder builder = new GVTBuilder(); + BridgeContext ctx = new BridgeContext(ua); + + GraphicsNode root; + try { + root = builder.build(ctx, doc); + } catch (Exception e) { + getLogger().error( + "svg graphic could not be built: " + e.getMessage(), e); + return; + } + float w = (float) ctx.getDocumentSize().getWidth() * 1000f; + float h = (float) ctx.getDocumentSize().getHeight() * 1000f; + + // correct integer roundoff + state.getGraph().translate(x / 1000, y / 1000); + + SVGSVGElement svg = ((SVGDocument) doc).getRootElement(); + AffineTransform at = ViewBox.getPreserveAspectRatioTransform(svg, + w / 1000f, h / 1000f); + AffineTransform inverse = null; + try { + inverse = at.createInverse(); + } catch (NoninvertibleTransformException e) { + getLogger().warn(e); + } + if (!at.isIdentity()) { + state.getGraph().transform(at); + } + + try { + root.paint(state.getGraph()); + } catch (Exception e) { + e.printStackTrace(); + } + + if (inverse != null && !inverse.isIdentity()) { + state.getGraph().transform(inverse); + } + // correct integer roundoff + // currentState.getCurrentGraphics().translate(-x / 1000f, y / 1000f - + // pageHeight); + state.getGraph().translate(-(x + 500) / 1000, + (y + 500) / 1000 - pageHeight); + } +} Index: src/java/org/apache/fop/render/java2d/RendererState.java =================================================================== RCS file: src/java/org/apache/fop/render/java2d/RendererState.java diff -N src/java/org/apache/fop/render/java2d/RendererState.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/fop/render/java2d/RendererState.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,145 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id: RendererState.java,v 1.1 2005/03/08 11:47:55 jeremias Exp $ */ + +package org.apache.fop.render.java2d; + +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.geom.AffineTransform; + +import org.apache.fop.datatypes.ColorType; + +/** + * An interface for the classes which hold the state of the current graphics context. + */ +public interface RendererState { + + /** + * Push the current state onto the stack. + */ + public abstract void push(); + + /** + * Pop the state from the stack and restore the graphics context. + * @return the restored state, null if the stack is empty. + */ + public abstract Graphics2D pop(); + + /** + * Get the current stack level. + * + * @return the current stack level + */ + public abstract int getStackLevel(); + + /** + * Establishes a new foreground or fill color. + * @param col the color to apply (null skips this operation) + * @param fill true to set the fill color, false for the foreground color + * @param pdf only used by the PDFRenderer, is set to null. + * @return true if the new Color changes the current Color + */ + public abstract boolean updateColor(ColorType col, boolean fill, StringBuffer pdf); + + /** + * Set the current font name. Check if the font name will change and then + * set the new name. + * + * @param name the new font name + * @param size + * @param pdf + * @return true if the new Font changes the current Font + */ + public abstract boolean updateFont(String name, int size, StringBuffer pdf); + + /** + * Sets the current Stroke. The line width should be set with + * updateLineWidth() before calling this method + * + * @param style the constant for the style of the line as an int + * @return true if the new Stroke changes the current Stroke + */ + public abstract boolean updateStroke(float width, int style); + + /** + * Set the current paint. This checks if the paint will change and then sets + * the current paint. + * + * @param p the new paint + * @return true if the new paint changes the current paint + */ + public abstract boolean updatePaint(Paint p); + + /** + * Check if the clip will change the current state. A clip is assumed to be + * used in a situation where it will add to any clip in the current or + * parent states. A clip cannot be cleared, this can only be achieved by + * going to a parent level with the correct clip. If the clip is different + * then it may start a new state so that it can return to the previous clip. + * + * @param cl the clip shape to check + * @return true if the clip will change the current clip. + */ + // TODO test + public abstract boolean checkClip(Shape cl); + + /** + * Set the current clip. This either sets a new clip or sets the clip to the + * intersect of the old clip and the new clip. + * + * @param cl the new clip in the current state + */ + public abstract boolean updateClip(Shape cl); + + /** + * Check the current transform. The transform for the current state is the + * combination of all transforms in the current state. The parameter is + * compared against this current transform. + * + * @param tf the transform to check against + * @return true if the new transform is different from the current transform + */ + public abstract boolean checkTransform(AffineTransform tf); + + /** + * Overwrites the Transform in the Graphics2D context. Use transform() if you + * wish to compose with the current Affinetransform instead. + * @see java.awt.Graphics2D.setTransform(). + * @param tf the transform to concatonate to the current level transform + */ + public abstract void setTransform(AffineTransform tf); + + /** + * Composes an AffineTransform object with the Transform in this Graphics2D + * according to the rule last-specified-first-applied. + * @see java.awt.Graphics2D.transform(). + * + * @param tf the transform to concatonate to the current level transform + */ + public abstract void transform(AffineTransform tf); + + /** + * Get the current transform. This gets the combination of all transforms in + * the current state. + * + * @return the calculate combined transform for the current state + */ + public abstract AffineTransform getTransform(); + +} Index: src/java/org/apache/fop/render/png/PNGRenderer.java =================================================================== RCS file: src/java/org/apache/fop/render/png/PNGRenderer.java diff -N src/java/org/apache/fop/render/png/PNGRenderer.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/fop/render/png/PNGRenderer.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,116 @@ +package org.apache.fop.render.png; + +import java.awt.image.RenderedImage; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.batik.ext.awt.image.myCodec.PNGEncodeParam; +import org.apache.batik.ext.awt.image.myCodec.PNGImageEncoder; +import org.apache.fop.apps.FOPException; +import org.apache.fop.area.PageViewport; +import org.apache.fop.render.java2d.Java2DRenderer; + +/** + * PNG Renderer This class actually does not render itself, instead it extends + * org.apache.fop.render.java2D.Java2DRenderer and just encode + * rendering results into PNG format using Batik's image codec + */ +public class PNGRenderer extends Java2DRenderer { + + /** The MIME type for png-Rendering */ + public static final String MIME_TYPE = "image/png"; + + /** The file syntax prefix, eg. "page" will output "page1.png" etc */ + private String fileSyntax; + + /** The output directory where images are to be written */ + private File outputDir; + + /** The PNGEncodeParam for the image */ + private PNGEncodeParam renderParams; + + /** The OutputStream for the first Image */ + private OutputStream firstOutputStream; + + /** @see org.apache.fop.render.AbstractRenderer */ + public String getMimeType() { + return MIME_TYPE; + } + + /** default constructor */ + public PNGRenderer() {} + + /** @see org.apache.fop.render.Renderer#startRenderer(java.io.OutputStream) */ + public void startRenderer(OutputStream outputStream) throws IOException { + getLogger().info("rendering areas to PNG"); + setOutputDirectory(); + this.firstOutputStream = outputStream; + } + + /** + * Sets the output directory, either from the outfile specified on the + * command line, or from the directory specified in configuration file. Also + * sets the file name syntax, eg. "page" + */ + private void setOutputDirectory() { + + // the file provided on the command line + File f = getUserAgent().getOutputFile(); + + outputDir = f.getParentFile(); + + // extracting file name syntax + String s = f.getName(); + int i = s.lastIndexOf("."); + if (s.charAt(i - 1) == '1') { + i--; // getting rid of the "1" + } + fileSyntax = s.substring(0, i); + } + + public void stopRenderer() throws IOException { + + super.stopRenderer(); + + for (int i = 0; i < pageViewportList.size(); i++) { + + // Do the rendering: get the image for this page + RenderedImage image = (RenderedImage) getPageImage((PageViewport) pageViewportList + .get(i)); + + // Encode this image + getLogger().debug("Encoding Page " + (i + 1)); + renderParams = PNGEncodeParam.getDefaultEncodeParam(image); + OutputStream os = getCurrentOutputStream(i); + PNGImageEncoder encoder = new PNGImageEncoder(os, renderParams); + encoder.encode(image); + os.flush(); + } + } + + /** + * Builds the OutputStream corresponding to this page + * @param 0-based pageNumber + * @return the corresponding OutputStream + */ + private OutputStream getCurrentOutputStream(int pageNumber) { + + if (pageNumber == 0) { + return firstOutputStream; + } + + File f = new File(outputDir + File.separator + fileSyntax + + (pageNumber + 1) + ".png"); + try { + OutputStream os = new BufferedOutputStream(new FileOutputStream(f)); + return os; + } catch (FileNotFoundException e) { + new FOPException("Can't build the OutputStream\n" + e); + return null; + } + } +} Index: src/java/org/apache/fop/render/png/PNGRenderer_onthefly.java =================================================================== RCS file: src/java/org/apache/fop/render/png/PNGRenderer_onthefly.java diff -N src/java/org/apache/fop/render/png/PNGRenderer_onthefly.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/fop/render/png/PNGRenderer_onthefly.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,117 @@ +package org.apache.fop.render.png; + +import java.awt.image.RenderedImage; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.batik.ext.awt.image.myCodec.PNGEncodeParam; +import org.apache.batik.ext.awt.image.myCodec.PNGImageEncoder; +import org.apache.fop.apps.FOPException; +import org.apache.fop.area.PageViewport; +import org.apache.fop.render.java2d.Java2DRenderer; + +/** + * PNG Renderer This class actually does not render itself, instead it extends + * org.apache.fop.render.java2D.Java2DRenderer and just encode + * rendering results into PNG format using Batik's image codec + */ +public class PNGRenderer_onthefly extends Java2DRenderer { + + /** The MIME type for png-Rendering */ + public static final String MIME_TYPE = "image/png"; + + /** The file syntax prefix, eg. "page" will output "page1.png" etc */ + private String fileSyntax; + + /** The output directory where images are to be written */ + private File outputDir; + + /** The PNGEncodeParam for the image */ + private PNGEncodeParam renderParams; + + /** The OutputStream for the first Image */ + private OutputStream firstOutputStream; + + /** default constructor */ + public PNGRenderer_onthefly() {} + + /** @see org.apache.fop.render.AbstractRenderer */ + public String getMimeType() { + return MIME_TYPE; + } + + public boolean supportsOutOfOrder() { + return true; + } + + /** @see org.apache.fop.render.Renderer#startRenderer(java.io.OutputStream) */ + public void startRenderer(OutputStream outputStream) throws IOException { + getLogger().info("rendering areas to PNG"); + setOutputDirectory(); + this.firstOutputStream = outputStream; + } + + /** + * Sets the output directory, either from the outfile specified on the + * command line, or from the directory specified in configuration file. Also + * sets the file name syntax, eg. "page" + */ + private void setOutputDirectory() { + + // the file provided on the command line + File f = getUserAgent().getOutputFile(); + + outputDir = f.getParentFile(); + + // extracting file name syntax + String s = f.getName(); + int i = s.lastIndexOf("."); + if (s.charAt(i - 1) == '1') { + i--; // getting rid of the "1" + } + fileSyntax = s.substring(0, i); + } + + public void renderPage(PageViewport pageViewport) throws IOException, + FOPException { + + // Do the rendering: get the image for this page + RenderedImage image = (RenderedImage) getPageImage(pageViewport); + + // Encode this image + getLogger().debug("Encoding Page" + (getCurrentPageNumber() + 1)); + renderParams = PNGEncodeParam.getDefaultEncodeParam(image); + OutputStream os = getCurrentOutputStream(getCurrentPageNumber()); + PNGImageEncoder encoder = new PNGImageEncoder(os, renderParams); + encoder.encode(image); + os.flush(); + + setCurrentPageNumber(getCurrentPageNumber() + 1); + } + + /** + * Builds the OutputStream corresponding to this page + * @param 0-based pageNumber + * @return the corresponding OutputStream + */ + private OutputStream getCurrentOutputStream(int pageNumber) { + + if (pageNumber == 0) { + return firstOutputStream; + } + + File f = new File(outputDir + File.separator + fileSyntax + + (pageNumber + 1) + ".png"); + try { + OutputStream os = new BufferedOutputStream(new FileOutputStream(f)); + return os; + } catch (FileNotFoundException e) { + new FOPException("Can't build the OutputStream\n" + e); + return null; + } + } +} Index: src/java/org/apache/fop/render/print/JPSPrintRenderer.java =================================================================== RCS file: src/java/org/apache/fop/render/print/JPSPrintRenderer.java diff -N src/java/org/apache/fop/render/print/JPSPrintRenderer.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/fop/render/print/JPSPrintRenderer.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,178 @@ +/* + * Copyright 1999-2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.fop.render.print; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.print.Doc; +import javax.print.DocFlavor; +import javax.print.DocPrintJob; +import javax.print.PrintException; +import javax.print.PrintService; +import javax.print.PrintServiceLookup; +import javax.print.SimpleDoc; +import javax.print.attribute.HashPrintRequestAttributeSet; +import javax.print.attribute.PrintRequestAttributeSet; +import javax.print.attribute.standard.Copies; +import javax.print.attribute.standard.MediaSizeName; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.render.ps.DSCConstants; +import org.apache.fop.render.ps.PSRenderer; + +/** + * Renderer that prints through JPS using a PrintService. The data supplied to + * JPS is a ps from PSRenderer.
    + * A good overview of JPS can be found at + * http://java.sun.com/j2se/1.4.2/docs/guide/jps/ + */ +public class JPSPrintRenderer extends PSRenderer { + + private static final int EVEN_AND_ALL = 0; + + private static final int EVEN = 1; + + private static final int ODD = 2; + + private int startNumber; + + private int endNumber; + + private int mode = EVEN_AND_ALL; + + private int copies = 1; + + OutputStream out; + + public JPSPrintRenderer() { + configure(); + } + + /** + * Not via Avalon, but via command line + system properties + * @throws IllegalArgumentException + */ + private void configure() { + // read from command-line options + copies = getIntProperty("copies", 1); + startNumber = getIntProperty("start", 1) - 1; + endNumber = getIntProperty("end", -1); + String str = System.getProperty("even"); + if (str != null) { + mode = Boolean.valueOf(str).booleanValue() ? EVEN : ODD; + } + } + + private static int getIntProperty(String name, int def) { + String propValue = System.getProperty(name); + if (propValue != null) { + try { + return Integer.parseInt(propValue); + } catch (Exception e) { + return def; + } + } else { + return def; + } + } + + public void startRenderer(OutputStream stream) throws IOException { + + out = new ByteArrayOutputStream(); + super.startRenderer(out); + } + + /** + * @see org.apache.fop.render.Renderer#stopRenderer() + */ + public void stopRenderer() throws IOException { + gen.writeDSCComment(DSCConstants.TRAILER); +// gen.writeDSCComment(DSCConstants.PAGES, +//FIXME new Integer(this.currentPageNumber())); + gen.writeDSCComment(DSCConstants.EOF); + gen.flush(); + + PDFPrinter pdfPrinter = new PDFPrinter(); // TODO + + + + } + + /** Construct the print request specification. */ + class PDFPrinter { + + public PDFPrinter() { + + InputStream in = new ByteArrayInputStream( + ((ByteArrayOutputStream) out).toByteArray()); + + // see + // http://java.sun.com/j2se/1.4.2/docs/guide/jps/spec/attributes.fm5.html + // DocFlavor flavor = DocFlavor.INPUT_STREAM.PDF; + DocFlavor flavor = DocFlavor.INPUT_STREAM.POSTSCRIPT; + PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet(); + aset.add(new Copies(copies)); + aset.add(MediaSizeName.ISO_A4); + // aset.add(Sides.TWO_SIDED_LONG_EDGE); + // aset.add(Finishings.STAPLE); + + /* locate a print service that can handle it */ + PrintService[] pservices = PrintServiceLookup.lookupPrintServices( + flavor, aset); + PrintService defPservices = PrintServiceLookup + .lookupDefaultPrintService(); + if (pservices == null || pservices.length <= 0) { + // no print service found + if (null != defPservices) { + getLogger() + .info( + "Only Default-Printer available (lookupPrintServices failure)"); + pservices = new PrintService[] { defPservices }; + } else { + new FOPException( + "Cannot print document. No Printer available."); + } + } + getLogger().info("Selected printer " + pservices[0].getName()); + + /* create a print job for the chosen service */ + DocPrintJob pj = pservices[0].createPrintJob(); + + FileInputStream fis; + try { //TODO + // fis = new FileInputStream( + // "C:\\tmp_renaud\\fo\\helloFopPepping.pdf"); + + Doc doc = new SimpleDoc(in, flavor, null); + // Doc doc = new SimpleDoc(out, flavor, null); + + /* print the doc as specified */ + pj.print(doc, aset); + + } catch (PrintException e) { + System.err.println(e); + new Error(e); + } + + } + } +} Index: src/java/org/apache/fop/render/print/Java2DPrintRenderer.java =================================================================== RCS file: src/java/org/apache/fop/render/print/Java2DPrintRenderer.java diff -N src/java/org/apache/fop/render/print/Java2DPrintRenderer.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/fop/render/print/Java2DPrintRenderer.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,196 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id: Java2DPrintRenderer.java,v 1.7 2004/02/27 17:51:22 jeremias Exp $ */ + +package org.apache.fop.render.print; + +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.print.PageFormat; +import java.awt.print.Pageable; +import java.awt.print.Paper; +import java.awt.print.Printable; +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; +import java.io.IOException; +import java.util.Vector; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.render.java2d.Java2DRenderer; + +/** + * Renderer that prints through java.awt.PrintJob. + */ +public class Java2DPrintRenderer extends Java2DRenderer implements Pageable, Printable { + + private static final int EVEN_AND_ALL = 0; + + private static final int EVEN = 1; + + private static final int ODD = 2; + + private int startNumber; + + private int endNumber; + + private int mode = EVEN_AND_ALL; + + private int copies = 1; + + private PrinterJob printerJob; + + public Java2DPrintRenderer() { + initializePrinterJob(); + } + + private void initializePrinterJob() throws IllegalArgumentException { + // read from command-line options + copies = getIntProperty("copies", 1); + startNumber = getIntProperty("start", 1) - 1; + endNumber = getIntProperty("end", -1); + String str = System.getProperty("even"); + if (str != null) { + mode = Boolean.valueOf(str).booleanValue() ? EVEN : ODD; + } + + printerJob = PrinterJob.getPrinterJob(); + printerJob.setJobName("FOP Document"); + printerJob.setCopies(copies); + if (System.getProperty("dialog") != null) { + if (!printerJob.printDialog()) { + throw new IllegalArgumentException( + "Printing cancelled by operator"); + } + } + printerJob.setPageable(this); + } + + public void stopRenderer() throws IOException { + super.stopRenderer(); + + if (endNumber == -1) { + // was not set on command line + endNumber = getNumberOfPages(); + } + + Vector numbers = getInvalidPageNumbers(); + for (int i = numbers.size() - 1; i > -1; i--) { + // removePage(Integer.parseInt((String)numbers.elementAt(i))); + } + + try { + printerJob.print(); + } catch (PrinterException e) { + e.printStackTrace(); + throw new IOException("Unable to print: " + e.getClass().getName() + + ": " + e.getMessage()); + } + clearViewportList(); + } + + public static int getIntProperty(String name, int def) { + String propValue = System.getProperty(name); + if (propValue != null) { + try { + return Integer.parseInt(propValue); + } catch (Exception e) { + return def; + } + } else { + return def; + } + } + + private Vector getInvalidPageNumbers() { + Vector vec = new Vector(); + int max = getNumberOfPages(); + boolean isValid; + for (int i = 0; i < max; i++) { + isValid = true; + if (i < startNumber || i > endNumber) { + isValid = false; + } else if (mode != EVEN_AND_ALL) { + if (mode == EVEN && ((i + 1) % 2 != 0)) { + isValid = false; + } else if (mode == ODD && ((i + 1) % 2 != 1)) { + isValid = false; + } + } + + if (!isValid) { + vec.add(i + ""); + } + } + return vec; + } + + public int print(Graphics g, PageFormat pageFormat, int pageIndex) + throws PrinterException { + if (pageIndex >= getNumberOfPages()){ + return NO_SUCH_PAGE; + } + + Graphics2D g2 = (Graphics2D) g; + + BufferedImage image; + try { + image = getPageImage(pageIndex); + } catch (FOPException e) { + e.printStackTrace(); + return NO_SUCH_PAGE; + } + + g2.drawImage(image,null,0,0); + + return PAGE_EXISTS; + } + + public PageFormat getPageFormat(int pageIndex) + throws IndexOutOfBoundsException { + if (pageIndex >= getNumberOfPages()) + return null; + + PageFormat pageFormat = new PageFormat(); + + Paper paper = new Paper(); + pageFormat.setPaper(paper); + + Rectangle2D dim = getPageViewport(pageIndex).getViewArea(); + double width = dim.getWidth(); + double height = dim.getHeight(); + + // if the width is greater than the height assume lanscape mode + // and swap the width and height values in the paper format + if (width > height) { + paper.setImageableArea(0, 0, height / 1000d, width / 1000d); + paper.setSize(height / 1000d, width / 1000d); + pageFormat.setOrientation(PageFormat.LANDSCAPE); + } else { + paper.setImageableArea(0, 0, width / 1000d, height / 1000d); + paper.setSize(width / 1000d, height / 1000d); + pageFormat.setOrientation(PageFormat.PORTRAIT); + } + return pageFormat; + } + + public Printable getPrintable(int pageIndex) + throws IndexOutOfBoundsException { + return this; + } +} Index: src/java/org/apache/fop/render/tiff/TIFFRenderer.java =================================================================== RCS file: src/java/org/apache/fop/render/tiff/TIFFRenderer.java diff -N src/java/org/apache/fop/render/tiff/TIFFRenderer.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/fop/render/tiff/TIFFRenderer.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,194 @@ +/* + * Copyright 1999-2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.tiff; + +// Code originaly contributed by Oleg Tkachenko of Multiconn International Ltd +// (olegt@multiconn.com). + +import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Iterator; + +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.batik.ext.awt.image.codec.myTiff.TIFFEncodeParam; +import org.apache.batik.ext.awt.image.codec.myTiff.TIFFImageEncoder; +import org.apache.fop.apps.FOPException; +import org.apache.fop.render.java2d.Java2DRenderer; + +/** + *

    + * This class represents renderer to TIFF (Tagged Image File Format) format. It + * is one of the most popular and flexible of the current public domain raster + * file formats, which was is primarily designed for raster data interchange. + * Supported compression types are: + *

      + *
    • Raw noncompressed data
    • + *
    • Byte-oriented run-length encoding "PackBits" compression.
    • + *
    • Modified Huffman Compression (CCITT Group 3 1D facsimile compression)
    • + *
    • CCITT T.4 bilevel compression (CCITT Group 3 2D facsimile compression)
    • + *
    • CCITT T.6 bilevel compression (CCITT Group 4 facsimile compression)
    • + *
    • JPEG-in-TIFF compression
    • + *
    • DEFLATE lossless compression (also known as "Zip-in-TIFF")
    • + *
    • LZW compression
    • + * TODO + *

      + * This class actually does not render itself, instead it extends + * org.apache.fop.render.java2D.Java2DRenderer and just encode + * rendering results into TIFF format using Batik's image codec + */ +public class TIFFRenderer extends Java2DRenderer { + + /** The MIME type for tiff-Rendering */ + public static final String MIME_TYPE = "image/tiff"; + + /** */ + private TIFFEncodeParam renderParams; + + private OutputStream outputStream; + + /** @see org.apache.fop.render.AbstractRenderer */ + public String getMimeType() { + return MIME_TYPE; + } + + /** Creates TIFF renderer. */ + public TIFFRenderer() { + + renderParams = new TIFFEncodeParam(); + + } + + /** + * Configure the TIFF renderer. Get the configuration to be used for + * compression + * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration) + */ + public void configure(Configuration cfg) throws ConfigurationException { + + String c = cfg.getChild("directory").getAttribute("value"); + int comp = Integer.parseInt(c); + String name = null; + switch (comp) { + case TIFFEncodeParam.COMPRESSION_NONE: + name = "COMPRESSION_NONE"; + break; + case TIFFEncodeParam.COMPRESSION_JPEG_TTN2: + name = "COMPRESSION_JPEG_TTN2"; + break; + case TIFFEncodeParam.COMPRESSION_PACKBITS: + name = "COMPRESSION_PACKBITS"; + break; + case TIFFEncodeParam.COMPRESSION_DEFLATE: + name = "COMPRESSION_DEFLATE"; + break; + default: + getLogger().info("TIFF compression not supported: " + comp); + return; + } + getLogger().info("TIFF compression set to " + name); + + renderParams.setCompression(comp); + } + + /** @see org.apache.fop.render.Renderer#startRenderer(java.io.OutputStream) */ + public void startRenderer(OutputStream outputStream) throws IOException { + this.outputStream = outputStream; + super.startRenderer(outputStream); + } + + /** @see org.apache.fop.render.Renderer#stopRenderer() */ + public void stopRenderer() throws IOException { + + super.stopRenderer(); + getLogger().debug("Starting Tiff encoding ..."); + + // Creates encoder + TIFFImageEncoder enc = new TIFFImageEncoder(outputStream, renderParams); + + // Creates lazy iterator over generated page images + Iterator pageImagesItr = new LazyPageImagesIterator(getNumberOfPages()); + + // The first image to be passed to enc + RenderedImage first = (RenderedImage) pageImagesItr.next(); + + // The other images are set to the renderParams + renderParams.setExtraImages(pageImagesItr); + + // Start encoding + enc.encode(first); + + // Cleaning + outputStream.flush(); + clearViewportList(); + getLogger().debug("Tiff encoding done."); + + } + + /** Private inner class to lazy page rendering. */ + private class LazyPageImagesIterator implements Iterator { + private int count; + + private int current = 0; + + public LazyPageImagesIterator(int c) { + count = c; + } + + public boolean hasNext() { + return current < count; + } + + public Object next() { + getLogger().debug("[" + (current + 1) + "]"); + + // Renders current page as image + BufferedImage pageImage = null; + try { + pageImage = getPageImage(current++); + } catch (FOPException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + switch (renderParams.getCompression()) { //TODO + // These types of compression require bilevel image + case TIFFEncodeParam.COMPRESSION_GROUP3_1D: + case TIFFEncodeParam.COMPRESSION_GROUP3_2D: + case TIFFEncodeParam.COMPRESSION_GROUP4: + default: //FIXME + BufferedImage faxImage = new BufferedImage( + pageImage.getWidth(), pageImage.getHeight(), + BufferedImage.TYPE_BYTE_BINARY); + faxImage.getGraphics().drawImage(pageImage, 0, 0, null); + return faxImage; + //default: + // return pageImage; + } + + } + + public void remove() { + throw new UnsupportedOperationException( + "Method 'remove' is not supported."); + } + } +}