RectangleAperture.java

package com.varnernet.gerb4j;

import com.varnernet.gerb4j.render.GerberOutputTarget;

import java.awt.BasicStroke;
import java.awt.Shape;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

/**
 * A rectangular aperture.
 *
 * <p>A rectangle aperture defines a rectangular shape that can be flashed or used for drawing lines.
 * It may optionally have a hole in the center.
 */
public final class RectangleAperture extends Aperture {
    private final double xSize;
    private final double ySize;
    private final double holeDiameter;

    /**
     * Creates a new rectangle aperture.
     *
     * @param id           the aperture identifier
     * @param xSize        the width of the rectangle
     * @param ySize        the height of the rectangle
     * @param holeDiameter the diameter of the optional hole (0.0 for no hole)
     */
    public RectangleAperture(
            final String id, final String xSize, final String ySize, final String holeDiameter) {
        super(id);
        this.xSize = Double.parseDouble(xSize);
        this.ySize = Double.parseDouble(ySize);
        this.holeDiameter = (holeDiameter == null) ? 0.0 : Double.parseDouble(holeDiameter);
    }

    /**
     * Returns the width of the rectangle.
     *
     * @return the width
     */
    public double getWidth() {
        return xSize;
    }

    /**
     * Returns the height of the rectangle.
     *
     * @return the height
     */
    public double getHeight() {
        return ySize;
    }

    /**
     * Returns the diameter of the optional hole.
     *
     * @return the hole diameter (0.0 if no hole)
     */
    public double getHoleDiameter() {
        return holeDiameter;
    }

    /**
     * Bounds centred at (0,0).
     */
    @Override
    public Rectangle2D getBounds() {
        return new Rectangle2D.Double(-xSize / 2.0, -ySize / 2.0, xSize, ySize);
    }

    /**
     * Stroke width for D01 linear draws. Per spec §4.8.4, a rectangle aperture sweeps its full
     * cross-section along the path. We approximate this using min(xSize, ySize) as the stroke width.
     */
    @Override
    public double getStrokeWidth() {
        return Math.min(xSize, ySize);
    }

    /**
     * Rectangle apertures produce flat (butt) end caps.
     */
    @Override
    public int getCapStyle() {
        return BasicStroke.CAP_BUTT;
    }

    /**
     * Rectangle apertures use miter joins at shared vertices.
     */
    @Override
    public int getJoinStyle() {
        return BasicStroke.JOIN_MITER;
    }

    /**
     * Returns the rectangle cross-section for swept-region D01 rendering. Per Gerber spec §4.8.4, the
     * cross-section of the drawn line is the full axis-aligned rectangle, not a reduced stroke width.
     */
    @Override
    public Shape getApertureShape() {
        return new Rectangle2D.Double(-xSize / 2.0, -ySize / 2.0, xSize, ySize);
    }

    @Override
    public void render(final Point2D flashPoint, final GerberOutputTarget target, final Polarity polarity) {
        target.drawRectangle(flashPoint, xSize, ySize, 0.0, polarity);
        if (holeDiameter > 0) {
            target.drawCircle(flashPoint, holeDiameter, 0.0, invertPolarity(polarity));
        }
    }
}