package net.sourceforge.fidocadj.primitives; import java.io.*; import java.util.*; import net.sourceforge.fidocadj.dialogs.*; import net.sourceforge.fidocadj.export.*; import net.sourceforge.fidocadj.geom.*; import net.sourceforge.fidocadj.globals.*; import net.sourceforge.fidocadj.graphic.*; /** Class to handle the line primitive. <pre> This file is part of FidoCadJ. FidoCadJ is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. FidoCadJ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FidoCadJ. If not, @see <a href=http://www.gnu.org/licenses/>http://www.gnu.org/licenses/</a>. Copyright 2007-2016 by Davide Bucci </pre> @author Davide Bucci */ public final class PrimitiveLine extends GraphicPrimitive { static final int N_POINTS=4; // Info about arrow. private final Arrow arrowData; private int dashStyle; // Those are data which are kept for the fast redraw of this primitive. // Basically, they are calculated once and then used as much as possible // without having to calculate everything from scratch. private int xa, ya, xb, yb; private int x1, y1,x2,y2; private float w; private int length2; private int xbpap1, ybpap1; private boolean arrows; /** Constructor. @param x1 the x coordinate of the start point of the line. @param y1 the y coordinate of the start point of the line. @param x2 the x coordinate of the end point of the line. @param y2 the y coordinate of the end point of the line. @param layer the layer to be used. @param arrowS true if there is an arrow at the beginning of the line. @param arrowE true if there is an arrow at the end of the line. @param arrowSt style of the arrow. @param arrowLe length of the arrow. @param arrowWi width of the arrow. @param dashSt the dashing style. @param f the name of the font for attached text. @param size the size of the font for attached text. */ public PrimitiveLine(int x1, int y1, int x2, int y2, int layer, boolean arrowS, boolean arrowE, int arrowSt, int arrowLe, int arrowWi, int dashSt, String f, int size) { super(); arrowData=new Arrow(); arrowData.setArrowStart(arrowS); arrowData.setArrowEnd(arrowE); arrowData.setArrowHalfWidth(arrowWi); arrowData.setArrowLength(arrowLe); arrowData.setArrowStyle(arrowSt); dashStyle = dashSt; initPrimitive(-1, f, size); virtualPoint[0].x=x1; virtualPoint[0].y=y1; virtualPoint[1].x=x2; virtualPoint[1].y=y2; virtualPoint[getNameVirtualPointNumber()].x=x1+5; virtualPoint[getNameVirtualPointNumber()].y=y1+5; virtualPoint[getValueVirtualPointNumber()].x=x1+5; virtualPoint[getValueVirtualPointNumber()].y=y1+10; setLayer(layer); } /** Constructor. @param f the name of the font for attached text. @param size the size of the font for attached text. */ public PrimitiveLine(String f, int size) { super(); arrowData=new Arrow(); initPrimitive(-1, f, size); } /** Gets the number of control points used. @return the number of points used by the primitive */ public int getControlPointNumber() { return N_POINTS; } /** Get the control parameters of the given primitive. @return a vector of ParameterDescription containing each control parameter. */ public Vector<ParameterDescription> getControls() { Vector<ParameterDescription> v=super.getControls(); arrowData.getControlsForArrow(v); ParameterDescription pd = new ParameterDescription(); pd.parameter=new DashInfo(dashStyle); pd.description=Globals.messages.getString("ctrl_dash_style"); pd.isExtension = true; v.add(pd); return v; } /** Set the control parameters of the given primitive. This method is specular to getControls(). @param v a vector of ParameterDescription containing each control parameter. The first parameters should always be the virtual points. @return the next index in v to be scanned (if needed) after the execution of this function. */ public int setControls(Vector<ParameterDescription> v) { int i=super.setControls(v); i=arrowData.setParametersForArrow(v, i); ParameterDescription pd=(ParameterDescription)v.get(i++); if (pd.parameter instanceof DashInfo) dashStyle=((DashInfo)pd.parameter).style; else System.out.println("Warning: 6-unexpected parameter!"+pd); // Parameters validation and correction if(dashStyle>=Globals.dashNumber) dashStyle=Globals.dashNumber-1; if(dashStyle<0) dashStyle=0; return i; } /** Draw the graphic primitive on the given graphic context. @param g the graphic context in which the primitive should be drawn. @param coordSys the graphic coordinates system to be applied. @param layerV the layer description. */ public void draw(GraphicsInterface g, MapCoordinates coordSys, Vector layerV) { if(!selectLayer(g,layerV)) return; drawText(g, coordSys, layerV, -1); if(changed) { changed=false; // in the line primitive, the first two virtual points represent // the beginning and the end of the segment to be drawn. x1=coordSys.mapX(virtualPoint[0].x,virtualPoint[0].y); y1=coordSys.mapY(virtualPoint[0].x,virtualPoint[0].y); x2=coordSys.mapX(virtualPoint[1].x,virtualPoint[1].y); y2=coordSys.mapY(virtualPoint[1].x,virtualPoint[1].y); // We store the coordinates in an ordered way in order to ease // the determination of the clip rectangle. if (x1>x2) { xa=x2; xb=x1; } else { xa=x1; xb=x2; } if (y1>y2) { ya=y2; yb=y1; } else { ya=y1; yb=y2; } // Calculate the width of the stroke in pixel. It should not // make our lines disappear, even at very small zoom ratios. // So we put a limit D_MIN. w = (float)(Globals.lineWidth*coordSys.getXMagnitude()); if (w<D_MIN) w=D_MIN; // Calculate the square of the length in pixel. length2=(xa-xb)*(xa-xb)+(ya-yb)*(ya-yb); arrows = arrowData.atLeastOneArrow(); // This correction solves bug #3101041 (old SF code) // We do need to apply a correction to the clip calculation // rectangle if necessary to take into account the arrow heads if (arrows) { int h=arrowData.prepareCoordinateMapping(coordSys); xa -= Math.abs(h); ya -= Math.abs(h); xb += Math.abs(h); yb += Math.abs(h); } xbpap1=xb-xa+1; ybpap1=yb-ya+1; } // This is a trick. We skip drawing the line if it is too short. if(length2>2) { if(!g.hitClip(xa,ya, xbpap1,ybpap1)) return; g.applyStroke(w, dashStyle); // Eventually, we draw the arrows at the extremes. if (arrows) { if (arrowData.isArrowStart()) { arrowData.drawArrow(g,x1,y1,x2,y2); } if (arrowData.isArrowEnd()) { arrowData.drawArrow(g,x2,y2,x1,y1); } } g.drawLine(x1,y1,x2,y2); } } /** Parse a token array and store the graphic data for a given primitive Obviously, that routine should be called *after* having recognized that the called primitive is correct. That routine also sets the current layer. @param tokens the tokens to be processed. tokens[0] should be the command of the actual primitive. @param N the number of tokens present in the array. @throws IOException if the arguments are incorrect or the primitive is invalid. */ public void parseTokens(String[] tokens, int N) throws IOException { changed=true; // assert it is the correct primitive if (tokens[0].equals("LI")) { // Line if (N<5) { IOException E=new IOException("bad arguments on LI"); throw E; } // Load the points in the virtual points associated to the // current primitive. int x1 = virtualPoint[0].x=Integer.parseInt(tokens[1]); int y1 = virtualPoint[0].y=Integer.parseInt(tokens[2]); virtualPoint[1].x=Integer.parseInt(tokens[3]); virtualPoint[1].y=Integer.parseInt(tokens[4]); virtualPoint[getNameVirtualPointNumber()].x=x1+5; virtualPoint[getNameVirtualPointNumber()].y=y1+5; virtualPoint[getValueVirtualPointNumber()].x=x1+5; virtualPoint[getValueVirtualPointNumber()].y=y1+10; if(N>5) parseLayer(tokens[5]); // FidoCadJ extensions if(N>6 && tokens[6].equals("FCJ")) { int i=arrowData.parseTokens(tokens, 7); dashStyle = checkDashStyle(Integer.parseInt(tokens[i])); } } else { IOException E=new IOException("LI: Invalid primitive:"+tokens[0]+ " programming error?"); throw E; } } /** Gets the distance (in primitive's coordinates space) between a given point and the primitive. When it is reasonable, the behaviour can be binary (polygons, ovals...). In other cases (lines, points), it can be proportional. @param px the x coordinate of the given point. @param py the y coordinate of the given point. @return the distance in logical units. */ public int getDistanceToPoint(int px, int py) { // Here we check if the given point lies inside the text areas if(checkText(px, py)) return 0; return GeometricDistances.pointToSegment( virtualPoint[0].x,virtualPoint[0].y, virtualPoint[1].x,virtualPoint[1].y, px,py); } /** Obtain a string command descripion of the primitive. @param extensions true if FidoCadJ extensions to the old FidoCAD format should be active. @return the FIDOCAD command line. */ public String toString(boolean extensions) { // A single point line without anything is not worth converting. if (name.length()==0 && value.length()==0 && virtualPoint[0].x==virtualPoint[1].x && virtualPoint[0].y==virtualPoint[1].y) { return ""; } String s= "LI "+virtualPoint[0].x+" "+virtualPoint[0].y+" "+ +virtualPoint[1].x+" "+virtualPoint[1].y+" "+ getLayer()+"\n"; if(extensions && (arrowData.atLeastOneArrow() || dashStyle>0 || name!=null && name.length()!=0) || value!=null && value.length()!=0) { String text = "0"; // We take into account that there may be some text associated // to that primitive. if (hasName() || hasValue()) text = "1"; s+="FCJ "+arrowData.createArrowTokens()+" "+dashStyle+ " "+text+"\n"; } // The false is needed since saveText should not write the FCJ tag. s+=saveText(false); return s; } /** Export the primitive on a vector graphic format. @param exp the export interface to employ. @param cs the coordinate mapping to employ. @throws IOException if a problem occurs, such as it is impossible to write on the output file. */ public void export(ExportInterface exp, MapCoordinates cs) throws IOException { exportText(exp, cs, -1); exp.exportLine(cs.mapX(virtualPoint[0].x,virtualPoint[0].y), cs.mapY(virtualPoint[0].x,virtualPoint[0].y), cs.mapX(virtualPoint[1].x,virtualPoint[1].y), cs.mapY(virtualPoint[1].x,virtualPoint[1].y), getLayer(), arrowData.isArrowStart(), arrowData.isArrowEnd(), arrowData.getArrowStyle(), (int)(arrowData.getArrowLength()*cs.getXMagnitude()), (int)(arrowData.getArrowHalfWidth()*cs.getXMagnitude()), dashStyle, Globals.lineWidth*cs.getXMagnitude()); } /** Get the number of the virtual point associated to the Name property @return the number of the virtual point associated to the Name property */ public int getNameVirtualPointNumber() { return 2; } /** Get the number of the virtual point associated to the Value property @return the number of the virtual point associated to the Value property */ public int getValueVirtualPointNumber() { return 3; } }