function draw(g, state, drawInfo) {
    var x = drawInfo.texArea[0];
    var y = drawInfo.texArea[1];
    var w = drawInfo.texArea[2] - drawInfo.texArea[0];
    var h = drawInfo.texArea[3] - drawInfo.texArea[1];

    g.setColor(Color.WHITE);
    g.fillRect(drawInfo.texArea[0], drawInfo.texArea[1], drawInfo.texArea[2], drawInfo.texArea[3]);

    var routeInfo = drawInfo.routeInfo;
    // setDebugInfo(`Drawing! routeinfo: ${JSON.stringify(routeInfo)}`);
    if (routeInfo != null && routeInfo != undefined) {
        var CircularState = {
            NONE: "NONE",
            CLOCKWISE: "CLOCKWISE",
            ANTICLOCKWISE: "ANTICLOCKWISE"
        };
        var originalClip = g.getClip();
        var originalTransform = g.getTransform();
        // setDebugInfo(`drawing! drawinfo: ${JSON.stringify(drawInfo, replacer)}`);
        var font = loadResource("font", "mtrsteamloco:fonts/source-han-sans.otf");
        var fontBold = loadResource("font", "mtrsteamloco:fonts/source-han-sans-bold.otf");
        var routeColor = routeInfo.routeColor;
        var arrowSide = drawInfo.arrowDirection ? Number(drawInfo.arrowDirection) : 0;
        var circularState = routeInfo.circularState;
        var currentY = heightPercent(0.2);
        var sideText = circularState == CircularState.NONE ? routeInfo.drawStations[routeInfo.drawStations.length - 1].stationName : routeInfo.drawStations[drawInfo.index + 1].stationName;
        switch (arrowSide) {
            case 0:

            case 1:
                g.setColor(routeColor);
                g.fillRect(x, y, w * 0.2, h);
                if (arrowSide == 1) g.drawImage(loadResource("img", "fangsu:sign/al.png"), widthPercent(0.05), heightPercent(0.05), w * 0.1, w * 0.1, null);
                g.setColor(Color.WHITE);
                if (hasCjkPart(sideText)) {
                    if (circularState == CircularState.NONE) {
                        drawStrUnified(g, font, "开", widthPercent(0.15), currentY, w * 0.1, 2);
                        currentY += h * 0.06;
                        drawStrUnified(g, font, "往", widthPercent(0.15), currentY, w * 0.1, 2);
                        currentY += h * 0.06;
                    } else {
                        currentY -= h * 0.02;
                        drawStrUnified(g, font, "下", widthPercent(0.15), currentY, w * 0.06, 2);
                        currentY += h * 0.04;
                        drawStrUnified(g, font, "一", widthPercent(0.15), currentY, w * 0.06, 2);
                        currentY += h * 0.04;
                        drawStrUnified(g, font, "站", widthPercent(0.15), currentY, w * 0.06, 2);
                        currentY += h * 0.06;
                    }
                    var text = getMatching(sideText, true);
                    for (var i = 0; i < String(getMatching(sideText, true)).length; i++) {
                        var chr = String(text)[i];
                        if (TextUtil.isCjk(chr)) {
                            drawStrUnified(g, fontBold, chr, widthPercent(0.15), currentY, w * 0.1, 2);
                            currentY += h * 0.06;
                        } else {
                            var transform = AffineTransform.getRotateInstance(0.5 * Math.PI, widthPercent(0.07), currentY - w * 0.025);
                            g.setTransform(transform);
                            currentY += drawStrUnified(g, fontBold, chr, widthPercent(0.07), currentY - w * 0.025, w * 0.1, 2);
                            g.setTransform(originalTransform);
                        }
                    }
                    currentY += w * 0.05;
                }
                if (hasNonCjkPart(sideText)) {
                    if (circularState == CircularState.NONE) {
                        var transform = AffineTransform.getRotateInstance(0.5 * Math.PI, widthPercent(0.075), currentY - w * 0.05);
                        g.setTransform(transform);
                        currentY += drawStrUnified(g, fontBold, "To " + getMatching(sideText, false), widthPercent(0.075), currentY - w * 0.05, w * 0.075, 0);
                        g.setTransform(originalTransform);
                    } else {
                        var transform = AffineTransform.getRotateInstance(0.5 * Math.PI, widthPercent(0.1), currentY - w * 0.05);
                        g.setTransform(transform);
                        currentY += Math.max(
                            drawStrUnified(g, font, "Next Station", widthPercent(0.1), currentY - w * 0.05, w * 0.05, 0),
                            drawStrUnified(g, fontBold, getMatching(sideText, false), widthPercent(0.1), currentY, w * 0.05, 0)
                        );
                        g.setTransform(originalTransform);
                    }
                }
                break;
            case 2:
                g.setColor(routeColor);
                g.fillRect(widthPercent(0.8), y, w * 0.2, h);
                g.drawImage(loadResource("img", "fangsu:sign/ar.png"), widthPercent(0.85), heightPercent(0.05), w * 0.1, w * 0.1, null);
                g.setColor(Color.WHITE);
                if (hasCjkPart(sideText)) {
                    if (circularState == CircularState.NONE) {
                        drawStrUnified(g, font, "开", widthPercent(0.85), currentY, w * 0.1, 0);
                        currentY += h * 0.06;
                        drawStrUnified(g, font, "往", widthPercent(0.85), currentY, w * 0.1, 0);
                        currentY += h * 0.06;
                    } else {
                        currentY -= h * 0.02;
                        drawStrUnified(g, font, "下", widthPercent(0.85), currentY, w * 0.06, 0);
                        currentY += h * 0.04;
                        drawStrUnified(g, font, "一", widthPercent(0.85), currentY, w * 0.06, 0);
                        currentY += h * 0.04;
                        drawStrUnified(g, font, "站", widthPercent(0.85), currentY, w * 0.06, 0);
                        currentY += h * 0.06;
                    }
                    var text = getMatching(sideText, true);
                    for (var i = 0; i < getMatching(sideText, true).length; i++) {
                        if (TextUtil.isCjk(chr)) {
                            drawStrUnified(g, fontBold, chr, widthPercent(0.85), currentY, w * 0.1, 0);
                            currentY += h * 0.06;
                        } else {
                            var transform = AffineTransform.getRotateInstance(0.5 * Math.PI, widthPercent(0.87), currentY - w * 0.025);
                            g.setTransform(transform);
                            currentY += drawStrUnified(g, fontBold, chr, widthPercent(0.87), currentY - w * 0.025, w * 0.1, 2);
                            g.setTransform(originalTransform);
                        }
                    }
                    currentY += w * 0.05;
                }
                if (hasNonCjkPart(sideText)) {
                    if (circularState == CircularState.NONE) {
                        var transform = AffineTransform.getRotateInstance(0.5 * Math.PI, widthPercent(0.875), currentY - w * 0.05);
                        g.setTransform(transform);
                        currentY += drawStrUnified(g, fontBold, "To " + getMatching(sideText, false), widthPercent(0.875), currentY - w * 0.05, w * 0.075, 0);
                        g.setTransform(originalTransform);
                    } else {
                        var transform = AffineTransform.getRotateInstance(0.5 * Math.PI, widthPercent(0.9), currentY - w * 0.05);
                        g.setTransform(transform);
                        currentY += Math.max(
                            drawStrUnified(g, font, "Next Station", widthPercent(0.9), currentY - w * 0.05, w * 0.05, 0),
                            drawStrUnified(g, fontBold, getMatching(sideText, false), widthPercent(0.9), currentY, w * 0.05, 0)
                        );
                        g.setTransform(originalTransform);
                    }
                }
                break;
        }

        var routeAtRight = arrowSide != 2;
        if (circularState == CircularState.NONE) {
            for (var i = 0; i < routeInfo.drawStations.length; i++) {
                var currentY = heightPercent(0.05) + ((h * 0.9) / (routeInfo.drawStations.length - 1)) * (routeInfo.drawStations.length - i - 1);
                var hasPassed = i <= drawInfo.index;

                if (i > 0) {
                    g.setColor(hasPassed ? rgbToColor(124, 124, 124) : routeInfo.routeColor);
                    g.fillRect(widthPercent(routeAtRight ? 0.385 : 0.585), currentY, w * 0.03, (h * 0.9) / (routeInfo.drawStations.length - 1));
                }
                if (i == 1) {
                    g.setColor(hasPassed ? rgbToColor(124, 124, 124) : routeInfo.routeColor);
                    g.fillOval(widthPercent(routeAtRight ? 0.385 : 0.585), currentY + (h * 0.9) / (routeInfo.drawStations.length - 1) - w * 0.015, w * 0.03, w * 0.03);
                }
                if (i == routeInfo.drawStations.length - 1) {
                    g.setColor(hasPassed ? rgbToColor(124, 124, 124) : routeInfo.routeColor);
                    g.fillOval(widthPercent(routeAtRight ? 0.385 : 0.585), h * 0.04, w * 0.03, w * 0.03);
                }
            }
            for (var i = 0; i < routeInfo.drawStations.length; i++) {
                var currentY = heightPercent(0.05) + ((h * 0.9) / (routeInfo.drawStations.length - 1)) * (routeInfo.drawStations.length - i - 1);
                var thisStn = routeInfo.drawStations[i];
                var hasPassed = i < drawInfo.index;
                var isTransfer = thisStn.transInfo.length > 0;
                if (isTransfer) {
                    g.setClip(
                        new Polygon(
                            routeAtRight
                                ? [widthPercent(0.35), widthPercent(0.36), widthPercent(0.4), widthPercent(0.4), widthPercent(0.36)]
                                : [widthPercent(0.65), widthPercent(0.64), widthPercent(0.6), widthPercent(0.6), widthPercent(0.64)],
                            [currentY, currentY - w * 0.015, currentY - w * 0.015, currentY + w * 0.015, currentY + w * 0.015],
                            5
                        )
                    );
                    for (var j = 0; j < thisStn.transInfo.length; j++) {
                        var thisY = currentY - w * 0.015 + ((w * 0.03) / thisStn.transInfo.length) * j;
                        var thisTrans = thisStn.transInfo[j];
                        g.setColor(hasPassed ? rgbToColor(124, 124, 124) : thisTrans.routeColor);
                        g.fillRect(widthPercent(0.35), thisY, w * 0.3, (w * 0.03) / thisStn.transInfo.length + 1);
                    }

                    g.setClip(originalClip);
                    g.setColor(hasPassed ? rgbToColor(124, 124, 124) : Color.BLACK);

                    for (var j = 0; j < thisStn.transInfo.length; j++) {
                        var thisTrans = thisStn.transInfo[j];
                        var boxY = currentY - w * 0.03 * thisStn.transInfo.length * 0.5 + w * 0.03 * j;
                        var textWidth = isNumLine(thisTrans.routeName)
                            ? getDLStringWidth(g, fontBold, font, `号线|${getMatching(thisTrans.routeName, false)}`, w * 0.02) +
                              getUnifiedStringWidth(g, fontBold, String(getCJKLineName(getMatching(thisTrans.routeName, true))), w * 0.02)
                            : getDLStringWidth(g, fontBold, font, thisTrans.routeName, w * 0.02);
                        g.setColor(hasPassed ? rgbToColor(124, 124, 124) : thisTrans.routeColor);
                        if (routeAtRight) {
                            g.fillRect(widthPercent(0.33) - textWidth - w * 0.02, boxY, textWidth + w * 0.02, w * 0.03 + 1);
                            g.setColor(hasPassed ? Color.WHITE : isLightColor(thisTrans.routeColor) ? Color.BLACK : Color.WHITE);
                            if (isNumLine(thisTrans.routeName)) {
                                var textX = widthPercent(0.32);
                                textX -= drawStrDL(g, fontBold, font, `号线|${getMatching(thisTrans.routeName, false)}`, textX, boxY + w * 0.005, w * 0.02, 2, 0);
                                textX -= drawStrUnified(g, fontBold, String(getCJKLineName(getMatching(thisTrans.routeName, true))), textX, boxY + w * 0.02, w * 0.02, 2);
                            } else drawStrDL(g, fontBold, font, thisTrans.routeName, widthPercent(0.32), boxY + w * 0.005, w * 0.02, 2, 1);
                        } else {
                            g.fillRect(widthPercent(0.67), boxY, textWidth + w * 0.02, w * 0.03 + 1);
                            g.setColor(hasPassed ? Color.WHITE : isLightColor(thisTrans.routeColor) ? Color.BLACK : Color.WHITE);
                            if (isNumLine(thisTrans.routeName)) {
                                var textX = widthPercent(0.68);
                                textX += drawStrUnified(g, fontBold, String(getCJKLineName(getMatching(thisTrans.routeName, true))), textX, boxY + w * 0.02, w * 0.02, 0);
                                textX += drawStrDL(g, fontBold, font, `号线|${getMatching(thisTrans.routeName, false)}`, textX, boxY + w * 0.005, w * 0.02, 0, 0);
                            } else drawStrDL(g, fontBold, font, thisTrans.routeName, widthPercent(0.68), boxY + w * 0.005, w * 0.02, 0, 1);
                        }
                    }
                }
                if (isTransfer || i == drawInfo.index) {
                    g.setColor(hasPassed ? rgbToColor(124, 124, 124) : routeColor);
                    g.fillOval(widthPercent(routeAtRight ? 0.38 : 0.58), currentY - w * 0.02, w * 0.04, w * 0.04);
                    if (i == drawInfo.index) {
                        g.setColor(Color.RED);
                        g.fillOval(widthPercent(routeAtRight ? 0.385 : 0.585), currentY - w * 0.015, w * 0.03, w * 0.03);
                        g.drawImage(loadResource("img", "fangsu:sign/aub.png"), widthPercent(routeAtRight ? 0.44 : 0.52), currentY - w * 0.02, w * 0.04, w * 0.04, null);
                        if (isTransfer) {
                            g.drawImage(loadResource("img", "fangsu:ris/imgtrans.png"), widthPercent(routeAtRight ? 0.385 : 0.585), currentY - w * 0.015, w * 0.03, w * 0.03, null);
                        }
                    } else {
                        g.setColor(Color.WHITE);
                        g.fillOval(widthPercent(routeAtRight ? 0.385 : 0.585), currentY - w * 0.015, w * 0.03, w * 0.03);
                        g.drawImage(
                            changeImageColor(loadResource("img", "fangsu:ris/imgtrans.png"), hasPassed ? rgbToColor(124, 124, 124) : routeColor),
                            widthPercent(routeAtRight ? 0.385 : 0.585),
                            currentY - w * 0.015,
                            w * 0.03,
                            w * 0.03,
                            null
                        );
                    }
                } else {
                    g.setColor(Color.WHITE);
                    g.fillOval(widthPercent(routeAtRight ? 0.39 : 0.59), currentY - w * 0.01, w * 0.02, w * 0.02);
                }

                var thisX = widthPercent(0.5);
                g.setColor(hasPassed ? rgbToColor(124, 124, 124) : Color.BLACK);
                if (i == drawInfo.index) {
                    var textLength = 0;
                    textLength += getUnifiedStringWidth(g, fontBold, getMatching(thisStn.stationName, true), w * 0.04);
                    textLength += getUnifiedStringWidth(g, fontBold, getMatching(thisStn.stationName, false), w * 0.025);
                    g.setColor(routeColor);
                    if (routeAtRight) {
                        g.fillRoundRect(widthPercent(0.49), currentY - w * 0.03, textLength + w * 0.02, w * 0.06, w * 0.01, w * 0.01);
                    } else {
                        g.fillRoundRect(widthPercent(0.49) - textLength, currentY - w * 0.03, textLength + w * 0.02, w * 0.06, w * 0.01, w * 0.01);
                    }
                    g.setColor(isLightColor(routeColor) ? Color.BLACK : Color.WHITE);
                }

                if (routeAtRight) {
                    thisX += drawStrUnified(g, fontBold, getMatching(thisStn.stationName, true), thisX, currentY + w * 0.02, w * 0.04, 0);
                    thisX += drawStrUnified(g, fontBold, getMatching(thisStn.stationName, false), thisX, currentY + w * 0.02, w * 0.025, 0);
                } else {
                    thisX -= drawStrUnified(g, fontBold, getMatching(thisStn.stationName, true), thisX, currentY + w * 0.02, w * 0.04, 2);
                    thisX -= drawStrUnified(g, fontBold, getMatching(thisStn.stationName, false), thisX, currentY + w * 0.02, w * 0.025, 2);
                }
            }
        } else {
            var leftStations = [];
            var rightStations = [];
            var leftBaseX = widthPercent(routeAtRight ? 0.45 : 0.25);
            var rightBaseX = widthPercent(routeAtRight ? 0.75 : 0.55);

            var stationCount = routeInfo.drawStations.length - 1;
            var leftStationCount = Math.floor((stationCount - 1) / 2) + 1;
            var rightStationCount = stationCount - leftStationCount;
            for (var i = 0; i < stationCount; i++) {
                if (circularState == CircularState.CLOCKWISE) {
                    if (i < rightStationCount) rightStations.push(routeInfo.drawStations[i]);
                    else leftStations.push(routeInfo.drawStations[i]);
                } else {
                    if (i < leftStationCount) leftStations.push(routeInfo.drawStations[i]);
                    else rightStations.push(routeInfo.drawStations[i]);
                }
            }
            if (circularState == CircularState.CLOCKWISE) leftStations.reverse();
            else rightStations.reverse();

            var leftStep = (h * 0.7) / (leftStationCount - 1);
            var rightStep = (h * 0.7) / (rightStationCount - 1);
            var gradientStart = drawInfo.index;
            var gradientEnd = Math.floor(gradientStart - stationCount / 2);
            var halfCrossed = true;
            if (gradientEnd < 0) {
                gradientEnd += stationCount;
                halfCrossed = false;
            }
            // if (gradientStart >= (circularState == CircularState.CLOCKWISE ? rightStationCount : leftStationCount)) halfCrossed = true;
            // else halfCrossed = false;
            setDebugInfo(`leftStationCount ${leftStationCount} rightStationCount ${rightStationCount} gradientStart ${gradientStart} gradientEnd ${gradientEnd} halfCrossed ${halfCrossed}`);

            //弧:直=3:7
            if (circularState == CircularState.CLOCKWISE) {
                if (halfCrossed) {
                    var part3 = 1;
                    var part2 = (1 - (gradientStart - rightStationCount) / leftStationCount) * 0.7;
                    var part1 = part2 + 0.3;
                    var color1 = getGradientColor(routeColor, part1);
                    var color2 = getGradientColor(routeColor, part2);
                    var color3 = getGradientColor(routeColor, part3);
                    drawGradientArc(g, 0.5 * (leftBaseX + rightBaseX), heightPercent(0.15), 0.5 * (rightBaseX - leftBaseX) + w * 0.015, w * 0.03, routeColor, routeColor, true, 80);
                    drawGradientArc(g, 0.5 * (leftBaseX + rightBaseX), heightPercent(0.85), 0.5 * (rightBaseX - leftBaseX) + w * 0.015, w * 0.03, color1, color2, false, 80);
                    g.setColor(routeColor);
                    g.fillRect(leftBaseX - w * 0.015, heightPercent(0.15), w * 0.03, h * 0.7 + 1);
                    g.fillRect(rightBaseX - w * 0.015, heightPercent(0.15), w * 0.03, h * 0.7 + 1);
                    drawGradientRect(
                        g,
                        rightBaseX - w * 0.015,
                        heightPercent(0.85) - Math.abs(gradientEnd - leftStationCount + 1) * rightStep,
                        w * 0.03,
                        Math.abs((gradientEnd - leftStationCount + 1) * rightStep) + 1,
                        routeColor,
                        color2
                    );
                    drawGradientRect(
                        g,
                        leftBaseX - w * 0.015,
                        heightPercent(0.85) - (gradientStart - rightStationCount) * leftStep,
                        w * 0.03,
                        Math.abs((gradientStart - rightStationCount) * leftStep) + 1,
                        color3,
                        color1
                    );
                } else {
                    var part3 = 1;
                    var part2 = (1 - gradientStart / rightStationCount) * 0.7;
                    var part1 = part2 + 0.3;
                    var color1 = getGradientColor(routeColor, part1);
                    var color2 = getGradientColor(routeColor, part2);
                    var color3 = getGradientColor(routeColor, part3);
                    drawGradientArc(g, 0.5 * (leftBaseX + rightBaseX), heightPercent(0.15), 0.5 * (rightBaseX - leftBaseX) + w * 0.015, w * 0.03, color2, color1, true, 80);
                    drawGradientArc(g, 0.5 * (leftBaseX + rightBaseX), heightPercent(0.85), 0.5 * (rightBaseX - leftBaseX) + w * 0.015, w * 0.03, routeColor, routeColor, false, 80);
                    g.setColor(routeColor);
                    g.fillRect(leftBaseX - w * 0.015, heightPercent(0.15), w * 0.03, h * 0.7 + 1);
                    g.fillRect(rightBaseX - w * 0.015, heightPercent(0.15), w * 0.03, h * 0.7 + 1);
                    drawGradientRect(g, rightBaseX - w * 0.015, heightPercent(0.15), w * 0.03, Math.abs(gradientStart * rightStep) + 1, color1, color3);
                    drawGradientRect(g, leftBaseX - w * 0.015, heightPercent(0.15), w * 0.03, Math.abs((leftStationCount - (gradientEnd - rightStationCount) - 1) * leftStep) + 1, color2, routeColor);
                }
            } else {
                if (halfCrossed) {
                    var part3 = 1;
                    var part2 = (1 - (gradientStart - leftStationCount) / rightStationCount) * 0.7;
                    var part1 = part2 + 0.3;
                    var color1 = getGradientColor(routeColor, part1);
                    var color2 = getGradientColor(routeColor, part2);
                    var color3 = getGradientColor(routeColor, part3);
                    drawGradientArc(g, 0.5 * (leftBaseX + rightBaseX), heightPercent(0.15), 0.5 * (rightBaseX - leftBaseX) + w * 0.015, w * 0.03, routeColor, routeColor, true, 80);
                    drawGradientArc(g, 0.5 * (leftBaseX + rightBaseX), heightPercent(0.85), 0.5 * (rightBaseX - leftBaseX) + w * 0.015, w * 0.03, color2, color1, false, 80);
                    g.setColor(routeColor);
                    g.fillRect(leftBaseX - w * 0.015, heightPercent(0.15), w * 0.03, h * 0.7 + 1);
                    g.fillRect(rightBaseX - w * 0.015, heightPercent(0.15), w * 0.03, h * 0.7 + 1);
                    drawGradientRect(
                        g,
                        leftBaseX - w * 0.015,
                        heightPercent(0.85) - Math.abs(gradientEnd - leftStationCount + 1) * rightStep,
                        w * 0.03,
                        Math.abs((gradientEnd - leftStationCount + 1) * rightStep) + 1,
                        routeColor,
                        color2
                    );
                    drawGradientRect(
                        g,
                        rightBaseX - w * 0.015,
                        heightPercent(0.85) - (gradientStart - rightStationCount) * leftStep,
                        w * 0.03,
                        Math.abs((gradientStart - rightStationCount) * leftStep) + 1,
                        color3,
                        color1
                    );
                } else {
                    var part3 = 1;
                    var part2 = (1 - gradientStart / leftStationCount) * 0.7;
                    var part1 = part2 + 0.3;
                    var color1 = getGradientColor(routeColor, part1);
                    var color2 = getGradientColor(routeColor, part2);
                    var color3 = getGradientColor(routeColor, part3);
                    drawGradientArc(g, 0.5 * (leftBaseX + rightBaseX), heightPercent(0.15), 0.5 * (rightBaseX - leftBaseX) + w * 0.015, w * 0.03, color1, color2, true, 80);
                    drawGradientArc(g, 0.5 * (leftBaseX + rightBaseX), heightPercent(0.85), 0.5 * (rightBaseX - leftBaseX) + w * 0.015, w * 0.03, routeColor, routeColor, false, 80);
                    g.setColor(routeColor);
                    g.fillRect(leftBaseX - w * 0.015, heightPercent(0.15), w * 0.03, h * 0.7 + 1);
                    g.fillRect(rightBaseX - w * 0.015, heightPercent(0.15), w * 0.03, h * 0.7 + 1);
                    drawGradientRect(g, leftBaseX - w * 0.015, heightPercent(0.15), w * 0.03, Math.abs(gradientStart * rightStep) + 1, color1, color3);
                    drawGradientRect(g, rightBaseX - w * 0.015, heightPercent(0.15), w * 0.03, Math.abs((leftStationCount - (gradientEnd - rightStationCount) - 1) * leftStep) + 1, color2, routeColor);
                }
            }

            for (var i = 0; i < rightStationCount; i++) {
                var currentY = heightPercent(0.15) + rightStep * i;
                var thisStn = rightStations[i];
                var rightIndex = circularState == CircularState.CLOCKWISE ? drawInfo.index : rightStationCount - (drawInfo.index - leftStationCount) - 1;
                var hasPassed = false;
                var isTransfer = thisStn.transInfo.length > 0;
                if (isTransfer) {
                    g.setClip(
                        new Polygon(
                            [rightBaseX - w * 0.05, rightBaseX - w * 0.04, rightBaseX, rightBaseX, rightBaseX - w * 0.04],
                            [currentY, currentY - w * 0.015, currentY - w * 0.015, currentY + w * 0.015, currentY + w * 0.015],
                            5
                        )
                    );
                    for (var j = 0; j < thisStn.transInfo.length; j++) {
                        var thisY = currentY - w * 0.015 + ((w * 0.03) / thisStn.transInfo.length) * j;
                        var thisTrans = thisStn.transInfo[j];
                        g.setColor(hasPassed ? rgbToColor(124, 124, 124) : thisTrans.routeColor);
                        g.fillRect(widthPercent(0.1), thisY, w * 0.9, (w * 0.03) / thisStn.transInfo.length + 1);
                    }

                    g.setClip(originalClip);
                    g.setColor(hasPassed ? rgbToColor(124, 124, 124) : Color.BLACK);

                    for (var j = 0; j < thisStn.transInfo.length; j++) {
                        var thisTrans = thisStn.transInfo[j];
                        var boxY = currentY - w * 0.03 * thisStn.transInfo.length * 0.5 + w * 0.03 * j;
                        var textWidth = isNumLine(thisTrans.routeName)
                            ? getDLStringWidth(g, fontBold, font, `号线|${getMatching(thisTrans.routeName, false)}`, w * 0.02) +
                              getUnifiedStringWidth(g, fontBold, String(getCJKLineName(getMatching(thisTrans.routeName, true))), w * 0.02)
                            : getDLStringWidth(g, fontBold, font, thisTrans.routeName, w * 0.02);
                        g.setColor(hasPassed ? rgbToColor(124, 124, 124) : thisTrans.routeColor);

                        g.fillRect(rightBaseX - w * 0.07 - textWidth - w * 0.02, boxY, textWidth + w * 0.02 + 1, w * 0.03);
                        g.setColor(hasPassed ? Color.WHITE : isLightColor(thisTrans.routeColor) ? Color.BLACK : Color.WHITE);
                        if (isNumLine(thisTrans.routeName)) {
                            var textX = rightBaseX - w * 0.08;
                            textX -= drawStrDL(g, fontBold, font, `号线|${getMatching(thisTrans.routeName, false)}`, textX, boxY + w * 0.005, w * 0.02, 2, 0);
                            textX -= drawStrUnified(g, fontBold, String(getCJKLineName(getMatching(thisTrans.routeName, true))), textX, boxY + w * 0.02, w * 0.02, 2);
                        } else drawStrDL(g, fontBold, font, thisTrans.routeName, rightBaseX - w * 0.08, boxY + w * 0.005, w * 0.02, 2, 1);
                    }
                }
                if (isTransfer || i == rightIndex) {
                    g.setColor(hasPassed ? rgbToColor(124, 124, 124) : routeColor);
                    g.fillOval(rightBaseX - w * 0.02, currentY - w * 0.02, w * 0.04, w * 0.04);
                    if (i == rightIndex) {
                        g.setColor(Color.RED);
                        g.fillOval(rightBaseX - w * 0.015, currentY - w * 0.015, w * 0.03, w * 0.03);
                        if (circularState == CircularState.CLOCKWISE) g.drawImage(loadResource("img", "fangsu:sign/adb.png"), rightBaseX + w * 0.0225, currentY - w * 0.015, w * 0.03, w * 0.03, null);
                        else g.drawImage(loadResource("img", "fangsu:sign/aub.png"), rightBaseX + w * 0.0225, currentY - w * 0.015, w * 0.03, w * 0.03, null);
                        if (isTransfer) {
                            g.drawImage(loadResource("img", "fangsu:ris/imgtrans.png"), rightBaseX - w * 0.015, currentY - w * 0.015, w * 0.03, w * 0.03, null);
                        }
                    } else {
                        g.setColor(Color.WHITE);
                        g.fillOval(rightBaseX - w * 0.015, currentY - w * 0.015, w * 0.03, w * 0.03);
                        g.drawImage(
                            changeImageColor(loadResource("img", "fangsu:ris/imgtrans.png"), hasPassed ? rgbToColor(124, 124, 124) : routeColor),
                            rightBaseX - w * 0.015,
                            currentY - w * 0.015,
                            w * 0.03,
                            w * 0.03,
                            null
                        );
                    }
                } else {
                    g.setColor(Color.WHITE);
                    g.fillOval(rightBaseX - w * 0.01, currentY - w * 0.01, w * 0.02, w * 0.02);
                }
                g.setColor(hasPassed ? rgbToColor(124, 124, 124) : Color.BLACK);
                if (i == rightIndex) {
                    var textLength = getDLStringWidth(g, fontBold, font, thisStn.stationName, h * 0.035);
                    g.setColor(routeColor);
                    g.fillRoundRect(rightBaseX + w * 0.05, currentY - w * 0.03, textLength + w * 0.02, w * 0.06, w * 0.01, w * 0.01);
                    g.setColor(isLightColor(routeColor) ? Color.BLACK : Color.WHITE);
                }

                drawStrDL(g, fontBold, font, thisStn.stationName, rightBaseX + w * 0.06, currentY - w * 0.03, h * 0.035, 0, 0);
            }
            for (var i = 0; i < leftStationCount; i++) {
                var currentY = heightPercent(0.15) + rightStep * i;
                var thisStn = leftStations[i];
                var leftIndex = circularState == CircularState.CLOCKWISE ? leftStationCount - (drawInfo.index - rightStationCount) - 1 : drawInfo.index;
                var hasPassed = false;
                var isTransfer = thisStn.transInfo.length > 0;
                if (isTransfer) {
                    g.setClip(
                        new Polygon(
                            [leftBaseX + w * 0.05, leftBaseX + w * 0.04, leftBaseX, leftBaseX, leftBaseX + w * 0.04],
                            [currentY, currentY - w * 0.015, currentY - w * 0.015, currentY + w * 0.015, currentY + w * 0.015],
                            5
                        )
                    );
                    for (var j = 0; j < thisStn.transInfo.length; j++) {
                        var thisY = currentY - w * 0.015 + ((w * 0.03) / thisStn.transInfo.length) * j;
                        var thisTrans = thisStn.transInfo[j];
                        g.setColor(hasPassed ? rgbToColor(124, 124, 124) : thisTrans.routeColor);
                        g.fillRect(widthPercent(0.1), thisY, w * 0.9, (w * 0.03) / thisStn.transInfo.length + 1);
                    }

                    g.setClip(originalClip);
                    g.setColor(hasPassed ? rgbToColor(124, 124, 124) : Color.BLACK);

                    for (var j = 0; j < thisStn.transInfo.length; j++) {
                        var thisTrans = thisStn.transInfo[j];
                        var boxY = currentY - w * 0.03 * thisStn.transInfo.length * 0.5 + w * 0.03 * j;
                        var textWidth = isNumLine(thisTrans.routeName)
                            ? getDLStringWidth(g, fontBold, font, `号线|${getMatching(thisTrans.routeName, false)}`, w * 0.02) +
                              getUnifiedStringWidth(g, fontBold, String(getCJKLineName(getMatching(thisTrans.routeName, true))), w * 0.02)
                            : getDLStringWidth(g, fontBold, font, thisTrans.routeName, w * 0.02);
                        g.setColor(hasPassed ? rgbToColor(124, 124, 124) : thisTrans.routeColor);

                        g.fillRect(leftBaseX + w * 0.07, boxY, textWidth + w * 0.02, w * 0.03 + 1);
                        g.setColor(hasPassed ? Color.WHITE : isLightColor(thisTrans.routeColor) ? Color.BLACK : Color.WHITE);
                        if (isNumLine(thisTrans.routeName)) {
                            var textX = leftBaseX + w * 0.08;
                            textX += drawStrUnified(g, fontBold, String(getCJKLineName(getMatching(thisTrans.routeName, true))), textX, boxY + w * 0.02, w * 0.02, 0);
                            textX += drawStrDL(g, fontBold, font, `号线|${getMatching(thisTrans.routeName, false)}`, textX, boxY + w * 0.005, w * 0.02, 0, 0);
                        } else drawStrDL(g, fontBold, font, thisTrans.routeName, leftBaseX + w * 0.08, boxY + w * 0.005, w * 0.02, 0, 1);
                    }
                }
                if (isTransfer || i == leftIndex) {
                    g.setColor(hasPassed ? rgbToColor(124, 124, 124) : routeColor);
                    g.fillOval(leftBaseX - w * 0.02, currentY - w * 0.02, w * 0.04, w * 0.04);
                    if (i == leftIndex) {
                        g.setColor(Color.RED);
                        g.fillOval(leftBaseX - w * 0.015, currentY - w * 0.015, w * 0.03, w * 0.03);
                        if (circularState == CircularState.CLOCKWISE) g.drawImage(loadResource("img", "fangsu:sign/aub.png"), leftBaseX - w * 0.0525, currentY - w * 0.015, w * 0.03, w * 0.03, null);
                        else g.drawImage(loadResource("img", "fangsu:sign/adb.png"), leftBaseX - w * 0.0525, currentY - w * 0.015, w * 0.03, w * 0.03, null);
                        if (isTransfer) {
                            g.drawImage(loadResource("img", "fangsu:ris/imgtrans.png"), leftBaseX - w * 0.015, currentY - w * 0.015, w * 0.03, w * 0.03, null);
                        }
                    } else {
                        g.setColor(Color.WHITE);
                        g.fillOval(leftBaseX - w * 0.015, currentY - w * 0.015, w * 0.03, w * 0.03);
                        g.drawImage(
                            changeImageColor(loadResource("img", "fangsu:ris/imgtrans.png"), hasPassed ? rgbToColor(124, 124, 124) : routeColor),
                            leftBaseX - w * 0.015,
                            currentY - w * 0.015,
                            w * 0.03,
                            w * 0.03,
                            null
                        );
                    }
                } else {
                    g.setColor(Color.WHITE);
                    g.fillOval(leftBaseX - w * 0.01, currentY - w * 0.01, w * 0.02, w * 0.02);
                }
                g.setColor(hasPassed ? rgbToColor(124, 124, 124) : Color.BLACK);
                if (i == leftIndex) {
                    var textLength = getDLStringWidth(g, fontBold, font, thisStn.stationName, h * 0.035);
                    g.setColor(routeColor);
                    g.fillRoundRect(leftBaseX - w * 0.07 - textLength, currentY - w * 0.03, textLength + w * 0.02, w * 0.06, w * 0.01, w * 0.01);
                    g.setColor(isLightColor(routeColor) ? Color.BLACK : Color.WHITE);
                }

                drawStrDL(g, fontBold, font, thisStn.stationName, leftBaseX - w * 0.06, currentY - w * 0.03, h * 0.035, 2, 2);
            }
        }
    }
    function widthPercent(p) {
        return x + w * p;
    }
    function heightPercent(p) {
        return y + h * p;
    }
    function drawGradientRect(g2d, x, y, width, height, topColor, bottomColor) {
        // 创建渐变起点（矩形顶部中点）和终点（矩形底部中点）
        var startPoint = new java.awt.geom.Point2D.Float(x + width / 2, y);
        var endPoint = new java.awt.geom.Point2D.Float(x + width / 2, y + height);

        // 创建线性渐变
        var gradient = new java.awt.GradientPaint(
            startPoint,
            topColor,
            endPoint,
            bottomColor,
            false // 不循环渐变
        );

        // 保存原始画刷状态
        var originalPaint = g2d.getPaint();

        // 应用渐变并绘制矩形
        g2d.setPaint(gradient);
        g2d.fillRect(x, y, width, height);

        // 恢复原始画刷
        g2d.setPaint(originalPaint);
    }

    function drawGradientArc(g, centerX, centerY, outerRadius, width, startColor, endColor, isTop, segments) {
        // var Color = Java.type("java.awt.Color");
        // var Path2D = Java.type("java.awt.geom.Path2D$Double");
        // var Math = Java.type("java.lang.Math");

        segments = segments || 180;
        var innerRadius = outerRadius - width;
        var angleIncrement = 180.0 / segments;

        // 修正：添加屏幕坐标系转换（Y轴向下为正）
        function toScreenCoords(deg) {
            return {
                x: Math.cos((deg * Math.PI) / 180.0),
                y: -Math.sin((deg * Math.PI) / 180.0) // 注意负号，适应屏幕坐标系
            };
        }

        function interpolateColor(color1, color2, t) {
            var r = Math.round(color1.getRed() + t * (color2.getRed() - color1.getRed()));
            var g = Math.round(color1.getGreen() + t * (color2.getGreen() - color1.getGreen()));
            var b = Math.round(color1.getBlue() + t * (color2.getBlue() - color1.getBlue()));
            var a = Math.round(color1.getAlpha() + t * (color2.getAlpha() - color1.getAlpha()));
            return rgbaToColor(r, g, b, a);
        }

        // 修正：反转isTop逻辑
        var startAngle = isTop ? 180 : 0; // 上半弧从180°开始，下半弧从0°开始
        var endAngle = isTop ? 360 : 180; // 上半弧到360°(0°)结束，下半弧到180°结束
        var direction = isTop ? -1 : -1; // 方向：上半弧逆时针，下半弧顺时针

        // 修正：使用正确的角度计算
        for (var i = 0; i < segments; i++) {
            var progress = i / segments;
            var angle1 = startAngle + direction * i * angleIncrement;
            var angle2 = startAngle + direction * (i + 1) * angleIncrement;

            var t = isTop ? progress : 1 - progress;
            var segmentColor = interpolateColor(startColor, endColor, t);
            g.setColor(segmentColor);

            var path = new java.awt.geom.Path2D$Double();

            function getPoint(angle, radius) {
                var coords = toScreenCoords(angle);
                return [centerX + radius * coords.x, centerY + radius * coords.y];
            }

            var innerStart = getPoint(angle1, innerRadius);
            var outerStart = getPoint(angle1, outerRadius);
            var outerEnd = getPoint(angle2, outerRadius);
            var innerEnd = getPoint(angle2, innerRadius);

            path.moveTo(innerStart[0], innerStart[1]);
            path.lineTo(outerStart[0], outerStart[1]);
            path.lineTo(outerEnd[0], outerEnd[1]);
            path.lineTo(innerEnd[0], innerEnd[1]);
            path.closePath();

            g.fill(path);
        }
    }
    function getGradientColor(startColor, t) {
        // 确保比例 t 在 [0,1] 范围内
        t = Math.max(0, Math.min(1, t));

        // 提取起始颜色的 RGB 分量
        var r = startColor.getRed();
        var g = startColor.getGreen();
        var b = startColor.getBlue();

        // 计算目标颜色
        var targetR = (r + 2 * 255) / 3;
        var targetG = (g + 2 * 255) / 3;
        var targetB = (b + 2 * 255) / 3;

        // 线性插值计算当前颜色
        var resultR = r + t * (targetR - r);
        var resultG = g + t * (targetG - g);
        var resultB = b + t * (targetB - b);

        // 四舍五入取整并确保在 [0,255] 范围内
        var finalR = Math.round(Math.max(0, Math.min(255, resultR)));
        var finalG = Math.round(Math.max(0, Math.min(255, resultG)));
        var finalB = Math.round(Math.max(0, Math.min(255, resultB)));

        // 创建并返回新的 Color 对象
        return rgbToColor(finalR, finalG, finalB);
    }
}
