This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package org.pubvm.j3270.ui;
|
package org.pubvm.j3270.ui;
|
||||||
|
|
||||||
import org.lib3270j.Telnet3270Client;
|
import org.lib3270j.Telnet3270Client;
|
||||||
|
import org.lib3270j.graphics.GocaConstants;
|
||||||
import org.lib3270j.screen.ExtendedAttribute;
|
import org.lib3270j.screen.ExtendedAttribute;
|
||||||
import org.lib3270j.screen.ScreenBuffer;
|
import org.lib3270j.screen.ScreenBuffer;
|
||||||
|
|
||||||
@@ -905,6 +906,63 @@ public class TerminalPanel extends JPanel {
|
|||||||
|
|
||||||
public void setClient(Telnet3270Client client) {
|
public void setClient(Telnet3270Client client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
if (client != null) {
|
||||||
|
setupGraphicsPlaneRenderer();
|
||||||
|
updateCellSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupGraphicsPlaneRenderer() {
|
||||||
|
if (client != null && client.getGraphicsPlane() != null) {
|
||||||
|
client.getGraphicsPlane().setTextRenderer((plane, x, y, text, colorArgb, cw, ch, dir, angle) -> {
|
||||||
|
int[] rgb = plane.getRgbBuffer();
|
||||||
|
int pw = plane.getCanvasWidth();
|
||||||
|
int ph = plane.getCanvasHeight();
|
||||||
|
if (rgb == null || pw <= 0 || ph <= 0) return;
|
||||||
|
|
||||||
|
java.awt.image.BufferedImage img = new java.awt.image.BufferedImage(
|
||||||
|
pw, ph, java.awt.image.BufferedImage.TYPE_INT_ARGB
|
||||||
|
);
|
||||||
|
img.setRGB(0, 0, pw, ph, rgb, 0, pw);
|
||||||
|
|
||||||
|
Graphics2D g2 = img.createGraphics();
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||||
|
|
||||||
|
int fontSize = (int) Math.round(ch * 0.95);
|
||||||
|
if (fontSize < 10) fontSize = 10;
|
||||||
|
Font f = (boldTerminalFont != null ? boldTerminalFont : terminalFont).deriveFont((float) fontSize);
|
||||||
|
g2.setFont(f);
|
||||||
|
FontMetrics fm = g2.getFontMetrics();
|
||||||
|
int ascent = fm.getAscent();
|
||||||
|
|
||||||
|
g2.setColor(new Color(colorArgb, true));
|
||||||
|
|
||||||
|
double curX = x;
|
||||||
|
double curY = y;
|
||||||
|
for (int i = 0; i < text.length(); i++) {
|
||||||
|
String s = text.substring(i, i + 1);
|
||||||
|
int charW = fm.stringWidth(s);
|
||||||
|
int drawX = (int) Math.round(curX + Math.max(0, (cw - charW) / 2.0));
|
||||||
|
int drawY = (int) Math.round(curY + ascent + Math.max(0, (ch - fm.getHeight()) / 2.0));
|
||||||
|
g2.drawString(s, drawX, drawY);
|
||||||
|
|
||||||
|
switch (dir) {
|
||||||
|
case GocaConstants.CD_TB: curY += ch; break;
|
||||||
|
case GocaConstants.CD_RL: curX -= cw; break;
|
||||||
|
case GocaConstants.CD_BT: curY -= ch; break;
|
||||||
|
case GocaConstants.CD_LR:
|
||||||
|
case GocaConstants.CD_DEFAULT:
|
||||||
|
default:
|
||||||
|
curX += cw;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g2.dispose();
|
||||||
|
img.getRGB(0, 0, pw, ph, rgb, 0, pw);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -931,9 +989,12 @@ public class TerminalPanel extends JPanel {
|
|||||||
|
|
||||||
Graphics2D g2 = (Graphics2D) g;
|
Graphics2D g2 = (Graphics2D) g;
|
||||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
|
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
|
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
|
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||||
|
|
||||||
// Clear entire panel with background color
|
// Clear entire panel with background color
|
||||||
g2.setColor(bgColor);
|
g2.setColor(bgColor);
|
||||||
@@ -962,9 +1023,13 @@ public class TerminalPanel extends JPanel {
|
|||||||
cachedGraphicsImage.setRGB(0, 0, gWidth, gHeight, rgb, 0, gWidth);
|
cachedGraphicsImage.setRGB(0, 0, gWidth, gHeight, rgb, 0, gWidth);
|
||||||
lastGraphicsUpdateCount = currentUpdateCount;
|
lastGraphicsUpdateCount = currentUpdateCount;
|
||||||
}
|
}
|
||||||
|
if (gWidth == gridW && gHeight == gridH) {
|
||||||
|
g2.drawImage(cachedGraphicsImage, ox, oy, null);
|
||||||
|
} else {
|
||||||
g2.drawImage(cachedGraphicsImage, ox, oy, gridW, gridH, null);
|
g2.drawImage(cachedGraphicsImage, ox, oy, gridW, gridH, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Track current field attribute for monochrome color decisions
|
// Track current field attribute for monochrome color decisions
|
||||||
byte currentFA = 0;
|
byte currentFA = 0;
|
||||||
|
|||||||
@@ -105,6 +105,12 @@ public final class GocaConstants {
|
|||||||
public static final int LW_NORMAL = 1;
|
public static final int LW_NORMAL = 1;
|
||||||
public static final int LW_THICK = 2;
|
public static final int LW_THICK = 2;
|
||||||
|
|
||||||
|
// Character Precision (G_GSCC / 0x3B)
|
||||||
|
public static final int CP_DEFAULT = 0;
|
||||||
|
public static final int CP_STRING = 1;
|
||||||
|
public static final int CP_CHAR = 2;
|
||||||
|
public static final int CP_STROKE = 3;
|
||||||
|
|
||||||
// Fill Patterns (GSPT)
|
// Fill Patterns (GSPT)
|
||||||
public static final int PT_DEFAULT = 0;
|
public static final int PT_DEFAULT = 0;
|
||||||
public static final int PT_D1 = 1;
|
public static final int PT_D1 = 1;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public class GocaDecoder {
|
|||||||
private int charWidth = 9;
|
private int charWidth = 9;
|
||||||
private int charHeight = 16;
|
private int charHeight = 16;
|
||||||
private int charSet = 0;
|
private int charSet = 0;
|
||||||
|
private int charPrecision = GocaConstants.CP_STRING;
|
||||||
private int arcParamP = 1;
|
private int arcParamP = 1;
|
||||||
private int arcParamQ = 0;
|
private int arcParamQ = 0;
|
||||||
private int arcParamR = 0;
|
private int arcParamR = 0;
|
||||||
@@ -47,6 +48,8 @@ public class GocaDecoder {
|
|||||||
private boolean areaFill = true;
|
private boolean areaFill = true;
|
||||||
private final List<Integer> areaPointsX = new ArrayList<>();
|
private final List<Integer> areaPointsX = new ArrayList<>();
|
||||||
private final List<Integer> areaPointsY = new ArrayList<>();
|
private final List<Integer> areaPointsY = new ArrayList<>();
|
||||||
|
private final List<Integer> areaPolygons = new ArrayList<>();
|
||||||
|
private int currentPolyPts = 0;
|
||||||
|
|
||||||
// Image accumulation
|
// Image accumulation
|
||||||
private boolean inImage = false;
|
private boolean inImage = false;
|
||||||
@@ -212,6 +215,7 @@ public class GocaDecoder {
|
|||||||
charDir = GocaConstants.CD_LR;
|
charDir = GocaConstants.CD_LR;
|
||||||
charAngle = 0.0;
|
charAngle = 0.0;
|
||||||
charSet = 0;
|
charSet = 0;
|
||||||
|
charPrecision = GocaConstants.CP_STRING;
|
||||||
inArea = false;
|
inArea = false;
|
||||||
areaDrawBoundary = true;
|
areaDrawBoundary = true;
|
||||||
areaFill = true;
|
areaFill = true;
|
||||||
@@ -226,24 +230,30 @@ public class GocaDecoder {
|
|||||||
/**
|
/**
|
||||||
* Determines the total byte length of a GOCA drawing order starting at data[idx].
|
* Determines the total byte length of a GOCA drawing order starting at data[idx].
|
||||||
*
|
*
|
||||||
* IMPORTANT ARCHITECTURE NOTE:
|
* IMPORTANT ARCHITECTURE & PARSING SAFETY NOTE:
|
||||||
* GOCA orders follow IBM GA23-0059 architecture rules:
|
* GOCA orders follow IBM GA23-0059 and Host On-Demand (HOD) architecture rules:
|
||||||
* 1. 1-byte standalone orders (NOP, etc.) -> length 1.
|
* 1. 1-byte standalone orders (NOP, ERASE, etc.) -> length 1.
|
||||||
* 2. Delimiter orders (GEAR, ENDSEGM, ENDPROLOGUE, GEIMG) -> 1 or 2 bytes (with 0x00 trailing byte).
|
* 2. Delimiter orders (GEAR, ENDSEGM, ENDPROLOGUE, GEIMG, GPOP) -> 1 or 2 bytes (with 0x00 trailing byte).
|
||||||
* 3. Fixed 1-byte immediate 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.
|
||||||
* 4. Orders with opcode >= 0x20 (including GCALL 0x2A, GSCS 0x38, GSCD 0x3A, GSPT 0x28, GSMT 0x29, GBAR 0x68,
|
* 4. Flexible 1-byte attribute orders (GBAR 0x68, GSCS 0x38, GSCD 0x3A, GSCC 0x3B, GSMP 0x39,
|
||||||
* GLINE 0xC1, GARC 0xC6, GCHST 0xC3, etc.) are self-defining with a 1-byte length field data[idx+1],
|
* GSPT 0x28, GSMT 0x29, GSMS_SET 0x3C) can be transmitted either as:
|
||||||
* making total length = payloadLen + 2.
|
* - Short 2-byte form: [opcode] [value] (e.g. GBAR with flags 0x80 -> '68 80')
|
||||||
* Never hardcode orders >= 0x20 to 2 bytes, as that desynchronizes the GOCA order stream.
|
* - 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.)
|
||||||
|
* 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) {
|
if (order == GocaConstants.G_NOP1 || order == 0xFF || order == 0x00) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (order == GocaConstants.G_GEAR ||
|
if (order == GocaConstants.G_GEAR ||
|
||||||
order == GocaConstants.G_ENDSEGM || order == GocaConstants.G_ENDPROLOGUE ||
|
order == GocaConstants.G_ENDSEGM || order == GocaConstants.G_ENDPROLOGUE ||
|
||||||
order == GocaConstants.G_GEIMG || order == GocaConstants.G_GPOP) {
|
order == GocaConstants.G_GEIMG || order == GocaConstants.G_GPOP ||
|
||||||
|
order == GocaConstants.G_GERASE) {
|
||||||
return (idx + 1 < end && data[idx + 1] == 0x00) ? 2 : 1;
|
return (idx + 1 < end && data[idx + 1] == 0x00) ? 2 : 1;
|
||||||
}
|
}
|
||||||
if (idx + 1 >= end) {
|
if (idx + 1 >= end) {
|
||||||
@@ -259,7 +269,8 @@ public class GocaDecoder {
|
|||||||
// Flexible 1-byte attribute orders (support both short 2-byte or long 3-byte if len byte == 1)
|
// Flexible 1-byte attribute orders (support both short 2-byte or long 3-byte if len byte == 1)
|
||||||
if (order == GocaConstants.G_GSPT || order == GocaConstants.G_GSMT ||
|
if (order == GocaConstants.G_GSPT || order == GocaConstants.G_GSMT ||
|
||||||
order == GocaConstants.G_GSCS || order == GocaConstants.G_GSCD ||
|
order == GocaConstants.G_GSCS || order == GocaConstants.G_GSCD ||
|
||||||
order == GocaConstants.G_GBAR) {
|
order == GocaConstants.G_GSCC || order == GocaConstants.G_GSMP ||
|
||||||
|
order == GocaConstants.G_GSMS_SET || order == GocaConstants.G_GBAR) {
|
||||||
return (data[idx + 1] == 0x01 && idx + 2 < end) ? 3 : 2;
|
return (data[idx + 1] == 0x01 && idx + 2 < end) ? 3 : 2;
|
||||||
}
|
}
|
||||||
if (order == GocaConstants.G_GCALL) {
|
if (order == GocaConstants.G_GCALL) {
|
||||||
@@ -578,10 +589,15 @@ public class GocaDecoder {
|
|||||||
idx += orderLen;
|
idx += orderLen;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case GocaConstants.G_GSCC: { // Set Character Precision (0x3B)
|
||||||
|
charPrecision = (orderLen == 3) ? (inputData[idx + 2] & 0xFF) : (inputData[idx + 1] & 0xFF);
|
||||||
|
if (charPrecision == 0) charPrecision = GocaConstants.CP_STRING;
|
||||||
|
idx += orderLen;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 0x04:
|
case 0x04:
|
||||||
case GocaConstants.G_GSMX:
|
case GocaConstants.G_GSMX:
|
||||||
case GocaConstants.G_GSFLW:
|
case GocaConstants.G_GSFLW:
|
||||||
case GocaConstants.G_GSCC:
|
|
||||||
case GocaConstants.G_GSMS_SET:
|
case GocaConstants.G_GSMS_SET:
|
||||||
case GocaConstants.G_GPOP: {
|
case GocaConstants.G_GPOP: {
|
||||||
idx += orderLen;
|
idx += orderLen;
|
||||||
@@ -594,10 +610,8 @@ public class GocaDecoder {
|
|||||||
}
|
}
|
||||||
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 == 0);
|
boolean drawBoundary = (flags & 0x80) != 0 || (flags & 0x40) != 0 || (flags == 0);
|
||||||
boolean fill = (flags == 0) || (flags & 0x40) != 0 ||
|
beginArea(drawBoundary);
|
||||||
(pattern >= 1 && pattern <= 14);
|
|
||||||
beginArea(drawBoundary, fill);
|
|
||||||
idx += orderLen;
|
idx += orderLen;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -814,22 +828,31 @@ public class GocaDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beginArea(boolean drawBoundary, boolean fill) {
|
private void beginArea(boolean drawBoundary) {
|
||||||
this.inArea = true;
|
this.inArea = true;
|
||||||
this.areaDrawBoundary = drawBoundary;
|
this.areaDrawBoundary = drawBoundary;
|
||||||
this.areaFill = fill;
|
this.areaFill = true;
|
||||||
this.fillColor = this.curColor;
|
this.fillColor = this.curColor;
|
||||||
this.areaPointsX.clear();
|
this.areaPointsX.clear();
|
||||||
this.areaPointsY.clear();
|
this.areaPointsY.clear();
|
||||||
|
this.areaPolygons.clear();
|
||||||
|
this.currentPolyPts = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endArea() {
|
private void endArea() {
|
||||||
if (!inArea || areaPointsX.size() < 3) {
|
if (!inArea) return;
|
||||||
|
if (currentPolyPts > 0) {
|
||||||
|
areaPolygons.add(currentPolyPts);
|
||||||
|
currentPolyPts = 0;
|
||||||
|
}
|
||||||
|
if (areaPointsX.size() < 3 || areaPolygons.isEmpty()) {
|
||||||
inArea = false;
|
inArea = false;
|
||||||
areaPointsX.clear();
|
areaPointsX.clear();
|
||||||
areaPointsY.clear();
|
areaPointsY.clear();
|
||||||
|
areaPolygons.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int n = areaPointsX.size();
|
int n = areaPointsX.size();
|
||||||
int[] px = new int[n];
|
int[] px = new int[n];
|
||||||
int[] py = new int[n];
|
int[] py = new int[n];
|
||||||
@@ -838,21 +861,49 @@ public class GocaDecoder {
|
|||||||
py[i] = plane.mapY(areaPointsY.get(i));
|
py[i] = plane.mapY(areaPointsY.get(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
plane.fillArea(px, py, n, fillColor, areaFill ? pattern : GocaConstants.PT_EMPTY,
|
int numPolys = areaPolygons.size();
|
||||||
areaDrawBoundary, curColor, lineType, lineWidth, bgMix, bgColor);
|
int[] polyCounts = new int[numPolys];
|
||||||
|
for (int i = 0; i < numPolys; i++) {
|
||||||
|
polyCounts[i] = areaPolygons.get(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
plane.fillArea(px, py, n, polyCounts, numPolys, fillColor,
|
||||||
|
pattern, areaDrawBoundary, curColor, lineType, lineWidth, bgMix, bgColor);
|
||||||
inArea = false;
|
inArea = false;
|
||||||
areaPointsX.clear();
|
areaPointsX.clear();
|
||||||
areaPointsY.clear();
|
areaPointsY.clear();
|
||||||
|
areaPolygons.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAreaLineStart(int startX, int startY) {
|
||||||
|
if (!inArea) return;
|
||||||
|
int sz = areaPointsX.size();
|
||||||
|
if (sz > 0 && currentPolyPts > 0) {
|
||||||
|
int lastX = areaPointsX.get(sz - 1);
|
||||||
|
int lastY = areaPointsY.get(sz - 1);
|
||||||
|
if (lastX != startX || lastY != startY) {
|
||||||
|
areaPolygons.add(currentPolyPts);
|
||||||
|
currentPolyPts = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentPolyPts == 0) {
|
||||||
|
areaPointsX.add(startX);
|
||||||
|
areaPointsY.add(startY);
|
||||||
|
currentPolyPts++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAreaPoint(int x, int y) {
|
private void addAreaPoint(int x, int y) {
|
||||||
if (inArea) {
|
if (!inArea) return;
|
||||||
int sz = areaPointsX.size();
|
int sz = areaPointsX.size();
|
||||||
if (sz == 0 || areaPointsX.get(sz - 1) != x || areaPointsY.get(sz - 1) != y) {
|
if (sz > 0 && currentPolyPts > 0) {
|
||||||
|
if (areaPointsX.get(sz - 1) == x && areaPointsY.get(sz - 1) == y) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
areaPointsX.add(x);
|
areaPointsX.add(x);
|
||||||
areaPointsY.add(y);
|
areaPointsY.add(y);
|
||||||
}
|
currentPolyPts++;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beginImage(int x, int y, int w, int h) {
|
private void beginImage(int x, int y, int w, int h) {
|
||||||
@@ -897,7 +948,7 @@ public class GocaDecoder {
|
|||||||
trackPoint(startX, startY);
|
trackPoint(startX, startY);
|
||||||
|
|
||||||
if (inArea) {
|
if (inArea) {
|
||||||
addAreaPoint(startX, startY);
|
addAreaLineStart(startX, startY);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (pos + 4 <= end) {
|
while (pos + 4 <= end) {
|
||||||
@@ -910,8 +961,8 @@ public class GocaDecoder {
|
|||||||
if (inArea) {
|
if (inArea) {
|
||||||
addAreaPoint(nextX, nextY);
|
addAreaPoint(nextX, nextY);
|
||||||
} else {
|
} else {
|
||||||
plane.drawLine(plane.mapX(startX), plane.mapY(startY),
|
plane.drawLine(plane.mapXDouble(startX), plane.mapYDouble(startY),
|
||||||
plane.mapX(nextX), plane.mapY(nextY),
|
plane.mapXDouble(nextX), plane.mapYDouble(nextY),
|
||||||
curColor, lineType, lineWidth);
|
curColor, lineType, lineWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -939,7 +990,7 @@ public class GocaDecoder {
|
|||||||
trackPoint(startX, startY);
|
trackPoint(startX, startY);
|
||||||
|
|
||||||
if (inArea) {
|
if (inArea) {
|
||||||
addAreaPoint(startX, startY);
|
addAreaLineStart(startX, startY);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (pos + 2 <= end) {
|
while (pos + 2 <= end) {
|
||||||
@@ -955,8 +1006,8 @@ public class GocaDecoder {
|
|||||||
if (inArea) {
|
if (inArea) {
|
||||||
addAreaPoint(nextX, nextY);
|
addAreaPoint(nextX, nextY);
|
||||||
} else {
|
} else {
|
||||||
plane.drawLine(plane.mapX(startX), plane.mapY(startY),
|
plane.drawLine(plane.mapXDouble(startX), plane.mapYDouble(startY),
|
||||||
plane.mapX(nextX), plane.mapY(nextY),
|
plane.mapXDouble(nextX), plane.mapYDouble(nextY),
|
||||||
curColor, lineType, lineWidth);
|
curColor, lineType, lineWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1011,11 +1062,13 @@ public class GocaDecoder {
|
|||||||
if (semiAxis1 < 1.0) semiAxis1 = 1.0;
|
if (semiAxis1 < 1.0) semiAxis1 = 1.0;
|
||||||
if (semiAxis2 < 1.0) semiAxis2 = 1.0;
|
if (semiAxis2 < 1.0) semiAxis2 = 1.0;
|
||||||
|
|
||||||
// Map to pixel space
|
// Map to pixel space with double precision
|
||||||
int rx = Math.abs(plane.mapX((int) Math.round(semiAxis1)) - plane.mapX(0));
|
double cx = plane.mapXDouble(centerX);
|
||||||
int ry = Math.abs(plane.mapY(0) - plane.mapY((int) Math.round(semiAxis2)));
|
double cy = plane.mapYDouble(centerY);
|
||||||
if (rx <= 0) rx = Math.max(1, (int) Math.round(semiAxis1));
|
double rx = Math.abs(plane.mapXDouble(semiAxis1) - plane.mapXDouble(0));
|
||||||
if (ry <= 0) ry = Math.max(1, (int) Math.round(semiAxis2));
|
double ry = Math.abs(plane.mapYDouble(0) - plane.mapYDouble(semiAxis2));
|
||||||
|
if (rx <= 0.0) rx = Math.max(1.0, semiAxis1);
|
||||||
|
if (ry <= 0.0) ry = Math.max(1.0, semiAxis2);
|
||||||
|
|
||||||
// For partial arcs, determine start angle and sweep angle
|
// For partial arcs, determine start angle and sweep angle
|
||||||
double startAngleDeg = 0.0;
|
double startAngleDeg = 0.0;
|
||||||
@@ -1037,14 +1090,14 @@ public class GocaDecoder {
|
|||||||
int sweepFrac = data[pos + 1] & 0xFF;
|
int sweepFrac = data[pos + 1] & 0xFF;
|
||||||
sweepAngleDeg = (sweepInt + sweepFrac / 256.0) * 360.0;
|
sweepAngleDeg = (sweepInt + sweepFrac / 256.0) * 360.0;
|
||||||
if (curX != centerX || curY != centerY) {
|
if (curX != centerX || curY != centerY) {
|
||||||
double startRad = Math.atan2(plane.mapY(centerY) - plane.mapY(curY), plane.mapX(curX) - plane.mapX(centerX));
|
double startRad = Math.atan2(plane.mapYDouble(centerY) - plane.mapYDouble(curY), plane.mapXDouble(curX) - plane.mapXDouble(centerX));
|
||||||
startAngleDeg = Math.toDegrees(startRad);
|
startAngleDeg = Math.toDegrees(startRad);
|
||||||
if (startAngleDeg < 0) startAngleDeg += 360.0;
|
if (startAngleDeg < 0) startAngleDeg += 360.0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No sweep data — if we have a current point, start there and sweep full circle
|
// No sweep data — if we have a current point, start there and sweep full circle
|
||||||
if (curX != centerX || curY != centerY) {
|
if (curX != centerX || curY != centerY) {
|
||||||
double startRad = Math.atan2(plane.mapY(centerY) - plane.mapY(curY), plane.mapX(curX) - plane.mapX(centerX));
|
double startRad = Math.atan2(plane.mapYDouble(centerY) - plane.mapYDouble(curY), plane.mapXDouble(curX) - plane.mapXDouble(centerX));
|
||||||
startAngleDeg = Math.toDegrees(startRad);
|
startAngleDeg = Math.toDegrees(startRad);
|
||||||
if (startAngleDeg < 0) startAngleDeg += 360.0;
|
if (startAngleDeg < 0) startAngleDeg += 360.0;
|
||||||
}
|
}
|
||||||
@@ -1055,11 +1108,7 @@ 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));
|
||||||
|
|
||||||
System.out.println("processArc: center=(" + centerX + "," + centerY + ") cur=(" + curX + "," + curY
|
plane.drawArc(cx, cy, rx, ry, startAngleDeg, sweepAngleDeg,
|
||||||
+ ") rx=" + rx + " ry=" + ry + " start=" + startAngleDeg + " sweep=" + sweepAngleDeg
|
|
||||||
+ " isFull=" + isFull);
|
|
||||||
|
|
||||||
plane.drawArc(plane.mapX(centerX), plane.mapY(centerY), rx, ry, startAngleDeg, sweepAngleDeg,
|
|
||||||
curColor, lineType, lineWidth, isFull);
|
curColor, lineType, lineWidth, isFull);
|
||||||
|
|
||||||
curX = centerX;
|
curX = centerX;
|
||||||
@@ -1070,19 +1119,19 @@ public class GocaDecoder {
|
|||||||
int pos = off;
|
int pos = off;
|
||||||
int end = off + len;
|
int end = off + len;
|
||||||
|
|
||||||
List<Integer> ptsX = new ArrayList<>();
|
List<Double> ptsX = new ArrayList<>();
|
||||||
List<Integer> ptsY = new ArrayList<>();
|
List<Double> ptsY = new ArrayList<>();
|
||||||
|
|
||||||
if (fromCurPos) {
|
if (fromCurPos) {
|
||||||
ptsX.add(plane.mapX(curX));
|
ptsX.add(plane.mapXDouble(curX));
|
||||||
ptsY.add(plane.mapY(curY));
|
ptsY.add(plane.mapYDouble(curY));
|
||||||
}
|
}
|
||||||
|
|
||||||
while (pos + 4 <= end) {
|
while (pos + 4 <= end) {
|
||||||
int x = readCoord(data, pos);
|
int x = readCoord(data, pos);
|
||||||
int y = readCoord(data, pos + 2);
|
int y = readCoord(data, pos + 2);
|
||||||
ptsX.add(plane.mapX(x));
|
ptsX.add(plane.mapXDouble(x));
|
||||||
ptsY.add(plane.mapY(y));
|
ptsY.add(plane.mapYDouble(y));
|
||||||
curX = x;
|
curX = x;
|
||||||
curY = y;
|
curY = y;
|
||||||
pos += 4;
|
pos += 4;
|
||||||
@@ -1090,8 +1139,8 @@ public class GocaDecoder {
|
|||||||
|
|
||||||
if (ptsX.size() >= 2) {
|
if (ptsX.size() >= 2) {
|
||||||
int n = ptsX.size();
|
int n = ptsX.size();
|
||||||
int[] px = new int[n];
|
double[] px = new double[n];
|
||||||
int[] py = new int[n];
|
double[] py = new double[n];
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
px[i] = ptsX.get(i);
|
px[i] = ptsX.get(i);
|
||||||
py[i] = ptsY.get(i);
|
py[i] = ptsY.get(i);
|
||||||
@@ -1106,14 +1155,14 @@ public class GocaDecoder {
|
|||||||
|
|
||||||
if (fromCurPos) {
|
if (fromCurPos) {
|
||||||
trackPoint(curX, curY);
|
trackPoint(curX, curY);
|
||||||
plane.drawMarker(plane.mapX(curX), plane.mapY(curY), markerType, markerSize, markerColor);
|
plane.drawMarker(plane.mapXDouble(curX), plane.mapYDouble(curY), markerType, markerSize, markerColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (pos + 4 <= end) {
|
while (pos + 4 <= end) {
|
||||||
int x = readCoord(data, pos);
|
int x = readCoord(data, pos);
|
||||||
int y = readCoord(data, pos + 2);
|
int y = readCoord(data, pos + 2);
|
||||||
trackPoint(x, y);
|
trackPoint(x, y);
|
||||||
plane.drawMarker(plane.mapX(x), plane.mapY(y), markerType, markerSize, markerColor);
|
plane.drawMarker(plane.mapXDouble(x), plane.mapYDouble(y), markerType, markerSize, markerColor);
|
||||||
curX = x;
|
curX = x;
|
||||||
curY = y;
|
curY = y;
|
||||||
pos += 4;
|
pos += 4;
|
||||||
@@ -1137,8 +1186,8 @@ public class GocaDecoder {
|
|||||||
if (textLen <= 0) return;
|
if (textLen <= 0) return;
|
||||||
|
|
||||||
// IBM 3279 vector graphics base cell is 9x12
|
// IBM 3279 vector graphics base cell is 9x12
|
||||||
int cw = charWidth > 0 ? (int) Math.round((double) charWidth * plane.getCanvasWidth() / (plane.getScreenCols() * 9.0)) : 10;
|
double cw = charWidth > 0 ? ((double) charWidth * plane.getCanvasWidth() / (plane.getScreenCols() * 9.0)) : 10.0;
|
||||||
int ch = charHeight > 0 ? (int) Math.round((double) charHeight * plane.getCanvasHeight() / (plane.getScreenRows() * 12.0)) : 14;
|
double ch = charHeight > 0 ? ((double) charHeight * plane.getCanvasHeight() / (plane.getScreenRows() * 12.0)) : 14.0;
|
||||||
|
|
||||||
int totalW = textLen * (charWidth > 0 ? charWidth : 9);
|
int totalW = textLen * (charWidth > 0 ? charWidth : 9);
|
||||||
int totalH = (charHeight > 0 ? charHeight : 14);
|
int totalH = (charHeight > 0 ? charHeight : 14);
|
||||||
@@ -1149,20 +1198,24 @@ public class GocaDecoder {
|
|||||||
if (charSet != 0 && programSymbolManager != null) {
|
if (charSet != 0 && programSymbolManager != null) {
|
||||||
for (int i = 0; i < textLen; i++) {
|
for (int i = 0; i < textLen; i++) {
|
||||||
int code = data[pos + i] & 0xFF;
|
int code = data[pos + i] & 0xFF;
|
||||||
int px = plane.mapX(startX);
|
double px = plane.mapXDouble(startX);
|
||||||
int py = plane.mapY(startY) - ch;
|
double py = plane.mapYDouble(startY) - ch;
|
||||||
ProgramSymbolSet.SymbolSlot slot = programSymbolManager.getSymbol(charSet, code);
|
ProgramSymbolSet.SymbolSlot slot = programSymbolManager.getSymbol(charSet, code);
|
||||||
if (slot != null) {
|
if (slot != null) {
|
||||||
int[] rgb = slot.getRgbPixels(curColor, 0);
|
int[] rgb = slot.getRgbPixels(curColor, 0);
|
||||||
int symW = slot.getWidth();
|
int symW = slot.getWidth();
|
||||||
int symH = slot.getHeight();
|
int symH = slot.getHeight();
|
||||||
for (int dy = 0; dy < ch; dy++) {
|
int ipx = (int) Math.round(px);
|
||||||
int sy = (dy * symH) / ch;
|
int ipy = (int) Math.round(py);
|
||||||
for (int dx = 0; dx < cw; dx++) {
|
int icw = (int) Math.round(cw);
|
||||||
int sx = (dx * symW) / cw;
|
int ich = (int) Math.round(ch);
|
||||||
|
for (int dy = 0; dy < ich; dy++) {
|
||||||
|
int sy = (dy * symH) / ich;
|
||||||
|
for (int dx = 0; dx < icw; dx++) {
|
||||||
|
int sx = (dx * symW) / icw;
|
||||||
int pixelArgb = rgb[sy * symW + sx];
|
int pixelArgb = rgb[sy * symW + sx];
|
||||||
if ((pixelArgb >>> 24) != 0) {
|
if ((pixelArgb >>> 24) != 0) {
|
||||||
plane.setPixel(px + dx, py + dy, pixelArgb);
|
plane.setPixel(ipx + dx, ipy + dy, pixelArgb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1183,8 +1236,13 @@ public class GocaDecoder {
|
|||||||
}
|
}
|
||||||
String text = new String(chars);
|
String text = new String(chars);
|
||||||
|
|
||||||
plane.drawVectorText(plane.mapX(startX), plane.mapY(startY) - ch, text,
|
if (charPrecision == GocaConstants.CP_STROKE) {
|
||||||
|
plane.drawVectorText(plane.mapXDouble(startX), plane.mapYDouble(startY) - ch, text,
|
||||||
curColor, cw, ch, charDir, charAngle);
|
curColor, cw, ch, charDir, charAngle);
|
||||||
|
} else {
|
||||||
|
plane.drawText(plane.mapXDouble(startX), plane.mapYDouble(startY) - ch, text,
|
||||||
|
curColor, cw, ch, charDir, charAngle);
|
||||||
|
}
|
||||||
|
|
||||||
curX = startX + (textLen * (charWidth > 0 ? charWidth : 9));
|
curX = startX + (textLen * (charWidth > 0 ? charWidth : 9));
|
||||||
curY = startY;
|
curY = startY;
|
||||||
|
|||||||
@@ -121,15 +121,32 @@ public class GraphicsPlane {
|
|||||||
return screenRows;
|
return screenRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a 3179G / GOCA signed coordinate (centered at screen midpoint) to canvas pixel X as a double.
|
||||||
|
*/
|
||||||
|
public double mapXDouble(double gocaX) {
|
||||||
|
int nominalWidth = screenCols * 9;
|
||||||
|
int xMax = (nominalWidth - 1) / 2 + ((nominalWidth - 1) % 2 != 0 ? 1 : 0);
|
||||||
|
double nx = gocaX + xMax;
|
||||||
|
return (nx * canvasWidth) / (double) (nominalWidth > 0 ? nominalWidth : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a 3179G / GOCA signed coordinate (centered at screen midpoint, bottom-up) to canvas pixel Y (top-down) as a double.
|
||||||
|
*/
|
||||||
|
public double mapYDouble(double gocaY) {
|
||||||
|
int nominalHeight = screenRows * 12;
|
||||||
|
int yMax = (nominalHeight - 1) / 2;
|
||||||
|
double ny = yMax - gocaY;
|
||||||
|
return (ny * canvasHeight) / (double) (nominalHeight > 0 ? nominalHeight : 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps a 3179G / GOCA signed coordinate (centered at screen midpoint) to canvas pixel X.
|
* Maps a 3179G / GOCA signed coordinate (centered at screen midpoint) to canvas pixel X.
|
||||||
* Coordinate space is symmetric: -xMax to +xMax, where nominalWidth = cols * 9 (e.g. 720 for 80 cols).
|
* Coordinate space is symmetric: -xMax to +xMax, where nominalWidth = cols * 9 (e.g. 720 for 80 cols).
|
||||||
*/
|
*/
|
||||||
public int mapX(int gocaX) {
|
public int mapX(int gocaX) {
|
||||||
int nominalWidth = screenCols * 9;
|
return (int) Math.round(mapXDouble((double) gocaX));
|
||||||
int xMax = (nominalWidth - 1) / 2 + ((nominalWidth - 1) % 2 != 0 ? 1 : 0);
|
|
||||||
int nx = gocaX + xMax;
|
|
||||||
return (int) Math.round((double) nx * canvasWidth / nominalWidth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,10 +155,7 @@ public class GraphicsPlane {
|
|||||||
* NOTE: Do not apply arbitrary offsets here. The GOCA coordinate system is 1:1 synchronized with host GDDM.
|
* NOTE: Do not apply arbitrary offsets here. The GOCA coordinate system is 1:1 synchronized with host GDDM.
|
||||||
*/
|
*/
|
||||||
public int mapY(int gocaY) {
|
public int mapY(int gocaY) {
|
||||||
int nominalHeight = screenRows * 12;
|
return (int) Math.round(mapYDouble((double) gocaY));
|
||||||
int yMax = (nominalHeight - 1) / 2;
|
|
||||||
int ny = yMax - gocaY;
|
|
||||||
return (int) Math.round((double) ny * canvasHeight / nominalHeight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -167,20 +181,163 @@ public class GraphicsPlane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Safely plots a pixel at (x, y).
|
* Safely plots a pixel at (x, y) with Porter-Duff source-over alpha blending.
|
||||||
*/
|
*/
|
||||||
public synchronized void setPixel(int x, int y, int colorArgb) {
|
public synchronized void setPixel(int x, int y, int colorArgb) {
|
||||||
if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight) {
|
if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight) {
|
||||||
rgbBuffer[y * canvasWidth + x] = colorArgb;
|
int srcA = (colorArgb >>> 24) & 0xFF;
|
||||||
|
if (srcA == 0) return;
|
||||||
|
int idx = y * canvasWidth + x;
|
||||||
|
if (srcA == 255) {
|
||||||
|
rgbBuffer[idx] = colorArgb;
|
||||||
|
} else {
|
||||||
|
int dst = rgbBuffer[idx];
|
||||||
|
int dstA = (dst >>> 24) & 0xFF;
|
||||||
|
if (dstA == 0) {
|
||||||
|
rgbBuffer[idx] = colorArgb;
|
||||||
|
} else {
|
||||||
|
int srcR = (colorArgb >>> 16) & 0xFF;
|
||||||
|
int srcG = (colorArgb >>> 8) & 0xFF;
|
||||||
|
int srcB = colorArgb & 0xFF;
|
||||||
|
|
||||||
|
int dstR = (dst >>> 16) & 0xFF;
|
||||||
|
int dstG = (dst >>> 8) & 0xFF;
|
||||||
|
int dstB = dst & 0xFF;
|
||||||
|
|
||||||
|
int invSrcA = 255 - srcA;
|
||||||
|
int outA = srcA + (dstA * invSrcA + 127) / 255;
|
||||||
|
int outR = (srcR * srcA + dstR * invSrcA + 127) / 255;
|
||||||
|
int outG = (srcG * srcA + dstG * invSrcA + 127) / 255;
|
||||||
|
int outB = (srcB * srcA + dstB * invSrcA + 127) / 255;
|
||||||
|
|
||||||
|
rgbBuffer[idx] = ((outA & 0xFF) << 24) | ((outR & 0xFF) << 16) | ((outG & 0xFF) << 8) | (outB & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
hasContent = true;
|
hasContent = true;
|
||||||
updateCount++;
|
updateCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws an absolute or relative line using Bresenham's algorithm with line styles and widths.
|
* Plots a pixel with fractional alpha coverage (0.0 to 1.0) for anti-aliasing.
|
||||||
|
*/
|
||||||
|
public synchronized void setPixelCoverage(int x, int y, int colorRgb, double coverage) {
|
||||||
|
if (coverage <= 0.0) return;
|
||||||
|
int alpha = (int) Math.round(coverage * 255.0);
|
||||||
|
if (alpha > 255) alpha = 255;
|
||||||
|
if (alpha <= 0) return;
|
||||||
|
setPixel(x, y, (alpha << 24) | (colorRgb & 0x00FFFFFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws an anti-aliased line using Xiaolin Wu's algorithm with sub-pixel double coordinates.
|
||||||
|
*/
|
||||||
|
public synchronized void drawLine(double x0, double y0, double x1, double y1, int colorArgb, int lineType, int lineWidth) {
|
||||||
|
int color = (colorArgb != 0) ? (colorArgb & 0x00FFFFFF) : 0x00FFFFFF;
|
||||||
|
|
||||||
|
if (lineType != GocaConstants.LT_SOLID && lineType != GocaConstants.LT_DEFAULT) {
|
||||||
|
drawStyledLine((int) Math.round(x0), (int) Math.round(y0),
|
||||||
|
(int) Math.round(x1), (int) Math.round(y1),
|
||||||
|
(0xFF << 24) | color, lineType, lineWidth);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case: single point or zero-length line
|
||||||
|
if (Math.abs(x1 - x0) < 1e-5 && Math.abs(y1 - y0) < 1e-5) {
|
||||||
|
drawPixelWithThickness((int) Math.round(x0), (int) Math.round(y0), (0xFF << 24) | color, (lineWidth == GocaConstants.LW_THICK) ? 2 : 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean steep = Math.abs(y1 - y0) > Math.abs(x1 - x0);
|
||||||
|
if (steep) {
|
||||||
|
double tmp = x0; x0 = y0; y0 = tmp;
|
||||||
|
tmp = x1; x1 = y1; y1 = tmp;
|
||||||
|
}
|
||||||
|
if (x0 > x1) {
|
||||||
|
double tmp = x0; x0 = x1; x1 = tmp;
|
||||||
|
tmp = y0; y0 = y1; y1 = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
double dx = x1 - x0;
|
||||||
|
double dy = y1 - y0;
|
||||||
|
double gradient = (dx == 0.0) ? 1.0 : (dy / dx);
|
||||||
|
|
||||||
|
// First endpoint
|
||||||
|
double xend = Math.round(x0);
|
||||||
|
double yend = y0 + gradient * (xend - x0);
|
||||||
|
double xgap = 1.0 - (x0 + 0.5 - Math.floor(x0 + 0.5));
|
||||||
|
int xpxl1 = (int) xend;
|
||||||
|
int ypxl1 = (int) Math.floor(yend);
|
||||||
|
|
||||||
|
if (steep) {
|
||||||
|
plotPixelWu(ypxl1, xpxl1, color, (1.0 - (yend - Math.floor(yend))) * xgap, lineWidth);
|
||||||
|
plotPixelWu(ypxl1 + 1, xpxl1, color, (yend - Math.floor(yend)) * xgap, lineWidth);
|
||||||
|
} else {
|
||||||
|
plotPixelWu(xpxl1, ypxl1, color, (1.0 - (yend - Math.floor(yend))) * xgap, lineWidth);
|
||||||
|
plotPixelWu(xpxl1, ypxl1 + 1, color, (yend - Math.floor(yend)) * xgap, lineWidth);
|
||||||
|
}
|
||||||
|
double intery = yend + gradient;
|
||||||
|
|
||||||
|
// Second endpoint
|
||||||
|
xend = Math.round(x1);
|
||||||
|
yend = y1 + gradient * (xend - x1);
|
||||||
|
xgap = x1 + 0.5 - Math.floor(x1 + 0.5);
|
||||||
|
int xpxl2 = (int) xend;
|
||||||
|
int ypxl2 = (int) Math.floor(yend);
|
||||||
|
|
||||||
|
if (steep) {
|
||||||
|
plotPixelWu(ypxl2, xpxl2, color, (1.0 - (yend - Math.floor(yend))) * xgap, lineWidth);
|
||||||
|
plotPixelWu(ypxl2 + 1, xpxl2, color, (yend - Math.floor(yend)) * xgap, lineWidth);
|
||||||
|
} else {
|
||||||
|
plotPixelWu(xpxl2, ypxl2, color, (1.0 - (yend - Math.floor(yend))) * xgap, lineWidth);
|
||||||
|
plotPixelWu(xpxl2, ypxl2 + 1, color, (yend - Math.floor(yend)) * xgap, lineWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main anti-aliased stepping loop
|
||||||
|
if (steep) {
|
||||||
|
for (int x = xpxl1 + 1; x < xpxl2; x++) {
|
||||||
|
int y = (int) Math.floor(intery);
|
||||||
|
double frac = intery - y;
|
||||||
|
plotPixelWu(y, x, color, 1.0 - frac, lineWidth);
|
||||||
|
plotPixelWu(y + 1, x, color, frac, lineWidth);
|
||||||
|
intery += gradient;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int x = xpxl1 + 1; x < xpxl2; x++) {
|
||||||
|
int y = (int) Math.floor(intery);
|
||||||
|
double frac = intery - y;
|
||||||
|
plotPixelWu(x, y, color, 1.0 - frac, lineWidth);
|
||||||
|
plotPixelWu(x, y + 1, color, frac, lineWidth);
|
||||||
|
intery += gradient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasContent = true;
|
||||||
|
updateCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void plotPixelWu(int x, int y, int colorRgb, double brightness, int lineWidth) {
|
||||||
|
if (brightness <= 0.0) return;
|
||||||
|
if (lineWidth == GocaConstants.LW_THICK) {
|
||||||
|
setPixelCoverage(x, y, colorRgb, 1.0);
|
||||||
|
setPixelCoverage(x + 1, y, colorRgb, Math.min(1.0, brightness));
|
||||||
|
setPixelCoverage(x, y + 1, colorRgb, Math.min(1.0, brightness));
|
||||||
|
setPixelCoverage(x + 1, y + 1, colorRgb, Math.min(1.0, brightness * 0.7));
|
||||||
|
} else {
|
||||||
|
// Perceptual gamma correction for crisp contrast on dark backgrounds
|
||||||
|
double b = Math.min(1.0, Math.pow(brightness, 0.75) * 1.15);
|
||||||
|
setPixelCoverage(x, y, colorRgb, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws an absolute or relative line using anti-aliasing for smooth vectors.
|
||||||
*/
|
*/
|
||||||
public synchronized void drawLine(int x1, int y1, int x2, int y2, int colorArgb, int lineType, int lineWidth) {
|
public synchronized void drawLine(int x1, int y1, int x2, int y2, int colorArgb, int lineType, int lineWidth) {
|
||||||
|
drawLine((double) x1, (double) y1, (double) x2, (double) y2, colorArgb, lineType, 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 : 0xFFFFFFFF;
|
||||||
int thickness = (lineWidth == GocaConstants.LW_THICK) ? 2 : 1;
|
int thickness = (lineWidth == GocaConstants.LW_THICK) ? 2 : 1;
|
||||||
|
|
||||||
@@ -255,25 +412,25 @@ public class GraphicsPlane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a full or partial arc / ellipse.
|
* Draws a full or partial arc / ellipse with sub-pixel double precision and anti-aliasing.
|
||||||
*/
|
*/
|
||||||
public synchronized void drawArc(int cx, int cy, int rx, int ry, double startAngleDeg, double sweepAngleDeg,
|
public synchronized void drawArc(double cx, double cy, double rx, double ry, double startAngleDeg, double sweepAngleDeg,
|
||||||
int colorArgb, int lineType, int lineWidth, boolean isFull) {
|
int colorArgb, int lineType, int lineWidth, boolean isFull) {
|
||||||
if (rx <= 0) rx = 1;
|
if (rx <= 0) rx = 1.0;
|
||||||
if (ry <= 0) ry = 1;
|
if (ry <= 0) ry = 1.0;
|
||||||
|
|
||||||
int numSteps = Math.max(24, Math.max(rx, ry) * 4);
|
int numSteps = (int) Math.max(36, Math.max(rx, ry) * 6);
|
||||||
double startRad = Math.toRadians(startAngleDeg);
|
double startRad = Math.toRadians(startAngleDeg);
|
||||||
double sweepRad = isFull ? (2.0 * Math.PI) : Math.toRadians(sweepAngleDeg);
|
double sweepRad = isFull ? (2.0 * Math.PI) : Math.toRadians(sweepAngleDeg);
|
||||||
double stepRad = sweepRad / numSteps;
|
double stepRad = sweepRad / numSteps;
|
||||||
|
|
||||||
int prevX = (int) Math.round(cx + rx * Math.cos(startRad));
|
double prevX = cx + rx * Math.cos(startRad);
|
||||||
int prevY = (int) Math.round(cy - ry * Math.sin(startRad));
|
double prevY = cy - ry * Math.sin(startRad);
|
||||||
|
|
||||||
for (int i = 1; i <= numSteps; i++) {
|
for (int i = 1; i <= numSteps; i++) {
|
||||||
double angle = startRad + i * stepRad;
|
double angle = startRad + i * stepRad;
|
||||||
int nextX = (int) Math.round(cx + rx * Math.cos(angle));
|
double nextX = cx + rx * Math.cos(angle);
|
||||||
int nextY = (int) Math.round(cy - ry * Math.sin(angle));
|
double nextY = cy - ry * Math.sin(angle);
|
||||||
drawLine(prevX, prevY, nextX, nextY, colorArgb, lineType, lineWidth);
|
drawLine(prevX, prevY, nextX, nextY, colorArgb, lineType, lineWidth);
|
||||||
prevX = nextX;
|
prevX = nextX;
|
||||||
prevY = nextY;
|
prevY = nextY;
|
||||||
@@ -282,10 +439,15 @@ public class GraphicsPlane {
|
|||||||
updateCount++;
|
updateCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void drawArc(int cx, int cy, int rx, int ry, double startAngleDeg, double sweepAngleDeg,
|
||||||
|
int colorArgb, int lineType, int lineWidth, boolean isFull) {
|
||||||
|
drawArc((double) cx, (double) cy, (double) rx, (double) ry, startAngleDeg, sweepAngleDeg, colorArgb, lineType, lineWidth, isFull);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a Fillet (spline / curve approximation across control points).
|
* Draws a Fillet (spline / curve approximation across control points) with sub-pixel precision.
|
||||||
*/
|
*/
|
||||||
public synchronized void drawFillet(int[] px, int[] py, int numPoints, int colorArgb, int lineType, int lineWidth) {
|
public synchronized void drawFillet(double[] px, double[] py, int numPoints, int colorArgb, int lineType, int lineWidth) {
|
||||||
if (px == null || py == null || numPoints < 2) return;
|
if (px == null || py == null || numPoints < 2) return;
|
||||||
|
|
||||||
if (numPoints == 2) {
|
if (numPoints == 2) {
|
||||||
@@ -293,8 +455,8 @@ public class GraphicsPlane {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int prevX = px[0];
|
double prevX = px[0];
|
||||||
int prevY = py[0];
|
double prevY = py[0];
|
||||||
|
|
||||||
for (int i = 0; i < numPoints - 1; i++) {
|
for (int i = 0; i < numPoints - 1; i++) {
|
||||||
double p0x = (i == 0) ? px[0] : (px[i - 1] + px[i]) / 2.0;
|
double p0x = (i == 0) ? px[0] : (px[i - 1] + px[i]) / 2.0;
|
||||||
@@ -304,23 +466,32 @@ public class GraphicsPlane {
|
|||||||
double p2x = (i == numPoints - 2) ? px[numPoints - 1] : (px[i] + px[i + 1]) / 2.0;
|
double p2x = (i == numPoints - 2) ? px[numPoints - 1] : (px[i] + px[i + 1]) / 2.0;
|
||||||
double p2y = (i == numPoints - 2) ? py[numPoints - 1] : (py[i] + py[i + 1]) / 2.0;
|
double p2y = (i == numPoints - 2) ? py[numPoints - 1] : (py[i] + py[i + 1]) / 2.0;
|
||||||
|
|
||||||
int steps = 20;
|
int steps = 30;
|
||||||
for (int s = 1; s <= steps; s++) {
|
for (int s = 1; s <= steps; s++) {
|
||||||
double t = (double) s / steps;
|
double t = (double) s / steps;
|
||||||
double oneMinusT = 1.0 - t;
|
double oneMinusT = 1.0 - t;
|
||||||
double bx = oneMinusT * oneMinusT * p0x + 2.0 * oneMinusT * t * p1x + t * t * p2x;
|
double bx = oneMinusT * oneMinusT * p0x + 2.0 * oneMinusT * t * p1x + t * t * p2x;
|
||||||
double by = oneMinusT * oneMinusT * p0y + 2.0 * oneMinusT * t * p1y + t * t * p2y;
|
double by = oneMinusT * oneMinusT * p0y + 2.0 * oneMinusT * t * p1y + t * t * p2y;
|
||||||
int nextX = (int) Math.round(bx);
|
drawLine(prevX, prevY, bx, by, colorArgb, lineType, lineWidth);
|
||||||
int nextY = (int) Math.round(by);
|
prevX = bx;
|
||||||
drawLine(prevX, prevY, nextX, nextY, colorArgb, lineType, lineWidth);
|
prevY = by;
|
||||||
prevX = nextX;
|
|
||||||
prevY = nextY;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hasContent = true;
|
hasContent = true;
|
||||||
updateCount++;
|
updateCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void drawFillet(int[] px, int[] py, int numPoints, int colorArgb, int lineType, int lineWidth) {
|
||||||
|
if (px == null || py == null || numPoints < 2) return;
|
||||||
|
double[] dpx = new double[numPoints];
|
||||||
|
double[] dpy = new double[numPoints];
|
||||||
|
for (int i = 0; i < numPoints; i++) {
|
||||||
|
dpx[i] = px[i];
|
||||||
|
dpy[i] = py[i];
|
||||||
|
}
|
||||||
|
drawFillet(dpx, dpy, numPoints, colorArgb, lineType, lineWidth);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fills a closed polygon area with a solid color or hatching pattern.
|
* Fills a closed polygon area with a solid color or hatching pattern.
|
||||||
*/
|
*/
|
||||||
@@ -335,13 +506,24 @@ public class GraphicsPlane {
|
|||||||
public synchronized void fillArea(int[] px, int[] py, int numPoints, int fillColorArgb, int pattern,
|
public synchronized void fillArea(int[] px, int[] py, int numPoints, 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, null, 1, fillColorArgb, pattern, drawBoundary, boundaryColorArgb, lineType, lineWidth, bgMix, bgColorArgb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills an area containing one or more closed polygon subpaths with a solid color or hatching pattern,
|
||||||
|
* using the even-odd fill rule across all subpath contours.
|
||||||
|
*/
|
||||||
|
public synchronized void fillArea(int[] px, int[] py, int numPoints, int[] polyCounts, int numPolys,
|
||||||
|
int fillColorArgb, 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 : 0xFFFFFFFF;
|
||||||
int bg = bgColorArgb;
|
int bg = bgColorArgb;
|
||||||
|
|
||||||
if (pattern != GocaConstants.PT_EMPTY) {
|
if (pattern != GocaConstants.PT_EMPTY) {
|
||||||
// Find polygon vertical bounds
|
// Find polygon vertical bounds across all points
|
||||||
int minY = py[0];
|
int minY = py[0];
|
||||||
int maxY = py[0];
|
int maxY = py[0];
|
||||||
for (int i = 1; i < numPoints; i++) {
|
for (int i = 1; i < numPoints; i++) {
|
||||||
@@ -356,14 +538,26 @@ public class GraphicsPlane {
|
|||||||
|
|
||||||
for (int y = minY; y <= maxY; y++) {
|
for (int y = minY; y <= maxY; y++) {
|
||||||
nodeX.clear();
|
nodeX.clear();
|
||||||
int j = numPoints - 1;
|
int offset = 0;
|
||||||
for (int i = 0; i < numPoints; i++) {
|
int polyCount = (polyCounts != null && numPolys > 0) ? numPolys : 1;
|
||||||
if ((py[i] < y && py[j] >= y) || (py[j] < y && py[i] >= y)) {
|
for (int p = 0; p < polyCount; p++) {
|
||||||
int x = px[i] + (int) Math.round((double) (y - py[i]) / (py[j] - py[i]) * (px[j] - px[i]));
|
int pLen = (polyCounts != null && p < polyCounts.length) ? polyCounts[p] : numPoints;
|
||||||
|
if (pLen >= 3) {
|
||||||
|
int j = pLen - 1;
|
||||||
|
for (int i = 0; i < pLen; i++) {
|
||||||
|
int yi = py[offset + i];
|
||||||
|
int yj = py[offset + j];
|
||||||
|
int xi = px[offset + i];
|
||||||
|
int xj = px[offset + j];
|
||||||
|
if ((yi < y && yj >= y) || (yj < y && yi >= y)) {
|
||||||
|
int x = xi + (int) Math.round((double) (y - yi) / (yj - yi) * (xj - xi));
|
||||||
nodeX.add(x);
|
nodeX.add(x);
|
||||||
}
|
}
|
||||||
j = i;
|
j = i;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
offset += pLen;
|
||||||
|
}
|
||||||
|
|
||||||
Collections.sort(nodeX);
|
Collections.sort(nodeX);
|
||||||
|
|
||||||
@@ -373,7 +567,7 @@ 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 >= 16) {
|
if (pattern == GocaConstants.PT_SOLID || pattern == 0 || pattern >= 16) {
|
||||||
setPixel(x, y, fill);
|
setPixel(x, y, fill);
|
||||||
} else {
|
} else {
|
||||||
int b = patRows[y & 7] & 0xFF;
|
int b = patRows[y & 7] & 0xFF;
|
||||||
@@ -389,9 +583,23 @@ public class GraphicsPlane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (drawBoundary && boundaryColorArgb != 0) {
|
if (drawBoundary && boundaryColorArgb != 0) {
|
||||||
for (int i = 0; i < numPoints; i++) {
|
int offset = 0;
|
||||||
int next = (i + 1) % numPoints;
|
int polyCount = (polyCounts != null && numPolys > 0) ? numPolys : 1;
|
||||||
drawLine(px[i], py[i], px[next], py[next], boundaryColorArgb, lineType, lineWidth);
|
for (int p = 0; p < polyCount; p++) {
|
||||||
|
int pLen = (polyCounts != null && p < polyCounts.length) ? polyCounts[p] : numPoints;
|
||||||
|
if (pLen >= 2) {
|
||||||
|
for (int i = 0; i < pLen - 1; i++) {
|
||||||
|
drawLine((double) px[offset + i], (double) py[offset + i],
|
||||||
|
(double) px[offset + i + 1], (double) py[offset + i + 1],
|
||||||
|
boundaryColorArgb, lineType, lineWidth);
|
||||||
|
}
|
||||||
|
if (pLen >= 3 && (px[offset] != px[offset + pLen - 1] || py[offset] != py[offset + pLen - 1])) {
|
||||||
|
drawLine((double) px[offset + pLen - 1], (double) py[offset + pLen - 1],
|
||||||
|
(double) px[offset], (double) py[offset],
|
||||||
|
boundaryColorArgb, lineType, lineWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset += pLen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hasContent = true;
|
hasContent = true;
|
||||||
@@ -399,11 +607,11 @@ public class GraphicsPlane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a GOCA marker symbol (+, x, diamond, square, star, dot, circle).
|
* Draws a GOCA marker symbol (+, x, diamond, square, star, dot, circle) with sub-pixel precision.
|
||||||
*/
|
*/
|
||||||
public synchronized void drawMarker(int x, int 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 : 0xFFFFFFFF;
|
||||||
int s = Math.max(3, size > 0 ? size : 5);
|
double s = Math.max(3.0, size > 0 ? (double) size : 5.0);
|
||||||
|
|
||||||
switch (markerType) {
|
switch (markerType) {
|
||||||
case GocaConstants.MK_CROSS: // x
|
case GocaConstants.MK_CROSS: // x
|
||||||
@@ -429,8 +637,8 @@ public class GraphicsPlane {
|
|||||||
break;
|
break;
|
||||||
case GocaConstants.MK_6STAR: // 6-point star
|
case GocaConstants.MK_6STAR: // 6-point star
|
||||||
drawLine(x - s, y, x + s, y, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
drawLine(x - s, y, x + s, y, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
drawLine(x - s / 2, y - s, x + s / 2, y + s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
drawLine(x - s / 2.0, y - s, x + s / 2.0, y + s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
drawLine(x - s / 2, y + s, x + s / 2, y - s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
drawLine(x - s / 2.0, y + s, x + s / 2.0, y - s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
break;
|
break;
|
||||||
case GocaConstants.MK_8STAR: // 8-point star
|
case GocaConstants.MK_8STAR: // 8-point star
|
||||||
drawLine(x - s, y, x + s, y, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
drawLine(x - s, y, x + s, y, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
@@ -439,21 +647,17 @@ public class GraphicsPlane {
|
|||||||
drawLine(x - s, y + s, x + s, y - s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
drawLine(x - s, y + s, x + s, y - s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
break;
|
break;
|
||||||
case GocaConstants.MK_SDIAMOND: // solid diamond
|
case GocaConstants.MK_SDIAMOND: // solid diamond
|
||||||
fillArea(new int[]{x, x + s, x, x - s}, new int[]{y - s, y, y + s, y}, 4,
|
fillArea(new int[]{(int) Math.round(x), (int) Math.round(x + s), (int) Math.round(x), (int) Math.round(x - s)},
|
||||||
color, GocaConstants.PT_SOLID, false, 0, 0, 0);
|
new int[]{(int) Math.round(y - s), (int) Math.round(y), (int) Math.round(y + s), (int) Math.round(y)},
|
||||||
|
4, color, GocaConstants.PT_SOLID, false, 0, 0, 0);
|
||||||
break;
|
break;
|
||||||
case GocaConstants.MK_SSQUARE: // solid square
|
case GocaConstants.MK_SSQUARE: // solid square
|
||||||
fillArea(new int[]{x - s, x + s, x + s, x - s}, new int[]{y - s, y - s, y + s, y + s}, 4,
|
fillArea(new int[]{(int) Math.round(x - s), (int) Math.round(x + s), (int) Math.round(x + s), (int) Math.round(x - s)},
|
||||||
color, GocaConstants.PT_SOLID, false, 0, 0, 0);
|
new int[]{(int) Math.round(y - s), (int) Math.round(y - s), (int) Math.round(y + s), (int) Math.round(y + s)},
|
||||||
|
4, color, GocaConstants.PT_SOLID, false, 0, 0, 0);
|
||||||
break;
|
break;
|
||||||
case GocaConstants.MK_DOT: // dot
|
case GocaConstants.MK_DOT: // dot
|
||||||
for (int dy = -2; dy <= 2; dy++) {
|
drawArc(x, y, 2.0, 2.0, 0.0, 360.0, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL, true);
|
||||||
for (int dx = -2; dx <= 2; dx++) {
|
|
||||||
if (dx * dx + dy * dy <= 4) {
|
|
||||||
setPixel(x + dx, y + dy, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case GocaConstants.MK_CIRCLE: // circle
|
case GocaConstants.MK_CIRCLE: // circle
|
||||||
default:
|
default:
|
||||||
@@ -464,6 +668,10 @@ public class GraphicsPlane {
|
|||||||
updateCount++;
|
updateCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void drawMarker(int x, int y, int markerType, int size, int colorArgb) {
|
||||||
|
drawMarker((double) x, (double) y, markerType, size, colorArgb);
|
||||||
|
}
|
||||||
|
|
||||||
private static final int[] VSS_OFFSETS = new int[256];
|
private static final int[] VSS_OFFSETS = new int[256];
|
||||||
static {
|
static {
|
||||||
Arrays.fill(VSS_OFFSETS, -1);
|
Arrays.fill(VSS_OFFSETS, -1);
|
||||||
@@ -481,18 +689,54 @@ public class GraphicsPlane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface TextRenderer {
|
||||||
|
void drawText(GraphicsPlane plane, double x, double y, String text, int colorArgb,
|
||||||
|
double cellWidth, double cellHeight, int dir, double angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextRenderer textRenderer;
|
||||||
|
|
||||||
|
public void setTextRenderer(TextRenderer renderer) {
|
||||||
|
this.textRenderer = renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextRenderer getTextRenderer() {
|
||||||
|
return this.textRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws stroked vector text using IBM Vector Symbol Set (VSS).
|
* Draws character text (using pluggable TextRenderer or fallback vector font).
|
||||||
*/
|
*/
|
||||||
public synchronized void drawVectorText(int x, int y, String text, int colorArgb,
|
public synchronized void drawText(double x, double y, String text, int colorArgb,
|
||||||
|
double cellWidth, double cellHeight, int dir, double angle) {
|
||||||
|
if (text == null || text.isEmpty()) return;
|
||||||
|
if (textRenderer != null) {
|
||||||
|
textRenderer.drawText(this, x, y, text, colorArgb, cellWidth, cellHeight, dir, angle);
|
||||||
|
hasContent = true;
|
||||||
|
updateCount++;
|
||||||
|
} else {
|
||||||
|
drawVectorText(x, y, text, colorArgb, cellWidth, cellHeight, dir, angle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void drawText(int x, int y, String text, int colorArgb,
|
||||||
int cellWidth, int cellHeight, int dir, double angle) {
|
int cellWidth, int cellHeight, int dir, double angle) {
|
||||||
|
drawText((double) x, (double) y, text, colorArgb, (double) cellWidth, (double) cellHeight, dir, angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws stroked vector text using IBM Vector Symbol Set (VSS) with sub-pixel anti-aliasing.
|
||||||
|
*/
|
||||||
|
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;
|
if (text == null || text.isEmpty()) return;
|
||||||
int color = (colorArgb != 0) ? colorArgb : 0xFFFFFFFF;
|
int color = (colorArgb != 0) ? colorArgb : 0xFFFFFFFF;
|
||||||
|
|
||||||
int curX = x;
|
double curX = x;
|
||||||
int curY = y;
|
double curY = y;
|
||||||
int cw = cellWidth > 0 ? cellWidth : 12;
|
double cw = cellWidth > 0 ? cellWidth : 12.0;
|
||||||
int ch = cellHeight > 0 ? cellHeight : 20;
|
double ch = cellHeight > 0 ? cellHeight : 20.0;
|
||||||
|
|
||||||
for (int i = 0; i < text.length(); i++) {
|
for (int i = 0; i < text.length(); i++) {
|
||||||
char c = text.charAt(i);
|
char c = text.charAt(i);
|
||||||
@@ -513,7 +757,12 @@ public class GraphicsPlane {
|
|||||||
updateCount++;
|
updateCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawVssChar(int x, int y, char c, int color, int cw, int ch) {
|
public synchronized void drawVectorText(int x, int y, String text, int colorArgb,
|
||||||
|
int cellWidth, int cellHeight, int dir, double angle) {
|
||||||
|
drawVectorText((double) x, (double) y, text, colorArgb, (double) cellWidth, (double) cellHeight, dir, angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawVssChar(double x, double y, char c, int color, double cw, double ch) {
|
||||||
int code = (int) c;
|
int code = (int) c;
|
||||||
if (code < VectorSymbolData.VSS_SYMBOL_START || code >= 256) {
|
if (code < VectorSymbolData.VSS_SYMBOL_START || code >= 256) {
|
||||||
return;
|
return;
|
||||||
@@ -532,20 +781,33 @@ public class GraphicsPlane {
|
|||||||
int dataPtr = ptr + 2;
|
int dataPtr = ptr + 2;
|
||||||
|
|
||||||
if (numPoints >= 2) {
|
if (numPoints >= 2) {
|
||||||
int prevVx = ((VectorSymbolData.vss_data[dataPtr] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + 1] & 0xFF);
|
double[] px = new double[numPoints];
|
||||||
int prevVy = ((VectorSymbolData.vss_data[dataPtr + 2] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + 3] & 0xFF);
|
double[] py = new double[numPoints];
|
||||||
int prevPx = x + (int) Math.round(((double) prevVx / VectorSymbolData.VSS_WIDTH) * cw);
|
int[] ipx = new int[numPoints];
|
||||||
int prevPy = y + (int) Math.round(((double)(VectorSymbolData.VSS_HEIGHT - prevVy) / VectorSymbolData.VSS_HEIGHT) * ch);
|
int[] ipy = new int[numPoints];
|
||||||
|
|
||||||
for (int p = 1; p < numPoints; p++) {
|
for (int p = 0; p < numPoints; p++) {
|
||||||
int vx = ((VectorSymbolData.vss_data[dataPtr + p * 4] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + p * 4 + 1] & 0xFF);
|
int vx = ((VectorSymbolData.vss_data[dataPtr + p * 4] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + p * 4 + 1] & 0xFF);
|
||||||
int vy = ((VectorSymbolData.vss_data[dataPtr + p * 4 + 2] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + p * 4 + 3] & 0xFF);
|
int vy = ((VectorSymbolData.vss_data[dataPtr + p * 4 + 2] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + p * 4 + 3] & 0xFF);
|
||||||
int px = x + (int) Math.round(((double) vx / VectorSymbolData.VSS_WIDTH) * cw);
|
px[p] = x + ((double) vx / VectorSymbolData.VSS_WIDTH) * cw;
|
||||||
int py = y + (int) Math.round(((double)(VectorSymbolData.VSS_HEIGHT - vy) / VectorSymbolData.VSS_HEIGHT) * ch);
|
py[p] = y + ((double) (VectorSymbolData.VSS_HEIGHT - vy) / VectorSymbolData.VSS_HEIGHT) * ch;
|
||||||
|
ipx[p] = (int) Math.round(px[p]);
|
||||||
|
ipy[p] = (int) Math.round(py[p]);
|
||||||
|
}
|
||||||
|
|
||||||
drawLine(prevPx, prevPy, px, py, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
// If contour is closed (e.g. bold character loop), fill with solid color
|
||||||
prevPx = px;
|
int firstVx = ((VectorSymbolData.vss_data[dataPtr] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + 1] & 0xFF);
|
||||||
prevPy = py;
|
int firstVy = ((VectorSymbolData.vss_data[dataPtr + 2] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + 3] & 0xFF);
|
||||||
|
int lastVx = ((VectorSymbolData.vss_data[dataPtr + (numPoints - 1) * 4] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + (numPoints - 1) * 4 + 1] & 0xFF);
|
||||||
|
int lastVy = ((VectorSymbolData.vss_data[dataPtr + (numPoints - 1) * 4 + 2] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + (numPoints - 1) * 4 + 3] & 0xFF);
|
||||||
|
|
||||||
|
boolean isClosed = (numPoints >= 4) && (firstVx == lastVx) && (firstVy == lastVy);
|
||||||
|
if (isClosed) {
|
||||||
|
fillArea(ipx, ipy, numPoints, color, GocaConstants.PT_SOLID, false, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int p = 0; p < numPoints - 1; p++) {
|
||||||
|
drawLine(px[p], py[p], px[p + 1], py[p + 1], color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ptr += 2 + byteLen;
|
ptr += 2 + byteLen;
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public class DataStreamProcessorTest {
|
|||||||
input.setLastAid(AID_ENTER);
|
input.setLastAid(AID_ENTER);
|
||||||
|
|
||||||
java.util.concurrent.atomic.AtomicReference<byte[]> sentData = new java.util.concurrent.atomic.AtomicReference<>();
|
java.util.concurrent.atomic.AtomicReference<byte[]> sentData = new java.util.concurrent.atomic.AtomicReference<>();
|
||||||
processor.setOutputCallback(sentData::set);
|
processor.setOutputSender(sentData::set);
|
||||||
|
|
||||||
byte[] rbRecord = new byte[] { (byte) CMD_RB };
|
byte[] rbRecord = new byte[] { (byte) CMD_RB };
|
||||||
processor.processRecord(rbRecord, 0, rbRecord.length, true);
|
processor.processRecord(rbRecord, 0, rbRecord.length, true);
|
||||||
|
|||||||
@@ -335,4 +335,224 @@ public class GocaDecoderTest {
|
|||||||
assertEquals(0x40, decoder.getCharSet());
|
assertEquals(0x40, decoder.getCharSet());
|
||||||
assertTrue(plane.hasContent(), "Expected plane to have content after GCALL segment execution");
|
assertTrue(plane.hasContent(), "Expected plane to have content after GCALL segment execution");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAntialiasedLineRendering() {
|
||||||
|
GraphicsPlane plane = new GraphicsPlane(100, 100);
|
||||||
|
plane.clear();
|
||||||
|
int red = 0xFFFF0000;
|
||||||
|
|
||||||
|
// Draw a diagonal line with Xiaolin Wu anti-aliasing
|
||||||
|
plane.drawLine(10.0, 10.0, 50.0, 30.0, red, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
|
assertTrue(plane.hasContent());
|
||||||
|
|
||||||
|
int[] buffer = plane.getRgbBuffer();
|
||||||
|
boolean hasIntermediateAlpha = false;
|
||||||
|
int nonZeroPixels = 0;
|
||||||
|
|
||||||
|
for (int y = 0; y < 100; y++) {
|
||||||
|
for (int x = 0; x < 100; x++) {
|
||||||
|
int pixel = buffer[y * 100 + x];
|
||||||
|
if (pixel != 0) {
|
||||||
|
nonZeroPixels++;
|
||||||
|
int alpha = (pixel >>> 24) & 0xFF;
|
||||||
|
int r = (pixel >>> 16) & 0xFF;
|
||||||
|
assertEquals(255, r, "Red channel must be preserved");
|
||||||
|
if (alpha > 0 && alpha < 255) {
|
||||||
|
hasIntermediateAlpha = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(nonZeroPixels > 30, "Expected non-zero pixels along the line");
|
||||||
|
assertTrue(hasIntermediateAlpha, "Expected Xiaolin Wu anti-aliasing to produce fractional alpha coverage");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSubpixelArcAndAlphaBlending() {
|
||||||
|
GraphicsPlane plane = new GraphicsPlane(100, 100);
|
||||||
|
plane.clear();
|
||||||
|
int green = 0xFF00FF00;
|
||||||
|
|
||||||
|
plane.drawArc(50.0, 50.0, 30.0, 30.0, 0.0, 360.0, green, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL, true);
|
||||||
|
assertTrue(plane.hasContent());
|
||||||
|
|
||||||
|
int[] buffer = plane.getRgbBuffer();
|
||||||
|
boolean hasIntermediateAlpha = false;
|
||||||
|
int nonZeroPixels = 0;
|
||||||
|
|
||||||
|
for (int p : buffer) {
|
||||||
|
if (p != 0) {
|
||||||
|
nonZeroPixels++;
|
||||||
|
int alpha = (p >>> 24) & 0xFF;
|
||||||
|
if (alpha > 0 && alpha < 255) {
|
||||||
|
hasIntermediateAlpha = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(nonZeroPixels > 50, "Expected arc pixels");
|
||||||
|
assertTrue(hasIntermediateAlpha, "Expected anti-aliased arc edges with smooth alpha");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVectorTextAntialiasedDrawing() {
|
||||||
|
GraphicsPlane plane = new GraphicsPlane(200, 100);
|
||||||
|
plane.clear();
|
||||||
|
int yellow = 0xFFFFFF00;
|
||||||
|
|
||||||
|
plane.drawVectorText(10.0, 10.0, "8% 1985 TAX", yellow, 12.0, 20.0, GocaConstants.CD_LR, 0.0);
|
||||||
|
assertTrue(plane.hasContent());
|
||||||
|
|
||||||
|
int[] buffer = plane.getRgbBuffer();
|
||||||
|
boolean hasIntermediateAlpha = false;
|
||||||
|
int nonZeroPixels = 0;
|
||||||
|
|
||||||
|
for (int p : buffer) {
|
||||||
|
if (p != 0) {
|
||||||
|
nonZeroPixels++;
|
||||||
|
int alpha = (p >>> 24) & 0xFF;
|
||||||
|
if (alpha > 0 && alpha < 255) {
|
||||||
|
hasIntermediateAlpha = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(nonZeroPixels > 40, "Expected stroked vector text pixels");
|
||||||
|
assertTrue(hasIntermediateAlpha, "Expected anti-aliased vector text strokes with fractional alpha");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTextRendererPrecisionSwitching() {
|
||||||
|
GraphicsPlane plane = new GraphicsPlane(200, 100);
|
||||||
|
GocaDecoder decoder = new GocaDecoder(plane);
|
||||||
|
|
||||||
|
final boolean[] textRendererCalled = new boolean[1];
|
||||||
|
plane.setTextRenderer((p, x, y, text, color, cw, ch, dir, angle) -> {
|
||||||
|
textRendererCalled[0] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test String Precision (Default): GCHST (0xC3) with "TAX"
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
out.write(GocaConstants.G_GCHST);
|
||||||
|
out.write(0x07); // length = 7 (4 bytes pos + 3 bytes text)
|
||||||
|
out.write(0x00); out.write(0x00); // x = 0
|
||||||
|
out.write(0x00); out.write(0x00); // y = 0
|
||||||
|
out.write(0xE3); // 'T' in EBCDIC
|
||||||
|
out.write(0xC1); // 'A' in EBCDIC
|
||||||
|
out.write(0xE7); // 'X' in EBCDIC
|
||||||
|
|
||||||
|
byte[] stream = out.toByteArray();
|
||||||
|
decoder.decodeStream(stream, 0, stream.length);
|
||||||
|
|
||||||
|
assertTrue(textRendererCalled[0], "Expected pluggable TextRenderer to be called for String precision");
|
||||||
|
|
||||||
|
// Now set Stroke precision (G_GSCC with 3):
|
||||||
|
textRendererCalled[0] = false;
|
||||||
|
ByteArrayOutputStream outStroke = new ByteArrayOutputStream();
|
||||||
|
outStroke.write(GocaConstants.G_GSCC);
|
||||||
|
outStroke.write(0x01);
|
||||||
|
outStroke.write(GocaConstants.CP_STROKE); // 3
|
||||||
|
outStroke.write(GocaConstants.G_GCHST);
|
||||||
|
outStroke.write(0x07);
|
||||||
|
outStroke.write(0x00); outStroke.write(0x00);
|
||||||
|
outStroke.write(0x00); outStroke.write(0x00);
|
||||||
|
outStroke.write(0xE3); outStroke.write(0xC1); outStroke.write(0xE7);
|
||||||
|
|
||||||
|
byte[] strokeStream = outStroke.toByteArray();
|
||||||
|
decoder.decodeStream(strokeStream, 0, strokeStream.length);
|
||||||
|
|
||||||
|
assertFalse(textRendererCalled[0], "Expected drawVectorText (not textRenderer) when precision is CP_STROKE");
|
||||||
|
assertTrue(plane.hasContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThickLineRendering() {
|
||||||
|
GraphicsPlane plane = new GraphicsPlane(100, 100);
|
||||||
|
plane.clear();
|
||||||
|
|
||||||
|
plane.drawLine(10.0, 10.0, 50.0, 50.0, 0xFF00FF00, GocaConstants.LT_SOLID, GocaConstants.LW_THICK);
|
||||||
|
assertTrue(plane.hasContent());
|
||||||
|
|
||||||
|
int[] buffer = plane.getRgbBuffer();
|
||||||
|
int nonZero = 0;
|
||||||
|
for (int p : buffer) {
|
||||||
|
if (p != 0) nonZero++;
|
||||||
|
}
|
||||||
|
assertTrue(nonZero > 60, "Expected thick line to occupy more pixels than standard 1px line");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShortFormAttributeOrderParsing() {
|
||||||
|
GraphicsPlane plane = new GraphicsPlane(200, 200);
|
||||||
|
GocaDecoder decoder = new GocaDecoder(plane);
|
||||||
|
|
||||||
|
// Sequence: GBAR (0x68) short form with 0x80 flag (boundary=true, fill=false),
|
||||||
|
// followed immediately by GLINE (0xC1 len=12 for 3 points) and GEAR (0x60)
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
out.write(GocaConstants.G_GBAR);
|
||||||
|
out.write(0x80); // Short 2-byte form with 0x80 flag! Must NOT be treated as length=128!
|
||||||
|
out.write(GocaConstants.G_GLINE);
|
||||||
|
out.write(0x0C); // 3 points = 12 bytes
|
||||||
|
out.write(0x00); out.write(0x00);
|
||||||
|
out.write(0x00); out.write(0x00);
|
||||||
|
out.write(0x00); out.write(0x32);
|
||||||
|
out.write(0x00); out.write(0x00);
|
||||||
|
out.write(0x00); out.write(0x32);
|
||||||
|
out.write(0x00); out.write(0x32);
|
||||||
|
out.write(GocaConstants.G_GEAR);
|
||||||
|
out.write(0x00);
|
||||||
|
|
||||||
|
byte[] stream = out.toByteArray();
|
||||||
|
decoder.decodeStream(stream, 0, stream.length);
|
||||||
|
|
||||||
|
assertTrue(plane.hasContent(), "Expected GLINE and GEAR inside GBAR short-form 0x80 to be decoded properly");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultiPolygonAreaFilling() {
|
||||||
|
GraphicsPlane plane = new GraphicsPlane(200, 200);
|
||||||
|
GocaDecoder decoder = new GocaDecoder(plane);
|
||||||
|
|
||||||
|
// Sequence: GBAR (0x68) short form 0x80 (bounded, always filled in GOCA)
|
||||||
|
// Polygon 1 (e.g. Letter 'T' bar): (10,10) to (30,10) to (30,20) to (10,20) to (10,10)
|
||||||
|
// 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_GBAR);
|
||||||
|
out.write(0x80);
|
||||||
|
|
||||||
|
// Polygon 1
|
||||||
|
out.write(GocaConstants.G_GLINE);
|
||||||
|
out.write(0x14); // 5 points = 20 bytes
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(10);
|
||||||
|
out.write(0x00); out.write(30); out.write(0x00); out.write(10);
|
||||||
|
out.write(0x00); out.write(30); out.write(0x00); out.write(20);
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(20);
|
||||||
|
out.write(0x00); out.write(10); out.write(0x00); out.write(10);
|
||||||
|
|
||||||
|
// Polygon 2 (disconnected start -> triggers new subpath)
|
||||||
|
out.write(GocaConstants.G_GLINE);
|
||||||
|
out.write(0x14); // 5 points = 20 bytes
|
||||||
|
out.write(0x00); out.write(18); out.write(0x00); out.write(20);
|
||||||
|
out.write(0x00); out.write(22); out.write(0x00); out.write(20);
|
||||||
|
out.write(0x00); out.write(22); out.write(0x00); out.write(40);
|
||||||
|
out.write(0x00); out.write(18); out.write(0x00); out.write(40);
|
||||||
|
out.write(0x00); out.write(18); out.write(0x00); out.write(20);
|
||||||
|
|
||||||
|
out.write(GocaConstants.G_GEAR);
|
||||||
|
out.write(0x00);
|
||||||
|
|
||||||
|
byte[] stream = out.toByteArray();
|
||||||
|
decoder.decodeStream(stream, 0, stream.length);
|
||||||
|
|
||||||
|
assertTrue(plane.hasContent());
|
||||||
|
int[] buffer = plane.getRgbBuffer();
|
||||||
|
int nonZero = 0;
|
||||||
|
for (int p : buffer) {
|
||||||
|
if (p != 0) nonZero++;
|
||||||
|
}
|
||||||
|
assertTrue(nonZero > 50, "Expected both filled subpath polygons to render filled pixels");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.lib3270j.input;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.lib3270j.TerminalModel;
|
import org.lib3270j.TerminalModel;
|
||||||
import org.lib3270j.charset.EbcdicTranslator;
|
import org.lib3270j.charset.EbcdicTranslator;
|
||||||
|
import org.lib3270j.datastream.DataStreamProcessor;
|
||||||
import org.lib3270j.screen.ScreenBuffer;
|
import org.lib3270j.screen.ScreenBuffer;
|
||||||
import org.lib3270j.telnet.TelnetFSM;
|
import org.lib3270j.telnet.TelnetFSM;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
@@ -184,7 +185,7 @@ public class InputProcessorTest {
|
|||||||
// Let's verify DataStreamProcessor ReadModified behavior with the same buffer
|
// Let's verify DataStreamProcessor ReadModified behavior with the same buffer
|
||||||
DataStreamProcessor dsp = new DataStreamProcessor(screen, translator);
|
DataStreamProcessor dsp = new DataStreamProcessor(screen, translator);
|
||||||
dsp.setInputProcessor(input);
|
dsp.setInputProcessor(input);
|
||||||
dsp.setOutputCallback(sent::set);
|
dsp.setOutputSender(sent::set);
|
||||||
|
|
||||||
byte[] rmRecord = new byte[] { (byte) CMD_RM };
|
byte[] rmRecord = new byte[] { (byte) CMD_RM };
|
||||||
dsp.processRecord(rmRecord, 0, rmRecord.length, true);
|
dsp.processRecord(rmRecord, 0, rmRecord.length, true);
|
||||||
|
|||||||
Reference in New Issue
Block a user