org.jpedal.objects.SwingShape Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of OpenViewerFX Show documentation
Show all versions of OpenViewerFX Show documentation
Open Source (LGPL) JavaFX PDF Viewer for NetBeans plugin
/*
* ===========================================
* 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;
}
}