Antialiasing fixes
Build and Test j3270 / Build JAR & Run Tests (push) Successful in 40s

This commit is contained in:
2026-08-25 23:31:14 +00:00
parent 2584f4289f
commit bcfd4ba2e0
6 changed files with 132 additions and 72 deletions
@@ -423,23 +423,19 @@ public class QueryReplyBuilder {
}
private byte[] buildGraphics(int maxCols, int maxRows) {
int width = maxCols * 9;
int height = maxRows * 12;
return new byte[]{
(byte) 0x80, 0x02,
(byte) ((width >> 8) & 0xFF), (byte) (width & 0xFF),
(byte) ((height >> 8) & 0xFF), (byte) (height & 0xFF),
0x00, 0x00,
0x00, (byte) 0xFC,
0x00
};
}
private byte[] buildGImage(int maxCols, int maxRows) {
int width = maxCols * 9;
int height = maxRows * 12;
return new byte[]{
0x00, 0x01,
(byte) ((width >> 8) & 0xFF), (byte) (width & 0xFF),
(byte) ((height >> 8) & 0xFF), (byte) (height & 0xFF),
0x00, 0x00,
0x00, (byte) 0xFC,
0x00,
0x06, 0x40, 0x06, 0x40, 0x06, 0x01,
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xF0
@@ -163,7 +163,7 @@ public class GocaDecoder {
public synchronized int findPickedSegment(int gx, int gy) {
for (int i = activeSegmentsInOrder.size() - 1; i >= 0; i--) {
SegmentBounds sb = activeSegmentsInOrder.get(i);
if (sb.contains(gx, gy, 12)) {
if (sb.contains(gx, gy, 25)) {
System.err.println(String.format(
"findPickedSegment: goca=(%d, %d) HIT segId=%d bounds=[%d..%d, %d..%d] tag=%d",
gx, gy, sb.segId, sb.minX, sb.maxX, sb.minY, sb.maxY, sb.tag
@@ -171,6 +171,15 @@ public class GocaDecoder {
return sb.segId;
}
}
for (SegmentBounds sb : segmentBoundsMap.values()) {
if (sb.contains(gx, gy, 25)) {
System.err.println(String.format(
"findPickedSegment (fallback): goca=(%d, %d) HIT segId=%d bounds=[%d..%d, %d..%d] tag=%d",
gx, gy, sb.segId, sb.minX, sb.maxX, sb.minY, sb.maxY, sb.tag
));
return sb.segId;
}
}
System.err.println(String.format("findPickedSegment: goca=(%d, %d) NO HIT (defaulting to 0/canvas)", gx, gy));
return 0;
}
@@ -798,7 +807,10 @@ public class GocaDecoder {
}
case GocaConstants.P_ERASE: { // 0x0A: Erase Graphics Presentation Space
plane.clear();
resetDefaults();
resetAttributes();
curX = 0;
curY = 0;
activeSegmentsInOrder.clear();
idx += 2;
break;
}
@@ -816,6 +828,26 @@ public class GocaDecoder {
int endSeg = (idx + 7 < end) ? (((data[idx + 6] & 0xFF) << 8) | (data[idx + 7] & 0xFF)) : startSeg;
logger.info(String.format("GOCA P_SCUDEF: flags0=0x%02x flags1=0x%02x startSeg=%d endSeg=%d",
flags0, flags1, startSeg, endSeg));
// Redraw all stored base segments not in dynamic range [startSeg..endSeg]
for (int segId : segmentOrderList) {
if (segId < startSeg || segId > endSeg) {
if (!chainedTargets.contains(segId)) {
byte[] segBytes = segmentStore.get(segId);
if (segBytes != null) {
int savedSegId = currentSegId;
currentSegId = segId;
SegmentBounds sb = segmentBoundsMap.computeIfAbsent(segId, SegmentBounds::new);
activeSegmentsInOrder.remove(sb);
activeSegmentsInOrder.add(sb);
callDepth++;
decodeStreamDirect(segBytes, 0, segBytes.length);
callDepth--;
currentSegId = savedSegId;
}
}
}
}
for (int s = startSeg; s <= endSeg; s++) {
byte[] segBytes = segmentStore.get(s);
if (segBytes != null) {
@@ -35,31 +35,19 @@ public class GraphicInputBuilder {
/**
* Builds the 56-byte Graphic Input Structured Field with picked segment ID and correlation tag.
*
* IMPORTANT ARCHITECTURE NOTE:
* Per IBM GA23-0059 / GDDM specifications:
* - Bytes 0-1: Total structured field length (0x0038 = 56 bytes).
* - Bytes 24-27: (gocaX, gocaY) cursor coordinates.
* - Bytes 28-31: Picked Segment Identifier (32-bit big-endian). When clicking on a menu item or
* interactive element (e.g. DRAW button = Segment 2, EXIT button = Segment 5), GDDM requires
* the exact segment ID in bytes 28-31. If mismatched, GDDM rejects the click with a WCC 0xF7 alarm beep.
* - Bytes 32-33: Pick Correlation Tag (16-bit big-endian) set by G_GSETAG (0x39).
* - Byte 34: Modifier flags (Shift = 0x80, Ctrl = 0x40, 0xFF for keyboard AID).
* - Byte 35: Button ID (0x01 = Button 1 / Pick, 0x02 = Button 2 / Action) or 3270 AID code.
*
* @param gocaX GOCA signed X coordinate (-xMax..+xMax)
* @param gocaY GOCA signed Y coordinate (-yMax..+yMax)
* @param buttonOrAidCode The mouse button number (1=Pick, 2=Action) or 3270 AID code for keyboard
* @param isMouseAction true if triggered directly by mouse button press, false for keyboard AID
* @param isShift true if shift key was down
* @param isCtrl true if ctrl key was down
* @param pickedSegId Picked GOCA segment ID (0 if none)
* @param pickTag Pick correlation tag
* @return 56-byte payload
*/
public static byte[] buildGraphicInput(int gocaX, int gocaY, int buttonOrAidCode,
boolean isMouseAction, boolean isShift, boolean isCtrl,
int pickedSegId, int pickTag) {
return buildGraphicInput(gocaX, gocaY, 0, 0, buttonOrAidCode, isMouseAction, isShift, isCtrl, pickedSegId, pickTag);
}
/**
* Builds the 56-byte Graphic Input Structured Field with cursor row/col, picked segment ID and correlation tag.
*/
public static byte[] buildGraphicInput(int gocaX, int gocaY, int row, int col, int buttonOrAidCode,
boolean isMouseAction, boolean isShift, boolean isCtrl,
int pickedSegId, int pickTag) {
byte[] sf = new byte[MASK.length];
System.arraycopy(MASK, 0, sf, 0, MASK.length);
@@ -67,6 +55,14 @@ public class GraphicInputBuilder {
sf[0] = (byte) ((MASK.length >> 8) & 0xFF);
sf[1] = (byte) (MASK.length & 0xFF);
// Bytes 16-19: Cursor Row & Column
if (row > 0 || col > 0) {
sf[16] = (byte) (row & 0xFF);
sf[17] = (byte) (col & 0xFF);
sf[18] = (byte) (row & 0xFF);
sf[19] = (byte) (col & 0xFF);
}
// Byte 24-25: GOCA X coordinate (signed 16-bit big-endian)
sf[24] = (byte) ((gocaX >> 8) & 0xFF);
sf[25] = (byte) (gocaX & 0xFF);
@@ -115,11 +115,6 @@ public class GraphicsPlane {
public synchronized void setScreenDimensions(int cols, int rows) {
this.screenCols = cols > 0 ? cols : 80;
this.screenRows = rows > 0 ? rows : 24;
int targetW = this.screenCols * 9;
int targetH = this.screenRows * 12;
if (this.canvasWidth != targetW || this.canvasHeight != targetH) {
resize(targetW, targetH);
}
}
public int getScreenCols() {
@@ -130,29 +125,50 @@ public class GraphicsPlane {
return screenRows;
}
public int getTotalWidth() {
int cols = screenCols > 0 ? screenCols : 80;
return cols * 9;
}
public int getTotalHeight() {
int rows = screenRows > 0 ? screenRows : 24;
return rows * 12;
}
public int getXMax() {
int totalW = getTotalWidth();
return (totalW - 1) / 2 + (totalW - 1) % 2;
}
public int getYMax() {
int totalH = getTotalHeight();
return (totalH - 1) / 2;
}
/**
* Maps a 3179G / GOCA signed coordinate (centered at screen midpoint) to canvas pixel X as a double.
* IBM 3179G / HOD presentation space coordinate range is [-xMax .. +xMax] (width = cols * 9).
*/
public double mapXDouble(double gocaX) {
int nominalWidth = screenCols * 9;
int xMax = (nominalWidth - 1) / 2 + ((nominalWidth - 1) % 2 != 0 ? 1 : 0);
int totalW = getTotalWidth();
int xMax = getXMax();
double nx = gocaX + xMax;
return (nx * canvasWidth) / (double) (nominalWidth > 0 ? nominalWidth : 1);
return (nx * canvasWidth) / (double) totalW;
}
/**
* Maps a 3179G / GOCA signed coordinate (centered at screen midpoint, bottom-up) to canvas pixel Y (top-down) as a double.
* IBM 3179G / HOD presentation space coordinate range is [-yMax .. +yMax] (height = rows * 12).
*/
public double mapYDouble(double gocaY) {
int nominalHeight = screenRows * 12;
int yMax = (nominalHeight - 1) / 2;
int totalH = getTotalHeight();
int yMax = getYMax();
double ny = yMax - gocaY;
return (ny * canvasHeight) / (double) (nominalHeight > 0 ? nominalHeight : 1);
return (ny * canvasHeight) / (double) totalH;
}
/**
* Maps a 3179G / GOCA signed coordinate (centered at screen midpoint) to canvas pixel X.
* Coordinate space is symmetric: -xMax to +xMax, where nominalWidth = cols * 9 (e.g. 720 for 80 cols).
*/
public int mapX(int gocaX) {
return (int) Math.round(mapXDouble((double) gocaX));
@@ -160,8 +176,6 @@ public class GraphicsPlane {
/**
* Maps a 3179G / GOCA signed coordinate (centered at screen midpoint, bottom-up) to canvas pixel Y (top-down).
* Coordinate space is symmetric: -yMax to +yMax, where nominalHeight = rows * 12 (e.g. 516 for 43 rows).
* NOTE: Do not apply arbitrary offsets here. The GOCA coordinate system is 1:1 synchronized with host GDDM.
*/
public int mapY(int gocaY) {
return (int) Math.round(mapYDouble((double) gocaY));
@@ -172,20 +186,20 @@ public class GraphicsPlane {
* Invariant: unmapX(mapX(x)) == x for all valid canvas pixels.
*/
public int unmapX(int px) {
int nominalWidth = screenCols * 9;
int xMax = (nominalWidth - 1) / 2 + ((nominalWidth - 1) % 2 != 0 ? 1 : 0);
int nx = (int) Math.round((double) px * nominalWidth / (canvasWidth > 0 ? canvasWidth : 1));
int totalW = getTotalWidth();
int xMax = getXMax();
int nx = (int) Math.round((double) px * totalW / (canvasWidth > 0 ? canvasWidth : 1));
return nx - xMax;
}
/**
* Maps a canvas pixel Y coordinate (top-down) back to GOCA signed coordinate (bottom-up).
* Maps a canvas pixel Y coordinate (top-down) back to GOCA signed coordinate (bottom-up, -yMax..+yMax).
* Invariant: unmapY(mapY(y)) == y for all valid canvas pixels.
*/
public int unmapY(int py) {
int nominalHeight = screenRows * 12;
int yMax = (nominalHeight - 1) / 2;
int ny = (int) Math.round((double) py * nominalHeight / (canvasHeight > 0 ? canvasHeight : 1));
int totalH = getTotalHeight();
int yMax = getYMax();
int ny = (int) Math.round((double) py * totalH / (canvasHeight > 0 ? canvasHeight : 1));
return yMax - ny;
}
@@ -324,12 +324,27 @@ public class InputProcessor {
int gy = gocaDecoder.getGraphicCursorY();
int pickedSeg = gocaDecoder.findPickedSegment(gx, gy);
int pickTag = gocaDecoder.getSegmentTag(pickedSeg);
int cursorAddr = screen != null ? screen.getCursorAddress() : 0;
int cols = (screen != null && screen.getCols() > 0) ? screen.getCols() : 80;
int row = cursorAddr / cols;
int col = cursorAddr % cols;
if (gocaDecoder.getGraphicsPlane() != null) {
int px = gocaDecoder.getGraphicsPlane().mapX(gx);
int py = gocaDecoder.getGraphicsPlane().mapY(gy);
int canvasW = gocaDecoder.getGraphicsPlane().getCanvasWidth();
int canvasH = gocaDecoder.getGraphicsPlane().getCanvasHeight();
int numRows = (screen != null && screen.getRows() > 0) ? screen.getRows() : 43;
if (canvasH > 0) row = (py * numRows) / canvasH;
if (canvasW > 0) col = (px * cols) / canvasW;
}
byte[] sf = haus.nightmare.lib3270j.graphics.GraphicInputBuilder.buildGraphicInput(
gx, gy, button, true, isShift, isCtrl, pickedSeg, pickTag
gx, gy, row, col, button, true, isShift, isCtrl, pickedSeg, pickTag
);
System.err.println(String.format(
"sendGraphicMouseAid: goca=(%d, %d) pickedSeg=%d pickTag=%d btn=%d shift=%b ctrl=%b",
gx, gy, pickedSeg, pickTag, button, isShift, isCtrl
"sendGraphicMouseAid: goca=(%d, %d) row=%d col=%d pickedSeg=%d pickTag=%d btn=%d shift=%b ctrl=%b",
gx, gy, row, col, pickedSeg, pickTag, button, isShift, isCtrl
));
out.write(AID_SF);
try {