/*
 *  PathOutline.cpp
 *  nlivarot
 *
 *  Created by fred on Fri Nov 28 2003.
 *
 */

#include "livarot/Path.h"
#include "livarot/path-description.h"
#include <libnr/nr-point-fns.h>

/*
 * the "outliner"
 * takes a sequence of path commands and produces a set of commands that approximates the offset
 * result is stored in dest (that paremeter is handed to all the subfunctions)
 * not that the result is in general not mathematically correct; you can end up with unwanted holes in your
 * beautiful offset. a better way is to do path->polyline->polygon->offset of polygon->polyline(=contours of the polygon)->path
 * but computing offsets of the path is faster...
 */

// outline of a path.
// computed by making 2 offsets, one of the "left" side of the path, one of the right side, and then glueing the two
// the left side has to be reversed to make a contour
void Path::Outline(Path *dest, double width, JoinType join, ButtType butt, double miter)
{
    if ( descr_flags & descr_adding_bezier ) {
        CancelBezier();
    }
    if ( descr_flags & descr_doing_subpath ) {
        CloseSubpath();
    }
    if ( descr_cmd.size() <= 1 ) {
        return;
    }
    if ( dest == NULL ) {
        return;
    }

    dest->Reset();
    dest->SetBackData(false);

    outline_callbacks calls;
    NR::Point endButt;
    NR::Point endPos;
    calls.cubicto = StdCubicTo;
    calls.bezierto = StdBezierTo;
    calls.arcto = StdArcTo;

    Path *rev = new Path;

    // we repeat the offset contour creation for each subpath
    int curP = 0;
    do {
        int lastM = curP;
        do {
            curP++;
            if (curP >= int(descr_cmd.size())) {
                break;
            }
            int typ = descr_cmd[curP]->getType();
            if (typ == descr_moveto) {
                break;
            }
        } while (curP < int(descr_cmd.size()));

        if (curP >= int(descr_cmd.size())) {
            curP = descr_cmd.size();
        }

        if (curP > lastM + 1) {
            // we have isolated a subpath, now we make a reversed version of it
            // we do so by taking the subpath in the reverse and constructing a path as appropriate
            // the construct is stored in "rev"
            int curD = curP - 1;
            NR::Point curX;
            NR::Point nextX;
            int firstTyp = descr_cmd[curD]->getType();
            bool const needClose = (firstTyp == descr_close);
            while (curD > lastM && descr_cmd[curD]->getType() == descr_close) {
                curD--;
            }

            int realP = curD + 1;
            if (curD > lastM) {
                curX = PrevPoint(curD);
                rev->Reset ();
                rev->MoveTo(curX);
                while (curD > lastM) {
                    int const typ = descr_cmd[curD]->getType();
                    if (typ == descr_moveto) {
                        //                                              rev->Close();
                        curD--;
                    } else if (typ == descr_forced) {
                        //                                              rev->Close();
                        curD--;
                    } else if (typ == descr_lineto) {
                        nextX = PrevPoint (curD - 1);
                        rev->LineTo (nextX);
                        curX = nextX;
                        curD--;
                    } else if (typ == descr_cubicto) {
                        PathDescrCubicTo* nData = dynamic_cast<PathDescrCubicTo*>(descr_cmd[curD]);
                        nextX = PrevPoint (curD - 1);
                        NR::Point  isD=-nData->start;
                        NR::Point  ieD=-nData->end;
                        rev->CubicTo (nextX, ieD,isD);
                        curX = nextX;
                        curD--;
                    } else if (typ == descr_arcto) {
                        PathDescrArcTo* nData = dynamic_cast<PathDescrArcTo*>(descr_cmd[curD]);
                        nextX = PrevPoint (curD - 1);
                        rev->ArcTo (nextX, nData->rx,nData->ry,nData->angle,nData->large,nData->clockwise);
                        curX = nextX;
                        curD--;
                    } else if (typ == descr_bezierto) {
                        nextX = PrevPoint (curD - 1);
                        rev->LineTo (nextX);
                        curX = nextX;
                        curD--;
                    }  else if (typ == descr_interm_bezier) {
                        int nD = curD - 1;
                        while (nD > lastM && descr_cmd[nD]->getType() != descr_bezierto) nD--;
                        if ((descr_cmd[nD]->getType()) !=  descr_bezierto)  {
                            // pas trouve le debut!?
                            // Not find the start?!
                            nextX = PrevPoint (nD);
                            rev->LineTo (nextX);
                            curX = nextX;
                        } else {
                            nextX = PrevPoint (nD - 1);
                            rev->BezierTo (nextX);
                            for (int i = curD; i > nD; i--) {
                                PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[i]);
                                rev->IntermBezierTo (nData->p);
                            }
                            rev->EndBezierTo ();
                            curX = nextX;
                        }
                        curD = nD - 1;
                    } else {
                        curD--;
                    }
                }

                // offset the paths and glue everything
                // actual offseting is done in SubContractOutline()
                if (needClose) {
                    rev->Close ();
                    rev->SubContractOutline (0, rev->descr_cmd.size(),
                                             dest, calls, 0.0025 * width * width, width,
                                             join, butt, miter, true, false, endPos, endButt);
                    SubContractOutline (lastM, realP + 1 - lastM,
                                        dest, calls, 0.0025 * width * width,
                                        width, join, butt, miter, true, false, endPos, endButt);
                } else {
                    rev->SubContractOutline (0, rev->descr_cmd.size(),
                                             dest, calls,  0.0025 * width * width, width,
                                             join, butt, miter, false, false, endPos, endButt);
                    NR::Point endNor=endButt.ccw();
                    if (butt == butt_round) {
                        dest->ArcTo (endPos+width*endNor,  1.0001 * width, 1.0001 * width, 0.0, true, true);
                    }  else if (butt == butt_square) {
                        dest->LineTo (endPos-width*endNor+width*endButt);
                        dest->LineTo (endPos+width*endNor+width*endButt);
                        dest->LineTo (endPos+width*endNor);
                    }  else if (butt == butt_pointy) {
                        dest->LineTo (endPos+width*endButt);
                        dest->LineTo (endPos+width*endNor);
                    } else {
                        dest->LineTo (endPos+width*endNor);
                    }
                    SubContractOutline (lastM, realP - lastM,
                                        dest, calls, 0.0025 * width * width,  width, join, butt,
                                        miter, false, true, endPos, endButt);

                    endNor=endButt.ccw();
                    if (butt == butt_round) {
                        dest->ArcTo (endPos+width*endNor, 1.0001 * width, 1.0001 * width, 0.0, true, true);
                    } else if (butt == butt_square) {
                        dest->LineTo (endPos-width*endNor+width*endButt);
                        dest->LineTo (endPos+width*endNor+width*endButt);
                        dest->LineTo (endPos+width*endNor);
                    } else if (butt == butt_pointy) {
                        dest->LineTo (endPos+width*endButt);
                        dest->LineTo (endPos+width*endNor);
                    } else {
                        dest->LineTo (endPos+width*endNor);
                    }
                    dest->Close ();
                }
            } // if (curD > lastM)
        } // if (curP > lastM + 1)

    } while (curP < int(descr_cmd.size()));

    delete rev;
}

