/*
 * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include <cmath>                /* for cos(), sin(), etc */

#include "jni_util.h"
#include "awt.h"
#include "sun_java2d_windows_GDIRenderer.h"
#include "java_awt_geom_PathIterator.h"

#include "GDIWindowSurfaceData.h"
#include "awt_Component.h"
#include "awt_Pen.h"
#include "awt_Brush.h"

#include "GraphicsPrimitiveMgr.h"

#define MAX_CLAMP_BND (1<<26)
#define MIN_CLAMP_BND (-MAX_CLAMP_BND)

#define CLAMP(x) (((x) > MAX_CLAMP_BND) ?   \
    MAX_CLAMP_BND : ((x) < MIN_CLAMP_BND) ? \
        MIN_CLAMP_BND : (x))


extern "C" {

#define POLYTEMPSIZE    (512 / sizeof(POINT))

static void AngleToCoord(int angle, int w, int h, int *x, int *y)
{
    const double pi = 3.1415926535;
    const double toRadians = 2 * pi / 360;

    *x = (long)(cos((double)angle * toRadians) * w);
    *y = -(long)(sin((double)angle * toRadians) * h);
}

static POINT *TransformPoly(jint *xpoints, jint *ypoints,
                            jint transx, jint transy,
                            POINT *pPoints, jint *pNpoints,
                            BOOL close, BOOL fixend)
{
    int npoints = *pNpoints;
    int outpoints = npoints;
    jint x, y;

    // Fix for 4298688 - draw(Line) and Polygon omit last pixel
    // We will need to add a point if we need to close it off or
    // if we need to fix the endpoint to accommodate the Windows
    // habit of never drawing the last pixel of a Polyline.  Note
    // that if the polyline is already closed then neither fix
    // is needed because the last pixel is also the first pixel
    // and so will be drawn just fine.
    // Clarification for 4298688 - regression bug 4678208 points
    // out that we still need to fix the endpoint if the closed
    // polygon never went anywhere (all vertices on same coordinate).
    jint mx = xpoints[0];
    jint my = ypoints[0];
    BOOL isclosed = (xpoints[npoints-1] == mx && ypoints[npoints-1] == my);
    if ((close && !isclosed) || fixend) {
        outpoints++;
        *pNpoints = outpoints;
    }
    if (outpoints > POLYTEMPSIZE) {
        try {
            pPoints = (POINT *) SAFE_SIZE_ARRAY_ALLOC(safe_Malloc, sizeof(POINT), outpoints);
        } catch (const std::bad_alloc&) {
            return NULL;
        }
    }
    BOOL isempty = fixend;
    for (int i = 0; i < npoints; i++) {
        x = xpoints[i];
        y = ypoints[i];
        isempty = isempty && (x == mx && y == my);
        pPoints[i].x = CLAMP(x + transx);
        pPoints[i].y = CLAMP(y + transy);
    }
    if (close && !isclosed) {
        pPoints[npoints] = pPoints[0];
    } else if (fixend) {
        if (!close || isempty) {
            // Fix for 4298688 - draw(Line) and Polygon omit last pixel
            // Fix up the last segment by adding another segment after
            // it that is only 1 pixel long.  The first pixel of that
            // segment will be drawn, but the second pixel is the one
            // that Windows omits.
            pPoints[npoints] = pPoints[npoints-1];
            pPoints[npoints].x++;
        } else {
            outpoints--;
            *pNpoints = outpoints;
        }
    }

    return pPoints;
}

/*
 * Class:     sun_java2d_windows_GDIRenderer
 * Method:    doDrawLine
 * Signature: (Lsun/java2d/windows/GDIWindowSurfaceData;Lsun/java2d/pipe/Region;Ljava/awt/Composite;IIIII)V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIRenderer_doDrawLine
    (JNIEnv *env, jobject wr,
     jobject sData,
     jobject clip, jobject comp, jint color,
     jint x1, jint y1, jint x2, jint y2)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIRenderer_doDrawLine");
    J2dTraceLn(J2D_TRACE_VERBOSE,
               "  color=0x%x x1=%-4d y1=%-4d x2=%-4d y2=%-4d",
               color, x1, y1, x2, y2);
    GDIWinSDOps *wsdo = GDIWindowSurfaceData_GetOps(env, sData);
    if (wsdo == NULL) {
        return;
    }

    HDC hdc;
    jint patrop;
    if (x1 == x2 || y1 == y2) {
        if (x1 > x2) {
            jint t = x1; x1 = x2; x2 = t;
        }
        if (y1 > y2) {
            jint t = y1; y1 = y2; y2 = t;
        }
        hdc = wsdo->GetDC(env, wsdo, BRUSH, &patrop, clip, comp, color);
        if (hdc == NULL) {
            return;
        }
        ::PatBlt(hdc, x1, y1, x2-x1+1, y2-y1+1, patrop);
    } else {
        hdc = wsdo->GetDC(env, wsdo, PENBRUSH, &patrop, clip, comp, color);
        if (hdc == NULL) {
            return;
        }
        ::MoveToEx(hdc, x1, y1, NULL);
        ::LineTo(hdc, x2, y2);
        ::PatBlt(hdc, x2, y2, 1, 1, patrop);
    }
    wsdo->ReleaseDC(env, wsdo, hdc);
}

/*
 * Class:     sun_java2d_windows_GDIRenderer
 * Method:    doDrawRect
 * Signature: (Lsun/java2d/windows/GDIWindowSurfaceData;Lsun/java2d/pipe/Region;Ljava/awt/Composite;IIIII)V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIRenderer_doDrawRect
    (JNIEnv *env, jobject wr,
     jobject sData,
     jobject clip, jobject comp, jint color,
     jint x, jint y, jint w, jint h)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIRenderer_doDrawRect");
    J2dTraceLn(J2D_TRACE_VERBOSE,
               "  color=0x%x x=%-4d y=%-4d w=%-4d h=%-4d",
               color, x, y, w, h);
    if (w < 0 || h < 0) {
        return;
    }

    GDIWinSDOps *wsdo = GDIWindowSurfaceData_GetOps(env, sData);
    if (wsdo == NULL) {
        return;
    }
    jint patrop;
    HDC hdc = wsdo->GetDC(env, wsdo, BRUSH, &patrop, clip, comp, color);
    if (hdc == NULL) {
        return;
    }
    if (w < 2 || h < 2) {
        // If one dimension is less than 2 then there is no
        // gap in the middle - draw a solid filled rectangle.
        ::PatBlt(hdc, x, y, w+1, h+1, patrop);
    } else {
        // Avoid drawing the endpoints twice.
        // Also prefer including the endpoints in the
        // horizontal sections which draw pixels faster.
        ::PatBlt(hdc,  x,   y,  w+1,  1,  patrop);
        ::PatBlt(hdc,  x,  y+1,  1,  h-1, patrop);
        ::PatBlt(hdc, x+w, y+1,  1,  h-1, patrop);
        ::PatBlt(hdc,  x,  y+h, w+1,  1,  patrop);
    }
    wsdo->ReleaseDC(env, wsdo, hdc);
}

/*
 * Class:     sun_java2d_windows_GDIRenderer
 * Method:    doDrawRoundRect
 * Signature: (Lsun/java2d/windows/GDIWindowSurfaceData;Lsun/java2d/pipe/Region;Ljava/awt/Composite;IIIIIII)V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIRenderer_doDrawRoundRect
    (JNIEnv *env, jobject wr,
     jobject sData,
     jobject clip, jobject comp, jint color,
     jint x, jint y, jint w, jint h, jint arcW, jint arcH)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIRenderer_doDrawRoundRect");
    J2dTraceLn(J2D_TRACE_VERBOSE,
               "  color=0x%x x=%-4d y=%-4d w=%-4d h=%-4d",
               color, x, y, w, h);
    J2dTraceLn(J2D_TRACE_VERBOSE, "  arcW=%-4d arcH=%-4d",
               arcW, arcH);
    if (w < 2 || h < 2 || arcW <= 0 || arcH <= 0) {
        // Fix for 4524760 - drawRoundRect0 test case fails on Windows 98
        // Thin round rects degenerate into regular rectangles
        // because there is no room for the arc sections.  Also
        // if there is no arc dimension then the roundrect must
        // be a simple rectangle.  Defer to the DrawRect function
        // which handles degenerate sizes better.
        Java_sun_java2d_windows_GDIRenderer_doDrawRect(env, wr,
                                                       sData, clip,
                                                       comp, color,
                                                       x, y, w, h);
        return;
    }

    GDIWinSDOps *wsdo = GDIWindowSurfaceData_GetOps(env, sData);
    if (wsdo == NULL) {
        return;
    }
    HDC hdc = wsdo->GetDC(env, wsdo, PENONLY, NULL, clip, comp, color);
    if (hdc == NULL) {
        return;
    }
    ::RoundRect(hdc, x, y, x+w+1, y+h+1, arcW, arcH);
    wsdo->ReleaseDC(env, wsdo, hdc);
}

/*
 * Class:     sun_java2d_windows_GDIRenderer
 * Method:    doDrawOval
 * Signature: (Lsun/java2d/windows/GDIWindowSurfaceData;Lsun/java2d/pipe/Region;Ljava/awt/Composite;IIIII)V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIRenderer_doDrawOval
    (JNIEnv *env, jobject wr,
     jobject sData,
     jobject clip, jobject comp, jint color,
     jint x, jint y, jint w, jint h)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIRenderer_doDrawOval");
    J2dTraceLn(J2D_TRACE_VERBOSE,
               "  color=0x%x x=%-4d y=%-4d w=%-4d h=%-4d",
               color, x, y, w, h);
    if (w < 2 || h < 2) {
        // Thin enough ovals have no room for curvature.  Defer to
        // the DrawRect method which handles degenerate sizes better.
        Java_sun_java2d_windows_GDIRenderer_doDrawRect(env, wr,
                                                       sData, clip,
                                                       comp, color,
                                                       x, y, w, h);
        return;
    }

    GDIWinSDOps *wsdo = GDIWindowSurfaceData_GetOps(env, sData);
    if (wsdo == NULL) {
        return;
    }
    HDC hdc = wsdo->GetDC(env, wsdo, PENONLY, NULL, clip, comp, color);
    if (hdc == NULL) {
        return;
    }
    ::Ellipse(hdc, x, y, x+w+1, y+h+1);
    wsdo->ReleaseDC(env, wsdo, hdc);
}

/*
 * Class:     sun_java2d_windows_GDIRenderer
 * Method:    doDrawArc
 * Signature: (Lsun/java2d/windows/GDIWindowSurfaceData;Lsun/java2d/pipe/Region;Ljava/awt/Composite;IIIIIII)V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIRenderer_doDrawArc
    (JNIEnv *env, jobject wr,
     jobject sData,
     jobject clip, jobject comp, jint color,
     jint x, jint y, jint w, jint h,
     jint angleStart, jint angleExtent)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIRenderer_doDrawArc");
    J2dTraceLn(J2D_TRACE_VERBOSE,
               "  color=0x%x x=%-4d y=%-4d w=%-4d h=%-4d",
               color, x, y, w, h);
    J2dTraceLn(J2D_TRACE_VERBOSE,
               "  angleStart=%-4d angleExtent=%-4d",
               angleStart, angleExtent);
    if (w < 0 || h < 0 || angleExtent == 0) {
        return;
    }

    GDIWinSDOps *wsdo = GDIWindowSurfaceData_GetOps(env, sData);
    if (wsdo == NULL) {
        return;
    }

    int sx, sy, ex, ey;
    if (angleExtent >= 360 || angleExtent <= -360) {
        sx = ex = x + w;
        sy = ey = y + h/2;
    } else {
        int angleEnd;
        if (angleExtent < 0) {
            angleEnd = angleStart;
            angleStart += angleExtent;
        } else {
            angleEnd = angleStart + angleExtent;
        }
        AngleToCoord(angleStart, w, h, &sx, &sy);
        sx += x + w/2;
        sy += y + h/2;
        AngleToCoord(angleEnd, w, h, &ex, &ey);
        ex += x + w/2;
        ey += y + h/2;
    }
    HDC hdc = wsdo->GetDC(env, wsdo, PEN, NULL, clip, comp, color);
    if (hdc == NULL) {
        return;
    }
    ::Arc(hdc, x, y, x+w+1, y+h+1, sx, sy, ex, ey);
    wsdo->ReleaseDC(env, wsdo, hdc);
}

/*
 * Class:     sun_java2d_windows_GDIRenderer
 * Method:    doDrawPoly
 * Signature: (Lsun/java2d/windows/GDIWindowSurfaceData;Lsun/java2d/pipe/Region;Ljava/awt/Composite;III[I[IIZ)V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIRenderer_doDrawPoly
    (JNIEnv *env, jobject wr,
     jobject sData,
     jobject clip, jobject comp, jint color,
     jint transx, jint transy,
     jintArray xpointsarray, jintArray ypointsarray,
     jint npoints, jboolean isclosed)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIRenderer_doDrawPoly");
    J2dTraceLn(J2D_TRACE_VERBOSE,
               "  color=0x%x transx=%-4d transy=%-4d "\
               "npoints=%-4d isclosed=%-4d",
               color, transx, transy, npoints, isclosed);
    if (JNU_IsNull(env, xpointsarray) || JNU_IsNull(env, ypointsarray)) {
        JNU_ThrowNullPointerException(env, "coordinate array");
        return;
    }
    if (env->GetArrayLength(xpointsarray) < npoints ||
        env->GetArrayLength(ypointsarray) < npoints)
    {
        JNU_ThrowArrayIndexOutOfBoundsException(env, "coordinate array");
        return;
    }
    if (npoints < 2) {
        // Fix for 4067534 - assertion failure in 1.3.1 for degenerate polys
        // Not enough points for a line.
        // Note that this would be ignored later anyway, but returning
        // here saves us from mistakes in TransformPoly and seeing bad
        // return values from the Windows Polyline function.
        return;
    }

    GDIWinSDOps *wsdo = GDIWindowSurfaceData_GetOps(env, sData);
    if (wsdo == NULL) {
        return;
    }

    POINT tmpPts[POLYTEMPSIZE], *pPoints = NULL;

    jint *xpoints = (jint *) env->GetPrimitiveArrayCritical(xpointsarray, NULL);

    if (xpoints != NULL) {
        jint *ypoints = (jint *) env->GetPrimitiveArrayCritical(ypointsarray, NULL);
        if (ypoints != NULL) {
            pPoints = TransformPoly(xpoints, ypoints, transx, transy,
                                    tmpPts, &npoints, isclosed, TRUE);
            env->ReleasePrimitiveArrayCritical(ypointsarray, ypoints, JNI_ABORT);
        }
        env->ReleasePrimitiveArrayCritical(xpointsarray, xpoints, JNI_ABORT);
    }

    if (pPoints == NULL) {
        return;
    }

    HDC hdc = wsdo->GetDC(env, wsdo, PEN, NULL, clip, comp, color);
    if (hdc == NULL) {
        return;
    }
    ::Polyline(hdc, pPoints, npoints);
    wsdo->ReleaseDC(env, wsdo, hdc);
    if (pPoints != tmpPts) {
        free(pPoints);
    }
}

/*
 * Class:     sun_java2d_windows_GDIRenderer
 * Method:    doFillRect
 * Signature: (Lsun/java2d/windows/GDIWindowSurfaceData;Lsun/java2d/pipe/Region;Ljava/awt/Composite;IIIII)V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIRenderer_doFillRect
    (JNIEnv *env, jobject wr,
     jobject sData,
     jobject clip, jobject comp, jint color,
     jint x, jint y, jint w, jint h)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIRenderer_doFillRect");
    J2dTraceLn(J2D_TRACE_VERBOSE,
               "  color=0x%x x=%-4d y=%-4d w=%-4d h=%-4d",
               color, x, y, w, h);
    if (w <= 0 || h <= 0) {
        return;
    }

    GDIWinSDOps *wsdo = GDIWindowSurfaceData_GetOps(env, sData);
    if (wsdo == NULL) {
        return;
    }
    jint patrop;
    HDC hdc = wsdo->GetDC(env, wsdo, BRUSH, &patrop, clip, comp, color);
    if (hdc == NULL) {
        return;
    }
    ::PatBlt(hdc, x, y, w, h, patrop);
    wsdo->ReleaseDC(env, wsdo, hdc);
}

/*
 * Class:     sun_java2d_windows_GDIRenderer
 * Method:    doFillRoundRect
 * Signature: (Lsun/java2d/windows/GDIWindowSurfaceData;Lsun/java2d/pipe/Region;Ljava/awt/Composite;IIIIIII)V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIRenderer_doFillRoundRect
    (JNIEnv *env, jobject wr,
     jobject sData,
     jobject clip, jobject comp, jint color,
     jint x, jint y, jint w, jint h, jint arcW, jint arcH)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIRenderer_doFillRoundRect");
    J2dTraceLn(J2D_TRACE_VERBOSE,
               "  color=0x%x x=%-4d y=%-4d w=%-4d h=%-4d",
               color, x, y, w, h);
    J2dTraceLn(J2D_TRACE_VERBOSE, "  arcW=%-4d arcH=%-4d",
               arcW, arcH);
    if (w < 2 || h < 2 || arcW <= 0 || arcH <= 0) {
        // Fix related to 4524760 - drawRoundRect0 fails on Windows 98
        // Thin round rects have no room for curvature.  Also, if
        // the curvature is empty then the primitive has degenerated
        // into a simple rectangle.  Defer to the FillRect method
        // which deals with degenerate sizes better.
        Java_sun_java2d_windows_GDIRenderer_doFillRect(env, wr,
                                                       sData, clip,
                                                       comp, color,
                                                       x, y, w, h);
        return;
    }

    GDIWinSDOps *wsdo = GDIWindowSurfaceData_GetOps(env, sData);
    if (wsdo == NULL) {
        return;
    }
    HDC hdc = wsdo->GetDC(env, wsdo, BRUSHONLY, NULL, clip, comp, color);
    if (hdc == NULL) {
        return;
    }
    ::RoundRect(hdc, x, y, x+w+1, y+h+1, arcW, arcH);
    wsdo->ReleaseDC(env, wsdo, hdc);
}

/*
 * Class:     sun_java2d_windows_GDIRenderer
 * Method:    doFillOval
 * Signature: (Lsun/java2d/windows/GDIWindowSurfaceData;Lsun/java2d/pipe/Region;Ljava/awt/Composite;IIIII)V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIRenderer_doFillOval
    (JNIEnv *env, jobject wr,
     jobject sData,
     jobject clip, jobject comp, jint color,
     jint x, jint y, jint w, jint h)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIRenderer_doFillOval");
    J2dTraceLn(J2D_TRACE_VERBOSE,
               "  color=0x%x x=%-4d y=%-4d w=%-4d h=%-4d",
               color, x, y, w, h);
    if (w < 3 || h < 3) {
        // Fix for 4411814 - small ovals do not draw anything
        // (related to 4205762 on Solaris platform)
        // Most platform graphics packages have poor rendering
        // for thin ellipses and the rendering is most strikingly
        // different from our theoretical arcs.  Ideally we should
        // trap all ovals less than some fairly large size and
        // try to draw aesthetically pleasing ellipses, but that
        // would require considerably more work to get the corresponding
        // drawArc variants to match pixel for pixel.
        // Thin ovals of girth 1 pixel are simple rectangles.
        // Thin ovals of girth 2 pixels are simple rectangles with
        // potentially smaller lengths.  Determine the correct length
        // by calculating .5*.5 + scaledlen*scaledlen == 1.0 which
        // means that scaledlen is the sqrt(0.75).  Scaledlen is
        // relative to the true length (w or h) and needs to be
        // adjusted by half a pixel in different ways for odd or
        // even lengths.
#define SQRT_3_4 0.86602540378443864676
        if (w > 2 && h > 1) {
            int adjw = (int) ((SQRT_3_4 * w - ((w&1)-1)) * 0.5);
            adjw = adjw * 2 + (w&1);
            x += (w-adjw)/2;
            w = adjw;
        } else if (h > 2 && w > 1) {
            int adjh = (int) ((SQRT_3_4 * h - ((h&1)-1)) * 0.5);
            adjh = adjh * 2 + (h&1);
            y += (h-adjh)/2;
            h = adjh;
        }
#undef SQRT_3_4
        if (w > 0 && h > 0) {
            Java_sun_java2d_windows_GDIRenderer_doFillRect(env, wr, sData,
                                                           clip, comp, color,
                                                           x, y, w, h);
        }
        return;
    }

    GDIWinSDOps *wsdo = GDIWindowSurfaceData_GetOps(env, sData);
    if (wsdo == NULL) {
        return;
    }
    HDC hdc = wsdo->GetDC(env, wsdo, BRUSHONLY, NULL, clip, comp, color);
    if (hdc == NULL) {
        return;
    }
    ::Ellipse(hdc, x, y, x+w+1, y+h+1);
    wsdo->ReleaseDC(env, wsdo, hdc);
}

/*
 * Class:     sun_java2d_windows_GDIRenderer
 * Method:    doFillArc
 * Signature: (Lsun/java2d/windows/GDIWindowSurfaceData;Lsun/java2d/pipe/Region;Ljava/awt/Composite;IIIIIII)V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIRenderer_doFillArc
    (JNIEnv *env, jobject wr,
     jobject sData,
     jobject clip, jobject comp, jint color,
     jint x, jint y, jint w, jint h,
     jint angleStart, jint angleExtent)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIRenderer_doFillArc");
    J2dTraceLn(J2D_TRACE_VERBOSE,
               "  color=0x%x x=%-4d y=%-4d w=%-4d h=%-4d",
               color, x, y, w, h);
    J2dTraceLn(J2D_TRACE_VERBOSE,
               "  angleStart=%-4d angleExtent=%-4d",
               angleStart, angleExtent);
    if (w <= 0 || h <= 0 || angleExtent == 0) {
        return;
    }
    if (angleExtent >= 360 || angleExtent <= -360) {
        // Fix related to 4411814 - small ovals (and arcs) do not draw
        // If the arc is a full circle, let the Oval method handle it
        // since that method can deal with degenerate sizes better.
        Java_sun_java2d_windows_GDIRenderer_doFillOval(env, wr,
                                                       sData, clip,
                                                       comp, color,
                                                       x, y, w, h);
        return;
    }

    GDIWinSDOps *wsdo = GDIWindowSurfaceData_GetOps(env, sData);
    if (wsdo == NULL) {
        return;
    }
    int sx, sy, ex, ey;
    int angleEnd;
    if (angleExtent < 0) {
        angleEnd = angleStart;
        angleStart += angleExtent;
    } else {
        angleEnd = angleStart + angleExtent;
    }
    AngleToCoord(angleStart, w, h, &sx, &sy);
    sx += x + w/2;
    sy += y + h/2;
    AngleToCoord(angleEnd, w, h, &ex, &ey);
    ex += x + w/2;
    ey += y + h/2;
    HDC hdc = wsdo->GetDC(env, wsdo, BRUSHONLY, NULL, clip, comp, color);
    if (hdc == NULL) {
        return;
    }
    ::Pie(hdc, x, y, x+w+1, y+h+1, sx, sy, ex, ey);
    wsdo->ReleaseDC(env, wsdo, hdc);
}

/*
 * Class:     sun_java2d_windows_GDIRenderer
 * Method:    doFillPoly
 * Signature: (Lsun/java2d/windows/GDIWindowSurfaceData;Lsun/java2d/pipe/Region;Ljava/awt/Composite;III[I[II)V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIRenderer_doFillPoly
    (JNIEnv *env, jobject wr,
     jobject sData,
     jobject clip, jobject comp, jint color,
     jint transx, jint transy,
     jintArray xpointsarray, jintArray ypointsarray,
     jint npoints)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIRenderer_doFillPoly");
    J2dTraceLn(J2D_TRACE_VERBOSE,
               "  color=0x%x transx=%-4d transy=%-4d npoints=%-4d",
               color, transx, transy, npoints);
    if (JNU_IsNull(env, xpointsarray) || JNU_IsNull(env, ypointsarray)) {
        JNU_ThrowNullPointerException(env, "coordinate array");
        return;
    }
    if (env->GetArrayLength(xpointsarray) < npoints ||
        env->GetArrayLength(ypointsarray) < npoints)
    {
        JNU_ThrowArrayIndexOutOfBoundsException(env, "coordinate array");
        return;
    }
    if (npoints < 3) {
        // Fix for 4067534 - assertion failure in 1.3.1 for degenerate polys
        // Not enough points for a triangle.
        // Note that this would be ignored later anyway, but returning
        // here saves us from mistakes in TransformPoly and seeing bad
        // return values from the Windows Polyline function.
        return;
    }

    GDIWinSDOps *wsdo = GDIWindowSurfaceData_GetOps(env, sData);
    if (wsdo == NULL) {
        return;
    }

    POINT tmpPts[POLYTEMPSIZE], *pPoints = NULL;

    jint *xpoints = (jint *) env->GetPrimitiveArrayCritical(xpointsarray, NULL);
    if (xpoints != NULL) {
        jint *ypoints = (jint *) env->GetPrimitiveArrayCritical(ypointsarray, NULL);
        if (ypoints != NULL) {
            pPoints = TransformPoly(xpoints, ypoints, transx, transy,
                                tmpPts, &npoints, FALSE, FALSE);
            env->ReleasePrimitiveArrayCritical(ypointsarray, ypoints, JNI_ABORT);
        }
        env->ReleasePrimitiveArrayCritical(xpointsarray, xpoints, JNI_ABORT);
    }

    if (pPoints == NULL) {
        return;
    }

    HDC hdc = wsdo->GetDC(env, wsdo, BRUSHONLY, NULL, clip, comp, color);
    if (hdc == NULL) {
        return;
    }
    ::SetPolyFillMode(hdc, ALTERNATE);
    ::Polygon(hdc, pPoints, npoints);
    wsdo->ReleaseDC(env, wsdo, hdc);
    if (pPoints != tmpPts) {
        free(pPoints);
    }
}

/*
 * Class:     sun_java2d_windows_GDIRenderer
 * Method:    doShape
 * Signature:  (Lsun/java2d/windows/GDIWindowSurfaceData;Lsun/java2d/pipe/Region;
 *              Ljava/awt/Composite;IIILjava/awt/geom/Path2D.Float;Z)V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIRenderer_doShape
    (JNIEnv *env, jobject wr,
     jobject sData,
     jobject clip, jobject comp, jint color,
     jint transX, jint transY,
     jobject p2df, jboolean isfill)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIRenderer_doShape");
    J2dTraceLn(J2D_TRACE_VERBOSE,
               "  color=0x%x transx=%-4d transy=%-4d isfill=%-4d",
               color, transX, transY, isfill);
    GDIWinSDOps *wsdo = GDIWindowSurfaceData_GetOps(env, sData);
    if (wsdo == NULL) {
        return;
    }

    jarray typesarray = (jarray) env->GetObjectField(p2df, path2DTypesID);
    jarray coordsarray = (jarray) env->GetObjectField(p2df,
                                                      path2DFloatCoordsID);
    if (coordsarray == NULL) {
        JNU_ThrowNullPointerException(env, "coordinates array");
        return;
    }
    jint numtypes = env->GetIntField(p2df, path2DNumTypesID);
    if (env->GetArrayLength(typesarray) < numtypes) {
        JNU_ThrowArrayIndexOutOfBoundsException(env, "types array");
        return;
    }
    jint maxcoords = env->GetArrayLength(coordsarray);
    jint rule = env->GetIntField(p2df, path2DWindingRuleID);

    HDC hdc = wsdo->GetDC(env, wsdo, (isfill ? BRUSH : PEN), NULL,
                          clip, comp, color);
    if (hdc == NULL) {
        return;
    }

    jbyte *types = (jbyte *) env->GetPrimitiveArrayCritical(typesarray,
                                                            NULL);
    if (types == NULL) {
        wsdo->ReleaseDC(env, wsdo, hdc);
        return;
    }

    jfloat *coords = (jfloat *) env->GetPrimitiveArrayCritical(coordsarray,
                                                               NULL);
    if (coords == NULL) {
        env->ReleasePrimitiveArrayCritical(typesarray, types, JNI_ABORT);
        wsdo->ReleaseDC(env, wsdo, hdc);
        return;
    }

    ::SetPolyFillMode(hdc, (rule == java_awt_geom_PathIterator_WIND_NON_ZERO
                            ? WINDING : ALTERNATE));
    ::BeginPath(hdc);

    int index = 0;
    BOOL ok = TRUE;
    BOOL isempty = TRUE;
    BOOL isapoint = TRUE;
    int mx = 0, my = 0, x1 = 0, y1 = 0;
    POINT ctrlpts[3];
    for (int i = 0; ok && i < numtypes; i++) {
        switch (types[i]) {
        case java_awt_geom_PathIterator_SEG_MOVETO:
            if (!isfill && !isempty) {
                // Fix for 4298688 - draw(Line) omits last pixel
                // Windows omits the last pixel of a path when stroking.
                // Fix up the last segment of the previous subpath by
                // adding another segment after it that is only 1 pixel
                // long.  The first pixel of that segment will be drawn,
                // but the second pixel is the one that Windows omits.
                ::LineTo(hdc, x1+1, y1);
            }
            if (index + 2 <= maxcoords) {
                mx = x1 = transX + (int) floor(coords[index++]);
                my = y1 = transY + (int) floor(coords[index++]);
                ::MoveToEx(hdc, x1, y1, NULL);
                isempty = TRUE;
                isapoint = TRUE;
            } else {
                ok = FALSE;
            }
            break;
        case java_awt_geom_PathIterator_SEG_LINETO:
            if (index + 2 <= maxcoords) {
                x1 = transX + (int) floor(coords[index++]);
                y1 = transY + (int) floor(coords[index++]);
                ::LineTo(hdc, x1, y1);
                isapoint = isapoint && (x1 == mx && y1 == my);
                isempty = FALSE;
            } else {
                ok = FALSE;
            }
            break;
        case java_awt_geom_PathIterator_SEG_QUADTO:
            if (index + 4 <= maxcoords) {
                ctrlpts[0].x = transX + (int) floor(coords[index++]);
                ctrlpts[0].y = transY + (int) floor(coords[index++]);
                ctrlpts[2].x = transX + (int) floor(coords[index++]);
                ctrlpts[2].y = transY + (int) floor(coords[index++]);
                ctrlpts[1].x = (ctrlpts[0].x * 2 + ctrlpts[2].x) / 3;
                ctrlpts[1].y = (ctrlpts[0].y * 2 + ctrlpts[2].y) / 3;
                ctrlpts[0].x = (ctrlpts[0].x * 2 + x1) / 3;
                ctrlpts[0].y = (ctrlpts[0].y * 2 + y1) / 3;
                x1 = ctrlpts[2].x;
                y1 = ctrlpts[2].y;
                ::PolyBezierTo(hdc, ctrlpts, 3);
                isapoint = isapoint && (x1 == mx && y1 == my);
                isempty = FALSE;
            } else {
                ok = FALSE;
            }
            break;
        case java_awt_geom_PathIterator_SEG_CUBICTO:
            if (index + 6 <= maxcoords) {
                ctrlpts[0].x = transX + (int) floor(coords[index++]);
                ctrlpts[0].y = transY + (int) floor(coords[index++]);
                ctrlpts[1].x = transX + (int) floor(coords[index++]);
                ctrlpts[1].y = transY + (int) floor(coords[index++]);
                ctrlpts[2].x = transX + (int) floor(coords[index++]);
                ctrlpts[2].y = transY + (int) floor(coords[index++]);
                x1 = ctrlpts[2].x;
                y1 = ctrlpts[2].y;
                ::PolyBezierTo(hdc, ctrlpts, 3);
                isapoint = isapoint && (x1 == mx && y1 == my);
                isempty = FALSE;
            } else {
                ok = FALSE;
            }
            break;
        case java_awt_geom_PathIterator_SEG_CLOSE:
            ::CloseFigure(hdc);
            if (x1 != mx || y1 != my) {
                x1 = mx;
                y1 = my;
                ::MoveToEx(hdc, x1, y1, NULL);
                isempty = TRUE;
                isapoint = TRUE;
            } else if (!isfill && !isempty && isapoint) {
                ::LineTo(hdc, x1+1, y1);
                ::MoveToEx(hdc, x1, y1, NULL);
                isempty = TRUE;
                isapoint = TRUE;
            }
            break;
        }
    }
    env->ReleasePrimitiveArrayCritical(typesarray, types, JNI_ABORT);
    env->ReleasePrimitiveArrayCritical(coordsarray, coords, JNI_ABORT);
    if (ok) {
        if (!isfill && !isempty) {
            // Fix for 4298688 - draw(Line) omits last pixel
            // Windows omits the last pixel of a path when stroking.
            // Fix up the last segment of the previous subpath by
            // adding another segment after it that is only 1 pixel
            // long.  The first pixel of that segment will be drawn,
            // but the second pixel is the one that Windows omits.
            ::LineTo(hdc, x1+1, y1);
        }
        ::EndPath(hdc);
        if (isfill) {
            ::FillPath(hdc);
        } else {
            ::StrokePath(hdc);
        }
    } else {
        ::AbortPath(hdc);
        JNU_ThrowArrayIndexOutOfBoundsException(env, "coords array");
    }
    wsdo->ReleaseDC(env, wsdo, hdc);
}

} /* extern "C" */

/*
 * Class:     sun_java2d_windows_GDIRenderer
 * Method:    devCopyArea
 * Signature: (Lsun/java2d/windows/GDIWindowSurfaceData;IIIIII)V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIRenderer_devCopyArea
    (JNIEnv *env, jobject wr,
     jobject wsd,
     jint srcx, jint srcy,
     jint dx, jint dy,
     jint width, jint height)
{
    GDIWinSDOps *wsdo = GDIWindowSurfaceData_GetOps(env, wsd);
    J2dTraceLn(J2D_TRACE_INFO, "GDIWindowSurfaceData_devCopyArea");
    J2dTraceLn(J2D_TRACE_VERBOSE, "   srcx=%-4d srcy=%-4d dx=%-4d dy=%-4d",
               srcx, srcy, dx, dy);
    J2dTraceLn(J2D_TRACE_VERBOSE, "     w=%-4d h=%-4d", width, height);
    if (wsdo == NULL) {
        return;
    }
    if (wsdo->invalid) {
        SurfaceData_ThrowInvalidPipeException(env,
            "GDIRenderer_devCopyArea: invalid surface data");
        return;
    }

    HDC hDC = wsdo->GetDC(env, wsdo, 0, NULL, NULL, NULL, 0);
    if (hDC == NULL) {
        return;
    }

    RECT r;
    ::SetRect(&r, srcx, srcy, srcx + width, srcy + height);
    HRGN rgnUpdate = ::CreateRectRgn(0, 0, 0, 0);
    VERIFY(::ScrollDC(hDC, dx, dy, &r, NULL, rgnUpdate, NULL));

    // ScrollDC invalidates the part of the source rectangle that
    // is outside of the destination rectangle on the assumption
    // that you wanted to "move" the pixels from source to dest,
    // and so now you will want to paint new pixels in the source.
    // Since our copyarea operation involves no such semantics we
    // are only interested in the part of the update region that
    // corresponds to unavailable source pixels - i.e. the part
    // that falls within the destination rectangle.

    // The update region will be in client relative coordinates
    // but the destination rect will be in window relative coordinates
    ::OffsetRect(&r, dx-wsdo->insets.left, dy-wsdo->insets.top);
    HRGN rgnDst = ::CreateRectRgnIndirect(&r);
    int result = ::CombineRgn(rgnUpdate, rgnUpdate, rgnDst, RGN_AND);

    // Invalidate the exposed area.
    if (result != NULLREGION) {
        ::InvalidateRgn(wsdo->window, rgnUpdate, TRUE);
    }
    ::DeleteObject(rgnUpdate);
    ::DeleteObject(rgnDst);

    wsdo->ReleaseDC(env, wsdo, hDC);
}
