#include "GeomDraw.h" #pragma hdrstop NAMESPACE_UPP #define LLOG(x) // LOG(x) enum { THICK = 5, THICKDOT = 20 }; extern void (*PathDraw_Output_Thick[THICK][THICK])(PathDraw::Output& out, Point a, Point b); static const char *ParseCString(const char *p, String& out) { try { CParser parser(p); out = parser.ReadOneString(); return parser.GetPtr(); } catch(CParser::Error e) { throw Exc(e); } } static String FormatPrec(double d) { return Format("%+.*g", PathStyle::PREC, d); } ////////////////////////////////////////////////////////////////////// // PathStyle::Trace:: String PathStyle::Trace::Encode() const { String out; out << FormatPrec(left) << FormatPrec(left_top) << FormatPrec(left_bottom) << FormatPrec(right) << FormatPrec(right_top) << FormatPrec(right_bottom); if(!IsNull(color)) out << Format("*%02x%02x%02x", color.GetR(), color.GetG(), color.GetB()); return out; } const char *PathStyle::Trace::Decode(const char *s) { if(IsNull(left = ScanDouble(s, &s)) || IsNull(left_top = ScanDouble(s, &s)) || IsNull(left_bottom = ScanDouble(s, &s)) || IsNull(right = ScanDouble(s, &s)) || IsNull(right_top = ScanDouble(s, &s)) || IsNull(right_bottom = ScanDouble(s, &s))) return 0; if(*s == '*') { // color for(int i = 1; i <= 6; i++) if(!IsXDigit(s[i])) return 0; // error color = Color(16 * ctoi(s[1]) + ctoi(s[2]), 16 * ctoi(s[3]) + ctoi(s[4]), 16 * ctoi(s[5]) + ctoi(s[6])); s += 7; } return s; } void PathStyle::Trace::Serialize(Stream& stream) { String s; if(stream.IsStoring()) s = Encode(); stream % s; if(stream.IsLoading()) { const char *p = Decode(s); if(!p || *p) stream.SetError(); } } bool PathStyle::Trace::IsEqual(const Trace& t) const { return left == t.left && left_top == t.left_top && left_bottom == t.left_bottom && right == t.right && right_top == t.right_top && right_bottom == t.right_bottom && color == t.color; } PathStyle::Trace& PathStyle::Trace::Crop(double l, double r) { if(l <= left && r >= right) return *this; if(r <= l || right <= l) { left = right = l; left_top = right_top; left_bottom = right_bottom; } else if(left >= r) { right = left = r; right_top = left_top; right_bottom = left_bottom; } else { if(l < left) l = left; if(r > right) r = right; double rl = (l - left) / Width(), rr = (right - r) / Width(); double th = TopHeight(), bh = BottomHeight(); left = l; right = r; left_top += rl * th; right_top -= rr * th; left_bottom += rl * bh; right_bottom -= rr * bh; } return *this; } void PathStyle::Trace::Inflate(Pointf amount) { left -= amount.x; right += amount.x; left_top -= amount.y; right_top -= amount.y; left_bottom += amount.y; right_bottom += amount.y; } bool PathStyle::Trace::Contains(Pointf pt) const { if(pt.x < left || pt.x > right || pt.y < min(left_top, right_top) || pt.y > max(left_bottom, right_bottom)) return false; double wd = right - left; if(left_top != right_top && wd * (pt.y - left_top) < (pt.x - left) * (right_top - left_top)) return false; if(left_bottom != right_bottom && wd * (pt.y - left_bottom) > (pt.x - left) * (right_bottom - left_bottom)) return false; return true; } double PathStyle::Trace::GetDistance(Pointf pt) const { double dl = (pt.y < left_top ? pt | LeftTop() : pt.y > left_bottom ? pt | LeftBottom() : tabs(pt.x - left)); double dr = (pt.y < right_top ? pt | RightTop() : pt.y > right_bottom ? pt | RightBottom() : tabs(pt.x - right)); double dt = Distance(pt, LeftTop(), RightTop()); double db = Distance(pt, LeftBottom(), RightBottom()); return min(min(dl, dr), min(dt, db)); } static bool CheckTrack(Pointf A, Pointf B, Pointf tolerance) { return tabs(A.x - B.x) <= tolerance.x && tabs(A.y - B.y) <= tolerance.y; } int PathStyle::Trace::GetTrackStyle(Pointf pt, Pointf tolerance, Pointf& start) const { return CheckTrack(pt, start = LeftTop(), tolerance) ? LEFT | LEFT_TOP : CheckTrack(pt, start = CenterTop(), tolerance) ? LEFT_TOP | RIGHT_TOP : CheckTrack(pt, start = RightTop(), tolerance) ? RIGHT | RIGHT_TOP : CheckTrack(pt, start = LeftCenter(), tolerance) ? LEFT : CheckTrack(pt, start = RightCenter(), tolerance) ? RIGHT : CheckTrack(pt, start = LeftBottom(), tolerance) ? LEFT | LEFT_BOTTOM : CheckTrack(pt, start = CenterBottom(), tolerance) ? LEFT_BOTTOM | RIGHT_BOTTOM : CheckTrack(pt, start = RightBottom(), tolerance) ? RIGHT | RIGHT_BOTTOM : 0; } Image PathStyle::Trace::GetTrackCursor(int style) { switch(style) { case LEFT | LEFT_TOP: return Image::SizeTopLeft(); case RIGHT | RIGHT_BOTTOM: return Image::SizeBottomRight(); case RIGHT | RIGHT_TOP: return Image::SizeTopRight(); case LEFT | LEFT_BOTTOM: return Image::SizeBottomLeft(); case LEFT_TOP | RIGHT_TOP: case LEFT_BOTTOM | RIGHT_BOTTOM: return Image::SizeVert(); case LEFT: case RIGHT: return Image::SizeHorz(); } return Null; } void PathStyle::Trace::Paint(PlotterTool& tool, bool reduce, Color outline) const { bool he = IsHorzEmpty(); bool ve = IsVertEmpty(); if(he && ve) { // point Plotter& plot = tool.GetPlotter(); if(plot.InLClip(LeftTop())) { Point pt = plot.LtoPoint(LeftTop()); plot.draw->DrawRect(pt.x - 4, pt.y - 4, 9, 9, outline); } } else if(reduce && (he || ve)) { tool.MoveTo(left, Top()); tool.LineTo(right, Bottom()); tool.Paint(); } else { tool.MoveTo(left, left_top); tool.LineTo(right, right_top); tool.LineTo(right, right_bottom); tool.LineTo(left, left_bottom); tool.LineTo(left, left_top); tool.Paint(); } } PathStyle::Trace& PathStyle::Trace::Track(Pointf delta, int style) { if(style & LEFT) left += delta.x; if(style & LEFT_TOP) left_top += delta.y; if(style & RIGHT_TOP) right_top += delta.y; if(style & RIGHT) right += delta.x; if(style & LEFT_BOTTOM) left_bottom += delta.y; if(style & RIGHT_BOTTOM) right_bottom += delta.y; if(left > right) { Swap(left, right); Swap(left_top, right_top); Swap(left_bottom, right_bottom); } if(left_top > left_bottom) Swap(left_top, left_bottom); if(right_top > right_bottom) Swap(right_top, right_bottom); return *this; } PathStyle::Trace& PathStyle::Trace::Bind(const Rectf& rc) { if(rc.left > left) left = rc.left; if(rc.right < right) right = rc.right; if(rc.top > left_top) left_top = rc.top; if(rc.top > right_top) right_top = rc.top; if(rc.bottom < left_bottom) left_bottom = rc.bottom; if(rc.bottom < right_bottom) right_bottom = rc.bottom; return *this; } ////////////////////////////////////////////////////////////////////// // PathStyle::Clip:: void PathStyle::Clip::Serialize(Stream& stream) { int version = StreamHeading(stream, 1, 1, 1, "PathStyle::Clip"); if(version >= 1) stream % traces; } Rectf PathStyle::Clip::GetExtent() const { Rectf rc = Null; for(int i = 0; i < traces.GetCount(); i++) rc |= traces[i].GetExtent(); return rc; } ////////////////////////////////////////////////////////////////////// // PathStyle:: PathStyle::PathStyle(pick_ PathStyle& style) : PathStyleMisc(style), traces(style.traces) { ASSERT(&style != &PathStyle::solid()); } PathStyle& PathStyle::operator = (pick_ PathStyle& style) { ASSERT(&style != &PathStyle::solid()); PathStyleMisc::operator = (style); traces = style.traces; return *this; } bool PathStyle::IsSolid(double& wd, Color& co) const { double tol = 1e-6; if(traces.GetCount() != 1 || tabs(begin) > tol || tabs(end) > tol) return false; const Trace& tr = traces[0]; if(tabs(tr.left) > tol || tabs(tr.right - segment) > tol || tabs(tr.left_top - tr.right_top) > tol || tabs(tr.left_bottom - tr.right_bottom) > tol || tabs(tr.left_top + tr.left_bottom) > tol) return false; wd = 2 * tabs(tr.left_top); co = tr.color; return true; } String PathStyle::Encode() const { String out; out << 'p' << FormatPrec(width) << FormatPrec(begin) << FormatPrec(segment) << FormatPrec(end) << '<'; for(int i = 0; i < traces.GetCount(); i++) out.Cat(traces[i].Encode()); out << '>' << (miter == MITER_ROUND ? 'r' : miter == MITER_SHARP ? 's' : 'f'); if(miter == MITER_SHARP) out << FormatPrec(chamfer); return out; } const char *PathStyle::Decode(const char *s) { if(*s++ == 'p' && !IsNull(width = ScanDouble(s, &s)) && !IsNull(begin = ScanDouble(s, &s)) && !IsNull(segment = ScanDouble(s, &s)) && !IsNull(end = ScanDouble(s, &s)) && *s++ == '<') { while(*s == '+' || *s == '-') { Trace t; if((s = t.Decode(s)) == 0) return 0; traces.Add(t); } if(*s++ == '>' && (*s == 'r' || *s == 's' || *s == 'f')) { chamfer = STD_CHAMFER; miter = (*s == 'r' ? MITER_ROUND : *s == 's' ? MITER_SHARP : MITER_FLAT); s++; if(miter != MITER_SHARP || !IsNull(chamfer = ScanDouble(s, &s))) return s; } } return 0; } void PathStyle::Serialize(Stream& stream) { String s; if(stream.IsStoring()) s = Encode(); stream % s; if(stream.IsLoading() && !Decode(s)) stream.SetError(); } const PathStyle& PathStyle::empty() { static PathStyle p; return p; } const PathStyle& PathStyle::solid() { static PathStyle p; if(p.IsEmpty()) p.traces << PathStyle::Trace(0, -0.5, +0.5, 1, -0.5, +0.5); return p; } const PathStyle& PathStyle::dash() { static PathStyle p; if(p.IsEmpty()) { p.segment = 4; p.traces << PathStyle::Trace(0, -0.5, +0.5, 1, -0.5, +0.5) << PathStyle::Trace(3, -0.5, +0.5, 4, -0.5, +0.5); } return p; } const PathStyle& PathStyle::dot() { static PathStyle p; if(p.IsEmpty()) { p.segment = 2; p.traces << PathStyle::Trace(0.5, -0.5, +0.5, 1.5, -0.5, +0.5); } return p; } const PathStyle& PathStyle::dash_dot() { static PathStyle p; if(p.IsEmpty()) { p.segment = 7; p.traces << PathStyle::Trace(0, -0.5, +0.5, 1, -0.5, +0.5) << PathStyle::Trace(3, -0.5, +0.5, 4, -0.5, +0.5) << PathStyle::Trace(6, -0.5, +0.5, 7, -0.5, +0.5); } return p; } const PathStyle& PathStyle::dash_dot_dot() { static PathStyle p; if(p.traces.IsEmpty()) { p.segment = 10; p.traces << PathStyle::Trace(0, -0.5, +0.5, 1, -0.5, +0.5) << PathStyle::Trace(3, -0.5, +0.5, 4, -0.5, +0.5) << PathStyle::Trace(6, -0.5, +0.5, 7, -0.5, +0.5) << PathStyle::Trace(9, -0.5, +0.5, 10, -0.5, +0.5); } return p; } void PathStyleMap::Serialize(Stream& stream) { int version = 1; stream / version % map; if(stream.IsLoading()) sort.Clear(); } String PathStyleMap::Export() const { String out; out << "# PathStyleMap version 1\n" "$" << AsCString(name) << "\n"; for(int i = 0; i < map.GetCount(); i++) out << AsCString(map.GetKey(i)) << '=' << map[i].Encode() << '\n'; return out; } void PathStyleMap::Import(String s, bool update_existing) { int line = 1; try { for(const char *p = s; *p;) if((byte)*p <= ' ') { if(*p++ == '\n') line++; } else if(*p == '#') { // comment - skip until end of line while(*p && *p++ != '\n') ; line++; } else if(*p == '$') { // style name if(*++p != '\"') throw Exc(String().Cat() << "očekáván znak \", nalezeno: " << StringSample(p, 10)); p = ParseCString(p + 1, name); } else if(*p == '\"') { String sn; p = ParseCString(p + 1, sn); if(*p != '=') throw Exc(String().Cat() << "očekáváno '=', nalezeno: " << StringSample(p, 10)); if(IsNull(sn)) throw Exc(String().Cat() << "název stylu je prázdný"); for(p++; *p && *p != '\n' && (byte)*p <= ' '; p++) ; PathStyle ps; p = ps.Decode(p); if(!p) throw Exc(String().Cat() << "neplatná definice stylu '" << name << "'"); if(map.Find(sn) < 0 || update_existing) Set(sn, ps); } else throw Exc(String().Cat() << "neplatný začátek řádku: " << StringSample(p, 10)); } catch(Exc e) { throw Exc(String().Cat() << "řádek " << line << ": " << e); } } const Vector& PathStyleMap::GetSort() const { if(sort.IsEmpty() && !map.IsEmpty()) sort = GetSortOrder(map.GetKeys(), GetLanguageInfo()); return sort; } PathStyle PathStyleMap::Scan(String name, const PathStyle& dflt) const { if(IsNull(name)) return PathStyle(dflt, 0); if(this) { int i = map.Find(name); if(i >= 0) return PathStyle(map[i], 0); } PathStyle ps; const char *end = ps.Decode(name); if(!end || *end) return PathStyle(dflt, 0); return ps; } int PathStyleMap::FindSortName(String name, int exclude) const { for(int i = 0; i < GetCount(); i++) if(i != exclude && GetSortName(i) == name) return i; return -1; } String PathStyleMap::GetUniqueName(String prefix) const { int max_index = -1; int pl = prefix.GetLength(); for(int i = 0; i < GetCount(); i++) { String s = map.GetKey(i); if(s.GetLength() >= pl + 2 && !memcmp(s, prefix, pl)) { const char *p = s.Begin() + pl; if(*p == ' ' && IsDigit(*++p)) { int x = stou(p, &p); if(*p == 0) max_index = max(max_index, x); } } else if(s == prefix) max_index = 0; } if(max_index < 0) return prefix; return prefix + ' ' + IntStr(max_index + 1); } PathStyleMap& PathStyleMap::App() { static PathStyleMap psm; if(psm.IsEmpty()) { psm.Set(".empty", PathStyle::empty()); psm.Set(".solid", PathStyle::solid()); psm.Set(".dash", PathStyle::dash()); psm.Set(".dot", PathStyle::dot()); psm.Set(".dash_dot", PathStyle::dash_dot()); psm.Set(".dash_dot_dot", PathStyle::dash_dot_dot()); } return psm; } static void RecurseArc(Vector& out, Pointf begin, Pointf end, double l, double h, int maxvert) { if(maxvert <= 1 || tabs(h) <= 1 || l <= 1) { out.Add(PointfToPoint(end)); return; } Pointf normal = Left(end - begin) * (h / l); Pointf centre = Mid(begin, end) + normal; double ll = centre | end; double hh = h * ll / (2 * ll + l); maxvert >>= 1; RecurseArc(out, begin, centre, ll, hh, maxvert); RecurseArc(out, centre, end, ll, hh, maxvert); } static Vector& GetArcPath(Vector& out, Point begin, Point end, double height, int maxvert = 256) { RecurseArc(out, begin, end, hypot(begin.x - end.x, begin.y - end.y), height, maxvert); return out; } static Vector GetArcPath(Point begin, Point end, double height, int maxvert = 256) { Vector out; return GetArcPath(out, begin, end, height, maxvert); } void PathDraw::Output::AddNext(const Vector& list) { Point old = vertices.Top(); int c = vertices.GetCount(); for(const Point *p = list.Begin(), *e = list.End(); p != e; ++p) if(*p != old) vertices.Add(old = *p); counts.Top() += vertices.GetCount() - c; } void PathDraw::Output::AddFirst(Point pt) { if(!counts.IsEmpty() && counts.Top() == 1) vertices.Top() = pt; else if(vertices.IsEmpty() || vertices.Top() != pt) { vertices.Add(pt); counts.Add(1); } } void PathDraw::Output::AddThick(void (*ln)(Output& out, Point a, Point b), Point start, const Vector& list) { if(!list.IsEmpty()) { ln(*this, start, list[0]); for(const Point *p = list.Begin() + 1, *e = list.End(); p < e; p++) ln(*this, p[-1], p[0]); } } Point *PathDraw::Output::AddSeg(int segments, int seg_count) { int c = vertices.GetCount(); int n = counts.GetCount(); if(c > 0 && counts[n - 1] == 1) c--, n--; vertices.SetCountR(c + segments * seg_count); counts.SetCountR(n + segments); Fill(counts.Begin() + n, counts.End(), seg_count); return vertices.Begin() + c; } Point *PathDraw::Output::AddSeg(int count) { int c = vertices.GetCount(); int n = counts.GetCount(); if(n > 0 && counts[n - 1] == 1) { c--; counts[n - 1] = count; } else counts.Add(count); vertices.SetCountR(c + count); return &vertices[c]; } void PathDraw::Output::Flush() { int cc = counts.GetCount(); if(cc == 0) return; int vc = vertices.GetCount(); if(counts[cc - 1] == 1) { if(--cc == 0) return; vc--; } draw.DrawPolyPolyline(vertices.Begin(), vc, counts.Begin(), cc, width, color); Point end = vertices.Top(); vertices.SetCountR(1); vertices[0] = end; counts.SetCountR(1); counts[0] = 1; } PathDraw::PathDraw() { moveto = 0; lineto = 0; } PathDraw::PathDraw(Draw& _draw, const PathStyle& _style, Color _color, double _width, double _dash, bool _closed) { Set(_draw, _style, _color, _width, _dash, _closed); } void PathDraw::Clear() { draw = 0; style = 0; split_buffer.Clear(); outputs.Clear(); segment_lines.Clear(); open_traces.Clear(); traces.Clear(); } void PathDraw::AddSegment(const PathStyle::Trace& trace, bool infinite) { int ltop, lbot; ltop = fround(width * trace.Top()); lbot = fround(width * trace.Bottom()); if(width * trace.LeftHeight() > 1) { ltop = fround(width * trace.left_top); lbot = ltop + fround(width * trace.LeftHeight()); } else ltop = lbot = fround(width * (trace.left_top + trace.left_bottom) / 2); Color c = Nvl(trace.color, std_color); /* int wd = int x = outputs.Find(c); if(x < 0 || infinite) { x = outputs.GetCount(); outputs.Add(c, new Output(c, *draw)); } */ if(infinite) { Linear& ln = segment_lines.Add(); ln = Linear(ltop, lbot, c, -1); int w = ln.bottom - ln.top; ln.ground = false; ln.color = c; if(w <= (draw->Pixels() ? THICK : THICKDOT)) { w = max(w - 1, 0); ln.ground = (tabs(ln.top + ln.bottom) <= 1); ln.top = ln.bottom = (ln.top + ln.bottom) >> 1; if(draw->Pixels()) { ln.hline = PathDraw_Output_Thick[w][w]; w = 0; } else ln.hline = PathDraw_Output_Thick[0][0]; } if(!ln.ground && (ltop || lbot)) calc_miter = true; int x; for(x = outputs.Find(c); x >= 0 && outputs[x].width != w; x = outputs.FindNext(c)) ; if(x < 0) { x = outputs.GetCount(); outputs.Add(c, new Output(c, w, *draw)); } ln.index = x; } else { if(traces.GetCount() == seg.start) { seg.top = ltop; seg.bottom = lbot; } else { seg.top = min(seg.top, ltop); seg.bottom = max(seg.bottom, lbot); } Trace& t = traces.Add(); t.left_top = ltop; t.left_bottom = lbot; t.right_top = fround(width * trace.right_top); t.right_bottom = t.right_top + fround(width * trace.RightHeight()); double wd = dash * trace.Width(); t.top_step = (t.right_top - t.left_top) / wd; t.bottom_step = (t.right_bottom - t.left_bottom) / wd; t.start = fround(dash * trace.left); t.width = fround(dash * trace.right) - t.start + 1; t.vline = (t.width <= 1); t.hline = 0; int lw = t.left_bottom - t.left_top; int rw = t.right_bottom - t.right_top; int sw = 0; if(draw->Dots() && lw <= THICKDOT && tabs(rw - lw) <= 5) { sw = (rw + lw) >> 1; t.hline = PathDraw_Output_Thick[0][0]; /* if(t.width > 0) { t.start++; t.width = max(t.width - 2, 1); } */ } else if(draw->Pixels() && lw <= THICK && rw <= THICK) { sw = 0; t.hline = PathDraw_Output_Thick[max(0, lw - 1)] [max(0, rw - 1)]; t.left_top = t.left_bottom = (t.left_top + t.left_bottom) >> 1; t.right_top = t.right_bottom = (t.right_top + t.right_bottom) >> 1; /* if(t.width > 0 && lw > 1) { t.start++; t.width = max(t.width - 2, 1); } */ } t.left_ground = (t.left_top ^ t.left_bottom) < 0; t.right_ground = (t.right_top ^ t.right_bottom) < 0; t.color = c; int x; for(x = outputs.Find(c); x >= 0 && outputs[x].width != sw; x = outputs.FindNext(x)) ; if(x < 0) { x = outputs.GetCount(); outputs.Add(c, new Output(c, sw, *draw)); } t.index = x; calc_miter = true; } } void PathDraw::Set(Draw& _draw, const PathStyle& _style, Color _color, double _width, double _dash, bool _closed) { //RTIMING("PathDraw::Set"); ASSERT(_dash != 0); Clear(); draw = &_draw; style = &_style; std_color = _color; width = _width; double avg = GetAvgPixelsPerDot(*draw); if(width < 0) width *= -avg; if(width < 1) width = 1; LLOG("PathDraw::Set: pixels = " << draw->Pixels() << ", avg pixels per dot = " << avg << ", px width = " << width); dash = Nvl(_dash, width); if(dash < 0) dash *= -avg; miter = style->miter; clip_rect = draw->GetClip(); double cr = style->width; if(style->miter == style->MITER_SHARP) { double ca = 2 * atan2(1, style->chamfer); chamfer_ratio = -cos(ca); chamfer_dist = style->chamfer; cr = hypot(cr, style->chamfer); } clip_radius = fceil(width * cr); clip_rect.Inflate(clip_radius); closed = _closed; calc_miter = false; Array bseg, sseg, eseg; Index overlap_begin, overlap_end; double prec = 1.0 / width; double endpos = style->begin + style->segment; double bsub = style->begin - prec, badd = style->begin + prec; double ssub = endpos - prec, sadd = endpos + prec; { // split style traces double prec2 = sqr(prec); double total = endpos + style->end; int i; for(i = 0; i < style->traces.GetCount(); i++) { const PathStyle::Trace& t = style->traces[i]; if(t.left <= bsub) bseg.Add(t.GetCropRel(0, style->begin)); if(t.left <= ssub && t.right >= badd) sseg.Add(t.GetCropRel(style->begin, endpos)); if(t.right >= sadd) eseg.Add(t.GetCropRel(endpos, total)); } // generate overlaps /* for(i = 0; i < sseg.GetCount() && sseg[i].left <= prec; i++) { const PathStyle::Trace& beg = sseg[i]; for(int j = i; ++j < sseg.GetCount();) { const PathStyle::Trace& end = sseg[j]; if(end.color == beg.color && end.left > beg.right && end.right >= ssub && tabs(beg.left_top - end.right_top) <= prec && tabs(beg.left_bottom - end.right_bottom) <= prec && tabs(end.Width() * beg.TopHeight() - beg.Width() * end.TopHeight()) <= prec2 && tabs(end.Width() * beg.BottomHeight() -beg.Width() * end.BottomHeight()) <= prec2) { // join segments overlap_begin.Add(i); overlap_end.Add(j); break; } } } //*/ } seg = Segment(0, 0, 0, 0, 0); if(!closed) { // process begin segment seg.length = style->begin * dash; int i; for(i = 0; i < bseg.GetCount(); i++) AddSegment(bseg[i], false); // add left overlap segments to begin segment for(i = 0; i < overlap_begin.GetCount(); i++) AddSegment(sseg[overlap_begin[i]], false); } seg.end = traces.GetCount(); seg_begin = seg; // process main segment seg = Segment(0, 0, seg.end, seg.end, dash * style->segment); for(int i = 0; i < sseg.GetCount(); i++) if(overlap_begin.Find(i) < 0) { const PathStyle::Trace& ti = sseg[i]; int o = overlap_end.Find(i); if(o >= 0) { // join with begin segment const PathStyle::Trace& ji = sseg[overlap_begin[o]]; PathStyle::Trace new_trace; new_trace.left = ti.left; new_trace.left_top = ti.left_top; new_trace.left_bottom = ti.left_bottom; new_trace.right = ji.right + style->segment; new_trace.right_top = ji.right_top; new_trace.right_bottom = ji.right_bottom; AddSegment(new_trace, false); } else AddSegment(sseg[i], ti.left <= 0 && ti.right >= style->segment && tabs(ti.TopHeight()) <= prec && tabs(ti.BottomHeight()) <= prec); } seg.end = traces.GetCount(); seg_segment = seg; seg = Segment(0, 0, seg.end, seg.end, 0); if(!closed) { seg.length = dash * style->end; for(int i = 0; i < eseg.GetCount(); i++) AddSegment(eseg[i], false); } seg.end = traces.GetCount(); seg_end = seg; moveto = &PathDraw::MoveToFull; lineto = &PathDraw::LineToFull; if(seg_begin.IsEmpty() && seg_end.IsEmpty() && traces.IsEmpty() && segment_lines.GetCount() == 1) { const Linear& l = segment_lines[0]; one_output = &outputs[0]; if(l.hline == PathDraw_Output_Thick[0][0]) { moveto = &PathDraw::MoveToSimple; lineto = &PathDraw::LineToSimple; } else if(l.hline && l.ground && draw->Pixels() && tabs(l.top + l.bottom) <= 1) { moveto = &PathDraw::MoveToThick; lineto = &PathDraw::LineToThick; one_thick = l.hline; } } init_empty = false; init_part = (closed ? PART_OPEN : seg_begin.IsEmpty() ? PART_SEGMENT : PART_BEGIN); init_seg = (init_part == PART_BEGIN ? seg_begin : seg_segment); if(init_seg.IsEmpty() && seg_segment.IsEmpty() && segment_lines.IsEmpty()) if(closed) { empty = init_empty = true; return; } else init_part = PART_SKIPEND; if(init_part == PART_SKIPEND && seg_end.IsEmpty()) init_empty = true; Restart(); } void PathDraw::SetSegment(const Segment& s, PART p) { seg = s; part = p; next_index = seg.start; skip_size = (seg.IsEmpty() ? 1e100 : traces[next_index].start); } void PathDraw::Restart() { if(empty = init_empty) return; open_traces.Clear(); Fill(raw_pos, raw_pos + __countof(raw_pos), Null); Fill(split_pos, split_pos + __countof(split_pos), Null); pos = raw_pos; part_total = 0; ASSERT(init_seg.end <= traces.GetCount()); SetSegment(init_seg, init_part); } bool PathDraw::SetExtent(const Rect& rc) { if(!IsNull(rc) && !rc.Intersects(clip_rect)) return false; // todo: clip optimization return true; } void PathDraw::MoveToSimple(Point pt) { //RTIMING("PathDraw::MoveToSimple"); if(closed && !IsNull(close_line[0]) && !IsNull(pos[0])) one_output->AddNext(close_line[0]); pos[0] = close_line[0] = pt; one_output->AddFirst(pt); } void PathDraw::LineToSimple(Point pt) { //RTIMING("PathDraw::LineToSimple"); one_output->CheckFlush(); one_output->AddNext(pos[0] = pt); } void PathDraw::MoveToThick(Point pt) { //RTIMING("PathDraw::MoveToThick"); if(closed && !IsNull(close_line[0]) && !IsNull(pos[0])) one_thick(*one_output, pos[0], close_line[0]); one_output->CheckFlush(); pos[0] = close_line[0] = pt; } void PathDraw::LineToThick(Point pt) { //RTIMING("PathDraw::LineToThick"); one_output->CheckFlush(); one_thick(*one_output, pos[0], pt); pos[0] = pt; } void PathDraw::MoveToFull(Point p) { //RTIMING("PathDraw::MoveToFull"); if(empty) return; if(!closed) AddPos(Null); bool split_this = true; if(pos[2] != Null) switch(part) { case PART_OPEN: break; case PART_BEGIN: if(!DoBegin()) break; split_this = false; case PART_SKIPEND: case PART_SEGMENT: if(closed) { for(int i = 0; i < __countof(close_line); i++) if(close_line[i] != raw_pos[0]) { AddPos(close_line[i]); LineToRaw(); } } else if(seg_end.length > 0) { // process split buffer if(split_this) AddSplit(); FlushSplit(seg_end.length); if(split_buffer.IsEmpty()) break; pos = split_pos; Split sp = split_buffer.Head(); split_buffer.DropHead(); AddPos(sp.pos); part_total -= seg_end.length; double t = sp.GetEndPos(sp.total - part_total); pos[0].x = pos[1].x + fround((pos[0].x - pos[1].x) * t); pos[0].y = pos[1].y + fround((pos[0].y - pos[1].y) * t); Point bk = pos[0]; if(pos[0] != pos[1]) { if(part != PART_SKIPEND) LineToRaw(); AddPos(); } pos[0] = Null; if(part != PART_SKIPEND) LineToRaw(); Restart(); pos[1] = bk; pos[2] = pos[3] = Null; pos[0] = sp.pos; if(pos[0] == pos[1]) pos[1] = Null; SetSegment(seg_end, PART_END); while(!split_buffer.IsEmpty()) { AddPos(split_buffer.Head().pos); split_buffer.DropHead(); LineToRaw(); } AddPos(Null); LineToRaw(); pos = raw_pos; } else LineToRaw(); break; } Restart(); split_pos[0] = raw_pos[0] = p; } void PathDraw::CheckSplit() { double tl = 0; for(int i = 0; i < split_buffer.GetCount(); i++) tl += split_buffer[i].total; if(tabs(tl - part_total) >= 0.1) { NEVER(); } } void PathDraw::FlushSplit(double keep_length) { // CheckSplit(); pos = split_pos; if(part == PART_SKIPEND) { while(!split_buffer.IsEmpty()) { // flush part of buffer const Split& sp = split_buffer.Head(); if(part_total - sp.total < seg_end.length) break; AddPos(sp.pos); part_total -= sp.total; split_buffer.DropHead(); } } else { while(!split_buffer.IsEmpty()) { // flush part of buffer const Split& sp = split_buffer.Head(); if(part_total - sp.total < seg_end.length) break; AddPos(sp.pos); LineToRaw(); part_total -= sp.total; split_buffer.DropHead(); } } pos = raw_pos; // CheckSplit(); } #define CALC_MITER() \ bool start = IsNull(pos[3]); \ bool end = IsNull(pos[0]); \ Pointf a, b, a0, b0, at, bt, ar, up; \ double al, bl; \ double a_b, axb, /* rseg, */ ulen; \ bool too_sharp; \ if(calc_miter) \ { \ a = Sizef(pos[1] - pos[2]); \ al = Length(a); \ a0 = a / al; \ ar = Left(a0); \ too_sharp = false; \ if(!end) \ { \ b = Sizef(pos[0] - pos[1]); \ bl = Length(b); \ b0 = b / bl; \ a_b = a0 ^ b0; \ axb = a0 % b0; \ /* rseg = (axb + 1) / 2; */ \ if(a_b < 0) /* sharp angle */ \ up = a0 - b0; \ else \ { \ up.x = b0.y + a0.y; \ up.y = -b0.x - a0.x; \ } \ double ua = a0 % up; \ up /= (ua <= 1 && ua >= -1 ? (ua >= 0 ? 1 : -1) : ua); \ ulen = -(up ^ a0); \ if(miter == PathStyle::MITER_SHARP) \ { \ ASSERT(a0 % up >= 0); \ too_sharp = (a_b <= chamfer_ratio); \ if(too_sharp) \ { \ double cd = (axb >= 0 ? -chamfer_dist : chamfer_dist); \ at = Left(a0) + a0 * cd; \ bt = Left(b0) - b0 * cd; \ } \ } \ else if(miter == PathStyle::MITER_FLAT) \ { /* calc flat miter */ \ Pointf mr = Unit(up); \ if(tabs(mr % ar) <= 1e-5) \ at = bt = mr; \ else \ { \ double k = (tabs(ar.x + mr.x) >= tabs(ar.y + mr.y) \ ? (ar.y - mr.y) / (ar.x + mr.x) : (mr.x - ar.x) / (ar.y + mr.y)); \ at = mr + k * Left(mr); \ bt = mr + k * Right(mr); \ } \ } \ } \ else \ { \ up.x = -a0.y; \ up.y = +a0.x; \ /* rseg = 0; */ \ } \ if(start) \ { \ old_ulen = 0; \ old_up.x = -a0.y; \ old_up.y = +a0.x; \ } \ } #define CALC_TOP_SHARP(t) \ if(top_sharp) \ new_top = next_top = pos[1] + PointfToSize(up * double(t)); \ else \ { \ new_top = pos[1] + PointfToSize(at * double(t)); \ next_top = pos[1] + PointfToSize(bt * double(t)); \ } #define CALC_TOP_ROUND(t) \ if(!top_sharp) \ next_top = new_top = pos[1] + PointfToSize(up * double(t)); \ else \ { \ new_top.x = pos[1].x - fround((t) * a0.y); \ new_top.y = pos[1].y + fround((t) * a0.x); \ next_top.x = pos[1].x - fround((t) * b0.y); \ next_top.y = pos[1].y + fround((t) * b0.x); \ if(miter == PathStyle::MITER_ROUND) \ { \ double ht = tabs((t)) - sqrt(max(0.0, (t) * (t) - Squared(next_top - new_top) * 0.25)); \ GetArcPath(arc_path, new_top, next_top, (t) >= 0 ? ht : -ht); \ } \ } #define DRAW_TOP_SHARP(ln, ot) \ ln(out, ot, new_top); \ ln(out, new_top, next_top); #define DRAW_TOP_ROUND(ln, ot) \ ln(out, ot, new_top); \ if(!arc_path.IsEmpty()) \ out.AddThick(ln, new_top, arc_path); \ else \ ln(out, new_top, next_top); #define DRAW_BOTTOM_SHARP(b, col) \ bool bot_sharp = (!too_sharp || axb > 0 || (b) < 0); \ if(bot_sharp) \ new_bot = next_bot = pos[1] + PointfToSize(up * double(b)); \ else \ { \ new_bot = pos[1] + PointfToSize(at * double(b)); \ next_bot = pos[1] + PointfToSize(bt * double(b)); \ } \ poly[px++] = new_top; \ if(too_sharp) \ { \ poly[px++] = next_top; \ poly[px++] = next_bot; \ } \ poly[px++] = new_bot; \ DrawPolygon(*draw, poly, px, col, 0, Null, 0); #define DRAW_BOTTOM_ROUND(b, col) \ if(top_sharp && arc_path.IsEmpty()) \ arc_path.Add(next_top); \ new_bot.x = pos[1].x - fround(b * a0.y); \ new_bot.y = pos[1].y + fround(b * a0.x); \ if(end || too_sharp) \ arc_path.Add(new_bot); \ else if((b < 0) == (axb < 0)) \ arc_path.Add(next_bot = pos[1] + PointfToSize(up * double(b))); \ else \ { \ next_bot.x = pos[1].x - fround(b * b0.y); \ next_bot.y = pos[1].y + fround(b * b0.x); \ arc_path.Add(next_bot); \ double ht = tabs(b) - sqrt(max(0.0, b * b - Squared(next_bot - new_bot) * 0.25)); \ GetArcPath(arc_path, next_bot, new_bot, b <= 0 ? ht : -ht); \ } \ if(px) \ { \ int pos = arc_path.GetCount(); \ arc_path.InsertN(pos, px); \ Copy(&arc_path[pos], poly, poly + px); \ } \ arc_path.Add(new_top); \ DrawPolygon(*draw, arc_path, col, 0, Null, 0); enum { LEADER_DELTA = 0 }; #define CALC_SHARP_LEADER(dest, y, ix) \ { \ bool ll = (!start && (ix) <= (y) * old_ulen - LEADER_DELTA); \ bool rl = (!end && atotal - (ix) <= (y) * ulen - LEADER_DELTA); \ if(ll && rl) \ dest = pos[2] + PointfToSize(old_up * double(atotal / (old_ulen + ulen))); \ else if(ll) \ dest = pos[2] + PointfToSize(old_up * double(y)); \ else if(rl) \ dest = pos[1] + PointfToSize(up * double(y)); \ else \ dest = pos[2] + PointfToSize(a0 * double(ix) + ar * double(y)); \ } #define CALC_START_TOP_LEADER() \ Point old_top; \ old_top = pos[1] - (apart + ar * t.left_top).AsSize(); /* bool start_top_leader = (!start && old_axb_left == (t.left_top > 0) && atotal - al <= t.left_top * old_ulen - LEADER_DELTA); \ if(start_top_leader) \ old_top = pos[1] - (apart + old_up * t.left_top).AsSize(); \ else \ old_top = pos[1] - (apart + ar * t.left_top).AsSize(); \ */ #define CALC_START_BOTTOM_LEADER() \ poly[px++] = pos[1] - (apart + ar * t.left_bottom).AsSize(); \ poly[px++] = old_top; /* bool start_bot_leader = (!start && old_axb_left == (t.left_bottom > 0) && atotal - al <= t.left_bottom * old_ulen - LEADER_DELTA); \ if(start_bot_leader) \ poly[px++] = pos[1] - (apart + old_up * t.left_bottom).AsSize(); \ else \ poly[px++] = pos[1] - (apart + ar * t.left_bottom).AsSize(); \ if(t.left_ground && start_bot_leader != start_top_leader) \ poly[px++] = pos[1] - apart.AsSize(); \ poly[px++] = old_top; */ #define CALC_END_TOP_LEADER(rem) \ Point new_top; \ new_top = pos[1] - (bpart + ar * t.right_top).AsSize(); /* bool end_top_leader = (!end && (axb < 0) == (t.right_top > 0) && al - (rem) <= t.right_top * ulen - LEADER_DELTA); \ if(end_top_leader) \ new_top = pos[1] - (bpart + up * t.right_top).AsSize(); \ else \ new_top = pos[1] - (bpart + ar * t.right_top).AsSize(); */ #define CALC_END_BOTTOM_LEADER(rem) \ poly[px++] = new_top; \ poly[px++] = pos[1] - (bpart + ar * t.right_bottom).AsSize(); /* bool end_bot_leader = (!end && (axb < 0) == (t.right_bottom > 0) && al - (rem) <= t.right_bottom * ulen - LEADER_DELTA); \ if(t.right_ground && end_top_leader != end_bot_leader) \ poly[px++] = pos[1] - bpart.AsSize(); \ if(end_bot_leader) \ poly[px++] = pos[1] - (bpart + up * t.right_bottom).AsSize(); \ else \ poly[px++] = pos[1] - (bpart + ar * t.right_bottom).AsSize(); */ void PathDraw::CalcLineBegin() { CALC_MITER(); for(int i = 0; i < segment_lines.GetCount(); i++) { Linear& sl = segment_lines[i]; bool top_sharp = (!too_sharp || axb < 0 || sl.top > 0); sl.last_top = pos[1] + PointfToSize((top_sharp ? up : bt) * double(sl.top)); bool bot_sharp = (!too_sharp || axb > 0 || sl.bottom < 0); sl.last_bottom = pos[1] + PointfToSize((bot_sharp ? up : bt) * double(sl.bottom)); } old_up = up; old_ulen = ulen; old_axb_left = (axb < 0); } bool PathDraw::DoBegin() { if(IsNull(pos[2])) return false; Split split = SplitLine(); if((part_total += split.total) >= seg.length) { // break segment Point p0 = pos[0], p1 = pos[1], p2 = pos[2], p3 = pos[3], px; double t = minmax(split.GetEndPos(part_total - seg.length), 0, 1); pos[1].x = pos[2].x + fround((pos[1].x - pos[2].x) * t); pos[1].y = pos[2].y + fround((pos[1].y - pos[2].y) * t); px = pos[1]; if(pos[1] != pos[2]) { pos[0] = (px == p1 ? p0 : p1); LineToRaw(); } Restart(); pos[3] = Null; pos[0] = p1; bool end_seg = (px != p1); if(end_seg) { pos[1] = px; pos[2] = (px == p2 ? Null : p2); } else { pos[1] = p2; pos[2] = p3; } if(!seg_segment.IsEmpty() || !segment_lines.IsEmpty()) { SetSegment(seg_segment, PART_SEGMENT); CalcLineBegin(); } else if(!seg_end.IsEmpty()) part = PART_SKIPEND; else { empty = true; return false; } AddPos(p0); if(seg_end.length > 0) { if(!end_seg) split_pos[0] = pos[1]; else { split_pos[0] = pos[2]; AddSplit(); } } if(end_seg && part == PART_SEGMENT && seg_end.length <= 0) LineToRaw(); } else LineToRaw(); return true; } void PathDraw::LineToFull(Point p) { //RTIMING("PathDraw::LineToFull"); if(p == raw_pos[0] || empty) return; AddPos(p); if(part == PART_SEGMENT) { if(seg_end.length <= 0) { LineToRaw(); return; } else if(!IsNull(pos[2])) { AddSplit(); FlushSplit(seg_end.length); } return; } switch(part) { case PART_OPEN: if(!IsNull(pos[2])) { close_line[0] = pos[2]; close_line[1] = pos[1]; close_line[2] = pos[0]; part = PART_SEGMENT; CalcLineBegin(); } return; case PART_BEGIN: if(!DoBegin()) return; if(part != PART_SKIPEND) break; case PART_SKIPEND: { AddSplit(); FlushSplit(seg_end.length); } break; } } PathDraw::Split PathDraw::SplitLine() { ASSERT(pos[0] != pos[1] && pos[1] != pos[2] && pos[2] != pos[3]); if(IsNull(pos[2]) || IsNull(pos[1])) return Split(0, 0, 0, Null); CALC_MITER(); double pos_start = (start ? 0 : max(-(old_up ^ a0) * (old_axb_left ? seg.bottom : seg.top), 0)); double pos_end = (end ? 0 : max((up ^ a0) * (axb < 0 ? seg.bottom : seg.top), 0)); return Split(pos_start, pos_end, pos_end + pos_start + al, pos[1]); } void PathDraw::LineToRaw() { if(IsNull(pos[1]) || IsNull(pos[2])) return; ASSERT(part != PART_SKIPEND); ASSERT(pos[0] != pos[1] && pos[1] != pos[2] && pos[2] != pos[3]); CALC_MITER(); if(!traces.IsEmpty()) { double pos_start = (start ? 0 : (old_up ^ a0) * (old_axb_left ? seg.bottom : seg.top)); double pos_end = al + (end ? 0 : (up ^ a0) * (axb < 0 ? seg.bottom : seg.top)); double atotal = al, adelta = pos_end - pos_start, aindex = pos_start; for(int o = open_traces.GetCount(); --o >= 0;) { Trace& t = traces[open_traces[o]]; Output& out = outputs[t.index]; out.CheckFlush(); if(adelta <= t.width - t.pos) { // segment spans the whole line - draw joint double top = t.left_top + t.top_step * (t.pos += adelta); Point new_top, next_top; bool top_sharp = (!too_sharp || axb < 0 || t.left_top > 0); CALC_TOP_SHARP(top); if(t.hline) { // single line DRAW_TOP_SHARP(t.hline, t.last_top); } else { Point poly[6]; Point next_bot, new_bot; double bottom = t.left_bottom + t.bottom_step * t.pos; poly[0] = t.last_bottom; poly[1] = t.last_top; int px = 2; DRAW_BOTTOM_SHARP(bottom, t.color); t.last_bottom = next_bot; } t.last_top = next_top; /* double top = t.left_top + t.top_step * (t.pos += adelta); Point new_top = pos[1] + (top * up).AsSize(); if(t.hline) { out.AddFirst(t.last_top); out.AddNext(new_top); } else { double bottom = t.left_bottom + t.bottom_step * t.pos; Point poly[6]; poly[0] = t.last_bottom; poly[1] = t.last_top; Point new_bot = pos[1] + (up * bottom).AsSize(); poly[2] = new_top; poly[3] = new_bot; DrawPolygon(*draw, poly, 4, t.color, 0, Null, 0); t.last_bottom = new_bot; } t.last_top = new_top; */ } else { // finish segment double end_pos = pos_start + t.width - t.pos; Point new_top; CALC_SHARP_LEADER(new_top, t.right_top, end_pos); if(t.hline) t.hline(out, t.last_top, new_top); else { Point poly[6]; poly[0] = t.last_bottom; poly[1] = t.last_top; Point new_bot; CALC_SHARP_LEADER(new_bot, t.right_bottom, end_pos); poly[2] = new_top; poly[3] = new_bot; DrawPolygon(*draw, poly, 4, t.color, 0, Null, 0); } open_traces.Remove(o); } } while((aindex += skip_size) < pos_end) { double left = pos_end - aindex; int tix = next_index++; // bool skip = (done.Find(next_index) >= 0); Trace& t = traces[tix]; Output& out = outputs[t.index]; skip_size = (next_index == seg.end ? (part == PART_SEGMENT ? seg.length + traces[next_index = seg.start].start - t.start : 1e100) : traces[next_index].start - t.start); // if(skip) // continue; Point old_top; CALC_SHARP_LEADER(old_top, t.left_top, aindex); Point new_top; if(left >= t.width) { // full trace fits here CALC_SHARP_LEADER(new_top, t.right_top, aindex + t.width); if(t.hline) t.hline(out, old_top, new_top); else { Point poly[4]; CALC_SHARP_LEADER(poly[0], t.right_bottom, aindex + t.width); CALC_SHARP_LEADER(poly[1], t.left_bottom, aindex); poly[2] = old_top; poly[3] = new_top; DrawPolygon(*draw, poly, 4, t.color, 0, Null, 0); } } else { // split trace & add to open list double top = t.left_top + t.top_step * (t.pos = left); Point new_top, next_top; bool top_sharp = (!too_sharp || axb < 0 || t.left_top > 0); CALC_TOP_SHARP(top); if(t.hline) { // single line DRAW_TOP_SHARP(t.hline, old_top); } else { Point poly[6]; Point next_bot, new_bot; double bottom = t.left_bottom + t.bottom_step * t.pos; CALC_SHARP_LEADER(poly[0], t.left_bottom, aindex); poly[1] = old_top; int px = 2; DRAW_BOTTOM_SHARP(bottom, t.color); t.last_bottom = next_bot; } t.last_top = next_top; /* Point new_top = pos[1] + (up * top).AsSize(); if(t.hline) { out.AddFirst(old_top); out.AddNext(new_top); } else { Point poly[4]; double bottom = t.left_bottom + t.bottom_step * t.pos; poly[0] = pos[1] + (up * bottom).AsSize(); CALC_SHARP_LEADER(poly[1], t.left_bottom, aindex); poly[2] = old_top; poly[3] = new_top; DrawPolygon(*draw, poly, 4, t.color, 0, Null, 0); t.last_bottom = poly[0]; } t.last_top = new_top; */ open_traces.Add(tix); } #ifdef DEBUG_DRAW FlushLines(); GdiFlush(); #endif } skip_size = aindex - pos_end; } /* bool try_rep = (al >= 2 * segment); for(int o = open_traces.GetCount(); --o >= 0;) { int x = open_traces[o]; // done.Add(x); Trace& t = traces[x]; Output& out = outputs[t.index]; out.CheckFlush(); if(al <= t.width - t.pos) { // segment spans the whole line - draw joint double top = t.left_top + t.top_step * (t.pos += al); Point new_top, next_top; if(miter == PathStyle::MITER_SHARP) { bool top_sharp = (!too_sharp || (axb < 0) == (top >= 0)); CALC_TOP_SHARP(top) if(t.hline) { DRAW_TOP_SHARP(t.hline, t.last_top); } else { Point new_bot, next_bot; double bottom = t.left_bottom + t.bottom_step * t.pos; Point poly[6]; poly[0] = t.last_bottom; poly[1] = t.last_top; int px = 2; DRAW_BOTTOM_SHARP(bottom, t.color); t.last_bottom = next_bot; } } else { ASSERT(miter == PathStyle::MITER_ROUND || miter == PathStyle::MITER_FLAT); Vector arc_path; bool top_sharp = (!end && (axb < 0) != (top >= 0)); CALC_TOP_ROUND(top) if(t.hline) { DRAW_TOP_ROUND(t.last_top); } else { Point poly[2]; poly[0] = t.last_bottom; poly[1] = t.last_top; enum { px = 2 }; Point new_bot, next_bot; double bottom = t.left_bottom + t.bottom_step * t.pos; DRAW_BOTTOM_ROUND(bottom, t.color); t.last_bottom = next_bot; } } t.last_top = next_top; } else { // segment is shorter, just calculate end & join double rem = t.width - t.pos; Pointf bpart = a0 * (al - rem); CALC_END_TOP_LEADER(rem); if(t.hline) { out.AddFirst(t.last_top); out.AddNext(new_top); } else { Point poly[8]; poly[0] = t.last_bottom; poly[1] = t.last_top; int px = 2; CALC_END_BOTTOM_LEADER(rem); DrawPolygon(*draw, poly, px, t.color, 0, Null, 0); } // if(al > segment - t.pos + 1) // { // repeat segment // // } open_traces.Remove(o); } #ifdef DEBUG_DRAW FlushLines(); GdiFlush(); #endif } double atotal = al; while(skip_size <= al) { al -= skip_size; int tix = next_index++; // bool skip = (done.Find(next_index) >= 0); Trace& t = traces[tix]; Output& out = outputs[t.index]; if(next_index == end_index) skip_size = (repeat ? segment + traces[next_index = seg_segment.start].start - t.start : 1e100); else skip_size = traces[next_index].start - t.start; // if(skip) // continue; Pointf apart = a0 * al; CALC_START_TOP_LEADER(); if(al >= t.width) { // full trace fits here Pointf bpart = a0 * (al - t.width); CALC_END_TOP_LEADER(t.width); if(t.hline) { out.AddFirst(old_top); out.AddNext(new_top); } else { Point poly[6]; int px = 0; CALC_START_BOTTOM_LEADER(); CALC_END_BOTTOM_LEADER(t.width); DrawPolygon(*draw, poly, px, t.color, 0, Null, 0); } } else { // split trace & add to open list double top = t.left_top + t.top_step * (t.pos = al); Point new_top, next_top; if(miter == PathStyle::MITER_SHARP) { bool top_sharp = (!too_sharp || (axb < 0) == (top >= 0)); CALC_TOP_SHARP(top); if(t.hline) { DRAW_TOP_SHARP(t.hline, old_top); } else { Point poly[8]; int px = 0; CALC_START_BOTTOM_LEADER(); double bottom = t.left_bottom + t.bottom_step * t.pos; Point new_bot, next_bot; DRAW_BOTTOM_SHARP(bottom, t.color); t.last_bottom = next_bot; } } else { ASSERT(miter == PathStyle::MITER_ROUND || miter == PathStyle::MITER_FLAT); Vector arc_path; bool top_sharp = (!end && (axb < 0) != (top >= 0)); CALC_TOP_ROUND(top); if(t.hline) { DRAW_TOP_ROUND(old_top); } else { Point poly[4]; int px = 0; CALC_START_BOTTOM_LEADER(); double bottom = t.left_bottom + t.bottom_step * t.pos; Point new_bot, next_bot; DRAW_BOTTOM_ROUND(bottom, t.color); t.last_bottom = next_bot; } } t.last_top = next_top; open_traces.Add(tix); } #ifdef DEBUG_DRAW FlushLines(); GdiFlush(); #endif } skip_size -= al; */ if(part == PART_SEGMENT) { for(int i = 0; i < segment_lines.GetCount(); i++) { Linear& sl = segment_lines[i]; Output& out = outputs[sl.index]; out.CheckFlush(); if(sl.ground) { // simple case sl.hline(out, pos[2], pos[1]); } else if(miter == PathStyle::MITER_SHARP) { // complex case Point old_top = start ? pos[2] + PointfToSize(ar * double(sl.top)) : sl.last_top; Point new_top, next_top; bool top_sharp = (!too_sharp || axb < 0 || sl.top > 0); CALC_TOP_SHARP(sl.top); if(sl.hline) { // single line DRAW_TOP_SHARP(sl.hline, old_top); } else { Point poly[6]; Point next_bot, new_bot; poly[0] = start ? pos[2] + PointfToSize(ar * double(sl.bottom)) : sl.last_bottom; poly[1] = old_top; int px = 2; DRAW_BOTTOM_SHARP(sl.bottom, sl.color); sl.last_bottom = next_bot; } sl.last_top = next_top; } else if(miter == PathStyle::MITER_ROUND) { Point new_top, next_top; Point old_top = start ? pos[2] + PointfToSize(ar * double(sl.top)) : sl.last_top; Vector arc_path; bool top_sharp = (!end && (axb < 0) == (sl.top >= 0)); CALC_TOP_ROUND(sl.top); if(sl.hline) { // single line DRAW_TOP_ROUND(sl.hline, old_top); } else { Point poly[2]; poly[0] = (start ? pos[2] + PointfToSize(ar * double(sl.bottom)) : sl.last_bottom); poly[1] = old_top; enum { px = 2 }; Point new_bot, next_bot; DRAW_BOTTOM_ROUND(sl.bottom, sl.color); sl.last_bottom = next_bot; } sl.last_top = next_top; } else if(miter == PathStyle::MITER_FLAT) { Point old_top = start ? pos[2] + PointfToSize(ar * double(sl.top)) : sl.last_top; Point new_top, next_top; bool top_sharp = (!end && (axb < 0) == (sl.top >= 0)); if(top_sharp) { new_top = pos[1] + PointfToSize(at * double(sl.top)); next_top = pos[1] + PointfToSize(bt * double(sl.top)); } else new_top = next_top = pos[1] + PointfToSize(up * double(sl.top)); if(sl.hline) { sl.hline(out, old_top, new_top); sl.hline(out, new_top, next_top); } else { Point poly[6]; poly[0] = (start ? pos[2] + PointfToSize(ar * double(sl.bottom)) : sl.last_bottom); poly[1] = old_top; poly[2] = new_top; int px = 3; if(top_sharp) poly[px++] = next_top; bool bot_sharp = (!too_sharp || axb > 0 || sl.bottom < 0); if(bot_sharp) { poly[px++] = sl.last_bottom = pos[1] + PointfToSize(bt * double(sl.bottom)); poly[px++] = pos[1] + PointfToSize(at * double(sl.bottom)); } else poly[px++] = sl.last_bottom = pos[1] + PointfToSize(up * double(sl.bottom)); DrawPolygon(*draw, poly, px, sl.color, 0, Null, 0); } sl.last_top = next_top; } else NEVER(); #ifdef DEBUG_DRAW FlushLines(); GdiFlush(); #endif } } if(calc_miter) { old_up = up; old_ulen = ulen; old_axb_left = (axb < 0); } } void PathDraw::Paint() { MoveTo(Null); FlushLines(); Restart(); } void PathDraw::FlushLines() { for(int i = 0; i < outputs.GetCount(); i++) outputs[i].Flush(); } void PathDraw::RecurseArc(Point next, int length, int bulge, int levels) { if(--levels < 0 || tabs(bulge) <= 1 || length <= 2) LineTo(next); // degenerate next line else { // bisect arc Size normal = iscale(Size(next.y - pos[0].y, pos[0].x - next.x), bulge, length); Point centre = ((pos[0] + next) >> 1) + normal; int ll = (int)Length(centre - next); int hh = iscale(bulge, ll, 2 * ll + length); RecurseArc(centre, ll, hh, levels); RecurseArc(next, ll, hh, levels); } } void PathDraw::ArcTo(Point pt, int bulge) { ASSERT(!IsNull(raw_pos[0])); RecurseArc(pt, (int)Length(pt - pos[0]), bulge, 10); } void PathDraw::Circle(Point pt, int radius) { MoveTo(pt.x - radius, pt.y); ArcTo(pt.x + radius, pt.y, radius); ArcTo(pt.x - radius, pt.y, radius); } PathStyleDisplay::PathStyleDisplay(const PathStyleMap *path_map) : path_map(path_map) {} PathStyleDisplay::~PathStyleDisplay() {} void PathStyleDisplay::Paint(Draw& draw, const Rect& rc, const Value& v, Color i, Color p, dword s) const { draw.DrawRect(rc, p); if(IsNull(v) || IsString(v)) { String s = v; PathStyle style = path_map->Scan(s); PathDraw pd; pd.Set(draw, style, i, rc.Height() >> 2, rc.Height() >> 2, false); int mid = (rc.top + rc.bottom) >> 1; pd.MoveTo(rc.left, mid); pd.LineTo(rc.right, mid); pd.Paint(); } } END_UPP_NAMESPACE