// versions for outlining closed path: they only make one side of the offset contour
void
Path::OutsideOutline (Path * dest, double width, JoinType join, ButtType butt,
                      double miter)
{
	if (descr_flags & descr_adding_bezier) {
		CancelBezier();
	}
	if (descr_flags & descr_doing_subpath) {
		CloseSubpath();
	}
	if (int(descr_cmd.size()) <= 1) return;
	if (dest == NULL) return;
	dest->Reset ();
	dest->SetBackData (false);

	outline_callbacks calls;
	NR::Point endButt, endPos;
	calls.cubicto = StdCubicTo;
	calls.bezierto = StdBezierTo;
	calls.arcto = StdArcTo;
	SubContractOutline (0, descr_cmd.size(),
                            dest, calls, 0.0025 * width * width, width, join, butt,
			    miter, true, false, endPos, endButt);
}

void
Path::InsideOutline (Path * dest, double width, JoinType join, ButtType butt,
                     double miter)
{
	if ( descr_flags & descr_adding_bezier ) {
		CancelBezier();
	}
	if ( descr_flags & descr_doing_subpath ) {
		CloseSubpath();
	}
	if (int(descr_cmd.size()) <= 1) return;
	if (dest == NULL) return;
	dest->Reset ();
	dest->SetBackData (false);

	outline_callbacks calls;
	NR::Point endButt, endPos;
	calls.cubicto = StdCubicTo;
	calls.bezierto = StdBezierTo;
	calls.arcto = StdArcTo;

	Path *rev = new Path;

	int curP = 0;
	do {
		int lastM = curP;
		do {
			curP++;
			if (curP >= int(descr_cmd.size())) break;
			int typ = descr_cmd[curP]->getType();
			if (typ == descr_moveto) break;
		} while (curP < int(descr_cmd.size()));
		if (curP >= int(descr_cmd.size()))  curP = descr_cmd.size();
		if (curP > lastM + 1) {
			// Otherwise there's only one point.  (tr: or "only a point")
			// [sinon il n'y a qu'un point]
			int curD = curP - 1;
			NR::Point curX;
			NR::Point nextX;
			while (curD > lastM && (descr_cmd[curD]->getType()) == descr_close) curD--;
			if (curD > lastM) {
				curX = PrevPoint (curD);
				rev->Reset ();
				rev->MoveTo (curX);
				while (curD > lastM) {
					int typ = descr_cmd[curD]->getType();
					if (typ == descr_moveto) {
						rev->Close ();
						curD--;
					} else if (typ == descr_forced) {
						curD--;
					} else if (typ == descr_lineto) {
						nextX = PrevPoint (curD - 1);
						rev->LineTo (nextX);
						curX = nextX;
						curD--;
					}  else if (typ == descr_cubicto) {
                                            PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo*>(descr_cmd[curD]);
						nextX = PrevPoint (curD - 1);
						NR::Point  isD=-nData->start;
						NR::Point  ieD=-nData->end;
						rev->CubicTo (nextX, ieD,isD);
						curX = nextX;
						curD--;
					} else if (typ == descr_arcto) {
                                            PathDescrArcTo* nData = dynamic_cast<PathDescrArcTo*>(descr_cmd[curD]);
						nextX = PrevPoint (curD - 1);
						rev->ArcTo (nextX, nData->rx,nData->ry,nData->angle,nData->large,nData->clockwise);
						curX = nextX;
						curD--;
					} else if (typ == descr_bezierto) {
						nextX = PrevPoint (curD - 1);
						rev->LineTo (nextX);
						curX = nextX;
						curD--;
					} else if (typ == descr_interm_bezier) {
						int nD = curD - 1;
						while (nD > lastM && (descr_cmd[nD]->getType()) != descr_bezierto) nD--;
						if (descr_cmd[nD]->getType() != descr_bezierto) {
							// pas trouve le debut!?
							nextX = PrevPoint (nD);
							rev->LineTo (nextX);
							curX = nextX;
						} else {
							nextX = PrevPoint (nD - 1);
							rev->BezierTo (nextX);
							for (int i = curD; i > nD; i--) {
                                                            PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[i]);
								rev->IntermBezierTo (nData->p);
							}
							rev->EndBezierTo ();
							curX = nextX;
						}
						curD = nD - 1;
					} else {
						curD--;
					}
				}
				rev->Close ();
				rev->SubContractOutline (0, rev->descr_cmd.size(),
                                                         dest, calls, 0.0025 * width * width,
							 width, join, butt, miter, true, false,
							 endPos, endButt);
			}
		}
	}  while (curP < int(descr_cmd.size()));

	delete rev;
}


