/** 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(); }