org.jpedal.objects.SwingShape Maven / Gradle / Ivy
/*
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info: http://www.idrsolutions.com
* Help section for developers at http://www.idrsolutions.com/support/
*
* (C) Copyright 1997-2017 IDRsolutions and Contributors.
*
* This file is part of JPedal/JPDF2HTML5
*
@LICENSE@
*
* ---------------
* SwingShape.java
* ---------------
*/
package org.jpedal.objects;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.io.Serializable;
import org.jpedal.parser.Cmd;
import org.jpedal.utils.LogWriter;
import org.jpedal.utils.repositories.Vector_Float;
import org.jpedal.utils.repositories.Vector_Int;
/**
*
* defines the current shape which is created by command stream
*
*
* This class is NOT part of the API
*
. Shapes can be drawn onto pdf or used as a clip on other
* image/shape/text. Shape is built up by storing commands and then turning
* these commands into a shape. Has to be done this way as Winding rule is not
* necessarily declared at start.
*/
public class SwingShape implements Serializable, PdfShape {
/**
* used to stop lots of huge, complex shapes.
* Note we DO NOT reset as we reuse this object and
* it stores cumulative count
*/
int complexClipCount;
private static final int initSize = 1000;
/**
* we tell user we have not used some shapes only ONCE
*/
private final Vector_Float shape_primitive_x2 = new Vector_Float(initSize);
private final Vector_Float shape_primitive_y = new Vector_Float(initSize);
/**
* store shape currently being assembled
*/
private final Vector_Int shape_primitives = new Vector_Int(initSize);
/**
* type of winding rule used to draw shape
*/
private int winding_rule = GeneralPath.WIND_NON_ZERO;
private final Vector_Float shape_primitive_x3 = new Vector_Float(initSize);
private final Vector_Float shape_primitive_y3 = new Vector_Float(initSize);
/**
* used when trying to choose which shapes to use to test furniture
*/
private final Vector_Float shape_primitive_y2 = new Vector_Float(initSize);
private final Vector_Float shape_primitive_x = new Vector_Float(initSize);
private static final int H = 3;
private static final int L = 2;
private static final int V = 6;
/**
* flags for commands used
*/
private static final int M = 1;
private static final int Y = 4;
private static final int C = 5;
ShapeMetrics metrics = new ShapeMetrics();
/**
* flag to show if image is for clip
*/
private boolean isClip;
/**
* flag to show if S.java needs to adjust lineWidth because we have modified the shape
*/
private Shape currentShape;
public SwingShape(final Shape currentShape) {
this.currentShape = currentShape;
}
public SwingShape() {
}
@Override
public boolean isClosed() {
return isClosed;
}
private boolean isClosed;
/////////////////////////////////////////////////////////////////////////
/**
* end a shape, storing info for later
*/
@Override
public final void closeShape() {
shape_primitives.addElement(H);
//add empty values
shape_primitive_x.addElement(0);
shape_primitive_y.addElement(0);
shape_primitive_x2.addElement(0);
shape_primitive_y2.addElement(0);
shape_primitive_x3.addElement(0);
shape_primitive_y3.addElement(0);
}
//////////////////////////////////////////////////////////////////////////
/**
* add a curve to the shape
*/
@Override
public final void addBezierCurveC(final float x, final float y, final float x2, final float y2, final float x3, final float y3) {
shape_primitives.addElement(C);
shape_primitive_x.addElement(x);
shape_primitive_y.addElement(y);
//add empty values to keep in sync
//add empty values
shape_primitive_x2.addElement(x2);
shape_primitive_y2.addElement(y2);
shape_primitive_x3.addElement(x3);
shape_primitive_y3.addElement(y3);
}
//////////////////////////////////////////////////////////////////////////
/**
* set winding rule - non zero
*/
@Override
public final void setNONZEROWindingRule() {
winding_rule = GeneralPath.WIND_NON_ZERO;
}
//////////////////////////////////////////////////////////////////////////
/**
* add a line to the shape
*/
@Override
public final void lineTo(final float x, final float y) {
shape_primitives.addElement(L);
shape_primitive_x.addElement(x);
shape_primitive_y.addElement(y);
//add empty values to keep in sync
//add empty values
shape_primitive_x2.addElement(0);
shape_primitive_y2.addElement(0);
shape_primitive_x3.addElement(0);
shape_primitive_y3.addElement(0);
}
///////////////////////////////////////////////////////////////////////////
/**
* add a curve to the shape
*/
@Override
public final void addBezierCurveV(final float x2, final float y2, final float x3, final float y3) {
shape_primitives.addElement(V);
shape_primitive_x.addElement(200);
shape_primitive_y.addElement(200);
//add empty values to keep in sync
//add empty values
shape_primitive_x2.addElement(x2);
shape_primitive_y2.addElement(y2);
shape_primitive_x3.addElement(x3);
shape_primitive_y3.addElement(y3);
}
//////////////////////////////////////////////////////////////////////////
/**
* turn shape commands into a Shape object, storing info for later. Has to
* be done this way because we need the winding rule to initialise the shape
* in Java, and it could be set anywhere in the command stream
*/
@Override
public final Shape generateShapeFromPath(final float[][] CTM, final float thickness, final int cmd) {
isClosed = false; //will be set to true if closed
boolean is_clip = this.isClip;
if (cmd == Cmd.n) {
is_clip = false;
}
//create the shape - we have to do it this way
//because we get the WINDING RULE last and we need it
//to initialise the shape
GeneralPath current_path = null;
Area current_area = null;
currentShape = null;
//init points
final float[] x = shape_primitive_x.get();
final float[] y = shape_primitive_y.get();
final float[] x2 = shape_primitive_x2.get();
final float[] y2 = shape_primitive_y2.get();
final float[] x3 = shape_primitive_x3.get();
final float[] y3 = shape_primitive_y3.get();
final int[] command = shape_primitives.get();
//float lx=0,ly=0;
//float xs=0,ys=0;
final int end = shape_primitives.size() - 1;
//code to fix rounding issue in clipping if rect and boundary just over 0.5
//tweaked for abacus/Zebra_als_PDF_OK.pdf
if (end == 6 && cmd == Cmd.B && thickness >= 0.9f && is_clip) {
for (int aa = 0; aa < 8; aa++) {
final float diff = x[aa] - (int) x[aa];
if (diff > 0.5f) {
x[aa] = (int) x[aa] - 1f;
}
}
}
//used to debug
final boolean show = false;
final boolean debug = false;
//loop through commands and add to shape
for (int i = 0; i < end; i++) {
if (current_path == null) {
if (command[i] != M) {
continue;
}
current_path = new GeneralPath(winding_rule);
current_path.moveTo(x[i], y[i]);
//lx=x[i];
//ly=y[i];
//xs=lx;
//ys=ly;
if (show) {
LogWriter.writeLog("==START=" + x[i] + ' ' + y[i]);
}
}
//only used to create clips
if (command[i] == H) {
isClosed = true;
current_path.closePath();
if (is_clip) {
//current_path.lineTo(xs,ys);
//current_path.closePath();
if (show) {
LogWriter.writeLog("==H\n\n" + current_area + ' ' + current_path.getBounds2D() + ' ' + new Area(current_path).getBounds2D());
}
if (current_area == null) {
current_area = new Area(current_path);
//trap for apparent bug in Java where small paths create a 0 size Area
if ((current_area.getBounds2D().getWidth() <= 0.0) ||
(current_area.getBounds2D().getHeight() <= 0.0)) {
current_area = new Area(current_path.getBounds2D());
}
} else {
current_area.add(new Area(current_path));
}
current_path = null;
} else {
if (show) {
LogWriter.writeLog("close shape " + command[i] + " i=" + i);
}
}
}
if (command[i] == L) {
current_path.lineTo(x[i], y[i]);
//lx=x[i];
//ly=y[i];
if (show) {
LogWriter.writeLog("==L" + x[i] + ',' + y[i] + " ");
}
} else if (command[i] == M) {
current_path.moveTo(x[i], y[i]);
//lx=x[i];
//ly=y[i];
if (show) {
LogWriter.writeLog("==M" + x[i] + ',' + y[i] + " ");
}
} else {
//cubic curves which use 2 control points
if (command[i] == Y) {
if (show) {
LogWriter.writeLog("==Y " + x[i] + ' ' + y[i] + ' ' + x3[i] + ' ' + y3[i] + ' ' + x3[i] + ' ' + y3[i]);
}
current_path.curveTo(x[i], y[i], x3[i], y3[i], x3[i], y3[i]);
//lx=x3[i];
//ly=y3[i];
} else if (command[i] == C) {
if (show) {
LogWriter.writeLog("==C " + x[i] + ' ' + y[i] + ' ' + x2[i] + ' ' + y2[i] + ' ' + x3[i] + ' ' + y3[i]);
}
current_path.curveTo(x[i], y[i], x2[i], y2[i], x3[i], y3[i]);
//lx=x3[i];
//ly=y3[i];
} else if (command[i] == V) {
final float c_x = (float) current_path.getCurrentPoint().getX();
final float c_y = (float) current_path.getCurrentPoint().getY();
if (show) {
LogWriter.writeLog("==v " + c_x + ',' + c_y + ',' + x2[i] + ',' + y2[i] + ',' + x3[i] + ',' + y3[i]);
}
current_path.curveTo(c_x, c_y, x2[i], y2[i], x3[i], y3[i]);
//lx=x3[i];
//ly=y3[i];
}
}
if (debug) {
try {
final java.awt.image.BufferedImage img = new java.awt.image.BufferedImage(700, 700, java.awt.image.BufferedImage.TYPE_INT_ARGB);
final Graphics2D gg2 = img.createGraphics();
gg2.setPaint(Color.RED);
gg2.translate(current_path.getBounds().width + 10, current_path.getBounds().height + 10);
gg2.draw(current_path);
org.jpedal.gui.ShowGUIMessage.showGUIMessage("path", img, "path " + current_path.getBounds());
} catch (final Exception e) {
LogWriter.writeLog("Exception: " + e.getMessage());
}
}
}
//transform matrix only if needed
if ((CTM[0][0] == (float) 1.0) && (CTM[1][0] == (float) 0.0) &&
(CTM[2][0] == (float) 0.0) && (CTM[0][1] == (float) 0.0) &&
(CTM[1][1] == (float) 1.0) && (CTM[2][1] == (float) 0.0) &&
(CTM[0][2] == (float) 0.0) && (CTM[1][2] == (float) 0.0) && (CTM[2][2] == (float) 1.0)) {
//don't waste time if not needed
} else {
final AffineTransform CTM_transform = new AffineTransform(CTM[0][0], CTM[0][1], CTM[1][0], CTM[1][1], CTM[2][0], CTM[2][1]);
//apply CTM alterations
if (current_path != null) {
//transform
current_path.transform(CTM_transform);
//if(CTM[0][0]==0 && CTM[1][1]==0 && CTM[0][1]<0 && CTM[1][0]>0){
// current_path.transform(AffineTransform.getTranslateInstance(0,current_path.getBounds().height/CTM[0][1]));
//System.out.println("transforms "+CTM_transform+" "+current_path.getBounds());
//}
} else if (current_area != null) {
current_area.transform(CTM_transform);
}
}
/*
* fix for single rotated lines with thickness
*/
if (current_path != null && CTM[0][0] == 1 && CTM[1][1] == -1 && current_path.getBounds().height == 1 && thickness > 10) {
final Rectangle currentBounds = current_path.getBounds();
current_path = new GeneralPath(winding_rule);
current_path.moveTo(currentBounds.x, currentBounds.y - thickness / 2);
current_path.lineTo(currentBounds.x, currentBounds.y + thickness / 2);
current_path.closePath();
}
//set to current or clip
if (!is_clip) {
if (current_area == null) {
currentShape = current_path;
} else {
currentShape = current_area;
}
} else {
currentShape = current_area;
}
//track complex clips
if (cmd == Cmd.n && getSegmentCount() > 2500) {
complexClipCount++;
}
return currentShape;
}
//////////////////////////////////////////////////////////////////////////
/**
* add a rectangle to set of shapes
*/
@Override
public final void appendRectangle(final float x, final float y, final float w, final float h) {
moveTo(x, y);
lineTo(x + w, y);
lineTo(x + w, y + h);
lineTo(x, y + h);
lineTo(x, y);
closeShape();
metrics.incrementRectangleCount();
}
/**
* start a shape by creating a shape object
*/
@Override
public final void moveTo(final float x, final float y) {
shape_primitives.addElement(M);
shape_primitive_x.addElement(x);
shape_primitive_y.addElement(y);
//add empty values
shape_primitive_x2.addElement(0);
shape_primitive_y2.addElement(0);
shape_primitive_x3.addElement(0);
shape_primitive_y3.addElement(0);
//delete lines for grouping over boxes
}
/**
* add a curve to the shape
*/
@Override
public final void addBezierCurveY(final float x, final float y, final float x3, final float y3) {
shape_primitives.addElement(Y);
shape_primitive_x.addElement(x);
shape_primitive_y.addElement(y);
//add empty values to keep in sync
//add empty values
shape_primitive_x2.addElement(0);
shape_primitive_y2.addElement(0);
shape_primitive_x3.addElement(x3);
shape_primitive_y3.addElement(y3);
}
/**
* reset path to empty
*/
@Override
public final void resetPath() {
//reset the store
shape_primitives.clear();
shape_primitive_x.clear();
shape_primitive_y.clear();
shape_primitive_x2.clear();
shape_primitive_y2.clear();
shape_primitive_x3.clear();
shape_primitive_y3.clear();
//and reset winding rule
winding_rule = GeneralPath.WIND_NON_ZERO;
this.metrics.setRectangleCount(0);
}
///////////////////////////////////////////////////////////////////////////
/**
* set winding rule - even odd
*/
@Override
public final void setEVENODDWindingRule() {
winding_rule = GeneralPath.WIND_EVEN_ODD;
}
/**
* number of segments in current shape (0 if no shape or none)
*/
@Override
public int getSegmentCount() {
if (shape_primitives == null) {
return 0;
} else {
return shape_primitives.size() - 1;
}
}
@Override
public void setClip(final boolean b) {
this.isClip = b;
}
@Override
public boolean isClip() {
return isClip;
}
@Override
public int getComplexClipCount() {
return complexClipCount;
}
@Override
public Object getPath() {
return null;
}
@Override
public void setShape(final Shape currentShape) {
this.currentShape = currentShape;
}
@Override
public Shape getShape() {
return currentShape;
}
public ShapeMetrics getMetrics() {
return metrics;
}
}