/* Copyright (C) 2014-2016 A.Vontobel  
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * The license is in LICENCE-GPL.txt.
 * If not, see http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
 */

package net.sf.drawj2d.cmd;

import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.BasicStroke;
import java.awt.Stroke;
import java.awt.Font;
import java.awt.geom.*;
import java.math.BigDecimal;
import java.math.MathContext;
import com.udojava.evalex.Expression;
import java.awt.AlphaComposite;
import java.awt.FontFormatException;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.regex.PatternSyntaxException;
import java.util.Stack;
import java.util.EmptyStackException;
import javax.imageio.ImageIO;
import net.sf.drawj2d.UTF2asciiConverter;
import org.scilab.forge.jlatexmath.TeXConstants; 
import org.scilab.forge.jlatexmath.TeXFormula;
import org.scilab.forge.jlatexmath.TeXIcon;
import org.scilab.forge.jlatexmath.Box;

/**
 *
 * @author av
 */
public class API {
    
    static final int SOLID = 1;
    static final int DASHED = 2;
    static final int DOTTED = 3;
    static final int DASHDOTTED = 4;
    
    private final Graphics2D g;
    private Point2D currentpos = new Point2D.Double(0,0);
    UTF2asciiConverter UTFconverter = new UTF2asciiConverter();
    static final double inch = 25.4; // mm
    
    private final Stack blocks = new Stack();
    
    // Strichstärke
    private final float stdstroke = 0.50f; //mm
    float[] dashed = {2f, 1.5f};
    float[] dotted = {0f, 1.5f};
    float[] dashdotted = {2f, 1.5f, 0f, 1.5f};
    private float currentstrokesize = stdstroke;
    private int currentlinetype = SOLID;
        
    // Schrift    
    private final int schriftgrStd = 4; // Standartschriftgrösse mm
    private int schriftgr = schriftgrStd;
    private final String fontStd = "Serif"; // Standartschrift
    private final String texfont = "/org/scilab/forge/jlatexmath/fonts/latin/jlm_cmr10.ttf";
    private Font jarfont;
    private String font = fontStd;
    private double lastavailabletextwidth = -1;
    private boolean lastavailabletextjustified = false;
    private final double bzuh = 0.61; // Breite zur Höhe eines Buchstabens
    boolean horizFontOnly = false; // if backend (e.g. jtikz) does not support getFontMetrics or rotated fontt
    private final int digitsStd = 3;
    private int digits = digitsStd; // Stellen nach Komma
    private int angledigitsStd = 2;
    private int angledigits = angledigitsStd;
    private final int forcedigitsStd = 1;
    private int forcedigits = forcedigitsStd; // Stellen nach Komma
    
    /** Unit length, im mm */
    private double u = 1;
    /** Natural unit, mesured in mm .
      * required for image (otherwise image scale 1:100 can not be turned in natural units)*/
    private double naturalunit = 1;
    /** Force unit length, im mm */
    private double fu = 1;
    
    private final boolean debug = false;
    
    
    public API(Graphics2D g) {
        this.g = g;
        pen();
        font();
        // Test backend capabilities
        try {
            g.getFontMetrics().getStringBounds("Test", g);
        } catch (Exception e) { // Can happen if backend (e.g. jtikz) does not support getFontMetrics
            horizFontOnly = true;
            if (debug) System.out.println("Warning: Backend has limited font capabilities.");
        }
    }
    
    /**
     * Set the line and font color.
     * @param color 
     */
    public void pen(Color color) {
        g.setColor(color);
    }
    
    /**
     * Set the line stroke width.
     * @param size 
     */
    public void pen(float size) {
        currentstrokesize = size;
        pen(currentlinetype);
    }
    
    /**
     * Set the line type
     * @param linetype DASHED, DOTTED, DASHDOTTED, SOLID
     */
    public void pen(int linetype) {
        boolean solidline;
        float[] dash = null; // "on, off, on, off"
        switch (linetype) {
            case DASHED:
                dash = dashed;
                solidline = false;
                break;
            case DOTTED:
                dash = dotted;
                solidline = false;
                break;
            case DASHDOTTED:
                dash = dashdotted;
                solidline = false;
                break;
            case SOLID:
                solidline = true;
                break;
            default:
                assert false;
                solidline = true;
        }
        
        final int cap = BasicStroke.CAP_ROUND;
        final int join = BasicStroke.JOIN_ROUND;
        final float miterlimit = 10f; // = new BasicStroke().getMiterLimit();
        final float dashphase = 0f;
        BasicStroke stroke;
        if (solidline) {
            stroke = new BasicStroke(currentstrokesize, cap, join);
        } else {
            stroke = new BasicStroke(currentstrokesize, cap, join, miterlimit, dash, dashphase);
        }
        g.setStroke(stroke);
        currentlinetype = linetype;
    }
    
    /**
     * Reset the pen to black solid line with standard stroke with.
     */
    public final void pen() {
        g.setColor(Color.BLACK);
        g.setStroke(new BasicStroke(stdstroke, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
        currentstrokesize = stdstroke;
        currentlinetype = SOLID;
    }
    
    /**
     * Set the opacity.
     * @param opacity 1.0f is opaque.
     */
    public final void opacity(float opacity) {
        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity));
    }
    
    /* 
    * Set the font.
    * @param font Serif, Sans_Serif, Monospaced, font name. If "" keep the current font.
    * @param style font style: Font.PLAIN, Font.BOLD or Font.ITALIC
    * @param size font height in mm. If 0 keep the current size.
    */
    public void font(String font, int style, int size) throws FontFormatException, IOException {
        // set current setting for missing arguments
        if (size == 0) size = schriftgr;
        if (font.equals("")) font = this.font;
        
        if (font.equalsIgnoreCase("tex")) {
            jarfont = Font.createFont(Font.TRUETYPE_FONT, this.getClass().getResourceAsStream(texfont)).deriveFont(style, size);
            g.setFont(jarfont);
        }
        else g.setFont(new java.awt.Font(font, style, size));
        this.font = font;
        schriftgr = size;
        if (debug) {
            if (debug) System.out.println("font        " + font);
            Font f = g.getFont();
            System.out.println("getFamily   " + f.getFamily());
            System.out.println("getFontName " + f.getFontName());
            System.out.println("getName     " + f.getName());
        }
    }
    
    public final void font() {
        g.setFont(new java.awt.Font(fontStd, Font.PLAIN, schriftgrStd));
        font = fontStd;
        schriftgr = schriftgrStd;
    }
    
    /** Set the scale.
     * The natural unit is assumed to be [mm] (has impact on image import).
     * @param mm The length on the sheet for one drawing unit.
     */
    public void unitlength(double mm) {
        u = mm;
        naturalunit = 1;
    }
    
    /** Set the scale and unit. Tne unit length on sheet is the product of both.
     * @param scale The length on the sheet for one drawing unit.
     * @param unit natural unit mesured in mm. E.g. for [m] set 1000 [mm]
     */
    public void unitlength(double scale, double unit) {
        u = scale * unit;
        naturalunit = unit;
    }
    
    /**
     * mm to unit length conversion.
     * @param mm in mm
     * @return length in equivalent unit size
     */
    public double mm(double mm) {
        return mm/u;
    }
    
    /**
     * Force unit length
     * @param mm length of force unit. Usually kN.
     * @param decdigits number of decimal decdigits (Kommastellen)
     */
    public void forceunitlength(double mm, int decdigits) {
        fu = mm;
        forcedigits = decdigits;
    }
    
