/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */


#include <svx/svdtrans.hxx>
#include <math.h>
#include <svx/xpoly.hxx>
#include <rtl/ustrbuf.hxx>

#include <vcl/virdev.hxx>
#include <tools/bigint.hxx>
#include <tools/UnitConversion.hxx>
#include <unotools/syslocale.hxx>
#include <unotools/localedatawrapper.hxx>
#include <sal/log.hxx>

void MoveXPoly(XPolygon& rPoly, const Size& S)
{
    rPoly.Move(S.Width(),S.Height());
}

void ResizeRect(tools::Rectangle& rRect, const Point& rRef, const Fraction& rxFact, const Fraction& ryFact)
{
    Fraction aXFact(rxFact);
    Fraction aYFact(ryFact);

    if (!aXFact.IsValid()) {
        SAL_WARN( "svx.svdraw", "invalid fraction xFract, using Fraction(1,1)" );
        aXFact = Fraction(1,1);
        tools::Long nWdt = rRect.Right() - rRect.Left();
        if (nWdt == 0) rRect.AdjustRight( 1 );
    }
    rRect.SetLeft( rRef.X() + basegfx::fround<tools::Long>( (rRect.Left()  - rRef.X()) * double(aXFact) ) );
    rRect.SetRight( rRef.X() + basegfx::fround<tools::Long>( (rRect.Right() - rRef.X()) * double(aXFact) ) );

    if (!aYFact.IsValid()) {
        SAL_WARN( "svx.svdraw", "invalid fraction yFract, using Fraction(1,1)" );
        aYFact = Fraction(1,1);
        tools::Long nHgt = rRect.Bottom() - rRect.Top();
        if (nHgt == 0) rRect.AdjustBottom( 1 );
    }
    rRect.SetTop( rRef.Y() + basegfx::fround<tools::Long>( (rRect.Top()    - rRef.Y()) * double(aYFact) ) );
    rRect.SetBottom( rRef.Y() + basegfx::fround<tools::Long>( (rRect.Bottom() - rRef.Y()) * double(aYFact) ) );

    rRect.Normalize();
}


void ResizePoly(tools::Polygon& rPoly, const Point& rRef, const Fraction& xFact, const Fraction& yFact)
{
    sal_uInt16 nCount=rPoly.GetSize();
    for (sal_uInt16 i=0; i<nCount; i++) {
        ResizePoint(rPoly[i],rRef,xFact,yFact);
    }
}

void ResizeXPoly(XPolygon& rPoly, const Point& rRef, const Fraction& xFact, const Fraction& yFact)
{
    sal_uInt16 nCount=rPoly.GetPointCount();
    for (sal_uInt16 i=0; i<nCount; i++) {
        ResizePoint(rPoly[i],rRef,xFact,yFact);
    }
}

void RotatePoly(tools::Polygon& rPoly, const Point& rRef, double sn, double cs)
{
    sal_uInt16 nCount=rPoly.GetSize();
    for (sal_uInt16 i=0; i<nCount; i++) {
        RotatePoint(rPoly[i],rRef,sn,cs);
    }
}

void RotateXPoly(XPolygon& rPoly, const Point& rRef, double sn, double cs)
{
    sal_uInt16 nCount=rPoly.GetPointCount();
    for (sal_uInt16 i=0; i<nCount; i++) {
        RotatePoint(rPoly[i],rRef,sn,cs);
    }
}

void RotateXPoly(XPolyPolygon& rPoly, const Point& rRef, double sn, double cs)
{
    sal_uInt16 nCount=rPoly.Count();
    for (sal_uInt16 i=0; i<nCount; i++) {
        RotateXPoly(rPoly[i],rRef,sn,cs);
    }
}

