This commit is contained in:
@@ -1288,6 +1288,9 @@ public class DataStreamProcessor {
|
||||
byte[] fullStream = gocaAccumulator.toByteArray();
|
||||
gocaAccumulator.reset();
|
||||
log.info(String.format("GOCA stream SPAN_LAST assembled: %d bytes", fullStream.length));
|
||||
try {
|
||||
java.nio.file.Files.write(java.nio.file.Paths.get("/Users/rudi/Projects/j3270/captured_goca.bin"), fullStream);
|
||||
} catch (Exception ignored) {}
|
||||
if (currentGocaSubtype == haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJCNTL_SUB) {
|
||||
gocaDecoder.processProcedureOrders(fullStream, 0, fullStream.length);
|
||||
} else {
|
||||
|
||||
@@ -255,6 +255,29 @@ public class FillArea {
|
||||
}
|
||||
}
|
||||
|
||||
private void plotFillPixel(GraphicsPlane plane, int x, int y, int colorArgb, boolean isMixOr) {
|
||||
if (isMixOr) {
|
||||
int dst = plane.getPixel(x, y);
|
||||
int dstR = (dst >>> 16) & 0xFF;
|
||||
int dstG = (dst >>> 8) & 0xFF;
|
||||
int dstB = dst & 0xFF;
|
||||
|
||||
int srcR = (colorArgb >>> 16) & 0xFF;
|
||||
int srcG = (colorArgb >>> 8) & 0xFF;
|
||||
int srcB = colorArgb & 0xFF;
|
||||
|
||||
int outR = Math.min(255, dstR | srcR);
|
||||
int outG = Math.min(255, dstG | srcG);
|
||||
int outB = Math.min(255, dstB | srcB);
|
||||
int outA = Math.max((dst >>> 24) & 0xFF, (colorArgb >>> 24) & 0xFF);
|
||||
if (outA == 0 && (outR != 0 || outG != 0 || outB != 0)) outA = 255;
|
||||
|
||||
plane.setPixelDirect(x, y, (outA << 24) | (outR << 16) | (outG << 8) | outB);
|
||||
} else {
|
||||
plane.setPixel(x, y, colorArgb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rasterizes and fills the accumulated area polygons on the target GraphicsPlane with explicit fill rule.
|
||||
*/
|
||||
@@ -270,16 +293,11 @@ public class FillArea {
|
||||
|
||||
int fill = (fillColorArgb != 0) ? fillColorArgb : GocaConstants.GOCA_COLORS[0];
|
||||
int bg = bgColorArgb;
|
||||
|
||||
// Background mix / transparency rule for Black fills:
|
||||
// BMX_TRANSPARENT / 0 or 2 / MIX_DEFAULT: Transparent black
|
||||
// BMX_OPAQUE / 1: Opaque background overpaint
|
||||
boolean isTransparentBlack = ((fill & 0x00FFFFFF) == 0) &&
|
||||
(bgMix == GocaConstants.BMX_DEFAULT || bgMix == GocaConstants.BMX_TRANSPARENT ||
|
||||
bgMix == GocaConstants.MIX_DEFAULT || bgMix == GocaConstants.MIX_LEAVE || bgMix == 0 || bgMix == 2);
|
||||
boolean isOpaqueBg = (bgMix == GocaConstants.BMX_OPAQUE || bgMix == 1);
|
||||
|
||||
if (!isTransparentBlack && pattern != GocaConstants.PT_EMPTY && (pattern != 0 || !drawBoundary)) {
|
||||
boolean isMixOr = fillModeOR || (plane.getMixMode() == GocaConstants.MIX_OR && (getBounds().width >= 5 && getBounds().height >= 5 && getBounds().width * getBounds().height >= 100));
|
||||
|
||||
if (pattern != GocaConstants.PT_EMPTY) {
|
||||
double minY = Double.MAX_VALUE;
|
||||
double maxY = Double.MIN_VALUE;
|
||||
|
||||
@@ -340,18 +358,18 @@ public class FillArea {
|
||||
int pIdx = psY * psW + psX;
|
||||
boolean bit = (psPix != null && pIdx < psPix.length && psPix[pIdx] != 0);
|
||||
if (bit) {
|
||||
plane.setPixel(x, y, fill);
|
||||
plotFillPixel(plane, x, y, fill, isMixOr);
|
||||
} else if (isOpaqueBg) {
|
||||
plane.setPixel(x, y, bg);
|
||||
plotFillPixel(plane, x, y, bg, isMixOr);
|
||||
}
|
||||
} else if (pattern == GocaConstants.PT_SOLID || pattern == 16) {
|
||||
plane.setPixel(x, y, fill);
|
||||
} else if (pattern == GocaConstants.PT_SOLID || pattern == 16 || pattern == 0) {
|
||||
plotFillPixel(plane, x, y, fill, isMixOr);
|
||||
} else if (patRows != null) {
|
||||
int b = patRows[y & 7] & 0xFF;
|
||||
if (((b >> (7 - (x & 7))) & 1) != 0) {
|
||||
plane.setPixel(x, y, fill);
|
||||
plotFillPixel(plane, x, y, fill, isMixOr);
|
||||
} else if (isOpaqueBg) {
|
||||
plane.setPixel(x, y, bg);
|
||||
plotFillPixel(plane, x, y, bg, isMixOr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,18 +392,18 @@ public class FillArea {
|
||||
int pIdx = psY * psW + psX;
|
||||
boolean bit = (psPix != null && pIdx < psPix.length && psPix[pIdx] != 0);
|
||||
if (bit) {
|
||||
plane.setPixel(x, y, fill);
|
||||
plotFillPixel(plane, x, y, fill, isMixOr);
|
||||
} else if (isOpaqueBg) {
|
||||
plane.setPixel(x, y, bg);
|
||||
plotFillPixel(plane, x, y, bg, isMixOr);
|
||||
}
|
||||
} else if (pattern == GocaConstants.PT_SOLID || pattern == 16) {
|
||||
plane.setPixel(x, y, fill);
|
||||
} else if (pattern == GocaConstants.PT_SOLID || pattern == 16 || pattern == 0) {
|
||||
plotFillPixel(plane, x, y, fill, isMixOr);
|
||||
} else if (patRows != null) {
|
||||
int b = patRows[y & 7] & 0xFF;
|
||||
if (((b >> (7 - (x & 7))) & 1) != 0) {
|
||||
plane.setPixel(x, y, fill);
|
||||
plotFillPixel(plane, x, y, fill, isMixOr);
|
||||
} else if (isOpaqueBg) {
|
||||
plane.setPixel(x, y, bg);
|
||||
plotFillPixel(plane, x, y, bg, isMixOr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ public class GocaDecoder {
|
||||
private int curX = 0;
|
||||
private int curY = 0;
|
||||
private int curColor = GocaConstants.GOCA_COLORS[0];
|
||||
private int fgMix = GocaConstants.MIX_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;
|
||||
@@ -35,8 +36,8 @@ public class GocaDecoder {
|
||||
private double charAngle = 0.0;
|
||||
private double charShear = 0.0;
|
||||
private double fractionalLineWidth = 1.0;
|
||||
private int charWidth = 9;
|
||||
private int charHeight = 16;
|
||||
private double charWidth = 9.0;
|
||||
private double charHeight = 16.0;
|
||||
private int charSet = 0;
|
||||
private int charPrecision = GocaConstants.CP_STRING;
|
||||
private int arcParamP = 1;
|
||||
@@ -48,6 +49,14 @@ public class GocaDecoder {
|
||||
private boolean segDynamic = false;
|
||||
private boolean segVisible = true;
|
||||
|
||||
// Default attributes configured by P_SCUDEF (0x21) and restored on G_BEGSEGM (0x70)
|
||||
private int defColorIndex = 0;
|
||||
private int defFmix = GocaConstants.MIX_DEFAULT;
|
||||
private int defLineType = GocaConstants.LT_SOLID;
|
||||
private int defLineWidth = GocaConstants.LW_NORMAL;
|
||||
private int defPattern = GocaConstants.PT_SOLID;
|
||||
private int defPatternSet = 0;
|
||||
|
||||
private ProgramSymbolManager programSymbolManager;
|
||||
|
||||
// Area accumulation
|
||||
@@ -201,6 +210,10 @@ public class GocaDecoder {
|
||||
return sb != null ? sb.tag : 0;
|
||||
}
|
||||
|
||||
public synchronized int getCurrentSegId() {
|
||||
return currentSegId;
|
||||
}
|
||||
|
||||
private void trackPoint(int x, int y) {
|
||||
if (currentSegId != 0) {
|
||||
SegmentBounds sb = segmentBoundsMap.get(currentSegId);
|
||||
@@ -277,6 +290,10 @@ public class GocaDecoder {
|
||||
flags, segChained, segDynamic, segVisible));
|
||||
}
|
||||
|
||||
public synchronized int getFgMix() {
|
||||
return fgMix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes GOCA order 0x11 Fractional Line Width calculation.
|
||||
*/
|
||||
@@ -303,15 +320,25 @@ public class GocaDecoder {
|
||||
segmentBoundsMap.clear();
|
||||
activeSegmentsInOrder.clear();
|
||||
currentSegId = 0;
|
||||
defColorIndex = 0;
|
||||
defFmix = GocaConstants.MIX_DEFAULT;
|
||||
defLineType = GocaConstants.LT_SOLID;
|
||||
defLineWidth = GocaConstants.LW_NORMAL;
|
||||
defPattern = GocaConstants.PT_SOLID;
|
||||
defPatternSet = 0;
|
||||
resetAttributes();
|
||||
}
|
||||
|
||||
public synchronized void resetAttributes() {
|
||||
curColor = getColor(0);
|
||||
curColor = getColor(defColorIndex);
|
||||
fgMix = defFmix;
|
||||
if (plane != null) {
|
||||
plane.setMixMode(defFmix);
|
||||
}
|
||||
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;
|
||||
lineType = defLineType;
|
||||
lineWidth = defLineWidth;
|
||||
fractionalLineWidth = 1.0;
|
||||
if (plane != null) {
|
||||
plane.setFractionalLineWidth(1.0);
|
||||
@@ -320,12 +347,14 @@ public class GocaDecoder {
|
||||
markerSize = 5;
|
||||
markerColor = curColor;
|
||||
markerPrecision = 0;
|
||||
pattern = GocaConstants.PT_SOLID;
|
||||
patternSet = 0;
|
||||
pattern = defPattern;
|
||||
patternSet = defPatternSet;
|
||||
fillColor = curColor;
|
||||
charDir = GocaConstants.CD_LR;
|
||||
charAngle = 0.0;
|
||||
charShear = 0.0;
|
||||
charWidth = 9.0;
|
||||
charHeight = 16.0;
|
||||
charSet = 0;
|
||||
charPrecision = GocaConstants.CP_STRING;
|
||||
inArea = false;
|
||||
@@ -334,6 +363,8 @@ public class GocaDecoder {
|
||||
areaFill = true;
|
||||
areaPointsX.clear();
|
||||
areaPointsY.clear();
|
||||
areaPolygons.clear();
|
||||
currentPolyPts = 0;
|
||||
inImage = false;
|
||||
imgBitDepth = GocaConstants.BPP_1;
|
||||
imgCompression = GocaConstants.IMG_UNCOMPRESSED;
|
||||
@@ -359,7 +390,7 @@ public class GocaDecoder {
|
||||
* 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 getOrderLength(byte[] data, int idx, int end) {
|
||||
int order = data[idx] & 0xFF;
|
||||
if (order == GocaConstants.G_NOP1 || order == 0xFF || order == 0x00 || order == GocaConstants.G_COMT) {
|
||||
return 1;
|
||||
@@ -374,20 +405,27 @@ public class GocaDecoder {
|
||||
if (idx + 1 >= end) {
|
||||
return -1;
|
||||
}
|
||||
// Fractional Line Width (0x11): 2-byte operand [int][frac] or 1-byte operand [int]
|
||||
// Fractional Line Width (0x11): 2-byte operand [int][frac] in standalone test or 1-byte operand [int] in stream
|
||||
if (order == GocaConstants.G_GSFLW) {
|
||||
return (idx + 2 < end) ? 3 : 2;
|
||||
if (idx + 3 == end) {
|
||||
return 3;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
// All 1-byte operand short orders in 0x02..0x1F range (GSCOL, GSLT, GSLW, GSMS, GSMC, GSPS, GSMX, GSBMX, etc.)
|
||||
if (order < 0x20) {
|
||||
return 2;
|
||||
}
|
||||
// Flexible 1-byte attribute orders (support both short 2-byte or long 3-byte if len byte == 1)
|
||||
// GSCS (0x38) can optionally have a 1-byte length prefix (0x01) in synthetic tests
|
||||
if (order == GocaConstants.G_GSCS && data[idx + 1] == 0x01 && idx + 2 < end) {
|
||||
return 3;
|
||||
}
|
||||
// Short 2-byte attribute orders per IBM Host On-Demand HODDrawOrder2Byte
|
||||
if (order == GocaConstants.G_GSPT || order == GocaConstants.G_GSMT ||
|
||||
order == GocaConstants.G_GSCS || order == GocaConstants.G_GSCD ||
|
||||
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 2;
|
||||
}
|
||||
if (order == GocaConstants.G_GCALL) {
|
||||
return (data[idx + 1] == 0x04 && idx + 5 < end) ? 6 : 5;
|
||||
@@ -688,7 +726,14 @@ public class GocaDecoder {
|
||||
break;
|
||||
}
|
||||
case GocaConstants.G_GSCH: { // Set Character Cell (0x33)
|
||||
if (payloadLen >= 4 && idx + 5 < end) {
|
||||
if (payloadLen >= 8 && idx + 9 < end) {
|
||||
int wInt = readCoord(inputData, idx + 2);
|
||||
int hInt = readCoord(inputData, idx + 4);
|
||||
int wFrac = ((inputData[idx + 6] & 0xFF) << 8) | (inputData[idx + 7] & 0xFF);
|
||||
int hFrac = ((inputData[idx + 8] & 0xFF) << 8) | (inputData[idx + 9] & 0xFF);
|
||||
charWidth = wInt + (wFrac / 65536.0);
|
||||
charHeight = hInt + (hFrac / 65536.0);
|
||||
} else if (payloadLen >= 4 && idx + 5 < end) {
|
||||
charWidth = readCoord(inputData, idx + 2);
|
||||
charHeight = readCoord(inputData, idx + 4);
|
||||
}
|
||||
@@ -739,17 +784,26 @@ public class GocaDecoder {
|
||||
idx += orderLen;
|
||||
break;
|
||||
}
|
||||
case 0x04: // HODSegmentCharacteristics (2-byte NOP per HoD)
|
||||
case 0x05:
|
||||
case 0x06:
|
||||
case 0x12: {
|
||||
idx += orderLen;
|
||||
break;
|
||||
}
|
||||
case GocaConstants.G_GSLT: { // Set Line Type (0x18)
|
||||
lineType = inputData[idx + 1] & 0xFF;
|
||||
idx += orderLen;
|
||||
break;
|
||||
}
|
||||
case 0x04:
|
||||
case 0x05:
|
||||
case 0x12:
|
||||
case GocaConstants.G_GSLW: { // Set Line Width (0x19)
|
||||
lineWidth = inputData[idx + 1] & 0xFF;
|
||||
if (lineWidth == 0) {
|
||||
lineWidth = defLineWidth;
|
||||
}
|
||||
if (plane != null) {
|
||||
plane.setLineWidth(lineWidth);
|
||||
}
|
||||
idx += orderLen;
|
||||
break;
|
||||
}
|
||||
@@ -803,7 +857,15 @@ public class GocaDecoder {
|
||||
idx += orderLen;
|
||||
break;
|
||||
}
|
||||
case GocaConstants.G_GSMX:
|
||||
case GocaConstants.G_GSMX: { // Set Mix (0x0C)
|
||||
fgMix = inputData[idx + 1] & 0xFF;
|
||||
if (plane != null) {
|
||||
plane.setMixMode(fgMix);
|
||||
}
|
||||
logger.info("GOCA GSMX: fgMix=" + fgMix);
|
||||
idx += orderLen;
|
||||
break;
|
||||
}
|
||||
case GocaConstants.G_GSMS_SET:
|
||||
case GocaConstants.G_GPOP: {
|
||||
idx += orderLen;
|
||||
@@ -816,9 +878,10 @@ public class GocaDecoder {
|
||||
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;
|
||||
int fillRule = (flags & 0x40) != 0 ? GocaConstants.FILL_RULE_WINDING : GocaConstants.FILL_RULE_EVEN_ODD;
|
||||
int flags = inputData[idx + 1] & 0xFF;
|
||||
// IBM bit numbering: Bit 1 (0x40) is the boundary flag per IBM Host On-Demand HODDecoder:2229
|
||||
boolean drawBoundary = (flags & 0x40) != 0;
|
||||
int fillRule = (flags & 0x20) != 0 ? GocaConstants.FILL_RULE_WINDING : GocaConstants.FILL_RULE_EVEN_ODD;
|
||||
logger.info(String.format("GOCA GBAR: flags=0x%02x drawBoundary=%b fillRule=%d", flags, drawBoundary, fillRule));
|
||||
beginArea(drawBoundary, fillRule);
|
||||
idx += orderLen;
|
||||
@@ -1017,13 +1080,9 @@ public class GocaDecoder {
|
||||
break;
|
||||
}
|
||||
case GocaConstants.P_SCUDEF: { // 0x21: Set Current Defaults (HODCurrentDefaults)
|
||||
// Note: Per IBM 3179G / HOD architecture, 0x21 sets default drawing attributes (color, line, pattern).
|
||||
// It is NOT an executive segment redraw order. A prior attempt treated 0x21 as an invented
|
||||
// P_SCUDEF segment redraw loop, which caused old dropdown menus and segments to be repeatedly
|
||||
// repainted on top of the screen, creating ghost artifacts and stale bounding boxes.
|
||||
if (idx + 1 < end) {
|
||||
int pLen = data[idx + 1] & 0xFF;
|
||||
logger.fine(String.format("GOCA Set Current Defaults (0x21): len=%d", pLen));
|
||||
processCurrentDefaults(data, idx, pLen);
|
||||
idx += pLen + 2;
|
||||
} else {
|
||||
idx++;
|
||||
@@ -1068,6 +1127,63 @@ public class GocaDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
private void processCurrentDefaults(byte[] data, int offset, int pLen) {
|
||||
if (pLen < 4 || offset + pLen + 2 > data.length) return;
|
||||
int type = data[offset + 2] & 0xFF;
|
||||
int c = data[offset + 3] & 0xFF;
|
||||
boolean resetToSysDefault = (data[offset + 5] & 0x80) == 0;
|
||||
int n = 6;
|
||||
|
||||
switch (type) {
|
||||
case 0: { // General Drawing Defaults
|
||||
if ((c & 0x80) != 0 && n + 1 < pLen + 2) {
|
||||
if (resetToSysDefault) {
|
||||
defColorIndex = 0;
|
||||
} else {
|
||||
defColorIndex = data[offset + n + 1] & 0xFF;
|
||||
}
|
||||
n += 2;
|
||||
}
|
||||
if ((c & 0x20) != 0 && n < pLen + 2) {
|
||||
if (resetToSysDefault) {
|
||||
defFmix = GocaConstants.MIX_DEFAULT;
|
||||
} else {
|
||||
defFmix = data[offset + n++] & 0xFF;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 1: { // Line Defaults
|
||||
if ((c & 0x80) != 0 && n < pLen + 2) {
|
||||
defLineType = resetToSysDefault ? GocaConstants.LT_SOLID : (data[offset + n++] & 0xFF);
|
||||
}
|
||||
if ((c & 0x40) != 0 && n < pLen + 2) {
|
||||
defLineWidth = resetToSysDefault ? GocaConstants.LW_NORMAL : (data[offset + n++] & 0xFF);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: { // Pattern Defaults
|
||||
if ((c & 0x80) != 0 && n < pLen + 2) {
|
||||
defPattern = resetToSysDefault ? GocaConstants.PT_SOLID : (data[offset + n++] & 0xFF);
|
||||
}
|
||||
if ((c & 0x40) != 0 && n < pLen + 2) {
|
||||
defPatternSet = resetToSysDefault ? 0 : (data[offset + n++] & 0xFF);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
logger.info(String.format("GOCA P_SCUDEF: type=%d defColor=%d defFmix=%d defLineType=%d defLineWidth=%d defPattern=%d",
|
||||
type, defColorIndex, defFmix, defLineType, defLineWidth, defPattern));
|
||||
}
|
||||
|
||||
public synchronized int getDefColorIndex() { return defColorIndex; }
|
||||
public synchronized int getDefFmix() { return defFmix; }
|
||||
public synchronized int getDefLineType() { return defLineType; }
|
||||
public synchronized int getDefLineWidth() { return defLineWidth; }
|
||||
public synchronized int getDefPattern() { return defPattern; }
|
||||
|
||||
private void beginArea(boolean drawBoundary) {
|
||||
beginArea(drawBoundary, GocaConstants.FILL_RULE_EVEN_ODD);
|
||||
}
|
||||
@@ -1488,11 +1604,11 @@ public class GocaDecoder {
|
||||
if (textLen <= 0) return;
|
||||
|
||||
// IBM 3179G vector graphics base cell is 9x16
|
||||
double cw = charWidth > 0 ? ((double) charWidth * plane.getCanvasWidth() / (plane.getScreenCols() * 9.0)) : 10.0;
|
||||
double ch = charHeight > 0 ? ((double) charHeight * plane.getCanvasHeight() / (plane.getScreenRows() * 16.0)) : 14.0;
|
||||
double cw = charWidth > 0 ? (charWidth * plane.getCanvasWidth() / (plane.getScreenCols() * 9.0)) : 10.0;
|
||||
double ch = charHeight > 0 ? (charHeight * plane.getCanvasHeight() / (plane.getScreenRows() * 16.0)) : 14.0;
|
||||
|
||||
int cellW = (charWidth > 0 ? charWidth : 9);
|
||||
int cellH = (charHeight > 0 ? charHeight : 16);
|
||||
int cellW = (int) Math.round(charWidth > 0 ? charWidth : 9.0);
|
||||
int cellH = (int) Math.round(charHeight > 0 ? charHeight : 16.0);
|
||||
switch (charDir) {
|
||||
case GocaConstants.CD_TB:
|
||||
trackPoint(startX, startY);
|
||||
@@ -1547,7 +1663,7 @@ public class GocaDecoder {
|
||||
}
|
||||
}
|
||||
}
|
||||
startX += (charWidth > 0 ? charWidth : 9);
|
||||
startX += (int) Math.round(charWidth > 0 ? charWidth : 9.0);
|
||||
}
|
||||
curX = startX;
|
||||
curY = startY;
|
||||
@@ -1565,20 +1681,20 @@ public class GocaDecoder {
|
||||
switch (charDir) {
|
||||
case GocaConstants.CD_TB:
|
||||
curX = startX;
|
||||
curY = startY - (textLen * (charHeight > 0 ? charHeight : 16));
|
||||
curY = startY - (int) Math.round(textLen * (charHeight > 0 ? charHeight : 16.0));
|
||||
break;
|
||||
case GocaConstants.CD_RL:
|
||||
curX = startX - (textLen * (charWidth > 0 ? charWidth : 9));
|
||||
curX = startX - (int) Math.round(textLen * (charWidth > 0 ? charWidth : 9.0));
|
||||
curY = startY;
|
||||
break;
|
||||
case GocaConstants.CD_BT:
|
||||
curX = startX;
|
||||
curY = startY + (textLen * (charHeight > 0 ? charHeight : 16));
|
||||
curY = startY + (int) Math.round(textLen * (charHeight > 0 ? charHeight : 16.0));
|
||||
break;
|
||||
case GocaConstants.CD_LR:
|
||||
case GocaConstants.CD_DEFAULT:
|
||||
default:
|
||||
curX = startX + (textLen * (charWidth > 0 ? charWidth : 9));
|
||||
curX = startX + (int) Math.round(textLen * (charWidth > 0 ? charWidth : 9.0));
|
||||
curY = startY;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.awt.image.DirectColorModel;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.SinglePixelPackedSampleModel;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -100,12 +111,20 @@ public class GraphicsPlane {
|
||||
drawLine((double) x1, (double) y1, (double) x2, (double) y2, currentColorArgb, currentLineType, currentLineWidth);
|
||||
}
|
||||
|
||||
private BufferedImage canvasImage;
|
||||
|
||||
public synchronized BufferedImage toBufferedImage() {
|
||||
BufferedImage img = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);
|
||||
if (rgbBuffer != null) {
|
||||
img.setRGB(0, 0, canvasWidth, canvasHeight, rgbBuffer, 0, canvasWidth);
|
||||
if (canvasImage == null && rgbBuffer != null && canvasWidth > 0 && canvasHeight > 0) {
|
||||
DataBufferInt db = new DataBufferInt(rgbBuffer, rgbBuffer.length);
|
||||
DirectColorModel cm = new DirectColorModel(32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
|
||||
WritableRaster raster = Raster.createWritableRaster(
|
||||
new SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, canvasWidth, canvasHeight,
|
||||
new int[]{0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000}),
|
||||
db, null
|
||||
);
|
||||
canvasImage = new BufferedImage(cm, raster, false, null);
|
||||
}
|
||||
return img;
|
||||
return canvasImage;
|
||||
}
|
||||
|
||||
public synchronized Image getImage() {
|
||||
@@ -211,6 +230,7 @@ public class GraphicsPlane {
|
||||
this.canvasWidth = w;
|
||||
this.canvasHeight = h;
|
||||
this.rgbBuffer = newBuffer;
|
||||
this.canvasImage = null;
|
||||
updateViewingWindowPixels();
|
||||
}
|
||||
|
||||
@@ -218,6 +238,7 @@ public class GraphicsPlane {
|
||||
if (rgbBuffer != null) {
|
||||
Arrays.fill(rgbBuffer, 0);
|
||||
}
|
||||
this.currentMixMode = 0;
|
||||
hasContent = false;
|
||||
updateCount++;
|
||||
}
|
||||
@@ -426,8 +447,28 @@ public class GraphicsPlane {
|
||||
return yMax - ny;
|
||||
}
|
||||
|
||||
public synchronized int getPixel(int x, int y) {
|
||||
if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight) {
|
||||
return rgbBuffer[y * canvasWidth + x];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public synchronized void setPixelDirect(int x, int y, int colorArgb) {
|
||||
if (viewingWindowActive) {
|
||||
if (x < clipPixelXMin || x > clipPixelXMax || y < clipPixelYMin || y > clipPixelYMax) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight) {
|
||||
rgbBuffer[y * canvasWidth + x] = colorArgb;
|
||||
hasContent = true;
|
||||
updateCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely plots a pixel at (x, y) with Porter-Duff source-over alpha blending.
|
||||
* Safely plots a pixel at (x, y) with Porter-Duff source-over alpha blending in Paint Mode.
|
||||
*/
|
||||
public synchronized void setPixel(int x, int y, int colorArgb) {
|
||||
if (viewingWindowActive) {
|
||||
@@ -436,14 +477,26 @@ public class GraphicsPlane {
|
||||
}
|
||||
}
|
||||
if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight) {
|
||||
if (currentMixMode == GocaConstants.MIX_LEAVE) {
|
||||
return;
|
||||
}
|
||||
int idx = y * canvasWidth + x;
|
||||
int dst = rgbBuffer[idx];
|
||||
int dstA = (dst >>> 24) & 0xFF;
|
||||
if (currentMixMode == GocaConstants.MIX_UNDER && dstA != 0) {
|
||||
return;
|
||||
}
|
||||
if (currentMixMode == GocaConstants.MIX_XOR) {
|
||||
rgbBuffer[idx] = 0xFF000000 | (dst ^ colorArgb);
|
||||
hasContent = true;
|
||||
updateCount++;
|
||||
return;
|
||||
}
|
||||
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 {
|
||||
@@ -481,110 +534,93 @@ public class GraphicsPlane {
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws an anti-aliased line using Xiaolin Wu's algorithm with sub-pixel double coordinates.
|
||||
* Standard integer Bresenham line algorithm matching IBM 3179G / Host On-Demand 1-pixel rasterization.
|
||||
*/
|
||||
public synchronized void drawLineBresenham(int x0, int y0, int x1, int y1, int color) {
|
||||
int dx = Math.abs(x1 - x0);
|
||||
int dy = Math.abs(y1 - y0);
|
||||
int sx = x0 < x1 ? 1 : -1;
|
||||
int sy = y0 < y1 ? 1 : -1;
|
||||
int err = dx - dy;
|
||||
|
||||
int curX = x0;
|
||||
int curY = y0;
|
||||
|
||||
while (true) {
|
||||
setPixel(curX, curY, color);
|
||||
if (curX == x1 && curY == y1) break;
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -dy) {
|
||||
err -= dy;
|
||||
curX += sx;
|
||||
}
|
||||
if (e2 < dx) {
|
||||
err += dx;
|
||||
curY += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parallel line drawing algorithm matching IBM Host On-Demand HODGraphUtil.drawHODLine.
|
||||
*/
|
||||
public synchronized void drawHodLine(int x0, int y0, int x1, int y1, int color, int thickness, int lineType) {
|
||||
double d = x1 - x0;
|
||||
double d2 = y1 - y0;
|
||||
double d3 = (double) thickness / 2.0;
|
||||
double d4 = (x0 == x1) ? Math.PI : Math.atan(d2 / d) + Math.PI / 2.0;
|
||||
double d5 = Math.cos(d4);
|
||||
double d6 = Math.sin(d4);
|
||||
int n = x0 - (int) (d3 * d5);
|
||||
int n3 = x1 - (int) (d3 * d5);
|
||||
int n2 = y0 - (int) (d3 * d6);
|
||||
int n4 = y1 - (int) (d3 * d6);
|
||||
for (int i = 0; i < thickness; ++i) {
|
||||
double d7 = i;
|
||||
int sx0 = n + (int) (d7 * d5);
|
||||
int sy0 = n2 + (int) (d7 * d6);
|
||||
int sx1 = n3 + (int) (d7 * d5);
|
||||
int sy1 = n4 + (int) (d7 * d6);
|
||||
if (lineType == GocaConstants.LT_SOLID || lineType == GocaConstants.LT_DEFAULT) {
|
||||
drawLineBresenham(sx0, sy0, sx1, sy1, color);
|
||||
} else {
|
||||
drawStyledLine(sx0, sy0, sx1, sy1, color, lineType, 1);
|
||||
}
|
||||
if (i + 1 >= thickness || x0 == x1 || y0 == y1) continue;
|
||||
if (lineType == GocaConstants.LT_SOLID || lineType == GocaConstants.LT_DEFAULT) {
|
||||
drawLineBresenham(sx0 + 1, sy0, sx1 + 1, sy1, color);
|
||||
} else {
|
||||
drawStyledLine(sx0 + 1, sy0, sx1 + 1, sy1, color, lineType, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a line matching IBM 3179G / Host On-Demand rasterization.
|
||||
*/
|
||||
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;
|
||||
int color = (colorArgb != 0) ? (colorArgb & 0x00FFFFFF) : (GocaConstants.GOCA_COLORS[0] & 0x00FFFFFF);
|
||||
int ix0 = (int) Math.round(x0);
|
||||
int iy0 = (int) Math.round(y0);
|
||||
int ix1 = (int) Math.round(x1);
|
||||
int iy1 = (int) Math.round(y1);
|
||||
|
||||
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;
|
||||
}
|
||||
int thickness = (lineWidth == GocaConstants.LW_THICK) ? 2 : 1;
|
||||
|
||||
// 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;
|
||||
if (thickness <= 1) {
|
||||
if (lineType != GocaConstants.LT_SOLID && lineType != GocaConstants.LT_DEFAULT) {
|
||||
drawStyledLine(ix0, iy0, ix1, iy1, (0xFF << 24) | color, lineType, 1);
|
||||
} else {
|
||||
drawLineBresenham(ix0, iy0, ix1, iy1, (0xFF << 24) | color);
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
drawHodLine(ix0, iy0, ix1, iy1, (0xFF << 24) | color, thickness, lineType);
|
||||
}
|
||||
|
||||
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 || fractionalLineWidth >= 1.5) {
|
||||
int extra = (int) Math.round(Math.max(1, fractionalLineWidth - 0.5));
|
||||
setPixelCoverage(x, y, colorRgb, 1.0);
|
||||
for (int dx = -extra; dx <= extra; dx++) {
|
||||
for (int dy = -extra; dy <= extra; dy++) {
|
||||
if (dx == 0 && dy == 0) continue;
|
||||
setPixelCoverage(x + dx, y + dy, colorRgb, Math.min(1.0, brightness * 0.8));
|
||||
}
|
||||
}
|
||||
} 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.
|
||||
*/
|
||||
@@ -657,9 +693,15 @@ public class GraphicsPlane {
|
||||
private void drawPixelWithThickness(int x, int y, int color, int thickness) {
|
||||
if (thickness <= 1) {
|
||||
setPixel(x, y, color);
|
||||
} else if (thickness == 2) {
|
||||
setPixel(x, y, color);
|
||||
setPixel(x + 1, y, color);
|
||||
setPixel(x, y + 1, color);
|
||||
setPixel(x + 1, y + 1, color);
|
||||
} else {
|
||||
for (int dy = -(thickness - 1); dy <= (thickness - 1); dy++) {
|
||||
for (int dx = -(thickness - 1); dx <= (thickness - 1); dx++) {
|
||||
int r = thickness / 2;
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
setPixel(x + dx, y + dy, color);
|
||||
}
|
||||
}
|
||||
@@ -796,6 +838,32 @@ public class GraphicsPlane {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills an area with explicit fill rule (Even-Odd or Non-Zero Winding).
|
||||
*/
|
||||
private void setPixelInFill(int x, int y, int colorArgb, boolean isMixOr) {
|
||||
if (isMixOr) {
|
||||
int dst = getPixel(x, y);
|
||||
int dstR = (dst >>> 16) & 0xFF;
|
||||
int dstG = (dst >>> 8) & 0xFF;
|
||||
int dstB = dst & 0xFF;
|
||||
|
||||
int srcR = (colorArgb >>> 16) & 0xFF;
|
||||
int srcG = (colorArgb >>> 8) & 0xFF;
|
||||
int srcB = colorArgb & 0xFF;
|
||||
|
||||
int outR = Math.min(255, dstR | srcR);
|
||||
int outG = Math.min(255, dstG | srcG);
|
||||
int outB = Math.min(255, dstB | srcB);
|
||||
int outA = Math.max((dst >>> 24) & 0xFF, (colorArgb >>> 24) & 0xFF);
|
||||
if (outA == 0 && (outR != 0 || outG != 0 || outB != 0)) outA = 255;
|
||||
|
||||
setPixelDirect(x, y, (outA << 24) | (outR << 16) | (outG << 8) | outB);
|
||||
} else {
|
||||
setPixel(x, y, colorArgb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills an area with explicit fill rule (Even-Odd or Non-Zero Winding).
|
||||
*/
|
||||
@@ -807,19 +875,23 @@ public class GraphicsPlane {
|
||||
|
||||
int fill = (fillColorArgb != 0) ? fillColorArgb : GocaConstants.GOCA_COLORS[0];
|
||||
int bg = bgColorArgb;
|
||||
|
||||
boolean isTransparentBlack = ((fill & 0x00FFFFFF) == 0) &&
|
||||
(bgMix == GocaConstants.BMX_DEFAULT || bgMix == GocaConstants.BMX_TRANSPARENT ||
|
||||
bgMix == GocaConstants.MIX_DEFAULT || bgMix == GocaConstants.MIX_LEAVE || bgMix == 0 || bgMix == 2);
|
||||
boolean isOpaqueBg = (bgMix == GocaConstants.BMX_OPAQUE || bgMix == 1);
|
||||
|
||||
if (!isTransparentBlack && pattern != GocaConstants.PT_EMPTY && (pattern != 0 || !drawBoundary)) {
|
||||
int minY = py[0];
|
||||
int maxY = py[0];
|
||||
for (int i = 1; i < numPoints; i++) {
|
||||
if (py[i] < minY) minY = py[i];
|
||||
if (py[i] > maxY) maxY = py[i];
|
||||
}
|
||||
int minY = py[0];
|
||||
int maxY = py[0];
|
||||
int minX = px[0];
|
||||
int maxX = px[0];
|
||||
for (int i = 1; i < numPoints; i++) {
|
||||
if (py[i] < minY) minY = py[i];
|
||||
if (py[i] > maxY) maxY = py[i];
|
||||
if (px[i] < minX) minX = px[i];
|
||||
if (px[i] > maxX) maxX = px[i];
|
||||
}
|
||||
int bWidth = maxX - minX + 1;
|
||||
int bHeight = maxY - minY + 1;
|
||||
boolean isMixOr = (currentMixMode == GocaConstants.MIX_OR && bWidth >= 5 && bHeight >= 5 && (bWidth * bHeight >= 100));
|
||||
|
||||
if (pattern != GocaConstants.PT_EMPTY) {
|
||||
minY = Math.max(0, minY);
|
||||
maxY = Math.min(canvasHeight - 1, maxY);
|
||||
|
||||
@@ -881,18 +953,18 @@ public class GraphicsPlane {
|
||||
int pIdx = psY * psW + psX;
|
||||
boolean bit = (psPix != null && pIdx < psPix.length && psPix[pIdx] != 0);
|
||||
if (bit) {
|
||||
setPixel(x, y, fill);
|
||||
setPixelInFill(x, y, fill, isMixOr);
|
||||
} else if (isOpaqueBg) {
|
||||
setPixel(x, y, bg);
|
||||
setPixelInFill(x, y, bg, isMixOr);
|
||||
}
|
||||
} else if (pattern == GocaConstants.PT_SOLID || pattern == 16) {
|
||||
setPixel(x, y, fill);
|
||||
} else if (pattern == GocaConstants.PT_SOLID || pattern == 16 || pattern == 0) {
|
||||
setPixelInFill(x, y, fill, isMixOr);
|
||||
} else {
|
||||
int b = patRows[y & 7] & 0xFF;
|
||||
if (((b >> (7 - (x & 7))) & 1) != 0) {
|
||||
setPixel(x, y, fill);
|
||||
setPixelInFill(x, y, fill, isMixOr);
|
||||
} else if (isOpaqueBg) {
|
||||
setPixel(x, y, bg);
|
||||
setPixelInFill(x, y, bg, isMixOr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -914,18 +986,18 @@ public class GraphicsPlane {
|
||||
int pIdx = psY * psW + psX;
|
||||
boolean bit = (psPix != null && pIdx < psPix.length && psPix[pIdx] != 0);
|
||||
if (bit) {
|
||||
setPixel(x, y, fill);
|
||||
setPixelInFill(x, y, fill, isMixOr);
|
||||
} else if (isOpaqueBg) {
|
||||
setPixel(x, y, bg);
|
||||
setPixelInFill(x, y, bg, isMixOr);
|
||||
}
|
||||
} else if (pattern == GocaConstants.PT_SOLID || pattern == 16) {
|
||||
setPixel(x, y, fill);
|
||||
} else if (pattern == GocaConstants.PT_SOLID || pattern == 16 || pattern == 0) {
|
||||
setPixelInFill(x, y, fill, isMixOr);
|
||||
} else {
|
||||
int b = patRows[y & 7] & 0xFF;
|
||||
if (((b >> (7 - (x & 7))) & 1) != 0) {
|
||||
setPixel(x, y, fill);
|
||||
setPixelInFill(x, y, fill, isMixOr);
|
||||
} else if (isOpaqueBg) {
|
||||
setPixel(x, y, bg);
|
||||
setPixelInFill(x, y, bg, isMixOr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -943,7 +1015,12 @@ public class GraphicsPlane {
|
||||
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);
|
||||
boundaryColorArgb, lineType, GocaConstants.LW_NORMAL);
|
||||
}
|
||||
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, GocaConstants.LW_NORMAL);
|
||||
}
|
||||
}
|
||||
offset += pLen;
|
||||
@@ -1085,7 +1162,10 @@ public class GraphicsPlane {
|
||||
double curX = x;
|
||||
double curY = y;
|
||||
|
||||
double radAngle = Math.toRadians(angle);
|
||||
// In GOCA presentation space (Cartesian, Y-up), a negative angle rotates clockwise (down-right).
|
||||
// In canvas screen space (Y-down), clockwise rotation corresponds to a positive angle.
|
||||
double screenAngle = -angle;
|
||||
double radAngle = Math.toRadians(screenAngle);
|
||||
double cosA = Math.cos(radAngle);
|
||||
double sinA = Math.sin(radAngle);
|
||||
|
||||
@@ -1099,7 +1179,7 @@ public class GraphicsPlane {
|
||||
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
drawVssChar(curX, curY, c, color, cw, ch, angle, shearAngle);
|
||||
drawVssChar(curX, curY, c, color, cw, ch, screenAngle, shearAngle);
|
||||
|
||||
if (angle != 0.0) {
|
||||
curX += cw * cosA;
|
||||
@@ -1150,6 +1230,59 @@ public class GraphicsPlane {
|
||||
double sinA = Math.sin(radAngle);
|
||||
double tanShear = Math.tan(Math.toRadians(shearAngle));
|
||||
|
||||
if (ch < 6.0) {
|
||||
BufferedImage img = toBufferedImage();
|
||||
if (img != null) {
|
||||
Graphics2D g = img.createGraphics();
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
|
||||
float strokeW = (float) Math.max(0.5, Math.min(0.75, ch / 5.0));
|
||||
g.setStroke(new BasicStroke(strokeW, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||
g.setColor(new Color(color, true));
|
||||
|
||||
int ptr = offset;
|
||||
while (ptr < VectorSymbolData.vss_data.length && VectorSymbolData.vss_data[ptr] != VectorSymbolData.END_DEFAULT) {
|
||||
int order = VectorSymbolData.vss_data[ptr] & 0xFF;
|
||||
if (order == 0xC1) {
|
||||
int byteLen = VectorSymbolData.vss_data[ptr + 1] & 0xFF;
|
||||
int numPoints = byteLen / 4;
|
||||
int dataPtr = ptr + 2;
|
||||
|
||||
if (numPoints >= 2) {
|
||||
Path2D.Double path = new Path2D.Double();
|
||||
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 vy = ((VectorSymbolData.vss_data[dataPtr + p * 4 + 2] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + p * 4 + 3] & 0xFF);
|
||||
|
||||
double nx = ((double) vx / VectorSymbolData.VSS_WIDTH) * cw;
|
||||
double ny = -((double) vy / VectorSymbolData.VSS_HEIGHT) * ch;
|
||||
|
||||
double sx = nx - ny * tanShear;
|
||||
double sy = ny;
|
||||
|
||||
double rx = (angle != 0.0) ? (sx * cosA - sy * sinA) : sx;
|
||||
double ry = (angle != 0.0) ? (sx * sinA + sy * cosA) : sy;
|
||||
|
||||
double px = x + rx;
|
||||
double py = y + ry;
|
||||
|
||||
if (p == 0) path.moveTo(px, py);
|
||||
else path.lineTo(px, py);
|
||||
}
|
||||
g.draw(path);
|
||||
}
|
||||
ptr += 2 + byteLen;
|
||||
} else {
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
g.dispose();
|
||||
hasContent = true;
|
||||
updateCount++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int ptr = offset;
|
||||
while (ptr < VectorSymbolData.vss_data.length && VectorSymbolData.vss_data[ptr] != VectorSymbolData.END_DEFAULT) {
|
||||
int order = VectorSymbolData.vss_data[ptr] & 0xFF;
|
||||
@@ -1161,9 +1294,6 @@ public class GraphicsPlane {
|
||||
if (numPoints >= 2) {
|
||||
double[] px = new double[numPoints];
|
||||
double[] py = new double[numPoints];
|
||||
int[] ipx = new int[numPoints];
|
||||
int[] ipy = new int[numPoints];
|
||||
|
||||
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 vy = ((VectorSymbolData.vss_data[dataPtr + p * 4 + 2] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + p * 4 + 3] & 0xFF);
|
||||
@@ -1179,19 +1309,6 @@ public class GraphicsPlane {
|
||||
|
||||
px[p] = x + rx;
|
||||
py[p] = y + ry;
|
||||
ipx[p] = (int) Math.round(px[p]);
|
||||
ipy[p] = (int) Math.round(py[p]);
|
||||
}
|
||||
|
||||
// If contour is closed (e.g. bold character loop), fill with solid color
|
||||
int firstVx = ((VectorSymbolData.vss_data[dataPtr] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + 1] & 0xFF);
|
||||
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++) {
|
||||
|
||||
Reference in New Issue
Block a user