    /**
     * force unit to unit length conversion.
     * @param kN in force unit. Usually kN.
     * @return length in equivalent unit size
     */
    public double fu(double kN) {
        return kN*fu/u;
    }
    
    /**
     * Evaluate a mathematical expression.
     * @param str expression
     * @return double value
     */
    public double expr(String str) {
        // use 17 digits precision (double precision uses 16 digits)
        MathContext mc = new MathContext(17);
        Expression expression = new Expression(str, mc);
        BigDecimal result = expression.eval();
        double val = result.doubleValue();
        return val;
    }
    
    /** Offset the origin (0/0) coordinate.
     * Offset is relative to previous origin position.
     * @param dX in drawing units
     * @param dY in drawing units
     */
    public void offset(double dX, double dY) {
        g.translate(dX*u, dY*u);
    }
    
    /** Rotate the coordinate axes. Use allowed only within blocks.
     * Rotation has its center at the origin (0/0).
     * The coursor remains, thus the coordinates of current position do change.
     * @param α in degrees
     * @return false if called from outside a block
     */
    public boolean rotate(double α) {
        if (blocks.size() < 1) {
            return false;
        }
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        double αrad = Math.toRadians(α);
        double cos = Math.cos(αrad);
        double sin = Math.sin(αrad);
        g.rotate(αrad);
        double x1 =  x0 * cos + y0 * sin;
        double y1 = -x0 * sin + y0 * cos;
        currentpos.setLocation(x1, y1);
        return true;
    }
    
    /** Mirror vertically. Use allowed only within blocks.
     * The y-axis points upwards. Labels will also be mirrored.
     * The coursor remains, thus the coordinates of current position do change.
     * @return false if called from outside a block
     */
    public boolean flip() {
        if (blocks.size() < 1) {
            return false;
        }
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        g.scale(1, -1);
        currentpos.setLocation(x0, -y0);
        return true;
    }
    
    /** Scale the axes. Use allowed only within blocks.
     * The coursor remains, thus the coordinates of current position do change.
     * @param sx scale factor for x-axis
     * @param sy scale factor for y-axis
     * @return false if called from outside a block
     */
    public boolean scale(double sx, double sy) {
        if (blocks.size() < 1) {
            return false;
        }
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        g.scale(sx, sy);
        currentpos.setLocation(x0 / sx, y0 / sy);
        return true;
    }
    
    /** Create a block.
     * Puts the current AffineTransform and the current position onto a stack.
     * The block has its own coordinate space, (0,0) at the current position.
     * @return new size of the block stack
     */
    public int block() {
        clBlock bl = new clBlock(g.getTransform(), currentpos);
        blocks.push(bl);
        offset(currentpos.getX(), currentpos.getY());
        currentpos.setLocation(0, 0);
        return blocks.size();
    }
    
    /** End a block.
     * Resets the AffineTransform and the current position.
     * Takes both from the block stack.
     * @return remaining size of the block stack. -1 if stack was empty!
     */
    public int endblock() {
        clBlock bl;
        try {
            bl = blocks.pop();
        } catch (EmptyStackException e) {
            return -1;
        }
        AffineTransform savedAT = bl.getAffineTransform();
        g.setTransform(savedAT);
        currentpos.setLocation(bl.getPos());
        return blocks.size();
    }
    
    /** Move the cursor to a new position.
     * 
     * @param x1
     * @param y1 
     */
    public void moveto(double x1, double y1) {
        currentpos.setLocation(x1, y1);
    }
    
    /** Move the cursor to a new position.
     * 
     * @param xy array {x1, xy}
     */
    public void moveto(double[] xy) {
        currentpos.setLocation(xy[0], xy[1]);
    }
    
    /**
     * Move the cursor to a new x-coordinate while keeping the y-coordinate.
     * 
     * @param x1
     */
    public void movetox(double x1) {
        currentpos.setLocation(x1, currentpos.getY());
    }
    
    /**
     * Move the cursor to a new y-coordinate while keeping the x-coordinate.
     * 
     * @param y1
     */
    public void movetoy(double y1) {
        currentpos.setLocation(currentpos.getX(), y1);
    }
    
    /** Tells where the cursor is.
     * @return the current position of the cursor */
    public double[] here() {
        double[] here = new double[2];
        here[0] = currentpos.getX();
        here[1] = currentpos.getY();
        return here;
    }
    