void MirrorPoint(Point& rPnt, const Point& rRef1, const Point& rRef2)
{
    tools::Long mx=rRef2.X()-rRef1.X();
    tools::Long my=rRef2.Y()-rRef1.Y();
    if (mx==0) { // vertical axis
        tools::Long dx=rRef1.X()-rPnt.X();
        rPnt.AdjustX(2*dx );
    } else if (my==0) { // horizontal axis
        tools::Long dy=rRef1.Y()-rPnt.Y();
        rPnt.AdjustY(2*dy );
    } else if (mx==my) { // diagonal axis '\'
        tools::Long dx1=rPnt.X()-rRef1.X();
        tools::Long dy1=rPnt.Y()-rRef1.Y();
        rPnt.setX(rRef1.X()+dy1 );
        rPnt.setY(rRef1.Y()+dx1 );
    } else if (mx==-my) { // diagonal axis '/'
        tools::Long dx1=rPnt.X()-rRef1.X();
        tools::Long dy1=rPnt.Y()-rRef1.Y();
        rPnt.setX(rRef1.X()-dy1 );
        rPnt.setY(rRef1.Y()-dx1 );
    } else { // arbitrary axis
        // TODO: Optimize this! Raise perpendicular on the mirroring axis..?
        Degree100 nRefAngle=GetAngle(rRef2-rRef1);
        rPnt-=rRef1;
        Degree100 nPntAngle=GetAngle(rPnt);
        Degree100 nAngle=2_deg100*(nRefAngle-nPntAngle);
        double a = toRadians(nAngle);
        double nSin=sin(a);
        double nCos=cos(a);
        RotatePoint(rPnt,Point(),nSin,nCos);
        rPnt+=rRef1;
    }
}

void MirrorXPoly(XPolygon& rPoly, const Point& rRef1, const Point& rRef2)
{
    sal_uInt16 nCount=rPoly.GetPointCount();
    for (sal_uInt16 i=0; i<nCount; i++) {
        MirrorPoint(rPoly[i],rRef1,rRef2);
    }
}

void ShearPoly(tools::Polygon& rPoly, const Point& rRef, double tn)
{
    sal_uInt16 nCount=rPoly.GetSize();
    for (sal_uInt16 i=0; i<nCount; i++) {
        ShearPoint(rPoly[i],rRef,tn);
    }
}

void ShearXPoly(XPolygon& rPoly, const Point& rRef, double tn, bool bVShear)
{
    sal_uInt16 nCount=rPoly.GetPointCount();
    for (sal_uInt16 i=0; i<nCount; i++) {
        ShearPoint(rPoly[i],rRef,tn,bVShear);
    }
}

double CrookRotateXPoint(Point& rPnt, Point* pC1, Point* pC2, const Point& rCenter,
                         const Point& rRad, double& rSin, double& rCos, bool bVert)
{
    bool bC1=pC1!=nullptr;
    bool bC2=pC2!=nullptr;
    tools::Long x0=rPnt.X();
    tools::Long y0=rPnt.Y();
    tools::Long cx=rCenter.X();
    tools::Long cy=rCenter.Y();
    double nAngle=GetCrookAngle(rPnt,rCenter,rRad,bVert);
    double sn=sin(nAngle);
    double cs=cos(nAngle);
    RotatePoint(rPnt,rCenter,sn,cs);
    if (bC1) {
        if (bVert) {
            // move into the direction of the center, as a basic position for the rotation
            pC1->AdjustY( -y0 );
            // resize, account for the distance from the center
            pC1->setY(basegfx::fround<tools::Long>(static_cast<double>(pC1->Y()) /rRad.X()*(cx-pC1->X())) );
            pC1->AdjustY(cy );
        } else {
            // move into the direction of the center, as a basic position for the rotation
            pC1->AdjustX( -x0 );
            // resize, account for the distance from the center
            tools::Long nPntRad=cy-pC1->Y();
            double nFact=static_cast<double>(nPntRad)/static_cast<double>(rRad.Y());
            pC1->setX(basegfx::fround<tools::Long>(static_cast<double>(pC1->X()) * nFact));
            pC1->AdjustX(cx );
        }
        RotatePoint(*pC1,rCenter,sn,cs);
    }
    if (bC2) {
        if (bVert) {
            // move into the direction of the center, as a basic position for the rotation
            pC2->AdjustY( -y0 );
            // resize, account for the distance from the center
            pC2->setY(basegfx::fround<tools::Long>(static_cast<double>(pC2->Y()) /rRad.X()*(rCenter.X()-pC2->X())) );
            pC2->AdjustY(cy );
        } else {
            // move into the direction of the center, as a basic position for the rotation
            pC2->AdjustX( -x0 );
            // resize, account for the distance from the center
            tools::Long nPntRad=rCenter.Y()-pC2->Y();
            double nFact=static_cast<double>(nPntRad)/static_cast<double>(rRad.Y());
            pC2->setX(basegfx::fround<tools::Long>(static_cast<double>(pC2->X()) * nFact));
            pC2->AdjustX(cx );
        }
        RotatePoint(*pC2,rCenter,sn,cs);
    }
    rSin=sn;
    rCos=cs;
    return nAngle;
}

