Package ket.display.box

Source Code of ket.display.box.BoxGraphic

/*
* Copyright (C) 2011  Alasdair C. Hamilton
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*/

package ket.display.box;

import geom.Offset;
import geom.Position;
import java.awt.geom.*;

import java.awt.*;
import ket.math.*;
import ket.display.ColourScheme;

public class BoxGraphic extends Box {
  /////////////////////
  // DISPLAY OPTIONS //
  /////////////////////
  public static final int HORIZONTAL_LINE     = 0x01;
  public static final int VERTICAL_LINE       = 0x02;
  public static final int RADICAL             = 0x03;
  public static final int NOTHING             = 0x04;
  public static final int OPEN_SQUARE         = 0x05;
  public static final int CLOSE_SQUARE        = 0x06;
  public static final int OPEN_ROUND          = 0x07;
  public static final int CLOSE_ROUND         = 0x08;
  public static final int OPEN_PARENTHESIS    = 0x09;
  public static final int CLOSE_PARENTHESIS   = 0x0a;
  public static final int INTEGRAL            = 0x0b;
  public static final int CONTOUR             = 0x0c;
  public static final int BRA                 = 0x0d;
  public static final int KET                 = 0x0e;

  ///////////////////////
  // GRAPHICS SETTINGS //
  ///////////////////////

  // Fractional height (from base) of left tick line (without thickness).
  static final double FRACTIONAL_LEFT_TICK_HEIGHT = 2.0/5.0;
  // Fractional inset of horizontal part of the square root.
  static final double FRACTIONAL_HORIZONTAL_INSET = 1.0/9.0;
  static final double INTEGRAL_RATIO = 1.9;
  public static final int THICKNESS = 2;
  public static final int GUESS_SIZE = 20;

  // Spacing around round brackets on the left, top and right of the arc.
  static final double GAP = 0.025;

  /**
   * A flag that denotes which of the pre-determined shapes this instance
   * represents.
   */
  private int shape;

  @Override
  public Box cloneBox() {
    return new BoxGraphic(getArgument(), getSettings(), shape);
  }

  public BoxGraphic(Argument argument, long settings, int shape) {
    super(argument, settings);
    this.shape = shape;
  }
 
  @Override
  protected void calcMinimumSize() {
    switch (shape) {
      case HORIZONTAL_LINE:
        innerRectangle = new Offset(0.0, THICKNESS);
        break;

      case VERTICAL_LINE:
        innerRectangle = new Offset(THICKNESS, 0.0);
        break;

      case NOTHING:
        innerRectangle = new Offset(0.0, 0.0);
        break;

      case RADICAL:
        // TODO: While using a minimum size to ensure a
        // kind of aspect ratio, this should be
        // replaced with a context aware alternative.
        innerRectangle = new Offset(GUESS_SIZE, GUESS_SIZE);
        break;

      case OPEN_SQUARE:
        innerRectangle = new Offset(3*THICKNESS, 0.0);
        break;

      case CLOSE_SQUARE:
        innerRectangle = new Offset(3*THICKNESS, 0.0);
        break;

      case OPEN_ROUND:
        innerRectangle = new Offset(3*THICKNESS, 0.0);
        break;

      case CLOSE_ROUND:
        innerRectangle = new Offset(3*THICKNESS, 0.0);
        break;

      case CONTOUR:
        innerRectangle = new Offset(GUESS_SIZE, GUESS_SIZE);
        break;

      case INTEGRAL:
        innerRectangle = new Offset(GUESS_SIZE, GUESS_SIZE);
        break;

      case OPEN_PARENTHESIS:
        innerRectangle = new Offset(PARENTHESIS_MIN);
        break;

      case CLOSE_PARENTHESIS:
        innerRectangle = new Offset(PARENTHESIS_MIN);
        break;

      case BRA:
        innerRectangle = new Offset(GUESS_SIZE, GUESS_SIZE);
        break;

      case KET:
        innerRectangle = new Offset(GUESS_SIZE, GUESS_SIZE);
        break;

      default:
        throw new RuntimeException("unfinished");
    }
  }

