This commit is contained in:
2026-08-28 13:36:02 -04:00
parent f310d39fc1
commit 40ebd40fe2
11 changed files with 1180 additions and 49 deletions
@@ -0,0 +1,99 @@
package haus.nightmare.lib3270j.graphics;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class FillAreaTest {
@Test
public void testEmptyAndEdgeAddition() {
FillArea area = new FillArea();
assertTrue(area.isEmpty());
assertEquals(0, area.getEdgeCount());
area.addEdge(10.0, 10.0, 50.0, 50.0);
assertFalse(area.isEmpty());
assertEquals(1, area.getEdgeCount());
area.clear();
assertTrue(area.isEmpty());
assertEquals(0, area.getEdgeCount());
}
@Test
public void testAddPolygonAndSubpaths() {
FillArea area = new FillArea();
int[] px = new int[] { 10, 50, 50, 10 };
int[] py = new int[] { 10, 10, 50, 50 };
area.addPolygon(px, py, 4);
assertEquals(1, area.getSubpathCount());
// 2 non-horizontal directed edges for a closed 4-sided polygon (horizontal edges skipped in active edge table)
assertEquals(2, area.getEdgeCount());
}
@Test
public void testRasterizationSolidAndHatch() {
GraphicsPlane plane = new GraphicsPlane(100, 100);
FillArea area = new FillArea();
// Add a 40x40 box at (20,20) to (60,60)
area.addPolygon(new int[] { 20, 60, 60, 20 }, new int[] { 20, 20, 60, 60 }, 4);
int red = 0xFFFF0000;
area.fill(plane, red, 0, GocaConstants.PT_SOLID, true, red,
GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL, GocaConstants.MIX_DEFAULT, 0xFF000000, null);
assertTrue(plane.hasContent());
int[] buffer = plane.getRgbBuffer();
int redCount = 0;
for (int p : buffer) {
if (p == red) redCount++;
}
// Interior of 40x40 box should have ~1600 red pixels
assertTrue(redCount > 1200, "Expected filled red pixels in solid polygon");
// Clear plane and test Pattern 5 (Checkerboard)
plane.clear();
area.fill(plane, red, 0, 5, true, red,
GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL, GocaConstants.MIX_DEFAULT, 0xFF000000, null);
assertTrue(plane.hasContent());
int patCount = 0;
int zeroCount = 0;
for (int y = 25; y <= 55; y++) {
for (int x = 25; x <= 55; x++) {
int p = buffer[y * 100 + x];
if (p == red) patCount++;
else if (p == 0) zeroCount++;
}
}
assertTrue(patCount > 0, "Expected patterned red pixels");
assertTrue(zeroCount > 0, "Expected transparent gaps in checkerboard pattern under BMX_LEAVE");
}
@Test
public void testBackgroundMixOverpaint() {
GraphicsPlane plane = new GraphicsPlane(100, 100);
FillArea area = new FillArea();
area.addPolygon(new int[] { 20, 60, 60, 20 }, new int[] { 20, 20, 60, 60 }, 4);
int red = 0xFFFF0000;
int blue = 0xFF0000FF; // Background overpaint color
area.fill(plane, red, 0, 5, false, 0,
GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL, GocaConstants.MIX_OVER, blue, null);
int[] buffer = plane.getRgbBuffer();
int redCount = 0;
int blueCount = 0;
for (int y = 25; y <= 55; y++) {
for (int x = 25; x <= 55; x++) {
int p = buffer[y * 100 + x];
if (p == red) redCount++;
else if (p == blue) blueCount++;
}
}
assertTrue(redCount > 0, "Expected pattern foreground red pixels");
assertTrue(blueCount > 0, "Expected pattern background blue pixels under MIX_OVER");
}
}
@@ -0,0 +1,71 @@
package haus.nightmare.lib3270j.graphics;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class FilletPtsTest {
@Test
public void testEmptyAndSinglePointInputs() {
double[][] empty = FilletPts.calculate((double[]) null, (double[]) null, 0, 10);
assertEquals(0, empty[0].length);
assertEquals(0, empty[1].length);
double[][] single = FilletPts.calculate(new double[] { 10.0 }, new double[] { 20.0 }, 1, 10);
assertEquals(1, single[0].length);
assertEquals(10.0, single[0][0], 1e-6);
assertEquals(20.0, single[1][0], 1e-6);
}
@Test
public void testTwoPointsStraightLine() {
double[] px = new double[] { 10.0, 50.0 };
double[] py = new double[] { 20.0, 80.0 };
double[][] res = FilletPts.calculate(px, py, 2, 10);
assertEquals(2, res[0].length);
assertEquals(10.0, res[0][0], 1e-6);
assertEquals(20.0, res[1][0], 1e-6);
assertEquals(50.0, res[0][1], 1e-6);
assertEquals(80.0, res[1][1], 1e-6);
}
@Test
public void testThreePointsQuadraticSpline() {
// Control points: (0, 0) -> (50, 100) -> (100, 0)
double[] px = new double[] { 0.0, 50.0, 100.0 };
double[] py = new double[] { 0.0, 100.0, 0.0 };
int steps = 10;
double[][] res = FilletPts.calculate(px, py, 3, steps);
// Start point must be (0, 0)
assertEquals(0.0, res[0][0], 1e-6);
assertEquals(0.0, res[1][0], 1e-6);
// End point must be (100, 0)
int last = res[0].length - 1;
assertEquals(100.0, res[0][last], 1e-6);
assertEquals(0.0, res[1][last], 1e-6);
// Curve must be symmetric and positive Y in between
assertTrue(res[0].length > 15);
for (int i = 1; i < last; i++) {
assertTrue(res[1][i] > 0.0, "Y coordinate along upward curve should be positive");
assertTrue(res[0][i] >= 0.0 && res[0][i] <= 100.0);
}
}
@Test
public void testIntegerOverload() {
int[] px = new int[] { 10, 50, 90 };
int[] py = new int[] { 10, 100, 10 };
int[][] res = FilletPts.calculate(px, py, 3, 8);
assertEquals(10, res[0][0]);
assertEquals(10, res[1][0]);
int last = res[0].length - 1;
assertEquals(90, res[0][last]);
assertEquals(10, res[1][last]);
assertTrue(res[0].length > 10);
}
}
@@ -0,0 +1,296 @@
package haus.nightmare.lib3270j.graphics;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.awt.Point;
import static org.junit.jupiter.api.Assertions.*;
public class GocaDecoderPhase5Test {
@Test
public void testDecodeGocaByteAndCharOverloads() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(GocaConstants.G_GSCOL); out.write(0x02); // Red
out.write(GocaConstants.G_GLINE); out.write(0x08);
out.write(0x00); out.write(10); out.write(0x00); out.write(10);
out.write(0x00); out.write(50); out.write(0x00); out.write(50);
byte[] stream = out.toByteArray();
decoder.decodeGoca(stream, 0, stream.length);
assertTrue(plane.hasContent());
// Test char overload
char[] charStream = new char[stream.length];
for (int i = 0; i < stream.length; i++) {
charStream[i] = (char) (stream[i] & 0xFF);
}
plane.clear();
assertFalse(plane.hasContent());
decoder.decodeGoca(charStream, 0, charStream.length);
assertTrue(plane.hasContent());
}
@Test
public void testProcessSegmentAndProcedureSegment() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
// Define Segment 5
ByteArrayOutputStream s5 = new ByteArrayOutputStream();
s5.write(GocaConstants.G_BEGSEGM);
s5.write(0x0C);
s5.write(0x00); s5.write(0x00); s5.write(0x00); s5.write(0x05); // Seg ID 5
s5.write(0x00); s5.write(0x00); s5.write(0x00); s5.write(0x00);
s5.write(0x00); s5.write(0x00); s5.write(0x00); s5.write(0x00);
s5.write(GocaConstants.G_GSCOL); s5.write(0x04); // Green
s5.write(GocaConstants.G_GLINE); s5.write(0x08);
s5.write(0x00); s5.write(outCoord(20));
s5.write(0x00); s5.write(outCoord(20));
s5.write(0x00); s5.write(outCoord(80));
s5.write(0x00); s5.write(outCoord(80));
s5.write(GocaConstants.G_ENDSEGM); s5.write(0x00);
byte[] seg5Bytes = s5.toByteArray();
decoder.processSegment(seg5Bytes, 0, seg5Bytes.length);
assertTrue(plane.hasContent());
plane.clear();
assertFalse(plane.hasContent());
// Execute via procedureSegment(5)
decoder.procedureSegment(5);
assertTrue(plane.hasContent());
}
@Test
public void testChainedSegmentsExecution() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
// Segment 10 chains to Segment 11
ByteArrayOutputStream s10 = new ByteArrayOutputStream();
s10.write(GocaConstants.G_BEGSEGM);
s10.write(0x0C);
s10.write(0x00); s10.write(0x00); s10.write(0x00); s10.write(0x0A); // Seg 10
s10.write(0x00); s10.write(0x00);
s10.write(0x00); s10.write(0x00); // flags
s10.write(0x00); s10.write(0x00); s10.write(0x00); s10.write(0x0B); // Next Seg ID = 11!
s10.write(GocaConstants.G_GSCOL); s10.write(0x01); // Blue
s10.write(GocaConstants.G_GLINE); s10.write(0x08);
s10.write(0x00); s10.write(10); s10.write(0x00); s10.write(10);
s10.write(0x00); s10.write(30); s10.write(0x00); s10.write(30);
s10.write(GocaConstants.G_ENDSEGM); s10.write(0x00);
// Segment 11 draws a line in Yellow (Color 6)
ByteArrayOutputStream s11 = new ByteArrayOutputStream();
s11.write(GocaConstants.G_BEGSEGM);
s11.write(0x0C);
s11.write(0x00); s11.write(0x00); s11.write(0x00); s11.write(0x0B); // Seg 11
s11.write(0x00); s11.write(0x00);
s11.write(0x00); s11.write(0x00);
s11.write(0x00); s11.write(0x00); s11.write(0x00); s11.write(0x00);
s11.write(GocaConstants.G_GSCOL); s11.write(0x06); // Yellow
s11.write(GocaConstants.G_GLINE); s11.write(0x08);
s11.write(0x00); s11.write(40); s11.write(0x00); s11.write(40);
s11.write(0x00); s11.write(70); s11.write(0x00); s11.write(70);
s11.write(GocaConstants.G_ENDSEGM); s11.write(0x00);
// Store Segment 11 first
decoder.decodeGoca(s11.toByteArray(), 0, s11.toByteArray().length);
plane.clear();
assertFalse(plane.hasContent());
// Execute Segment 10; upon ENDSEGM, Segment 11 should be automatically chained!
decoder.decodeGoca(s10.toByteArray(), 0, s10.toByteArray().length);
assertTrue(plane.hasContent());
// Verify both Blue (Seg 10) and Yellow (Seg 11) pixels exist
int blueArgb = GocaConstants.GOCA_COLORS[1];
int yellowArgb = GocaConstants.GOCA_COLORS[6];
boolean foundBlue = false;
boolean foundYellow = false;
for (int p : plane.getRgbBuffer()) {
if (p == blueArgb) foundBlue = true;
if (p == yellowArgb) foundYellow = true;
}
assertTrue(foundBlue, "Expected Blue pixel from Segment 10");
assertTrue(foundYellow, "Expected Yellow pixel from chained Segment 11");
}
@Test
public void testEllipticArcConjugateParameters() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Set Arc Parameters (P=20, Q=0, R=0, S=10)
out.write(GocaConstants.G_GSAP);
out.write(0x08);
out.write(0x00); out.write(20); // P = 20
out.write(0x00); out.write(0); // Q = 0
out.write(0x00); out.write(0); // R = 0
out.write(0x00); out.write(10); // S = 10
// Full Arc Absolute at (100, 100), Multiplier = 1.0 (0x01, 0x00)
out.write(GocaConstants.G_GFARC);
out.write(0x06); // 4 bytes center + 2 bytes multiplier
out.write(0x00); out.write(100); // Center X = 100
out.write(0x00); out.write(100); // Center Y = 100
out.write(0x01); out.write(0x00); // Multiplier 1.0
byte[] stream = out.toByteArray();
decoder.decodeGoca(stream, 0, stream.length);
assertTrue(plane.hasContent());
}
@Test
public void testFilletDrawingAndAreaAccumulation() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Set Color Pink (3)
out.write(GocaConstants.G_GSCOL); out.write(0x03);
// Fillet Absolute (0,0) -> (50, 80) -> (100, 0)
out.write(GocaConstants.G_GFLT);
out.write(0x0C); // 3 points * 4 bytes = 12 bytes
out.write(0x00); out.write(0); out.write(0x00); out.write(0);
out.write(0x00); out.write(50); out.write(0x00); out.write(80);
out.write(0x00); out.write(100);out.write(0x00); out.write(0);
byte[] stream = out.toByteArray();
decoder.decodeGoca(stream, 0, stream.length);
assertTrue(plane.hasContent());
}
@Test
public void testGraphicsCursorAndErase() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
assertFalse(decoder.isGraphicsCursorActive());
decoder.attachGraphicCursor(50, 75);
assertTrue(decoder.isGraphicsCursorActive());
assertEquals(50, decoder.getGraphicCursorX());
assertEquals(75, decoder.getGraphicCursorY());
assertTrue(plane.isGraphicCursorAttached());
decoder.setHodCursorShape(2);
assertEquals(2, plane.getHodCursorShape());
decoder.detachGraphicCursor();
assertFalse(decoder.isGraphicsCursorActive());
assertFalse(plane.isGraphicCursorAttached());
plane.setPixel(10, 10, 0xFFFFFFFF);
assertTrue(plane.hasContent());
decoder.eraseGraphicsPlane();
assertFalse(plane.hasContent());
}
@Test
public void testGddmCoordinateTransformCalculate() {
GddmCoordinateTransform transform = new GddmCoordinateTransform(80, 24, 9, 16);
Point p1 = transform.calculate(0, 0);
Point p2 = transform.gocaToBase(0, 0);
assertEquals(p2, p1);
assertEquals(360, p1.x);
assertEquals(191, p1.y);
}
@Test
public void testProgramSymbolManagerSingleAndTriplePlane() {
ProgramSymbolManager psm = new ProgramSymbolManager(9, 16);
// LoadPS Format 1 Single-Plane (Flags=0x01, LCID=0x41, Start=0x40, RWS=0x02)
// 9x16 char slice: 18 bytes per symbol
byte[] loadps1 = new byte[4 + 18];
loadps1[0] = 0x01; // Format 1
loadps1[1] = 0x41; // LCID 0x41
loadps1[2] = 0x40; // Start codepoint
loadps1[3] = 0x02; // RWS slot 2
for (int i = 0; i < 18; i++) {
loadps1[4 + i] = (byte) 0xAA;
}
psm.loadps(loadps1);
ProgramSymbolSet set = psm.getSymbolSet(0x41);
assertNotNull(set);
ProgramSymbolSet.SymbolSlot slot = psm.getSymbol(0x41, 0x40);
assertNotNull(slot);
assertTrue(slot.isLoaded());
// Test clearSlot and clearSymbolSet
psm.clearSlot(0x41, 0x40);
assertNull(psm.getSymbol(0x41, 0x40));
psm.clearSymbolSet(0x41);
assertNull(psm.getSymbolSet(0x41));
// LoadPS Format 1 Triple-Plane (Flags=0x01, LCID=0x42, Start=0x40, RWS=0x04)
// 3 planes * 18 bytes = 54 bytes
byte[] loadps3Plane = new byte[4 + 54];
loadps3Plane[0] = 0x01; // Format 1
loadps3Plane[1] = 0x42; // LCID 0x42
loadps3Plane[2] = 0x40; // Start codepoint
loadps3Plane[3] = 0x04; // RWS slot 4 (Triple Plane)
for (int i = 0; i < 54; i++) {
loadps3Plane[4 + i] = (byte) 0xFF;
}
psm.loadps(loadps3Plane);
ProgramSymbolSet tripleSet = psm.getSymbolSet(0x42);
assertNotNull(tripleSet);
assertTrue(tripleSet.isTriplePlane());
}
@Test
public void testVectorSymbolDataIndexes() {
int[] vssIdx = VectorSymbolData.buildVssIndex();
assertEquals(256, vssIdx.length);
assertTrue(vssIdx[VectorSymbolData.VSS_SYMBOL_START] >= 0);
assertTrue(VectorSymbolData.getVssOffset(65) >= 0); // 'A'
int[] markerIdx = VectorSymbolData.buildMarkerIndex();
assertEquals(32, markerIdx.length);
assertTrue(markerIdx[1] >= 0);
}
@Test
public void testGraphicsPlanePatternAndVssDrawing() {
GraphicsPlane plane = new GraphicsPlane(300, 200);
plane.setPattern(5);
assertEquals(5, plane.getPattern());
plane.setPatternSet(0x40);
assertEquals(0x40, plane.getPatternSet());
// Draw VSS glyph 'A' (code 65)
plane.drawHodVss(65, 50.0, 50.0, 16.0, 24.0, 0xFF00FF00);
assertTrue(plane.hasContent());
}
@Test
public void testGraphicInputBuilderAllOverloads() {
byte[] sf1 = GraphicInputBuilder.buildGraphicInput(100, 200, 1, true, false, false);
assertEquals(56, sf1.length);
assertEquals(0x00, sf1[0]);
assertEquals(0x34, sf1[1]);
assertEquals(100, ((sf1[24] & 0xFF) << 8) | (sf1[25] & 0xFF));
assertEquals(200, ((sf1[26] & 0xFF) << 8) | (sf1[27] & 0xFF));
byte[] sf2 = GraphicInputBuilder.buildGraphicInput(50, 75, 5, 10, 0xF1, false, true, false, 2, 42);
assertEquals(56, sf2.length);
assertEquals(0x00, sf2[0]);
assertEquals(0x34, sf2[1]);
}
private static byte outCoord(int val) {
return (byte) (val & 0xFF);
}
}