    /**
     * Move the cursor relative to the actual position.
     * 
     * @param dx
     * @param dy 
     */
    public void moverel(double dx, double dy) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        currentpos.setLocation(x0 + dx, y0 + dy);
    }
    
    /**
     * Move the cursor in polar direction relative to the actual position.
     * 
     * @param dL
     * @param α direction from actual position, in degrees, clock-wise 
     */
    public void movepolar(double dL, double α) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        double dx = dL * Math.cos(Math.toRadians(α));
        double dy = dL * Math.sin(Math.toRadians(α));
        currentpos.setLocation(x0 + dx, y0 + dy);
    }
    
    /** 
     * Draw a line from the actual cursor position to the given coordinate.
     * Sets the cursor to the given coordinate.
     * @param x1
     * @param y1 
     */
    public void lineto(double x1, double y1) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        g.draw(new Line2D.Double(x0*u, y0*u, x1*u, y1*u));
        currentpos.setLocation(x1, y1);
    }
    
    /**
     * Draw a line from the actual cursor position in x-direction.
     * New x-coordinate while keeping the y-coordinate.
     * @param x1
     */
    public void linetox(double x1) {
        lineto(x1, currentpos.getY());
    }
    
    /**
     * Draw a line from the actual cursor position in y-direction.
     * New y-coordinate while keeping the x-coordinate.
     * @param y1
     */
    public void linetoy(double y1) {
        lineto(currentpos.getX(), y1);
    }
    
    /**
     * Draw a line from the first coordinate to the second one.
     * Sets the cursor to the end of the line (second coordinate x1/y1).
     * @param x0
     * @param y0
     * @param x1
     * @param y1 
     */
    public void line(double x0, double y0, double x1, double y1) {
        g.draw(new Line2D.Double(x0*u, y0*u, x1*u, y1*u));
        currentpos.setLocation(x1, y1);
    }
    
    /**
     * Draw a line from the actual cursor position to the given relative position.
     * Sets the cursor to the end of the line.
     * @param dx
     * @param dy 
     */
    public void linerel(double dx, double dy) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        double x1 = x0 + dx;
        double y1 = y0 + dy;
        g.draw(new Line2D.Double(x0*u, y0*u, x1*u, y1*u));
        currentpos.setLocation(x1, y1);
    }
    
    /**
     * Draw a line given its length and direction relative to the actual position.
     * Sets the cursor to the end of the line.
     * 
     * @param dL
     * @param α direction from actual position, in degrees, clock-wise 
     */
    public void linepolar(double dL, double α) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        double x1 = x0 + dL * Math.cos(Math.toRadians(α));
        double y1 = y0 + dL * Math.sin(Math.toRadians(α));
        g.draw(new Line2D.Double(x0*u, y0*u, x1*u, y1*u));
        currentpos.setLocation(x1, y1);
    }
    
    /**
     * Draw a line given its length and direction.
     * The middle of the line is set at the cursor position. The cursor does not move.
     * @param L
     * @param α
     */
    public void linemid(double L, double α) {
        double dx = L/2d * Math.cos(Math.toRadians(α));
        double dy = L/2d * Math.sin(Math.toRadians(α));
        double x0 = currentpos.getX() - dx;
        double y0 = currentpos.getY() - dy;
        double x1 = currentpos.getX() + dx;
        double y1 = currentpos.getY() + dy;
        g.draw(new Line2D.Double(x0*u, y0*u, x1*u, y1*u));
        // currentpos remains.
    }
    
    /**
     * Mark a point.
     */
    public void point() {
        double durchmesser = 1.4*currentstrokesize;
        g.fill(kreis(currentpos, durchmesser));
    }
    
    /**
     * Mark a point at the given coordinate.
     * Sets the cursor to the point.
     * @param x1
     * @param y1 
     */
    public void point(double x1, double y1) {
        moveto(x1, y1);
        point();
    }
    
    /** 
     * Draw an arrow from the actual cursor position to the given coordinate.
     * Sets the cursor to the given coordinate.
     * @param x1
     * @param y1 
     */
    public void arrowto(double x1, double y1) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        pfeil(x0,y0,x1,y1);
        currentpos.setLocation(x1, y1);
    }
    
    /**
     * Draw an arrow from the actual cursor position to the given relative position.
     * Sets the cursor to the arrow head.
     * @param dx
     * @param dy 
     */
    public void arrowrel(double dx, double dy) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        double x1 = x0 + dx;
        double y1 = y0 + dy;
        pfeil(x0,y0,x1,y1);
        currentpos.setLocation(x1, y1);
    }
    
    /**
     * Draw an arrow from the first coordinate to the second one.
     * Sets the cursor to the arrow head (second coordinate x1/y1).
     * @param x0
     * @param y0
     * @param x1
     * @param y1 
     */
    public void arrow(double x0, double y0, double x1, double y1) {
        pfeil(x0,y0,x1,y1);
        currentpos.setLocation(x1, y1);
    }
    
    /** 
     * Draw a double headed arrow from the actual cursor position to the given coordinate.
     * Sets the cursor to the given coordinate.
     * @param x1
     * @param y1 
     */
    public void arrowsto(double x1, double y1) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        doppelpfeil(x0,y0,x1,y1, false);
        currentpos.setLocation(x1, y1);
    }
    
    /**
     * Draw a double headed arrow from the actual cursor position to the given relative position.
     * Sets the cursor to the new position.
     * @param dx
     * @param dy 
     */
    public void arrowsrel(double dx, double dy) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        double x1 = x0 + dx;
        double y1 = y0 + dy;
        doppelpfeil(x0,y0,x1,y1, false);
        currentpos.setLocation(x1, y1);
    }
    
    /**
     * Draw a double headed arrow from the first coordinate to the second one.
     * Sets the cursor to the second coordinate x1/y1.
     * @param x0
     * @param y0
     * @param x1
     * @param y1 
     */
    public void arrows(double x0, double y0, double x1, double y1) {
        doppelpfeil(x0,y0,x1,y1, false);
        currentpos.setLocation(x1, y1);
    }
    
    /**
     * Draw a force arrow (pointing away).
     * The label is its absolute value.
     * Sets the cursor to the arrow head.
     * @param Fx
     * @param Fy 
     * @param isTeX 
     * @see forceunitlength
     */
    public void force(double Fx, double Fy, boolean isTeX) {
        double F = Math.hypot(Fx, Fy);
        String lb = Fkt.nf(F, forcedigits);
        force(Fx, Fy, lb, isTeX);
    }
    
    /**
     * Draw a force arrow (pointing away).
     * Sets the cursor to the arrow head.
     * @param Fx
     * @param Fy
     * @param lb 
     * @param isTeX 
     * @see forceunitlength
     */
    public void force(double Fx, double Fy, String lb, boolean isTeX) {
        double F = Math.hypot(Fx, Fy);
        if (F > 0) {
            double x0 = currentpos.getX();
            double y0 = currentpos.getY();
            double x1 = x0 + Fx * fu / u;
            double y1 = y0 + Fy * fu / u;
            pfeil(x0,y0,x1,y1);
            if (isTeX) texlabelline(x0,y0,x1,y1, lb);
            else labelline(x0,y0,x1,y1, lb);
            currentpos.setLocation(x1, y1);
        }
    }
    
    /**
     * Draw a force arrow (pointing to the cursor).
     * The label is its absolute value.
     * @param Fx
     * @param Fy 
     * @param isTeX 
     * @see forceunitlength
     */
    public void force2(double Fx, double Fy, boolean isTeX) {
        double F = Math.hypot(Fx, Fy);
        String lb = Fkt.nf(F, forcedigits);
        force2(Fx, Fy, lb, isTeX);
    }
    
    /**
     * Draw a force arrow (pointing to the cursor).
     * @param Fx
     * @param Fy
     * @param lb 
     * @param isTeX 
     * @see forceunitlength
     */
    public void force2(double Fx, double Fy, String lb, boolean isTeX) {
        double F = Math.hypot(Fx, Fy);
        if (F > 0) {
            double x1 = currentpos.getX();
            double y1 = currentpos.getY();
            double x0 = x1 - Fx * fu / u;
            double y0 = y1 - Fy * fu / u;
            pfeil(x0, y0, x1,y1);
            if (isTeX) texlabelline(x0,y0,x1,y1, lb);
            else labelline(x0,y0,x1,y1, lb);
        }
    }
    
    /** 
     * Draw a circle.
     * @param radius 
     */
    public void circle(double radius) {
        g.draw(kreis(currentpos, 2.0*radius*u));
    }
    
    /** 
     * Draw a circle.
     * Sets the cursor to the center.
     * @param x1
     * @param y1
     * @param radius 
     */
    public void circle(double x1, double y1, double radius) {
        currentpos.setLocation(x1, y1);
        g.draw(kreis(currentpos, 2.0*radius*u));
    }
    
    /** 
     * Draw an ellipse.
     * @param radius1
     * @param radius2
     * @param angle1 Angle of direction 1 to x-axis
     */
    public void ellipse(double radius1, double radius2, double angle1) {
        double centerx = currentpos.getX() * u;
        double centery = currentpos.getY() * u;
        radius1 *= u;
        radius2 *= u;
        Ellipse2D.Double ellip = new Ellipse2D.Double(centerx - radius1, centery - radius2,
        2d*radius1, 2d*radius2);
        AffineTransform rotiert;
        double alphaRot = Math.toRadians(angle1); // Rotationswinkel
        // Rotieren und schieben
        rotiert = AffineTransform.getRotateInstance(alphaRot, centerx, centery);
        Graphics2D tempGraphics = (Graphics2D) g.create();
        tempGraphics.setStroke(g.getStroke());
        tempGraphics.transform(rotiert);
        tempGraphics.draw(ellip);
        // Rotation und Verschiebung zurückstellen
        tempGraphics.dispose();
    }
    
    /** 
     * Fill an ellipse.
     * @param radius1
     * @param radius2
     * @param angle1 Angle of direction 1 to x-axis
     */
    public void fillellipse(double radius1, double radius2, double angle1) {
        double centerx = currentpos.getX() * u;
        double centery = currentpos.getY() * u;
        radius1 *= u;
        radius2 *= u;
        Ellipse2D.Double ellip = new Ellipse2D.Double(centerx - radius1, centery - radius2,
        2d*radius1, 2d*radius2);
        AffineTransform rotiert;
        double alphaRot = Math.toRadians(angle1); // Rotationswinkel
        // Rotieren und schieben
        rotiert = AffineTransform.getRotateInstance(alphaRot, centerx, centery);
        Graphics2D tempGraphics = (Graphics2D) g.create();
        tempGraphics.setStroke(g.getStroke());
        tempGraphics.transform(rotiert);
        tempGraphics.fill(ellip);
        // Rotation und Verschiebung zurückstellen
        tempGraphics.dispose();
    }
    
    /**  
     * Draw an arc.
     * Zero angle is where the x-axis points to. Clock-wise, consistent with coord sys.
     * @param radius
     * @param startangle in degrees, clock-wise
     * @param endangle in degrees, clock-wise
     */
    public void arc(double radius, double startangle, double endangle) {
        double centerx = currentpos.getX() * u;
        double centery = currentpos.getY() * u;
        radius *= u;
        double extent = endangle - startangle;
        if (extent < 0) extent += 360;
        Rectangle2D.Double quadr = new Rectangle2D.Double(centerx - radius, centery - radius,
                2d * radius, 2d * radius);
        Arc2D.Double bogen = new Arc2D.Double(quadr, -endangle, extent, Arc2D.OPEN); // java2D works counter-clock-wise
        g.draw(bogen);
    }
    
    /** 
     * Draw an arc.
     * Zero angle is where the x-axis points to. Clock-wise, consistent with coord sys.
     * Sets the cursor to the center.
     * @param x1 center
     * @param y1 center
     * @param radius
     * @param startangle in degrees, clock-wise
     * @param endangle in degrees, clock-wise
     */
    public void arc(double x1, double y1, double radius, double startangle, double endangle) {
        currentpos.setLocation(x1, y1);
        arc(radius, startangle, endangle);
    }
    
    /**  
     * Fill a segment (arc).
     * Zero angle is where the x-axis points to. Clock-wise, consistent with coord sys.
     * @param radius
     * @param startangle in degrees, clock-wise
     * @param endangle in degrees, clock-wise
     */
    public void fillsegment(double radius, double startangle, double endangle) {
        double centerx = currentpos.getX() * u;
        double centery = currentpos.getY() * u;
        radius *= u;
        double extent = endangle - startangle;
        if (extent < 0) extent += 360;
        Rectangle2D.Double quadr = new Rectangle2D.Double(centerx - radius, centery - radius,
                2d * radius, 2d * radius);
        Arc2D.Double bogen = new Arc2D.Double(quadr, -endangle, extent, Arc2D.OPEN); // java2D works counter-clock-wise
        g.fill(bogen);
    }
    
    /**
     * Draw a parabola from the first coordinate to the third one. 
     * The second is the control point (intersection of tangets).
     * Sets the cursor to the end of the line (second coordinate x1/y1).
     * @param x0
     * @param y0
     * @param xCtrl Control point (intersection of tangents)
     * @param yCtrl Control point (intersection of tangents)
     * @param x1
     * @param y1 
     */
    public void quadcurve(double x0, double y0, double xCtrl, double yCtrl, double x1, double y1) {
        g.draw(new QuadCurve2D.Double(x0*u, y0*u, xCtrl*u, yCtrl*u, x1*u, y1*u));
        currentpos.setLocation(x1, y1);
    }
    
    /**
     * Draw a cubic parabola from the first coordinate to the last one. 
     * The second and third are the control points (on the tangets).
     * Sets the cursor to the end of the line (second coordinate x1/y1).
     * @param x0
     * @param y0
     * @param x0Ctrl Control point (on first tangent)
     * @param y0Ctrl Control point (on first tangent)
     * @param x1Ctrl Control point (on second tangent)
     * @param y1Ctrl Control point (on second tangent)
     * @param x1
     * @param y1 
     */
    public void cubiccurve(double x0, double y0, double x0Ctrl, double y0Ctrl, double x1Ctrl, double y1Ctrl, double x1, double y1) {
        g.draw(new CubicCurve2D.Double(x0*u, y0*u, x0Ctrl*u, y0Ctrl*u, x1Ctrl*u, y1Ctrl*u, x1*u, y1*u));
        currentpos.setLocation(x1, y1);
    }
    
    /**
     * Draw a rectangle.
     * @param dx
     * @param dy 
     */
    public void rect(double dx, double dy) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        double X0 = Math.min(x0, x0+dx);
        double Y0 = Math.min(y0, y0+dy);
        double dX = Math.abs(dx);
        double dY = Math.abs(dy);
        g.draw(new java.awt.Rectangle.Double(X0*u, Y0*u, dX*u, dY*u));
    }
    
    /**
     * Fill a rectangle.
     * @param dx
     * @param dy 
     */
    public void fillrect(double dx, double dy) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        double X0 = Math.min(x0, x0+dx);
        double Y0 = Math.min(y0, y0+dy);
        double dX = Math.abs(dx);
        double dY = Math.abs(dy);
        g.fill(new java.awt.Rectangle.Double(X0*u, Y0*u, dX*u, dY*u));
    }
    
    /**
     * Draw a rod.
     * Actually a rectangle defined by its axis (starting point and orientation), length and width.
     * Sets the cursor to the end point on the axis, L from the starting point.
     * @param L length
     * @param w width
     * @param α orientation, in degrees, clock-wise
     */
    public void rod(double L, double w, double α) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        double cos = Math.cos(Math.toRadians(α));
        double sin = Math.sin(Math.toRadians(α));
        double x1 = x0 + L * cos;
        double y1 = y0 + L * sin;
        double dx = w/2d * sin;
        double dy = -w/2d * cos;
        Path2D path = new Path2D.Double();
        path.moveTo((x0-dx)*u, (y0-dy)*u);
        path.lineTo((x0+dx)*u, (y0+dy)*u);
        path.lineTo((x1+dx)*u, (y1+dy)*u);
        path.lineTo((x1-dx)*u, (y1-dy)*u);
        path.closePath();
        g.draw(path);
        currentpos.setLocation(x1, y1);
    }
    
    /**
     * Fill a rod.
     * Actually a rectangle defined by its axis (starting point and orientation), length and width.
     * Sets the cursor to the end point on the axis, L from the starting point.
     * @param L length
     * @param w width
     * @param α orientation, in degrees, clock-wise
     */
    public void fillrod(double L, double w, double α) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        double cos = Math.cos(Math.toRadians(α));
        double sin = Math.sin(Math.toRadians(α));
        double x1 = x0 + L * cos;
        double y1 = y0 + L * sin;
        double dx = w/2d * sin;
        double dy = -w/2d * cos;
        Path2D path = new Path2D.Double();
        path.moveTo((x0-dx)*u, (y0-dy)*u);
        path.lineTo((x0+dx)*u, (y0+dy)*u);
        path.lineTo((x1+dx)*u, (y1+dy)*u);
        path.lineTo((x1-dx)*u, (y1-dy)*u);
        path.closePath();
        g.fill(path);
        currentpos.setLocation(x1, y1);
    }
    
    /**
     * Draw a polygon.
     * Sets the cursor to the first vertex x0/y0.
     * polygon A-B-C-D is equivalent to lines A-B-C-D-A
     * @param x x coordinates of polygon vertices
     * @param y y coordinates of polygon vertices
     */
    public void polygon(double[] x, double[] y) {
        assert x.length == y.length;
        assert x.length > 2;
        currentpos.setLocation(x[0], y[0]);
        Path2D path = new Path2D.Double();
        path.moveTo(x[0]*u, y[0]*u);
        for(int i = 1; i < x.length; ++i) {
            path.lineTo(x[i]*u, y[i]*u);
        }
        path.closePath();
        g.draw(path);
    }
    
    /**
     * Fill a polygon.
     * Sets the cursor to the first vertex x0/y0.
     * polygon A-B-C-D is equivalent to lines A-B-C-D-A
     * @param x x coordinates of polygon vertices
     * @param y y coordinates of polygon vertices
     */
    public void fillpolygon(double[] x, double[] y) {
        assert x.length == y.length;
        assert x.length > 2;
        currentpos.setLocation(x[0], y[0]);
        Path2D path = new Path2D.Double();
        path.moveTo(x[0]*u, y[0]*u);
        for(int i = 1; i < x.length; ++i) {
            path.lineTo(x[i]*u, y[i]*u);
        }
        path.closePath();
        g.fill(path);
    }
    
    /**
     * Fill a circle.
     * @param radius 
     */
    public void fillcircle(double radius) {
        g.fill(kreis(currentpos, 2.0*radius*u));
    }
    
    /**
     * Label the position of the cursor.
     * The text is placed at the upper right (NE).
     * @param label text
     */
    public void label(String label) {
        label(label, "NE");
    }
    
    /**
     * Label the position of the cursor.
     * The placement of the text is defined by relpos.
     * @param label text
     * @param relpos N, NW, W, SW, S, SE, E, NE, C, BL
     */
    public void label(String label, String relpos) {
        double xpix = currentpos.getX()*u;
        double ypix = currentpos.getY()*u;
        double w;
        double h;
        // Mavscript-yacas replaces unicode chars (e.g. ω, but every char above 127) 
        // by a ascii hexadecimal representation (e.g. escu03c9)
        // We want to reinsert the original unicode char.
        if (UTFconverter.containsUnicodeHexCharacters(label)) {
            if (debug) System.out.println("ConvertFrom " + label);
            label = UTFconverter.convert2UTF(label);
            if (debug) System.out.println("ConvertFrom " + label);
        }
        if (horizFontOnly) {
            w = label.length()*bzuh*schriftgr;
            h = schriftgr;
        }
        else {
            Rectangle2D fr = g.getFontMetrics().getStringBounds(label, g);
            w = fr.getWidth();
            h = fr.getHeight();
        }
        if (relpos.equals("N")) {xpix -= w/2d; ypix -= h/3d;}
        else if (relpos.equals("NW")) {xpix -= w+h/4d; ypix -= h/3d;}
        else if (relpos.equals("W")) {xpix -= w+h/4d; ypix += h/3d;}
        else if (relpos.equals("SW")) {xpix -= w+h/4d; ypix += h;}
        else if (relpos.equals("S")) {xpix -= w/2d; ypix += h;}
        else if (relpos.equals("SE")) {xpix += h/4d; ypix += h;}
        else if (relpos.equals("E")) {xpix += h/4d; ypix += h/3d;}
        else if (relpos.equals("NE")) {xpix += h/4d; ypix -= h/3d;} // default
        else if (relpos.equals("C")) {xpix -= w/2d; ypix += h/3d;}
        else if (relpos.equals("BL")) {} // baseline
        else {xpix += h/4d; ypix -= h/3d;} // default
        
        g.drawString(label, (float) xpix, (float) ypix);
    }
    
    /**
     * Label the position of the cursor using TeX input.
     * The text is placed at the upper right (NE).
     * @param latex text
     */
    public void texlabel(String latex) {
        texlabel(latex, "NE");
    }
    
    /**
     * Label the position of the cursor using TeX input.
     * The placement of the text is defined by relpos.
     * @param latex text
     * @param relpos N, NW, W, SW, S, SE, E, NE, C, BL
     */
    public void texlabel(String latex, String relpos) {
        double cx = currentpos.getX(); double cy = currentpos.getY();
        double xpix = currentpos.getX()*u;
        double ypix = currentpos.getY()*u;
        double w;
        double h;
        // Mavscript-yacas replaces unicode chars (e.g. ω, but every char above 127) 
        // by a ascii hexadecimal representation (e.g. escu03c9)
        // We want to reinsert the original unicode char.
        if (UTFconverter.containsUnicodeHexCharacters(latex)) {
            if (debug) System.out.println("ConvertFrom " + latex);
            latex = UTFconverter.convert2UTF(latex);
            if (debug) System.out.println("ConvertFrom " + latex);
        }
        
        TeXFormula formula = new TeXFormula(latex);
        formula.setColor(g.getColor());
        //formula.setDEBUG(true);
        TeXIcon icon = formula.createTeXIcon(TeXConstants.STYLE_TEXT, schriftgr, true);
        
        // metrics
        w = icon.getTrueIconWidth();
        h = icon.getTrueIconHeight();
        // minimal font height
        TeXIcon tempicon = (new TeXFormula("b")).createTeXIcon(TeXConstants.STYLE_TEXT, schriftgr, true);
        double hmin = tempicon.getTrueIconHeight();
        if (h < hmin) h = hmin;
        
        if (relpos.equals("N")) {xpix -= w/2d; ypix -= h/3d;}
        else if (relpos.equals("NW")) {xpix -= w+h/4d; ypix -= h/3d;}
        else if (relpos.equals("W")) {xpix -= w+h/4d; ypix += h/3d;}
        else if (relpos.equals("SW")) {xpix -= w+h/4d; ypix += h;}
        else if (relpos.equals("S")) {xpix -= w/2d; ypix += h;}
        else if (relpos.equals("SE")) {xpix += h/4d; ypix += h;}
        else if (relpos.equals("E")) {xpix += h/4d; ypix += h/3d;}
        else if (relpos.equals("NE")) {xpix += h/4d; ypix -= h/3d;} // default
        else if (relpos.equals("C")) {xpix -= w/2d; ypix += h/2d;}
        else if (relpos.equals("BL")) {} // baseline
        else {xpix += h/4d; ypix -= h/3d;} // default
        
        Graphics2D tempGraphics = (Graphics2D) g.create();
        tempGraphics.translate(xpix, ypix); // schieben
        
        // new settings
        tempGraphics.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING,
                            java.awt.RenderingHints.VALUE_ANTIALIAS_ON);
        tempGraphics.setRenderingHint(java.awt.RenderingHints.KEY_RENDERING,
                            java.awt.RenderingHints.VALUE_RENDER_QUALITY);
        tempGraphics.setRenderingHint(java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,
                            java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        tempGraphics.scale(schriftgr, schriftgr); // the point size

        // draw formula box
        Box box = icon.getBox();
        box.draw(tempGraphics, 0, 0);
        tempGraphics.dispose();
        
        currentpos.setLocation(cx, cy);
    }
    
    /**
     * Write some text, break lines at previously used width.
     * @param text text
     */
    public void text(String text) {
        text(text, lastavailabletextwidth, lastavailabletextjustified);
    }
    
    /**
     * Write some text, break lines.
     * @param text text
     * @param width available width in drawing units
     * @param justified whether justified text or left aligned
     */
    public void text(String text, double availablewidth, boolean justified) {
        if (availablewidth >= 0) {
            lastavailabletextwidth = availablewidth;
        } else {
            if (lastavailabletextwidth >= 0) {
                availablewidth = lastavailabletextwidth;
            } else { // initial value
                availablewidth = mm(150);
            }
        }
        lastavailabletextjustified = justified;
        double xpix = currentpos.getX()*u;
        double ypix = currentpos.getY()*u;
        double w;
        double h;
        // Mavscript-yacas replaces unicode chars (e.g. ω, but every char above 127) 
        // by a ascii hexadecimal representation (e.g. escu03c9)
        // We want to reinsert the original unicode char.
        if (UTFconverter.containsUnicodeHexCharacters(text)) {
            if (debug) System.out.println("ConvertFrom " + text);
            text = UTFconverter.convert2UTF(text);
            if (debug) System.out.println("ConvertFrom " + text);
        }
        if (horizFontOnly) {
            w = text.length()*bzuh*schriftgr;
            h = schriftgr;
        }
        else {
            Rectangle2D fr = g.getFontMetrics().getStringBounds(text, g);
            w = fr.getWidth();
            h = fr.getHeight();
        }
        if (debug) System.out.println(h);
        double hLF = Math.max(h, 1.2*schriftgr); // vertical LineFeed
        hLF = Math.ceil(hLF); // ideally between 1.2 and 1.45*schriftgr
        if (debug) System.out.println(hLF);
        // If text fits into one line
        if (w <= availablewidth*u) {
            g.drawString(text, (float) xpix, (float) ypix);
            currentpos.setLocation(currentpos.getX(), currentpos.getY() + hLF/u);
            return;
        }
        // Else, linebreaks required
        double wS = g.getFontMetrics().getStringBounds(" ", g).getWidth();
        if (debug) System.out.println(wS);
        String[] words;
        try {
            words = text.split("[ \\t\\u2000-\\u200a\\u205f]"); // breaking horizontal whitespace characters
        }
        catch (PatternSyntaxException e) {
            words = text.split("[ \\t]");
            System.err.println(e.getLocalizedMessage());
            System.err.println("Work around activated. JRE: " + Runtime.class.getPackage().getSpecificationVersion());
        }
        if (debug) for (String wi : words) System.out.println(wi);
        double[] widths = new double[words.length];
        for (int i = 0; i < words.length; i++) {
            widths[i] = g.getFontMetrics().getStringBounds(words[i], g).getWidth();
        }
        if (debug) for (double wi : widths) System.out.println(wi);
        int writtenwords = 0;
        double usedwith; int i;
        while (writtenwords < words.length) {
            usedwith = 0;
            i = 0;
            while (usedwith <= availablewidth*u) {
                i++;
                if (i > 1) usedwith += wS;
                if (writtenwords+i-1 >= words.length) break;
                else usedwith += widths[writtenwords+i-1];
            }
            if (justified && (writtenwords + i-1 < words.length)) { // the last line will be left aligned
                if (i == 1 || i == 2) { // one word only at a line, even if it is longer than available width
                    g.drawString(words[writtenwords], (float) xpix, (float) ypix); // write it anyway
                    writtenwords += 1;
                }
                else { // normal case
                    assert i > 2; // at least two words on a line
                    double textonlywidth = 0;
                    for (int j = 0; j < i-1; j++) {
                        textonlywidth += widths[writtenwords+j];
                    }
                    double space = (availablewidth*u - textonlywidth) / (i-2);
                    double xpixpos = xpix;
                    for (int j = 0; j < i-1; j++) {
                        g.drawString(words[writtenwords+j], (float) xpixpos, (float) ypix);
                        xpixpos += widths[writtenwords+j];
                        xpixpos += space;
                    }
                    writtenwords += (i-1);
                }
            }
            else {// left aligned text
                StringBuilder sb = new StringBuilder();
                if (i == 1) { // even one word is longer than available width
                    sb.append(words[writtenwords]); // write it anyway
                    writtenwords += 1;
                }
                else { // normal case
                    for (int j = 0; j < i-1; j++) {
                        if (j > 0)  sb.append(" ");
                        sb.append(words[writtenwords+j]);
                    }
                    writtenwords += (i-1);
                }
                g.drawString(sb.toString(), (float) xpix, (float) ypix);
            }
            ypix += hLF;
        }
        currentpos.setLocation(currentpos.getX(), ypix/u);
    }
    
    /**
     * Set number format for dimension lines
     * @param nb number of decimal digits
     */
    public void decdigits(int nb) {
        if (nb >= 0) digits = nb;
        else digits = digitsStd;
    }
    
    /**
     * Draw a dimension line (double headed arrow) from the cursor position 
     * to the given position  and label by its length (in drawing units).
     * Set cursor to the new position.
     * @param x1
     * @param y1 
     * @param isTeX 
     */
    public void dimlineto(double x1, double y1, boolean isTeX) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        dimline(x0,y0,x1,y1, isTeX);
    }
    
    /**
     * Draw a dimension line (double headed arrow) from the cursor position 
     * to the given position and label it.
     * Set cursor to the new position.
     * @param x1
     * @param y1
     * @param lb 
     * @param isTeX 
     */
    public void dimlineto(double x1, double y1, String lb, boolean isTeX) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        dimline(x0,y0,x1,y1, lb, isTeX);
    }
    
    /**
     * Draw a dimension line (double headed arrow) from the cursor position 
     * to a relative position and label by its length (in drawing units).
     * Set cursor to the new position.
     * @param dx
     * @param dy 
     * @param isTeX 
     */
    public void dimlinerel(double dx, double dy, boolean isTeX) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        double x1 = x0 + dx;
        double y1 = y0 + dy;
        dimline(x0,y0,x1,y1, isTeX);
    }
    
    /**
     * Draw a dimension line (double headed arrow) from the cursor position 
     * to a relative position and label it.
     * Set cursor to the new position.
     * @param dx
     * @param dy
     * @param lb 
     * @param isTeX 
     */
    public void dimlinerel(double dx, double dy, String lb, boolean isTeX) {
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        double x1 = x0 + dx;
        double y1 = y0 + dy;
        dimline(x0,y0,x1,y1, lb, isTeX);
    }
    
    /**
     * Draw a dimension line (double headed arrow)
     * and label it by its length (in drawing units).
     * Sets the cursor to the new position (second coordinate x1/y1).
     * @param x0
     * @param y0
     * @param x1
     * @param y1 
     * @param isTeX 
     */
    public void dimline(double x0, double y0, double x1, double y1, boolean isTeX) {
        Point2D P0 = new Point2D.Double(x0,y0);
        Point2D P1 = new Point2D.Double(x1,y1);
        double dim = P1.distance(P0);
        String dimstr = Fkt.nf(dim, digits);
        if (dimstr.contains(".")) {
            while (dimstr.endsWith("0")) {
                dimstr = dimstr.substring(0, dimstr.length()-1);
            }
            if (dimstr.endsWith(".")) {
                dimstr = dimstr.substring(0, dimstr.length()-1);
            }
        }
        if (debug) System.out.println(dim + " -- " + dimstr);
        dimline(x0, y0, x1, y1, dimstr, isTeX);
    }
    
    /**
     * Draw a dimension line (double headed arrow) and label it.
     * Sets the cursor to the new position (second coordinate x1/y1).
     * @param x0
     * @param y0
     * @param x1
     * @param y1
     * @param lb text
     * @param isTeX
     */
    public void dimline(double x0, double y0, double x1, double y1, String lb, boolean isTeX) {
        doppelpfeil(x0,y0,x1,y1, true);
        if (isTeX) texlabelline(x0,y0,x1,y1, lb);
        else labelline(x0,y0,x1,y1, lb);
        currentpos.setLocation(x1, y1);
    }
    
    /**
     * Label a line.
     * Does not draw a line. Does not move the cursor.
     * @param x0
     * @param y0
     * @param x1
     * @param y1
     * @param lb 
     */
    public void labelline(double x0, double y0, double x1, double y1, String lb) {
        Point2D PM = mitte(new Point2D.Double(x0,y0), new Point2D.Double(x1,y1));
        if (horizFontOnly) {
            String pos; // TODO feiner positionieren
            if (Math.abs(x1 - x0) < Math.abs(y1 - y0)) pos = "E";
            else pos = "N";
            double curX = currentpos.getX();
            double curY = currentpos.getY();
            currentpos.setLocation(PM);
            label(lb, pos);
            currentpos.setLocation(curX, curY);
        } else {
            AffineTransform rotiert;
            double alphaRot; // Rotationswinkel
            double dx, dy;
            // Mavscript-yacas replaces unicode chars (e.g. ω, but every char above 127) 
            // by a ascii hexadecimal representation (e.g. escu03c9)
            // We want to reinsert the original unicode char.
            if (UTFconverter.containsUnicodeHexCharacters(lb)) {
                if (debug) System.out.println("ConvertFrom " + lb);
                lb = UTFconverter.convert2UTF(lb);
                if (debug) System.out.println("ConvertFrom " + lb);
            }
            // Font metrics
            Rectangle2D fr = g.getFontMetrics().getStringBounds(lb, g);
            double w = fr.getWidth();
            double h = fr.getHeight();
            // Rotationswinkel ermitteln
            dx = x1 - x0;
            dy = y1 - y0;
            if (dx == 0d) alphaRot = -Math.PI/2d;
            else alphaRot = Math.atan(dy/dx);
            // Rotieren und schieben
            float xpix = (float) (PM.getX() * u);
            float ypix = (float) (PM.getY() * u);
            rotiert = AffineTransform.getRotateInstance(alphaRot, xpix, ypix);
            Graphics2D tempGraphics = (Graphics2D) g.create();
            tempGraphics.setStroke(g.getStroke());
            tempGraphics.transform(rotiert);
            tempGraphics.translate(-w/2d, -h/3d); // schieben
            tempGraphics.drawString(lb, xpix, ypix);
            // Rotation und Verschiebung zurückstellen
            tempGraphics.dispose();
        }
    }
    
    /**
     * Label a line using TeX input.
     * Does not draw a line. Does not move the cursor.
     * @param x0
     * @param y0
     * @param x1
     * @param y1
     * @param latex Label using TeX math syntax 
     */
    public void texlabelline(double x0, double y0, double x1, double y1, String latex) {
        Point2D PM = mitte(new Point2D.Double(x0,y0), new Point2D.Double(x1,y1));
        // Mavscript-yacas replaces unicode chars (e.g. ω, but every char above 127) 
        // by a ascii hexadecimal representation (e.g. escu03c9)
        // We want to reinsert the original unicode char.
        if (UTFconverter.containsUnicodeHexCharacters(latex)) {
            if (debug) System.out.println("ConvertFrom " + latex);
            latex = UTFconverter.convert2UTF(latex);
            if (debug) System.out.println("ConvertFrom " + latex);
        }
        TeXFormula formula = new TeXFormula(latex);
        formula.setColor(g.getColor());
        //formula.setDEBUG(true);
        TeXIcon icon = formula.createTeXIcon(TeXConstants.STYLE_TEXT, schriftgr, true);
        
        double w = icon.getTrueIconWidth();
        double h = icon.getTrueIconHeight();
        
        AffineTransform rotiert;
        double alphaRot; // Rotationswinkel
        double dx, dy;
        // Rotationswinkel ermitteln
        dx = x1 - x0;
        dy = y1 - y0;
        if (dx == 0d) alphaRot = -Math.PI/2d;
        else alphaRot = Math.atan(dy/dx);
        // Rotieren und schieben
        float xpix = (float) (PM.getX() * u);
        float ypix = (float) (PM.getY() * u);
        rotiert = AffineTransform.getRotateInstance(alphaRot, 0, 0);
        Graphics2D tempGraphics = (Graphics2D) g.create();
        tempGraphics.translate(xpix, ypix); // schieben
        tempGraphics.setStroke(g.getStroke());
        tempGraphics.transform(rotiert);
        tempGraphics.translate(-w / 2d, -h / 3d); // schieben
        // new settings
        tempGraphics.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING,
                            java.awt.RenderingHints.VALUE_ANTIALIAS_ON);
        tempGraphics.setRenderingHint(java.awt.RenderingHints.KEY_RENDERING,
                            java.awt.RenderingHints.VALUE_RENDER_QUALITY);
        tempGraphics.setRenderingHint(java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,
                            java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        tempGraphics.scale(schriftgr, schriftgr); // the point size

        // draw formula box
        Box box = icon.getBox();
        box.draw(tempGraphics, 0, 0);
        
        tempGraphics.dispose();
    }
    
    /**
     * Set number format for dimension angles
     * @param nb number of decimal digits
     */
    public void decdigitsangle(int nb) {
        if (nb >= 0) angledigits = nb;
        else angledigits = angledigitsStd;
    }
    
    /**
     * Draw a dimension arc at a vertex.
     * The label is either its angle in degrees or any text. Clock-wise.
     * Sets the cursor to the vertex (x0/y0).
     * 
     * @param x0 vertex x-coord
     * @param y0 vertex y-coord
     * @param xP
     * @param yP
     * @param xQ
     * @param yQ
     * @param isTeX
     */
    public void dimangle(double x0, double y0, double xP, double yP, double xQ, double yQ, boolean isTeX) {
        double dir2 = Math.atan2(yQ - y0, xQ - x0);
        double dir1 = Math.atan2(yP - y0, xP - x0);
        double anglerad = dir2 - dir1;
        if (anglerad < 0) anglerad += 2. * Math.PI;
        double angledeg = 180. / Math.PI * anglerad;
        String dimstr = Fkt.nf(angledeg, angledigits);
        if (dimstr.contains(".")) {
            while (dimstr.endsWith("0")) {
                dimstr = dimstr.substring(0, dimstr.length()-1);
            }
            if (dimstr.endsWith(".")) {
                dimstr = dimstr.substring(0, dimstr.length()-1);
            }
        }
        if (isTeX || !font.equalsIgnoreCase("Tex")) dimstr = dimstr + "°"; // TeX (jlm_cmr10.ttf) does not contain °
        if (Math.abs(angledeg - 90) < 1E-10 && angledigits < 10) { // hard coded tolerance
            dimstr = "."; // right angle
        }
        dimangle(x0, y0, xP, yP, xQ, yQ, isTeX, dimstr);
    }
    
    /**
     * Draw a dimension arc at a vertex.
     * The label is either its angle in degrees or any text. Clock-wise.
     * Sets the cursor to the vertex (x0/y0).
     *
     * @param x0 vertex x-coord
     * @param y0 vertex y-coord
     * @param xP
     * @param yP
     * @param xQ
     * @param yQ
     * @param isTeX
     * @param lb Label. \".\" for right angle sign
     */
    public void dimangle(double x0, double y0, double xP, double yP, double xQ, double yQ, boolean isTeX, String lb) {
        double dir2 = Math.atan2(yQ - y0, xQ - x0);
        if (dir2 < 0) dir2 += 2. * Math.PI;
        double dir1 = Math.atan2(yP - y0, xP - x0);
        if (dir1 < 0) dir1 += 2. * Math.PI;
        double r = 4; // mm
        arc(x0, y0, r/u, 180. / Math.PI * dir1, 180. / Math.PI * dir2); // sets cursor to x0,y0
        double textdir = (dir1+dir2)/2d;
        if (dir2 < dir1) textdir += Math.PI;
        assert textdir >= 0;
        String orientation;
        if      (textdir <=   Math.PI/4d) orientation = "E";
        else if (textdir <  3*Math.PI/4d) orientation = "S";
        else if (textdir <= 5*Math.PI/4d) orientation = "W";
        else if (textdir <  7*Math.PI/4d) orientation = "N";
        else orientation = "E";
        if (lb.equals(".")) { // right angle
            moverel(0.6*r/u*Math.cos(textdir), 0.6*r/u*Math.sin(textdir));
            point();
        } else {
            moverel(r/u*Math.cos(textdir), r/u*Math.sin(textdir));
            if (isTeX) texlabel(lb, orientation);
            else label(lb, orientation);
        }
        currentpos.setLocation(x0, y0);
    }
    
    /**
     * Draw an image.
     * 
     * @param filename
     * @param dpi dots per inch
     * @param pxX image dots from the left to the insertion point
     * @param pxY image dots from the top to the insertion point
     * @param massstab scale of the plan (image). 0 does not do any scaling
     * @throws java.io.FileNotFoundException
     */
    public void image (String filename, double dpi, double pxX, double pxY, double massstab) throws FileNotFoundException, IOException {
        BufferedImage image;
        File file = new File(filename);
        image = ImageIO.read(file);
        double x0 = currentpos.getX();
        double y0 = currentpos.getY();
        double scalexu; // scale * u
        if (massstab == 0) scalexu = inch / dpi;
        else scalexu = massstab/naturalunit * inch / dpi * u;
        int sx1 = 0;
        int sy1 = 0;
        int sw = image.getWidth();
        int sh = image.getHeight();
        double dx1 = (x0*u - scalexu*pxX);
        double dy1 = (y0*u - scalexu*pxY);
        double dw = (scalexu*sw);
        double dh = (scalexu*sh);
        // The method drawImage expects integer values (dx1,dx2,dw,dh) for scaling the image:
        // g.drawImage(image, dx1, dy1, dx1 + dw, dy1 + dh, sx1, sy1, sx1 + sw, sy1 + sh, null);
        // To avoid rough rounding issues, a scaled graphic context is used.
        double sf = 256;
        AffineTransform scaled = AffineTransform.getScaleInstance(1d/sf, 1d/sf);
        Graphics2D tempGraphics = (Graphics2D) g.create();
        tempGraphics.translate(dx1, dy1);
        tempGraphics.transform(scaled);
        tempGraphics.drawImage(image, 0, 0, (int) (sf*dw), (int) (sf*dh), sx1, sy1, sx1 + sw, sy1 + sh, null);
        tempGraphics.dispose(); // Release system ressources
    }
    
    
    // ------------------------------------------------------------------
    
    /**
     * Get the current number format for dimension lines
     */
    int getdecdigits() {
        return digits;
    }
    
    /**
     * Get the unit length in mm
     */
    double getunitlength() {
        return u;
    }
    
    // ------------------------------------------------------------------
    
    /**  Returns a circle object.
     * @param zentrum center Will be scaled by the unit lenght
     * @param diameter diameter In mm, will not be scaled.
     */
    private Ellipse2D.Double kreis(Point2D zentrum, double diameter) {
        Ellipse2D.Double derkreis = new Ellipse2D.Double(zentrum.getX()*u - diameter/2d, zentrum.getY()*u - diameter/2d,
        diameter, diameter);
        return derkreis;
    }
    
    private Point2D mitte(Point2D pkt1, Point2D pkt2) {
        Point2D pkt = new Point2D.Double();
        pkt.setLocation((pkt1.getX()+pkt2.getX())/2d, (pkt1.getY()+pkt2.getY())/2d);
        return pkt;
    }
    
    private void pfeil(double vonX, double vonY, double bisX, double bisY) {
        vonX *= u;
        vonY *= u;
        bisX *= u;
        bisY *= u;
        double spitzenlängeMax = 15. / 72.*inch;
        double spitzenlängeMin = 7 / 72.*inch;
        double spitzenlänge = Math.hypot(bisY-vonY, bisX-vonX) / 4d; // bei allfälliger Anpassung, auch darstellenLasten() resp. Auflkr. anpassen
        if (spitzenlänge > spitzenlängeMax) spitzenlänge = spitzenlängeMax; //15;
        if (spitzenlänge < spitzenlängeMin) spitzenlänge = spitzenlängeMin; //7;
        double dx = spitzenlänge * (bisX-vonX) / Math.hypot(bisY-vonY, bisX-vonX);
        double dy = spitzenlänge * (bisY-vonY) / Math.hypot(bisY-vonY, bisX-vonX);
        if (Math.hypot(bisY-vonY, bisX-vonX) >= spitzenlänge) {
            g.draw(new Line2D.Double(vonX,vonY,bisX - dx/2d ,bisY - dy/2d));
        }
        GeneralPath spitze = new GeneralPath();
        spitze.moveTo((float)bisX, (float)bisY);
        spitze.lineTo((float)(bisX - dx + dy/3d), (float)(bisY - dy - dx/3d));
        spitze.lineTo((float)(bisX - dx - dy/3d), (float)(bisY - dy + dx/3d));
        spitze.closePath();
        g.fill(spitze);
    }
    
    private void doppelpfeil(double vonX, double vonY, double bisX, double bisY, boolean withbar) {
        vonX *= u;
        vonY *= u;
        bisX *= u;
        bisY *= u;
        double spitzenlängeMax = 11. / 72.*inch;
        double spitzenlängeMin = 5 / 72.*inch;
        double spitzenlänge = Math.hypot(bisY-vonY, bisX-vonX) / 4d;
        if (spitzenlänge > spitzenlängeMax) spitzenlänge = spitzenlängeMax; //11;
        if (spitzenlänge < spitzenlängeMin) spitzenlänge = spitzenlängeMin; //5;
        double dx = spitzenlänge * (bisX-vonX) / Math.hypot(bisY-vonY, bisX-vonX);
        double dy = spitzenlänge * (bisY-vonY) / Math.hypot(bisY-vonY, bisX-vonX);
        if (Math.hypot(bisY-vonY, bisX-vonX) >= spitzenlänge) {
            g.draw(new Line2D.Double(vonX + dx/2d,vonY + dy/2d,bisX - dx/2d ,bisY - dy/2d));
        }
        GeneralPath spitzeBis = new GeneralPath();
        spitzeBis.moveTo((float)bisX, (float)bisY);
        spitzeBis.lineTo((float)(bisX - dx + dy/3d), (float)(bisY - dy - dx/3d));
        spitzeBis.lineTo((float)(bisX - dx - dy/3d), (float)(bisY - dy + dx/3d));
        spitzeBis.closePath();
        g.fill(spitzeBis);
        GeneralPath spitzeVon = new GeneralPath();
        spitzeVon.moveTo((float)vonX, (float)vonY);
        spitzeVon.lineTo((float)(vonX + dx - dy/3d), (float)(vonY + dy + dx/3d));
        spitzeVon.lineTo((float)(vonX + dx + dy/3d), (float)(vonY + dy - dx/3d));
        spitzeVon.closePath();
        g.fill(spitzeVon);
        if (withbar) {
            Stroke cstroke = g.getStroke();
            g.setStroke(new java.awt.BasicStroke(currentstrokesize/2f));
            g.draw(new Line2D.Double(vonX + dy/2d, vonY - dx/2d, vonX - dy/2d, vonY + dx/2d));
            g.draw(new Line2D.Double(bisX + dy/2d, bisY - dx/2d, bisX - dy/2d, bisY + dx/2d));
            g.setStroke(cstroke);
        }
    }
    
    private class clBlock {
        private final AffineTransform at;
        private final Point2D pos;
        
        private clBlock (AffineTransform at, Point2D pos) {
            this.at = at;
            this.pos = new Point2D.Double(pos.getX(), pos.getY());
        }
        
        private AffineTransform getAffineTransform() {
            return at;
        }
        private Point2D getPos() {
            return pos;
        }
    }
    
}

 

Ads help maintain this website.