VectorLineMacroPrimitive.java
package com.varnernet.gerb4j.macro;
import com.varnernet.gerb4j.Polarity;
import com.varnernet.gerb4j.render.GerberOutputTarget;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
/**
* Gerber macro primitive type 20 — Vector Line. Format: {@code
* 20,exposure,width,startX,startY,endX,endY,rotation}
*
* <p>Per spec §4.5.1.5, primitive 20 is a <em>filled rectangle</em> with flat (square) ends — not a
* stroked path with round caps. The implementation computes the four rotated corners of the
* rectangle and delegates to {@link GerberOutputTarget#drawRegion}, exactly as {@link
* CenterLineMacroPrimitive} does for primitive 21.
*/
final class VectorLineMacroPrimitive extends AbstractMacroPrimitive {
private static final int CORNERS = 4;
private final double width;
private final double startX;
private final double startY;
private final double endX;
private final double endY;
private final double rotation;
VectorLineMacroPrimitive(
final boolean exposed,
final double width,
final double startX,
final double startY,
final double endX,
final double endY,
final double rotation) {
super(exposed);
this.width = width;
this.startX = startX;
this.startY = startY;
this.endX = endX;
this.endY = endY;
this.rotation = rotation;
}
@Override
public Rectangle2D getBounds() {
double[] s = rotatePoint(startX, startY, rotation);
double[] e = rotatePoint(endX, endY, rotation);
double half = width / 2.0;
double minX = Math.min(s[0], e[0]) - half;
double maxX = Math.max(s[0], e[0]) + half;
double minY = Math.min(s[1], e[1]) - half;
double maxY = Math.max(s[1], e[1]) + half;
return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
}
@Override
public void render(final Point2D flashPoint, final GerberOutputTarget target, final Polarity polarity) {
// Compute the perpendicular unit vector scaled to half-width.
// This gives the offset from the line axis to each long edge.
double dx = endX - startX;
double dy = endY - startY;
double len = Math.sqrt(dx * dx + dy * dy);
// Avoid degenerate zero-length lines
if (len == 0.0) {
return;
}
double half = width / 2.0;
// Perpendicular: rotate (dx,dy) by 90° CCW → (-dy, dx), then normalise and scale
double px = -dy / len * half;
double py = dx / len * half;
// Four corners in local (pre-rotation) macro space
double[][] local = {
{startX + px, startY + py}, // start-left
{endX + px, endY + py}, // end-left
{endX - px, endY - py}, // end-right
{startX - px, startY - py}, // start-right
};
// Apply macro rotation, then translate by flash point
Path2D.Double path = new Path2D.Double();
for (int i = 0; i < CORNERS; i++) {
double[] rot = rotatePoint(local[i][0], local[i][1], rotation);
double wx = flashPoint.getX() + rot[0];
double wy = flashPoint.getY() + rot[1];
if (i == 0) {
path.moveTo(wx, wy);
} else {
path.lineTo(wx, wy);
}
}
path.closePath();
target.drawRegion(path, effectivePolarity(polarity));
}
}