double CrookSlantXPoint(Point& rPnt, Point* pC1, Point* pC2, const Point& rCenter,
                        const Point& rRad, double& rSin, double& rCos, bool bVert)
{
    bool bC1=pC1!=nullptr;
    bool bC2=pC2!=nullptr;
    tools::Long x0=rPnt.X();
    tools::Long y0=rPnt.Y();
    tools::Long dx1=0,dy1=0;
    tools::Long dxC1=0,dyC1=0;
    tools::Long dxC2=0,dyC2=0;
    if (bVert) {
        tools::Long nStart=rCenter.X()-rRad.X();
        dx1=rPnt.X()-nStart;
        rPnt.setX(nStart );
        if (bC1) {
            dxC1=pC1->X()-nStart;
            pC1->setX(nStart );
        }
        if (bC2) {
            dxC2=pC2->X()-nStart;
            pC2->setX(nStart );
        }
    } else {
        tools::Long nStart=rCenter.Y()-rRad.Y();
        dy1=rPnt.Y()-nStart;
        rPnt.setY(nStart );
        if (bC1) {
            dyC1=pC1->Y()-nStart;
            pC1->setY(nStart );
        }
        if (bC2) {
            dyC2=pC2->Y()-nStart;
            pC2->setY(nStart );
        }
    }
    double nAngle=GetCrookAngle(rPnt,rCenter,rRad,bVert);
    double sn=sin(nAngle);
    double cs=cos(nAngle);
    RotatePoint(rPnt,rCenter,sn,cs);
    if (bC1) { if (bVert) pC1->AdjustY( -(y0-rCenter.Y()) ); else pC1->AdjustX( -(x0-rCenter.X()) ); RotatePoint(*pC1,rCenter,sn,cs); }
    if (bC2) { if (bVert) pC2->AdjustY( -(y0-rCenter.Y()) ); else pC2->AdjustX( -(x0-rCenter.X()) ); RotatePoint(*pC2,rCenter,sn,cs); }
    if (bVert) {
        rPnt.AdjustX(dx1 );
        if (bC1) pC1->AdjustX(dxC1 );
        if (bC2) pC2->AdjustX(dxC2 );
    } else {
        rPnt.AdjustY(dy1 );
        if (bC1) pC1->AdjustY(dyC1 );
        if (bC2) pC2->AdjustY(dyC2 );
    }
    rSin=sn;
    rCos=cs;
    return nAngle;
}

double CrookStretchXPoint(Point& rPnt, Point* pC1, Point* pC2, const Point& rCenter,
                          const Point& rRad, double& rSin, double& rCos, bool bVert,
                          const tools::Rectangle& rRefRect)
{
    tools::Long y0=rPnt.Y();
    CrookSlantXPoint(rPnt,pC1,pC2,rCenter,rRad,rSin,rCos,bVert);
    if (bVert) {
    } else {
        tools::Long nTop=rRefRect.Top();
        tools::Long nBtm=rRefRect.Bottom();
        tools::Long nHgt=nBtm-nTop;
        tools::Long dy=rPnt.Y()-y0;
        double a=static_cast<double>(y0-nTop)/nHgt;
        a*=dy;
        rPnt.setY(y0 + basegfx::fround<tools::Long>(a));
    }
    return 0.0;
}


void CrookRotatePoly(XPolygon& rPoly, const Point& rCenter, const Point& rRad, bool bVert)
{
    double nSin,nCos;
    sal_uInt16 nPointCnt=rPoly.GetPointCount();
    sal_uInt16 i=0;
    while (i<nPointCnt) {
        Point* pPnt=&rPoly[i];
        Point* pC1=nullptr;
        Point* pC2=nullptr;
        if (i+1<nPointCnt && rPoly.IsControl(i)) { // control point to the left
            pC1=pPnt;
            i++;
            pPnt=&rPoly[i];
        }
        i++;
        if (i<nPointCnt && rPoly.IsControl(i)) { // control point to the right
            pC2=&rPoly[i];
            i++;
        }
        CrookRotateXPoint(*pPnt,pC1,pC2,rCenter,rRad,nSin,nCos,bVert);
    }
}

