/** @file SymbolPoint.java * * @author marco corvi * @date dec 2012 * * @brief TopoDroid drawing: point symbol * -------------------------------------------------------- * Copyright This sowftare is distributed under GPL-3.0 or later * See the file COPYING. * -------------------------------------------------------- */ package com.topodroid.DistoX; import java.io.File; import java.io.FileReader; import java.io.FileInputStream; import java.io.InputStreamReader; import java.io.BufferedReader; import java.io.StringWriter; import java.io.PrintWriter; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Locale; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.graphics.Matrix; // to log error import android.util.Log; class SymbolPoint extends Symbol { static final float dxfScale = 0.05f; static final float csxScale = 5.00f; static final float csxdxfScale = csxScale * dxfScale; public Paint mPaint; public Path mPath; public Path mOrigPath; public String mName; public String mDxf; public String mSvg; boolean mHasText; // whether the point has a text boolean mOrientable; double mOrientation; // orientation [degrees] // SymbolPointBasic mPoint1; // basic point // boolean hasText() { return mHasText; } // double orientation() { return mOrientation; } @Override public boolean isOrientable() { return mOrientable; } // @Override public boolean isEnabled() { return mEnabled; } // @Override public void setEnabled( boolean enabled ) { mEnabled = enabled; } // @Override public void toggleEnabled() { mEnabled = ! mEnabled; } @Override public boolean setAngle( float angle ) // degrees { if ( ! mOrientable ) return false; float a = angle - (float)mOrientation; if ( Math.abs(a) > 1 ) { rotateGradP( a ); return true; } return false; } @Override public int getAngle() { return (int)mOrientation; } // degrees @Override public String getName( ) { return mName; } @Override public String getThName( ) { return mThName; } String getDxf( ) { return mDxf; } boolean hasThName( String th_name ) { return ( th_name.equals( mThName ) ); } @Override public Path getPath( ) { return mPath; } Path getOrigPath( ) { return mOrigPath; } @Override public Paint getPaint( ) { return mPaint; } SymbolPoint( String pathname, String fname, String locale, String iso ) { super( null, fname ); mOrientable = false; mHasText = false; mOrientation = 0.0; readFile( pathname, locale, iso ); } SymbolPoint( String n1, String tn1, String fname, int c1, String path, boolean orientable ) { super( tn1, fname ); mName = n1; mDxf = null; makePaint( c1, Paint.Style.STROKE ); // FIXME style if ( path != null ) { makePath( path ); } else { makePath( ); } mOrigPath = new Path( mPath ); mOrientable = orientable; mHasText = false; mOrientation = 0.0; } SymbolPoint( String n1, String tn1, String fname, int c1, String path, boolean orientable, boolean has_text ) { super( tn1, fname ); // FIXME fname mName = n1; mDxf = null; makePaint( c1, Paint.Style.STROKE ); //FIXME style if ( path != null ) { makePath( path ); } else { makePath( ); } mOrigPath = new Path( mPath ); mOrientable = orientable; mHasText = has_text; mOrientation = 0.0; } void rotateGradP( double a ) { if ( mOrientable ) { mOrientation += a; if ( mOrientation > 360.0 ) mOrientation -= 360.0; if ( mOrientation < 0.0 ) mOrientation += 360.0; Matrix m = new Matrix(); m.postRotate( (float)(a) ); mPath.transform( m ); } } void resetOrientation() { if ( mOrientable && mOrientation != 0.0 ) { Matrix m = new Matrix(); m.postRotate( (float)(-mOrientation) ); mPath.transform( m ); mOrientation = 0.0; } } /** create a symbol reading it from a file * The file syntax is * symbol point * name NAME * th_name THERION_NAME * has_text yes | NO * orientation yes | NO * color 0xHHHHHH_COLOR * style fill | STROKE * path * MULTILINE_PATH_STRING * endpath * endsymbol */ void readFile( String pathname, String locale, String iso ) { // Log.v( TopoDroidApp.TAG, "SymbolPoint::readFile " + pathname + " locale " + locale ); String name = null; String th_name = null; int color = 0; Paint.Style style = Paint.Style.STROKE; String path = null; int cnt = 0; try { // FileReader fr = new FileReader( pathname ); FileInputStream fr = new FileInputStream( pathname ); BufferedReader br = new BufferedReader( new InputStreamReader( fr, iso ) ); String line; line = br.readLine(); while ( line != null ) { line.trim(); String[] vals = line.split(" "); int s = vals.length; for (int k=0; k<s; ++k ) { if ( vals[k].startsWith( "#" ) ) break; if ( vals[k].equals("symbol") ) { name = null; th_name = null; color = 0x00000000; path = null; } else if ( vals[k].equals("name") || vals[k].equals(locale) ) { ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { // name = (new String( vals[k].getBytes(iso) )).replace("_", " "); name = vals[k].replace("_", " "); // Log.v( TopoDroidApp.TAG, "set name " + name ); } } else if ( vals[k].equals("th_name") ) { ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { th_name = vals[k]; } } else if ( vals[k].equals("orientation") ) { if ( cnt == 0 ) { ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { mOrientable = ( vals[k].equals("yes") || vals[k].equals("1") ); } } } else if ( vals[k].equals("has_text") ) { if ( cnt == 0 ) { ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { mHasText = ( vals[k].equals("yes") || vals[k].equals("1") ); } } } else if ( vals[k].equals("style") ) { if ( cnt == 0 ) { ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { if ( vals[k].equals("fill") ) { // style = Paint.Style.FILL; // } else if ( vals[k].equals("fill-stroke") ) { style = Paint.Style.FILL_AND_STROKE; } } } } else if ( vals[k].equals("color") ) { ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { color = Integer.decode( vals[k] ); color |= 0xff000000; } } else if ( vals[k].equals("csurvey") ) { try { ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { mCsxLayer = Integer.parseInt( vals[k] ); } ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { mCsxType = Integer.parseInt( vals[k] ); } ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { mCsxCategory = Integer.parseInt( vals[k] ); } } catch ( NumberFormatException e ) { TDLog.Error( pathname + " parse csurvey error: " + line ); } } else if ( vals[k].equals("path") ) { path = br.readLine(); if ( path != null ) { while ( ( line = br.readLine() ) != null ) { if ( line.startsWith( "endpath" ) ) break; path = path + " " + line; } } } else if ( vals[k].equals("endsymbol") ) { if ( name == null ) { TDLog.Error("NULL name " + pathname ); } else if ( th_name == null ) { TDLog.Error("NULL th_name " + pathname ); } else if ( path == null ) { TDLog.Error("NULL path " + pathname); } else { if ( cnt == 0 ) { mName = name; mThName = th_name; makePaint( color, style ); makePath( path ); mOrigPath = new Path( mPath ); // mPoint1 = new SymbolPointBasic( name, th_name, fname, color, path ); // } else if ( cnt == 1 ) { // if ( mOrientable == true ) { // // ERROR point1 is orientable // } else { // mPoint2 = new SymbolPointBasic( name, th_name, fname, color, path ); // mOrientable = true; // } } else { // ERROR only two points max } ++ cnt; } } } line = br.readLine(); } } catch ( FileNotFoundException e ) { // FIXME } catch( IOException e ) { // FIXME } mOrientation = 0.0; // Log.v( TopoDroidApp.TAG, "SymbolPoint::readFile " + pathname + " csurvey " + mCsxLayer ); } private void makePath() { mPath = new Path(); mPath.moveTo(0,0); mDxf = " 0\nLINE\n 8\nPOINT\n" + " 100\nAcDbEntity\n 100\nAcDbLine\n" + " 10\n0.0\n 20\n0.0\n 30\n0.0\n" + " 11\n1.0\n 21\n0.0\n 31\n0.0\n"; // 1 mm long mCsx = ""; mSvg = ""; } /* Make the path from its stringn description * The path string description is composed of the following directives * - "moveTo X Y" * - "lineTo X Y" * - "cubicTo X1 Y1 X2 Y2 X Y" * - "addCircle X Y R" * - "arcTo X1 Y1 X2 Y2 A0 DA" */ private void makePath( String path ) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter( sw ); // DXF writer StringWriter sv1 = new StringWriter(); PrintWriter pv1 = new PrintWriter( sv1 ); // CSX path writer StringWriter sv2 = new StringWriter(); PrintWriter pv2 = new PrintWriter( sv2 ); // CSX circle writer StringWriter sv3 = new StringWriter(); PrintWriter pv3 = new PrintWriter( sv3 ); // SVG circle writer float x00=0, y00=0; // last drawn point1 float unit = TDSetting.mUnit; mPath = new Path(); String[] vals = path.split(" "); int s = vals.length; for ( int k = 0; k<s; ++k ) { float x0=0, y0=0, x1=0, y1=0, x2=0, y2=0; if ( "moveTo".equals( vals[k] ) ) { try { ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { x0 = Float.parseFloat( vals[k] ); } ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { y0 = Float.parseFloat( vals[k] ); mPath.moveTo( x0*unit, y0*unit ); x00 = x0 * dxfScale; y00 = y0 * dxfScale; pv1.format(Locale.US, "M %.2f %.2f ", x00*csxScale, y00*csxScale ); } } catch ( NumberFormatException e ) { TDLog.Error( path + " parse moveTo error" ); } } else if ( "lineTo".equals( vals[k] ) ) { try { ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { x0 = Float.parseFloat( vals[k] ); } ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { y0 = Float.parseFloat( vals[k] ); mPath.lineTo( x0*unit, y0*unit ); DrawingDxf.printString( pw, 0, "LINE" ); DrawingDxf.printString( pw, 8, "POINT" ); DrawingDxf.printAcDb( pw, -1, "AcDbEntity", "AcDbLine" ); DrawingDxf.printXYZ( pw, x00, -y00, 0.0f, 0 ); // prev point x00 = x0 * dxfScale; y00 = y0 * dxfScale; DrawingDxf.printXYZ( pw, x00, -y00, 0.0f, 1 ); // current point pv1.format(Locale.US, "L %.2f %.2f ", x00*csxScale, y00*csxScale ); } } catch ( NumberFormatException e ) { TDLog.Error( path + " parse lineTo error" ); } } else if ( "cubicTo".equals( vals[k] ) ) { // cp1x cp1y cp2x cp2y p2x p2y try { ++k; while ( k < s && vals[k].length() == 0 ) ++k; // CP1 if ( k < s ) { x0 = Float.parseFloat( vals[k] ); } ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { y0 = Float.parseFloat( vals[k] ); } ++k; while ( k < s && vals[k].length() == 0 ) ++k; // CP2 if ( k < s ) { x1 = Float.parseFloat( vals[k] ); } ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { y1 = Float.parseFloat( vals[k] ); } ++k; while ( k < s && vals[k].length() == 0 ) ++k; // P2 if ( k < s ) { x2 = Float.parseFloat( vals[k] ); } ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { y2 = Float.parseFloat( vals[k] ); mPath.cubicTo( x0*unit, y0*unit, x1*unit, y1*unit, x2*unit, y2*unit ); x00 /= dxfScale; y00 /= dxfScale; float dx = TDMath.abs( x00 - x2 ); float dy = TDMath.abs( y00 - y2 ); float a1 = 0.0f; float a2 = 0.0f; // float zz = 1.0f; float cx = 0.0f; float cy = 0.0f; float r = TDMath.abs( x00-x2 ); float e = TDMath.abs( r /(y00-y2) ); if ( x00 > x2 ) { if ( y00 > y2 ) { if ( Math.abs(x1-x00) > Math.abs(y1-y00) ) { // clockwise cx = x00; cy = y2; a1 = TDMath.M_PI; a2 = 3 * TDMath.M_PI2; } else { // counter-clockwise cx = x2; cy = y00; a1 = 0.0f; a2 = TDMath.M_PI2; } } else if ( y00 < y2 ) { if ( Math.abs(x1-x00) > Math.abs(y1-y00) ) { // counter-clockwise cx = x00; cy = y2; a1 = TDMath.M_PI2; a2 = TDMath.M_PI; } else { cx = x2; cy = y00; a1 = 3 * TDMath.M_PI2; a2 = 2 * TDMath.M_PI; } } else { // y00 == y2 : semicircle cx = ( x00 + x2 ) /2; cy = y00; r /= 2; e = 1.0f; if ( y1 > y00 ) { // down a1 = TDMath.M_PI; } else { a1 = 0.0f; } a2 = a1 + TDMath.M_PI; } } else if ( x00 < x2 ) { if ( y00 > y2 ) { if ( Math.abs(x1-x00) > Math.abs(y1-y00) ) { // counter-clockwise cx = x00; cy = y2; a1 = 3 * TDMath.M_PI2; a2 = 2 * TDMath.M_PI; } else { cx = x2; cy = y00; a1 = TDMath.M_PI2; a2 = TDMath.M_PI; } } else if ( y00 < y2 ) { if ( Math.abs(x1-x00) > Math.abs(y1-y00) ) { // counter-clockwise cx = x00; cy = y2; a1 = 0.0f; a2 = TDMath.M_PI2; } else { cx = x2; cy = y00; a1 = TDMath.M_PI; a2 = 3 * TDMath.M_PI2; } } else { // y00 == y2 : semicircle cx = ( x00 + x2 ) / 2; cy = y00; r /= 2; e = 1.0f; if ( y1 > y00 ) { // down a1 = TDMath.M_PI; } else { a1 = 0.0f; } a2 = a1 + TDMath.M_PI; } } else { // x00 == x2 : semicircle cx = x00; cy = ( y00 + y2 ) / 2; r = TDMath.abs( y00-y2 ) / 2; e = 1.0f; if ( y00 > y2 ) { if ( x1 < x00 ) { // left a1 = TDMath.M_PI2; } else { a1 = 3 * TDMath.M_PI2; } } else { if ( x1 < x00 ) { a1 = 3 * TDMath.M_PI2; } else { a1 = TDMath.M_PI2; } } a2 = a1 + TDMath.M_PI; } // Log.v(TopoDroidApp.TAG, mName + " cubic " + x00 + " " + y00 + " " + x0 + " " + y0 + " " + x1 + " " + y1 + " " + x2 + " " + y2 ); // Log.v(TopoDroidApp.TAG, mName + " " + cx + " " + cy + " R " + r + " E " + e + " angles " + a1 + " " + a2 ); // DrawingDxf.printString( pw, 0, "ELLIPSE" ); // DrawingDxf.printString( pw, 8, "POINT" ); // DrawingDxf.printAcDb( pw, -1, "AcDbEntity", "AcDbEllipse" ); // pw.printf(Locale.US, // " 10\n%.2f\n 20\n%.2f\n 30\n%.2f\n 11\n%.2f\n 21\n%.2f\n 31\n%.2f\n 40\n%.2f\n 41\n%.2f\n 42\n%.2f\n", // cx*dxfScale, -cy*dxfScale, 0.0f, // CENTER // r*dxfScale, 0.0, 0.0f, // ENDPOINT OF MAJOR AXIS - CENTER // e, // RATIO MINOR/MAJOR // a1, a2 ); // START and END ANGLES [rad] DrawingDxf.printString( pw, 0, "ARC" ); DrawingDxf.printString( pw, 8, "POINT" ); DrawingDxf.printAcDb( pw, -1, "AcDbEntity", "AcDbCircle" ); DrawingDxf.printXYZ( pw, cx*dxfScale, -cy*dxfScale, 0.0f, 0 ); DrawingDxf.printFloat( pw, 40, r*dxfScale ); DrawingDxf.printString( pw, 100, "AcDbArc" ); DrawingDxf.printFloat( pw, 50, a1 * TDMath.RAD2GRAD ); DrawingDxf.printFloat( pw, 51, a2 * TDMath.RAD2GRAD ); x00 = x2 * dxfScale; y00 = y2 * dxfScale; pv1.format(Locale.US, "C %.2f %.2f %.2f %.2f %.2f %.2f ", x0*csxdxfScale, y0*csxdxfScale, x1*csxdxfScale, y1*csxdxfScale, x2*csxdxfScale, y2*csxdxfScale ); // FIXME // DrawingDxf.printString( pw, 0, "LINE" ); // DrawingDxf.printString( pw, 8, "POINT" ); // DrawingDxf.printAcDb( pw, -1, "AcDbEntity", "AcDbLine" ); // DrawingDxf.printXYZ( pw, x00, -y00, 0.0f, 0 ); // x00 = x2 * dxfScale; // y00 = y2 * dxfScale; // DrawingDxf.printXYZ( pw, x00, -y00, 0.0f, 1 ); } } catch ( NumberFormatException e ) { TDLog.Error( path + " parse cubicTo error" ); } } else if ( "addCircle".equals( vals[k] ) ) { try { ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { x0 = Float.parseFloat( vals[k] ); } // center X coord ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { y0 = Float.parseFloat( vals[k] ); } // center Y coord ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { x1 = Float.parseFloat( vals[k] ); // radius mPath.addCircle( x0*unit, y0*unit, x1*unit, Path.Direction.CCW ); DrawingDxf.printString( pw, 0, "CIRCLE" ); DrawingDxf.printString( pw, 8, "POINT" ); DrawingDxf.printAcDb( pw, -1, "AcDbEntity", "AcDbCircle" ); DrawingDxf.printXYZ( pw, x0*dxfScale, -y0*dxfScale, 0.0f, 0 ); DrawingDxf.printFloat( pw, 40, x1*dxfScale ); pv2.format(Locale.US, "<circle cx="%.2f" cy="%.2f" r="%.2f" />", x0*csxdxfScale, y0*csxdxfScale, x1*csxdxfScale ); pv3.format(Locale.US, "<circle cx=\"%.2f\" cy=\"%.2f\" r=\"%.2f\" />", x0*csxdxfScale, y0*csxdxfScale, x1*csxdxfScale ); } } catch ( NumberFormatException e ) { TDLog.Error( path + " parse circle error" ); } } else if ( "arcTo".equals( vals[k] ) ) { // (x0,y0) top-left corner of rect // (x1,y1) bottom-right corner of rect // x2 start-angle [degrees] // y2 sweep angle (clockwise) [degrees] // // (x0,y0) +-----=-----+ // | | | // |=====+=====| 0 angle (?) // | | | | sweep direction // +-----=-----+ V // try { ++k; while ( k < s && vals[k].length() == 0 ) ++k; // RECTANGLE first endpoint if ( k < s ) { x0 = Float.parseFloat( vals[k] ); } ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { y0 = Float.parseFloat( vals[k] ); } ++k; while ( k < s && vals[k].length() == 0 ) ++k; // RECTANGLE second endpoint if ( k < s ) { x1 = Float.parseFloat( vals[k] ); } ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { y1 = Float.parseFloat( vals[k] ); } ++k; while ( k < s && vals[k].length() == 0 ) ++k; // FROM, TT angles [deg] if ( k < s ) { x2 = Float.parseFloat( vals[k] ); } ++k; while ( k < s && vals[k].length() == 0 ) ++k; if ( k < s ) { y2 = Float.parseFloat( vals[k] ); mPath.arcTo( new RectF(x0*unit, y0*unit, x1*unit, y1*unit), x2, y2 ); // DrawingDxf.printString( pw, 0, "ELLIPSE" ); // DrawingDxf.printString( pw, 8, "POINT" ); // DrawingDxf.printAcDb(pw, -1, "AcDbEntity", AcDbEllipse" ); // pw.printf(Locale.US, // " 10\n%.2f\n 20\n%.2f\n 30\n%.2f\n 11\n%.2f\n 21\n%.2f\n 31\n%.2f\n 40\n%.2f\n 41\n%.2f\n 42\n%.2f\n", // (x0+x1)/2*dxfScale, -(y0+y1)/2*dxfScale, 0.0f, // CENTER // x1*dxfScale, -(y0+y1)/2*dxfScale, 0.0f, // ENDPOINT OF MAJOR AXIS // (y1-y0)/(x1-x0), // RATIO MINOR/MAJOR // x2*TDMath.GRAD2RAD, (x2+y2)*TDMath.GRAD2RAD ); // START and END PARAMS DrawingDxf.printString( pw, 0, "ARC" ); DrawingDxf.printString( pw, 8, "POINT" ); DrawingDxf.printAcDb(pw, -1, "AcDbEntity", "AcDbCircle" ); DrawingDxf.printXYZ( pw, (x0+x1)/2*dxfScale, -(y0+y1)/2*dxfScale, 0.0f, 0 ); // CENTER DrawingDxf.printFloat( pw, 40, x1*dxfScale ); // RADIUS DrawingDxf.printString( pw, 100, "AcDbArc" ); DrawingDxf.printFloat( pw, 50, x2 ); // ANGLES DrawingDxf.printFloat( pw, 51, x2+y2 ); float cx = (x1+x0)/2; float cy = (y1+y0)/2; float rx = (x1-x0)/2; float ry = (y1-y0)/2; float x0i = (cx + rx * TDMath.cosd( x2 ) )* dxfScale; // initial point float y0i = (cy + ry * TDMath.sind( x2 ) )* dxfScale; x00 = (cx + rx * TDMath.cosd( x2+y2 ) )* dxfScale; // final point y00 = (cy + ry * TDMath.sind( x2+y2 ) )* dxfScale; // mode to (x00, y00) pv1.format(Locale.US, "M %.2f %.2f ", x0i*csxScale, y0i*csxScale ); pv1.format(Locale.US, "A %.2f %.2f 0 1 %.2f %.2f ", rx*csxdxfScale, ry*csxdxfScale, x00*csxScale, y00*csxScale ); } } catch ( NumberFormatException e ) { TDLog.Error( path + " parse arcTo error" ); } } } mDxf = sw.getBuffer().toString(); mCsx = "<path d="" + sv1.getBuffer().toString() + "" />" + sv2.getBuffer().toString(); mSvg = "<path d=\"" + sv1.getBuffer().toString() + "\"/> " + sv3.getBuffer().toString(); } private void makePaint( int color, Paint.Style style ) { mPaint = new Paint(); mPaint.setDither(true); mPaint.setColor( color ); mPaint.setStyle( style ); mPaint.setStrokeJoin(Paint.Join.ROUND); mPaint.setStrokeCap(Paint.Cap.ROUND); mPaint.setStrokeWidth( 1 ); } }