/** read table of values */
  public int readXRefs(
      int current,
      final byte[] Bytes,
      final int endTable,
      int i,
      final long eof,
      final RandomAccessBuffer pdf_datafile) {

    char flag;
    int id, tokenCount, generation, lineLen, startLine, endLine;
    boolean skipNext = false;
    boolean isFirstValue = true;

    final int[] breaks = new int[5];
    final int[] starts = new int[5];

    // loop to read all references
    while (i < endTable) { // exit end at trailer

      startLine = i;
      endLine = -1;

      /** read line locations */
      // move to start of value ignoring spaces or returns
      while (Bytes[i] != 10 && Bytes[i] != 13) {
        // scan for %
        if ((endLine == -1) && (Bytes[i] == 37)) {
          endLine = i - 1;
        }

        i++;
      }

      // set end if no comment
      if (endLine == -1) {
        endLine = i - 1;
      }

      // strip any spaces
      while (Bytes[startLine] == 32) {
        startLine++;
      }

      // strip any spaces
      while (Bytes[endLine] == 32) {
        endLine--;
      }

      i++;

      /** decode the line */
      tokenCount = 0;
      lineLen = endLine - startLine + 1;

      if (lineLen > 0) {

        // decide if line is a section header or value

        // first count tokens
        int lastChar = 1, currentChar;
        for (int j = 1; j < lineLen; j++) {
          currentChar = Bytes[startLine + j];

          if ((currentChar == 32) && (lastChar != 32)) {
            breaks[tokenCount] = j;
            tokenCount++;
          } else if ((currentChar != 32) && (lastChar == 32)) {
            starts[tokenCount] = j;
          }

          lastChar = currentChar;
        }

        // update numbers so loops work
        breaks[tokenCount] = lineLen;
        tokenCount++;

        if (tokenCount == 1) { // fix for first 2 values on separate lines

          if (skipNext) {
            skipNext = false;
          } else {
            current = NumberUtils.parseInt(startLine, startLine + breaks[0], Bytes);
            skipNext = true;
          }

        } else if (tokenCount == 2) {
          current = NumberUtils.parseInt(startLine, startLine + breaks[0], Bytes);
        } else {

          id = NumberUtils.parseInt(startLine, startLine + breaks[0], Bytes);
          generation = NumberUtils.parseInt(startLine + starts[1], startLine + breaks[1], Bytes);

          flag = (char) Bytes[startLine + starts[2]];

          if ((flag == 'n')) { // only add objects in use

            /** assume not valid and test to see if valid */
            boolean isValid = false;

            // get bytes
            int bufSize = 20;

            // adjust buffer if less than 1024 bytes left in file
            if (id + bufSize > eof) {
              bufSize = (int) (eof - id);
            }

            if (bufSize > 0) {

              /** get bytes into buffer */
              final byte[] buffer = getBytes(id, bufSize, pdf_datafile);

              // look for space o b j
              for (int ii = 4; ii < bufSize; ii++) {
                if ((buffer[ii - 3] == 32 || buffer[ii - 3] == 10)
                    && (buffer[ii - 2] == 111)
                    && (buffer[ii - 1] == 98)
                    && (buffer[ii] == 106)) {
                  isValid = true;
                  ii = bufSize;
                }
              }

              // check number
              if (isValid && isFirstValue) {

                isFirstValue = false;

                if (buffer[0] == 48 && buffer[1] != 48 && current == 1) {
                  current = 0;
                } else if (buffer[0] == 49 && buffer[1] == 32) {
                  current = 1;
                }
              }

              if (isValid) {
                storeObjectOffset(current, id, generation, false, false);
                xref.addElement(id);
              } else if (LogWriter.isRunningFromIDE) {
                LogWriter.writeLog(current + " " + id + " is bum reference");
              }
            }

            current++; // update our pointer
          } else if (flag == 'f') {
            current++; // update our pointer
          }
        }
      }
    }
    return current;
  }
  /** read reference table from file so we can locate objects in pdf file and read the trailers */
  private PdfObject readLegacyReferenceTable(
      PdfObject rootObj, int pointer, final int eof, final PdfFileReader currentPdfFile)
      throws PdfException {

    int endTable, current = 0; // current object number
    byte[] Bytes;
    int bufSize = 1024;

    /** read and decode 1 or more trailers */
    while (true) {

      try {

        // allow for pointer outside file
        Bytes = Trailer.readTrailer(bufSize, pointer, eof, pdf_datafile);

      } catch (final Exception e) {

        try {
          closeFile();
        } catch (final IOException e1) {
          if (LogWriter.isOutput()) {
            LogWriter.writeLog("Exception " + e + " closing file " + e1);
          }
        }
        throw new PdfException("Exception " + e + " reading trailer");
      }

      if (Bytes == null) // safety catch
      {
        break;
      }

      /** get trailer */
      int i = 0;

      final int maxLen = Bytes.length;

      // for(int a=0;a<100;a++)
      //	System.out.println((char)Bytes[i+a]);
      while (i < maxLen) { // look for trailer keyword
        if (Bytes[i] == 116
            && Bytes[i + 1] == 114
            && Bytes[i + 2] == 97
            && Bytes[i + 3] == 105
            && Bytes[i + 4] == 108
            && Bytes[i + 5] == 101
            && Bytes[i + 6] == 114) {
          break;
        }

        i++;
      }

      // save endtable position for later
      endTable = i;

      if (i == Bytes.length) {
        break;
      }

      // move to beyond <<
      while (Bytes[i] != 60 && Bytes[i - 1] != 60) {
        i++;
      }

      i++;
      final PdfObject pdfObject = new CompressedObject("1 0 R");
      Dictionary.readDictionary(pdfObject, i, Bytes, -1, true, currentPdfFile, false);

      // move to beyond >>
      int level = 0;
      while (true) {

        if (Bytes[i] == 60 && Bytes[i - 1] == 60) {
          level++;
          i++;
        } else if (Bytes[i] == '[') {
          i++;
          while (Bytes[i] != ']') {
            i++;
            if (i == Bytes.length) {
              break;
            }
          }
        } else if (Bytes[i] == 62 && Bytes[i - 1] == 62) {
          level--;
          i++;
        }

        if (level == 0) {
          break;
        }

        i++;
      }

      // handle optional XRefStm
      final int XRefStm = pdfObject.getInt(PdfDictionary.XRefStm);

      if (XRefStm != -1) {
        pointer = XRefStm;
      } else { // usual way

        boolean hasRef = true;

        /** handle spaces and comments */
        while (Bytes[i] == 10 || Bytes[i] == 13) {
          i++;
        }

        while (Bytes[i] == '%') {
          while (Bytes[i] != 10) {

            i++;
          }
          i++;
        }
        /* fix for /Users/markee/Downloads/oneiderapartnerbrochure_web_1371798737.pdf
        /**/

        // look for xref as end of startref
        while (Bytes[i] != 116
            && Bytes[i + 1] != 120
            && Bytes[i + 2] != 114
            && Bytes[i + 3] != 101
            && Bytes[i + 4] != 102) {

          if (Bytes[i] == 'o' && Bytes[i + 1] == 'b' && Bytes[i + 2] == 'j') {
            hasRef = false;
            break;
          }
          i++;
        }

        if (hasRef) {

          i += 8;
          // move to start of value ignoring spaces or returns
          while ((i < maxLen) && (Bytes[i] == 10 || Bytes[i] == 32 || Bytes[i] == 13)) {
            i++;
          }

          final int s = i;

          // allow for characters between xref and startref
          while (i < maxLen && Bytes[i] != 10 && Bytes[i] != 32 && Bytes[i] != 13) {
            i++;
          }

          /** convert xref to string to get pointer */
          if (s != i) {
            pointer = NumberUtils.parseInt(s, i, Bytes);
          }
        }
      }

      i = 0;

      // allow for bum data at start
      while (Bytes[i] == 13 || Bytes[i] == 10 || Bytes[i] == 9) {
        i++;
      }

      if (pointer == -1) {
        if (LogWriter.isOutput()) {
          LogWriter.writeLog("No startRef");
        }

        /** now read the objects for the trailers */
      } else if (Bytes[i] == 120
          && Bytes[i + 1] == 114
          && Bytes[i + 2] == 101
          && Bytes[i + 3] == 102) { // make sure starts xref

        i = 5;

        // move to start of value ignoring spaces or returns
        while (Bytes[i] == 10 || Bytes[i] == 32 || Bytes[i] == 13) {
          i++;
        }

        current = offset.readXRefs(current, Bytes, endTable, i, eof, pdf_datafile);

        /**
         * now process trailer values - only first set of table values for root, encryption and info
         */
        if (rootObj == null) {

          rootObj = pdfObject.getDictionary(PdfDictionary.Root);

          encryptObj = pdfObject.getDictionary(PdfDictionary.Encrypt);
          if (encryptObj != null) {

            final byte[][] IDs = pdfObject.getStringArray(PdfDictionary.ID);
            if (IDs != null && this.ID == null) {
              // only the first encountered ID should be used as a fileID for decryption
              this.ID = IDs[0];
            }
          }

          infoObject = pdfObject.getDictionary(PdfDictionary.Info);
        }

        // make sure first values used if several tables and code for prev
        pointer = pdfObject.getInt(PdfDictionary.Prev);

        // see if other trailers
        if (pointer != -1 && pointer < this.eof) {
          // reset values for loop
          bufSize = 1024;

          // track ref table so we can work out object length
          offset.addXref(pointer);

        } else // reset if fails second test above
        {
          pointer = -1;
        }

      } else {
        pointer = -1;

        // needs to be read to pick up potential /Pages value
        //noinspection ObjectAllocationInLoop
        rootObj = new PageObject(BrokenRefTable.findOffsets(pdf_datafile, offset));
        currentPdfFile.readObject(rootObj);

        offset.setRefTableInvalid(true);
      }
      if (pointer == -1) {
        break;
      }
    }

    if (encryptObj == null
        && rootObj != null) { // manual check for broken file (ignore if Encrypted)

      int type = -1;

      int status = rootObj.getStatus();
      byte[] data = rootObj.getUnresolvedData();

      try {

        final ObjectDecoder objectDecoder = new ObjectDecoder(currentPdfFile);
        objectDecoder.checkResolved(rootObj);

        type = rootObj.getParameterConstant(PdfDictionary.Type);

      } catch (Exception e) { // we need to ignore so just catch, put back as was and log

        rootObj.setStatus(status);
        rootObj.setUnresolvedData(data, status);
        if (LogWriter.isOutput()) {
          LogWriter.writeLog("[PDF] Exception reading type on root object " + e);
        }
      }

      // something gone wrong so manually index
      if (type == PdfDictionary.Font) { // see 21153 - ref table in wrong order
        rootObj = null; // /will reset in code at end
      }
    }

    // something gone wrong so manually index
    if (rootObj == null) { // see 21382

      offset.clear();
      offset.reuse();

      // needs to be read to pick up potential /Pages value
      //noinspection ObjectAllocationInLoop
      rootObj = new PageObject(BrokenRefTable.findOffsets(pdf_datafile, offset));
      currentPdfFile.readObject(rootObj);

      offset.setRefTableInvalid(true);
    }

    return rootObj;
  }
  /** for the use of the printf commend */
  private static String convertToken(final String token, final String arg1) {

    if (!StringUtils.isNumber(arg1)) {
      return "";
    }

    final byte[] bytes = StringUtils.toBytes(arg1);
    final double value = NumberUtils.parseDouble(0, bytes.length, bytes);

    // a seperator is used for dates, phone numbers, times, ete
    int decimalPoints = -1, minWidth = 0;
    char decimal = '.'; // seperator = ',',
    boolean padd = false, floatDecimal = false;
    final StringBuilder sValue =
        new StringBuilder(); // used to store the value in string form for each type of output
    final StringBuilder returnString = new StringBuilder();

    // conversion token layout: %[,nDecSep][cFlags][nWidth][.nPrecision]cConvChar
    final char[] tokArray = token.toCharArray();
    if (tokArray[0] == '%') {
      int i = 1;
      final int size = tokArray.length;
      loop:
      while (i < size) {
        switch (tokArray[i]) {
          case ',':
            //					nDecSep - A comma character (,) followed by a digit that indicates the
            // decimal/separator format:
            switch (tokArray[++i]) {
              case '0':
                //						0 � Comma separated, period decimal point
                // seperator = ',';
                decimal = '.';
                break;
              case '1':
                //						1 � No separator, period decimal point
                // seperator = 0;
                decimal = '.';
                break;
              case '2':
                //						2 � Period separated, comma decimal point
                // seperator = '.';
                decimal = ',';
                break;
              case '3':
                //						3 � No separator, comma decimal point
                // seperator = 0;
                decimal = ',';
                break;
            }
            break;

            //				cFlags - Only valid for numeric conversions and consists of a number of characters
            // (in any order),
            //				which will modify the specification:
          case '+': // cFlags
            //					+ � Specifies that the number will always be formatted with a sign.
            if (value > 0) {
              returnString.append('+');
            } else {
              returnString.append('-');
            }
            break;
          case ' ': // cFlags
            //					space � If the first character is not a sign, a space will be prefixed.
            if (value > 0) {
              returnString.append(' ');
            } else {
              returnString.append('-');
            }
            break;
          case '0': // cFlags
            //					0 � Specifies padding to the field with leading zeros.
            padd = true;
            break;
          case '#': // cFlags
            //					# � Specifies an alternate output form. For f, the output will always have a
            // decimal point.
            floatDecimal = true;
            break;

          case '.':
            //					nPrecision - A period character (.) followed by a number that specifies the
            // number of digits
            //					after the decimal point for float conversions.
            decimalPoints = Integer.parseInt(String.valueOf(tokArray[++i]));
            break;

          case 'd': // cConvChar
            //				d � Integer (truncating if necessary)
            sValue.append((int) value);
            if (padd) {
              final int stringlen = returnString.length() + sValue.length();
              if (stringlen < minWidth) {
                for (int p = 0; p < minWidth - stringlen; p++) {
                  returnString.append('0');
                }
              }
            }
            returnString.append(sValue);
            break loop;

          case 'f': // cConvChar
            //				f � Floating-point number
            if (decimalPoints != -1) {
              if (decimalPoints == 0) {
                sValue.append((int) value);
              } else {
                final NumberFormat nf = NumberFormat.getInstance();
                nf.setMinimumFractionDigits(decimalPoints);
                nf.setMaximumFractionDigits(decimalPoints);
                sValue.append(nf.format(value));
              }
            } else {
              sValue.append((float) value);
            }

            if (floatDecimal && sValue.indexOf(".") != -1) {
              sValue.append('.');
            }
            if (padd) {
              final int stringlen = returnString.length() + sValue.length();
              if (stringlen < minWidth) {
                for (int p = 0; p < minWidth - stringlen; p++) {
                  returnString.append('0');
                }
              }
            }
            String ssVal = sValue.toString();
            ssVal = ssVal.replace('.', decimal); // replace the decimal point with the defined one
            returnString.append(ssVal);
            break loop;

          case 's': // cConvChar
            //				s � String
            sValue.append(value); // amay need arg1
            if (padd) {
              final int stringlen = returnString.length() + sValue.length();
              if (stringlen < minWidth) {
                for (int p = 0; p < minWidth - stringlen; p++) {
                  returnString.append('0');
                }
              }
            }
            returnString.append(sValue);
            break loop;

          case 'x': // cConvChar
            //				x � Integer (truncating if necessary) and formatted in unsigned hexadecimal
            // notation
            final int valI = (int) (value);
            final String retValS = Integer.toHexString(valI);

            sValue.append(retValS);
            if (padd) {
              final int stringlen = returnString.length() + sValue.length();
              if (stringlen < minWidth) {
                for (int p = 0; p < minWidth - stringlen; p++) {
                  returnString.append('0');
                }
              }
            }
            returnString.append(sValue);
            break loop;

          default:
            //					nWidth - A number specifying a minimum field width. The converted argument is
            // formatted to be at
            //					least this many characters wide, including the sign and decimal point, and may be
            // wider
            //					if necessary. If the converted argument has fewer characters than the field
            // width, it is
            //					padded on the left to make up the field width. The padding character is normally
            // a space,
            //					but is 0 if the zero padding flag is present (cFlags contains 0).
            minWidth = NumberUtils.parseInt(0, 1, new byte[] {(byte) tokArray[i]});
            break;
        }
        i++;
      } // end while loop
    }

    return returnString.toString();
  }