void CrookSlantPoly(XPolygon& rPoly, const Point& rCenter, const Point& rRad, bool bVert)
{
    double nSin,nCos;
    sal_uInt16 nPointCnt=rPoly.GetPointCount();
    sal_uInt16 i=0;
    while (i<nPointCnt) {
        Point* pPnt=&rPoly[i];
        Point* pC1=nullptr;
        Point* pC2=nullptr;
        if (i+1<nPointCnt && rPoly.IsControl(i)) { // control point to the left
            pC1=pPnt;
            i++;
            pPnt=&rPoly[i];
        }
        i++;
        if (i<nPointCnt && rPoly.IsControl(i)) { // control point to the right
            pC2=&rPoly[i];
            i++;
        }
        CrookSlantXPoint(*pPnt,pC1,pC2,rCenter,rRad,nSin,nCos,bVert);
    }
}

void CrookStretchPoly(XPolygon& rPoly, const Point& rCenter, const Point& rRad, bool bVert, const tools::Rectangle& rRefRect)
{
    double nSin,nCos;
    sal_uInt16 nPointCnt=rPoly.GetPointCount();
    sal_uInt16 i=0;
    while (i<nPointCnt) {
        Point* pPnt=&rPoly[i];
        Point* pC1=nullptr;
        Point* pC2=nullptr;
        if (i+1<nPointCnt && rPoly.IsControl(i)) { //  control point to the left
            pC1=pPnt;
            i++;
            pPnt=&rPoly[i];
        }
        i++;
        if (i<nPointCnt && rPoly.IsControl(i)) { // control point to the right
            pC2=&rPoly[i];
            i++;
        }
        CrookStretchXPoint(*pPnt,pC1,pC2,rCenter,rRad,nSin,nCos,bVert,rRefRect);
    }
}


void CrookRotatePoly(XPolyPolygon& rPoly, const Point& rCenter, const Point& rRad, bool bVert)
{
    sal_uInt16 nPolyCount=rPoly.Count();
    for (sal_uInt16 nPolyNum=0; nPolyNum<nPolyCount; nPolyNum++) {
        CrookRotatePoly(rPoly[nPolyNum],rCenter,rRad,bVert);
    }
}

void CrookSlantPoly(XPolyPolygon& rPoly, const Point& rCenter, const Point& rRad, bool bVert)
{
    sal_uInt16 nPolyCount=rPoly.Count();
    for (sal_uInt16 nPolyNum=0; nPolyNum<nPolyCount; nPolyNum++) {
        CrookSlantPoly(rPoly[nPolyNum],rCenter,rRad,bVert);
    }
}

void CrookStretchPoly(XPolyPolygon& rPoly, const Point& rCenter, const Point& rRad, bool bVert, const tools::Rectangle& rRefRect)
{
    sal_uInt16 nPolyCount=rPoly.Count();
    for (sal_uInt16 nPolyNum=0; nPolyNum<nPolyCount; nPolyNum++) {
        CrookStretchPoly(rPoly[nPolyNum],rCenter,rRad,bVert,rRefRect);
    }
}


Degree100 GetAngle(const Point& rPnt)
{
    Degree100 a;
    if (rPnt.Y()==0) {
        if (rPnt.X()<0) a=-18000_deg100;
    } else if (rPnt.X()==0) {
        if (rPnt.Y()>0) a=-9000_deg100;
        else a=9000_deg100;
    } else {
        a = Degree100(basegfx::fround(basegfx::rad2deg<100>(atan2(-static_cast<double>(rPnt.Y()), static_cast<double>(rPnt.X())))));
    }
    return a;
}

Degree100 NormAngle18000(Degree100 a)
{
    while (a<-18000_deg100) a+=36000_deg100;
    while (a>=18000_deg100) a-=36000_deg100;
    return a;
}

Degree100 NormAngle36000(Degree100 a)
{
    a %= 36000_deg100;
    if (a < 0_deg100)
        a += 36000_deg100;
    return a;
}