  @Override
  public void setupOuterRectangle(Offset actualSize) {
    switch (shape) {
      case HORIZONTAL_LINE:
        innerRectangle = new Offset(actualSize.width, THICKNESS);
        break;
 
      case VERTICAL_LINE:
        innerRectangle = new Offset(THICKNESS, actualSize.height);
        break;
 
      case NOTHING:
        innerRectangle = new Offset(0.0, 0.0);
        break;

      case CONTOUR:
        innerRectangle = new Offset(actualSize);
        break;
 
      case INTEGRAL:
        innerRectangle = new Offset(actualSize);
        if (actualSize.height < INTEGRAL_RATIO*actualSize.width) { // Does this matter?
          innerRectangle.width = innerRectangle.height/INTEGRAL_RATIO;
        }
        break;

      case RADICAL:
        // TODO: While using a minimum size to ensure a
        // kind of aspect ratio, this should be
        // replaced with a context aware alternative.
        innerRectangle = new Offset(actualSize);
        break;

      case OPEN_SQUARE:
        innerRectangle = new Offset(2*THICKNESS, actualSize.height);
        break;

      case CLOSE_SQUARE:
        innerRectangle = new Offset(2*THICKNESS, actualSize.height);
        break;

      case OPEN_ROUND:
        innerRectangle = new Offset(0, actualSize.height);
        break;

      case CLOSE_ROUND:
        innerRectangle = new Offset(0, actualSize.height)//? 0?
        break;

      case OPEN_PARENTHESIS:
        innerRectangle = new Offset(PARENTHESIS_MIN.width, actualSize.height);
        break;

      case CLOSE_PARENTHESIS:
        innerRectangle = new Offset(PARENTHESIS_MIN.height, actualSize.height);
        break;

      case BRA:
        innerRectangle = new Offset(actualSize);
        break;

      case KET:
        innerRectangle = new Offset(actualSize);
        break;

      default:
        throw new RuntimeException("unfinished");
    }    
    super.setupOuterRectangle(actualSize);
  }

  //? static final int scale = 1;
  //?public static Offset PARENTHESIS_MIN = new Offset(scale*GUESS_SIZE, scale*5*GUESS_SIZE/2);
  //- public static Offset PARENTHESIS_MIN = new Offset(0.6*GUESS_SIZE, GUESS_SIZE);
  public static Offset PARENTHESIS_MIN = new Offset(GUESS_SIZE, 5.0*GUESS_SIZE/3.0);