// the offset
// take each command and offset it.
// the bezier spline is split in a sequence of bezier curves, and these are transformed in cubic bezier (which is
// not hard since they are quadratic bezier)
// joins are put where needed
void Path::SubContractOutline(int off, int num_pd,
                              Path *dest, outline_callbacks & calls,
                              double tolerance, double width, JoinType join,
                              ButtType /*butt*/, double miter, bool closeIfNeeded,
                              bool skipMoveto, NR::Point &lastP, NR::Point &lastT)
{
    outline_callback_data callsData;

    callsData.orig = this;
    callsData.dest = dest;
    int curP = 1;

    // le moveto
    NR::Point curX;
    {
        int firstTyp = descr_cmd[off]->getType();
        if ( firstTyp != descr_moveto ) {
            curX[0] = curX[1] = 0;
            curP = 0;
        } else {
            PathDescrMoveTo* nData = dynamic_cast<PathDescrMoveTo*>(descr_cmd[off]);
            curX = nData->p;
        }
    }
    NR::Point curT(0, 0);

    bool doFirst = true;
    NR::Point firstP(0, 0);
    NR::Point firstT(0, 0);

	// et le reste, 1 par 1
	while (curP < num_pd)
	{
            int curD = off + curP;
		int nType = descr_cmd[curD]->getType();
		NR::Point nextX;
		NR::Point stPos, enPos, stTgt, enTgt, stNor, enNor;
		double stRad, enRad, stTle, enTle;
		if (nType == descr_forced)  {
			curP++;
		} else if (nType == descr_moveto) {
			PathDescrMoveTo* nData = dynamic_cast<PathDescrMoveTo*>(descr_cmd[curD]);
			nextX = nData->p;
			// et on avance
			if (doFirst) {
			} else {
				if (closeIfNeeded) {
					if ( NR::LInfty (curX- firstP) < 0.0001 ) {
						OutlineJoin (dest, firstP, curT, firstT, width, join,
									 miter);
						dest->Close ();
					}  else {
                                            PathDescrLineTo temp(firstP);

						TangentOnSegAt (0.0, curX, temp, stPos, stTgt,
										stTle);
						TangentOnSegAt (1.0, curX, temp, enPos, enTgt,
										enTle);
						stNor=stTgt.cw();
						enNor=enTgt.cw();

						// jointure
						{
							NR::Point pos;
							pos = curX;
							OutlineJoin (dest, pos, curT, stNor, width, join,
										 miter);
						}
						dest->LineTo (enPos+width*enNor);

						// jointure
						{
							NR::Point pos;
							pos = firstP;
							OutlineJoin (dest, enPos, enNor, firstT, width, join,
										 miter);
							dest->Close ();
						}
					}
				}
			}
			firstP = nextX;
			curP++;
		}
		else if (nType == descr_close)
		{
			if (doFirst == false)
			{
				if (NR::LInfty (curX - firstP) < 0.0001)
				{
					OutlineJoin (dest, firstP, curT, firstT, width, join,
								 miter);
					dest->Close ();
				}
				else
				{
                                    PathDescrLineTo temp(firstP);
					nextX = firstP;

					TangentOnSegAt (0.0, curX, temp, stPos, stTgt, stTle);
					TangentOnSegAt (1.0, curX, temp, enPos, enTgt, enTle);
					stNor=stTgt.cw();
					enNor=enTgt.cw();

					// jointure
					{
						OutlineJoin (dest, stPos, curT, stNor, width, join,
									 miter);
					}

					dest->LineTo (enPos+width*enNor);

					// jointure
					{
						OutlineJoin (dest, enPos, enNor, firstT, width, join,
									 miter);
						dest->Close ();
					}
				}
			}
			doFirst = true;
			curP++;
		}
		else if (nType == descr_lineto)
		{
			PathDescrLineTo* nData = dynamic_cast<PathDescrLineTo*>(descr_cmd[curD]);
			nextX = nData->p;
			// test de nullité du segment
			if (IsNulCurve (descr_cmd, curD, curX))
			{
				curP++;
				continue;
			}
			// et on avance
			TangentOnSegAt (0.0, curX, *nData, stPos, stTgt, stTle);
			TangentOnSegAt (1.0, curX, *nData, enPos, enTgt, enTle);
			stNor=stTgt.cw();
			enNor=enTgt.cw();

			lastP = enPos;
			lastT = enTgt;

			if (doFirst)
			{
				doFirst = false;
				firstP = stPos;
				firstT = stNor;
				if (skipMoveto)
				{
					skipMoveto = false;
				}
				else
					dest->MoveTo (curX+width*stNor);
			}
			else
			{
				// jointure
				NR::Point pos;
				pos = curX;
				OutlineJoin (dest, pos, curT, stNor, width, join, miter);
			}

			int n_d = dest->LineTo (nextX+width*enNor);
			if (n_d >= 0)
			{
				dest->descr_cmd[n_d]->associated = curP;
				dest->descr_cmd[n_d]->tSt = 0.0;
				dest->descr_cmd[n_d]->tEn = 1.0;
			}
			curP++;
		}
		else if (nType == descr_cubicto)
		{
			PathDescrCubicTo* nData = dynamic_cast<PathDescrCubicTo*>(descr_cmd[curD]);
			nextX = nData->p;
			// test de nullite du segment
			if (IsNulCurve (descr_cmd, curD, curX))
			{
				curP++;
				continue;
			}
			// et on avance
			TangentOnCubAt (0.0, curX, *nData, false, stPos, stTgt,
							stTle, stRad);
			TangentOnCubAt (1.0, curX, *nData, true, enPos, enTgt,
							enTle, enRad);
			stNor=stTgt.cw();
			enNor=enTgt.cw();

			lastP = enPos;
			lastT = enTgt;

			if (doFirst)
			{
				doFirst = false;
				firstP = stPos;
				firstT = stNor;
				if (skipMoveto)
				{
					skipMoveto = false;
				}
				else
					dest->MoveTo (curX+width*stNor);
			}
			else
			{
				// jointure
				NR::Point pos;
				pos = curX;
				OutlineJoin (dest, pos, curT, stNor, width, join, miter);
			}

			callsData.piece = curP;
			callsData.tSt = 0.0;
			callsData.tEn = 1.0;
			callsData.x1 = curX[0];
			callsData.y1 = curX[1];
			callsData.x2 = nextX[0];
			callsData.y2 = nextX[1];
			callsData.d.c.dx1 = nData->start[0];
			callsData.d.c.dy1 = nData->start[1];
			callsData.d.c.dx2 = nData->end[0];
			callsData.d.c.dy2 = nData->end[1];
			(calls.cubicto) (&callsData, tolerance, width);

			curP++;
		}
		else if (nType == descr_arcto)
		{
			PathDescrArcTo* nData = dynamic_cast<PathDescrArcTo*>(descr_cmd[curD]);
			nextX = nData->p;
			// test de nullité du segment
			if (IsNulCurve (descr_cmd, curD, curX))
			{
				curP++;
				continue;
			}
			// et on avance
			TangentOnArcAt (0.0, curX, *nData, stPos, stTgt, stTle,
							stRad);
			TangentOnArcAt (1.0, curX, *nData, enPos, enTgt, enTle,
							enRad);
			stNor=stTgt.cw();
			enNor=enTgt.cw();

			lastP = enPos;
			lastT = enTgt;	// tjs definie

			if (doFirst)
			{
				doFirst = false;
				firstP = stPos;
				firstT = stNor;
				if (skipMoveto)
				{
					skipMoveto = false;
				}
				else
					dest->MoveTo (curX+width*stNor);
			}
			else
			{
				// jointure
				NR::Point pos;
				pos = curX;
				OutlineJoin (dest, pos, curT, stNor, width, join, miter);
			}

			callsData.piece = curP;
			callsData.tSt = 0.0;
			callsData.tEn = 1.0;
			callsData.x1 = curX[0];
			callsData.y1 = curX[1];
			callsData.x2 = nextX[0];
			callsData.y2 = nextX[1];
			callsData.d.a.rx = nData->rx;
			callsData.d.a.ry = nData->ry;
			callsData.d.a.angle = nData->angle;
			callsData.d.a.clock = nData->clockwise;
			callsData.d.a.large = nData->large;
			(calls.arcto) (&callsData, tolerance, width);

			curP++;
		}
		else if (nType == descr_bezierto)
		{
			PathDescrBezierTo* nBData = dynamic_cast<PathDescrBezierTo*>(descr_cmd[curD]);
			int nbInterm = nBData->nb;
			nextX = nBData->p;

			if (IsNulCurve (descr_cmd, curD, curX)) {
				curP += nbInterm + 1;
				continue;
			}

			curP++;

			curD = off + curP;
                        int ip = curD;
			PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[ip]);

			if (nbInterm <= 0) {
				// et on avance
                            PathDescrLineTo temp(nextX);
				TangentOnSegAt (0.0, curX, temp, stPos, stTgt, stTle);
				TangentOnSegAt (1.0, curX, temp, enPos, enTgt, enTle);
				stNor=stTgt.cw();
				enNor=enTgt.cw();

				lastP = enPos;
				lastT = enTgt;

				if (doFirst) {
					doFirst = false;
					firstP = stPos;
					firstT = stNor;
					if (skipMoveto) {
						skipMoveto = false;
					} else dest->MoveTo (curX+width*stNor);
				} else {
					// jointure
					NR::Point pos;
					pos = curX;
					if (stTle > 0) OutlineJoin (dest, pos, curT, stNor, width, join, miter);
				}
				int n_d = dest->LineTo (nextX+width*enNor);
				if (n_d >= 0) {
					dest->descr_cmd[n_d]->associated = curP - 1;
					dest->descr_cmd[n_d]->tSt = 0.0;
					dest->descr_cmd[n_d]->tEn = 1.0;
				}
			} else if (nbInterm == 1) {
				NR::Point  midX;
				midX = nData->p;
				// et on avance
				TangentOnBezAt (0.0, curX, *nData, *nBData, false, stPos, stTgt, stTle, stRad);
				TangentOnBezAt (1.0, curX, *nData, *nBData, true, enPos, enTgt, enTle, enRad);
				stNor=stTgt.cw();
				enNor=enTgt.cw();

				lastP = enPos;
				lastT = enTgt;

				if (doFirst) {
					doFirst = false;
					firstP = stPos;
					firstT = stNor;
					if (skipMoveto) {
						skipMoveto = false;
					} else dest->MoveTo (curX+width*stNor);
				}  else {
					// jointure
					NR::Point pos;
					pos = curX;
					OutlineJoin (dest, pos, curT, stNor, width, join, miter);
				}

				callsData.piece = curP;
				callsData.tSt = 0.0;
				callsData.tEn = 1.0;
				callsData.x1 = curX[0];
				callsData.y1 = curX[1];
				callsData.x2 = nextX[0];
				callsData.y2 = nextX[1];
				callsData.d.b.mx = midX[0];
				callsData.d.b.my = midX[1];
				(calls.bezierto) (&callsData, tolerance, width);

			} else if (nbInterm > 1) {
				NR::Point  bx=curX;
				NR::Point cx=curX;
				NR::Point dx=curX;

				dx = nData->p;
				TangentOnBezAt (0.0, curX, *nData, *nBData, false, stPos, stTgt, stTle, stRad);
				stNor=stTgt.cw();

				ip++;
				nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[ip]);
				// et on avance
				if (stTle > 0) {
					if (doFirst) {
						doFirst = false;
						firstP = stPos;
						firstT = stNor;
						if (skipMoveto) {
							skipMoveto = false;
						} else  dest->MoveTo (curX+width*stNor);
					} else {
						// jointure
						NR::Point pos=curX;
						OutlineJoin (dest, pos, stTgt, stNor, width, join,  miter);
						//                                              dest->LineTo(curX+width*stNor.x,curY+width*stNor.y);
					}
				}

				cx = 2 * bx - dx;

				for (int k = 0; k < nbInterm - 1; k++) {
					bx = cx;
					cx = dx;

					dx = nData->p;
                                        ip++;
					nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[ip]);
					NR::Point stx = (bx + cx) / 2;
					//                                      double  stw=(bw+cw)/2;

					PathDescrBezierTo tempb((cx + dx) / 2, 1);
					PathDescrIntermBezierTo tempi(cx);
					TangentOnBezAt (1.0, stx, tempi, tempb, true, enPos, enTgt, enTle, enRad);
					enNor=enTgt.cw();

					lastP = enPos;
					lastT = enTgt;

					callsData.piece = curP + k;
					callsData.tSt = 0.0;
					callsData.tEn = 1.0;
					callsData.x1 = stx[0];
					callsData.y1 = stx[1];
					callsData.x2 = (cx[0] + dx[0]) / 2;
					callsData.y2 = (cx[1] + dx[1]) / 2;
					callsData.d.b.mx = cx[0];
					callsData.d.b.my = cx[1];
					(calls.bezierto) (&callsData, tolerance, width);
				}
				{
					bx = cx;
					cx = dx;

					dx = nextX;
					dx = 2 * dx - cx;

					NR::Point stx = (bx + cx) / 2;
					//                                      double  stw=(bw+cw)/2;

					PathDescrBezierTo tempb((cx + dx) / 2, 1);
					PathDescrIntermBezierTo tempi(cx);
					TangentOnBezAt (1.0, stx, tempi, tempb, true, enPos,
									enTgt, enTle, enRad);
					enNor=enTgt.cw();

					lastP = enPos;
					lastT = enTgt;

					callsData.piece = curP + nbInterm - 1;
					callsData.tSt = 0.0;
					callsData.tEn = 1.0;
					callsData.x1 = stx[0];
					callsData.y1 = stx[1];
					callsData.x2 = (cx[0] + dx[0]) / 2;
					callsData.y2 = (cx[1] + dx[1]) / 2;
					callsData.d.b.mx = cx[0];
					callsData.d.b.my = cx[1];
					(calls.bezierto) (&callsData, tolerance, width);

				}
			}

			// et on avance
			curP += nbInterm;
		}
		curX = nextX;
		curT = enNor;		// sera tjs bien definie
	}
	if (closeIfNeeded)
	{
		if (doFirst == false)
		{
		}
	}

}

