/** * Called when a string of text with spacing adjustments is to be shown. * * @param array array of encoded text strings and adjustments * @throws IOException if there was an error showing the text */ public void showTextStrings(COSArray array) throws IOException { PDTextState textState = getGraphicsState().getTextState(); float fontSize = textState.getFontSize(); float horizontalScaling = textState.getHorizontalScaling() / 100f; PDFont font = textState.getFont(); boolean isVertical = false; if (font != null) { isVertical = font.isVertical(); } for (COSBase obj : array) { if (obj instanceof COSNumber) { float tj = ((COSNumber) obj).floatValue(); // calculate the combined displacements float tx, ty; if (isVertical) { tx = 0; ty = -tj / 1000 * fontSize; } else { tx = -tj / 1000 * fontSize * horizontalScaling; ty = 0; } applyTextAdjustment(tx, ty); } else if (obj instanceof COSString) { byte[] string = ((COSString) obj).getBytes(); showText(string); } else { throw new IOException("Unknown type in array for TJ operation:" + obj); } } }
/** * Process text from the PDF Stream. You should override this method if you want to perform an * action when encoded text is being processed. * * @param string the encoded text * @throws IOException if there is an error processing the string */ protected void showText(byte[] string) throws IOException { PDGraphicsState state = getGraphicsState(); PDTextState textState = state.getTextState(); // get the current font PDFont font = textState.getFont(); if (font == null) { // LOG.warn("No current font, will use default"); font = PDFontFactory.createDefaultFont(); } float fontSize = textState.getFontSize(); float horizontalScaling = textState.getHorizontalScaling() / 100f; float charSpacing = textState.getCharacterSpacing(); // put the text state parameters into matrix form Matrix parameters = new Matrix( fontSize * horizontalScaling, 0, // 0 0, fontSize, // 0 0, textState.getRise()); // 1 // read the stream until it is empty InputStream in = new ByteArrayInputStream(string); while (in.available() > 0) { // decode a character int before = in.available(); int code = font.readCode(in); int codeLength = before - in.available(); String unicode = font.toUnicode(code); // Word spacing shall be applied to every occurrence of the single-byte character code // 32 in a string when using a simple font or a composite font that defines code 32 as // a single-byte code. float wordSpacing = 0; if (codeLength == 1 && code == 32) { wordSpacing += textState.getWordSpacing(); } // text rendering matrix (text space -> device space) Matrix ctm = state.getCurrentTransformationMatrix(); Matrix textRenderingMatrix = parameters.multiply(textMatrix).multiply(ctm); // get glyph's position vector if this is vertical text // changes to vertical text should be tested with PDFBOX-2294 and PDFBOX-1422 if (font.isVertical()) { // position vector, in text space Vector v = font.getPositionVector(code); // apply the position vector to the horizontal origin to get the vertical origin textRenderingMatrix.translate(v); } // get glyph's horizontal and vertical displacements, in text space Vector w = font.getDisplacement(code); // process the decoded glyph saveGraphicsState(); Matrix textMatrixOld = textMatrix; Matrix textLineMatrixOld = textLineMatrix; showGlyph(textRenderingMatrix, font, code, unicode, w); textMatrix = textMatrixOld; textLineMatrix = textLineMatrixOld; restoreGraphicsState(); // calculate the combined displacements float tx, ty; if (font.isVertical()) { tx = 0; ty = w.getY() * fontSize + charSpacing + wordSpacing; } else { tx = (w.getX() * fontSize + charSpacing + wordSpacing) * horizontalScaling; ty = 0; } // update the text matrix textMatrix.concatenate(Matrix.getTranslateInstance(tx, ty)); } }