IMON GRAPHIC!
Build and Test j3270 / Build JAR & Run Tests (Java 21) (push) Successful in 31s
Build and Test j3270 / Build JAR & Run Tests (Java 17) (push) Successful in 47s
Build and Test j3270 / Build JAR & Run Tests (Java 11) (push) Successful in 1m9s
Release j3270 / Build & Publish Release (push) Successful in 42s
Build and Test j3270 / Build JAR & Run Tests (Java 21) (push) Successful in 31s
Build and Test j3270 / Build JAR & Run Tests (Java 17) (push) Successful in 47s
Build and Test j3270 / Build JAR & Run Tests (Java 11) (push) Successful in 1m9s
Release j3270 / Build & Publish Release (push) Successful in 42s
This commit is contained in:
@@ -997,7 +997,13 @@ public class DataStreamProcessor {
|
|||||||
break;
|
break;
|
||||||
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJDATA: // 0x85: Graphics Object Data / GOCA
|
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJDATA: // 0x85: Graphics Object Data / GOCA
|
||||||
if (fieldLen > 3) {
|
if (fieldLen > 3) {
|
||||||
|
graphicsPlane.setCharDimensions(qrBuilder.getCharWidth(), qrBuilder.getCharHeight());
|
||||||
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
||||||
|
int targetW = screen.getCols() * qrBuilder.getCharWidth();
|
||||||
|
int targetH = screen.getRows() * qrBuilder.getCharHeight();
|
||||||
|
if (graphicsPlane.getCanvasWidth() != targetW || graphicsPlane.getCanvasHeight() != targetH) {
|
||||||
|
graphicsPlane.resize(targetW, targetH);
|
||||||
|
}
|
||||||
gocaDecoder.decodeStream(data, pos + 3, fieldLen - 3);
|
gocaDecoder.decodeStream(data, pos + 3, fieldLen - 3);
|
||||||
notifyScreenUpdated();
|
notifyScreenUpdated();
|
||||||
}
|
}
|
||||||
@@ -1311,9 +1317,10 @@ public class DataStreamProcessor {
|
|||||||
int orderOffset = (fieldLen >= 7) ? (offset + 7) : (offset + 4);
|
int orderOffset = (fieldLen >= 7) ? (offset + 7) : (offset + 4);
|
||||||
int orderLen = Math.max(0, fieldLen - (orderOffset - offset));
|
int orderLen = Math.max(0, fieldLen - (orderOffset - offset));
|
||||||
|
|
||||||
|
graphicsPlane.setCharDimensions(qrBuilder.getCharWidth(), qrBuilder.getCharHeight());
|
||||||
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
||||||
int targetW = screen.getCols() * 9;
|
int targetW = screen.getCols() * qrBuilder.getCharWidth();
|
||||||
int targetH = screen.getRows() * 16;
|
int targetH = screen.getRows() * qrBuilder.getCharHeight();
|
||||||
if (graphicsPlane.getCanvasWidth() != targetW || graphicsPlane.getCanvasHeight() != targetH) {
|
if (graphicsPlane.getCanvasWidth() != targetW || graphicsPlane.getCanvasHeight() != targetH) {
|
||||||
graphicsPlane.resize(targetW, targetH);
|
graphicsPlane.resize(targetW, targetH);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -424,8 +424,8 @@ public class QueryReplyBuilder {
|
|||||||
out.write((Yr_HOD >> 16) & 0xFF);
|
out.write((Yr_HOD >> 16) & 0xFF);
|
||||||
out.write((Yr_HOD >> 8) & 0xFF);
|
out.write((Yr_HOD >> 8) & 0xFF);
|
||||||
out.write(Yr_HOD & 0xFF);
|
out.write(Yr_HOD & 0xFF);
|
||||||
int charW = getCharWidth();
|
int charW = getCharWidth(maxRows);
|
||||||
int charH = getCharHeight();
|
int charH = getCharHeight(maxRows);
|
||||||
out.write(charW); // AW
|
out.write(charW); // AW
|
||||||
out.write(charH); // AH
|
out.write(charH); // AH
|
||||||
int buf = maxCols * maxRows;
|
int buf = maxCols * maxRows;
|
||||||
@@ -435,6 +435,11 @@ public class QueryReplyBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getCharWidth() {
|
public int getCharWidth() {
|
||||||
|
int rows = (screen != null) ? screen.getMaxRows() : MODEL_2_ROWS;
|
||||||
|
return getCharWidth(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCharWidth(int rows) {
|
||||||
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
||||||
return 12;
|
return 12;
|
||||||
}
|
}
|
||||||
@@ -442,18 +447,11 @@ public class QueryReplyBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getCharHeight() {
|
public int getCharHeight() {
|
||||||
// ARCHITECTURAL NOTE ON 3179G GOCA VERTICAL ALIGNMENT & QUERY REPLIES:
|
int rows = (screen != null) ? screen.getMaxRows() : MODEL_2_ROWS;
|
||||||
// Why hardcoding SH = 12 (0x0C) in Character Sets & Usable Area failed in past iterations:
|
return getCharHeight(rows);
|
||||||
// When SDH/AH is declared as 12 (0x0C) in Query Reply, the mainframe host GDDM engine computes
|
}
|
||||||
// total presentation space as rows * 12 (e.g. 43 * 12 = 516 units, yMax = 257).
|
|
||||||
// GDDM then places the top menu bar at Row 1 (gy = 187..200).
|
public int getCharHeight(int rows) {
|
||||||
// Meanwhile, the client emulator rendered into a 16-pitch grid (43 * 16 = 688 units, yMax = 343).
|
|
||||||
// On a 688-unit canvas, gy = 200 mapped to Row 9.2 (middle of the screen), leaving a massive void above.
|
|
||||||
// When the user clicked on the visual menu drawn at Row 9, the client emitted gy = 189 with cursor at Row 9,
|
|
||||||
// which GDDM rejected as outside its menu hit box (causing terminal alarm beeps).
|
|
||||||
//
|
|
||||||
// Solution: Declare SDH = 16 (0x10) when Vector Graphics is enabled (3179G standard), ensuring host GDDM
|
|
||||||
// and client GraphicsPlane share the exact same 16-pitch presentation space (720x688, yMax = 343).
|
|
||||||
return graphicsMode.isVectorGraphicsEnabled() ? 0x10 : SH_3279_2;
|
return graphicsMode.isVectorGraphicsEnabled() ? 0x10 : SH_3279_2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -395,13 +395,16 @@ public class GocaDecoder {
|
|||||||
if (order == GocaConstants.G_NOP1 || order == 0xFF || order == 0x00 || order == GocaConstants.G_COMT) {
|
if (order == GocaConstants.G_NOP1 || order == 0xFF || order == 0x00 || order == GocaConstants.G_COMT) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
// Orders with optional 0x00 trailing length/qualifier byte (e.g. 3E 00, 71 00, 60 00, 7E 00, 3F 00, 93 00, 91 00)
|
// Orders with optional 0x00 trailing length/qualifier byte (e.g. 3E 00, 71 00, 60 00, 7E 00, 3F 00)
|
||||||
if (order == GocaConstants.G_ENDPROLOGUE || order == GocaConstants.G_ENDSEGM ||
|
if (order == GocaConstants.G_ENDPROLOGUE || order == GocaConstants.G_ENDSEGM ||
|
||||||
order == GocaConstants.G_GEAR || order == GocaConstants.G_GERASE ||
|
order == GocaConstants.G_GEAR || order == GocaConstants.G_GERASE ||
|
||||||
order == GocaConstants.G_GPOP || order == GocaConstants.G_GEIMG ||
|
order == GocaConstants.G_GPOP) {
|
||||||
(inImage && order == GocaConstants.G_GEIMG_ALT)) {
|
|
||||||
return (idx + 1 < end && data[idx + 1] == 0x00) ? 2 : 1;
|
return (idx + 1 < end && data[idx + 1] == 0x00) ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
// G_GEIMG (0x93 End Image): self-defining draw order (e.g. 93 02 00 00 or 93 00 or 93)
|
||||||
|
if (order == GocaConstants.G_GEIMG) {
|
||||||
|
return (idx + 1 < end) ? ((data[idx + 1] & 0xFF) + 2) : 1;
|
||||||
|
}
|
||||||
if (idx + 1 >= end) {
|
if (idx + 1 >= end) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -552,18 +555,33 @@ public class GocaDecoder {
|
|||||||
idx += orderLen;
|
idx += orderLen;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GocaConstants.G_GEIMG: // 0x93
|
case GocaConstants.G_GEIMG: { // 0x93 End Image
|
||||||
case GocaConstants.G_GEIMG_ALT: { // 0x91
|
|
||||||
if (inImage || order == GocaConstants.G_GEIMG) {
|
|
||||||
endImage();
|
endImage();
|
||||||
idx += orderLen;
|
idx += orderLen;
|
||||||
} else {
|
break;
|
||||||
// 0x91: Begin Image at Current Position (G_GBIMGC)
|
}
|
||||||
if (payloadLen >= 4 && idx + 2 + payloadLen <= end) {
|
case GocaConstants.G_GBIMGC: { // 0x91: Begin Image at Current Position (G_GBIMGC)
|
||||||
int w = readCoord(inputData, idx + 2);
|
if (inImage) {
|
||||||
int h = readCoord(inputData, idx + 4);
|
endImage();
|
||||||
|
}
|
||||||
|
int w = 0, h = 0;
|
||||||
int bitDepth = GocaConstants.BPP_1;
|
int bitDepth = GocaConstants.BPP_1;
|
||||||
int compression = GocaConstants.IMG_UNCOMPRESSED;
|
int compression = GocaConstants.IMG_UNCOMPRESSED;
|
||||||
|
if (payloadLen >= 6 && idx + 2 + payloadLen <= end) {
|
||||||
|
w = readCoord(inputData, idx + 4);
|
||||||
|
h = readCoord(inputData, idx + 6);
|
||||||
|
if (payloadLen >= 7) {
|
||||||
|
int fmt = inputData[idx + 8] & 0xFF;
|
||||||
|
if (fmt == 2) bitDepth = GocaConstants.BPP_2;
|
||||||
|
else if (fmt == 4) bitDepth = GocaConstants.BPP_4;
|
||||||
|
else if (fmt == 8) bitDepth = GocaConstants.BPP_8;
|
||||||
|
}
|
||||||
|
if (payloadLen >= 8) {
|
||||||
|
compression = inputData[idx + 9] & 0xFF;
|
||||||
|
}
|
||||||
|
} else if (payloadLen >= 4 && idx + 2 + payloadLen <= end) {
|
||||||
|
w = readCoord(inputData, idx + 2);
|
||||||
|
h = readCoord(inputData, idx + 4);
|
||||||
if (payloadLen >= 5) {
|
if (payloadLen >= 5) {
|
||||||
int fmt = inputData[idx + 6] & 0xFF;
|
int fmt = inputData[idx + 6] & 0xFF;
|
||||||
if (fmt == 2) bitDepth = GocaConstants.BPP_2;
|
if (fmt == 2) bitDepth = GocaConstants.BPP_2;
|
||||||
@@ -573,10 +591,9 @@ public class GocaDecoder {
|
|||||||
if (payloadLen >= 6) {
|
if (payloadLen >= 6) {
|
||||||
compression = inputData[idx + 7] & 0xFF;
|
compression = inputData[idx + 7] & 0xFF;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
beginImage(curX, curY, w, h, bitDepth, compression);
|
beginImage(curX, curY, w, h, bitDepth, compression);
|
||||||
}
|
|
||||||
idx += orderLen;
|
idx += orderLen;
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GocaConstants.G_BEGSEGM: { // Begin Segment (0x70)
|
case GocaConstants.G_BEGSEGM: { // Begin Segment (0x70)
|
||||||
@@ -986,13 +1003,30 @@ public class GocaDecoder {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GocaConstants.G_GBIMG: { // Begin Image (0xD1)
|
case GocaConstants.G_GBIMG: { // Begin Image (0xD1)
|
||||||
|
if (inImage) {
|
||||||
|
endImage();
|
||||||
|
}
|
||||||
if (payloadLen >= 8 && idx + 2 + payloadLen <= end) {
|
if (payloadLen >= 8 && idx + 2 + payloadLen <= end) {
|
||||||
int x = readCoord(inputData, idx + 2);
|
int x = readCoord(inputData, idx + 2);
|
||||||
int y = readCoord(inputData, idx + 4);
|
int y = readCoord(inputData, idx + 4);
|
||||||
int w = readCoord(inputData, idx + 6);
|
int w, h;
|
||||||
int h = readCoord(inputData, idx + 8);
|
|
||||||
int bitDepth = GocaConstants.BPP_1;
|
int bitDepth = GocaConstants.BPP_1;
|
||||||
int compression = GocaConstants.IMG_UNCOMPRESSED;
|
int compression = GocaConstants.IMG_UNCOMPRESSED;
|
||||||
|
if (payloadLen >= 10) {
|
||||||
|
w = readCoord(inputData, idx + 8);
|
||||||
|
h = readCoord(inputData, idx + 10);
|
||||||
|
if (payloadLen >= 11) {
|
||||||
|
int fmt = inputData[idx + 12] & 0xFF;
|
||||||
|
if (fmt == 2) bitDepth = GocaConstants.BPP_2;
|
||||||
|
else if (fmt == 4) bitDepth = GocaConstants.BPP_4;
|
||||||
|
else if (fmt == 8) bitDepth = GocaConstants.BPP_8;
|
||||||
|
}
|
||||||
|
if (payloadLen >= 12) {
|
||||||
|
compression = inputData[idx + 13] & 0xFF;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w = readCoord(inputData, idx + 6);
|
||||||
|
h = readCoord(inputData, idx + 8);
|
||||||
if (payloadLen >= 9) {
|
if (payloadLen >= 9) {
|
||||||
int fmt = inputData[idx + 10] & 0xFF;
|
int fmt = inputData[idx + 10] & 0xFF;
|
||||||
if (fmt == 2) bitDepth = GocaConstants.BPP_2;
|
if (fmt == 2) bitDepth = GocaConstants.BPP_2;
|
||||||
@@ -1002,6 +1036,7 @@ public class GocaDecoder {
|
|||||||
if (payloadLen >= 10) {
|
if (payloadLen >= 10) {
|
||||||
compression = inputData[idx + 11] & 0xFF;
|
compression = inputData[idx + 11] & 0xFF;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
beginImage(x, y, w, h, bitDepth, compression);
|
beginImage(x, y, w, h, bitDepth, compression);
|
||||||
}
|
}
|
||||||
idx += orderLen;
|
idx += orderLen;
|
||||||
|
|||||||
@@ -418,9 +418,27 @@ public class GraphicsPlane implements PixelBuffer {
|
|||||||
return transform;
|
return transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Character cell dimensions matching Query Reply presentation space
|
||||||
|
private int charWidth = 9;
|
||||||
|
private int charHeight = 16;
|
||||||
|
|
||||||
|
public synchronized void setCharDimensions(int width, int height) {
|
||||||
|
if (width > 0) this.charWidth = width;
|
||||||
|
if (height > 0) this.charHeight = height;
|
||||||
|
this.transform.setDefaultCharMetrics(this.charWidth, this.charHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCharWidth() {
|
||||||
|
return charWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCharHeight() {
|
||||||
|
return charHeight;
|
||||||
|
}
|
||||||
|
|
||||||
public int getTotalWidth() {
|
public int getTotalWidth() {
|
||||||
int cols = screenCols > 0 ? screenCols : 80;
|
int cols = screenCols > 0 ? screenCols : 80;
|
||||||
return cols * 9;
|
return cols * charWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -457,7 +475,7 @@ public class GraphicsPlane implements PixelBuffer {
|
|||||||
*/
|
*/
|
||||||
public int getTotalHeight() {
|
public int getTotalHeight() {
|
||||||
int rows = screenRows > 0 ? screenRows : 24;
|
int rows = screenRows > 0 ? screenRows : 24;
|
||||||
return rows * 16;
|
return rows * charHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getXMax() {
|
public int getXMax() {
|
||||||
|
|||||||
@@ -285,6 +285,79 @@ public class GocaDecoderTest {
|
|||||||
assertTrue(plane.hasContent(), "Expected plane to have content after image decoding");
|
assertTrue(plane.hasContent(), "Expected plane to have content after image decoding");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testImonGddmImageRendering() throws Exception {
|
||||||
|
GraphicsPlane plane = new GraphicsPlane(1040, 1118);
|
||||||
|
plane.setCharDimensions(13, 26);
|
||||||
|
plane.setScreenDimensions(80, 43);
|
||||||
|
GocaDecoder decoder = new GocaDecoder(plane);
|
||||||
|
|
||||||
|
assertEquals(1040, plane.getTotalWidth());
|
||||||
|
assertEquals(1118, plane.getTotalHeight());
|
||||||
|
assertEquals(520, plane.getXMax());
|
||||||
|
assertEquals(558, plane.getYMax());
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
// GSCP: Set Current Position (377, 494)
|
||||||
|
out.write(GocaConstants.G_GSCP);
|
||||||
|
out.write(0x04);
|
||||||
|
out.write(0x01); out.write(0x79); // X = 377
|
||||||
|
out.write(0x01); out.write(0xEE); // Y = 494
|
||||||
|
|
||||||
|
// GSCOL: Color 1 = Blue
|
||||||
|
out.write(GocaConstants.G_GSCOL);
|
||||||
|
out.write(0x01);
|
||||||
|
|
||||||
|
// G_GBIMGC (0x91): Begin Image Current Position: len=6, flags=0, w=80, h=80
|
||||||
|
out.write(GocaConstants.G_GBIMGC);
|
||||||
|
out.write(0x06);
|
||||||
|
out.write(0x00); out.write(0x00); // flags/format
|
||||||
|
out.write(0x00); out.write(0x50); // w = 80
|
||||||
|
out.write(0x00); out.write(0x50); // h = 80
|
||||||
|
|
||||||
|
// G_GIMD (0x92): 10 bytes scanline
|
||||||
|
out.write(GocaConstants.G_GIMD);
|
||||||
|
out.write(0x0A);
|
||||||
|
out.write(new byte[] { (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF });
|
||||||
|
|
||||||
|
// G_GEIMG (0x93): End Image with 2-byte operand (02 00 00 as sent by IMON)
|
||||||
|
out.write(GocaConstants.G_GEIMG);
|
||||||
|
out.write(0x02);
|
||||||
|
out.write(0x00); out.write(0x00);
|
||||||
|
|
||||||
|
// Subsequent order to verify stream synchronization: GSCOL Color 2 = Red
|
||||||
|
out.write(GocaConstants.G_GSCOL);
|
||||||
|
out.write(0x02);
|
||||||
|
|
||||||
|
// G_GBIMGC (0x91): Second image tile
|
||||||
|
out.write(GocaConstants.G_GBIMGC);
|
||||||
|
out.write(0x06);
|
||||||
|
out.write(0x00); out.write(0x00);
|
||||||
|
out.write(0x00); out.write(0x50);
|
||||||
|
out.write(0x00); out.write(0x50);
|
||||||
|
|
||||||
|
out.write(GocaConstants.G_GIMD);
|
||||||
|
out.write(0x0A);
|
||||||
|
out.write(new byte[] { (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA });
|
||||||
|
|
||||||
|
out.write(GocaConstants.G_GEIMG);
|
||||||
|
out.write(0x02);
|
||||||
|
out.write(0x00); out.write(0x00);
|
||||||
|
|
||||||
|
byte[] stream = out.toByteArray();
|
||||||
|
decoder.decodeStream(stream, 0, stream.length);
|
||||||
|
|
||||||
|
assertTrue(plane.hasContent(), "Expected plane to have content after IMON GOCA decoding");
|
||||||
|
int mappedX = plane.mapX(377);
|
||||||
|
int mappedY = plane.mapY(494);
|
||||||
|
assertEquals(897, mappedX);
|
||||||
|
assertEquals(64, mappedY);
|
||||||
|
|
||||||
|
int[] rgb = plane.getRgbBuffer();
|
||||||
|
int pixelAtImg = rgb[mappedY * 1040 + mappedX];
|
||||||
|
assertNotEquals(0, pixelAtImg, "Pixel at image location should be rendered");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDirectObjectControlSf24() {
|
public void testDirectObjectControlSf24() {
|
||||||
haus.nightmare.lib3270j.screen.ScreenBuffer sb = new haus.nightmare.lib3270j.screen.ScreenBuffer(
|
haus.nightmare.lib3270j.screen.ScreenBuffer sb = new haus.nightmare.lib3270j.screen.ScreenBuffer(
|
||||||
|
|||||||
Reference in New Issue
Block a user