/*
 *
 * utilitaires pour l'outline
 *
 */

// like the name says: check whether the path command is actually more than a dumb point.
bool
Path::IsNulCurve (std::vector<PathDescr*> const &cmd, int curD, NR::Point const &curX)
{
	switch(cmd[curD]->getType()) {
    case descr_lineto:
    {
		PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo*>(cmd[curD]);
		if (NR::LInfty(nData->p - curX) < 0.00001) {
			return true;
		}
		return false;
    }
	case descr_cubicto:
    {
		PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo*>(cmd[curD]);
		NR::Point A = nData->start + nData->end + 2*(curX - nData->p);
		NR::Point B = 3*(nData->p - curX) - 2*nData->start - nData->end;
		NR::Point C = nData->start;
		if (NR::LInfty(A) < 0.0001
			&& NR::LInfty(B) < 0.0001
			&& NR::LInfty (C) < 0.0001) {
			return true;
		}
		return false;
    }
    case descr_arcto:
    {
		PathDescrArcTo* nData = dynamic_cast<PathDescrArcTo*>(cmd[curD]);
		if ( NR::LInfty(nData->p - curX) < 0.00001) {
			if ((nData->large == false)
				|| (fabs (nData->rx) < 0.00001
					|| fabs (nData->ry) < 0.00001)) {
				return true;
			}
		}
		return false;
    }
    case descr_bezierto:
    {
		PathDescrBezierTo* nBData = dynamic_cast<PathDescrBezierTo*>(cmd[curD]);
		if (nBData->nb <= 0)
		{
			if (NR::LInfty(nBData->p - curX) < 0.00001) {
				return true;
			}
			return false;
		}
		else if (nBData->nb == 1)
		{
			if (NR::LInfty(nBData->p - curX) < 0.00001) {
				int ip = curD + 1;
				PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(cmd[ip]);
				if (NR::LInfty(nData->p - curX) < 0.00001) {
					return true;
				}
			}
			return false;
		} else if (NR::LInfty(nBData->p - curX) < 0.00001) {
			for (int i = 1; i <= nBData->nb; i++) {
				int ip = curD + i;
				PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(cmd[ip]);
				if (NR::LInfty(nData->p - curX) > 0.00001) {
					return false;
				}
			}
			return true;
		}
    }
    default:
		return true;
	}
}