sal_uInt16 GetAngleSector(Degree100 nAngle) { return (NormAngle36000(nAngle) / 9000_deg100).get(); }

tools::Long GetLen(const Point& rPnt)
{
    tools::Long x=std::abs(rPnt.X());
    tools::Long y=std::abs(rPnt.Y());
    if (x+y<0x8000) { // because 7FFF * 7FFF * 2 = 7FFE0002
        x*=x;
        y*=y;
        x+=y;
        x = basegfx::fround<tools::Long>(sqrt(x));
        return x;
    } else {
        double nx=x;
        double ny=y;
        nx*=nx;
        ny*=ny;
        nx+=ny;
        nx=sqrt(nx);
        if (nx>0x7FFFFFFF) {
            return 0x7FFFFFFF; // we can't go any further, for fear of an overrun!
        } else {
            return basegfx::fround<tools::Long>(nx);
        }
    }
}


void GeoStat::RecalcSinCos()
{
    if (m_nRotationAngle==0_deg100) {
        mfSinRotationAngle=0.0;
        mfCosRotationAngle=1.0;
    } else {
        double a = toRadians(m_nRotationAngle);
        mfSinRotationAngle=sin(a);
        mfCosRotationAngle=cos(a);
    }
}

void GeoStat::RecalcTan()
{
    if (m_nShearAngle==0_deg100) {
        mfTanShearAngle=0.0;
    } else {
        double a = toRadians(m_nShearAngle);
        mfTanShearAngle=tan(a);
    }
}


tools::Polygon Rect2Poly(const tools::Rectangle& rRect, const GeoStat& rGeo)
{
    tools::Polygon aPol(5);
    aPol[0]=rRect.TopLeft();
    aPol[1]=rRect.TopRight();
    aPol[2]=rRect.BottomRight();
    aPol[3]=rRect.BottomLeft();
    aPol[4]=rRect.TopLeft();
    if (rGeo.m_nShearAngle) ShearPoly(aPol,rRect.TopLeft(),rGeo.mfTanShearAngle);
    if (rGeo.m_nRotationAngle) RotatePoly(aPol,rRect.TopLeft(),rGeo.mfSinRotationAngle,rGeo.mfCosRotationAngle);
    return aPol;
}

namespace svx
{
tools::Rectangle polygonToRectangle(const tools::Polygon& rPolygon, GeoStat& rGeo)
{
    rGeo.m_nRotationAngle = GetAngle(rPolygon[1] - rPolygon[0]);
    rGeo.m_nRotationAngle = NormAngle36000(rGeo.m_nRotationAngle);

    // rotation successful
    rGeo.RecalcSinCos();

    Point aPoint1(rPolygon[1] - rPolygon[0]);
    if (rGeo.m_nRotationAngle)
        RotatePoint(aPoint1, Point(0,0), -rGeo.mfSinRotationAngle, rGeo.mfCosRotationAngle); // -Sin to reverse rotation
    tools::Long nWidth = aPoint1.X();

    Point aPoint0(rPolygon[0]);
    Point aPoint3(rPolygon[3] - rPolygon[0]);
    if (rGeo.m_nRotationAngle)
        RotatePoint(aPoint3, Point(0,0), -rGeo.mfSinRotationAngle, rGeo.mfCosRotationAngle); // -Sin to reverse rotation
    tools::Long nHeight = aPoint3.Y();

    Degree100 nShearAngle = GetAngle(aPoint3);
    nShearAngle -= 27000_deg100; // ShearWink is measured against a vertical line
    nShearAngle = -nShearAngle;  // negating, because '+' is shearing clock-wise

    bool bMirror = aPoint3.Y() < 0;
    if (bMirror)
    {   // "exchange of points" when mirroring
        nHeight = -nHeight;
        nShearAngle += 18000_deg100;
        aPoint0 = rPolygon[3];
    }

    nShearAngle = NormAngle18000(nShearAngle);
    if (nShearAngle < -9000_deg100 || nShearAngle > 9000_deg100)
    {
        nShearAngle = NormAngle18000(nShearAngle + 18000_deg100);
    }

    if (nShearAngle < -SDRMAXSHEAR)
        nShearAngle = -SDRMAXSHEAR; // limit ShearWinkel (shear angle) to +/- 89.00 deg

    if (nShearAngle > SDRMAXSHEAR)
        nShearAngle = SDRMAXSHEAR;

    rGeo.m_nShearAngle = nShearAngle;
    rGeo.RecalcTan();

    Point aRU(aPoint0);
    aRU.AdjustX(nWidth);
    aRU.AdjustY(nHeight);

    return tools::Rectangle(aPoint0, aRU);
}

} // end svx

