diff options
author | Jeremias Maerki <jeremias@apache.org> | 2005-09-05 15:41:54 +0000 |
---|---|---|
committer | Jeremias Maerki <jeremias@apache.org> | 2005-09-05 15:41:54 +0000 |
commit | 8bb9b2bd62a760eb2b824b070f07a8cd2696cdce (patch) | |
tree | 454a5e0bc4db5eba79b031317e04993e255200fd /src/java/org | |
parent | 2b9729a5db839ce49bd9b8bd23ff344aa26a8ae7 (diff) | |
download | xmlgraphics-fop-8bb9b2bd62a760eb2b824b070f07a8cd2696cdce.tar.gz xmlgraphics-fop-8bb9b2bd62a760eb2b824b070f07a8cd2696cdce.zip |
Implemented embedding of Type 1 fonts.
font dict writing moved to new class PSFontUtils.
PSGenerator.resolveURI() does the same as FOUserAgent.resolveURI() but thus avoids the dependency on FOUserAgent. Needed for font embedding.
Treat fonts as resources now.
Wrap font keying in FOP-specific comments for reliable finding.
New class SubInputStream which reads a maximum number of bytes from an underlying InputStream and then behaves like it has reached EOF. Used for Type1 font embedding.
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@278782 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org')
8 files changed, 399 insertions, 93 deletions
diff --git a/src/java/org/apache/fop/render/ps/EPSDocumentGraphics2D.java b/src/java/org/apache/fop/render/ps/EPSDocumentGraphics2D.java index 7a8eacce9..81a202940 100644 --- a/src/java/org/apache/fop/render/ps/EPSDocumentGraphics2D.java +++ b/src/java/org/apache/fop/render/ps/EPSDocumentGraphics2D.java @@ -1,5 +1,5 @@ /* - * Copyright 2003,2004 The Apache Software Foundation. + * Copyright 2003-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. @@ -26,7 +26,6 @@ import org.apache.fop.apps.Fop; * This class is a wrapper for the <tt>AbstractPSDocumentGraphics2D</tt> that * is used to create EPS (Encapsulated PostScript) files instead of PS file. * - * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a> * @version $Id$ * @see org.apache.fop.render.ps.PSGraphics2D * @see org.apache.fop.render.ps.AbstractPSDocumentGraphics2D @@ -70,7 +69,7 @@ public class EPSDocumentGraphics2D extends AbstractPSDocumentGraphics2D { PSProcSets.writeFOPStdProcSet(gen); PSProcSets.writeFOPEPSProcSet(gen); if (fontInfo != null) { - PSProcSets.writeFontDict(gen, fontInfo); + PSFontUtils.writeFontDict(gen, fontInfo); } gen.writeDSCComment(DSCConstants.END_PROLOG); } diff --git a/src/java/org/apache/fop/render/ps/PSDocumentGraphics2D.java b/src/java/org/apache/fop/render/ps/PSDocumentGraphics2D.java index 4a3a05801..afa4a83e2 100644 --- a/src/java/org/apache/fop/render/ps/PSDocumentGraphics2D.java +++ b/src/java/org/apache/fop/render/ps/PSDocumentGraphics2D.java @@ -1,5 +1,5 @@ /* - * 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. @@ -34,7 +34,6 @@ import org.apache.fop.fonts.FontSetup; * <tt>PSGraphics2D</tt>. * * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a> - * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a> * @version $Id$ * @see org.apache.fop.render.ps.PSGraphics2D */ @@ -114,7 +113,7 @@ public class PSDocumentGraphics2D extends AbstractPSDocumentGraphics2D { PSProcSets.writeFOPStdProcSet(gen); PSProcSets.writeFOPEPSProcSet(gen); if (fontInfo != null) { - PSProcSets.writeFontDict(gen, fontInfo); + PSFontUtils.writeFontDict(gen, fontInfo); } gen.writeDSCComment(DSCConstants.END_SETUP); } diff --git a/src/java/org/apache/fop/render/ps/PSFontUtils.java b/src/java/org/apache/fop/render/ps/PSFontUtils.java new file mode 100644 index 000000000..8bcd0c413 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSFontUtils.java @@ -0,0 +1,241 @@ +/* + * Copyright 2001-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.ps; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.util.Iterator; +import java.util.Map; + +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; + +import org.apache.commons.io.EndianUtils; +import org.apache.commons.io.IOUtils; +import org.apache.fop.fonts.CustomFont; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.FontType; +import org.apache.fop.fonts.Glyphs; +import org.apache.fop.fonts.LazyFont; +import org.apache.fop.fonts.Typeface; +import org.apache.fop.util.ASCIIHexOutputStream; +import org.apache.fop.util.SubInputStream; + +/** + * Utility code for font handling in PostScript. + */ +public class PSFontUtils { + + /** + * Generates the PostScript code for the font dictionary. + * @param gen PostScript generator to use for output + * @param fontInfo available fonts + * @return a Map of PSResource instances representing all defined fonts (key: font key) + * @throws IOException in case of an I/O problem + */ + public static Map writeFontDict(PSGenerator gen, FontInfo fontInfo) + throws IOException { + gen.commentln("%FOPBeginFontDict"); + gen.writeln("/FOPFonts 100 dict dup begin"); + + // write("/gfF1{/Helvetica findfont} bd"); + // write("/gfF3{/Helvetica-Bold findfont} bd"); + Map fonts = fontInfo.getFonts(); + Map fontResources = new java.util.HashMap(); + Iterator iter = fonts.keySet().iterator(); + while (iter.hasNext()) { + String key = (String)iter.next(); + Typeface tf = (Typeface)fonts.get(key); + if (tf instanceof LazyFont) { + tf = ((LazyFont)tf).getRealFont(); + } + PSResource fontRes = new PSResource("font", tf.getFontName()); + fontResources.put(key, fontRes); + boolean embeddedFont = false; + if (FontType.TYPE1 == tf.getFontType()) { + if (tf instanceof CustomFont) { + CustomFont cf = (CustomFont)tf; + InputStream in = getInputStreamOnFont(gen, cf); + if (in != null) { + gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, + fontRes); + embedType1Font(gen, in); + gen.writeDSCComment(DSCConstants.END_RESOURCE); + gen.notifyResourceUsage(fontRes, false); + embeddedFont = true; + } + } + } + if (!embeddedFont) { + gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes); + //Resource usage shall be handled by renderer + //gen.notifyResourceUsage(fontRes, true); + } + gen.commentln("%FOPBeginFontKey: " + key); + gen.writeln("/" + key + " /" + tf.getFontName() + " def"); + gen.commentln("%FOPEndFontKey"); + } + gen.writeln("end def"); + gen.commentln("%FOPEndFontDict"); + gen.commentln("%FOPBeginFontReencode"); + defineWinAnsiEncoding(gen); + + //Rewrite font encodings + iter = fonts.keySet().iterator(); + while (iter.hasNext()) { + String key = (String)iter.next(); + Typeface fm = (Typeface)fonts.get(key); + if (null == fm.getEncoding()) { + //ignore (ZapfDingbats and Symbol run through here + //TODO: ZapfDingbats and Symbol should get getEncoding() fixed! + } else if ("WinAnsiEncoding".equals(fm.getEncoding())) { + gen.writeln("/" + fm.getFontName() + " findfont"); + gen.writeln("dup length dict begin"); + gen.writeln(" {1 index /FID ne {def} {pop pop} ifelse} forall"); + gen.writeln(" /Encoding " + fm.getEncoding() + " def"); + gen.writeln(" currentdict"); + gen.writeln("end"); + gen.writeln("/" + fm.getFontName() + " exch definefont pop"); + } else { + System.out.println("Only WinAnsiEncoding is supported. Font '" + + fm.getFontName() + "' asks for: " + fm.getEncoding()); + } + } + gen.commentln("%FOPEndFontReencode"); + return fontResources; + } + + /** + * This method reads a Type 1 font from a stream and embeds it into a PostScript stream. + * Note: Only the IBM PC Format as described in section 3.3 of the Adobe Technical Note #5040 + * is supported. + * @param gen The PostScript generator + * @param in the InputStream from which to read the Type 1 font + * @throws IOException in case an I/O problem occurs + */ + private static void embedType1Font(PSGenerator gen, InputStream in) throws IOException { + boolean finished = false; + while (!finished) { + int segIndicator = in.read(); + if (segIndicator < 0) { + throw new IOException("Unexpected end-of-file while reading segment indicator"); + } else if (segIndicator != 128) { + throw new IOException("Expected ASCII 128, found: " + segIndicator); + } + int segType = in.read(); + if (segType < 0) { + throw new IOException("Unexpected end-of-file while reading segment type"); + } + int dataSegLen = 0; + switch (segType) { + case 1: //ASCII + dataSegLen = EndianUtils.readSwappedInteger(in); + + BufferedReader reader = new BufferedReader( + new java.io.InputStreamReader( + new SubInputStream(in, dataSegLen), "US-ASCII")); + String line; + while ((line = reader.readLine()) != null) { + gen.writeln(line); + } + break; + case 2: //binary + dataSegLen = EndianUtils.readSwappedInteger(in); + + SubInputStream sin = new SubInputStream(in, dataSegLen); + ASCIIHexOutputStream hexOut = new ASCIIHexOutputStream(gen.getOutputStream()); + IOUtils.copy(sin, hexOut); + gen.newLine(); + break; + case 3: //EOF + finished = true; + break; + default: throw new IOException("Unsupported segment type: " + segType); + } + } + } + + private static InputStream getInputStreamOnFont(PSGenerator gen, CustomFont font) + throws IOException { + if (font.isEmbeddable()) { + Source source = null; + if (font.getEmbedFileName() != null) { + source = gen.resolveURI(font.getEmbedFileName()); + } + if (source == null && font.getEmbedResourceName() != null) { + source = new StreamSource(PSFontUtils.class + .getResourceAsStream(font.getEmbedResourceName())); + } + if (source == null) { + return null; + } + InputStream in = null; + if (source instanceof StreamSource) { + in = ((StreamSource) source).getInputStream(); + } + if (in == null && source.getSystemId() != null) { + try { + in = new java.net.URL(source.getSystemId()).openStream(); + } catch (MalformedURLException e) { + new FileNotFoundException( + "File not found. URL could not be resolved: " + + e.getMessage()); + } + } + if (in == null) { + return null; + } + //Make sure the InputStream is decorated with a BufferedInputStream + if (!(in instanceof java.io.BufferedInputStream)) { + in = new java.io.BufferedInputStream(in); + } + return in; + } else { + return null; + } + } + + private static void defineWinAnsiEncoding(PSGenerator gen) throws IOException { + gen.writeln("/WinAnsiEncoding ["); + for (int i = 0; i < Glyphs.WINANSI_ENCODING.length; i++) { + if (i > 0) { + if ((i % 5) == 0) { + gen.newLine(); + } else { + gen.write(" "); + } + } + final char ch = Glyphs.WINANSI_ENCODING[i]; + final String glyphname = Glyphs.charToGlyphName(ch); + if ("".equals(glyphname)) { + gen.write("/" + Glyphs.NOTDEF); + } else { + gen.write("/"); + gen.write(glyphname); + } + } + gen.newLine(); + gen.writeln("] def"); + } + + +} diff --git a/src/java/org/apache/fop/render/ps/PSGenerator.java b/src/java/org/apache/fop/render/ps/PSGenerator.java index efd07cab0..14d5b18fa 100644 --- a/src/java/org/apache/fop/render/ps/PSGenerator.java +++ b/src/java/org/apache/fop/render/ps/PSGenerator.java @@ -31,6 +31,8 @@ import java.util.Locale; import java.util.Set; import java.util.Stack; +import javax.xml.transform.Source; + /** * This class is used to output PostScript code to an OutputStream. * @@ -85,6 +87,17 @@ public class PSGenerator { } /** + * Attempts to resolve the given URI. PSGenerator should be subclasses to provide more + * sophisticated URI resolution. + * @param uri URI to access + * @return A {@link javax.xml.transform.Source} object, or null if the URI + * cannot be resolved. + */ + public Source resolveURI(String uri) { + return new javax.xml.transform.stream.StreamSource(uri); + } + + /** * Writes a newline character to the OutputStream. * * @throws IOException In case of an I/O problem @@ -241,15 +254,15 @@ public class PSGenerator { initialSize += initialSize / 2; StringBuffer sb = new StringBuffer(initialSize); if ((Long.getLong(text) != null) - || (text.indexOf(" ") >= 0) + || (text.indexOf(' ') >= 0) || forceParentheses) { - sb.append("("); + sb.append('('); for (int i = 0; i < text.length(); i++) { final char c = text.charAt(i); escapeChar(c, sb); } - sb.append(")"); + sb.append(')'); return sb.toString(); } else { return text; @@ -315,6 +328,8 @@ public class PSGenerator { } else if (params[i] instanceof Date) { DateFormat datef = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); tempBuffer.append(convertStringToDSC(datef.format((Date)params[i]))); + } else if (params[i] instanceof PSResource) { + tempBuffer.append(((PSResource)params[i]).getResourceSpecification()); } else { throw new IllegalArgumentException("Unsupported parameter type: " + params[i].getClass().getName()); @@ -528,6 +543,15 @@ public class PSGenerator { } /** + * Indicates whether a particular resource is supplied, rather than needed. + * @param res the resource + * @return true if the resource is registered as being supplied. + */ + public boolean isResourceSupplied(PSResource res) { + return documentSuppliedResources.contains(res); + } + + /** * Writes a DSC comment for the accumulated used resources, either at page level or * at document level. * @param pageLevel true if the DSC comment for the page level should be generated, @@ -562,9 +586,7 @@ public class PSGenerator { tempBuffer.append("%%+ "); } PSResource res = (PSResource)i.next(); - tempBuffer.append(res.getType()); - tempBuffer.append(" "); - tempBuffer.append(res.getName()); + tempBuffer.append(res.getResourceSpecification()); first = false; } writeln(tempBuffer.toString()); diff --git a/src/java/org/apache/fop/render/ps/PSProcSets.java b/src/java/org/apache/fop/render/ps/PSProcSets.java index c56a26a72..56ee8fd0e 100644 --- a/src/java/org/apache/fop/render/ps/PSProcSets.java +++ b/src/java/org/apache/fop/render/ps/PSProcSets.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2005 The Apache Software Foundation. + * Copyright 2001-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. @@ -19,12 +19,6 @@ package org.apache.fop.render.ps; import java.io.IOException; -import java.util.Iterator; -import java.util.Map; - -import org.apache.fop.fonts.Typeface; -import org.apache.fop.fonts.Glyphs; -import org.apache.fop.fonts.FontInfo; /** * This class defines the basic resources (procsets) used by FOP's PostScript @@ -211,76 +205,4 @@ public final class PSProcSets { ((EPSProcSet)EPS_PROCSET).writeTo(gen); } - /** - * Generates the PostScript code for the font dictionary. - * @param gen PostScript generator to use for output - * @param fontInfo available fonts - * @throws IOException in case of an I/O problem - */ - public static void writeFontDict(PSGenerator gen, FontInfo fontInfo) - throws IOException { - gen.commentln("%FOPBeginFontDict"); - gen.writeln("/FOPFonts 100 dict dup begin"); - - // write("/gfF1{/Helvetica findfont} bd"); - // write("/gfF3{/Helvetica-Bold findfont} bd"); - Map fonts = fontInfo.getFonts(); - Iterator iter = fonts.keySet().iterator(); - while (iter.hasNext()) { - String key = (String)iter.next(); - Typeface fm = (Typeface)fonts.get(key); - gen.writeln("/" + key + " /" + fm.getFontName() + " def"); - } - gen.writeln("end def"); - gen.commentln("%FOPEndFontDict"); - gen.commentln("%FOPBeginFontReencode"); - defineWinAnsiEncoding(gen); - - //Rewrite font encodings - iter = fonts.keySet().iterator(); - while (iter.hasNext()) { - String key = (String)iter.next(); - Typeface fm = (Typeface)fonts.get(key); - if (null == fm.getEncoding()) { - //ignore (ZapfDingbats and Symbol run through here - //TODO: ZapfDingbats and Symbol should get getEncoding() fixed! - } else if ("WinAnsiEncoding".equals(fm.getEncoding())) { - gen.writeln("/" + fm.getFontName() + " findfont"); - gen.writeln("dup length dict begin"); - gen.writeln(" {1 index /FID ne {def} {pop pop} ifelse} forall"); - gen.writeln(" /Encoding " + fm.getEncoding() + " def"); - gen.writeln(" currentdict"); - gen.writeln("end"); - gen.writeln("/" + fm.getFontName() + " exch definefont pop"); - } else { - System.out.println("Only WinAnsiEncoding is supported. Font '" - + fm.getFontName() + "' asks for: " + fm.getEncoding()); - } - } - gen.commentln("%FOPEndFontReencode"); - } - - private static void defineWinAnsiEncoding(PSGenerator gen) throws IOException { - gen.writeln("/WinAnsiEncoding ["); - for (int i = 0; i < Glyphs.WINANSI_ENCODING.length; i++) { - if (i > 0) { - if ((i % 5) == 0) { - gen.newLine(); - } else { - gen.write(" "); - } - } - final char ch = Glyphs.WINANSI_ENCODING[i]; - final String glyphname = Glyphs.charToGlyphName(ch); - if ("".equals(glyphname)) { - gen.write("/" + Glyphs.NOTDEF); - } else { - gen.write("/"); - gen.write(glyphname); - } - } - gen.newLine(); - gen.writeln("] def"); - } - } diff --git a/src/java/org/apache/fop/render/ps/PSRenderer.java b/src/java/org/apache/fop/render/ps/PSRenderer.java index f674942bb..7d3c046a1 100644 --- a/src/java/org/apache/fop/render/ps/PSRenderer.java +++ b/src/java/org/apache/fop/render/ps/PSRenderer.java @@ -28,6 +28,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import javax.xml.transform.Source; + // FOP import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; @@ -103,6 +105,9 @@ public class PSRenderer extends AbstractPathOrientedRenderer { /** Used to temporarily store PSSetupCode instance until they can be written. */ private List setupCodeList; + + /** This is a map of PSResource instances of all fonts defined (key: font key) */ + private Map fontResources; /** * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration) @@ -561,7 +566,12 @@ public class PSRenderer extends AbstractPathOrientedRenderer { log.debug("rendering areas to PostScript"); //Setup for PostScript generation - this.gen = new PSGenerator(outputStream); + this.gen = new PSGenerator(outputStream) { + /** Need to subclass PSGenerator to have better URI resolution */ + public Source resolveURI(String uri) { + return userAgent.resolveURI(uri); + } + }; this.currentPageNumber = 0; //PostScript Header @@ -585,6 +595,20 @@ public class PSRenderer extends AbstractPathOrientedRenderer { * @see org.apache.fop.render.Renderer#stopRenderer() */ public void stopRenderer() throws IOException { + //Notify resource usage for font which are not supplied + Map fonts = fontInfo.getUsedFonts(); + Iterator e = fonts.keySet().iterator(); + while (e.hasNext()) { + String key = (String)e.next(); + //Typeface font = (Typeface)fonts.get(key); + PSResource res = (PSResource)this.fontResources.get(key); + boolean supplied = gen.isResourceSupplied(res); + if (!supplied) { + gen.notifyResourceUsage(res, true); + } + } + + //Write trailer gen.writeDSCComment(DSCConstants.TRAILER); gen.writeDSCComment(DSCConstants.PAGES, new Integer(this.currentPageNumber)); gen.writeResources(false); @@ -623,7 +647,7 @@ public class PSRenderer extends AbstractPathOrientedRenderer { //Setup gen.writeDSCComment(DSCConstants.BEGIN_SETUP); writeSetupCodeList(setupCodeList, "SetupCode"); - PSProcSets.writeFontDict(gen, fontInfo); + this.fontResources = PSFontUtils.writeFontDict(gen, fontInfo); gen.writeln("FOPFonts begin"); gen.writeDSCComment(DSCConstants.END_SETUP); } catch (IOException ioe) { diff --git a/src/java/org/apache/fop/render/ps/PSResource.java b/src/java/org/apache/fop/render/ps/PSResource.java index d37850a2e..b68bcaf6d 100644 --- a/src/java/org/apache/fop/render/ps/PSResource.java +++ b/src/java/org/apache/fop/render/ps/PSResource.java @@ -53,4 +53,11 @@ public class PSResource { return this.name; } + /** @return the <resource> specification as defined in DSC v3.0 spec. */ + public String getResourceSpecification() { + StringBuffer sb = new StringBuffer(); + sb.append(getType()).append(" ").append(PSGenerator.convertStringToDSC(getName())); + return sb.toString(); + } + } diff --git a/src/java/org/apache/fop/util/SubInputStream.java b/src/java/org/apache/fop/util/SubInputStream.java new file mode 100644 index 000000000..83b146402 --- /dev/null +++ b/src/java/org/apache/fop/util/SubInputStream.java @@ -0,0 +1,92 @@ +/* + * 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$ */ + +package org.apache.fop.util; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * This class is a FilterInputStream descendant that reads from an underlying InputStream + * up to a defined number of bytes or the end of the underlying stream. Closing this InputStream + * will not result in the underlying InputStream to be closed, too. + * <p> + * This InputStream can be used to read chunks from a larger file of which the length is + * known in advance. + */ +public class SubInputStream extends FilterInputStream { + + /** Indicates the number of bytes remaning to be read from the underlying InputStream. */ + private long bytesToRead; + + /** + * Creates a new SubInputStream. + * @param in the InputStream to read from + * @param maxLen the maximum number of bytes to read from the underlying InputStream until + * the end-of-file is signalled. + */ + public SubInputStream(InputStream in, long maxLen) { + super(in); + this.bytesToRead = maxLen; + } + + /** @see java.io.InputStream#read() */ + public int read() throws IOException { + if (bytesToRead > 0) { + int result = super.read(); + if (result <= 0) { + bytesToRead--; + return result; + } else { + return -1; + } + } else { + return -1; + } + } + + /** @see java.io.InputStream#read(byte[], int, int) */ + public int read(byte[] b, int off, int len) throws IOException { + if (bytesToRead == 0) { + return -1; + } + int effRead = (int)Math.min(bytesToRead, len); + //cast to int is safe because len can never be bigger than Integer.MAX_VALUE + + int result = super.read(b, off, effRead); + if (result >= 0) { + bytesToRead -= result; + } + return result; + } + + /** @see java.io.InputStream#skip(long) */ + public long skip(long n) throws IOException { + long effRead = Math.min(bytesToRead, n); + long result = super.skip(effRead); + bytesToRead -= result; + return result; + } + + /** @see java.io.InputStream#close() */ + public void close() throws IOException { + //Don't close the underlying InputStream!!! + this.bytesToRead = 0; + } +} |