void SPOffset::set_shape() { if ( this->originalPath == NULL ) { // oops : no path?! (the offset object should do harakiri) return; } #ifdef OFFSET_VERBOSE g_print ("rad=%g\n", offset->rad); #endif // au boulot if ( fabs(this->rad) < 0.01 ) { // grosso modo: 0 // just put the source this as the offseted one, no one will notice // it's also useless to compute the offset with a 0 radius //XML Tree being used directly here while it shouldn't be. const char *res_d = this->getRepr()->attribute("inkscape:original"); if ( res_d ) { Geom::PathVector pv = sp_svg_read_pathv(res_d); SPCurve *c = new SPCurve(pv); g_assert(c != NULL); this->setCurveInsync (c, TRUE); this->setCurveBeforeLPE(c); c->unref(); } return; } // extra paraniac careful check. the preceding if () should take care of this case if (fabs (this->rad) < 0.01) { this->rad = (this->rad < 0) ? -0.01 : 0.01; } Path *orig = new Path; orig->Copy ((Path *)this->originalPath); if ( use_slow_but_correct_offset_method == false ) { // version par outline Shape *theShape = new Shape; Shape *theRes = new Shape; Path *originaux[1]; Path *res = new Path; res->SetBackData (false); // and now: offset float o_width; if (this->rad >= 0) { o_width = this->rad; orig->OutsideOutline (res, o_width, join_round, butt_straight, 20.0); } else { o_width = -this->rad; orig->OutsideOutline (res, -o_width, join_round, butt_straight, 20.0); } if (o_width >= 1.0) { // res->ConvertForOffset (1.0, orig, offset->rad); res->ConvertWithBackData (1.0); } else { // res->ConvertForOffset (o_width, orig, offset->rad); res->ConvertWithBackData (o_width); } res->Fill (theShape, 0); theRes->ConvertToShape (theShape, fill_positive); originaux[0] = res; theRes->ConvertToForme (orig, 1, originaux); Geom::OptRect bbox = this->desktopVisualBounds(); if ( bbox ) { gdouble size = L2(bbox->dimensions()); gdouble const exp = this->transform.descrim(); if (exp != 0) { size /= exp; } orig->Coalesce (size * 0.001); //g_print ("coa %g exp %g item %p\n", size * 0.001, exp, item); } // if (o_width >= 1.0) // { // orig->Coalesce (0.1); // small treshhold, since we only want to get rid of small segments // the curve should already be computed by the Outline() function // orig->ConvertEvenLines (1.0); // orig->Simplify (0.5); // } // else // { // orig->Coalesce (0.1*o_width); // orig->ConvertEvenLines (o_width); // orig->Simplify (0.5 * o_width); // } delete theShape; delete theRes; delete res; } else { // version par makeoffset Shape *theShape = new Shape; Shape *theRes = new Shape; // and now: offset float o_width; if (this->rad >= 0) { o_width = this->rad; } else { o_width = -this->rad; } // one has to have a measure of the details if (o_width >= 1.0) { orig->ConvertWithBackData (0.5); } else { orig->ConvertWithBackData (0.5*o_width); } orig->Fill (theShape, 0); theRes->ConvertToShape (theShape, fill_positive); Path *originaux[1]; originaux[0]=orig; Path *res = new Path; theRes->ConvertToForme (res, 1, originaux); int nbPart=0; Path** parts=res->SubPaths(nbPart,true); char *holes=(char*)malloc(nbPart*sizeof(char)); // we offset contours separately, because we can. // this way, we avoid doing a unique big ConvertToShape when dealing with big shapes with lots of holes { Shape* onePart=new Shape; Shape* oneCleanPart=new Shape; theShape->Reset(); for (int i=0; i<nbPart; i++) { double partSurf=parts[i]->Surface(); parts[i]->Convert(1.0); { // raffiner si besoin double bL,bT,bR,bB; parts[i]->PolylineBoundingBox(bL,bT,bR,bB); double mesure=((bR-bL)+(bB-bT))*0.5; if ( mesure < 10.0 ) { parts[i]->Convert(0.02*mesure); } } if ( partSurf < 0 ) { // inverse par rapport a la realite // plein holes[i]=0; parts[i]->Fill(oneCleanPart,0); onePart->ConvertToShape(oneCleanPart,fill_positive); // there aren't intersections in that one, but maybe duplicate points and null edges oneCleanPart->MakeOffset(onePart,this->rad,join_round,20.0); onePart->ConvertToShape(oneCleanPart,fill_positive); onePart->CalcBBox(); double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY)); if ( typicalSize < 0.05 ) { typicalSize=0.05; } typicalSize*=0.01; if ( typicalSize > 1.0 ) { typicalSize=1.0; } onePart->ConvertToForme (parts[i]); parts[i]->ConvertEvenLines (typicalSize); parts[i]->Simplify (typicalSize); double nPartSurf=parts[i]->Surface(); if ( nPartSurf >= 0 ) { // inversion de la surface -> disparait delete parts[i]; parts[i]=NULL; } else { } /* int firstP=theShape->nbPt; for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x); for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].st,firstP+onePart->aretes[j].en);*/ } else { // trou holes[i]=1; parts[i]->Fill(oneCleanPart,0,false,true,true); onePart->ConvertToShape(oneCleanPart,fill_positive); oneCleanPart->MakeOffset(onePart,-this->rad,join_round,20.0); onePart->ConvertToShape(oneCleanPart,fill_positive); // for (int j=0;j<onePart->nbAr;j++) onePart->Inverse(j); // pas oublier de reinverser onePart->CalcBBox(); double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY)); if ( typicalSize < 0.05 ) { typicalSize=0.05; } typicalSize*=0.01; if ( typicalSize > 1.0 ) { typicalSize=1.0; } onePart->ConvertToForme (parts[i]); parts[i]->ConvertEvenLines (typicalSize); parts[i]->Simplify (typicalSize); double nPartSurf=parts[i]->Surface(); if ( nPartSurf >= 0 ) { // inversion de la surface -> disparait delete parts[i]; parts[i]=NULL; } else { } /* int firstP=theShape->nbPt; for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x); for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].en,firstP+onePart->aretes[j].st);*/ } // delete parts[i]; } // theShape->MakeOffset(theRes,offset->rad,join_round,20.0); delete onePart; delete oneCleanPart; } if ( nbPart > 1 ) { theShape->Reset(); for (int i=0; i<nbPart; i++) { if ( parts[i] ) { parts[i]->ConvertWithBackData(1.0); if ( holes[i] ) { parts[i]->Fill(theShape,i,true,true,true); } else { parts[i]->Fill(theShape,i,true,true,false); } } } theRes->ConvertToShape (theShape, fill_positive); theRes->ConvertToForme (orig,nbPart,parts); for (int i=0; i<nbPart; i++) { if ( parts[i] ) { delete parts[i]; } } } else if ( nbPart == 1 ) { orig->Copy(parts[0]); for (int i=0; i<nbPart; i++) { if ( parts[i] ) { delete parts[i]; } } } else { orig->Reset(); } // theRes->ConvertToShape (theShape, fill_positive); // theRes->ConvertToForme (orig); /* if (o_width >= 1.0) { orig->ConvertEvenLines (1.0); orig->Simplify (1.0); } else { orig->ConvertEvenLines (1.0*o_width); orig->Simplify (1.0 * o_width); }*/ if ( parts ) { free(parts); } if ( holes ) { free(holes); } delete res; delete theShape; delete theRes; } { char *res_d = NULL; if (orig->descr_cmd.size() <= 1) { // Aie.... nothing left. res_d = strdup ("M 0 0 L 0 0 z"); //printf("%s\n",res_d); } else { res_d = orig->svg_dump_path (); } delete orig; Geom::PathVector pv = sp_svg_read_pathv(res_d); SPCurve *c = new SPCurve(pv); g_assert(c != NULL); this->setCurveInsync (c, TRUE); this->setCurveBeforeLPE(c); c->unref(); free (res_d); } }
// Variation on the fitting theme: try to merge path commands into cubic bezier patches. // The goal is to reduce the number of path commands, especially when operations on path produce // lots of small path elements; ideally you could get rid of very small segments at reduced visual cost. void Path::Coalesce(double tresh) { if ( descr_flags & descr_adding_bezier ) { CancelBezier(); } if ( descr_flags & descr_doing_subpath ) { CloseSubpath(); } if (descr_cmd.size() <= 2) { return; } SetBackData(false); Path* tempDest = new Path(); tempDest->SetBackData(false); ConvertEvenLines(0.25*tresh); int lastP = 0; int lastAP = -1; // As the elements are stored in a separate array, it's no longer worth optimizing // the rewriting in the same array. // [[comme les elements sont stockes dans un tableau a part, plus la peine d'optimiser // la réécriture dans la meme tableau]] int lastA = descr_cmd[0]->associated; int prevA = lastA; Geom::Point firstP; /* FIXME: the use of this variable probably causes a leak or two. ** It's a hack anyway, and probably only needs to be a type rather than ** a full PathDescr. */ PathDescr *lastAddition = new PathDescrMoveTo(Geom::Point(0, 0)); bool containsForced = false; PathDescrCubicTo pending_cubic(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); for (int curP = 0; curP < int(descr_cmd.size()); curP++) { int typ = descr_cmd[curP]->getType(); int nextA = lastA; if (typ == descr_moveto) { if (lastAddition->flags != descr_moveto) { FlushPendingAddition(tempDest,lastAddition,pending_cubic,lastAP); } lastAddition = descr_cmd[curP]; lastAP = curP; FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP); // Added automatically (too bad about multiple moveto's). // [fr: (tant pis pour les moveto multiples)] containsForced = false; PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[curP]); firstP = nData->p; lastA = descr_cmd[curP]->associated; prevA = lastA; lastP = curP; } else if (typ == descr_close) { nextA = descr_cmd[curP]->associated; if (lastAddition->flags != descr_moveto) { PathDescrCubicTo res(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); int worstP = -1; if (AttemptSimplify(lastA, nextA - lastA + 1, (containsForced) ? 0.05 * tresh : tresh, res, worstP)) { lastAddition = new PathDescrCubicTo(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); pending_cubic = res; lastAP = -1; } FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP); FlushPendingAddition(tempDest, descr_cmd[curP], pending_cubic, curP); } else { FlushPendingAddition(tempDest,descr_cmd[curP],pending_cubic,curP); } containsForced = false; lastAddition = new PathDescrMoveTo(Geom::Point(0, 0)); prevA = lastA = nextA; lastP = curP; lastAP = curP; } else if (typ == descr_forced) { nextA = descr_cmd[curP]->associated; if (lastAddition->flags != descr_moveto) { PathDescrCubicTo res(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); int worstP = -1; if (AttemptSimplify(lastA, nextA - lastA + 1, 0.05 * tresh, res, worstP)) { // plus sensible parce que point force // ca passe /* (Possible translation: More sensitive because contains a forced point.) */ containsForced = true; } else { // Force the addition. FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP); lastAddition = new PathDescrMoveTo(Geom::Point(0, 0)); prevA = lastA = nextA; lastP = curP; lastAP = curP; containsForced = false; } } } else if (typ == descr_lineto || typ == descr_cubicto || typ == descr_arcto) { nextA = descr_cmd[curP]->associated; if (lastAddition->flags != descr_moveto) { PathDescrCubicTo res(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); int worstP = -1; if (AttemptSimplify(lastA, nextA - lastA + 1, tresh, res, worstP)) { lastAddition = new PathDescrCubicTo(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); pending_cubic = res; lastAddition->associated = lastA; lastP = curP; lastAP = -1; } else { lastA = descr_cmd[lastP]->associated; // pourrait etre surecrit par la ligne suivante /* (possible translation: Could be overwritten by the next line.) */ FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP); lastAddition = descr_cmd[curP]; if ( typ == descr_cubicto ) { pending_cubic = *(dynamic_cast<PathDescrCubicTo*>(descr_cmd[curP])); } lastAP = curP; containsForced = false; } } else { lastA = prevA /*descr_cmd[curP-1]->associated */ ; lastAddition = descr_cmd[curP]; if ( typ == descr_cubicto ) { pending_cubic = *(dynamic_cast<PathDescrCubicTo*>(descr_cmd[curP])); } lastAP = curP; containsForced = false; } prevA = nextA; } else if (typ == descr_bezierto) { if (lastAddition->flags != descr_moveto) { FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP); lastAddition = new PathDescrMoveTo(Geom::Point(0, 0)); } lastAP = -1; lastA = descr_cmd[curP]->associated; lastP = curP; PathDescrBezierTo *nBData = dynamic_cast<PathDescrBezierTo*>(descr_cmd[curP]); for (int i = 1; i <= nBData->nb; i++) { FlushPendingAddition(tempDest, descr_cmd[curP + i], pending_cubic, curP + i); } curP += nBData->nb; prevA = nextA; } else if (typ == descr_interm_bezier) { continue; } else { continue; } } if (lastAddition->flags != descr_moveto) { FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP); } Copy(tempDest); delete tempDest; }