void OrthoDistance8(const Point& rPt0, Point& rPt, bool bBigOrtho)
{
    tools::Long dx=rPt.X()-rPt0.X();
    tools::Long dy=rPt.Y()-rPt0.Y();
    tools::Long dxa=std::abs(dx);
    tools::Long dya=std::abs(dy);
    if (dx==0 || dy==0 || dxa==dya) return;
    if (dxa>=dya*2) { rPt.setY(rPt0.Y() ); return; }
    if (dya>=dxa*2) { rPt.setX(rPt0.X() ); return; }
    if ((dxa<dya) != bBigOrtho) {
        rPt.setY(rPt0.Y()+(dxa* (dy>=0 ? 1 : -1) ) );
    } else {
        rPt.setX(rPt0.X()+(dya* (dx>=0 ? 1 : -1) ) );
    }
}

void OrthoDistance4(const Point& rPt0, Point& rPt, bool bBigOrtho)
{
    tools::Long dx=rPt.X()-rPt0.X();
    tools::Long dy=rPt.Y()-rPt0.Y();
    tools::Long dxa=std::abs(dx);
    tools::Long dya=std::abs(dy);
    if ((dxa<dya) != bBigOrtho) {
        rPt.setY(rPt0.Y()+(dxa* (dy>=0 ? 1 : -1) ) );
    } else {
        rPt.setX(rPt0.X()+(dya* (dx>=0 ? 1 : -1) ) );
    }
}


tools::Long BigMulDiv(tools::Long nVal, tools::Long nMul, tools::Long nDiv)
{
    if (!nDiv)
        return 0x7fffffff;
    return BigInt::Scale(nVal, nMul, nDiv);
}

static FrPair toPair(o3tl::Length eFrom, o3tl::Length eTo)
{
    const auto [nNum, nDen] = o3tl::getConversionMulDiv(eFrom, eTo);
    return FrPair(nNum, nDen);
}

// How many eU units fit into a mm, respectively an inch?
// Or: How many mm, respectively inches, are there in an eU (and then give me the inverse)

static FrPair GetInchOrMM(MapUnit eU)
{
    switch (eU) {
        case MapUnit::Map1000thInch: return toPair(o3tl::Length::in, o3tl::Length::in1000);
        case MapUnit::Map100thInch : return toPair(o3tl::Length::in, o3tl::Length::in100);
        case MapUnit::Map10thInch  : return toPair(o3tl::Length::in, o3tl::Length::in10);
        case MapUnit::MapInch       : return toPair(o3tl::Length::in, o3tl::Length::in);
        case MapUnit::MapPoint      : return toPair(o3tl::Length::in, o3tl::Length::pt);
        case MapUnit::MapTwip       : return toPair(o3tl::Length::in, o3tl::Length::twip);
        case MapUnit::Map100thMM   : return toPair(o3tl::Length::mm, o3tl::Length::mm100);
        case MapUnit::Map10thMM    : return toPair(o3tl::Length::mm, o3tl::Length::mm10);
        case MapUnit::MapMM         : return toPair(o3tl::Length::mm, o3tl::Length::mm);
        case MapUnit::MapCM         : return toPair(o3tl::Length::mm, o3tl::Length::cm);
        case MapUnit::MapPixel      : {
            ScopedVclPtrInstance< VirtualDevice > pVD;
            pVD->SetMapMode(MapMode(MapUnit::Map100thMM));
            Point aP(pVD->PixelToLogic(Point(64,64))); // 64 pixels for more accuracy
            return FrPair(6400,aP.X(),6400,aP.Y());
        }
        case MapUnit::MapAppFont: case MapUnit::MapSysFont: {
            ScopedVclPtrInstance< VirtualDevice > pVD;
            pVD->SetMapMode(MapMode(eU));
            Point aP(pVD->LogicToPixel(Point(32,32))); // 32 units for more accuracy
            pVD->SetMapMode(MapMode(MapUnit::Map100thMM));
            aP=pVD->PixelToLogic(aP);
            return FrPair(3200,aP.X(),3200,aP.Y());
        }
        default: break;
    }
    return Fraction(1,1);
}