// tangents and cuvarture computing, for the different path command types.
// the need for tangent is obvious: it gives the normal, along which we offset points
// curvature is used to do strength correction on the length of the tangents to the offset (see
// cubic offset)

/**
 *    \param at Distance along a tangent (0 <= at <= 1).
 *    \param iS Start point.
 *    \param fin LineTo description containing end point.
 *    \param pos Filled in with the position of `at' on the segment.
 *    \param tgt Filled in with the normalised tangent vector.
 *    \param len Filled in with the length of the segment.
 */

void Path::TangentOnSegAt(double at, NR::Point const &iS, PathDescrLineTo const &fin,
                          NR::Point &pos, NR::Point &tgt, double &len)
{
    NR::Point const iE = fin.p;
    NR::Point const seg = iE - iS;
    double const l = L2(seg);
    if (l <= 0.000001) {
        pos = iS;
        tgt = NR::Point(0, 0);
        len = 0;
    } else {
        tgt = seg / l;
        pos = (1 - at) * iS + at * iE; // in other words, pos = iS + at * seg
        len = l;
    }
}

// barf
void Path::TangentOnArcAt(double at, const NR::Point &iS, PathDescrArcTo const &fin,
                          NR::Point &pos, NR::Point &tgt, double &len, double &rad)
{
	NR::Point const iE  = fin.p;
	double const rx = fin.rx;
	double const ry = fin.ry;
	double const angle = fin.angle;
	bool const large = fin.large;
	bool const wise = fin.clockwise;

	pos = iS;
	tgt[0] = tgt[1] = 0;
	if (rx <= 0.0001 || ry <= 0.0001)
		return;

	double const sex = iE[0] - iS[0], sey = iE[1] - iS[1];
	double const ca = cos (angle), sa = sin (angle);
	double csex =  ca * sex + sa * sey;
        double csey = -sa * sex + ca * sey;
	csex /= rx;
	csey /= ry;
	double l = csex * csex + csey * csey;
	if (l >= 4)
		return;
	double const d = sqrt(std::max(1 - l / 4, 0.0));
	double csdx = csey;
        double csdy = -csex;
	l = sqrt(l);
	csdx /= l;
	csdy /= l;
	csdx *= d;
	csdy *= d;

	double sang;
        double eang;
	double rax = -csdx - csex / 2;
        double ray = -csdy - csey / 2;
	if (rax < -1)
	{
		sang = M_PI;
	}
	else if (rax > 1)
	{
		sang = 0;
	}
	else
	{
		sang = acos (rax);
		if (ray < 0)
			sang = 2 * M_PI - sang;
	}
	rax = -csdx + csex / 2;
	ray = -csdy + csey / 2;
	if (rax < -1)
	{
		eang = M_PI;
	}
	else if (rax > 1)
	{
		eang = 0;
	}
	else
	{
		eang = acos (rax);
		if (ray < 0)
			eang = 2 * M_PI - eang;
	}

	csdx *= rx;
	csdy *= ry;
	double drx = ca * csdx - sa * csdy;
        double dry = sa * csdx + ca * csdy;

	if (wise)
	{
		if (large == true)
		{
			drx = -drx;
			dry = -dry;
			double swap = eang;
			eang = sang;
			sang = swap;
			eang += M_PI;
			sang += M_PI;
			if (eang >= 2 * M_PI)
				eang -= 2 * M_PI;
			if (sang >= 2 * M_PI)
				sang -= 2 * M_PI;
		}
	}
	else
	{
		if (large == false)
		{
			drx = -drx;
			dry = -dry;
			double swap = eang;
			eang = sang;
			sang = swap;
			eang += M_PI;
			sang += M_PI;
			if (eang >= 2 * M_PI)
				eang -= 2 * M_PI;
			if (sang >= 2 * M_PI)
				sang -= 2 * M_PI;
		}
	}
	drx += (iS[0] + iE[0]) / 2;
	dry += (iS[1] + iE[1]) / 2;

	if (wise) {
		if (sang < eang)
			sang += 2 * M_PI;
		double b = sang * (1 - at) + eang * at;
		double cb = cos (b), sb = sin (b);
		pos[0] = drx + ca * rx * cb - sa * ry * sb;
		pos[1] = dry + sa * rx * cb + ca * ry * sb;
		tgt[0] = ca * rx * sb + sa * ry * cb;
		tgt[1] = sa * rx * sb - ca * ry * cb;
		NR::Point dtgt;
		dtgt[0] = -ca * rx * cb + sa * ry * sb;
		dtgt[1] = -sa * rx * cb - ca * ry * sb;
		len = L2(tgt);
		rad = len * dot(tgt, tgt) / (tgt[0] * dtgt[1] - tgt[1] * dtgt[0]);
		tgt /= len;
	}
	else
	{
		if (sang > eang)
			sang -= 2 * M_PI;
		double b = sang * (1 - at) + eang * at;
		double cb = cos (b), sb = sin (b);
		pos[0] = drx + ca * rx * cb - sa * ry * sb;
		pos[1] = dry + sa * rx * cb + ca * ry * sb;
		tgt[0] = ca * rx * sb + sa * ry * cb;
		tgt[1] = sa * rx * sb - ca * ry * cb;
		NR::Point dtgt;
		dtgt[0] = -ca * rx * cb + sa * ry * sb;
		dtgt[1] = -sa * rx * cb - ca * ry * sb;
		len = L2(tgt);
		rad = len * dot(tgt, tgt) / (tgt[0] * dtgt[1] - tgt[1] * dtgt[0]);
		tgt /= len;
	}
}
void
Path::TangentOnCubAt (double at, NR::Point const &iS, PathDescrCubicTo const &fin, bool before,
                      NR::Point &pos, NR::Point &tgt, double &len, double &rad)
{
	const NR::Point E = fin.p;
	const NR::Point Sd = fin.start;
	const NR::Point Ed = fin.end;

	pos = iS;
	tgt = NR::Point(0,0);
	len = rad = 0;

	const NR::Point A = Sd + Ed - 2*E + 2*iS;
	const NR::Point B = 0.5*(Ed - Sd);
	const NR::Point C = 0.25*(6*E - 6*iS - Sd - Ed);
	const NR::Point D = 0.125*(4*iS + 4*E - Ed + Sd);
	const double atb = at - 0.5;
	pos = (atb * atb * atb)*A + (atb * atb)*B + atb*C + D;
	const NR::Point der = (3 * atb * atb)*A  + (2 * atb)*B + C;
	const NR::Point dder = (6 * atb)*A + 2*B;
	const NR::Point ddder = 6 * A;

	double l = NR::L2 (der);
  // lots of nasty cases. inversion points are sadly too common...
	if (l <= 0.0001) {
		len = 0;
		l = L2(dder);
		if (l <= 0.0001) {
			l = L2(ddder);
			if (l <= 0.0001) {
				// pas de segment....
				return;
			}
			rad = 100000000;
			tgt = ddder / l;
			if (before) {
				tgt = -tgt;
			}
			return;
		}
		rad = -l * (dot(dder,dder)) / (cross(ddder,dder));
		tgt = dder / l;
		if (before) {
			tgt = -tgt;
		}
		return;
	}
	len = l;

	rad = -l * (dot(der,der)) / (cross(dder,der));

	tgt = der / l;
}

