@Override public void process(Operator operator, List<COSBase> arguments) throws MissingOperandException { if (arguments.size() < 2) { throw new MissingOperandException(operator, arguments); } Matrix textLineMatrix = context.getTextLineMatrix(); if (textLineMatrix == null) { // LOG.warn("TextLineMatrix is null, " + getName() + " operator will be ignored"); return; } COSBase base0 = arguments.get(0); COSBase base1 = arguments.get(1); if (!(base0 instanceof COSNumber)) { return; } if (!(base1 instanceof COSNumber)) { return; } COSNumber x = (COSNumber) base0; COSNumber y = (COSNumber) base1; Matrix matrix = new Matrix(1, 0, 0, 1, x.floatValue(), y.floatValue()); textLineMatrix.concatenate(matrix); context.setTextMatrix(textLineMatrix.clone()); }
/** * process : BI : begin inline image. * * @param operator The operator that is being executed. * @param arguments List * @throws IOException If there is an error displaying the inline image. */ public void process(PDFOperator operator, List arguments) throws IOException { PDFObjectExtractor drawer = (PDFObjectExtractor) context; Graphics2D graphics = drawer.getGraphics(); // begin inline image object ImageParameters params = operator.getImageParameters(); PDInlinedImage image = new PDInlinedImage(); image.setImageParameters(params); image.setImageData(operator.getImageData()); // 7.04.09 added try/catch to allow for mis-reading of images // in Kurier // BufferedImage awtImage = image.createImage(); BufferedImage awtImage = new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR); try { awtImage = image.createImage(); } catch (Exception e) { e.printStackTrace(); } Matrix ctm = drawer.getGraphicsState().getCurrentTransformationMatrix(); int width = awtImage.getWidth(); int height = awtImage.getHeight(); AffineTransform at = new AffineTransform( ctm.getValue(0, 0) / width, ctm.getValue(0, 1), ctm.getValue(1, 0), ctm.getValue(1, 1) / height, ctm.getValue(2, 0), ctm.getValue(2, 1)); // at.setToRotation((double)page.getRotation()); // The transformation should be done // 1 - Translation // 2 - Rotation // 3 - Scale or Skew // AffineTransform at = new AffineTransform(); // Translation // at = new AffineTransform(); // at.setToTranslation((double)ctm.getValue(0,0), // (double)ctm.getValue(0,1)); // Rotation // AffineTransform toAdd = new AffineTransform(); // toAdd.setToRotation(1.5705); // toAdd.setToRotation(ctm.getValue(2,0)*(Math.PI/180)); // at.concatenate(toAdd); // Scale / Skew? // toAdd.setToScale(width, height); // at.concatenate(toAdd); // at.setToScale( width, height ); graphics.drawImage(awtImage, at, null); // graphics.drawImage( awtImage,0,0, width,height,null); //// drawer.simpleDrawImage(width, height); }
/** * Constructor creates an instance to be used for fill operations. * * @param shading the shading type to be used * @param colorModel the color model to be used * @param xform transformation for user to device space * @param matrix the pattern matrix concatenated with that of the parent content stream * @param deviceBounds the bounds of the area to paint, in device units * @throws IOException if there is an error getting the color space or doing color conversion. */ public AxialShadingContext( PDShadingType2 shading, ColorModel colorModel, AffineTransform xform, Matrix matrix, Rectangle deviceBounds) throws IOException { super(shading, colorModel, xform, matrix); this.axialShadingType = shading; coords = shading.getCoords().toFloatArray(); // domain values if (shading.getDomain() != null) { domain = shading.getDomain().toFloatArray(); } else { // set default values domain = new float[] {0, 1}; } // extend values COSArray extendValues = shading.getExtend(); if (shading.getExtend() != null) { extend = new boolean[2]; extend[0] = ((COSBoolean) extendValues.get(0)).getValue(); extend[1] = ((COSBoolean) extendValues.get(1)).getValue(); } else { // set default values extend = new boolean[] {false, false}; } // calculate some constants to be used in getRaster x1x0 = coords[2] - coords[0]; y1y0 = coords[3] - coords[1]; d1d0 = domain[1] - domain[0]; denom = Math.pow(x1x0, 2) + Math.pow(y1y0, 2); try { // get inverse transform to be independent of current user / device space // when handling actual pixels in getRaster() rat = matrix.createAffineTransform().createInverse(); rat.concatenate(xform.createInverse()); } catch (NoninvertibleTransformException ex) { LOG.error(ex, ex); } // shading space -> device space AffineTransform shadingToDevice = (AffineTransform) xform.clone(); shadingToDevice.concatenate(matrix.createAffineTransform()); // worst case for the number of steps is opposite diagonal corners, so use that double dist = Math.sqrt( Math.pow(deviceBounds.getMaxX() - deviceBounds.getMinX(), 2) + Math.pow(deviceBounds.getMaxY() - deviceBounds.getMinY(), 2)); factor = (int) Math.ceil(dist); // build the color table for the given number of steps colorTable = calcColorTable(); }
/** * process : BI : begin inline image. * * @param operator The operator that is being executed. * @param arguments List * @throws IOException If there is an error displaying the inline image. */ public void process(PDFOperator operator, List arguments) throws IOException { PageDrawer drawer = (PageDrawer) context; Graphics2D graphics = drawer.getGraphics(); // begin inline image object ImageParameters params = operator.getImageParameters(); PDInlinedImage image = new PDInlinedImage(); image.setImageParameters(params); image.setImageData(operator.getImageData()); BufferedImage awtImage = image.createImage(); Matrix ctm = drawer.getGraphicsState().getCurrentTransformationMatrix(); int width = awtImage.getWidth(); int height = awtImage.getHeight(); AffineTransform at = new AffineTransform( ctm.getValue(0, 0) / width, ctm.getValue(0, 1), ctm.getValue(1, 0), ctm.getValue(1, 1) / height, ctm.getValue(2, 0), ctm.getValue(2, 1)); // at.setToRotation((double)page.getRotation()); // The transformation should be done // 1 - Translation // 2 - Rotation // 3 - Scale or Skew // AffineTransform at = new AffineTransform(); // Translation // at = new AffineTransform(); // at.setToTranslation((double)ctm.getValue(0,0), // (double)ctm.getValue(0,1)); // Rotation // AffineTransform toAdd = new AffineTransform(); // toAdd.setToRotation(1.5705); // toAdd.setToRotation(ctm.getValue(2,0)*(Math.PI/180)); // at.concatenate(toAdd); // Scale / Skew? // toAdd.setToScale(width, height); // at.concatenate(toAdd); // at.setToScale( width, height ); graphics.drawImage(awtImage, at, null); // graphics.drawImage( awtImage,0,0, width,height,null); }
/** * Computes a series of bounding boxes (PDRectangle) from a list of TextPositions. It will create * a new bounding box if the vertical tolerance is exceeded * * @param positions * @throws IOException */ public List<PDRectangle> getTextBoundingBoxes(final List<TextPosition> positions) { final List<PDRectangle> boundingBoxes = new ArrayList<PDRectangle>(); float lowerLeftX = -1, lowerLeftY = -1, upperRightX = -1, upperRightY = -1; boolean first = true; for (int i = 0; i < positions.size(); i++) { final TextPosition position = positions.get(i); if (position == null) { continue; } final Matrix textPos = position.getTextPos(); final float height = position.getHeight() * getHeightModifier(); if (first) { lowerLeftX = textPos.getXPosition(); upperRightX = lowerLeftX + position.getWidth(); lowerLeftY = textPos.getYPosition(); upperRightY = lowerLeftY + height; first = false; continue; } // we are still on the same line if (Math.abs(textPos.getYPosition() - lowerLeftY) <= getVerticalTolerance()) { upperRightX = textPos.getXPosition() + position.getWidth(); upperRightY = textPos.getYPosition() + height; } else { final PDRectangle boundingBox = boundingBox(lowerLeftX, lowerLeftY, upperRightX, upperRightY); boundingBoxes.add(boundingBox); // new line lowerLeftX = textPos.getXPosition(); upperRightX = lowerLeftX + position.getWidth(); lowerLeftY = textPos.getYPosition(); upperRightY = lowerLeftY + height; } } if (!(lowerLeftX == -1 && lowerLeftY == -1 && upperRightX == -1 && upperRightY == -1)) { final PDRectangle boundingBox = boundingBox(lowerLeftX, lowerLeftY, upperRightX, upperRightY); boundingBoxes.add(boundingBox); } return boundingBoxes; }
/** * Process the given tiling pattern. Allows the pattern matrix to be overridden for custom * rendering. * * @param tilingPattern the tiling pattern * @param color color to use, if this is an uncoloured pattern, otherwise null. * @param colorSpace color space to use, if this is an uncoloured pattern, otherwise null. * @param patternMatrix the pattern matrix, may be overridden for custom rendering. * @throws IOException if there is an error reading or parsing the tiling pattern content stream. */ protected final void processTilingPattern( PDTilingPattern tilingPattern, PDColor color, PDColorSpace colorSpace, Matrix patternMatrix) throws IOException { PDResources parent = pushResources(tilingPattern); Matrix parentMatrix = initialMatrix; initialMatrix = Matrix.concatenate(initialMatrix, patternMatrix); // save the original graphics state Stack<PDGraphicsState> savedStack = saveGraphicsStack(); // save a clean state (new clipping path, line path, etc.) Rectangle2D bbox = tilingPattern.getBBox().transform(patternMatrix).getBounds2D(); PDRectangle rect = new PDRectangle( (float) bbox.getX(), (float) bbox.getY(), (float) bbox.getWidth(), (float) bbox.getHeight()); graphicsStack.push(new PDGraphicsState(rect)); // non-colored patterns have to be given a color if (colorSpace != null) { color = new PDColor(color.getComponents(), colorSpace); getGraphicsState().setNonStrokingColorSpace(colorSpace); getGraphicsState().setNonStrokingColor(color); getGraphicsState().setStrokingColorSpace(colorSpace); getGraphicsState().setStrokingColor(color); } // transform the CTM using the stream's matrix getGraphicsState().getCurrentTransformationMatrix().concatenate(patternMatrix); // clip to bounding box clipToRect(tilingPattern.getBBox()); // processStreamOperators(tilingPattern); initialMatrix = parentMatrix; restoreGraphicsStack(savedStack); popResources(parent); }
/** * Constructor creates an instance to be used for fill operations. * * @param shading the shading type to be used * @param colorModel the color model to be used * @param xform transformation for user to device space * @param ctm the transformation matrix * @param dBounds device bounds * @param pageHeight height of the current page */ public RadialShadingContext( PDShadingType3 shading, ColorModel colorModel, AffineTransform xform, Matrix ctm, int pageHeight, Rectangle dBounds) throws IOException { super(shading, colorModel, xform, ctm, pageHeight, dBounds); this.radialShadingType = shading; coords = shading.getCoords().toFloatArray(); if (ctm != null) { // the shading is used in combination with the sh-operator // transform the coords from shading to user space ctm.createAffineTransform().transform(coords, 0, coords, 0, 1); ctm.createAffineTransform().transform(coords, 3, coords, 3, 1); // scale radius to user space coords[2] *= ctm.getXScale(); coords[5] *= ctm.getXScale(); // move the 0,0-reference coords[1] = pageHeight - coords[1]; coords[4] = pageHeight - coords[4]; } else { // the shading is used as pattern colorspace in combination // with a fill-, stroke- or showText-operator float translateY = (float) xform.getTranslateY(); // move the 0,0-reference including the y-translation from user to device space coords[1] = pageHeight + translateY - coords[1]; coords[4] = pageHeight + translateY - coords[4]; } // transform the coords from user to device space xform.transform(coords, 0, coords, 0, 1); xform.transform(coords, 3, coords, 3, 1); // scale radius to device space coords[2] *= xform.getScaleX(); coords[5] *= xform.getScaleX(); // a radius is always positive coords[2] = Math.abs(coords[2]); coords[5] = Math.abs(coords[5]); // domain values if (this.radialShadingType.getDomain() != null) { domain = shading.getDomain().toFloatArray(); } else { // set default values domain = new float[] {0, 1}; } // extend values COSArray extendValues = shading.getExtend(); if (shading.getExtend() != null) { extend = new boolean[2]; extend[0] = ((COSBoolean) extendValues.get(0)).getValue(); extend[1] = ((COSBoolean) extendValues.get(1)).getValue(); } else { // set default values extend = new boolean[] {false, false}; } // calculate some constants to be used in getRaster x1x0 = coords[3] - coords[0]; y1y0 = coords[4] - coords[1]; r1r0 = coords[5] - coords[2]; x1x0pow2 = Math.pow(x1x0, 2); y1y0pow2 = Math.pow(y1y0, 2); r0pow2 = Math.pow(coords[2], 2); denom = x1x0pow2 + y1y0pow2 - Math.pow(r1r0, 2); d1d0 = domain[1] - domain[0]; // get background values if available COSArray bg = shading.getBackground(); if (bg != null) { background = bg.toFloatArray(); rgbBackground = convertToRGB(background); } longestDistance = getLongestDis(); colorTable = calcColorTable(); }
/** Transforms a width using the CTM. */ protected float transformWidth(float width) { Matrix ctm = getGraphicsState().getCurrentTransformationMatrix(); float x = ctm.getScaleX() + ctm.getShearX(); float y = ctm.getScaleY() + ctm.getShearY(); return width * (float) Math.sqrt((x * x + y * y) * 0.5); }
/** * 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)); } }
/** * Applies a text position adjustment from the TJ operator. May be overridden in subclasses. * * @param tx x-translation * @param ty y-translation */ protected void applyTextAdjustment(float tx, float ty) throws IOException { // update the text matrix textMatrix.concatenate(Matrix.getTranslateInstance(tx, ty)); }