FormatSpecification.java
package com.varnernet.gerb4j;
/**
* Format specification for Gerber coordinate parsing.
*
* <p>Gerber files specify coordinate formats using FS commands like "FSLAX24Y24" which define
* the number of integer and decimal places for X and Y coordinates.
*/
public class FormatSpecification {
/**
* Minimum leading digits for coordinate format.
*/
private static final int MIN_LEADING_DIGITS = 1;
/**
* Maximum leading digits for coordinate format.
*/
private static final int MAX_LEADING_DIGITS = 6;
/**
* Minimum trailing digits for coordinate format.
*/
private static final int MIN_TRAILING_DIGITS = 5;
/**
* Maximum trailing digits for coordinate format.
*/
private static final int MAX_TRAILING_DIGITS = 6;
int xLeading;
int xTrailing;
int yLeading;
int yTrailing;
/**
* Default no-arg to facilitate testing.
*/
FormatSpecification() {
}
/**
* Constructs a FormatSpecification from known format specs.
*
* @param xFmt The X coordinate format
* @param yFmt The Y coordinate format
*/
public FormatSpecification(final String xFmt, final String yFmt) {
if (!xFmt.matches("[" + MIN_LEADING_DIGITS + "-" + MAX_LEADING_DIGITS + "][" + MIN_TRAILING_DIGITS + "-" + MAX_TRAILING_DIGITS + "]")) {
throw new IllegalArgumentException("Invalid format specification for Gerber X coordinates.");
}
if (!yFmt.matches("[" + MIN_LEADING_DIGITS + "-" + MAX_LEADING_DIGITS + "][" + MIN_TRAILING_DIGITS + "-" + MAX_TRAILING_DIGITS + "]")) {
throw new IllegalArgumentException("Invalid format specification for Gerber Y coordinates.");
}
setXFormat(xFmt.charAt(0), xFmt.charAt(1));
setYFormat(yFmt.charAt(0), yFmt.charAt(1));
}
/**
* Sets the X coordinate format.
*
* @param leading the number of leading digits
* @param trailing the number of trailing digits
*/
void setXFormat(final char leading, final char trailing) {
this.xLeading = Integer.parseInt("" + leading);
this.xTrailing = Integer.parseInt("" + trailing);
}
/**
* Sets the Y coordinate format.
*
* @param leading the number of leading digits
* @param trailing the number of trailing digits
*/
void setYFormat(final char leading, final char trailing) {
this.yLeading = Integer.parseInt("" + leading);
this.yTrailing = Integer.parseInt("" + trailing);
}
/**
* Parses an X coordinate string according to the format specification.
*
* @param val the coordinate string
* @return the parsed double value
*/
public double getX(final String val) {
// Strip optional sign before checking digit count
int digitLen = (val.startsWith("-") || val.startsWith("+")) ? val.length() - 1 : val.length();
if (digitLen > xLeading + xTrailing) {
throw new IllegalArgumentException(
"Coordinate Length cannot be longer than the configured scale and precision");
}
return Double.parseDouble(format(val, xLeading, xTrailing));
}
/**
* Parses a Y coordinate string according to the format specification.
*
* @param val the coordinate string
* @return the parsed double value
*/
public double getY(final String val) {
// Strip optional sign before checking digit count
int digitLen = (val.startsWith("-") || val.startsWith("+")) ? val.length() - 1 : val.length();
if (digitLen > yLeading + yTrailing) {
throw new IllegalArgumentException(
"Coordinate Length cannot be longer than the configured scale and precision");
}
return Double.parseDouble(format(val, yLeading, yTrailing));
}
private String format(final String val, final int leading, final int trailing) {
// Extract and preserve the sign so it is not included in digit padding
boolean negative = val.startsWith("-");
boolean positive = val.startsWith("+");
String digits = (negative || positive) ? val.substring(1) : val;
String padded = String.format("%" + (leading + trailing) + "s", digits).replace(' ', '0');
String formatted = padded.substring(0, leading) + "." + padded.substring(leading);
return negative ? "-" + formatted : formatted;
}
}