void
Path::TangentOnBezAt (double at, NR::Point const &iS,
                      PathDescrIntermBezierTo & mid,
                      PathDescrBezierTo & fin, bool before, NR::Point & pos,
                      NR::Point & tgt, double &len, double &rad)
{
	pos = iS;
	tgt = NR::Point(0,0);
	len = rad = 0;

	const NR::Point A = fin.p + iS - 2*mid.p;
	const NR::Point B = 2*mid.p - 2 * iS;
	const NR::Point C = iS;

	pos = at * at * A + at * B + C;
	const NR::Point der = 2 * at * A + B;
	const NR::Point dder = 2 * A;
	double l = NR::L2(der);

	if (l <= 0.0001) {
		l = NR::L2(dder);
		if (l <= 0.0001) {
			// pas de segment....
			// Not a segment.
			return;
		}
		rad = 100000000; // Why this number?
		tgt = dder / l;
		if (before) {
			tgt = -tgt;
		}
		return;
	}
	len = l;
	rad = -l * (dot(der,der)) / (cross(dder,der));

	tgt = der / l;
}

void
Path::OutlineJoin (Path * dest, NR::Point pos, NR::Point stNor, NR::Point enNor, double width,
                   JoinType join, double miter)
{
	const double angSi = cross (enNor,stNor);
	const double angCo = dot (stNor, enNor);
	// 1/1000 is very big/ugly, but otherwise it stuffs things up a little...
	// 1/1000 est tres grossier, mais sinon ca merde tout azimut
	if ((width >= 0 && angSi > -0.001)
	    || (width < 0 && angSi < 0.001)) {
		if (angCo > 0.999) {
			// straight ahead
			// tout droit
		} else if (angCo < -0.999) {
			// half turn
			// demit-tour
			dest->LineTo (pos + width*enNor);
		} else {
			dest->LineTo (pos);
			dest->LineTo (pos + width*enNor);
		}
	} else {
		if (join == join_round) {
			// Use the ends of the cubic: approximate the arc at the
			// point where .., and support better the rounding of
			// coordinates of the end points.

			// utiliser des bouts de cubique: approximation de l'arc (au point ou on en est...), et supporte mieux
			// l'arrondi des coordonnees des extremites
			/* double   angle=acos(angCo);
			   if ( angCo >= 0 ) {
			   NR::Point   stTgt,enTgt;
			   RotCCWTo(stNor,stTgt);
			   RotCCWTo(enNor,enTgt);
			   dest->CubicTo(pos.x+width*enNor.x,pos.y+width*enNor.y,
			   angle*width*stTgt.x,angle*width*stTgt.y,
			   angle*width*enTgt.x,angle*width*enTgt.y);
			   } else {
			   NR::Point   biNor;
			   NR::Point   stTgt,enTgt,biTgt;
			   biNor.x=stNor.x+enNor.x;
			   biNor.y=stNor.y+enNor.y;
			   double  biL=sqrt(biNor.x*biNor.x+biNor.y*biNor.y);
			   biNor.x/=biL;
			   biNor.y/=biL;
			   RotCCWTo(stNor,stTgt);
			   RotCCWTo(enNor,enTgt);
			   RotCCWTo(biNor,biTgt);
			   dest->CubicTo(pos.x+width*biNor.x,pos.y+width*biNor.y,
			   angle*width*stTgt.x,angle*width*stTgt.y,
			   angle*width*biTgt.x,angle*width*biTgt.y);
			   dest->CubicTo(pos.x+width*enNor.x,pos.y+width*enNor.y,
			   angle*width*biTgt.x,angle*width*biTgt.y,
			   angle*width*enTgt.x,angle*width*enTgt.y);
			   }*/
			if (width > 0) {
				dest->ArcTo (pos + width*enNor,
							 1.0001 * width, 1.0001 * width, 0.0, false, true);
			} else {
				dest->ArcTo (pos + width*enNor,
							 -1.0001 * width, -1.0001 * width, 0.0, false,
							 false);
			}
		} else if (join == join_pointy) {
			NR::Point const biss = unit_vector(NR::rot90( stNor - enNor ));
			double c2 = NR::dot (biss, enNor);
			double l = width / c2;
			if ( fabs(l) > miter) {
				dest->LineTo (pos + width*enNor);
			} else {
				dest->LineTo (pos+l*biss);
				dest->LineTo (pos+width*enNor);
			}
		} else {
			dest->LineTo (pos + width*enNor);
		}
	}
}

