/*
 * COPYRIGHT:        GNU GPL, See COPYING in the top level directory
 * PROJECT:          ReactOS kernel
 * PURPOSE:          Coordinate systems
 * FILE:             win32ss/gdi/ntgdi/coord.c
 * PROGRAMERS:       Timo Kreuzer (timo.kreuzer@rectos.org)
 *                   Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
 */

/* Coordinate translation overview
 * -------------------------------
 *
 * Windows uses 3 different coordinate systems, referred to as world space,
 * page space and device space.
 *
 * Device space:
 * This is the coordinate system of the physical device that displays the
 * graphics. One unit matches one pixel of the surface. The coordinate system
 * is always orthogonal.
 *
 * Page space:
 * This is the coordinate system on the screen or on the paper layout for
 * printer devices. The coordinate system is also orthogonal but one unit
 * does not necessarily match one pixel. Instead there are different mapping
 * modes that can be set using SetMapMode() that specify how page space units
 * are transformed into device space units. These mapping modes are:
 * - MM_TEXT: One unit matches one unit in device space (one pixel)
 * - MM_TWIPS One unit matches 1/20 point (1/1440 inch)
 * - MM_LOMETRIC: One unit matches 0.1 millimeter
 * - MM_HIMETRIC: One unit matches 0.01 millimeter
 * - MM_LOENGLISH: One unit matches 0.01 inch
 * - MM_HIENGLISH: One unit matches 0.001 inch
 * - MM_ISOTROPIC:
 * - MM_ANISOTROPIC:
 * If the mapping mode is either MM_ISOTROPIC or MM_ANISOTROPIC, the actual
 * transformation is calculated from the window and viewport extension.
 * The window extension can be set using SetWindowExtEx() and describes the
 * extents of an arbitrary window (not to confuse with the gui element!) in
 * page space coordinates.
 * The viewport extension can be set using SetViewportExtEx() and describes
 * the extent of the same window in device space coordinates. If the mapping
 * mode is MM_ISOTROPIC one of the viewport extensions can be adjusted by GDI
 * to make sure the mapping stays isotropic, i.e. that it has the same x/y
 * ratio as the window extension.
 *
 * World space:
 * World space is the coordinate system that is used for all GDI drawing
 * operations. The metrics of this coordinate system depend on the DCs
 * graphics mode, which can be set using SetGraphicsMode().
 * If the graphics mode is GM_COMPATIBLE, world space is identical to page
 * space and no additional transformation is applied.
 * If the graphics mode is GM_ADVANCED, an arbitrary coordinate transformation
 * can be set using SetWorldTransform(), which is applied to transform world
 * space coordinates into page space coordinates.
 *
 * User mode data:
 * All coordinate translation data is stored in the DC attribute, so the values
 * might be invalid. This has to be taken into account. Values might also be
 * zero, so when a division is made, the value has to be read first and then
 * checked! This is true for both integer and floating point values, even if
 * we cannot get floating point exceptions on x86, we can get them on all other
 * architectures that use the FPU directly instead of emulation.
 * The result of all operations might be completely random and invalid, if it was
 * messed with in an illegal way in user mode. This is not a problem, since the
 * result of coordinate transformations are never expected to be "valid" values.
 * In the worst case, the drawing operation draws rubbish into the DC.
 */

/* INCLUDES ******************************************************************/

#include <win32k.h>

#define NDEBUG
#include <debug.h>
C_ASSERT(sizeof(XFORML) == sizeof(XFORM));


/* GLOBALS *******************************************************************/

const MATRIX gmxIdentity =
{
    FLOATOBJ_1, FLOATOBJ_0,
    FLOATOBJ_0, FLOATOBJ_1,
    FLOATOBJ_0, FLOATOBJ_0,
    0, 0, XFORM_NO_TRANSLATION|XFORM_FORMAT_LTOL|XFORM_UNITY|XFORM_SCALE
};


/* FUNCTIONS *****************************************************************/

VOID
FASTCALL
DC_vFixIsotropicMapping(PDC pdc)
{
    PDC_ATTR pdcattr;
    LONG64 fx, fy;
    LONG s;
    SIZEL szlWindowExt, szlViewportExt;
    ASSERT(pdc->pdcattr->iMapMode == MM_ISOTROPIC);

    /* Get a pointer to the DC_ATTR */
    pdcattr = pdc->pdcattr;

    /* Read the extents, we rely on non-null values */
    szlWindowExt = pdcattr->szlWindowExt;
    szlViewportExt = pdcattr->szlViewportExt;

    /* Check if all values are valid */
    if ((szlWindowExt.cx == 0) || (szlWindowExt.cy == 0) ||
        (szlViewportExt.cx == 0) || (szlViewportExt.cy == 0))
    {
        /* Someone put rubbish into the fields, just ignore it. */
        return;
    }

    fx = abs((LONG64)szlWindowExt.cx * szlViewportExt.cy);
    fy = abs((LONG64)szlWindowExt.cy * szlViewportExt.cx);

    if (fx < fy)
    {
        s = (szlWindowExt.cy ^ szlViewportExt.cx) > 0 ? 1 : -1;
        pdcattr->szlViewportExt.cx = (LONG)(fx * s / szlWindowExt.cy);
    }
    else if (fx > fy)
    {
        s = (szlWindowExt.cx ^ szlViewportExt.cy) > 0 ? 1 : -1;
        pdcattr->szlViewportExt.cy = (LONG)(fy * s / szlWindowExt.cx);
    }

    /* Reset the flag */
    pdc->pdcattr->flXform &= ~PAGE_EXTENTS_CHANGED;
}

