package net.sourceforge.fidocadj.primitives; import java.io.*; import java.util.*; import net.sourceforge.fidocadj.dialogs.ParameterDescription; import net.sourceforge.fidocadj.dialogs.LayerInfo; import net.sourceforge.fidocadj.export.*; import net.sourceforge.fidocadj.geom.*; import net.sourceforge.fidocadj.globals.*; import net.sourceforge.fidocadj.layers.*; import net.sourceforge.fidocadj.graphic.*; /** GraphicPrimitive is an abstract class implementing the basic behaviour of a graphic primitive, which should be derived from it. <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 2008-2016 by Davide Bucci, phylum2 </pre> */ public abstract class GraphicPrimitive { // Tell that the dragging handle is invalid public static final int NO_DRAG=-1; // Tell that we are dragging the whole primitive public static final int DRAG_PRIMITIVE=-2; // Tell that we want to perform a selection in a rectangular area public static final int RECT_SELECTION=-3; // Maximum number of tokens private static final int MAX_TOKENS=120; // Indicates wether the primitive is selected or not public boolean selectedState; // Minimum width size of a line in pixel protected static final float D_MIN = 0.5f; // The layer public int layer; // The multiplication factor which is calculated for adjusting the screen // resolution, so that the size of handles or other important graphic stuff // is more or less the same. private float mult; // This is the screen resolution of the laptop on which I do most of the // development (DB), in dpi private static final int BASE_RESOLUTION=112; // Handle dimension. This is rescaled depending of the screen pixel // density. It is the size in pixel for a BASE_RESOLUTION dpi monitor. private static final int HANDLE_WIDTH=10; // Internal tolerance for snapping a double precision value to an integer. // Employed by roundIntelligently. private static final double INT_TOLERANCE=1E-5; // Array containing the points defining the primitive public PointG[] virtualPoint; // If changed is true, this means that the redraw operation should involve // an in-depth calculation of the primitive. Otherwise, a lot of // information is stored to speed up the redraw. protected boolean changed; private int macroFontSize; protected String macroFont; protected String name; protected String value; // Some caching data private LayerDesc currentLayer; private float alpha; private static float oldalpha=1.0f; private int old_layer=-1; // 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; // Text sizes in pixels private int h,th, w1, w2; // Text sizes in logical units. private int t_th, t_w1, t_w2; private int x2,y2,x3,y3; /* At first, non abstract methods */ /** Standard constructor. @param f the font to be employed for the associated text. @param size the size to be employed for the associated text. */ public GraphicPrimitive(String f, int size) { selectedState=false; layer=0; changed=true; name = ""; value = ""; mult = 1.0f; setMacroFontSize(size); macroFont=f; } /** Standard constructor. */ public GraphicPrimitive() { selectedState=false; layer=0; changed=true; name = ""; value = ""; mult = 1.0f; setMacroFontSize(4); macroFont=""; } /** Set the font to be used for name and value. @param f the font name. @param size the font size. */ public void setMacroFont(String f, int size) { macroFont = f; setMacroFontSize(size); changed=true; } /** Prepare the array of points for storing the different virtual points needed by the primitive. This method also prepares the name and value strings, as well as the font to be used. @param number if number is negative, obtain the number of points by using getControlPointNumber(); if it is positive, use the number of points given for the size of the array. @param font the font to be employed for the associated text. @param size the size to be employed for the associated text. */ public void initPrimitive(int number, String font, int size) { // Not very elegant. In fact, it would be better to use settings // present in DrawingModel, and not to have to use prefs here. setMacroFontSize(size); macroFont= font; name = ""; value = ""; int npoints=number; if (npoints<0) npoints = getControlPointNumber(); virtualPoint = new PointG[npoints]; for(int i=0;i<npoints;++i) virtualPoint[i]=new PointG(); } /** Get the font used for name and value @return the font name */ public String getMacroFont() { return macroFont; } /** Get the size of the macro font. @return the size of the macro font. */ public int getMacroFontSize() { return macroFontSize; } /** Set the size of the macro font. @param size the size of the macro font. */ public void setMacroFontSize(int size) { macroFontSize=size; // Silently correct a wrong size. This should never happen (the dialog // has a control, but avoids a wrong configuration to sneak somewhere // else. if(macroFontSize<=0) macroFontSize=1; } /** Check and correct if necessary the dashStyle number. @param dashStyle the style number to be checked. @return the checked dash style index. */ public int checkDashStyle(int dashStyle) { if(dashStyle>=Globals.dashNumber) return Globals.dashNumber-1; else if(dashStyle<0) return 0; return dashStyle; } /** Writes the macro name and value fields. This method uses heavily the caching system implemented via the precalculation of the sizes and positions. This means that the "changed" flag is tested, BUT NOT UPDATED, since this method should be one of the first to be called when a primitive implements its drawing. The primitive will HAVE TO update the "changed" flag accordingly to its needs, BEFORE calling drawText. @param g the graphic context. @param coordSys the current coordinate mapping system. @param layerV the vector containing the layers. @param drawOnlyLayer current layer which should be drawn (or -1). */ protected void drawText(GraphicsInterface g, MapCoordinates coordSys, Vector layerV, int drawOnlyLayer) { // If this method is not needed, exit immediately. if (value==null && name==null) return; if ("".equals(value) && "".equals(name)) return; if(drawOnlyLayer>=0 && drawOnlyLayer!=getLayer()) return; if(changed) { // Calculate the positions of the text lines x2=virtualPoint[getNameVirtualPointNumber()].x; y2=virtualPoint[getNameVirtualPointNumber()].y; x3=virtualPoint[getValueVirtualPointNumber()].x; y3=virtualPoint[getValueVirtualPointNumber()].y; xa=coordSys.mapX(x2,y2); ya=coordSys.mapY(x2,y2); xb=coordSys.mapX(x3,y3); yb=coordSys.mapY(x3,y3); // At first, write the name and the value fields in the given // positions g.setFont(macroFont, (int)(macroFontSize*12*coordSys.getYMagnitude()/7+.5)); h = g.getFontAscent(); th = h+g.getFontDescent(); if(name==null) w1=0; else w1 = g.getStringWidth(name); if(value==null) w2 = 0; else w2 = g.getStringWidth(value); // Calculates the size of the text in logical units. This is // useful for calculating wether the user has clicked inside a // text line (see getDistanceToPoint) t_w1 = (int)(w1/coordSys.getXMagnitude()); t_w2 = (int)(w2/coordSys.getXMagnitude()); t_th = (int)(th/coordSys.getYMagnitude()); // Track the points for calculating the drawing size coordSys.trackPoint(xa,ya); coordSys.trackPoint(xa+w1,ya+th); coordSys.trackPoint(xb,yb); coordSys.trackPoint(xb+w2, yb+th); } // If there is no need to draw the text, just exit. if(!g.hitClip(xa,ya, w1,th) && !g.hitClip(xb,yb, w2,th)) return; // This is useful and faster for small zooms if(th<Globals.textSizeLimit) { g.drawLine(xa,ya, xa+w1-1,ya); g.drawLine(xb,yb, xb+w2-1,yb); return; } if(!changed) g.setFont(macroFont, (int)(macroFontSize*12*coordSys.getYMagnitude()/7+.5)); /* The if's have been added thanks to this information: http://sourceforge.net/projects/fidocadj/forums/forum/997486 /topic/3474689?message=7798139 */ if (name!=null && name.length()!=0) { g.drawString(name,xa,ya+h); } if (value!=null && value.length()!=0) { g.drawString(value,xb,yb+h); } } /** Creates the text strings containing the name and value of the primitive. @param extensions if true, outputs the FCJ tag before the two TY commands. @return a string containing the commands. */ public String saveText(boolean extensions) { String subsFont; StringBuffer s2=new StringBuffer(); // Check if the font is default and in this case, just put an asterisk. if (macroFont.equals(Globals.defaultTextFont)) { subsFont = "*"; } else { StringBuffer s1=new StringBuffer(""); for (int i=0; i<macroFont.length(); ++i) { if(macroFont.charAt(i)==' ') s1.append("++"); else s1.append(macroFont.charAt(i)); } subsFont=s1.toString(); } // Write down the extensions only if needed if (name!=null && !"".equals(name) || value!=null && !"".equals(value)) { if(extensions) s2.append("FCJ\n"); s2.append("TY "); s2.append(virtualPoint[getNameVirtualPointNumber()].x); s2.append(" "); s2.append(virtualPoint[getNameVirtualPointNumber()].y); s2.append(" "); s2.append(macroFontSize*4/3); s2.append(" "); s2.append(macroFontSize); s2.append(" 0 0 "); s2.append(getLayer()); s2.append(" "); s2.append(subsFont); s2.append(" "); s2.append(name==null?"":name); s2.append("\n"); s2.append("TY "); s2.append(virtualPoint[getValueVirtualPointNumber()].x); s2.append(" "); s2.append(virtualPoint[getValueVirtualPointNumber()].y); s2.append(" "); s2.append(macroFontSize*4/3); s2.append(" "); s2.append(macroFontSize); s2.append(" 0 0 "); s2.append(getLayer()); s2.append(" "); s2.append(subsFont); s2.append(" "); s2.append(value==null?"":value); s2.append("\n"); } return s2.toString(); } /** Export the name and the value text lines associated to the primitive. This is done rather automatically by exploiting the export of the advanced text feature. It should be noted that the export is done only if necessary. @param exp the ExportInterface to be used. @param cs the coordinate mapping system to be used. @param drawOnlyLayer the layer to be drawn (or -1). @throws IOException if something wrong happens during the export, such as it is, or becomes impossible to write on the output file. */ public void exportText(ExportInterface exp, MapCoordinates cs, int drawOnlyLayer) throws IOException { double size= Math.abs(cs.mapXr(macroFontSize,macroFontSize)-cs.mapXr(0,0)); // Export the text associated to the name and value of the macro if(drawOnlyLayer<0 || drawOnlyLayer==getLayer()) { if(name!=null && !name.equals("")) exp.exportAdvText (cs.mapX( virtualPoint[getNameVirtualPointNumber()].x, virtualPoint[getNameVirtualPointNumber()].y), cs.mapY(virtualPoint[getNameVirtualPointNumber()].x, virtualPoint[getNameVirtualPointNumber()].y), (int)(size), (int)(size*12/7+.5), macroFont, false, false, false, 0, getLayer(), name); if(value!=null && !value.equals("")) exp.exportAdvText (cs.mapX( virtualPoint[getValueVirtualPointNumber()].x, virtualPoint[getValueVirtualPointNumber()].y), cs.mapY( virtualPoint[getValueVirtualPointNumber()].x, virtualPoint[getValueVirtualPointNumber()].y), (int)size, (int)(size*12/7+.5), macroFont, false, false, false, 0, getLayer(), value); } } /** Check if the given point (in logical units) lies inside one of the two text lines associated to the primitive. @param px the x coordinates of the given point. @param py the y coordinates of the given point. @return true if the point is inside one of the two text lines. */ public boolean checkText(int px, int py) { return !"".equals(name) && GeometricDistances.pointInRectangle( virtualPoint[getNameVirtualPointNumber()].x, virtualPoint[getNameVirtualPointNumber()].y,t_w1,t_th,px,py) || !"".equals(value) && GeometricDistances.pointInRectangle( virtualPoint[getValueVirtualPointNumber()].x, virtualPoint[getValueVirtualPointNumber()].y,t_w2,t_th,px,py); } /** Reads the TY line describing the "value" field @param tokens the array of tokens to be parsed @param N the number of tokens to be parsed. @throws IOException if something goes wrong, for example there is an invalid primitive found at an incongruous place (probably a programming error). */ public void setValue(String[] tokens, int N) throws IOException { StringBuffer txtb=new StringBuffer(); int j=8; changed=true; if (tokens[0].equals("TY")) { // Text (advanced) if (N<9) { IOException E=new IOException("bad arguments on TY"); throw E; } virtualPoint[getValueVirtualPointNumber()].x= Integer.parseInt(tokens[1]); virtualPoint[getValueVirtualPointNumber()].y= Integer.parseInt(tokens[2]); if(tokens[8].equals("*")) { macroFont = Globals.defaultTextFont; } else { macroFont = tokens[8].replaceAll("\\+\\+"," "); } // Adding the following line should fix bug #3522962 setMacroFontSize(Integer.parseInt(tokens[4])); while(j<N-1){ txtb.append(tokens[++j]); if (j<N-1) txtb.append(" "); } value=txtb.toString(); } else { IOException E=new IOException("Invalid primitive: "+tokens[0]+ " programming error?"); throw E; } } /** Reads the TY line describing the "name" field @param tokens the array of tokens to be parsed @param N the number of tokens to be parsed. @throws IOException if something goes wrong, for example there is an invalid primitive found at an incongruous place (probably a programming error). */ public void setName(String[] tokens, int N) throws IOException { StringBuffer txtb=new StringBuffer(); int j=8; changed=true; if (tokens[0].equals("TY")) { // Text (advanced) if (N<9) { IOException E=new IOException("bad arguments on TY"); throw E; } virtualPoint[getNameVirtualPointNumber()].x= Integer.parseInt(tokens[1]); virtualPoint[getNameVirtualPointNumber()].y= Integer.parseInt(tokens[2]); while(j<N-1){ txtb.append(tokens[++j]); if (j<N-1) txtb.append(" "); } name=txtb.toString(); } else { IOException E=new IOException("Invalid primitive:"+tokens[0]+ " programming error?"); throw E; } } /** Specifies that the current primitive has been modified or not. If it is true, during the redraw all parameters should be calulated from scratch. @param c the wanted changed state. */ public void setChanged(boolean c) { changed=c; } /** Get the first control point of the primitive @return the coordinates of the first control point of the object. */ public PointG getFirstPoint() { return virtualPoint[0]; } /** Move the primitive. @param dx the relative x displacement (logical units) @param dy the relative y displacement (logical units) */ public void movePrimitive(int dx, int dy) { int a, n=getControlPointNumber(); for(a=0; a<n; ++a) { virtualPoint[a].x+=dx; virtualPoint[a].y+=dy; } changed=true; } /** Mirror the primitive. Adapted from Lorenzo Lutti's original code. @param xPos is the symmetry axis */ public void mirrorPrimitive(int xPos) { int a, n=getControlPointNumber(); int xtmp; for(a=0; a<n; ++a) { xtmp = virtualPoint[a].x; virtualPoint[a].x = 2*xPos - xtmp; } changed=true; } /** Rotate the primitive. Adapted from Lorenzo Lutti's original code. @param bCounterClockWise specify if the rotation should be done counterclockwise. @param ix the x coordinate of the center of rotation @param iy the y coordinate of the center of rotation */ public void rotatePrimitive(boolean bCounterClockWise, int ix, int iy) { int b, m=getControlPointNumber(); PointG ptTmp=new PointG(); PointG pt=new PointG(); pt.x=ix; pt.y=iy; for(b=0; b<m; ++b) { ptTmp.x = virtualPoint[b].x; ptTmp.y = virtualPoint[b].y; if(bCounterClockWise) { virtualPoint[b].x = pt.x + ptTmp.y-pt.y; virtualPoint[b].y = pt.y - (ptTmp.x-pt.x); // NOPMD } else { virtualPoint[b].x = pt.x - (ptTmp.y-pt.y); // NOPMD virtualPoint[b].y = pt.y + ptTmp.x-pt.x; } } changed=true; } /** Specifies that only the given layer should be drawn. This is in practice useful only for macros, since they have an internal layer structure. @param i the layer to be used. */ public void setDrawOnlyLayer (int i) { // Normally, this does nothing, except for macros. } /** Returns true if the primitive contains the specified layer. @param l the index of the layer to check. @return true or false, if the specified layer is contained in the primitive. */ public boolean containsLayer(int l) { return l==layer; } /** Obtains the maximum layer which is contained by this primitive. It should redefined for macros, since they can contain more than one layer. The standard implementation returns the layer of the primitive, since this is the only one which is used. @return the maximum value of the layer contained in the primitive. */ public int getMaxLayer() { return layer; } /** Set the primitive as selected. @param s the new state. */ final public void setSelected(boolean s) { selectedState=s; //changed=true; }; /** Get the selection state of the primitive. @return true if the primitive is selected, false otherwise. */ final public boolean getSelected() { return selectedState; } /** Get the layer of the current primitive. @return the layer number. */ public final int getLayer() { return layer; } /** Parse the current string and interpret it as a layer indication. If this is correct, the layer is saved in the current primitive. @param token the token which corresponds to the layer. */ public void parseLayer(String token) { int l; try { l=Integer.parseInt(token); } catch (NumberFormatException E) { // We are unable to get the layer. Just suppose it's zero. l=0; } // We do check if everything is OK. if (l<0 || l>=LayerDesc.MAX_LAYERS) layer=0; else layer=l; changed=true; } /** Set the layer of the current primitive. A quick check is done. @param l the desired layer. */ final public void setLayer(int l) { if (l<0 || l>=LayerDesc.MAX_LAYERS) layer=0; else layer=l; changed=true; } /** Treat the current layer. In particular, select the corresponding color in the actual graphic context. If the primitive is selected, select the corrisponding color. This is a speed sensitive context. @param g the graphic context used for the drawing. @param layerV a LayerDesc vector with the descriptions of the layers being used. @return true if the layer is visible, false otherwise. */ protected final boolean selectLayer(GraphicsInterface g, Vector layerV) { // At first, we see if we need to retrieve the current layer. // It is important to check also the changed flag, since if not we // would now show changes apported to the layer being drawn when it is // modified. if(old_layer != layer || changed) { if(layer>=layerV.size()) layer=layerV.size()-1; currentLayer= (LayerDesc)layerV.get(layer); old_layer = layer; } // If the layer is not visible, we just exit, returning false. This // will made the caller not to draw the graphical element. if (!currentLayer.isVisible) { return false; } if(selectedState) { // We change the color for selected objects g.activateSelectColor(currentLayer); } else { if(g.getColor()!=currentLayer.getColor() || oldalpha!=alpha) { g.setColor(currentLayer.getColor()); alpha=currentLayer.getAlpha(); oldalpha = alpha; g.setAlpha(alpha); } } return true; } /** Draw the handles for the current primitive. @param g the graphic context to be used. @param cs the coordinate mapping used. */ public void drawHandles(GraphicsInterface g, MapCoordinates cs) { int xa; int ya; g.setColor(g.getColor().red()); g.applyStroke(2.0f,0); // Calculation of the reasonable multiplication factor. mult=g.getScreenDensity()/BASE_RESOLUTION; int size_x=(int)Math.round(mult*HANDLE_WIDTH); int size_y=(int)Math.round(mult*HANDLE_WIDTH); for(int i=0;i<getControlPointNumber();++i) { if (!testIfValidHandle(i)) continue; xa=cs.mapX(virtualPoint[i].x,virtualPoint[i].y); ya=cs.mapY(virtualPoint[i].x,virtualPoint[i].y); if(!g.hitClip(xa-size_x/2,ya-size_y/2, size_x,size_y)) continue; // A handle is a small red rectangle g.fillRect(xa-size_x/2,ya-size_y/2, size_x,size_y); } } /** Tells if the pointer is on an handle. The handles associated to the name and value strings are not considered if they are not defined. @param cs the coordinate mapping used. @param px the x (screen) coordinate of the pointer. @param py the y (screen) coordinate of the pointer. @return NO_DRAG if the pointer is not on an handle, or the index of the selected handle. */ public int onHandle(MapCoordinates cs, int px, int py) { int xa; int ya; int increase = 5; int hw2=(int)Math.round(mult*HANDLE_WIDTH/2); int hl2=(int)Math.round(mult*HANDLE_WIDTH/2); for(int i=0;i<getControlPointNumber();++i) { if (!testIfValidHandle(i)) continue; xa=cs.mapX(virtualPoint[i].x,virtualPoint[i].y); ya=cs.mapY(virtualPoint[i].x,virtualPoint[i].y); // Recognize if we have clicked on a handle. Basically, we check // if the point lies inside the rectangle given by the handle. if(GeometricDistances.pointInRectangle( xa-hw2-(int)Math.round(mult*increase), ya-hl2-(int)Math.round(mult*increase), (int)Math.round(mult*(HANDLE_WIDTH+2*increase)), (int)Math.round(mult*(HANDLE_WIDTH+2*increase)), px,py)) return i; } return NO_DRAG; } /** Select the primitive if one of its virtual point is in the specified rectangular region (given in logical coordinates). @param px the x coordinate of the top left point. @param py the y coordinate of the top left point. @param w the width of the region @param h the height of the region @return true if at least a primitive has been selected */ public boolean selectRect(int px, int py, int w, int h) { int xa; int ya; for(int i=0;i<getControlPointNumber();++i) { if (!testIfValidHandle(i)) continue; xa=virtualPoint[i].x; ya=virtualPoint[i].y; if(px<=xa && xa<px+w && py<=ya&& ya< py+h) { setSelected(true); return true; } } return false; } /** Function to determine if the name field is set @return true if the name field is set */ public boolean hasName() { return name!=null && name.length()!=0; } /** Function to determine if the value field is set @return true if the value field is set */ public boolean hasValue() { return value!=null && value.length()!=0; } /** Determines whether the handle specified is valid or is disabled. Are disabled in particular the handles associated to the name and value strings when they are not defined. @return true if the handle is active */ protected boolean testIfValidHandle(int i) { if (i==getNameVirtualPointNumber()) { if (name==null) return false; if(name.length()==0) return false; } if (i==getValueVirtualPointNumber()) { if(value==null) return false; if(value.length()==0) return false; } return true; } /** Get the control parameters of the given primitive. Each primitive should probably overload this version. We give here a very general implementation, allowing to change only virtual points. @return a vector of ParameterDescription containing each control parameter. The first parameters should always be the name and the value fields, followed by the layer. */ public Vector<ParameterDescription> getControls() { int i; Vector<ParameterDescription> v = new Vector<ParameterDescription>(10); ParameterDescription pd = new ParameterDescription(); pd.parameter=(name==null?"":name); pd.description=Globals.messages.getString("ctrl_name"); pd.isExtension = true; v.add(pd); pd = new ParameterDescription(); pd.parameter=(value==null?"":value); pd.description=Globals.messages.getString("ctrl_value"); pd.isExtension = true; v.add(pd); pd = new ParameterDescription(); pd.parameter=new LayerInfo(layer); pd.description=Globals.messages.getString("ctrl_layer"); v.add(pd); return v; } /** Set the control parameters of the given primitive. Each primitive should probably overload this version. We give here a very general implementation, allowing to change only virtual points. 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 index of the next parameter which remains to be read after this function ends. */ public int setControls(Vector<ParameterDescription> v) { int i=0; ParameterDescription pd; changed=true; pd=(ParameterDescription)v.get(i); ++i; // Check, just for sure... if (pd.parameter instanceof String) name=((String)pd.parameter); else System.out.println("Warning: unexpected parameter!"+pd); pd=(ParameterDescription)v.get(i); ++i; // Check, just for sure... if (pd.parameter instanceof String) value=((String)pd.parameter); else System.out.println("Warning: unexpected parameter!"+pd); pd = (ParameterDescription)v.get(i); // Check, just for sure... if (pd.parameter instanceof LayerInfo) layer=((LayerInfo)pd.parameter).getLayer(); else System.out.println("Warning: unexpected parameter! (layer)"); return ++i; } /** This function should be redefined if the graphic primitive needs holes. This implies that the redraw strategy should include a final pass to be sure that the holes are drawn correctly. Override this function if the primitive needs holes. The standard implementation just returns false. @return true if there are elements in the drawing which need holes. */ public boolean needsHoles() { return false; } /** Specify whether during the drawing phase the primitive should draw only the pads. This is useful only for the PrimitiveMacro and PrimitivePCBPad subclasses. @param t the wanted state. */ public void setDrawOnlyPads(boolean t) { // Does nothing, except for macros and pcbpads. } /** 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 LayerDesc the layer description. */ public abstract void draw(GraphicsInterface g, MapCoordinates coordSys, Vector LayerDesc); /** 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 correct layer. An IOException is thrown if there is an error. @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 something goes wrong. */ public abstract void parseTokens(String[] tokens, int N) throws IOException; /** 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 to point in logical coordinates. */ public abstract int getDistanceToPoint(int px, int py); /** Gets the number of control points used. @return the number of points used by the primitive */ public abstract int getControlPointNumber(); /** Obtain a string command descripion of the primitive. @param extensions produce a string eventually containing FidoCadJ extensions over the original FidoCad format. @return the FIDOCAD command line. */ public abstract String toString(boolean extensions); /** Each graphic primitive should call the appropriate exporting method of the export interface specified. @param exp the export interface that should be used. @param cs the actual coordinate mapping. @throws IOException if an error occurs, for example because it becomes impossible to access to the files being written. */ public abstract void export(ExportInterface exp, MapCoordinates cs) throws IOException; /** 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 abstract int getNameVirtualPointNumber(); /** 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 abstract int getValueVirtualPointNumber(); /** Get the size of the current element. @return the size. */ public DimensionG getSize() { GraphicPrimitive p = this; int qx = 0; int qy = 0; for (int i = 0; i < p.getControlPointNumber(); i++) { if (i == p.getNameVirtualPointNumber() || i == p.getValueVirtualPointNumber()) { continue; } for (int j = i + 1; j < p.getControlPointNumber(); j++) { if (j == p.getNameVirtualPointNumber() || j == p.getValueVirtualPointNumber()) { continue; } qx = Math.abs(p.virtualPoint[i].x - p.virtualPoint[j].x); qy = Math.abs(p.virtualPoint[i].y - p.virtualPoint[j].y); } } return new DimensionG(qx,qy); } /** Get the minimum x and y values of all control points of the element. @return the minimum x and y coordinates. */ public PointG getPosition() { GraphicPrimitive p = this; int qx = Integer.MAX_VALUE; int qy = Integer.MAX_VALUE; for (int i = 0; i < p.getControlPointNumber(); i++) { if (i == p.getNameVirtualPointNumber() || i == p.getValueVirtualPointNumber()) continue; if (p.virtualPoint[i].x<qx) qx = p.virtualPoint[i].x; if (p.virtualPoint[i].y<qy) qy = p.virtualPoint[i].y; } return new PointG(qx,qy); } /** Check wether we are very close to an integer value. In this case, the output will be done as an integer. This improves backward compatibility in cases where the fractional part is not needed. The output code is also marginally more compact. For example, roundIntelligently(1.00) produces "1" whereas roundIntelligently(1.23) produces "1.23". @param v the value to be rounded. @return a string containing the rounded value. */ public StringBuffer roundIntelligently(double v) { StringBuffer sb; if(Math.abs(v-Math.round(v))<INT_TOLERANCE) { int w=(int)Math.round(v); sb = new StringBuffer(""+w); } else { sb = new StringBuffer(""+v); } return sb; } }