// les callbacks

// see http://www.home.unix-ag.org/simon/sketch/pathstroke.py to understand what's happening here

void
Path::RecStdCubicTo (outline_callback_data * data, double tol, double width,
                     int lev)
{
	NR::Point stPos, miPos, enPos;
	NR::Point stTgt, enTgt, miTgt, stNor, enNor, miNor;
	double stRad, miRad, enRad;
	double stTle, miTle, enTle;
	// un cubic
	{
            PathDescrCubicTo temp(NR::Point(data->x2, data->y2),
                                    NR::Point(data->d.c.dx1, data->d.c.dy1),
                                    NR::Point(data->d.c.dx2, data->d.c.dy2));

		NR::Point initial_point(data->x1, data->y1);
		TangentOnCubAt (0.0, initial_point, temp, false, stPos, stTgt, stTle,
						stRad);
		TangentOnCubAt (0.5, initial_point, temp, false, miPos, miTgt, miTle,
						miRad);
		TangentOnCubAt (1.0, initial_point, temp, true, enPos, enTgt, enTle,
						enRad);
		stNor=stTgt.cw();
		miNor=miTgt.cw();
		enNor=enTgt.cw();
	}

	double stGue = 1, miGue = 1, enGue = 1;
  // correction of the lengths of the tangent to the offset
  // if you don't see why i wrote that, draw a little figure and everything will be clear
	if (fabs (stRad) > 0.01)
		stGue += width / stRad;
	if (fabs (miRad) > 0.01)
		miGue += width / miRad;
	if (fabs (enRad) > 0.01)
		enGue += width / enRad;
	stGue *= stTle;
	miGue *= miTle;
	enGue *= enTle;


	if (lev <= 0) {
		int n_d = data->dest->CubicTo (enPos + width*enNor,
									   stGue*stTgt,
									   enGue*enTgt);
		if (n_d >= 0) {
			data->dest->descr_cmd[n_d]->associated = data->piece;
			data->dest->descr_cmd[n_d]->tSt = data->tSt;
			data->dest->descr_cmd[n_d]->tEn = data->tEn;
		}
		return;
	}

	NR::Point chk;
	const NR::Point req = miPos + width * miNor;
	{
            PathDescrCubicTo temp(enPos + width * enNor,
                                    stGue * stTgt,
                                    enGue * enTgt);
		double chTle, chRad;
		NR::Point chTgt;
		TangentOnCubAt (0.5, stPos+width*stNor,
						temp, false, chk, chTgt, chTle, chRad);
	}
	const NR::Point diff = req - chk;
	const double err = dot(diff,diff);
	if (err <= tol ) {  // tolerance is given as a quadratic value, no need to use tol*tol here
//    printf("%f <= %f %i\n",err,tol,lev);
		int n_d = data->dest->CubicTo (enPos + width*enNor,
									   stGue*stTgt,
									   enGue*enTgt);
		if (n_d >= 0) {
			data->dest->descr_cmd[n_d]->associated = data->piece;
			data->dest->descr_cmd[n_d]->tSt = data->tSt;
			data->dest->descr_cmd[n_d]->tEn = data->tEn;
		}
	} else {
		outline_callback_data desc = *data;

		desc.tSt = data->tSt;
		desc.tEn = (data->tSt + data->tEn) / 2;
		desc.x1 = data->x1;
		desc.y1 = data->y1;
		desc.x2 = miPos[0];
		desc.y2 = miPos[1];
		desc.d.c.dx1 = 0.5 * stTle * stTgt[0];
		desc.d.c.dy1 = 0.5 * stTle * stTgt[1];
		desc.d.c.dx2 = 0.5 * miTle * miTgt[0];
		desc.d.c.dy2 = 0.5 * miTle * miTgt[1];
		RecStdCubicTo (&desc, tol, width, lev - 1);

		desc.tSt = (data->tSt + data->tEn) / 2;
		desc.tEn = data->tEn;
		desc.x1 = miPos[0];
		desc.y1 = miPos[1];
		desc.x2 = data->x2;
		desc.y2 = data->y2;
		desc.d.c.dx1 = 0.5 * miTle * miTgt[0];
		desc.d.c.dy1 = 0.5 * miTle * miTgt[1];
		desc.d.c.dx2 = 0.5 * enTle * enTgt[0];
		desc.d.c.dy2 = 0.5 * enTle * enTgt[1];
		RecStdCubicTo (&desc, tol, width, lev - 1);
	}
}