  @Override
  public void draw(Graphics2D g2D, Position topLeft, ColourScheme colourScheme) {
    if (outerRectangle==null) return; //BUG: Cause?
    colourSetup(g2D, colourScheme);
    double left = getXPosition(topLeft);
    double top = getYPosition(topLeft);
    double width = outerRectangle.width;
    double height = outerRectangle.height;
    Point A = new Point((int) left, (int) top);
    Point B = new Point((int) (left+width), (int) (top+height));

    switch (shape) {
      case HORIZONTAL_LINE:
        g2D.fillRect((int) left, (int) top, (int) width, THICKNESS);
        break;

      case VERTICAL_LINE:
        g2D.fillRect((int) left, (int) top, THICKNESS, (int) height);
        break;

      case NOTHING:
        break;

      case CONTOUR:
        // Draw anulus here and use INTEGRAL to draw the rest.
        GeneralPath annulus = new GeneralPath();
        double cx = left + width/2.0;
        double cy = top + height/2.0;
        double r = width/4.0;
        annulus.moveTo(cx+r, cy); // 1
        oneEighty(annulus, cx+r,cy, cx,cy+r, cx-r,cy); // 2,3,4
        oneEighty(annulus, cx-r,cy, cx,cy-r, cx+r,cy); // 5,6,7
        r -= THICKNESS;
        annulus.moveTo(cx+r,cy);
        oneEighty(annulus, cx+r,cy, cx,cy-r, cx-r,cy);
        oneEighty(annulus, cx-r,cy, cx,cy+r, cx+r,cy);
        annulus.closePath();
        g2D.fill(annulus);
        //...

      case INTEGRAL:
        {
        if (height < INTEGRAL_RATIO*width) {
          width = innerRectangle.height/INTEGRAL_RATIO;
        }
        double a = width/2.0;
        double t = THICKNESS/2.0;
        double q = (a + t)/2.0;
        GeneralPath path = new GeneralPath();
        path.moveTo(left+width, top+q); // 1
        oneEighty(path, left+width, top+q, left+width-q, top, left+a-t, top+q);
        path.lineTo(left+a-t, top+height-q); // 4
        oneEighty(path, left+a-t,top+height-q,left+q,top+height-THICKNESS,left+THICKNESS,top+height-q);
        path.lineTo(left, top+height-q); // 7
        oneEighty(path, left,top+height-q,left+q,top+height,left+a+t,top+height-q);
        path.lineTo(left+a+t, top+q); // 10
        oneEighty(path, left+a+t,top+q, left+width-q,top+THICKNESS, left+width-THICKNESS,top+q);
        path.closePath();
        g2D.fill(path);
        }
        break;

      case RADICAL:
        double M = FRACTIONAL_HORIZONTAL_INSET * width; 
        double h = FRACTIONAL_LEFT_TICK_HEIGHT * height;
        double tanPhi = (h+height-THICKNESS)/(width - M);
        double sinPhi = tanPhi / Math.sqrt(1.0 + tanPhi*tanPhi);
        double m = h/tanPhi;
        double k = THICKNESS*(sinPhi - 1.0/tanPhi);
        int[] xPoints = new int[]{
          (int) left,
          (int) left,
          (int) (left+M),
          (int) (left+M+m),
          (int) (left+width),
          (int) (left+width),
          (int) (left+width-k),
          (int) (left+m+M),
          (int) (left+M+k),
          (int) left
        };
        int[] yPoints = new int[]{
          (int) (top+height-THICKNESS-h),
          (int) (top+height-h),
          (int) (top+height-h),
          (int) (top+height),
          (int) (top+THICKNESS),
          (int) top,
          (int) top,
          (int) (top+height-2.0*THICKNESS*sinPhi),
          (int) (top+height-THICKNESS-h),
          (int) (top+height-THICKNESS-h)
        };
        g2D.fillPolygon(xPoints, yPoints, xPoints.length);
        break;

      case OPEN_SQUARE:
        int[] openSquareX = new int[]{
          (int) left,
          (int) (left+2*THICKNESS),
          (int) (left+2*THICKNESS),
          (int) (left+THICKNESS),
          (int) (left+THICKNESS),
          (int) (left+2*THICKNESS),
          (int) (left+2*THICKNESS),
          (int) left,
          (int) left};
        int[] openSquareY = new int[]{
          (int) top,
          (int) top,
          (int) (top+THICKNESS),
          (int) (top+THICKNESS),
          (int) (top+height-THICKNESS),
          (int) (top+height-THICKNESS),
          (int) (top+height),
          (int) (top+height),
          (int) top};
        g2D.fillPolygon(openSquareX, openSquareY, openSquareX.length);
        break;

      case CLOSE_SQUARE:
        int[] closeSquareX = new int[]{
          (int) (left + 2*THICKNESS),
          (int) left,
          (int) left,
          (int) (left + THICKNESS),
          (int) (left + THICKNESS),
          (int) left,
          (int) left,
          (int) (left + 2*THICKNESS),
          (int) (left + 2*THICKNESS)};
        int[] closeSquareY = new int[]{
          (int) top,
          (int) top,
          (int) (top+THICKNESS),
          (int) (top+THICKNESS),
          (int) (top+height-THICKNESS),
          (int) (top+height-THICKNESS),
          (int) (top+height),
          (int) (top+height),
          (int) top};
        g2D.fillPolygon(closeSquareX, closeSquareY, closeSquareX.length);
        break;

      case OPEN_ROUND:
        drawOpenRoundBracket(g2D, THICKNESS/2.0+left+GAP*width, top+GAP*height, (1.0-2.0*GAP)*width, (1.0-GAP)*height);
        break;

      case CLOSE_ROUND:
        drawCloseRoundBracket(g2D, left+GAP*width, top+GAP*height, (1.0-2.0*GAP)*width, (1.0-GAP)*height);
        break;

      case OPEN_PARENTHESIS:
        drawParenthesis(g2D, A, B, true, THICKNESS);
        break;

      case CLOSE_PARENTHESIS:
        drawParenthesis(g2D, A, B, false, THICKNESS);
        break;

      case BRA:
        lessThan(g2D, left, top, width, height);
        break;

      case KET:
        greaterThan(g2D, left, top, width, height);
        break;

      default:
        throw new RuntimeException("unfinished");
    }
  }

