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.*; import net.sourceforge.fidocadj.graphic.nil.*; /** Class to handle the advanced text 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-2014 by Davide Bucci </pre> @author Davide Bucci */ public final class PrimitiveAdvText extends GraphicPrimitive { private String txt; private int six; private int siy; private int sty; private int o; private String fontName; private boolean recalcSize; // Text style patterns static final int TEXT_BOLD=1; static final int TEXT_MIRRORED=4; static final int TEXT_ITALIC=2; // Maximum and minimum size allowed for the text. static final int MAXSIZE=2000; static final int MINSIZE=1; // A text is defined by one point. static final int N_POINTS=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 xaSCI; private int yaSCI; private int orientationSCI; private int hSCI, thSCI, wSCI; private int[] xpSCI, ypSCI; private boolean mirror; private int orientation; private int h, th, w; private double ymagnitude; private boolean coordmirroring; private int x1, y1, xa, ya, qq; private double xyfactor, si, co; private boolean needsStretching; /** Gets the number of control points used. @return the number of points used by the primitive. */ public int getControlPointNumber() { return N_POINTS; } /** Standard "empty" constructor. */ public PrimitiveAdvText() { super(); six=3; siy=4; o=0; txt=""; fontName = Globals.defaultTextFont; virtualPoint = new PointG[N_POINTS]; for(int i=0;i<N_POINTS;++i) virtualPoint[i]=new PointG(); changed=true; recalcSize=true; } /** Complete constructor. @param x the x position of the control point of the text. @param y the y position of the control point of the text. @param sx the x size of the font. @param sy the y size of the font. @param fn font name. @param or the orientation of the text. @param st the style of the text. @param t the text to be used. @param l the layer to be used. */ public PrimitiveAdvText(int x, int y, int sx, int sy, String fn, int or, int st, String t, int l) { this(); virtualPoint[0]=new PointG(x,y); six=sx; siy=sy; sty=st; txt=t; o=or; fontName=fn; setLayer(l); changed=true; recalcSize=true; } /** 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; /* For this: http://sourceforge.net/tracker/?func=detail&aid=2908420&group_id= 274886&atid=1167997 we are now checking if the text is "" before printing it. */ if(txt.length()==0) return; changed=true; ymagnitude=coordSys.getYMagnitude(); coordmirroring=coordSys.getMirror(); if(changed) { changed=false; mirror=false; recalcSize =true; /* in the simple text primitive, the the virtual point represents the position of the text to be drawn. */ x1=virtualPoint[0].x; y1=virtualPoint[0].y; xa=coordSys.mapX(x1,y1); ya=coordSys.mapY(x1,y1); /* siy is the font horizontal size in mils (1/1000 of an inch). 1 typographical point is 1/72 of an inch. */ g.setFont(fontName, six*12*coordSys.getYMagnitude()/7+.5, (sty & TEXT_ITALIC)!=0, (sty & TEXT_BOLD)!=0); orientation=o; mirror=false; if((sty & TEXT_MIRRORED)!=0){ mirror=!mirror; orientation=-orientation; } if (six==0 || siy==0) { siy=10; six=7; } orientation-=coordSys.getOrientation()*90; if(coordmirroring){ mirror=!mirror; orientation=-orientation; } // Determination of the size of the text string. h = g.getFontAscent(); th = h+g.getFontDescent(); w = g.getStringWidth(txt); xyfactor=1.0; needsStretching = false; if(siy/six != 10/7){ // Create a transformation for the font. xyfactor=(double)siy/(double)six*22.0/40.0; needsStretching = true; } if(orientation==0) { if (mirror){ coordSys.trackPoint(xa-w,ya); coordSys.trackPoint(xa,ya+(int)(th*xyfactor)); } else { coordSys.trackPoint(xa+w,ya); coordSys.trackPoint(xa,ya+(int)(h*xyfactor)); } } else { if(mirror){ si=Math.sin(Math.toRadians(-orientation)); co=Math.cos(Math.toRadians(-orientation)); } else { si=Math.sin(Math.toRadians(orientation)); co=Math.cos(Math.toRadians(orientation)); } // Calculate the bounding box. double bbx1=xa; double bby1=ya; double bbx2=xa+th*si; double bby2=ya+th*co*xyfactor; double bbx3=xa+w*co+th*si; double bby3=ya+(th*co-w*si)*xyfactor; double bbx4=xa+w*co; double bby4=ya-w*si*xyfactor; if(mirror) { bbx2=xa-th*si; bbx3=xa-w*co-th*si; bbx4=xa-w*co; } coordSys.trackPoint((int)bbx1,(int)bby1); coordSys.trackPoint((int)bbx2,(int)bby2); coordSys.trackPoint((int)bbx3,(int)bby3); coordSys.trackPoint((int)bbx4,(int)bby4); } qq=(int)(ya/xyfactor); } g.drawAdvText(xyfactor, xa, ya, qq, h, w, h, needsStretching, orientation, mirror, txt); } /** 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 a problem occurs. */ public void parseTokens(String[] tokens, int N) throws IOException { // assert it is the correct primitive changed=true; recalcSize = true; if (tokens[0].equals("TY")) { // Text (advanced) if (N<9) { IOException E=new IOException("bad arguments on TY"); throw E; } virtualPoint[0].x=Integer.parseInt(tokens[1]); virtualPoint[0].y=Integer.parseInt(tokens[2]); // We may accept non-integer data in the future. siy=(int)Math.round(Double.parseDouble(tokens[3])); six=(int)Math.round(Double.parseDouble(tokens[4])); checkSizes(); o=Integer.parseInt(tokens[5]); sty=Integer.parseInt(tokens[6]); parseLayer(tokens[7]); int j=8; StringBuffer txtb=new StringBuffer(); if(tokens[8].equals("*")) { fontName = Globals.defaultTextFont; } else { fontName = tokens[8].replaceAll("\\+\\+"," "); } /* siy is the font horizontal size in mils (1/1000 of an inch). 1 typographical point is 1/72 of an inch. */ while(j<N-1){ txtb.append(tokens[++j]); if (j<N-1) txtb.append(" "); } txt=txtb.toString(); } else if (tokens[0].equals("TE")) { // Text (simple) if (N<4) { IOException E=new IOException("bad arguments on TE"); throw E; } virtualPoint[0].x=Integer.parseInt(tokens[1]); virtualPoint[0].y=Integer.parseInt(tokens[2]); // Default sizes and styles six=3; siy=4; o=0; sty=0; int j=2; txt=""; while(j<N-1) txt+=tokens[++j]+" "; // In the original simple text primitive, the layer was not // handled. Here we just suppose it is always 0. parseLayer("0"); } else { IOException E=new IOException("Invalid primitive:"+ " programming error?"); throw E; } } /** Check and correct if necessary the text size range. */ public void checkSizes() { // Safety checks! if(siy<MINSIZE) siy=MINSIZE; if(six<MINSIZE) six=MINSIZE; if(siy>MAXSIZE) siy=MAXSIZE; if(six>MAXSIZE) six=MAXSIZE; } /** 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) { // This calculation takes a lot of time, since we need to obtain the // size of the font used, calculate the area which is active for the // mouse and so on. For this reason, we make it only when necessary, // by exploiting exactly the same principle of the optimized drawing // routines. if (changed||recalcSize) { if(changed) { GraphicsNull gSCI = new GraphicsNull(); gSCI.setFont(fontName, (int)(six*12.0/7.0+.5), (sty & TEXT_ITALIC)!=0, (sty & TEXT_BOLD)!=0); hSCI = gSCI.getFontAscent(); thSCI = hSCI+gSCI.getFontDescent(); wSCI = gSCI.getStringWidth(txt); } else { hSCI =(int)(h/ymagnitude); thSCI=(int)(th/ymagnitude); wSCI=(int)(w/ymagnitude); } // recalcSize is set to true when the draw method detects that the // graphical appearance of the text should be recalculated. recalcSize = false; xaSCI=virtualPoint[0].x; yaSCI=virtualPoint[0].y; orientationSCI=o; if(siy/six != 10/7){ hSCI=(int)Math.round(hSCI*((double)siy*22.0/40.0/(double)six)); thSCI=(int)Math.round((double)thSCI*((double)siy* 22.0/40.0/(double)six)); } // TODO: the calculation fails when mirrored text or rotated is // included into a mirrored or rotated macro. // Corrections for the mirrored text. if((sty & TEXT_MIRRORED)!=0){ orientationSCI=-orientationSCI; wSCI=-wSCI; } if (coordmirroring) { //orientationSCI=-orientationSCI; wSCI=-wSCI; } // If there is a tilt of the text, we calculate the four corner // of the tilted text area and we put them in a polygon. if(orientationSCI!=0){ double si=Math.sin(Math.toRadians(orientation)); double co=Math.cos(Math.toRadians(orientation)); xpSCI=new int[4]; ypSCI=new int[4]; xpSCI[0]=xaSCI; ypSCI[0]=yaSCI; xpSCI[1]=(int)(xaSCI+thSCI*si); ypSCI[1]=(int)(yaSCI+thSCI*co); xpSCI[2]=(int)(xaSCI+thSCI*si+wSCI*co); ypSCI[2]=(int)(yaSCI+thSCI*co-wSCI*si); xpSCI[3]=(int)(xaSCI+wSCI*co); ypSCI[3]=(int)(yaSCI-wSCI*si); } } if(orientationSCI==0) { if(GeometricDistances.pointInRectangle(Math.min(xaSCI, xaSCI+wSCI),yaSCI,Math.abs(wSCI),thSCI,px,py)) return 0; } else { if(GeometricDistances.pointInPolygon(xpSCI,ypSCI,4, px,py)) return 0; } // It is better not to obtain Integer.MAX_VALUE, but a value which // is very large yet smaller than Integer.MAX_VALUE. In fact, in some // cases, a test is done to see if a layer is present and this test // tries to see if the distance of a symbol is less than // Integer.MAX_VALUE, as this should be true when a symbol is present // and visible on the screen. return Integer.MAX_VALUE/2; } /** Get the control parameters of the given primitive. @return a vector of ParameterDescription containing each control parameter. The first parameters should always be the virtual points. */ public Vector<ParameterDescription> getControls() { Vector<ParameterDescription> v = new Vector<ParameterDescription>(10); int i; ParameterDescription pd = new ParameterDescription(); pd.parameter=txt; pd.description=Globals.messages.getString("ctrl_text"); v.add(pd); /* for (i=0;i<getControlPointNumber();++i) { pd = new ParameterDescription(); pd.parameter=virtualPoint[i]; pd.description=Globals.messages.getString("ctrl_control")+(i+1)+":"; v.add(pd); } */ pd = new ParameterDescription(); pd.parameter=new LayerInfo(getLayer()); pd.description=Globals.messages.getString("ctrl_layer"); v.add(pd); pd = new ParameterDescription(); pd.parameter=Integer.valueOf(six); pd.description=Globals.messages.getString("ctrl_xsize"); v.add(pd); pd = new ParameterDescription(); pd.parameter=Integer.valueOf(siy); pd.description=Globals.messages.getString("ctrl_ysize"); v.add(pd); pd = new ParameterDescription(); pd.parameter=Integer.valueOf(o); pd.description=Globals.messages.getString("ctrl_angle"); v.add(pd); pd = new ParameterDescription(); pd.parameter=Boolean.valueOf((sty & TEXT_MIRRORED)!=0); pd.description=Globals.messages.getString("ctrl_mirror"); v.add(pd); pd = new ParameterDescription(); pd.parameter=Boolean.valueOf((sty & TEXT_ITALIC)!=0); pd.description=Globals.messages.getString("ctrl_italic"); v.add(pd); pd = new ParameterDescription(); pd.parameter=Boolean.valueOf((sty & TEXT_BOLD)!=0); pd.description=Globals.messages.getString("ctrl_boldface"); v.add(pd); pd = new ParameterDescription(); pd.parameter=new FontG(fontName); pd.description=Globals.messages.getString("ctrl_font"); 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. @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=0; changed=true; recalcSize = true; ParameterDescription pd; pd=(ParameterDescription)v.get(i); ++i; // Check, just for sure... if (pd.parameter instanceof String) txt=(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 LayerInfo) setLayer(((LayerInfo)pd.parameter).getLayer()); else System.out.println("Warning: unexpected parameter!"); pd=(ParameterDescription)v.get(i); ++i; if (pd.parameter instanceof Integer) six=((Integer)pd.parameter).intValue(); else System.out.println("Warning: unexpected parameter!"+pd); pd=(ParameterDescription)v.get(i); ++i; if (pd.parameter instanceof Integer) siy=((Integer)pd.parameter).intValue(); else System.out.println("Warning: unexpected parameter!"+pd); pd=(ParameterDescription)v.get(i); ++i; if (pd.parameter instanceof Integer) o=((Integer)pd.parameter).intValue(); else System.out.println("Warning: unexpected parameter!"+pd); pd=(ParameterDescription)v.get(i++); if (pd.parameter instanceof Boolean) sty = ((Boolean)pd.parameter).booleanValue()? sty | TEXT_MIRRORED: sty & (~TEXT_MIRRORED); else System.out.println("Warning: unexpected parameter!"+pd); pd=(ParameterDescription)v.get(i++); if (pd.parameter instanceof Boolean) sty = ((Boolean)pd.parameter).booleanValue() ? sty | TEXT_ITALIC: sty & (~TEXT_ITALIC); else System.out.println("Warning: unexpected parameter!"+pd); pd=(ParameterDescription)v.get(i++); if (pd.parameter instanceof Boolean) sty= ((Boolean)pd.parameter).booleanValue() ? sty | TEXT_BOLD: sty & (~TEXT_BOLD); else System.out.println("Warning: unexpected parameter!"+pd); pd=(ParameterDescription)v.get(i++); if (pd.parameter instanceof FontG) fontName = ((FontG)pd.parameter).getFamily(); else System.out.println("Warning: unexpected parameter!"+pd); checkSizes(); return i; } /** Rotate the primitive. Here we just rotate 90° by 90° @param bc specify if the rotation should be done counterclockwise. @param ix the x coordinate of the rotation center @param iy the y coordinate of the rotation center */ public void rotatePrimitive(boolean bc, int ix, int iy) { boolean bCounterClockWise=bc; super.rotatePrimitive(bCounterClockWise, ix, iy); int po=o/90; if((sty & TEXT_MIRRORED)!=0) { bCounterClockWise=!bCounterClockWise; } if (bCounterClockWise) po=++po%4; else po=(po+3)%4; o=90*po; } /** Mirror the primitive. For the text, it is different than for the other primitives, since we just need to toggle the mirror flag. @param xPos is the symmetry axis */ public void mirrorPrimitive(int xPos) { super.mirrorPrimitive(xPos); sty ^= TEXT_MIRRORED; changed=true; recalcSize = true; } /** Obtain a string command descripion of the primitive. @param extensions true if the FidoCadJ extensions to the old FidoCAD formad should be taken into account. @return the FidoCad code corresponding to the primitive. */ public String toString(boolean extensions) { String subsFont; // The standard font is indicated with an asterisk if (fontName.equals(Globals.defaultTextFont)) { subsFont = "*"; } else { StringBuffer s=new StringBuffer(""); // All spaces are substituted with "++" in order to avoid problems // while parsing. for (int i=0; i<fontName.length(); ++i) { if(fontName.charAt(i)==' ') s.append("++"); else s.append(fontName.charAt(i)); } subsFont=s.toString(); } String s= "TY "+virtualPoint[0].x+" "+virtualPoint[0].y+" "+siy+" " +six+" "+o+" "+sty+" "+getLayer()+" "+subsFont+" "+txt+"\n"; return s; } /** Export the text 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 { int resulting_o=o-cs.getOrientation()*90; exp.exportAdvText (cs.mapX(virtualPoint[0].x,virtualPoint[0].y), cs.mapY(virtualPoint[0].x,virtualPoint[0].y), (int)Math.abs(cs.mapXr(six,six)-cs.mapXr(0,0)), (int)Math.abs(cs.mapYr(siy,siy)-cs.mapYr(0,0)), fontName, (sty & TEXT_BOLD)!=0, (sty & TEXT_MIRRORED)!=0, (sty & TEXT_ITALIC)!=0, resulting_o, getLayer(), txt); } /** 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 -1; } /** 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 -1; } }