VOID
FASTCALL
DC_vGetPageToDevice(PDC pdc, MATRIX *pmx)
{
    PDC_ATTR pdcattr = pdc->pdcattr;
    PSIZEL pszlViewPortExt;
    SIZEL szlWindowExt;

    /* Get the viewport extension */
    pszlViewPortExt = DC_pszlViewportExt(pdc);

    /* Copy the window extension, so no one can mess with it */
    szlWindowExt = pdcattr->szlWindowExt;

    /* No shearing / rotation */
    FLOATOBJ_SetLong(&pmx->efM12, 0);
    FLOATOBJ_SetLong(&pmx->efM21, 0);

    /* Calculate scaling */
    if (szlWindowExt.cx != 0)
    {
        FLOATOBJ_SetLong(&pmx->efM11, pszlViewPortExt->cx);
        FLOATOBJ_DivLong(&pmx->efM11, szlWindowExt.cx);
    }
    else
        FLOATOBJ_SetLong(&pmx->efM11, 1);

    if (szlWindowExt.cy != 0)
    {
        FLOATOBJ_SetLong(&pmx->efM22, pszlViewPortExt->cy);
        FLOATOBJ_DivLong(&pmx->efM22, szlWindowExt.cy);
    }
    else
        FLOATOBJ_SetLong(&pmx->efM22, 1);

    /* Calculate x offset */
    FLOATOBJ_SetLong(&pmx->efDx, -pdcattr->ptlWindowOrg.x);
    FLOATOBJ_Mul(&pmx->efDx, &pmx->efM11);
    FLOATOBJ_AddLong(&pmx->efDx, pdcattr->ptlViewportOrg.x);

    /* Calculate y offset */
    FLOATOBJ_SetLong(&pmx->efDy, -pdcattr->ptlWindowOrg.y);
    FLOATOBJ_Mul(&pmx->efDy, &pmx->efM22);
    FLOATOBJ_AddLong(&pmx->efDy, pdcattr->ptlViewportOrg.y);
}

VOID
FASTCALL
DC_vUpdateWorldToDevice(PDC pdc)
{
    XFORMOBJ xoPageToDevice, xoWorldToPage, xoWorldToDevice;
    MATRIX mxPageToDevice;

    // FIXME: make sure world-to-page is valid!

    /* Construct a transformation to do the page-to-device conversion */
    DC_vGetPageToDevice(pdc, &mxPageToDevice);
    XFORMOBJ_vInit(&xoPageToDevice, &mxPageToDevice);

    /* Recalculate the world-to-device xform */
    XFORMOBJ_vInit(&xoWorldToPage, &pdc->pdcattr->mxWorldToPage);
    XFORMOBJ_vInit(&xoWorldToDevice, &pdc->pdcattr->mxWorldToDevice);
    XFORMOBJ_iCombine(&xoWorldToDevice, &xoWorldToPage, &xoPageToDevice);

    /* Reset the flags */
    pdc->pdcattr->flXform &= ~WORLD_XFORM_CHANGED;
}

VOID
FASTCALL
DC_vUpdateDeviceToWorld(PDC pdc)
{
    XFORMOBJ xoWorldToDevice, xoDeviceToWorld;
    PMATRIX pmxWorldToDevice;

    /* Get the world-to-device translation */
    pmxWorldToDevice = DC_pmxWorldToDevice(pdc);
    XFORMOBJ_vInit(&xoWorldToDevice, pmxWorldToDevice);

    /* Create inverse of world-to-device transformation */
    XFORMOBJ_vInit(&xoDeviceToWorld, &pdc->pdcattr->mxDeviceToWorld);
    if (XFORMOBJ_iInverse(&xoDeviceToWorld, &xoWorldToDevice) == DDI_ERROR)
    {
        MX_Set0(&pdc->pdcattr->mxDeviceToWorld);
        return;
    }

    /* Reset the flag */
    pdc->pdcattr->flXform &= ~DEVICE_TO_WORLD_INVALID;
}

BOOL
NTAPI
GreCombineTransform(
    XFORML *pxformDest,
    XFORML *pxform1,
    XFORML *pxform2)
{
    MATRIX mxDest, mx1, mx2;
    XFORMOBJ xoDest, xo1, xo2;

    /* Check for illegal parameters */
    if (!pxformDest || !pxform1 || !pxform2) return FALSE;

    /* Initialize XFORMOBJs */
    XFORMOBJ_vInit(&xoDest, &mxDest);
    XFORMOBJ_vInit(&xo1, &mx1);
    XFORMOBJ_vInit(&xo2, &mx2);

    /* Convert the XFORMLs into XFORMOBJs */
    XFORMOBJ_iSetXform(&xo1, pxform1);
    XFORMOBJ_iSetXform(&xo2, pxform2);

    /* Combine them */
    XFORMOBJ_iCombine(&xoDest, &xo1, &xo2);

    /* Translate back into XFORML */
    XFORMOBJ_iGetXform(&xoDest, pxformDest);

    return TRUE;
}

BOOL
APIENTRY
NtGdiCombineTransform(
    LPXFORM UnsafeXFormResult,
    LPXFORM Unsafexform1,
    LPXFORM Unsafexform2)
{
    BOOL Ret;

    _SEH2_TRY
    {
        ProbeForWrite(UnsafeXFormResult, sizeof(XFORM), 1);
        ProbeForRead(Unsafexform1, sizeof(XFORM), 1);
        ProbeForRead(Unsafexform2, sizeof(XFORM), 1);
        Ret = GreCombineTransform((XFORML*)UnsafeXFormResult,
                                  (XFORML*)Unsafexform1,
                                  (XFORML*)Unsafexform2);
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
        Ret = FALSE;
    }
    _SEH2_END;

    return Ret;
}