  private void lessThan(Graphics2D g2D, double left, double top, double width, double height) {
    int w = (int) (2.0*THICKNESS/Math.sqrt(2.0)); // ish
    width = height/2 + w;
    //> height = 2*(width - w);
    int[] xs = new int[]{
      (int) width,
        (int) width,
        (int) (width-(height-2*w)/2),
        (int) width,
        (int) width,
        (int) width-w,
        0,
        (int) width-w};
    int[] ys = new int[]{0, w, (int) height/2, (int) height-w, (int) height, (int) height, (int) height/2, 0};
    for (int i=0; i<xs.length; i++) {
      xs[i] += left;
      ys[i] += top;
    }
    g2D.fillPolygon(xs, ys, xs.length);
  }

  private void greaterThan(Graphics2D g2D, double left, double top, double width, double height) {
    int w = (int) (THICKNESS / Math.sqrt(2.0)); //?
    width = height/2 + w;
    //> height = 2*(width - w);
    int[] xs = new int[]{0, 0, ((int) height-2*w)/2, 0, 0, w, (int) width, w};
    int[] ys = new int[]{0, w, (int) height/2, (int) height-w, (int) height, (int) height, (int) height/2, 0};
    for (int i=0; i<xs.length; i++) {
      xs[i] += left;
      ys[i] += top;
    }
    g2D.fillPolygon(xs, ys, xs.length);
  }

