BlockAperture.java
package com.varnernet.gerb4j;
import com.varnernet.gerb4j.render.GerberOperation;
import com.varnernet.gerb4j.render.GerberOutputTarget;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
/**
* An aperture defined by a recorded sequence of drawing operations (%AB...%AB*%).
*
* <p>When this aperture is flashed (D03), all its stored operations are replayed at the flash
* position (each operation's coordinate is translated by the flash X/Y).
*
* <p>Nested BlockApertures work automatically: a FLASH DrawingOperation inside the block that
* targets another BlockAperture ID will recurse through the normal render path when the block is
* replayed.
*
* <p>Two callbacks are injected by {@code GerberContext.addAperture()} so that {@link #renderFlash}
* and {@link #expandFlashBounds} can participate in the polymorphic {@link Aperture} contracts
* without a circular compile-time dependency on {@code GerberContext}.
*/
public class BlockAperture extends Aperture {
// ── Functional-interface callbacks ────────────────────────────────────────
/**
* Callback used by {@link #renderFlash} to recursively render the block's child operations
* through {@code GerberContext.renderItems()}.
*/
@FunctionalInterface
public interface BlockRenderer {
/**
* Renders the given operations with the specified offset and transform.
*
* @param ops the list of operations to render
* @param dx the x offset
* @param dy the y offset
* @param target the output target
* @param transform the aperture transform
*/
void render(
List<GerberOperation> ops,
double dx,
double dy,
GerberOutputTarget target,
ApertureTransform transform);
}
/**
* Callback used by {@link #expandFlashBounds} to recursively expand bounds through {@code
* GerberContext.expandBounds()}.
*/
@FunctionalInterface
public interface BoundsExpander {
/**
* Expands the bounds for the given operations.
*
* @param ops the list of operations
* @param dx the x offset
* @param dy the y offset
* @param transform the affine transform
* @param extents the extents array to update
*/
void expand(
List<GerberOperation> ops,
double dx,
double dy,
AffineTransform transform,
double[] extents);
}
// ── State ─────────────────────────────────────────────────────────────────
/**
* Type-safe list of operations in source order.
*/
private final List<GerberOperation> operations;
private BlockRenderer blockRenderer;
private BoundsExpander boundsExpander;
// ── Constructors ──────────────────────────────────────────────────────────
/**
* Creates a new block aperture with the given ID and an empty operation list.
*
* @param id the aperture identifier (e.g. "D10")
*/
public BlockAperture(final String id) {
super(id);
this.operations = new ArrayList<>();
}
/**
* Creates a new block aperture with the given ID and operation list.
*
* @param id the aperture identifier (e.g. "D10")
* @param operations the list of operations to store (defensive copy made)
*/
public BlockAperture(final String id, final List<GerberOperation> operations) {
super(id);
this.operations = new ArrayList<>(operations);
}
// ── Callback wiring ───────────────────────────────────────────────────────
/**
* Inject the rendering callback (called once from {@code GerberContext.addAperture()}).
*
* @param renderer the callback to use for rendering operations
*/
public void setBlockRenderer(final BlockRenderer renderer) {
this.blockRenderer = renderer;
}
/**
* Inject the bounds-expansion callback (called once from {@code GerberContext.addAperture()}).
*
* @param expander the callback to use for bounds expansion
*/
public void setBoundsExpander(final BoundsExpander expander) {
this.boundsExpander = expander;
}
// ── Aperture contract ─────────────────────────────────────────────────────
/**
* Returns a defensive copy of the operation list.
*
* @return a new list containing all operations
*/
public List<GerberOperation> getOperations() {
return new ArrayList<>(operations);
}
/**
* Block aperture bounds depend on contained operations and flash context; not computable here.
*/
@Override
public Rectangle2D getBounds() {
return null;
}
/**
* Renders this block aperture by delegating to the injected {@link BlockRenderer}. The {@code
* transform} from the enclosing flash operation is forwarded so that LM/LR/LS attributes on the
* flash are honoured during recursive rendering.
*/
@Override
public void renderFlash(
final Point2D flashPoint,
final GerberOutputTarget target,
final Polarity polarity,
final ApertureTransform transform) {
if (blockRenderer != null) {
blockRenderer.render(
getOperations(), flashPoint.getX(), flashPoint.getY(), target, transform);
}
}
/**
* Expands {@code extents} by delegating to the injected {@link BoundsExpander}, which recurses
* into the child operations using the aperture dictionary held by {@code GerberContext}.
*/
@Override
public void expandFlashBounds(
final double x,
final double y,
final AffineTransform transform,
final double[] extents) {
if (boundsExpander != null) {
boundsExpander.expand(getOperations(), x, y, transform, extents);
}
}
}