// Calculate the factor that we need to convert units from eS to eD.
// e. g. GetMapFactor(UNIT_MM,UNIT_100TH_MM) => 100.

FrPair GetMapFactor(MapUnit eS, MapUnit eD)
{
    if (eS==eD) return FrPair(1,1,1,1);
    const auto eFrom = MapToO3tlLength(eS, o3tl::Length::invalid);
    const auto eTo = MapToO3tlLength(eD, o3tl::Length::invalid);
    if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid)
        return toPair(eFrom, eTo);
    FrPair aS(GetInchOrMM(eS));
    FrPair aD(GetInchOrMM(eD));
    bool bSInch=IsInch(eS);
    bool bDInch=IsInch(eD);
    FrPair aRet(aD.X()/aS.X(),aD.Y()/aS.Y());
    if (bSInch && !bDInch) { aRet.X()*=Fraction(127,5); aRet.Y()*=Fraction(127,5); }
    if (!bSInch && bDInch) { aRet.X()*=Fraction(5,127); aRet.Y()*=Fraction(5,127); }
    return aRet;
};

FrPair GetMapFactor(FieldUnit eS, FieldUnit eD)
{
    if (eS==eD) return FrPair(1,1,1,1);
    auto eFrom = FieldToO3tlLength(eS), eTo = FieldToO3tlLength(eD);
    if (eFrom == o3tl::Length::invalid)
    {
        if (eTo == o3tl::Length::invalid)
            return FrPair(1,1,1,1);
        eFrom = IsInch(eD) ? o3tl::Length::in : o3tl::Length::mm;
    }
    else if (eTo == o3tl::Length::invalid)
        eTo = IsInch(eS) ? o3tl::Length::in : o3tl::Length::mm;
    return toPair(eFrom, eTo);
};

void SdrFormatter::Undirty()
{
    const o3tl::Length eFrom = MapToO3tlLength(m_eSrcMU, o3tl::Length::invalid);
    const o3tl::Length eTo = MapToO3tlLength(m_eDstMU, o3tl::Length::invalid);
    if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid)
    {
        const auto [mul, div] = o3tl::getConversionMulDiv(eFrom, eTo);
        sal_Int64 nMul = mul;
        sal_Int64 nDiv = div;
        short nComma = 0;

        // shorten trailing zeros for dividend
        while (0 == (nMul % 10))
        {
            nComma--;
            nMul /= 10;
        }

        // shorten trailing zeros for divisor
        while (0 == (nDiv % 10))
        {
            nComma++;
            nDiv /= 10;
        }
        m_nMul = nMul;
        m_nDiv = nDiv;
        m_nComma = nComma;
    }
    else
    {
        m_nMul = m_nDiv = 1;
        m_nComma = 0;
    }
    m_bDirty=false;
}