void
Path::StdCubicTo (Path::outline_callback_data * data, double tol, double width)
{
//	fflush (stdout);
	RecStdCubicTo (data, tol, width, 8);
}

void
Path::StdBezierTo (Path::outline_callback_data * data, double tol, double width)
{
    PathDescrBezierTo tempb(NR::Point(data->x2, data->y2), 1);
    PathDescrIntermBezierTo tempi(NR::Point(data->d.b.mx, data->d.b.my));
	NR::Point stPos, enPos, stTgt, enTgt;
	double stRad, enRad, stTle, enTle;
	NR::Point  tmp(data->x1,data->y1);
	TangentOnBezAt (0.0, tmp, tempi, tempb, false, stPos, stTgt,
					stTle, stRad);
	TangentOnBezAt (1.0, tmp, tempi, tempb, true, enPos, enTgt,
					enTle, enRad);
	data->d.c.dx1 = stTle * stTgt[0];
	data->d.c.dy1 = stTle * stTgt[1];
	data->d.c.dx2 = enTle * enTgt[0];
	data->d.c.dy2 = enTle * enTgt[1];
	RecStdCubicTo (data, tol, width, 8);
}

void
Path::RecStdArcTo (outline_callback_data * data, double tol, double width,
                   int lev)
{
	NR::Point stPos, miPos, enPos;
	NR::Point stTgt, enTgt, miTgt, stNor, enNor, miNor;
	double stRad, miRad, enRad;
	double stTle, miTle, enTle;
	// un cubic
	{
            PathDescrArcTo temp(NR::Point(data->x2, data->y2),
                                  data->d.a.rx, data->d.a.ry,
                                  data->d.a.angle, data->d.a.large, data->d.a.clock);

		NR::Point tmp(data->x1,data->y1);
		TangentOnArcAt (data->d.a.stA, tmp, temp, stPos, stTgt,
						stTle, stRad);
		TangentOnArcAt ((data->d.a.stA + data->d.a.enA) / 2, tmp,
						temp, miPos, miTgt, miTle, miRad);
		TangentOnArcAt (data->d.a.enA, tmp, temp, enPos, enTgt,
						enTle, enRad);
		stNor=stTgt.cw();
		miNor=miTgt.cw();
		enNor=enTgt.cw();
	}

	double stGue = 1, miGue = 1, enGue = 1;
	if (fabs (stRad) > 0.01)
		stGue += width / stRad;
	if (fabs (miRad) > 0.01)
		miGue += width / miRad;
	if (fabs (enRad) > 0.01)
		enGue += width / enRad;
	stGue *= stTle;
	miGue *= miTle;
	enGue *= enTle;
	double sang, eang;
	{
		NR::Point  tms(data->x1,data->y1),tme(data->x2,data->y2);
		ArcAngles (tms,tme, data->d.a.rx,
				   data->d.a.ry, data->d.a.angle, data->d.a.large, !data->d.a.clock,
				   sang, eang);
	}
	double scal = eang - sang;
	if (scal < 0)
		scal += 2 * M_PI;
	if (scal > 2 * M_PI)
		scal -= 2 * M_PI;
	scal *= data->d.a.enA - data->d.a.stA;

	if (lev <= 0)
	{
		int n_d = data->dest->CubicTo (enPos + width*enNor,
									   stGue*scal*stTgt,
									   enGue*scal*enTgt);
		if (n_d >= 0) {
			data->dest->descr_cmd[n_d]->associated = data->piece;
			data->dest->descr_cmd[n_d]->tSt = data->d.a.stA;
			data->dest->descr_cmd[n_d]->tEn = data->d.a.enA;
		}
		return;
	}

	NR::Point chk;
	const NR::Point req = miPos + width*miNor;
	{
            PathDescrCubicTo temp(enPos + width * enNor, stGue * scal * stTgt, enGue * scal * enTgt);
		double chTle, chRad;
		NR::Point chTgt;
		TangentOnCubAt (0.5, stPos+width*stNor,
						temp, false, chk, chTgt, chTle, chRad);
	}
	const NR::Point diff = req - chk;
	const double err = (dot(diff,diff));
	if (err <= tol * tol)
	{
		int n_d = data->dest->CubicTo (enPos + width*enNor,
									   stGue*scal*stTgt,
									   enGue*scal*enTgt);
		if (n_d >= 0) {
			data->dest->descr_cmd[n_d]->associated = data->piece;
			data->dest->descr_cmd[n_d]->tSt = data->d.a.stA;
			data->dest->descr_cmd[n_d]->tEn = data->d.a.enA;
		}
	} else {
		outline_callback_data desc = *data;

		desc.d.a.stA = data->d.a.stA;
		desc.d.a.enA = (data->d.a.stA + data->d.a.enA) / 2;
		RecStdArcTo (&desc, tol, width, lev - 1);

		desc.d.a.stA = (data->d.a.stA + data->d.a.enA) / 2;
		desc.d.a.enA = data->d.a.enA;
		RecStdArcTo (&desc, tol, width, lev - 1);
	}
}

void
Path::StdArcTo (Path::outline_callback_data * data, double tol, double width)
{
	data->d.a.stA = 0.0;
	data->d.a.enA = 1.0;
	RecStdArcTo (data, tol, width, 8);
}

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
