This commit is contained in:
@@ -59,6 +59,7 @@ public class DataStreamProcessor {
|
||||
this.outputBuffer = new byte[32768];
|
||||
this.outputPos = 0;
|
||||
this.gocaDecoder.setProgramSymbolManager(programSymbolManager);
|
||||
this.graphicsPlane.setProgramSymbolManager(programSymbolManager);
|
||||
}
|
||||
|
||||
public QueryReplyBuilder getQueryReplyBuilder() {
|
||||
@@ -838,6 +839,13 @@ public class DataStreamProcessor {
|
||||
int orderOffset = (fieldLen >= 7) ? (pos + 7) : (pos + 4);
|
||||
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());
|
||||
|
||||
if (flags == 0x80) { // SPAN_FIRST
|
||||
@@ -856,6 +864,7 @@ public class DataStreamProcessor {
|
||||
}
|
||||
byte[] fullStream = gocaAccumulator.toByteArray();
|
||||
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) {
|
||||
gocaDecoder.processProcedureOrders(fullStream, 0, fullStream.length);
|
||||
} else {
|
||||
@@ -864,6 +873,7 @@ public class DataStreamProcessor {
|
||||
notifyScreenUpdated();
|
||||
} else { // SPAN_ONLY (0xC0)
|
||||
gocaAccumulator.reset();
|
||||
log.info(String.format("GOCA stream SPAN_ONLY: %d bytes", orderLen));
|
||||
if (sfSubId == haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJCNTL_SUB) {
|
||||
gocaDecoder.processProcedureOrders(data, orderOffset, orderLen);
|
||||
} else {
|
||||
|
||||
@@ -20,7 +20,7 @@ public class GocaDecoder {
|
||||
private int curX = 0;
|
||||
private int curY = 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 lineType = GocaConstants.LT_SOLID;
|
||||
private int lineWidth = GocaConstants.LW_NORMAL;
|
||||
@@ -28,6 +28,7 @@ public class GocaDecoder {
|
||||
private int markerSize = 5;
|
||||
private int markerColor = GocaConstants.GOCA_COLORS[0];
|
||||
private int pattern = GocaConstants.PT_SOLID;
|
||||
private int patternSet = 0;
|
||||
private int fillColor = GocaConstants.GOCA_COLORS[0];
|
||||
private int charDir = GocaConstants.CD_LR;
|
||||
private double charAngle = 0.0;
|
||||
@@ -99,7 +100,7 @@ public class GocaDecoder {
|
||||
private final java.util.List<SegmentBounds> activeSegmentsInOrder = new java.util.ArrayList<>();
|
||||
private int currentSegId = 0;
|
||||
|
||||
// Graphic Cursor (Light-Pen) state
|
||||
// Graphics Cursor state
|
||||
private boolean graphicsCursorActive = false;
|
||||
private int graphicCursorX = 0;
|
||||
private int graphicCursorY = 0;
|
||||
@@ -110,6 +111,9 @@ public class GocaDecoder {
|
||||
|
||||
public void setProgramSymbolManager(ProgramSymbolManager psm) {
|
||||
this.programSymbolManager = psm;
|
||||
if (this.plane != null) {
|
||||
this.plane.setProgramSymbolManager(psm);
|
||||
}
|
||||
}
|
||||
|
||||
public GraphicsPlane getGraphicsPlane() {
|
||||
@@ -203,7 +207,7 @@ public class GocaDecoder {
|
||||
|
||||
public synchronized void resetAttributes() {
|
||||
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
|
||||
lineType = GocaConstants.LT_SOLID;
|
||||
lineWidth = GocaConstants.LW_NORMAL;
|
||||
@@ -211,6 +215,7 @@ public class GocaDecoder {
|
||||
markerSize = 5;
|
||||
markerColor = curColor;
|
||||
pattern = GocaConstants.PT_SOLID;
|
||||
patternSet = 0;
|
||||
fillColor = curColor;
|
||||
charDir = GocaConstants.CD_LR;
|
||||
charAngle = 0.0;
|
||||
@@ -232,30 +237,26 @@ public class GocaDecoder {
|
||||
*
|
||||
* IMPORTANT ARCHITECTURE & PARSING SAFETY NOTE:
|
||||
* GOCA orders follow IBM GA23-0059 and Host On-Demand (HOD) architecture rules:
|
||||
* 1. 1-byte standalone orders (NOP, ERASE, etc.) -> length 1.
|
||||
* 2. Delimiter orders (GEAR, ENDSEGM, ENDPROLOGUE, GEIMG, GPOP) -> 1 or 2 bytes (with 0x00 trailing byte).
|
||||
* 3. Fixed 1-byte operand orders (0x00..0x1F range: GSCOL, GSLT, GSLW, GSMS, GSMC, GSPS, GSBMX) -> 2 bytes.
|
||||
* 4. Flexible 1-byte attribute orders (GBAR 0x68, GSCS 0x38, GSCD 0x3A, GSCC 0x3B, GSMP 0x39,
|
||||
* 1. 1-byte standalone orders (NOP, ERASE, GEAR, ENDSEGM, ENDPROLOGUE, GEIMG, GPOP) -> length 1.
|
||||
* 2. 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,
|
||||
* 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')
|
||||
* - 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
|
||||
* 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!
|
||||
* 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.
|
||||
*/
|
||||
private int getOrderLength(byte[] data, int idx, int end) {
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
return -1;
|
||||
}
|
||||
@@ -399,23 +400,30 @@ public class GocaDecoder {
|
||||
break;
|
||||
}
|
||||
case GocaConstants.G_BEGSEGM: { // Begin Segment (0x70)
|
||||
int segId = 0;
|
||||
int flag0 = 0;
|
||||
int flag1 = 0;
|
||||
if (idx + 5 < end) {
|
||||
int segId = ((inputData[idx + 2] & 0xFF) << 24) |
|
||||
((inputData[idx + 3] & 0xFF) << 16) |
|
||||
((inputData[idx + 4] & 0xFF) << 8) |
|
||||
(inputData[idx + 5] & 0xFF);
|
||||
segId = ((inputData[idx + 2] & 0xFF) << 24) |
|
||||
((inputData[idx + 3] & 0xFF) << 16) |
|
||||
((inputData[idx + 4] & 0xFF) << 8) |
|
||||
(inputData[idx + 5] & 0xFF);
|
||||
currentSegId = segId;
|
||||
SegmentBounds sb = segmentBoundsMap.computeIfAbsent(segId, SegmentBounds::new);
|
||||
activeSegmentsInOrder.remove(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();
|
||||
}
|
||||
idx += orderLen;
|
||||
break;
|
||||
}
|
||||
case GocaConstants.G_ENDSEGM: { // End Segment (0x71)
|
||||
logger.info(String.format("GOCA ENDSEGM: segId=%d", currentSegId));
|
||||
if (currentSegId != 0) {
|
||||
SegmentBounds sb = segmentBoundsMap.get(currentSegId);
|
||||
if (sb != null) {
|
||||
@@ -486,6 +494,7 @@ public class GocaDecoder {
|
||||
} else {
|
||||
targetSegId = -1;
|
||||
}
|
||||
logger.info(String.format("GOCA GCALL: targetSegId=%d callDepth=%d", targetSegId, callDepth));
|
||||
if (targetSegId != -1) {
|
||||
byte[] targetSeg = segmentStore.get(targetSegId);
|
||||
if (targetSeg != null) {
|
||||
@@ -498,6 +507,8 @@ public class GocaDecoder {
|
||||
decodeStreamDirect(targetSeg, 0, targetSeg.length);
|
||||
callDepth--;
|
||||
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)
|
||||
int colIdx = inputData[idx + 1] & 0xFF;
|
||||
curColor = getColor(colIdx);
|
||||
fillColor = curColor;
|
||||
if (!inArea) {
|
||||
fillColor = curColor;
|
||||
}
|
||||
idx += orderLen;
|
||||
break;
|
||||
}
|
||||
@@ -538,7 +551,9 @@ public class GocaDecoder {
|
||||
if (payloadLen >= 2 && idx + 3 < end) {
|
||||
int colIdx = inputData[idx + 3] & 0xFF;
|
||||
curColor = getColor(colIdx);
|
||||
fillColor = curColor;
|
||||
if (!inArea) {
|
||||
fillColor = curColor;
|
||||
}
|
||||
}
|
||||
idx += orderLen;
|
||||
break;
|
||||
@@ -570,12 +585,14 @@ public class GocaDecoder {
|
||||
break;
|
||||
}
|
||||
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;
|
||||
break;
|
||||
}
|
||||
case GocaConstants.G_GSPT: { // Set Pattern Symbol (0x28)
|
||||
pattern = (orderLen == 3) ? (inputData[idx + 2] & 0xFF) : (inputData[idx + 1] & 0xFF);
|
||||
logger.info("GOCA GSPT: pattern=" + pattern);
|
||||
idx += orderLen;
|
||||
break;
|
||||
}
|
||||
@@ -605,12 +622,14 @@ public class GocaDecoder {
|
||||
}
|
||||
case GocaConstants.G_GSBMX: { // Set Background Mix (0x0D)
|
||||
bgMix = inputData[idx + 1] & 0xFF;
|
||||
logger.info("GOCA GSBMX: bgMix=" + bgMix);
|
||||
idx += orderLen;
|
||||
break;
|
||||
}
|
||||
case GocaConstants.G_GBAR: { // Begin Area (0x68)
|
||||
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);
|
||||
idx += orderLen;
|
||||
break;
|
||||
@@ -837,6 +856,8 @@ public class GocaDecoder {
|
||||
this.areaPointsY.clear();
|
||||
this.areaPolygons.clear();
|
||||
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() {
|
||||
@@ -846,6 +867,7 @@ public class GocaDecoder {
|
||||
currentPolyPts = 0;
|
||||
}
|
||||
if (areaPointsX.size() < 3 || areaPolygons.isEmpty()) {
|
||||
logger.info(String.format("GOCA endArea: dropped (pts=%d, polys=%d)", areaPointsX.size(), areaPolygons.size()));
|
||||
inArea = false;
|
||||
areaPointsX.clear();
|
||||
areaPointsY.clear();
|
||||
@@ -867,7 +889,10 @@ public class GocaDecoder {
|
||||
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);
|
||||
inArea = false;
|
||||
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));
|
||||
|
||||
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,
|
||||
curColor, lineType, lineWidth, isFull);
|
||||
|
||||
@@ -1125,6 +1166,13 @@ public class GocaDecoder {
|
||||
if (fromCurPos) {
|
||||
ptsX.add(plane.mapXDouble(curX));
|
||||
ptsY.add(plane.mapYDouble(curY));
|
||||
if (inArea) {
|
||||
if (currentPolyPts == 0) {
|
||||
addAreaLineStart(curX, curY);
|
||||
} else {
|
||||
addAreaPoint(curX, curY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (pos + 4 <= end) {
|
||||
@@ -1132,6 +1180,13 @@ public class GocaDecoder {
|
||||
int y = readCoord(data, pos + 2);
|
||||
ptsX.add(plane.mapXDouble(x));
|
||||
ptsY.add(plane.mapYDouble(y));
|
||||
if (inArea) {
|
||||
if (ptsX.size() == 1 && currentPolyPts == 0) {
|
||||
addAreaLineStart(x, y);
|
||||
} else {
|
||||
addAreaPoint(x, y);
|
||||
}
|
||||
}
|
||||
curX = x;
|
||||
curY = y;
|
||||
pos += 4;
|
||||
|
||||
@@ -44,6 +44,15 @@ public class GraphicsPlane {
|
||||
|
||||
private int screenCols = 80;
|
||||
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) {
|
||||
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) {
|
||||
int color = (colorArgb != 0) ? colorArgb : 0xFFFFFFFF;
|
||||
int color = (colorArgb != 0) ? colorArgb : GocaConstants.GOCA_COLORS[0];
|
||||
int thickness = (lineWidth == GocaConstants.LW_THICK) ? 2 : 1;
|
||||
|
||||
int dx = Math.abs(x2 - x1);
|
||||
@@ -517,12 +526,23 @@ public class GraphicsPlane {
|
||||
int fillColorArgb, int pattern,
|
||||
boolean drawBoundary, int boundaryColorArgb, int lineType, int lineWidth,
|
||||
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;
|
||||
|
||||
int fill = (fillColorArgb != 0) ? fillColorArgb : 0xFFFFFFFF;
|
||||
int fill = (fillColorArgb != 0) ? fillColorArgb : GocaConstants.GOCA_COLORS[0];
|
||||
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
|
||||
int minY = py[0];
|
||||
int maxY = py[0];
|
||||
@@ -534,7 +554,18 @@ public class GraphicsPlane {
|
||||
maxY = Math.min(canvasHeight - 1, maxY);
|
||||
|
||||
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++) {
|
||||
nodeX.clear();
|
||||
@@ -567,13 +598,26 @@ public class GraphicsPlane {
|
||||
int rightX = Math.min(canvasWidth - 1, nodeX.get(i + 1));
|
||||
|
||||
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);
|
||||
} else {
|
||||
int b = patRows[y & 7] & 0xFF;
|
||||
if (((b >> (7 - (x & 7))) & 1) != 0) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -610,7 +654,7 @@ public class GraphicsPlane {
|
||||
* 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) {
|
||||
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);
|
||||
|
||||
switch (markerType) {
|
||||
@@ -731,7 +775,7 @@ public class GraphicsPlane {
|
||||
public synchronized void drawVectorText(double x, double y, String text, int colorArgb,
|
||||
double cellWidth, double cellHeight, int dir, double angle) {
|
||||
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 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) {
|
||||
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;
|
||||
|
||||
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)
|
||||
// GEAR (0x60)
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(GocaConstants.G_GSPT);
|
||||
out.write(GocaConstants.PT_SOLID);
|
||||
out.write(GocaConstants.G_GBAR);
|
||||
out.write(0x80);
|
||||
|
||||
@@ -555,4 +557,240 @@ public class GocaDecoderTest {
|
||||
}
|
||||
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