ultimatepp/uppsrc/Painter/Stroker.cpp
2025-12-02 00:16:55 +01:00

188 lines
3.8 KiB
C++

#include "Painter.h"
#define LLOG(x) // DLOG(x)
namespace Upp {
void Stroker::Init(double width, double miterlimit, double tolerance, int _linecap, int _linejoin, const Rectf& preclip_)
{
LLOG("Init");
linecap = _linecap;
linejoin = _linejoin;
preclip = preclip_;
w2 = width / 2;
qmiter = miterlimit * w2;
if(!IsNull(preclip))
tw = 4 * max(qmiter, width); // preclipping width
qmiter *= qmiter;
fid = acos(1 - tolerance / w2);
p0 = p1 = p2 = Null;
first = Null;
lines = 0;
}
void Stroker::Move(const Pointf& p)
{
LLOG("Stroker::Move " << p);
Finish();
first = p1 = p;
p0 = p2 = Null;
}
void Stroker::Round(const Pointf& p, const Pointf& v1, const Pointf& v2, double r)
{
double a1 = Bearing(v1);
double a2 = Bearing(v2);
if(a1 < a2)
a1 += 2 * M_PI;
while(a1 > a2) {
PutLine(Polar(p, r, a1));
a1 -= fid;
}
}
inline bool Stroker::PreClipped(Pointf p2, Pointf p3)
{
return p2.x + tw < preclip.left && p3.x + tw < preclip.left ||
p2.x - tw > preclip.right && p3.x - tw > preclip.right ||
p2.y + tw < preclip.top && p3.y + tw < preclip.top ||
p2.y - tw > preclip.bottom && p3.y - tw > preclip.bottom;
}
void Stroker::Line(const Pointf& p3)
{
LLOG("Stroker::Line " << p3);
lines++;
if(IsNull(p1)) {
Move(p3);
return;
}
Pointf v2 = p3 - p2;
double l = Length(v2);
if(IsNull(p2) || l < 1e-15) {
Pointf v = p3 - p1;
double l = Length(v);
if(l < 1e-8) // lower precision to accomodate Arc precision issue
return;
p2 = p3;
v1 = v;
o1 = Orthogonal(v1) * w2 / l;
a1 = p1 + o1;
b1 = p1 - o1;
if(IsNull(p0)) {
p0 = p1;
v0 = v1;
o0 = o1;
a0 = a1;
b0 = b1;
}
return;
}
Pointf o2 = Orthogonal(v2) * w2 / l;
Pointf a2 = p2 + o2;
Pointf b2 = p2 - o2;
double d = v1.y * v2.x - v2.y * v1.x;
if(d > 1e-15) {
Pointf ts = a1 + v1 * (v2.y * (a1.x - a2.x) - v2.x * (a1.y - a2.y)) / d;
PutMove(a1);
if(linejoin != LINEJOIN_MITER || SquaredDistance(ts, p2) > qmiter) {
PutLine(a1 + v1);
if(linejoin == LINEJOIN_ROUND)
Round(p2, o1, o2, w2);
}
else
PutLine(ts);
PutLine(a2);
PutMove(b2);
PutLine(p2);
PutLine(b1 + v1);
PutLine(b1);
}
else
if(d < -1e-15) {
Pointf ts = b2 + v2 * (v1.y * (a2.x - a1.x) - v1.x * (a2.y - a1.y)) / d;
PutMove(b2);
if(linejoin != LINEJOIN_MITER || SquaredDistance(ts, p2) > qmiter) {
if(linejoin == LINEJOIN_ROUND)
Round(p2, -o2, -o1, w2);
PutLine(b1 + v1);
}
else
PutLine(ts);
PutLine(b1);
PutMove(a1);
PutLine(a1 + v1);
PutLine(p2);
PutLine(a2);
}
else {
PutMove(a1);
PutLine(a1 + v1);
if(linejoin == LINEJOIN_ROUND)
Round(p2, o1, o2, w2);
PutLine(a2);
PutMove(b2);
PutLine(b1 + v1);
PutLine(b1);
}
p1 = p2;
v1 = v2;
o1 = o2;
a1 = a2;
b1 = b2;
p2 = p3;
}
void Stroker::Cap(const Pointf& p, const Pointf& v, const Pointf& o,
const Pointf& a, const Pointf& b)
{
PutMove(a);
if(linecap == LINECAP_SQUARE) {
Pointf nv = Orthogonal(o);
PutLine(a + nv);
PutLine(b + nv);
}
if(linecap == LINECAP_ROUND)
Round(p, -o, o, w2);
PutLine(b);
}
void Stroker::Finish()
{
if(IsNull(p1) || IsNull(p2) || IsNull(p0))
return;
LLOG("-- Finish " << p1 << " to " << p2 << ", lines " << lines);
if(lines == 1 && !IsNull(preclip) && PreClipped(p1, p2)) { // this is mostly intended to preclip dasher segments
LLOG("FINISH PRECLIPPED " << p1 << " - " << p2);
lines = 0;
return;
}
if(p2 == p0)
Line(p0 + v0);
else {
PutMove(a1);
PutLine(a1 + v1);
PutMove(b1 + v1);
PutLine(b1);
if(p2 != first) {
Cap(p0, v0, o0, b0, a0);
Cap(p2, -v1, -o1, a1 + v1, b1 + v1);
}
}
first = p0 = p1 = p2 = Null;
lines = 0;
LLOG("* done");
}
void Stroker::End()
{
Finish();
PutEnd();
}
}