// FIXME: Should be XFORML and use XFORMOBJ functions directly
BOOL
APIENTRY
NtGdiGetTransform(
    HDC hdc,
    DWORD iXform,
    LPXFORM pXForm)
{
    PDC pdc;
    BOOL ret = TRUE;
    MATRIX mxPageToDevice;
    XFORMOBJ xo;
    PMATRIX pmx;

    if (!pXForm)
    {
        EngSetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    pdc = DC_LockDc(hdc);
    if (!pdc)
    {
        EngSetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }

    switch (iXform)
    {
        case GdiWorldSpaceToPageSpace:
            pmx = DC_pmxWorldToPage(pdc);
            break;

        case GdiWorldSpaceToDeviceSpace:
            pmx = DC_pmxWorldToDevice(pdc);
            break;

        case GdiDeviceSpaceToWorldSpace:
            pmx = DC_pmxDeviceToWorld(pdc);
            break;

        case GdiPageSpaceToDeviceSpace:
            DC_vGetPageToDevice(pdc, &mxPageToDevice);
            pmx = &mxPageToDevice;
            break;

        default:
            DPRINT1("Unknown transform %lu\n", iXform);
            ret = FALSE;
            goto leave;
    }

    /* Initialize an XFORMOBJ */
    XFORMOBJ_vInit(&xo, pmx);

    _SEH2_TRY
    {
        ProbeForWrite(pXForm, sizeof(XFORML), 1);
        XFORMOBJ_iGetXform(&xo, (XFORML*)pXForm);
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
        ret = FALSE;
    }
    _SEH2_END;

leave:
    DC_UnlockDc(pdc);
    return ret;
}


/*!
 * Converts points from logical coordinates into device coordinates.
 * Conversion depends on the mapping mode,
 * world transfrom, viewport origin settings for the given device context.
 * \param	hDC		device context.
 * \param	Points	an array of POINT structures (in/out).
 * \param	Count	number of elements in the array of POINT structures.
 * \return  TRUE if success, FALSE otherwise.
*/
BOOL
APIENTRY
NtGdiTransformPoints(
    HDC hDC,
    PPOINT UnsafePtsIn,
    PPOINT UnsafePtOut,
    INT Count,
    INT iMode)
{
    PDC pdc;
    LPPOINT Points;
    ULONG Size;
    BOOL ret = TRUE;

    if (Count <= 0)
        return TRUE;

    if (!UnsafePtsIn || !UnsafePtOut)
    {
        return FALSE;
    }

    pdc = DC_LockDc(hDC);
    if (!pdc)
    {
        return FALSE;
    }

    Size = Count * sizeof(POINT);

    // FIXME: It would be wise to have a small stack buffer as optimization
    Points = ExAllocatePoolWithTag(PagedPool, Size, GDITAG_TEMP);
    if (!Points)
    {
        DC_UnlockDc(pdc);
        EngSetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return FALSE;
    }

    _SEH2_TRY
    {
        ProbeForWrite(UnsafePtOut, Size, 1);
        ProbeForRead(UnsafePtsIn, Size, 1);
        RtlCopyMemory(Points, UnsafePtsIn, Size);
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
        /* Do not set last error */
        _SEH2_YIELD(goto leave;)
    }
    _SEH2_END;

    switch (iMode)
    {
        case GdiDpToLp:
            ret = INTERNAL_APPLY_MATRIX(DC_pmxDeviceToWorld(pdc), Points, Count);
            break;

        case GdiLpToDp:
            ret = INTERNAL_APPLY_MATRIX(DC_pmxWorldToDevice(pdc), Points, Count);
            break;

        case 2: // Not supported yet. Need testing.
        default:
        {
            EngSetLastError(ERROR_INVALID_PARAMETER);
            ret = FALSE;
            goto leave;
        }
    }

    if (ret)
    {
        _SEH2_TRY
        {
            /* Pointer was already probed! */
            RtlCopyMemory(UnsafePtOut, Points, Size);
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            /* Do not set last error */
            ret = FALSE;
        }
        _SEH2_END;
    }

//
// If we are getting called that means User XForms is a mess!
//
leave:
    DC_UnlockDc(pdc);
    ExFreePoolWithTag(Points, GDITAG_TEMP);
    return ret;
}

BOOL
NTAPI
GreModifyWorldTransform(
    PDC pdc,
    const XFORML *pxform,
    DWORD dwMode)
{
    MATRIX mxSrc;
    XFORMOBJ xoSrc, xoDC;

    switch (dwMode)
    {
        case MWT_IDENTITY:
            pdc->pdcattr->mxWorldToPage = gmxIdentity;
            break;

        case MWT_LEFTMULTIPLY:
            XFORMOBJ_vInit(&xoDC, &pdc->pdcattr->mxWorldToPage);
            XFORMOBJ_vInit(&xoSrc, &mxSrc);
            if (XFORMOBJ_iSetXform(&xoSrc, pxform) == DDI_ERROR)
                return FALSE;
            XFORMOBJ_iCombine(&xoDC, &xoSrc, &xoDC);
            break;

        case MWT_RIGHTMULTIPLY:
            XFORMOBJ_vInit(&xoDC, &pdc->pdcattr->mxWorldToPage);
            XFORMOBJ_vInit(&xoSrc, &mxSrc);
            if (XFORMOBJ_iSetXform(&xoSrc, pxform) == DDI_ERROR)
                return FALSE;
            XFORMOBJ_iCombine(&xoDC, &xoDC, &xoSrc);
            break;

        case MWT_SET:
            XFORMOBJ_vInit(&xoDC, &pdc->pdcattr->mxWorldToPage);
            if (XFORMOBJ_iSetXform(&xoDC, pxform) == DDI_ERROR)
                return FALSE;
            break;

        default:
            return FALSE;
    }

    /*Set invalidation flags */
    pdc->pdcattr->flXform |= WORLD_XFORM_CHANGED|DEVICE_TO_WORLD_INVALID;

    return TRUE;
}

BOOL
APIENTRY
NtGdiModifyWorldTransform(
    HDC hdc,
    LPXFORM pxformUnsafe,
    DWORD dwMode)
{
    PDC pdc;
    XFORML xformSafe;
    BOOL Ret = TRUE;

    pdc = DC_LockDc(hdc);
    if (!pdc)
    {
        EngSetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }

    /* The xform is permitted to be NULL for MWT_IDENTITY.
     * However, if it is not NULL, then it must be valid even
     * though it is not used. */
    if ((dwMode != MWT_IDENTITY) && (pxformUnsafe == NULL))
    {
        DC_UnlockDc(pdc);
        return FALSE;
    }

    if (pxformUnsafe != NULL)
    {
        _SEH2_TRY
        {
            ProbeForRead(pxformUnsafe, sizeof(XFORML), 1);
            RtlCopyMemory(&xformSafe, pxformUnsafe, sizeof(XFORML));
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            Ret = FALSE;
        }
        _SEH2_END;
    }

    /* Safe to handle kernel mode data. */
    if (Ret) Ret = GreModifyWorldTransform(pdc, &xformSafe, dwMode);
    DC_UnlockDc(pdc);
    return Ret;
}

BOOL
APIENTRY
NtGdiOffsetViewportOrgEx(
    HDC hDC,
    int XOffset,
    int YOffset,
    LPPOINT UnsafePoint)
{
    PDC      dc;
    PDC_ATTR pdcattr;
    NTSTATUS Status = STATUS_SUCCESS;

    dc = DC_LockDc(hDC);
    if (!dc)
    {
        EngSetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }
    pdcattr = dc->pdcattr;

    if (UnsafePoint)
    {
        _SEH2_TRY
        {
            ProbeForWrite(UnsafePoint, sizeof(POINT), 1);
            UnsafePoint->x = pdcattr->ptlViewportOrg.x;
            UnsafePoint->y = pdcattr->ptlViewportOrg.y;
            if (pdcattr->dwLayout & LAYOUT_RTL)
            {
                UnsafePoint->x = -UnsafePoint->x;
            }
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            Status = _SEH2_GetExceptionCode();
        }
        _SEH2_END;

        if (!NT_SUCCESS(Status))
        {
            SetLastNtError(Status);
            DC_UnlockDc(dc);
            return FALSE;
        }
    }

    if (pdcattr->dwLayout & LAYOUT_RTL)
    {
        XOffset = -XOffset;
    }
    pdcattr->ptlViewportOrg.x += XOffset;
    pdcattr->ptlViewportOrg.y += YOffset;
    pdcattr->flXform |= PAGE_XLATE_CHANGED | WORLD_XFORM_CHANGED | DEVICE_TO_WORLD_INVALID;

    DC_UnlockDc(dc);

    return TRUE;
}

BOOL
APIENTRY
NtGdiOffsetWindowOrgEx(
    HDC hDC,
    int XOffset,
    int YOffset,
    LPPOINT Point)
{
    PDC dc;
    PDC_ATTR pdcattr;

    dc = DC_LockDc(hDC);
    if (!dc)
    {
        EngSetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }
    pdcattr = dc->pdcattr;

    if (Point)
    {
        NTSTATUS Status = STATUS_SUCCESS;

        _SEH2_TRY
        {
            ProbeForWrite(Point, sizeof(POINT), 1);
            Point->x = pdcattr->ptlWindowOrg.x;
            Point->y = pdcattr->ptlWindowOrg.y;
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            Status = _SEH2_GetExceptionCode();
        }
        _SEH2_END;

        if (!NT_SUCCESS(Status))
        {
            SetLastNtError(Status);
            DC_UnlockDc(dc);
            return FALSE;
        }
    }

    pdcattr->ptlWindowOrg.x += XOffset;
    pdcattr->ptlWindowOrg.y += YOffset;
    pdcattr->flXform |= PAGE_XLATE_CHANGED | WORLD_XFORM_CHANGED | DEVICE_TO_WORLD_INVALID;

    DC_UnlockDc(dc);

    return TRUE;
}

BOOL
APIENTRY
NtGdiScaleViewportExtEx(
    HDC hDC,
    int Xnum,
    int Xdenom,
    int Ynum,
    int Ydenom,
    LPSIZE pSize)
{
    PDC pDC;
    PDC_ATTR pdcattr;
    BOOL Ret = FALSE;
    LONG X, Y;

    pDC = DC_LockDc(hDC);
    if (!pDC)
    {
        EngSetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }
    pdcattr = pDC->pdcattr;

    if (pdcattr->iMapMode > MM_TWIPS)
    {
        if (Xdenom && Ydenom)
        {
            DC_pszlViewportExt(pDC);
            X = Xnum * pdcattr->szlViewportExt.cx / Xdenom;
            if (X)
            {
                Y = Ynum * pdcattr->szlViewportExt.cy / Ydenom;
                if (Y)
                {
                    pdcattr->szlViewportExt.cx = X;
                    pdcattr->szlViewportExt.cy = Y;
                    pdcattr->flXform |= PAGE_XLATE_CHANGED;

                    IntMirrorWindowOrg(pDC);

                    pdcattr->flXform |= (PAGE_EXTENTS_CHANGED |
                                         INVALIDATE_ATTRIBUTES |
                                         WORLD_XFORM_CHANGED |
                                         DEVICE_TO_WORLD_INVALID);

                    if (pdcattr->iMapMode == MM_ISOTROPIC)
                    {
                        DC_vFixIsotropicMapping(pDC);
                    }

                    Ret = TRUE;
                }
            }
        }
    }
    else
        Ret = TRUE;

    if (pSize)
    {
        _SEH2_TRY
        {
            ProbeForWrite(pSize, sizeof(SIZE), 1);

            pSize->cx = pdcattr->szlViewportExt.cx;
            pSize->cy = pdcattr->szlViewportExt.cy;
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            SetLastNtError(_SEH2_GetExceptionCode());
            Ret = FALSE;
        }
        _SEH2_END;
    }

    DC_UnlockDc(pDC);
    return Ret;
}

BOOL
APIENTRY
NtGdiScaleWindowExtEx(
    HDC hDC,
    int Xnum,
    int Xdenom,
    int Ynum,
    int Ydenom,
    LPSIZE pSize)
{
    PDC pDC;
    PDC_ATTR pdcattr;
    BOOL Ret = FALSE;
    LONG X, Y;

    pDC = DC_LockDc(hDC);
    if (!pDC)
    {
        EngSetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }
    pdcattr = pDC->pdcattr;

    if (pSize)
    {
        NTSTATUS Status = STATUS_SUCCESS;

        _SEH2_TRY
        {
            ProbeForWrite(pSize, sizeof(SIZE), 1);

            X = pdcattr->szlWindowExt.cx;
            if (pdcattr->dwLayout & LAYOUT_RTL) X = -X;
            pSize->cx = X;
            pSize->cy = pdcattr->szlWindowExt.cy;
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            Status = _SEH2_GetExceptionCode();
        }
        _SEH2_END;

        if (!NT_SUCCESS(Status))
        {
            SetLastNtError(Status);
            DC_UnlockDc(pDC);
            return FALSE;
        }
    }

    if (pdcattr->iMapMode > MM_TWIPS)
    {
        if (Xdenom && Ydenom)
        {
            X = Xnum * pdcattr->szlWindowExt.cx / Xdenom;
            if (X)
            {
                Y = Ynum * pdcattr->szlWindowExt.cy / Ydenom;
                if (Y)
                {
                    pdcattr->szlWindowExt.cx = X;
                    pdcattr->szlWindowExt.cy = Y;

                    IntMirrorWindowOrg(pDC);

                    pdcattr->flXform |= (PAGE_EXTENTS_CHANGED |
                                         INVALIDATE_ATTRIBUTES |
                                         WORLD_XFORM_CHANGED |
                                         DEVICE_TO_WORLD_INVALID);

                    Ret = TRUE;
                }
            }
        }
    }
    else
        Ret = TRUE;

    DC_UnlockDc(pDC);
    return Ret;
}

int
APIENTRY
IntGdiSetMapMode(
    PDC dc,
    int MapMode)
{
    INT iPrevMapMode;
    FLONG flXform;
    PDC_ATTR pdcattr = dc->pdcattr;

    if (MapMode == pdcattr->iMapMode)
        return MapMode;

    flXform = pdcattr->flXform & ~(ISO_OR_ANISO_MAP_MODE|PTOD_EFM22_NEGATIVE|
        PTOD_EFM11_NEGATIVE|POSITIVE_Y_IS_UP|PAGE_TO_DEVICE_SCALE_IDENTITY|
        PAGE_TO_DEVICE_IDENTITY);

    switch (MapMode)
    {
        case MM_TEXT:
            pdcattr->szlWindowExt.cx = 1;
            pdcattr->szlWindowExt.cy = 1;
            pdcattr->szlViewportExt.cx = 1;
            pdcattr->szlViewportExt.cy = 1;
            flXform |= PAGE_TO_DEVICE_SCALE_IDENTITY;
            break;

        case MM_ISOTROPIC:
            flXform |= ISO_OR_ANISO_MAP_MODE;
            /* Fall through */

        case MM_LOMETRIC:
            pdcattr->szlWindowExt.cx = pdcattr->szlVirtualDeviceMm.cx * 10;
            pdcattr->szlWindowExt.cy = pdcattr->szlVirtualDeviceMm.cy * 10;
            pdcattr->szlViewportExt.cx =  pdcattr->szlVirtualDevicePixel.cx;
            pdcattr->szlViewportExt.cy = -pdcattr->szlVirtualDevicePixel.cy;
            break;

        case MM_HIMETRIC:
            pdcattr->szlWindowExt.cx = pdcattr->szlVirtualDeviceMm.cx * 100;
            pdcattr->szlWindowExt.cy = pdcattr->szlVirtualDeviceMm.cy * 100;
            pdcattr->szlViewportExt.cx =  pdcattr->szlVirtualDevicePixel.cx;
            pdcattr->szlViewportExt.cy = -pdcattr->szlVirtualDevicePixel.cy;
            break;

        case MM_LOENGLISH:
            pdcattr->szlWindowExt.cx = EngMulDiv(1000, pdcattr->szlVirtualDeviceMm.cx, 254);
            pdcattr->szlWindowExt.cy = EngMulDiv(1000, pdcattr->szlVirtualDeviceMm.cy, 254);
            pdcattr->szlViewportExt.cx =  pdcattr->szlVirtualDevicePixel.cx;
            pdcattr->szlViewportExt.cy = -pdcattr->szlVirtualDevicePixel.cy;
            break;

        case MM_HIENGLISH:
            pdcattr->szlWindowExt.cx = EngMulDiv(10000, pdcattr->szlVirtualDeviceMm.cx, 254);
            pdcattr->szlWindowExt.cy = EngMulDiv(10000, pdcattr->szlVirtualDeviceMm.cy, 254);
            pdcattr->szlViewportExt.cx =  pdcattr->szlVirtualDevicePixel.cx;
            pdcattr->szlViewportExt.cy = -pdcattr->szlVirtualDevicePixel.cy;
            break;

        case MM_TWIPS:
            pdcattr->szlWindowExt.cx = EngMulDiv(14400, pdcattr->szlVirtualDeviceMm.cx, 254);
            pdcattr->szlWindowExt.cy = EngMulDiv(14400, pdcattr->szlVirtualDeviceMm.cy, 254);
            pdcattr->szlViewportExt.cx =  pdcattr->szlVirtualDevicePixel.cx;
            pdcattr->szlViewportExt.cy = -pdcattr->szlVirtualDevicePixel.cy;
            break;

        case MM_ANISOTROPIC:
            flXform &= ~(PAGE_TO_DEVICE_IDENTITY|POSITIVE_Y_IS_UP);
            flXform |= ISO_OR_ANISO_MAP_MODE;
            break;

        default:
            return 0;
    }

    /* Save the old map mode and set the new one */
    iPrevMapMode = pdcattr->iMapMode;
    pdcattr->iMapMode = MapMode;

    /* Update xform flags */
    pdcattr->flXform = flXform | (PAGE_XLATE_CHANGED | PAGE_EXTENTS_CHANGED |
                                  INVALIDATE_ATTRIBUTES | DEVICE_TO_PAGE_INVALID |
                                  WORLD_XFORM_CHANGED | DEVICE_TO_WORLD_INVALID);

    return iPrevMapMode;
}

BOOL
FASTCALL
GreSetViewportOrgEx(
    HDC hDC,
    int X,
    int Y,
    LPPOINT Point)
{
    PDC dc;
    PDC_ATTR pdcattr;

    dc = DC_LockDc(hDC);
    if (!dc)
    {
        EngSetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }
    pdcattr = dc->pdcattr;

    if (Point)
    {
       Point->x = pdcattr->ptlViewportOrg.x;
       Point->y = pdcattr->ptlViewportOrg.y;
    }

    pdcattr->ptlViewportOrg.x = X;
    pdcattr->ptlViewportOrg.y = Y;
    pdcattr->flXform |= PAGE_XLATE_CHANGED | WORLD_XFORM_CHANGED | DEVICE_TO_WORLD_INVALID;

    DC_UnlockDc(dc);
    return TRUE;
}

BOOL
APIENTRY
NtGdiSetViewportOrgEx(
    HDC hDC,
    int X,
    int Y,
    LPPOINT Point)
{
    PDC dc;
    PDC_ATTR pdcattr;

    dc = DC_LockDc(hDC);
    if (!dc)
    {
        EngSetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }
    pdcattr = dc->pdcattr;

    if (Point)
    {
        NTSTATUS Status = STATUS_SUCCESS;

        _SEH2_TRY
        {
            ProbeForWrite(Point, sizeof(POINT), 1);
            Point->x = pdcattr->ptlViewportOrg.x;
            Point->y = pdcattr->ptlViewportOrg.y;
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            Status = _SEH2_GetExceptionCode();
        }
        _SEH2_END;

        if (!NT_SUCCESS(Status))
        {
            SetLastNtError(Status);
            DC_UnlockDc(dc);
            return FALSE;
        }
    }

    pdcattr->ptlViewportOrg.x = X;
    pdcattr->ptlViewportOrg.y = Y;
    pdcattr->flXform |= PAGE_XLATE_CHANGED | WORLD_XFORM_CHANGED | DEVICE_TO_WORLD_INVALID;

    DC_UnlockDc(dc);

    return TRUE;
}

BOOL
APIENTRY
NtGdiSetWindowOrgEx(
    HDC hDC,
    int X,
    int Y,
    LPPOINT Point)
{
    PDC dc;
    PDC_ATTR pdcattr;

    dc = DC_LockDc(hDC);
    if (!dc)
    {
        EngSetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }
    pdcattr = dc->pdcattr;

    if (Point)
    {
        NTSTATUS Status = STATUS_SUCCESS;

        _SEH2_TRY
        {
            ProbeForWrite(Point, sizeof(POINT), 1);
            Point->x = pdcattr->ptlWindowOrg.x;
            Point->y = pdcattr->ptlWindowOrg.y;
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            Status = _SEH2_GetExceptionCode();
        }
        _SEH2_END;

        if (!NT_SUCCESS(Status))
        {
            SetLastNtError(Status);
            DC_UnlockDc(dc);
            return FALSE;
        }
    }

    pdcattr->ptlWindowOrg.x = X;
    pdcattr->ptlWindowOrg.y = Y;
    pdcattr->flXform |= PAGE_XLATE_CHANGED | WORLD_XFORM_CHANGED | DEVICE_TO_WORLD_INVALID;

    DC_UnlockDc(dc);

    return TRUE;
}

//
// Mirror Window function.
//
VOID
FASTCALL
IntMirrorWindowOrg(PDC dc)
{
    PDC_ATTR pdcattr;
    LONG X, cx;

    pdcattr = dc->pdcattr;

    if (!(pdcattr->dwLayout & LAYOUT_RTL))
    {
        pdcattr->ptlWindowOrg.x = pdcattr->lWindowOrgx; // Flip it back.
        return;
    }

    /* Copy the window extension, so no one can mess with it */
    cx = pdcattr->szlViewportExt.cx;
    if (cx == 0) return;
    //
    // WOrgx = wox - (Width - 1) * WExtx / VExtx
    //
    X = (dc->erclWindow.right - dc->erclWindow.left) - 1; // Get device width - 1

    X = (X * pdcattr->szlWindowExt.cx) / cx;

    pdcattr->ptlWindowOrg.x = pdcattr->lWindowOrgx - X; // Now set the inverted win origion.
    pdcattr->flXform |= PAGE_XLATE_CHANGED | WORLD_XFORM_CHANGED | DEVICE_TO_WORLD_INVALID;

    return;
}

VOID
NTAPI
DC_vSetLayout(
    IN PDC pdc,
    IN LONG wox,
    IN DWORD dwLayout)
{
    PDC_ATTR pdcattr = pdc->pdcattr;

    pdcattr->dwLayout = dwLayout;

    if (!(dwLayout & LAYOUT_ORIENTATIONMASK)) return;

    if (dwLayout & LAYOUT_RTL)
    {
        pdcattr->iMapMode = MM_ANISOTROPIC;
    }

    //pdcattr->szlWindowExt.cy = -pdcattr->szlWindowExt.cy;
    //pdcattr->ptlWindowOrg.x  = -pdcattr->ptlWindowOrg.x;

    //if (wox == -1)
    //    IntMirrorWindowOrg(pdc);
    //else
    //    pdcattr->ptlWindowOrg.x = wox - pdcattr->ptlWindowOrg.x;

    if (!(pdcattr->flTextAlign & TA_CENTER)) pdcattr->flTextAlign |= TA_RIGHT;

    if (pdc->dclevel.flPath & DCPATH_CLOCKWISE)
        pdc->dclevel.flPath &= ~DCPATH_CLOCKWISE;
    else
        pdc->dclevel.flPath |= DCPATH_CLOCKWISE;

    pdcattr->flXform |= (PAGE_EXTENTS_CHANGED |
                         INVALIDATE_ATTRIBUTES |
                         WORLD_XFORM_CHANGED |
                         DEVICE_TO_WORLD_INVALID);
}

// NtGdiSetLayout
//
// The default is left to right. This function changes it to right to left, which
// is the standard in Arabic and Hebrew cultures.
//
/*
 * @implemented
 */
DWORD
APIENTRY
NtGdiSetLayout(
    IN HDC hdc,
    IN LONG wox,
    IN DWORD dwLayout)
{
    PDC pdc;
    DWORD dwOldLayout;

    pdc = DC_LockDc(hdc);
    if (!pdc)
    {
        EngSetLastError(ERROR_INVALID_HANDLE);
        return GDI_ERROR;
    }

    dwOldLayout = pdc->pdcattr->dwLayout;
    DC_vSetLayout(pdc, wox, dwLayout);

    DC_UnlockDc(pdc);
    return dwOldLayout;
}

/*
 * @implemented
 */
LONG
APIENTRY
NtGdiGetDeviceWidth(
    IN HDC hdc)
{
    PDC dc;
    LONG Ret;
    dc = DC_LockDc(hdc);
    if (!dc)
    {
        EngSetLastError(ERROR_INVALID_HANDLE);
        return 0;
    }
    Ret = dc->erclWindow.right - dc->erclWindow.left;
    DC_UnlockDc(dc);
    return Ret;
}

/*
 * @implemented
 */
BOOL
APIENTRY
NtGdiMirrorWindowOrg(
    IN HDC hdc)
{
    PDC dc;
    dc = DC_LockDc(hdc);
    if (!dc)
    {
        EngSetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }
    IntMirrorWindowOrg(dc);
    DC_UnlockDc(dc);
    return TRUE;
}

/*
 * @implemented
 */
BOOL
APIENTRY
NtGdiSetSizeDevice(
    IN HDC hdc,
    IN INT cxVirtualDevice,
    IN INT cyVirtualDevice)
{
    PDC dc;
    PDC_ATTR pdcattr;

    if (!cxVirtualDevice || !cyVirtualDevice)
    {
        return FALSE;
    }

    dc = DC_LockDc(hdc);
    if (!dc) return FALSE;

    pdcattr = dc->pdcattr;

    pdcattr->szlVirtualDeviceSize.cx = cxVirtualDevice;
    pdcattr->szlVirtualDeviceSize.cy = cyVirtualDevice;

    DC_UnlockDc(dc);

    return TRUE;
}

/*
 * @implemented
 */
BOOL
APIENTRY
NtGdiSetVirtualResolution(
    IN HDC hdc,
    IN INT cxVirtualDevicePixel,
    IN INT cyVirtualDevicePixel,
    IN INT cxVirtualDeviceMm,
    IN INT cyVirtualDeviceMm)
{
    PDC dc;
    PDC_ATTR pdcattr;

    /* Check parameters (all zeroes resets to real resolution) */
    if (cxVirtualDevicePixel == 0 && cyVirtualDevicePixel == 0 &&
        cxVirtualDeviceMm == 0 && cyVirtualDeviceMm == 0)
    {
        cxVirtualDevicePixel = NtGdiGetDeviceCaps(hdc, HORZRES);
        cyVirtualDevicePixel = NtGdiGetDeviceCaps(hdc, VERTRES);
        cxVirtualDeviceMm = NtGdiGetDeviceCaps(hdc, HORZSIZE);
        cyVirtualDeviceMm = NtGdiGetDeviceCaps(hdc, VERTSIZE);
    }
    else if (cxVirtualDevicePixel == 0 || cyVirtualDevicePixel == 0 ||
             cxVirtualDeviceMm == 0 || cyVirtualDeviceMm == 0)
    {
        return FALSE;
    }

    dc = DC_LockDc(hdc);
    if (!dc) return FALSE;

    pdcattr = dc->pdcattr;

    pdcattr->szlVirtualDevicePixel.cx = cxVirtualDevicePixel;
    pdcattr->szlVirtualDevicePixel.cy = cyVirtualDevicePixel;
    pdcattr->szlVirtualDeviceMm.cx = cxVirtualDeviceMm;
    pdcattr->szlVirtualDeviceMm.cy = cyVirtualDeviceMm;

//    DC_vUpdateXforms(dc);
    DC_UnlockDc(dc);
    return TRUE;
}

static
VOID FASTCALL
DC_vGetAspectRatioFilter(PDC pDC, LPSIZE AspectRatio)
{
    if (pDC->pdcattr->flFontMapper & 1) // TRUE assume 1.
    {
        // "This specifies that Windows should only match fonts that have the
        // same aspect ratio as the display.", Programming Windows, Fifth Ed.
        AspectRatio->cx = pDC->ppdev->gdiinfo.ulLogPixelsX;
        AspectRatio->cy = pDC->ppdev->gdiinfo.ulLogPixelsY;
    }
    else
    {
        AspectRatio->cx = 0;
        AspectRatio->cy = 0;
    }
}

BOOL APIENTRY
GreGetDCPoint(
    HDC hDC,
    UINT iPoint,
    PPOINTL Point)
{
    BOOL Ret = TRUE;
    DC *pdc;
    SIZE Size;
    PSIZEL pszlViewportExt;

    if (!Point)
    {
        EngSetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    pdc = DC_LockDc(hDC);
    if (!pdc)
    {
        EngSetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }

    switch (iPoint)
    {
        case GdiGetViewPortExt:
            pszlViewportExt = DC_pszlViewportExt(pdc);
            Point->x = pszlViewportExt->cx;
            Point->y = pszlViewportExt->cy;
            break;

        case GdiGetWindowExt:
            Point->x = pdc->pdcattr->szlWindowExt.cx;
            Point->y = pdc->pdcattr->szlWindowExt.cy;
            break;

        case GdiGetViewPortOrg:
            *Point = pdc->pdcattr->ptlViewportOrg;
            break;

        case GdiGetWindowOrg:
            *Point = pdc->pdcattr->ptlWindowOrg;
            break;

        case GdiGetDCOrg:
            *Point = pdc->ptlDCOrig;
            break;

        case GdiGetAspectRatioFilter:
            DC_vGetAspectRatioFilter(pdc, &Size);
            Point->x = Size.cx;
            Point->y = Size.cy;
            break;

        default:
            EngSetLastError(ERROR_INVALID_PARAMETER);
            Ret = FALSE;
            break;
    }

    DC_UnlockDc(pdc);
    return Ret;
}

BOOL
WINAPI
GreSetDCOrg(
    _In_ HDC hdc,
    _In_ LONG x,
    _In_ LONG y,
    _In_opt_ PRECTL Rect)
{
    PDC dc;

    dc = DC_LockDc(hdc);
    if (!dc) return FALSE;

    /* Set DC Origin */
    dc->ptlDCOrig.x = x;
    dc->ptlDCOrig.y = y;

    /* Recalculate Fill Origin */
    dc->ptlFillOrigin.x = dc->dclevel.ptlBrushOrigin.x + x;
    dc->ptlFillOrigin.y = dc->dclevel.ptlBrushOrigin.y + y;

    /* Set DC Window Rectangle */
    if (Rect)
        dc->erclWindow = *Rect;

    DC_UnlockDc(dc);
    return TRUE;
}

BOOL
WINAPI
GreGetDCOrgEx(
    _In_ HDC hdc,
    _Out_ PPOINTL Point,
    _Out_ PRECTL Rect)
{
    PDC dc;

    dc = DC_LockDc(hdc);
    if (!dc) return FALSE;

    /* Retrieve DC Window Rectangle without a check */
    *Rect = dc->erclWindow;

    DC_UnlockDc(dc);

    /* Use default call for DC Origin and parameter checking */
    return GreGetDCPoint( hdc, GdiGetDCOrg, Point);
}

BOOL
WINAPI
GreGetWindowExtEx(
    _In_ HDC hdc,
    _Out_ LPSIZE lpSize)
{
    return GreGetDCPoint(hdc, GdiGetWindowExt, (PPOINTL)lpSize);
}

BOOL
WINAPI
GreGetViewportExtEx(
    _In_ HDC hdc,
    _Out_ LPSIZE lpSize)
{
    return GreGetDCPoint(hdc, GdiGetViewPortExt, (PPOINTL)lpSize);
}

BOOL APIENTRY
NtGdiGetDCPoint(
    HDC hDC,
    UINT iPoint,
    PPOINTL Point)
{
    BOOL Ret;
    POINTL SafePoint;

    if (!Point)
    {
        EngSetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    Ret = GreGetDCPoint(hDC, iPoint, &SafePoint);
    if (Ret)
    {
        _SEH2_TRY
        {
            ProbeForWrite(Point, sizeof(POINT), 1);
            *Point = SafePoint;
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            Ret = FALSE;
        }
        _SEH2_END;
    }

    return Ret;
}

/* EOF */