  private void drawArc(Graphics2D g2D, double x, double y, double radius, double angleDeg, double subtendDeg) {
    Stroke old = g2D.getStroke();
    float arcWidth = (float)THICKNESS/1.0f;
    BasicStroke bracketStroke = new BasicStroke(arcWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
    g2D.setStroke(bracketStroke);
    Arc2D.Double arc = new Arc2D.Double();
    arc.setArcByCenter(x, y, radius, angleDeg, subtendDeg, Arc2D.OPEN);
    g2D.draw(arc);
    g2D.setStroke(old);
  }

  private void drawOpenRoundBracket(Graphics2D g2D, double left, double top, double width, double height) {
    double radius = height*height/8.0/width + width/2.0;
    double u = height*height/8.0/width - width/2.0;
    double angle = 180.0*Math.asin(height/2.0/radius)/Math.PI;
    drawArc(g2D, left+u, top+height/2.0, radius, 180.0-angle, 2.0*angle);
  }

  private void drawCloseRoundBracket(Graphics2D g2D, double left, double top, double width, double height) {
    double radius = height*height/8.0/width + width/2.0;
    double u = height*height/8.0/width - width/2.0;
    double angle = 180.0*Math.asin(height/2.0/radius)/Math.PI;
    drawArc(g2D, left-u, top+height/2.0, radius, -angle, 2.0*angle);
  }

  private void oneEighty(GeneralPath path, double x1, double y1, double x2, double y2, double x3, double y3) {
    ninty(path, x1, y1, x2, y2, x1, y2);
    ninty(path, x2, y2, x3, y3, x3, y2);
  }

  private void ninty(GeneralPath path, double Ax, double Ay, double Bx, double By, double Cx, double Cy) {
    //- double Ux = (Ax + Bx)/2.0;
    //- double Uy = (Ay + By)/2.0;
    double ACx = (Ax + Cx)/2.0;
    double ACy = (Ay + Cy)/2.0;
    double BCx = (Bx + Cx)/2.0;
    double BCy = (By + Cy)/2.0;
    path.curveTo(ACx, ACy, BCx, BCy, Bx, By);
  }

  public String description() {
    switch (shape) {
      case HORIZONTAL_LINE:
        return "'horizontal line'";

      case VERTICAL_LINE:
        return "'vertical line'";

      case NOTHING:
        return "'nothing'";

      case RADICAL:
        return "'radical'";

      case INTEGRAL:
        return "'integral'";

      case OPEN_SQUARE:
        return "'['";

      case CLOSE_SQUARE:
        return "']'";

      case OPEN_ROUND:
        return "'('";

      case CLOSE_ROUND:
        return "')'";

      case OPEN_PARENTHESIS:
        return "{";

      case CLOSE_PARENTHESIS:
        return "}";

      default:
        throw new RuntimeException("unfinished");
    }
  }

  @Override
  public String toString() {
    return "BoxGraphic:" + description() + super.toString() + " ";
  }


  // ===================
  // === PARENTHESIS ===
  // ===================

  public Point calcCircle(int sign, Point A, Point B, int radius, int thickness) {
    int Ax = (A.x + B.x)/2 + sign*thickness/2;
    int s = radius - (B.x - Ax);
    int v = (int) Math.sqrt(radius*radius - s*s);
    //Ket.out.printf("s=%13.7g, v=%13.7g, r=%13.7g\n", 1.0*s, 1.0*v, 1.0*radius); //D
    return new Point(B.x+s, B.y-v);
  }

  public Area generateCrescent(Point U, Point V, int radius) { // Remove g2D
    Ellipse2D.Double a = new Ellipse2D.Double(U.x-radius, U.y-radius, 2*radius, 2*radius);
    Ellipse2D.Double b = new Ellipse2D.Double(V.x-radius, V.y-radius, 2*radius, 2*radius);
    Area areaA = new Area(a);
    Area areaB = new Area(b);
    areaA.subtract(areaB);
    return areaA;
  }

  public void drawParenthesis(Graphics2D g2D, Point A, Point B, boolean open, int thickness) {
    int radius = (2 * (B.x - A.x)) / 5; // 2/5 'th width is too simple.
    int m = (A.y+B.y) / 2;
    drawS(g2D, A, new Point(B.x, m), open, radius, thickness);
    drawS(g2D, new Point(A.x, m), B, !open, radius, thickness);
  }

  private void swapX(Point a, Point b) {
    int x = a.x;
    a.x = b.x;
    b.x = x;
  }

  private void drawS(Graphics2D g2D, Point A, Point B, boolean reversed, int radius, int thickness) {
    Point U = calcCircle(-1, A, B, radius, thickness);
    Point V = calcCircle(+1, A, B, radius, thickness);
    Point W = new Point(B.x+A.x-U.x, B.y+A.y-U.y);
    Point X = new Point(B.x+A.x-V.x, B.y+A.y-V.y);
    if (reversed) {
      swapX(U, W);
      swapX(V, X);
    }
    int sign = reversed?+1:-1;
    Point QU = new Point(U.x+sign*radius, U.y);
    Point QV = new Point(V.x+sign*radius, V.y);
    Point QW = new Point(B.x+A.x-QU.x, B.y+A.y-QU.y);
    Point QX = new Point(B.x+A.x-QV.x, B.y+A.y-QV.y);
    if (reversed) {
      swapX(QU, QW);
      swapX(QV, QX);
    }
    Area core = calcCore(QU, QV, QW, QX);
    // (intersection (union core bottom top) mask)
    core.add(generateCrescent(W, X, radius));
    core.add(generateCrescent(U, V, radius));
    mask(core, A, B, QU, QV, QW, QX, reversed);
    g2D.fill(core);
  }

  private void mask(Area core, Point A, Point B, Point QU, Point QV, Point QW, Point QX, boolean reversed) {
    int[] xs;
    if (reversed) {
      xs = new int[]{QU.x, A.x, A.x, QV.x, QV.x, QV.x, QW.x, B.x, B.x, QX.x, QX.x, QX.x};
    } else {
      xs = new int[]{QU.x, QU.x, QU.x, B.x, B.x, QV.x, QW.x, QW.x, QW.x, A.x, A.x, QX.x};
    }
    int[] ys = new int[]{QU.y, QU.y, B.y, B.y, QV.y, QV.y, QW.y, QW.y, A.y, A.y, QX.y, QX.y};
    core.intersect(new Area(new Polygon(xs, ys, 12)));
  }

  private Area calcCore(Point QU, Point QV, Point QW, Point QX) {
    int[] xs = new int[]{QU.x, QV.x, QW.x, QX.x};
    int[] ys = new int[]{QU.y, QV.y, QW.y, QX.y};
    return new Area(new Polygon(xs, ys, 4));
  }
}
TOP

Related Classes of ket.display.box.BoxGraphic

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.