/**
  * Extracts <code>len</code> bytes of data from the current or subsequent fragments.
  *
  * @param b the buffer into which the data is read.
  * @param off the start offset of the data.
  * @param len the number of bytes read.
  * @return the total number of bytes read into the buffer (always whatever was asked for), or -1
  *     if there is no more data because the end of a frame has been reached.
  * @exception IOException if an I/O error occurs.
  */
 public final int read(byte b[], int off, int len) throws IOException {
   // System.err.println("EncapsulatedInputStream.read(byte [],"+off+","+len+")");
   // System.err.println("EncapsulatedInputStream.read() at start,
   // fragmentRemaining="+fragmentRemaining);
   // System.err.println("EncapsulatedInputStream.read() at start,
   // endOfFrameEncountered="+endOfFrameEncountered);
   // System.err.println("EncapsulatedInputStream.read() at start,
   // currentFragmentContainsEndOfFrame="+currentFragmentContainsEndOfFrame);
   if (endOfFrameEncountered) {
     // System.err.println("EncapsulatedInputStream.read() returning -1 since
     // endOfFrameEncountered");
     return -1; // i.e., won't advance until nextFrame() is called to reset this state
   }
   int count = 0;
   int remainingToDo = len;
   while (remainingToDo > 0 && !sequenceDelimiterEncountered && !endOfFrameEncountered) {
     // System.err.println("EncapsulatedInputStream.read() remainingToDo="+remainingToDo);
     if (fragment == null) {
       if (firstTime) {
         // System.err.println("EncapsulatedInputStream.read() firstTime");
         // first time ... skip offset table ...
         long offsetTableLength = readItemTag();
         if (sequenceDelimiterEncountered) {
           throw new IOException("Expected offset table item tag; got sequence delimiter");
         }
         // System.err.println("EncapsulatedInputStream.read() skipping
         // offsetTableLength="+offsetTableLength);
         i.skipInsistently(offsetTableLength);
         bytesRead += offsetTableLength;
         firstTime = false;
       }
       // load a new fragment ...
       // System.err.println("EncapsulatedInputStream.read() loading a new fragment");
       long vl =
           readItemTag(); // if sequenceDelimiterEncountered, vl will be zero and no more will be
                          // done
       if (vl != 0) {
         currentFragmentContainsEndOfFrame = false;
         fragmentRemaining = fragmentSize = (int) vl;
         fragment = new byte[fragmentSize];
         i.readInsistently(fragment, 0, fragmentSize);
         bytesRead += fragmentSize;
         fragmentOffset = 0;
         // System.err.println("EncapsulatedInputStream.read() fragmentRemaining
         // initially="+fragmentRemaining);
         // System.err.println("EncapsulatedInputStream.read() fragment =
         // "+com.pixelmed.utils.HexDump.dump(fragment));
         // Ignore everything between (the last) EOI marker and the end of the fragment
         int positionOfEOI = fragmentRemaining - 1;
         while (--positionOfEOI > 0) {
           int firstMarkerByte = fragment[positionOfEOI] & 0xff;
           int secondMarkerByte = fragment[positionOfEOI + 1] & 0xff;
           // System.err.println("EncapsulatedInputStream.read() fragment
           // fragment["+positionOfEOI+"] = 0x"+Integer.toHexString(firstMarkerByte));
           // System.err.println("EncapsulatedInputStream.read() fragment
           // fragment["+(positionOfEOI+1)+"] = 0x"+Integer.toHexString(secondMarkerByte));
           if (firstMarkerByte == 0xff && secondMarkerByte == 0xd9) {
             currentFragmentContainsEndOfFrame = true;
             break;
           }
         }
         // System.err.println("EncapsulatedInputStream.read() positionOfEOI="+positionOfEOI);
         if (positionOfEOI > 0) { // will be zero if we did not find one
           fragmentRemaining =
               positionOfEOI + 2; // effectively skips all (hopefully padding) bytes after the EOI
         }
         // System.err.println("EncapsulatedInputStream.read() fragmentRemaining after removing
         // trailing padding="+fragmentRemaining);
       }
     }
     int amountToCopyFromThisFragment =
         remainingToDo < fragmentRemaining ? remainingToDo : fragmentRemaining;
     // System.err.println("EncapsulatedInputStream.read()
     // amountToCopyFromThisFragment="+amountToCopyFromThisFragment);
     if (amountToCopyFromThisFragment > 0) {
       System.arraycopy(fragment, fragmentOffset, b, off, amountToCopyFromThisFragment);
       off += amountToCopyFromThisFragment;
       fragmentOffset += amountToCopyFromThisFragment;
       fragmentRemaining -= amountToCopyFromThisFragment;
       remainingToDo -= amountToCopyFromThisFragment;
       count += amountToCopyFromThisFragment;
     }
     if (fragmentRemaining <= 0) {
       fragment = null;
       if (currentFragmentContainsEndOfFrame) {
         endOfFrameEncountered =
             true; // once EOI has been seen in a fragment, use the rest of the fragment including
                   // the EOI, but no further
       }
     }
   }
   // System.err.println("EncapsulatedInputStream.read() returning count="+count);
   // System.err.println("EncapsulatedInputStream.read()
   // returning="+com.pixelmed.utils.HexDump.dump(fragment,off,count));
   return count == 0
       ? -1
       : count; // always returns more than 0 unless end, which is signalled by -1
 }