OUString SdrFormatter::GetStr(tools::Long nVal) const
{
    static constexpr OUString aNullCode(u"0"_ustr);

    if(!nVal)
    {
        return aNullCode;
    }

    // we may lose some decimal places here, because of MulDiv instead of Real
    bool bNeg(nVal < 0);
    SvtSysLocale aSysLoc;
    const LocaleDataWrapper& rLoc = aSysLoc.GetLocaleData();

    if (m_bDirty)
        const_cast<SdrFormatter*>(this)->Undirty();

    sal_Int16 nC(m_nComma);

    if(bNeg)
        nVal = -nVal;

    while(nC <= -3)
    {
        nVal *= 1000;
        nC += 3;
    }

    while(nC <= -1)
    {
        nVal *= 10;
        nC++;
    }

    if(m_nMul != m_nDiv)
        nVal = BigMulDiv(nVal, m_nMul, m_nDiv);

    OUStringBuffer aStr = OUString::number(nVal);

    if(nC > 0 && aStr.getLength() <= nC )
    {
        // decimal separator necessary
        sal_Int32 nCount(nC - aStr.getLength());

        if(nCount >= 0 && LocaleDataWrapper::isNumLeadingZero())
            nCount++;

        for(sal_Int32  i=0; i<nCount; i++)
            aStr.insert(0, aNullCode);

        // remove superfluous decimal points
        sal_Int32 nNumDigits(LocaleDataWrapper::getNumDigits());
        sal_Int32 nWeg(nC - nNumDigits);

        if(nWeg > 0)
        {
            // TODO: we should round here
            aStr.remove(aStr.getLength() - nWeg, nWeg);
            nC = nNumDigits;
        }
    }

    // remember everything before the decimal separator for later
    sal_Int32 nForComma(aStr.getLength() - nC);

    if(nC > 0)
    {
        // insert comma char (decimal separator)
        // remove trailing zeros
        while(nC > 0 && aStr[aStr.getLength() - 1] == aNullCode.getStr()[0])
        {
            aStr.remove(aStr.getLength() - 1, 1);
            nC--;
        }

        if(nC > 0)
        {
            // do we still have decimal places?
            sal_Unicode cDec(rLoc.getNumDecimalSep()[0]);
            aStr.insert(nForComma, cDec);
        }
    }

    // add in thousands separator (if necessary)
    if( nForComma > 3 )
    {
        const OUString& aThoSep( rLoc.getNumThousandSep() );
        if ( aThoSep.getLength() > 0 )
        {
            sal_Unicode cTho( aThoSep[0] );
            sal_Int32 i(nForComma - 3);

            while(i > 0)
            {
                aStr.insert(i, cTho);
                i -= 3;
            }
        }
    }

    if(aStr.isEmpty())
        aStr.append(aNullCode);

    if(bNeg && (aStr.getLength() > 1 || aStr[0] != aNullCode.getStr()[0]))
    {
        aStr.insert(0, "-");
    }

    return aStr.makeStringAndClear();
}

OUString SdrFormatter::GetUnitStr(MapUnit eUnit)
{
    switch(eUnit)
    {
        // metrically
        case MapUnit::Map100thMM   :
            return u"/100mm"_ustr;
        case MapUnit::Map10thMM    :
            return u"/10mm"_ustr;
        case MapUnit::MapMM         :
            return u"mm"_ustr;
        case MapUnit::MapCM         :
            return u"cm"_ustr;

        // Inch
        case MapUnit::Map1000thInch:
            return u"/1000\""_ustr;
        case MapUnit::Map100thInch :
            return u"/100\""_ustr;
        case MapUnit::Map10thInch  :
            return u"/10\""_ustr;
        case MapUnit::MapInch       :
            return u"\""_ustr;
        case MapUnit::MapPoint      :
            return u"pt"_ustr;
        case MapUnit::MapTwip       :
            return u"twip"_ustr;

        // others
        case MapUnit::MapPixel      :
            return u"pixel"_ustr;
        case MapUnit::MapSysFont    :
            return u"sysfont"_ustr;
        case MapUnit::MapAppFont    :
            return u"appfont"_ustr;
        case MapUnit::MapRelative   :
            return u"%"_ustr;
        default:
            return OUString();
    }
}

OUString SdrFormatter::GetUnitStr(FieldUnit eUnit)
{
    switch(eUnit)
    {
        default             :
        case FieldUnit::NONE     :
        case FieldUnit::CUSTOM   :
            return OUString();

        // metrically
        case FieldUnit::MM_100TH:
            return u"/100mm"_ustr;
        case FieldUnit::MM     :
            return u"mm"_ustr;
        case FieldUnit::CM     :
            return u"cm"_ustr;
        case FieldUnit::M      :
            return u"m"_ustr;
        case FieldUnit::KM     :
            return u"km"_ustr;

        // Inch
        case FieldUnit::TWIP   :
            return u"twip"_ustr;
        case FieldUnit::POINT  :
            return u"pt"_ustr;
        case FieldUnit::PICA   :
            return u"pica"_ustr;
        case FieldUnit::INCH   :
            return u"\""_ustr;
        case FieldUnit::FOOT   :
            return u"ft"_ustr;
        case FieldUnit::MILE   :
            return u"mile(s)"_ustr;

        // others
        case FieldUnit::PERCENT:
            return u"%"_ustr;
    }
}


/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
