This commit is contained in:
@@ -87,3 +87,63 @@ echo "=== Build Complete ==="
|
|||||||
echo "Executable JAR created: $BUILD_DIR/j3270.jar"
|
echo "Executable JAR created: $BUILD_DIR/j3270.jar"
|
||||||
ls -lh "$BUILD_DIR/j3270.jar"
|
ls -lh "$BUILD_DIR/j3270.jar"
|
||||||
|
|
||||||
|
# 5. Compile and run tests if requested
|
||||||
|
if [ "$1" = "test" ] || [ "$1" = "check" ]; then
|
||||||
|
echo "=== Running Tests ==="
|
||||||
|
TEST_BUILD_DIR="$BUILD_DIR/test-classes"
|
||||||
|
mkdir -p "$TEST_BUILD_DIR"
|
||||||
|
JUNIT_CP=$(find "$HOME/.gradle/caches/modules-2/files-2.1/" -name "*.jar" 2>/dev/null | grep -E "junit|opentest|apiguardian" | tr "\n" ":" || true)
|
||||||
|
FULL_CP="$BUILD_DIR/lib3270j:$BUILD_DIR/j3270:$JUNIT_CP"
|
||||||
|
|
||||||
|
find "$SCRIPT_DIR/lib3270j/src/test/java" -name "*.java" > "$BUILD_DIR/test_sources.txt" 2>/dev/null || true
|
||||||
|
if [ -d "$SCRIPT_DIR/j3270/src/test/java" ]; then
|
||||||
|
find "$SCRIPT_DIR/j3270/src/test/java" -name "*.java" >> "$BUILD_DIR/test_sources.txt" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -s "$BUILD_DIR/test_sources.txt" ] && [ -n "$JUNIT_CP" ]; then
|
||||||
|
echo "Compiling tests..."
|
||||||
|
"$JAVAC_BIN" -cp "$FULL_CP" -d "$TEST_BUILD_DIR" @"$BUILD_DIR/test_sources.txt"
|
||||||
|
rm -f "$BUILD_DIR/test_sources.txt"
|
||||||
|
|
||||||
|
echo "Executing tests with JUnit Platform..."
|
||||||
|
cat << 'EOF' > "$BUILD_DIR/TestRunner.java"
|
||||||
|
import org.junit.platform.launcher.*;
|
||||||
|
import org.junit.platform.launcher.core.*;
|
||||||
|
import org.junit.platform.launcher.listeners.*;
|
||||||
|
import org.junit.platform.engine.discovery.*;
|
||||||
|
import static org.junit.platform.engine.discovery.DiscoverySelectors.*;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
public class TestRunner {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
|
||||||
|
.selectors(
|
||||||
|
selectPackage("haus.nightmare.lib3270j.graphics"),
|
||||||
|
selectPackage("haus.nightmare.lib3270j.datastream"),
|
||||||
|
selectPackage("haus.nightmare.lib3270j.screen"),
|
||||||
|
selectPackage("haus.nightmare.lib3270j.protocol")
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Launcher launcher = LauncherFactory.create();
|
||||||
|
SummaryGeneratingListener listener = new SummaryGeneratingListener();
|
||||||
|
launcher.registerTestExecutionListeners(listener);
|
||||||
|
launcher.execute(request);
|
||||||
|
|
||||||
|
TestExecutionSummary summary = listener.getSummary();
|
||||||
|
summary.printTo(new PrintWriter(System.out));
|
||||||
|
summary.printFailuresTo(new PrintWriter(System.err));
|
||||||
|
|
||||||
|
if (summary.getTotalFailureCount() > 0) {
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
"$JAVAC_BIN" -cp "$FULL_CP:$TEST_BUILD_DIR" -d "$BUILD_DIR" "$BUILD_DIR/TestRunner.java"
|
||||||
|
JAVA_BIN_EXEC="${JAVA_HOME:+$JAVA_HOME/bin/java}"
|
||||||
|
JAVA_BIN_EXEC="${JAVA_BIN_EXEC:-java}"
|
||||||
|
"$JAVA_BIN_EXEC" -cp "$FULL_CP:$TEST_BUILD_DIR:$BUILD_DIR" TestRunner
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
exec "$(dirname "$0")/build_all.sh"
|
exec "$(dirname "$0")/build_all.sh" "$@"
|
||||||
|
|||||||
@@ -501,9 +501,9 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
consoleHandler.setFilter(appFilter);
|
consoleHandler.setFilter(appFilter);
|
||||||
globalRoot.addHandler(consoleHandler);
|
globalRoot.addHandler(consoleHandler);
|
||||||
|
|
||||||
Logger.getLogger("haus.nightmare").setLevel(logLevel);
|
Logger.getLogger("haus.nightmare").setLevel(Level.ALL);
|
||||||
Logger.getLogger("haus.nightmare.j3270").setLevel(logLevel);
|
Logger.getLogger("haus.nightmare.j3270").setLevel(Level.ALL);
|
||||||
Logger.getLogger("haus.nightmare.lib3270j").setLevel(logLevel);
|
Logger.getLogger("haus.nightmare.lib3270j").setLevel(Level.ALL);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
java.util.logging.FileHandler fileHandler = new java.util.logging.FileHandler("j3270.log", 10 * 1024 * 1024, 1, false) {
|
java.util.logging.FileHandler fileHandler = new java.util.logging.FileHandler("j3270.log", 10 * 1024 * 1024, 1, false) {
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ public class DataStreamProcessor {
|
|||||||
this.outputBuffer = new byte[32768];
|
this.outputBuffer = new byte[32768];
|
||||||
this.outputPos = 0;
|
this.outputPos = 0;
|
||||||
this.gocaDecoder.setProgramSymbolManager(programSymbolManager);
|
this.gocaDecoder.setProgramSymbolManager(programSymbolManager);
|
||||||
|
this.graphicsPlane.setProgramSymbolManager(programSymbolManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryReplyBuilder getQueryReplyBuilder() {
|
public QueryReplyBuilder getQueryReplyBuilder() {
|
||||||
@@ -838,6 +839,13 @@ public class DataStreamProcessor {
|
|||||||
int orderOffset = (fieldLen >= 7) ? (pos + 7) : (pos + 4);
|
int orderOffset = (fieldLen >= 7) ? (pos + 7) : (pos + 4);
|
||||||
int orderLen = Math.max(0, fieldLen - (orderOffset - pos));
|
int orderLen = Math.max(0, fieldLen - (orderOffset - pos));
|
||||||
|
|
||||||
|
StringBuilder hexDump = new StringBuilder();
|
||||||
|
for (int i = 0; i < Math.min(32, fieldLen); i++) {
|
||||||
|
hexDump.append(String.format("%02x ", data[pos + i] & 0xFF));
|
||||||
|
}
|
||||||
|
log.info(String.format("SF 0x0F sub=0x%02x len=%d flags=0x%02x orderOffset=+%d orderLen=%d bytes=[%s]",
|
||||||
|
sfSubId, fieldLen, flags, (orderOffset - pos), orderLen, hexDump.toString().trim()));
|
||||||
|
|
||||||
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
||||||
|
|
||||||
if (flags == 0x80) { // SPAN_FIRST
|
if (flags == 0x80) { // SPAN_FIRST
|
||||||
@@ -856,6 +864,7 @@ public class DataStreamProcessor {
|
|||||||
}
|
}
|
||||||
byte[] fullStream = gocaAccumulator.toByteArray();
|
byte[] fullStream = gocaAccumulator.toByteArray();
|
||||||
gocaAccumulator.reset();
|
gocaAccumulator.reset();
|
||||||
|
log.info(String.format("GOCA stream SPAN_LAST assembled: %d bytes", fullStream.length));
|
||||||
if (currentGocaSubtype == haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJCNTL_SUB) {
|
if (currentGocaSubtype == haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJCNTL_SUB) {
|
||||||
gocaDecoder.processProcedureOrders(fullStream, 0, fullStream.length);
|
gocaDecoder.processProcedureOrders(fullStream, 0, fullStream.length);
|
||||||
} else {
|
} else {
|
||||||
@@ -864,6 +873,7 @@ public class DataStreamProcessor {
|
|||||||
notifyScreenUpdated();
|
notifyScreenUpdated();
|
||||||
} else { // SPAN_ONLY (0xC0)
|
} else { // SPAN_ONLY (0xC0)
|
||||||
gocaAccumulator.reset();
|
gocaAccumulator.reset();
|
||||||
|
log.info(String.format("GOCA stream SPAN_ONLY: %d bytes", orderLen));
|
||||||
if (sfSubId == haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJCNTL_SUB) {
|
if (sfSubId == haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJCNTL_SUB) {
|
||||||
gocaDecoder.processProcedureOrders(data, orderOffset, orderLen);
|
gocaDecoder.processProcedureOrders(data, orderOffset, orderLen);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class GocaDecoder {
|
|||||||
private int curX = 0;
|
private int curX = 0;
|
||||||
private int curY = 0;
|
private int curY = 0;
|
||||||
private int curColor = GocaConstants.GOCA_COLORS[0];
|
private int curColor = GocaConstants.GOCA_COLORS[0];
|
||||||
private int bgMix = 2; // BMX_OVERPAINT default
|
private int bgMix = 0; // BMX_DEFAULT (MIX_LEAVE / transparent background mix per GOCA spec)
|
||||||
private int bgColor = GocaConstants.GOCA_COLORS[8]; // Black
|
private int bgColor = GocaConstants.GOCA_COLORS[8]; // Black
|
||||||
private int lineType = GocaConstants.LT_SOLID;
|
private int lineType = GocaConstants.LT_SOLID;
|
||||||
private int lineWidth = GocaConstants.LW_NORMAL;
|
private int lineWidth = GocaConstants.LW_NORMAL;
|
||||||
@@ -28,6 +28,7 @@ public class GocaDecoder {
|
|||||||
private int markerSize = 5;
|
private int markerSize = 5;
|
||||||
private int markerColor = GocaConstants.GOCA_COLORS[0];
|
private int markerColor = GocaConstants.GOCA_COLORS[0];
|
||||||
private int pattern = GocaConstants.PT_SOLID;
|
private int pattern = GocaConstants.PT_SOLID;
|
||||||
|
private int patternSet = 0;
|
||||||
private int fillColor = GocaConstants.GOCA_COLORS[0];
|
private int fillColor = GocaConstants.GOCA_COLORS[0];
|
||||||
private int charDir = GocaConstants.CD_LR;
|
private int charDir = GocaConstants.CD_LR;
|
||||||
private double charAngle = 0.0;
|
private double charAngle = 0.0;
|
||||||
@@ -99,7 +100,7 @@ public class GocaDecoder {
|
|||||||
private final java.util.List<SegmentBounds> activeSegmentsInOrder = new java.util.ArrayList<>();
|
private final java.util.List<SegmentBounds> activeSegmentsInOrder = new java.util.ArrayList<>();
|
||||||
private int currentSegId = 0;
|
private int currentSegId = 0;
|
||||||
|
|
||||||
// Graphic Cursor (Light-Pen) state
|
// Graphics Cursor state
|
||||||
private boolean graphicsCursorActive = false;
|
private boolean graphicsCursorActive = false;
|
||||||
private int graphicCursorX = 0;
|
private int graphicCursorX = 0;
|
||||||
private int graphicCursorY = 0;
|
private int graphicCursorY = 0;
|
||||||
@@ -110,6 +111,9 @@ public class GocaDecoder {
|
|||||||
|
|
||||||
public void setProgramSymbolManager(ProgramSymbolManager psm) {
|
public void setProgramSymbolManager(ProgramSymbolManager psm) {
|
||||||
this.programSymbolManager = psm;
|
this.programSymbolManager = psm;
|
||||||
|
if (this.plane != null) {
|
||||||
|
this.plane.setProgramSymbolManager(psm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public GraphicsPlane getGraphicsPlane() {
|
public GraphicsPlane getGraphicsPlane() {
|
||||||
@@ -203,7 +207,7 @@ public class GocaDecoder {
|
|||||||
|
|
||||||
public synchronized void resetAttributes() {
|
public synchronized void resetAttributes() {
|
||||||
curColor = getColor(0);
|
curColor = getColor(0);
|
||||||
bgMix = 2; // BMX_OVERPAINT
|
bgMix = 0; // BMX_DEFAULT (MIX_LEAVE / transparent background mix per GOCA spec)
|
||||||
bgColor = GocaConstants.GOCA_COLORS[8]; // Black
|
bgColor = GocaConstants.GOCA_COLORS[8]; // Black
|
||||||
lineType = GocaConstants.LT_SOLID;
|
lineType = GocaConstants.LT_SOLID;
|
||||||
lineWidth = GocaConstants.LW_NORMAL;
|
lineWidth = GocaConstants.LW_NORMAL;
|
||||||
@@ -211,6 +215,7 @@ public class GocaDecoder {
|
|||||||
markerSize = 5;
|
markerSize = 5;
|
||||||
markerColor = curColor;
|
markerColor = curColor;
|
||||||
pattern = GocaConstants.PT_SOLID;
|
pattern = GocaConstants.PT_SOLID;
|
||||||
|
patternSet = 0;
|
||||||
fillColor = curColor;
|
fillColor = curColor;
|
||||||
charDir = GocaConstants.CD_LR;
|
charDir = GocaConstants.CD_LR;
|
||||||
charAngle = 0.0;
|
charAngle = 0.0;
|
||||||
@@ -232,30 +237,26 @@ public class GocaDecoder {
|
|||||||
*
|
*
|
||||||
* IMPORTANT ARCHITECTURE & PARSING SAFETY NOTE:
|
* IMPORTANT ARCHITECTURE & PARSING SAFETY NOTE:
|
||||||
* GOCA orders follow IBM GA23-0059 and Host On-Demand (HOD) architecture rules:
|
* GOCA orders follow IBM GA23-0059 and Host On-Demand (HOD) architecture rules:
|
||||||
* 1. 1-byte standalone orders (NOP, ERASE, etc.) -> length 1.
|
* 1. 1-byte standalone orders (NOP, ERASE, GEAR, ENDSEGM, ENDPROLOGUE, GEIMG, GPOP) -> length 1.
|
||||||
* 2. Delimiter orders (GEAR, ENDSEGM, ENDPROLOGUE, GEIMG, GPOP) -> 1 or 2 bytes (with 0x00 trailing byte).
|
* 2. Fixed 1-byte operand orders (0x00..0x1F range: GSCOL, GSLT, GSLW, GSMS, GSMC, GSPS, GSBMX) -> 2 bytes.
|
||||||
* 3. Fixed 1-byte operand orders (0x00..0x1F range: GSCOL, GSLT, GSLW, GSMS, GSMC, GSPS, GSBMX) -> 2 bytes.
|
* 3. Flexible 1-byte attribute orders (GBAR 0x68, GSCS 0x38, GSCD 0x3A, GSCC 0x3B, GSMP 0x39,
|
||||||
* 4. Flexible 1-byte attribute orders (GBAR 0x68, GSCS 0x38, GSCD 0x3A, GSCC 0x3B, GSMP 0x39,
|
|
||||||
* GSPT 0x28, GSMT 0x29, GSMS_SET 0x3C) can be transmitted either as:
|
* GSPT 0x28, GSMT 0x29, GSMS_SET 0x3C) can be transmitted either as:
|
||||||
* - Short 2-byte form: [opcode] [value] (e.g. GBAR with flags 0x80 -> '68 80')
|
* - Short 2-byte form: [opcode] [value] (e.g. GBAR with flags 0x80 -> '68 80')
|
||||||
* - Self-defining 3-byte form: [opcode] [length=0x01] [value] (e.g. '68 01 80')
|
* - Self-defining 3-byte form: [opcode] [length=0x01] [value] (e.g. '68 01 80')
|
||||||
* CRITICAL: NEVER allow 1-byte attribute orders (like GBAR 0x68 or GSCC 0x3B) to fall through to the
|
* CRITICAL: NEVER allow 1-byte attribute orders (like GBAR 0x68 or GSCC 0x3B) to fall through to the
|
||||||
* variable-length formula `(data[idx + 1] & 0xFF) + 2`. If GBAR flags (e.g. 0x80 for boundary) are read as
|
* variable-length formula `(data[idx + 1] & 0xFF) + 2`. If GBAR flags (e.g. 0x80 for boundary) are read as
|
||||||
* a length field, the decoder will skip 130 bytes, corrupting and skipping all subsequent drawing orders!
|
* a length field, the decoder will skip 130 bytes, corrupting and skipping all subsequent drawing orders!
|
||||||
* 5. Self-defining orders with multi-byte payloads (GLINE 0xC1, GARC 0xC6, GCHST 0xC3, GRLINE 0xE1, etc.)
|
* 4. Self-defining orders with multi-byte payloads (GLINE 0xC1, GARC 0xC6, GCHST 0xC3, GRLINE 0xE1, etc.)
|
||||||
* have a 1-byte length byte at data[idx + 1], making total length = payloadLen + 2.
|
* have a 1-byte length byte at data[idx + 1], making total length = payloadLen + 2.
|
||||||
*/
|
*/
|
||||||
private int getOrderLength(byte[] data, int idx, int end) {
|
private int getOrderLength(byte[] data, int idx, int end) {
|
||||||
int order = data[idx] & 0xFF;
|
int order = data[idx] & 0xFF;
|
||||||
if (order == GocaConstants.G_NOP1 || order == 0xFF || order == 0x00) {
|
if (order == GocaConstants.G_NOP1 || order == 0xFF || order == 0x00 ||
|
||||||
|
order == GocaConstants.G_GEAR || order == GocaConstants.G_ENDSEGM ||
|
||||||
|
order == GocaConstants.G_ENDPROLOGUE || order == GocaConstants.G_GEIMG ||
|
||||||
|
order == GocaConstants.G_GPOP || order == GocaConstants.G_GERASE) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (order == GocaConstants.G_GEAR ||
|
|
||||||
order == GocaConstants.G_ENDSEGM || order == GocaConstants.G_ENDPROLOGUE ||
|
|
||||||
order == GocaConstants.G_GEIMG || order == GocaConstants.G_GPOP ||
|
|
||||||
order == GocaConstants.G_GERASE) {
|
|
||||||
return (idx + 1 < end && data[idx + 1] == 0x00) ? 2 : 1;
|
|
||||||
}
|
|
||||||
if (idx + 1 >= end) {
|
if (idx + 1 >= end) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -399,8 +400,11 @@ public class GocaDecoder {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GocaConstants.G_BEGSEGM: { // Begin Segment (0x70)
|
case GocaConstants.G_BEGSEGM: { // Begin Segment (0x70)
|
||||||
|
int segId = 0;
|
||||||
|
int flag0 = 0;
|
||||||
|
int flag1 = 0;
|
||||||
if (idx + 5 < end) {
|
if (idx + 5 < end) {
|
||||||
int segId = ((inputData[idx + 2] & 0xFF) << 24) |
|
segId = ((inputData[idx + 2] & 0xFF) << 24) |
|
||||||
((inputData[idx + 3] & 0xFF) << 16) |
|
((inputData[idx + 3] & 0xFF) << 16) |
|
||||||
((inputData[idx + 4] & 0xFF) << 8) |
|
((inputData[idx + 4] & 0xFF) << 8) |
|
||||||
(inputData[idx + 5] & 0xFF);
|
(inputData[idx + 5] & 0xFF);
|
||||||
@@ -409,13 +413,17 @@ public class GocaDecoder {
|
|||||||
activeSegmentsInOrder.remove(sb);
|
activeSegmentsInOrder.remove(sb);
|
||||||
activeSegmentsInOrder.add(sb);
|
activeSegmentsInOrder.add(sb);
|
||||||
}
|
}
|
||||||
if (idx + 7 < end && (inputData[idx + 7] & 0x06) == 0) {
|
if (idx + 6 < end) flag0 = inputData[idx + 6] & 0xFF;
|
||||||
|
if (idx + 7 < end) flag1 = inputData[idx + 7] & 0xFF;
|
||||||
|
logger.info(String.format("GOCA BEGSEGM: segId=%d flag0=0x%02x flag1=0x%02x callDepth=%d", segId, flag0, flag1, callDepth));
|
||||||
|
if (idx + 7 < end && (flag1 & 0x06) == 0) {
|
||||||
resetAttributes();
|
resetAttributes();
|
||||||
}
|
}
|
||||||
idx += orderLen;
|
idx += orderLen;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GocaConstants.G_ENDSEGM: { // End Segment (0x71)
|
case GocaConstants.G_ENDSEGM: { // End Segment (0x71)
|
||||||
|
logger.info(String.format("GOCA ENDSEGM: segId=%d", currentSegId));
|
||||||
if (currentSegId != 0) {
|
if (currentSegId != 0) {
|
||||||
SegmentBounds sb = segmentBoundsMap.get(currentSegId);
|
SegmentBounds sb = segmentBoundsMap.get(currentSegId);
|
||||||
if (sb != null) {
|
if (sb != null) {
|
||||||
@@ -486,6 +494,7 @@ public class GocaDecoder {
|
|||||||
} else {
|
} else {
|
||||||
targetSegId = -1;
|
targetSegId = -1;
|
||||||
}
|
}
|
||||||
|
logger.info(String.format("GOCA GCALL: targetSegId=%d callDepth=%d", targetSegId, callDepth));
|
||||||
if (targetSegId != -1) {
|
if (targetSegId != -1) {
|
||||||
byte[] targetSeg = segmentStore.get(targetSegId);
|
byte[] targetSeg = segmentStore.get(targetSegId);
|
||||||
if (targetSeg != null) {
|
if (targetSeg != null) {
|
||||||
@@ -498,6 +507,8 @@ public class GocaDecoder {
|
|||||||
decodeStreamDirect(targetSeg, 0, targetSeg.length);
|
decodeStreamDirect(targetSeg, 0, targetSeg.length);
|
||||||
callDepth--;
|
callDepth--;
|
||||||
currentSegId = savedSegId;
|
currentSegId = savedSegId;
|
||||||
|
} else {
|
||||||
|
logger.warning("GOCA GCALL: target segment not found in store: " + targetSegId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -530,7 +541,9 @@ public class GocaDecoder {
|
|||||||
case GocaConstants.G_GSCOL: { // Set Color (0x0A)
|
case GocaConstants.G_GSCOL: { // Set Color (0x0A)
|
||||||
int colIdx = inputData[idx + 1] & 0xFF;
|
int colIdx = inputData[idx + 1] & 0xFF;
|
||||||
curColor = getColor(colIdx);
|
curColor = getColor(colIdx);
|
||||||
|
if (!inArea) {
|
||||||
fillColor = curColor;
|
fillColor = curColor;
|
||||||
|
}
|
||||||
idx += orderLen;
|
idx += orderLen;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -538,8 +551,10 @@ public class GocaDecoder {
|
|||||||
if (payloadLen >= 2 && idx + 3 < end) {
|
if (payloadLen >= 2 && idx + 3 < end) {
|
||||||
int colIdx = inputData[idx + 3] & 0xFF;
|
int colIdx = inputData[idx + 3] & 0xFF;
|
||||||
curColor = getColor(colIdx);
|
curColor = getColor(colIdx);
|
||||||
|
if (!inArea) {
|
||||||
fillColor = curColor;
|
fillColor = curColor;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
idx += orderLen;
|
idx += orderLen;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -570,12 +585,14 @@ public class GocaDecoder {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GocaConstants.G_GSPS: { // Set Pattern Set (0x08)
|
case GocaConstants.G_GSPS: { // Set Pattern Set (0x08)
|
||||||
pattern = inputData[idx + 1] & 0xFF;
|
patternSet = inputData[idx + 1] & 0xFF;
|
||||||
|
logger.info("GOCA GSPS: patternSet=" + patternSet);
|
||||||
idx += orderLen;
|
idx += orderLen;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GocaConstants.G_GSPT: { // Set Pattern Symbol (0x28)
|
case GocaConstants.G_GSPT: { // Set Pattern Symbol (0x28)
|
||||||
pattern = (orderLen == 3) ? (inputData[idx + 2] & 0xFF) : (inputData[idx + 1] & 0xFF);
|
pattern = (orderLen == 3) ? (inputData[idx + 2] & 0xFF) : (inputData[idx + 1] & 0xFF);
|
||||||
|
logger.info("GOCA GSPT: pattern=" + pattern);
|
||||||
idx += orderLen;
|
idx += orderLen;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -605,12 +622,14 @@ public class GocaDecoder {
|
|||||||
}
|
}
|
||||||
case GocaConstants.G_GSBMX: { // Set Background Mix (0x0D)
|
case GocaConstants.G_GSBMX: { // Set Background Mix (0x0D)
|
||||||
bgMix = inputData[idx + 1] & 0xFF;
|
bgMix = inputData[idx + 1] & 0xFF;
|
||||||
|
logger.info("GOCA GSBMX: bgMix=" + bgMix);
|
||||||
idx += orderLen;
|
idx += orderLen;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GocaConstants.G_GBAR: { // Begin Area (0x68)
|
case GocaConstants.G_GBAR: { // Begin Area (0x68)
|
||||||
int flags = (orderLen == 3) ? (inputData[idx + 2] & 0xFF) : (inputData[idx + 1] & 0xFF);
|
int flags = (orderLen == 3) ? (inputData[idx + 2] & 0xFF) : (inputData[idx + 1] & 0xFF);
|
||||||
boolean drawBoundary = (flags & 0x80) != 0 || (flags & 0x40) != 0 || (flags == 0);
|
boolean drawBoundary = (flags & 0x80) != 0;
|
||||||
|
logger.info(String.format("GOCA GBAR: flags=0x%02x drawBoundary=%b", flags, drawBoundary));
|
||||||
beginArea(drawBoundary);
|
beginArea(drawBoundary);
|
||||||
idx += orderLen;
|
idx += orderLen;
|
||||||
break;
|
break;
|
||||||
@@ -837,6 +856,8 @@ public class GocaDecoder {
|
|||||||
this.areaPointsY.clear();
|
this.areaPointsY.clear();
|
||||||
this.areaPolygons.clear();
|
this.areaPolygons.clear();
|
||||||
this.currentPolyPts = 0;
|
this.currentPolyPts = 0;
|
||||||
|
logger.info(String.format("GOCA beginArea: drawBoundary=%b fillColor=0x%08x patternSet=%d pattern=%d",
|
||||||
|
drawBoundary, fillColor, patternSet, pattern));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endArea() {
|
private void endArea() {
|
||||||
@@ -846,6 +867,7 @@ public class GocaDecoder {
|
|||||||
currentPolyPts = 0;
|
currentPolyPts = 0;
|
||||||
}
|
}
|
||||||
if (areaPointsX.size() < 3 || areaPolygons.isEmpty()) {
|
if (areaPointsX.size() < 3 || areaPolygons.isEmpty()) {
|
||||||
|
logger.info(String.format("GOCA endArea: dropped (pts=%d, polys=%d)", areaPointsX.size(), areaPolygons.size()));
|
||||||
inArea = false;
|
inArea = false;
|
||||||
areaPointsX.clear();
|
areaPointsX.clear();
|
||||||
areaPointsY.clear();
|
areaPointsY.clear();
|
||||||
@@ -867,7 +889,10 @@ public class GocaDecoder {
|
|||||||
polyCounts[i] = areaPolygons.get(i);
|
polyCounts[i] = areaPolygons.get(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
plane.fillArea(px, py, n, polyCounts, numPolys, fillColor,
|
logger.info(String.format("GOCA endArea: fillArea pts=%d polys=%d fillColor=0x%08x patternSet=%d pattern=%d drawBoundary=%b boundaryColor=0x%08x bgMix=%d",
|
||||||
|
n, numPolys, fillColor, patternSet, pattern, areaDrawBoundary, curColor, bgMix));
|
||||||
|
|
||||||
|
plane.fillArea(px, py, n, polyCounts, numPolys, fillColor, patternSet,
|
||||||
pattern, areaDrawBoundary, curColor, lineType, lineWidth, bgMix, bgColor);
|
pattern, areaDrawBoundary, curColor, lineType, lineWidth, bgMix, bgColor);
|
||||||
inArea = false;
|
inArea = false;
|
||||||
areaPointsX.clear();
|
areaPointsX.clear();
|
||||||
@@ -1108,6 +1133,22 @@ public class GocaDecoder {
|
|||||||
trackPoint(centerX - (int) Math.round(semiAxis1), centerY - (int) Math.round(semiAxis2));
|
trackPoint(centerX - (int) Math.round(semiAxis1), centerY - (int) Math.round(semiAxis2));
|
||||||
trackPoint(centerX + (int) Math.round(semiAxis1), centerY + (int) Math.round(semiAxis2));
|
trackPoint(centerX + (int) Math.round(semiAxis1), centerY + (int) Math.round(semiAxis2));
|
||||||
|
|
||||||
|
if (inArea) {
|
||||||
|
int steps = Math.max(8, (int) Math.round(Math.abs(sweepAngleDeg) / 15.0));
|
||||||
|
double startRad = Math.toRadians(startAngleDeg);
|
||||||
|
double sweepRad = Math.toRadians(sweepAngleDeg);
|
||||||
|
for (int i = 0; i <= steps; i++) {
|
||||||
|
double a = startRad + (sweepRad * i) / steps;
|
||||||
|
int ax = centerX + (int) Math.round(Math.cos(a) * semiAxis1);
|
||||||
|
int ay = centerY + (int) Math.round(Math.sin(a) * semiAxis2);
|
||||||
|
if (i == 0 && currentPolyPts == 0) {
|
||||||
|
addAreaLineStart(ax, ay);
|
||||||
|
} else {
|
||||||
|
addAreaPoint(ax, ay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
plane.drawArc(cx, cy, rx, ry, startAngleDeg, sweepAngleDeg,
|
plane.drawArc(cx, cy, rx, ry, startAngleDeg, sweepAngleDeg,
|
||||||
curColor, lineType, lineWidth, isFull);
|
curColor, lineType, lineWidth, isFull);
|
||||||
|
|
||||||
@@ -1125,6 +1166,13 @@ public class GocaDecoder {
|
|||||||
if (fromCurPos) {
|
if (fromCurPos) {
|
||||||
ptsX.add(plane.mapXDouble(curX));
|
ptsX.add(plane.mapXDouble(curX));
|
||||||
ptsY.add(plane.mapYDouble(curY));
|
ptsY.add(plane.mapYDouble(curY));
|
||||||
|
if (inArea) {
|
||||||
|
if (currentPolyPts == 0) {
|
||||||
|
addAreaLineStart(curX, curY);
|
||||||
|
} else {
|
||||||
|
addAreaPoint(curX, curY);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (pos + 4 <= end) {
|
while (pos + 4 <= end) {
|
||||||
@@ -1132,6 +1180,13 @@ public class GocaDecoder {
|
|||||||
int y = readCoord(data, pos + 2);
|
int y = readCoord(data, pos + 2);
|
||||||
ptsX.add(plane.mapXDouble(x));
|
ptsX.add(plane.mapXDouble(x));
|
||||||
ptsY.add(plane.mapYDouble(y));
|
ptsY.add(plane.mapYDouble(y));
|
||||||
|
if (inArea) {
|
||||||
|
if (ptsX.size() == 1 && currentPolyPts == 0) {
|
||||||
|
addAreaLineStart(x, y);
|
||||||
|
} else {
|
||||||
|
addAreaPoint(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
curX = x;
|
curX = x;
|
||||||
curY = y;
|
curY = y;
|
||||||
pos += 4;
|
pos += 4;
|
||||||
|
|||||||
@@ -44,6 +44,15 @@ public class GraphicsPlane {
|
|||||||
|
|
||||||
private int screenCols = 80;
|
private int screenCols = 80;
|
||||||
private int screenRows = 24;
|
private int screenRows = 24;
|
||||||
|
private ProgramSymbolManager programSymbolManager;
|
||||||
|
|
||||||
|
public void setProgramSymbolManager(ProgramSymbolManager psm) {
|
||||||
|
this.programSymbolManager = psm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProgramSymbolManager getProgramSymbolManager() {
|
||||||
|
return programSymbolManager;
|
||||||
|
}
|
||||||
|
|
||||||
public GraphicsPlane(int width, int height) {
|
public GraphicsPlane(int width, int height) {
|
||||||
this.canvasWidth = Math.max(1, width);
|
this.canvasWidth = Math.max(1, width);
|
||||||
@@ -338,7 +347,7 @@ public class GraphicsPlane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void drawStyledLine(int x1, int y1, int x2, int y2, int colorArgb, int lineType, int lineWidth) {
|
private void drawStyledLine(int x1, int y1, int x2, int y2, int colorArgb, int lineType, int lineWidth) {
|
||||||
int color = (colorArgb != 0) ? colorArgb : 0xFFFFFFFF;
|
int color = (colorArgb != 0) ? colorArgb : GocaConstants.GOCA_COLORS[0];
|
||||||
int thickness = (lineWidth == GocaConstants.LW_THICK) ? 2 : 1;
|
int thickness = (lineWidth == GocaConstants.LW_THICK) ? 2 : 1;
|
||||||
|
|
||||||
int dx = Math.abs(x2 - x1);
|
int dx = Math.abs(x2 - x1);
|
||||||
@@ -517,12 +526,23 @@ public class GraphicsPlane {
|
|||||||
int fillColorArgb, int pattern,
|
int fillColorArgb, int pattern,
|
||||||
boolean drawBoundary, int boundaryColorArgb, int lineType, int lineWidth,
|
boolean drawBoundary, int boundaryColorArgb, int lineType, int lineWidth,
|
||||||
int bgMix, int bgColorArgb) {
|
int bgMix, int bgColorArgb) {
|
||||||
|
fillArea(px, py, numPoints, polyCounts, numPolys, fillColorArgb, 0, pattern, drawBoundary, boundaryColorArgb, lineType, lineWidth, bgMix, bgColorArgb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills an area with symbol-set-aware and pattern-symbol-aware rasterization.
|
||||||
|
*/
|
||||||
|
public synchronized void fillArea(int[] px, int[] py, int numPoints, int[] polyCounts, int numPolys,
|
||||||
|
int fillColorArgb, int patternSet, int pattern,
|
||||||
|
boolean drawBoundary, int boundaryColorArgb, int lineType, int lineWidth,
|
||||||
|
int bgMix, int bgColorArgb) {
|
||||||
if (px == null || py == null || numPoints < 3) return;
|
if (px == null || py == null || numPoints < 3) return;
|
||||||
|
|
||||||
int fill = (fillColorArgb != 0) ? fillColorArgb : 0xFFFFFFFF;
|
int fill = (fillColorArgb != 0) ? fillColorArgb : GocaConstants.GOCA_COLORS[0];
|
||||||
int bg = bgColorArgb;
|
int bg = bgColorArgb;
|
||||||
|
|
||||||
if (pattern != GocaConstants.PT_EMPTY) {
|
boolean isTransparentBlack = (fill == GocaConstants.GOCA_COLORS[8] || (fill & 0x00FFFFFF) == 0) && (bgMix != GocaConstants.MIX_OVER);
|
||||||
|
if (pattern != GocaConstants.PT_EMPTY && (pattern != 0 || !drawBoundary) && !isTransparentBlack) {
|
||||||
// Find polygon vertical bounds across all points
|
// Find polygon vertical bounds across all points
|
||||||
int minY = py[0];
|
int minY = py[0];
|
||||||
int maxY = py[0];
|
int maxY = py[0];
|
||||||
@@ -534,7 +554,18 @@ public class GraphicsPlane {
|
|||||||
maxY = Math.min(canvasHeight - 1, maxY);
|
maxY = Math.min(canvasHeight - 1, maxY);
|
||||||
|
|
||||||
List<Integer> nodeX = new ArrayList<>();
|
List<Integer> nodeX = new ArrayList<>();
|
||||||
byte[] patRows = (pattern >= 0 && pattern < PATTERN_DATA.length) ? PATTERN_DATA[pattern] : PATTERN_DATA[0];
|
byte[] patRows = null;
|
||||||
|
ProgramSymbolSet.SymbolSlot psSlot = null;
|
||||||
|
if (patternSet >= 0x40 && programSymbolManager != null) {
|
||||||
|
psSlot = programSymbolManager.getSymbol(patternSet, pattern);
|
||||||
|
}
|
||||||
|
if (psSlot == null) {
|
||||||
|
if (pattern >= 0 && pattern < PATTERN_DATA.length) {
|
||||||
|
patRows = PATTERN_DATA[pattern];
|
||||||
|
} else {
|
||||||
|
patRows = PATTERN_DATA[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int y = minY; y <= maxY; y++) {
|
for (int y = minY; y <= maxY; y++) {
|
||||||
nodeX.clear();
|
nodeX.clear();
|
||||||
@@ -567,13 +598,26 @@ public class GraphicsPlane {
|
|||||||
int rightX = Math.min(canvasWidth - 1, nodeX.get(i + 1));
|
int rightX = Math.min(canvasWidth - 1, nodeX.get(i + 1));
|
||||||
|
|
||||||
for (int x = leftX; x <= rightX; x++) {
|
for (int x = leftX; x <= rightX; x++) {
|
||||||
if (pattern == GocaConstants.PT_SOLID || pattern == 0 || pattern >= 16) {
|
if (psSlot != null) {
|
||||||
|
int psW = psSlot.getWidth();
|
||||||
|
int psH = psSlot.getHeight();
|
||||||
|
int psX = (psW > 0) ? (x % psW) : 0;
|
||||||
|
int psY = (psH > 0) ? (y % psH) : 0;
|
||||||
|
byte[] psPix = psSlot.getPixelData();
|
||||||
|
int pIdx = psY * psW + psX;
|
||||||
|
boolean bit = (psPix != null && pIdx < psPix.length && psPix[pIdx] != 0);
|
||||||
|
if (bit) {
|
||||||
|
setPixel(x, y, fill);
|
||||||
|
} else if (bgMix == GocaConstants.MIX_OVER) {
|
||||||
|
setPixel(x, y, bg);
|
||||||
|
}
|
||||||
|
} else if (pattern == GocaConstants.PT_SOLID || pattern == 16) {
|
||||||
setPixel(x, y, fill);
|
setPixel(x, y, fill);
|
||||||
} else {
|
} else {
|
||||||
int b = patRows[y & 7] & 0xFF;
|
int b = patRows[y & 7] & 0xFF;
|
||||||
if (((b >> (7 - (x & 7))) & 1) != 0) {
|
if (((b >> (7 - (x & 7))) & 1) != 0) {
|
||||||
setPixel(x, y, fill);
|
setPixel(x, y, fill);
|
||||||
} else if (bgMix != 0) { // BMX_OVERPAINT (opaque background)
|
} else if (bgMix == GocaConstants.MIX_OVER) { // BMX_OVERPAINT (opaque background)
|
||||||
setPixel(x, y, bg);
|
setPixel(x, y, bg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -610,7 +654,7 @@ public class GraphicsPlane {
|
|||||||
* Draws a GOCA marker symbol (+, x, diamond, square, star, dot, circle) with sub-pixel precision.
|
* Draws a GOCA marker symbol (+, x, diamond, square, star, dot, circle) with sub-pixel precision.
|
||||||
*/
|
*/
|
||||||
public synchronized void drawMarker(double x, double y, int markerType, int size, int colorArgb) {
|
public synchronized void drawMarker(double x, double y, int markerType, int size, int colorArgb) {
|
||||||
int color = (colorArgb != 0) ? colorArgb : 0xFFFFFFFF;
|
int color = (colorArgb != 0) ? colorArgb : GocaConstants.GOCA_COLORS[0];
|
||||||
double s = Math.max(3.0, size > 0 ? (double) size : 5.0);
|
double s = Math.max(3.0, size > 0 ? (double) size : 5.0);
|
||||||
|
|
||||||
switch (markerType) {
|
switch (markerType) {
|
||||||
@@ -731,7 +775,7 @@ public class GraphicsPlane {
|
|||||||
public synchronized void drawVectorText(double x, double y, String text, int colorArgb,
|
public synchronized void drawVectorText(double x, double y, String text, int colorArgb,
|
||||||
double cellWidth, double cellHeight, int dir, double angle) {
|
double cellWidth, double cellHeight, int dir, double angle) {
|
||||||
if (text == null || text.isEmpty()) return;
|
if (text == null || text.isEmpty()) return;
|
||||||
int color = (colorArgb != 0) ? colorArgb : 0xFFFFFFFF;
|
int color = (colorArgb != 0) ? colorArgb : GocaConstants.GOCA_COLORS[0];
|
||||||
|
|
||||||
double curX = x;
|
double curX = x;
|
||||||
double curY = y;
|
double curY = y;
|
||||||
@@ -822,7 +866,7 @@ public class GraphicsPlane {
|
|||||||
*/
|
*/
|
||||||
public synchronized void drawImage(int x, int y, int width, int height, byte[] imageData, int fgColorArgb) {
|
public synchronized void drawImage(int x, int y, int width, int height, byte[] imageData, int fgColorArgb) {
|
||||||
if (imageData == null || width <= 0 || height <= 0) return;
|
if (imageData == null || width <= 0 || height <= 0) return;
|
||||||
int fgColor = (fgColorArgb != 0) ? fgColorArgb : 0xFFFFFFFF;
|
int fgColor = (fgColorArgb != 0) ? fgColorArgb : GocaConstants.GOCA_COLORS[0];
|
||||||
int bytesPerRow = (width + 7) / 8;
|
int bytesPerRow = (width + 7) / 8;
|
||||||
|
|
||||||
for (int row = 0; row < height; row++) {
|
for (int row = 0; row < height; row++) {
|
||||||
|
|||||||
@@ -520,6 +520,8 @@ public class GocaDecoderTest {
|
|||||||
// Polygon 2 (e.g. Letter 'T' stem): (18,20) to (22,20) to (22,40) to (18,40) to (18,20)
|
// Polygon 2 (e.g. Letter 'T' stem): (18,20) to (22,20) to (22,40) to (18,40) to (18,20)
|
||||||
// GEAR (0x60)
|
// GEAR (0x60)
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
out.write(GocaConstants.G_GSPT);
|
||||||
|
out.write(GocaConstants.PT_SOLID);
|
||||||
out.write(GocaConstants.G_GBAR);
|
out.write(GocaConstants.G_GBAR);
|
||||||
out.write(0x80);
|
out.write(0x80);
|
||||||
|
|
||||||
@@ -555,4 +557,240 @@ public class GocaDecoderTest {
|
|||||||
}
|
}
|
||||||
assertTrue(nonZero > 50, "Expected both filled subpath polygons to render filled pixels");
|
assertTrue(nonZero > 50, "Expected both filled subpath polygons to render filled pixels");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPatternSetAndPatternSymbolSeparation() {
|
||||||
|
GraphicsPlane plane = new GraphicsPlane(200, 200);
|
||||||
|
GocaDecoder decoder = new GocaDecoder(plane);
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
// Set Color to Blue (Color 1)
|
||||||
|
out.write(GocaConstants.G_GSCOL);
|
||||||
|
out.write(0x01); // Blue
|
||||||
|
|
||||||
|
// Set Pattern Set to 0 (default standard patterns)
|
||||||
|
out.write(GocaConstants.G_GSPS);
|
||||||
|
out.write(0x00);
|
||||||
|
|
||||||
|
// Set Pattern Symbol to 5 (D5 50% checkerboard)
|
||||||
|
out.write(GocaConstants.G_GSPT);
|
||||||
|
out.write(0x05);
|
||||||
|
|
||||||
|
// Begin Area with boundary (0x80)
|
||||||
|
out.write(GocaConstants.G_GBAR);
|
||||||
|
out.write(0x80);
|
||||||
|
|
||||||
|
// Draw rectangle (0,0) to (50,0) to (50,50) to (0,50) to (0,0)
|
||||||
|
out.write(GocaConstants.G_GLINE);
|
||||||
|
out.write(0x14); // 5 points * 4 bytes = 20 bytes
|
||||||
|
out.write(0x00); out.write(0); out.write(0x00); out.write(0);
|
||||||
|
out.write(0x00); out.write(50); out.write(0x00); out.write(0);
|
||||||
|
out.write(0x00); out.write(50); out.write(0x00); out.write(50);
|
||||||
|
out.write(0x00); out.write(0); out.write(0x00); out.write(50);
|
||||||
|
out.write(0x00); out.write(0); out.write(0x00); out.write(0);
|
||||||
|
|
||||||
|
// End Area
|
||||||
|
out.write(GocaConstants.G_GEAR);
|
||||||
|
|
||||||
|
byte[] stream = out.toByteArray();
|
||||||
|
decoder.decodeStream(stream, 0, stream.length);
|
||||||
|
|
||||||
|
assertTrue(plane.hasContent());
|
||||||
|
// Verify that D5 checkerboard pattern is NOT solid fill (it should have transparent gaps)
|
||||||
|
int[] buffer = plane.getRgbBuffer();
|
||||||
|
int bluePixels = 0;
|
||||||
|
int zeroPixels = 0;
|
||||||
|
int blueArgb = GocaConstants.GOCA_COLORS[1];
|
||||||
|
for (int p : buffer) {
|
||||||
|
if (p == blueArgb) bluePixels++;
|
||||||
|
else if (p == 0) zeroPixels++;
|
||||||
|
}
|
||||||
|
assertTrue(bluePixels > 0, "Expected blue pixels in patterned area");
|
||||||
|
assertTrue(zeroPixels > 0, "Expected transparent zero pixels in patterned area due to D5 checkerboard gaps");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyPatternAreaDrawsOnlyBoundary() {
|
||||||
|
GraphicsPlane plane = new GraphicsPlane(200, 200);
|
||||||
|
GocaDecoder decoder = new GocaDecoder(plane);
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
// Set Color to Red (Color 2)
|
||||||
|
out.write(GocaConstants.G_GSCOL);
|
||||||
|
out.write(0x02);
|
||||||
|
|
||||||
|
// Set Pattern Symbol to PT_EMPTY (15)
|
||||||
|
out.write(GocaConstants.G_GSPT);
|
||||||
|
out.write(GocaConstants.PT_EMPTY);
|
||||||
|
|
||||||
|
// Begin Area with boundary (0x80)
|
||||||
|
out.write(GocaConstants.G_GBAR);
|
||||||
|
out.write(0x80);
|
||||||
|
|
||||||
|
// Draw rectangle
|
||||||
|
out.write(GocaConstants.G_GLINE);
|
||||||
|
out.write(0x14);
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(10);
|
||||||
|
out.write(0x00); out.write(40); out.write(0x00); out.write(10);
|
||||||
|
out.write(0x00); out.write(40); out.write(0x00); out.write(40);
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(40);
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(10);
|
||||||
|
|
||||||
|
// End Area
|
||||||
|
out.write(GocaConstants.G_GEAR);
|
||||||
|
|
||||||
|
byte[] stream = out.toByteArray();
|
||||||
|
decoder.decodeStream(stream, 0, stream.length);
|
||||||
|
|
||||||
|
assertTrue(plane.hasContent());
|
||||||
|
// Verify that center of the box is transparent (not filled)
|
||||||
|
int centerX = plane.mapX(25);
|
||||||
|
int centerY = plane.mapY(25);
|
||||||
|
int centerPixel = plane.getRgbBuffer()[centerY * plane.getCanvasWidth() + centerX];
|
||||||
|
assertEquals(0, centerPixel, "Expected interior of PT_EMPTY area to remain transparent");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGearFollowedByNopOrder() {
|
||||||
|
GraphicsPlane plane = new GraphicsPlane(200, 200);
|
||||||
|
GocaDecoder decoder = new GocaDecoder(plane);
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
// Area with PT_EMPTY
|
||||||
|
out.write(GocaConstants.G_GSPT);
|
||||||
|
out.write(GocaConstants.PT_EMPTY);
|
||||||
|
out.write(GocaConstants.G_GBAR);
|
||||||
|
out.write(0x80);
|
||||||
|
out.write(GocaConstants.G_GLINE);
|
||||||
|
out.write(0x10);
|
||||||
|
out.write(0x00); out.write(0); out.write(0x00); out.write(0);
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(0);
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(10);
|
||||||
|
out.write(0x00); out.write(0); out.write(0x00); out.write(0);
|
||||||
|
out.write(GocaConstants.G_GEAR); // 0x60
|
||||||
|
out.write(GocaConstants.G_NOP1); // 0x00 NOP - must not be consumed as part of GEAR!
|
||||||
|
|
||||||
|
// Followed by a line
|
||||||
|
out.write(GocaConstants.G_GSCOL);
|
||||||
|
out.write(0x03); // Pink
|
||||||
|
out.write(GocaConstants.G_GLINE);
|
||||||
|
out.write(0x08);
|
||||||
|
out.write(0x00); out.write(20); out.write(0x00); out.write(20);
|
||||||
|
out.write(0x00); out.write(40); out.write(0x00); out.write(40);
|
||||||
|
|
||||||
|
byte[] stream = out.toByteArray();
|
||||||
|
decoder.decodeStream(stream, 0, stream.length);
|
||||||
|
|
||||||
|
assertTrue(plane.hasContent());
|
||||||
|
int pinkArgb = GocaConstants.GOCA_COLORS[3];
|
||||||
|
boolean foundPink = false;
|
||||||
|
for (int p : plane.getRgbBuffer()) {
|
||||||
|
if (p == pinkArgb) {
|
||||||
|
foundPink = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(foundPink, "Expected pink line after GEAR + NOP to be drawn");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAdmopsSlidePreviewSequence() {
|
||||||
|
GraphicsPlane plane = new GraphicsPlane(200, 200);
|
||||||
|
GocaDecoder decoder = new GocaDecoder(plane);
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
// 1. Draw Blue stippled slide background (Pattern 6, Blue color 1)
|
||||||
|
out.write(GocaConstants.G_GSCOL); out.write(0x01); // Blue
|
||||||
|
out.write(GocaConstants.G_GSPT); out.write(0x06); // Pattern 6
|
||||||
|
out.write(GocaConstants.G_GBAR); out.write(0x80);
|
||||||
|
out.write(GocaConstants.G_GLINE); out.write(0x14); // 5 points
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(10);
|
||||||
|
out.write(0x00); out.write(90); out.write(0x00); out.write(10);
|
||||||
|
out.write(0x00); out.write(90); out.write(0x00); out.write(90);
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(90);
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(10);
|
||||||
|
out.write(GocaConstants.G_GEAR);
|
||||||
|
|
||||||
|
// 2. Draw Red banner at top (Solid Pattern 16, Red color 2)
|
||||||
|
out.write(GocaConstants.G_GSCOL); out.write(0x02); // Red
|
||||||
|
out.write(GocaConstants.G_GSPT); out.write(0x10); // Solid
|
||||||
|
out.write(GocaConstants.G_GBAR); out.write(0x80);
|
||||||
|
out.write(GocaConstants.G_GLINE); out.write(0x14); // 5 points
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(10);
|
||||||
|
out.write(0x00); out.write(90); out.write(0x00); out.write(10);
|
||||||
|
out.write(0x00); out.write(90); out.write(0x00); out.write(30);
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(30);
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(10);
|
||||||
|
out.write(GocaConstants.G_GEAR);
|
||||||
|
|
||||||
|
// 3. Draw Yellow bullet text (Color 6, default modal solid pattern)
|
||||||
|
out.write(GocaConstants.G_GSCOL); out.write(0x06); // Yellow
|
||||||
|
out.write(GocaConstants.G_GBAR); out.write(0x80);
|
||||||
|
out.write(GocaConstants.G_GLINE); out.write(0x14); // 5 points
|
||||||
|
out.write(0x00); out.write(20); out.write(0x00); out.write(40);
|
||||||
|
out.write(0x00); out.write(80); out.write(0x00); out.write(40);
|
||||||
|
out.write(0x00); out.write(80); out.write(0x00); out.write(50);
|
||||||
|
out.write(0x00); out.write(20); out.write(0x00); out.write(50);
|
||||||
|
out.write(0x00); out.write(20); out.write(0x00); out.write(40);
|
||||||
|
out.write(GocaConstants.G_GEAR);
|
||||||
|
|
||||||
|
// 4. Draw White slide boundary frame (Color set to 8/Black before GBAR, GSCOL White inside GBAR)
|
||||||
|
out.write(GocaConstants.G_GSCOL); out.write(0x08); // Black (background)
|
||||||
|
out.write(GocaConstants.G_GBAR); out.write(0x80);
|
||||||
|
out.write(GocaConstants.G_GSCOL); out.write(0x07); // White line color
|
||||||
|
out.write(GocaConstants.G_GSLT); out.write(GocaConstants.LT_DOT); // Dotted line
|
||||||
|
out.write(GocaConstants.G_GLINE); out.write(0x14); // 5 points
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(10);
|
||||||
|
out.write(0x00); out.write(90); out.write(0x00); out.write(10);
|
||||||
|
out.write(0x00); out.write(90); out.write(0x00); out.write(90);
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(90);
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(10);
|
||||||
|
out.write(GocaConstants.G_GEAR);
|
||||||
|
|
||||||
|
byte[] stream = out.toByteArray();
|
||||||
|
decoder.decodeStream(stream, 0, stream.length);
|
||||||
|
|
||||||
|
assertTrue(plane.hasContent());
|
||||||
|
|
||||||
|
// Verify that the red banner (e.g. at (50, 20)) was NOT overwritten by black/white!
|
||||||
|
int bannerX = plane.mapX(50);
|
||||||
|
int bannerY = plane.mapY(20);
|
||||||
|
int bannerPixel = plane.getRgbBuffer()[bannerY * plane.getCanvasWidth() + bannerX];
|
||||||
|
int redArgb = GocaConstants.GOCA_COLORS[2];
|
||||||
|
assertEquals(redArgb, bannerPixel, "Red header banner must be solid red and preserved");
|
||||||
|
|
||||||
|
// Verify that the yellow text (e.g. at (50, 45)) was NOT overwritten!
|
||||||
|
int textX = plane.mapX(50);
|
||||||
|
int textY = plane.mapY(45);
|
||||||
|
int textPixel = plane.getRgbBuffer()[textY * plane.getCanvasWidth() + textX];
|
||||||
|
int yellowArgb = GocaConstants.GOCA_COLORS[6];
|
||||||
|
assertEquals(yellowArgb, textPixel, "Yellow bullet text must be solid yellow and preserved");
|
||||||
|
|
||||||
|
// Verify that the blue stipple (in body region (20..80, 60..80)) contains blue pixels and was NOT overwritten by white!
|
||||||
|
int blueArgb = GocaConstants.GOCA_COLORS[1];
|
||||||
|
boolean foundBlue = false;
|
||||||
|
int yMin = Math.min(plane.mapY(60), plane.mapY(80));
|
||||||
|
int yMax = Math.max(plane.mapY(60), plane.mapY(80));
|
||||||
|
int xMin = Math.min(plane.mapX(20), plane.mapX(80));
|
||||||
|
int xMax = Math.max(plane.mapX(20), plane.mapX(80));
|
||||||
|
for (int y = yMin; y <= yMax; y++) {
|
||||||
|
for (int x = xMin; x <= xMax; x++) {
|
||||||
|
int pix = plane.getRgbBuffer()[y * plane.getCanvasWidth() + x];
|
||||||
|
if (pix == blueArgb) {
|
||||||
|
foundBlue = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (foundBlue) break;
|
||||||
|
}
|
||||||
|
assertTrue(foundBlue, "Slide body must contain stippled blue pixels");
|
||||||
|
|
||||||
|
// Verify that the boundary (e.g. at (10, 50)) is drawn in White!
|
||||||
|
int whiteArgb = GocaConstants.GOCA_COLORS[7];
|
||||||
|
int borderX = plane.mapX(10);
|
||||||
|
int borderY = plane.mapY(50);
|
||||||
|
int borderPixel = plane.getRgbBuffer()[borderY * plane.getCanvasWidth() + borderX];
|
||||||
|
assertEquals(whiteArgb, borderPixel, "Slide border outline must be drawn in White");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user